Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.CodeEditor/3.3/CodeEditor.cs @ 11799

Last change on this file since 11799 was 11657, checked in by jkarder, 10 years ago

#2262: applied some of the changes suggested by swagner in comment:17:ticket:2262

  • added highlighting of current line
  • added error markers and bookmarks
  • fixed <Ctrl> + <Backspace> bug
  • minor code changes
File size: 16.2 KB
Line 
1// CSharp Editor Example with Code Completion
2// Copyright (c) 2006, Daniel Grunwald
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification, are
6// permitted provided that the following conditions are met:
7//
8// - Redistributions of source code must retain the above copyright notice, this list
9//   of conditions and the following disclaimer.
10//
11// - Redistributions in binary form must reproduce the above copyright notice, this list
12//   of conditions and the following disclaimer in the documentation and/or other materials
13//   provided with the distribution.
14//
15// - Neither the name of the ICSharpCode team nor the names of its contributors may be used to
16//   endorse or promote products derived from this software without specific prior written
17//   permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &AS IS& AND ANY EXPRESS
20// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28using System;
29using System.CodeDom.Compiler;
30using System.Collections.Generic;
31using System.Diagnostics;
32using System.Drawing;
33using System.IO;
34using System.Reflection;
35using System.Threading;
36using System.Windows.Forms;
37using HeuristicLab.Common.Resources;
38using ICSharpCode.TextEditor;
39using ICSharpCode.TextEditor.Document;
40using Dom = ICSharpCode.SharpDevelop.Dom;
41using NRefactory = ICSharpCode.NRefactory;
42
43namespace HeuristicLab.CodeEditor {
44
45  public partial class CodeEditor : UserControl {
46
47    #region Fields & Properties
48    private static Color WarningColor = Color.Blue;
49    private static Color ErrorColor = Color.Red;
50
51    internal Dom.ProjectContentRegistry projectContentRegistry;
52    internal Dom.DefaultProjectContent projectContent;
53    internal Dom.ParseInformation parseInformation = new Dom.ParseInformation();
54    Dom.ICompilationUnit lastCompilationUnit;
55    Thread parserThread;
56
57    /// <summary>
58    /// Many SharpDevelop.Dom methods take a file name, which is really just a unique identifier
59    /// for a file - Dom methods don't try to access code files on disk, so the file does not have
60    /// to exist.
61    /// SharpDevelop itself uses internal names of the kind "[randomId]/Class1.cs" to support
62    /// code-completion in unsaved files.
63    /// </summary>
64    public const string DummyFileName = "edited.cs";
65
66    private IDocument Doc {
67      get {
68        return textEditor.Document;
69      }
70    }
71
72    private string prefix = "";
73    private TextMarker prefixMarker =
74      new TextMarker(0, 1, TextMarkerType.SolidBlock, Color.LightGray) {
75        IsReadOnly = true,
76      };
77    public string Prefix {
78      get {
79        return prefix;
80      }
81      set {
82        if (value == null) value = "";
83        if (prefix == value) return;
84        Doc.MarkerStrategy.RemoveMarker(prefixMarker);
85        Doc.Remove(0, prefix.Length);
86        prefix = value;
87        if (value.Length > 0) {
88          Doc.Insert(0, value);
89          prefixMarker.Offset = 0;
90          prefixMarker.Length = prefix.Length;
91          Doc.MarkerStrategy.AddMarker(prefixMarker);
92        }
93        Doc.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
94        Doc.CommitUpdate();
95      }
96    }
97
98    private string suffix = "";
99    private TextMarker suffixMarker =
100      new TextMarker(0, 1, TextMarkerType.SolidBlock, Color.LightGray) {
101        IsReadOnly = true,
102      };
103    public string Suffix {
104      get {
105        return suffix;
106      }
107      set {
108        if (value == null) value = "";
109        if (suffix == value) return;
110        Doc.MarkerStrategy.RemoveMarker(suffixMarker);
111        Doc.Remove(Doc.TextLength - suffix.Length, suffix.Length);
112        suffix = value;
113        if (value.Length > 0) {
114          suffixMarker.Offset = Doc.TextLength;
115          Doc.Insert(Doc.TextLength, value);
116          suffixMarker.Length = suffix.Length;
117          Doc.MarkerStrategy.AddMarker(suffixMarker);
118        }
119        Doc.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
120        Doc.CommitUpdate();
121      }
122    }
123
124    public string UserCode {
125      get {
126        return Doc.GetText(
127          prefix.Length,
128          Doc.TextLength - suffix.Length - prefix.Length);
129      }
130      set {
131        if (Doc.TextContent == value) return;
132        Doc.Replace(prefix.Length, Doc.TextLength - suffix.Length - prefix.Length, value);
133        Doc.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
134        Doc.CommitUpdate();
135      }
136    }
137
138    public bool ReadOnly {
139      get { return Doc.ReadOnly; }
140      set { Doc.ReadOnly = value; }
141    }
142
143    #endregion
144
145    public event EventHandler TextEditorValidated;
146    protected void OnTextEditorValidated() {
147      if (TextEditorValidated != null)
148        TextEditorValidated(this, EventArgs.Empty);
149    }
150
151    public event EventHandler TextEditorTextChanged;
152    protected void OnTextEditorTextChanged() {
153      if (TextEditorTextChanged != null)
154        TextEditorTextChanged(this, EventArgs.Empty);
155    }
156
157    public CodeEditor() {
158      InitializeComponent();
159
160      textEditor.ActiveTextAreaControl.TextEditorProperties.SupportReadOnlySegments = true;
161
162      LoadHighlightingStrategy();
163      HostCallbackImplementation.Register(this);
164      CodeCompletionKeyHandler.Attach(this, textEditor);
165      ToolTipProvider.Attach(this, textEditor);
166
167      projectContentRegistry = new Dom.ProjectContentRegistry(); // Default .NET 2.0 registry
168      try {
169        string persistencePath = Path.Combine(Path.GetTempPath(), "HeuristicLab.CodeEditor");
170        if (!Directory.Exists(persistencePath))
171          Directory.CreateDirectory(persistencePath);
172        FileStream fs = File.Create(Path.Combine(persistencePath, "test.tmp"));
173        fs.Close();
174        File.Delete(Path.Combine(persistencePath, "test.tmp"));
175        // if we made it this far, enable on-disk parsing cache
176        projectContentRegistry.ActivatePersistence(persistencePath);
177      } catch (NotSupportedException) {
178      } catch (IOException) {
179      } catch (System.Security.SecurityException) {
180      } catch (UnauthorizedAccessException) {
181      } catch (ArgumentException) {
182      }
183      projectContent = new Dom.DefaultProjectContent();
184      projectContent.Language = Dom.LanguageProperties.CSharp;
185    }
186
187    protected override void OnLoad(EventArgs e) {
188      base.OnLoad(e);
189
190      if (DesignMode)
191        return;
192
193      textEditor.ActiveTextAreaControl.TextArea.KeyEventHandler += TextArea_KeyEventHandler;
194      textEditor.ActiveTextAreaControl.TextArea.DoProcessDialogKey += TextArea_DoProcessDialogKey;
195
196      parserThread = new Thread(ParserThread) { IsBackground = true };
197      parserThread.Start();
198
199      textEditor.Validated += (s, a) => OnTextEditorValidated();
200      textEditor.TextChanged += (s, a) => {
201        Doc.MarkerStrategy.RemoveAll(m => errorMarkers.Contains(m)); errorMarkers.Clear();
202        Doc.BookmarkManager.RemoveMarks(m => errorBookmarks.Contains(m)); errorBookmarks.Clear();
203        Doc.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
204        Doc.CommitUpdate();
205        OnTextEditorTextChanged();
206      };
207      InitializeImageList();
208    }
209
210    private void LoadHighlightingStrategy() {
211      var strategy = (DefaultHighlightingStrategy)HighlightingManager.Manager.FindHighlighter("C#");
212      strategy.SetColorFor("CaretMarker", new HighlightColor(Color.Beige, false, false));
213      Doc.HighlightingStrategy = strategy;
214    }
215
216    private void InitializeImageList() {
217      imageList1.Images.Clear();
218      imageList1.Images.Add("Icons.16x16.Class.png", VSImageLibrary.Class);
219      imageList1.Images.Add("Icons.16x16.Method.png", VSImageLibrary.Method);
220      imageList1.Images.Add("Icons.16x16.Property.png", VSImageLibrary.Properties);
221      imageList1.Images.Add("Icons.16x16.Field.png", VSImageLibrary.Field);
222      imageList1.Images.Add("Icons.16x16.Enum.png", VSImageLibrary.Enum);
223      imageList1.Images.Add("Icons.16x16.NameSpace.png", VSImageLibrary.Namespace);
224      imageList1.Images.Add("Icons.16x16.Event.png", VSImageLibrary.Event);
225    }
226
227    #region keyboard handlers: filter input in read-only areas
228    bool TextArea_KeyEventHandler(char ch) {
229      int caret = textEditor.ActiveTextAreaControl.Caret.Offset;
230      return caret < prefix.Length || caret > Doc.TextLength - suffix.Length;
231    }
232
233    bool TextArea_DoProcessDialogKey(Keys keyData) {
234      if (keyData == Keys.Return) {
235        int caret = textEditor.ActiveTextAreaControl.Caret.Offset;
236        if (caret < prefix.Length ||
237            caret > Doc.TextLength - suffix.Length) {
238          return true;
239        }
240      }
241      return false;
242    }
243    #endregion
244
245    public void ScrollAfterPrefix() {
246      int lineNr = prefix != null ? Doc.OffsetToPosition(prefix.Length).Line : 0;
247      textEditor.ActiveTextAreaControl.JumpTo(lineNr + 1);
248    }
249
250    public void ScrollToPosition(int line, int column) {
251      var segment = GetSegmentAtOffset(line, column);
252      var position = Doc.OffsetToPosition(segment.Offset + segment.Length);
253      var caret = textEditor.ActiveTextAreaControl.Caret;
254      caret.Position = position;
255      textEditor.ActiveTextAreaControl.CenterViewOn(line, 0);
256    }
257
258    private List<TextMarker> errorMarkers = new List<TextMarker>();
259    private List<Bookmark> errorBookmarks = new List<Bookmark>();
260    public void ShowCompileErrors(CompilerErrorCollection compilerErrors, string filename) {
261      errorLabel.Text = "";
262      errorLabel.ToolTipText = null;
263      if (compilerErrors == null)
264        return;
265      foreach (CompilerError error in compilerErrors) {
266        if (!error.FileName.EndsWith(filename)) {
267          errorLabel.Text = "Error in generated code";
268          errorLabel.ToolTipText = string.Format("{0}{1}:{2} -> {3}",
269            errorLabel.ToolTipText != null ? (errorLabel.ToolTipText + "\n\n") : "",
270            error.Line, error.Column,
271            error.ErrorText);
272          continue;
273        }
274        var startPosition = Doc.OffsetToPosition(prefix.Length);
275        if (error.Line == 1)
276          error.Column += startPosition.Column;
277        error.Line += startPosition.Line;
278        AddErrorMarker(error);
279        AddErrorBookmark(error);
280      }
281      Doc.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
282      Doc.CommitUpdate();
283    }
284
285    private void AddErrorMarker(CompilerError error) {
286      var segment = GetSegmentAtOffset(error.Line, error.Column);
287      Color color = error.IsWarning ? WarningColor : ErrorColor;
288      var marker = new TextMarker(segment.Offset, segment.Length, TextMarkerType.WaveLine, color) {
289        ToolTip = error.ErrorText,
290      };
291      errorMarkers.Add(marker);
292      Doc.MarkerStrategy.AddMarker(marker);
293    }
294
295    private void AddErrorBookmark(CompilerError error) {
296      Color color = error.IsWarning ? WarningColor : ErrorColor;
297      var bookmark = new ErrorBookmark(Doc, new TextLocation(error.Column, error.Line - 1), color);
298      errorBookmarks.Add(bookmark);
299      Doc.BookmarkManager.AddMark(bookmark);
300    }
301
302    private AbstractSegment GetSegmentAtOffset(int lineNr, int columnNr) {
303      lineNr = Math.Max(Doc.OffsetToPosition(prefix.Length).Line, lineNr - 1);
304      lineNr = Math.Min(Doc.OffsetToPosition(Doc.TextLength - suffix.Length).Line, lineNr);
305      var line = Doc.GetLineSegment(lineNr);
306      columnNr = Math.Max(0, columnNr - 1);
307      columnNr = Math.Min(line.Length, columnNr);
308      var word = line.GetWord(columnNr);
309      AbstractSegment segment = new AbstractSegment();
310      if (word != null) {
311        segment.Offset = line.Offset + word.Offset;
312        segment.Length = word.Length;
313      } else {
314        segment.Offset = line.Offset + columnNr;
315        segment.Length = 1;
316      }
317      return segment;
318    }
319
320    private HashSet<Assembly> assemblies = new HashSet<Assembly>();
321    public IEnumerable<Assembly> ReferencedAssemblies {
322      get { return assemblies; }
323    }
324    public void AddAssembly(Assembly a) {
325      ShowMessage("Loading " + a.GetName().Name + "...");
326      if (!assemblies.Contains(a)) {
327        var reference = projectContentRegistry.GetProjectContentForReference(a.GetName().Name, a.Location);
328        projectContent.AddReferencedContent(reference);
329        assemblies.Add(a);
330      }
331      ShowMessage("Ready");
332    }
333    public void RemoveAssembly(Assembly a) {
334      ShowMessage("Unloading " + a.GetName().Name + "...");
335      if (assemblies.Contains(a)) {
336        var content = projectContentRegistry.GetExistingProjectContent(a.Location);
337        if (content != null) {
338          projectContent.ReferencedContents.Remove(content);
339          projectContentRegistry.UnloadProjectContent(content);
340        }
341      }
342      ShowMessage("Ready");
343    }
344
345    private bool runParser = true;
346    private void ParserThread() {
347      BeginInvoke(new MethodInvoker(delegate { parserThreadLabel.Text = "Loading mscorlib..."; }));
348      projectContent.AddReferencedContent(projectContentRegistry.Mscorlib);
349      ParseStep();
350      BeginInvoke(new MethodInvoker(delegate { parserThreadLabel.Text = "Ready"; }));
351      while (runParser && !IsDisposed) {
352        ParseStep();
353        Thread.Sleep(2000);
354      }
355    }
356
357    private void ParseStep() {
358      try {
359        string code = null;
360        Invoke(new MethodInvoker(delegate { code = textEditor.Text; }));
361        TextReader textReader = new StringReader(code);
362        Dom.ICompilationUnit newCompilationUnit;
363        NRefactory.SupportedLanguage supportedLanguage;
364        supportedLanguage = NRefactory.SupportedLanguage.CSharp;
365        using (NRefactory.IParser p = NRefactory.ParserFactory.CreateParser(supportedLanguage, textReader)) {
366          p.ParseMethodBodies = false;
367          p.Parse();
368          newCompilationUnit = ConvertCompilationUnit(p.CompilationUnit);
369        }
370        projectContent.UpdateCompilationUnit(lastCompilationUnit, newCompilationUnit, DummyFileName);
371        lastCompilationUnit = newCompilationUnit;
372        parseInformation.SetCompilationUnit(newCompilationUnit);
373      } catch { }
374    }
375
376    Dom.ICompilationUnit ConvertCompilationUnit(NRefactory.Ast.CompilationUnit cu) {
377      Dom.NRefactoryResolver.NRefactoryASTConvertVisitor converter;
378      converter = new Dom.NRefactoryResolver.NRefactoryASTConvertVisitor(projectContent);
379      cu.AcceptVisitor(converter, null);
380      return converter.Cu;
381    }
382
383    private void ShowMessage(string m) {
384      if (this.Handle == null)
385        return;
386      BeginInvoke(new Action(() => parserThreadLabel.Text = m));
387    }
388
389    private void toolStripStatusLabel1_Click(object sender, EventArgs e) {
390      var proc = new Process();
391      proc.StartInfo.FileName = sharpDevelopLabel.Tag.ToString();
392      proc.Start();
393    }
394
395    private void CodeEditor_Resize(object sender, EventArgs e) {
396      var textArea = textEditor.ActiveTextAreaControl.TextArea;
397      var vScrollBar = textEditor.ActiveTextAreaControl.VScrollBar;
398      var hScrollBar = textEditor.ActiveTextAreaControl.HScrollBar;
399
400      textArea.SuspendLayout();
401      textArea.Width = textEditor.Width - vScrollBar.Width;
402      textArea.Height = textEditor.Height - hScrollBar.Height;
403      textArea.ResumeLayout();
404
405      vScrollBar.SuspendLayout();
406      vScrollBar.Location = new Point(textArea.Width, 0);
407      vScrollBar.Height = textArea.Height;
408      vScrollBar.ResumeLayout();
409
410      hScrollBar.SuspendLayout();
411      hScrollBar.Location = new Point(0, textArea.Height);
412      hScrollBar.Width = textArea.Width;
413      hScrollBar.ResumeLayout();
414    }
415  }
416}
Note: See TracBrowser for help on using the repository browser.