Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.CodeEditor/3.2/CodeEditor.cs @ 2701

Last change on this file since 2701 was 2673, checked in by epitzer, 15 years ago

use SharpDevelop's TextArea.KeyEventHandler to prevent editing before Prefix and after Suffix (#842)

File size: 14.5 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.Linq;
30using System.Collections.Generic;
31using System.Drawing;
32using System.Windows.Forms;
33using System.IO;
34using System.Threading;
35using HeuristicLab.Common.Resources;
36
37using NRefactory = ICSharpCode.NRefactory;
38using Dom = ICSharpCode.SharpDevelop.Dom;
39using ICSharpCode.TextEditor.Document;
40using System.CodeDom.Compiler;
41using ICSharpCode.TextEditor;
42using System.Reflection;
43using System.Diagnostics;
44
45namespace HeuristicLab.CodeEditor {
46
47  public partial class CodeEditor : UserControl {
48
49    #region Fields & Properties
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        Doc.Replace(prefix.Length, Doc.TextLength - suffix.Length - prefix.Length, value);
132        Doc.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
133        Doc.CommitUpdate();
134      }
135    }
136
137    #endregion
138
139    public event EventHandler TextEditorValidated;
140
141    protected void OnTextEditorValidated() {
142      if (TextEditorValidated != null)
143        TextEditorValidated(this, EventArgs.Empty);
144    }
145
146    public CodeEditor() {
147      InitializeComponent();
148
149      textEditor.ActiveTextAreaControl.TextEditorProperties.SupportReadOnlySegments = true;
150
151      textEditor.SetHighlighting("C#");
152      textEditor.ShowEOLMarkers = false;
153      textEditor.ShowInvalidLines = false;
154      HostCallbackImplementation.Register(this);
155      CodeCompletionKeyHandler.Attach(this, textEditor);
156      ToolTipProvider.Attach(this, textEditor);
157
158      projectContentRegistry = new Dom.ProjectContentRegistry(); // Default .NET 2.0 registry
159      projectContentRegistry.ActivatePersistence(Path.Combine(Path.GetTempPath(),
160                                                  "CSharpCodeCompletion"));
161      projectContent = new Dom.DefaultProjectContent();
162      projectContent.Language = Dom.LanguageProperties.CSharp;
163    }
164
165    protected override void OnLoad(EventArgs e) {
166      base.OnLoad(e);
167
168      if (DesignMode)
169        return;
170
171      textEditor.ActiveTextAreaControl.TextArea.KeyEventHandler += new ICSharpCode.TextEditor.KeyEventHandler(TextArea_KeyEventHandler);
172      textEditor.ActiveTextAreaControl.TextArea.DoProcessDialogKey += new DialogKeyProcessor(TextArea_DoProcessDialogKey);
173
174      parserThread = new Thread(ParserThread);
175      parserThread.IsBackground = true;
176      parserThread.Start();
177
178      textEditor.Validated += (s, a) => { OnTextEditorValidated(); };
179      InitializeImageList();
180    }
181
182    private void InitializeImageList() {
183      imageList1.Images.Clear();
184      imageList1.Images.Add("Icons.16x16.Class.png", VS2008ImageLibrary.Class);
185      imageList1.Images.Add("Icons.16x16.Method.png", VS2008ImageLibrary.Method);
186      imageList1.Images.Add("Icons.16x16.Property.png", VS2008ImageLibrary.Properties);
187      imageList1.Images.Add("Icons.16x16.Field.png", VS2008ImageLibrary.Field);
188      imageList1.Images.Add("Icons.16x16.Enum.png", VS2008ImageLibrary.Enum);
189      imageList1.Images.Add("Icons.16x16.NameSpace.png", VS2008ImageLibrary.Namespace);
190      imageList1.Images.Add("Icons.16x16.Event.png", VS2008ImageLibrary.Event);
191    }
192
193    #region keyboard handlers: filter input in read-only areas
194
195    bool TextArea_KeyEventHandler(char ch) {
196      int caret = textEditor.ActiveTextAreaControl.Caret.Offset;
197      return caret < prefix.Length || caret > Doc.TextLength - suffix.Length;
198    }
199
200    bool TextArea_DoProcessDialogKey(Keys keyData) {
201      if (keyData == Keys.Return) {
202        int caret = textEditor.ActiveTextAreaControl.Caret.Offset;
203        if (caret < prefix.Length ||
204            caret > Doc.TextLength - suffix.Length) {
205          return true;
206        }
207      }
208      return false;
209    }
210
211    #endregion
212
213    public void ScrollAfterPrefix() {
214      int lineNr = prefix != null ? Doc.OffsetToPosition(prefix.Length).Line : 0;
215      textEditor.ActiveTextAreaControl.JumpTo(lineNr + 1);
216    }
217
218    private List<TextMarker> errorMarkers = new List<TextMarker>();
219    private List<Bookmark> errorBookmarks = new List<Bookmark>();
220    public void ShowCompileErrors(CompilerErrorCollection compilerErrors, string filename) {
221      Doc.MarkerStrategy.RemoveAll(m => errorMarkers.Contains(m));
222      Doc.BookmarkManager.RemoveMarks(m => errorBookmarks.Contains(m));
223      errorMarkers.Clear();
224      errorBookmarks.Clear();
225      errorLabel.Text = "";
226      errorLabel.ToolTipText = null;
227      if (compilerErrors == null)
228        return;
229      foreach (CompilerError error in compilerErrors) {
230        if (!error.FileName.EndsWith(filename)) {
231          errorLabel.Text = "error in generated code";
232          errorLabel.ToolTipText = string.Format("{0}{1}:{2} -> {3}",
233            errorLabel.ToolTipText != null ? (errorLabel.ToolTipText + "\n\n") : "",
234            error.Line, error.Column,
235            error.ErrorText);
236          continue;
237        }
238        var startPosition = Doc.OffsetToPosition(prefix.Length);
239        if (error.Line == 1)
240          error.Column += startPosition.Column;
241        error.Line += startPosition.Line;
242        AddErrorMarker(error);
243        AddErrorBookmark(error);
244      }
245      Doc.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
246      Doc.CommitUpdate();
247    }
248
249    private void AddErrorMarker(CompilerError error) {
250      var segment = GetSegmentAtOffset(error.Line, error.Column);
251      Color color = error.IsWarning ? Color.Blue : Color.Red;
252      var marker = new TextMarker(segment.Offset, segment.Length, TextMarkerType.WaveLine, color) {
253        ToolTip = error.ErrorText,
254      };
255      errorMarkers.Add(marker);
256      Doc.MarkerStrategy.AddMarker(marker);
257    }
258
259    private void AddErrorBookmark(CompilerError error) {
260      var bookmark = new ErrorBookmark(Doc, new TextLocation(error.Column, error.Line - 1));
261      errorBookmarks.Add(bookmark);
262      Doc.BookmarkManager.AddMark(bookmark);
263    }
264
265    private AbstractSegment GetSegmentAtOffset(int lineNr, int columnNr) {
266      lineNr = Math.Max(Doc.OffsetToPosition(prefix.Length).Line, lineNr);
267      lineNr = Math.Min(Doc.OffsetToPosition(Doc.TextLength - suffix.Length).Line, lineNr);
268      var line = Doc.GetLineSegment(lineNr - 1);
269      columnNr = Math.Max(0, columnNr);
270      columnNr = Math.Min(line.Length, columnNr);
271      var word = line.GetWord(columnNr);
272      AbstractSegment segment = new AbstractSegment();
273      if (word != null) {
274        segment.Offset = line.Offset + word.Offset;
275        segment.Length = word.Length;
276      } else {
277        segment.Offset = line.Offset + columnNr - 1;
278        segment.Length = 1;
279      }
280      return segment;
281    }
282
283    private HashSet<Assembly> assemblies = new HashSet<Assembly>();
284    public IEnumerable<Assembly> ReferencedAssemblies {
285      get { return assemblies; }
286    }
287    public void AddAssembly(Assembly a) {
288      ShowMessage("Loading " + a.GetName().Name + "...");
289      if (assemblies.Contains(a))
290        return;
291      var reference = projectContentRegistry.GetProjectContentForReference(a.GetName().Name, a.Location);
292      projectContent.AddReferencedContent(reference);
293      assemblies.Add(a);
294      ShowMessage("Ready");
295    }
296    public void RemoveAssembly(Assembly a) {
297      ShowMessage("Unloading " + a.GetName().Name + "...");
298      if (!assemblies.Contains(a))
299        return;
300      var content = projectContentRegistry.GetExistingProjectContent(a.Location);
301      if (content != null) {
302        projectContent.ReferencedContents.Remove(content);
303        projectContentRegistry.UnloadProjectContent(content);
304      }
305      ShowMessage("Ready");
306    }
307
308    private void ParserThread() {
309      BeginInvoke(new MethodInvoker(delegate { parserThreadLabel.Text = "Loading mscorlib..."; }));
310      projectContent.AddReferencedContent(projectContentRegistry.Mscorlib);
311      ParseStep();
312      BeginInvoke(new MethodInvoker(delegate { parserThreadLabel.Text = "Ready"; }));
313      try {
314        while (!IsDisposed) {
315          ParseStep();
316          Thread.Sleep(2000);
317        }
318      } catch { }
319    }
320
321    private void ParseStep() {
322      string code = null;
323      Invoke(new MethodInvoker(delegate {
324        code = textEditor.Text;
325      }));
326      TextReader textReader = new StringReader(code);
327      Dom.ICompilationUnit newCompilationUnit;
328      NRefactory.SupportedLanguage supportedLanguage;
329      supportedLanguage = NRefactory.SupportedLanguage.CSharp;
330      using (NRefactory.IParser p = NRefactory.ParserFactory.CreateParser(supportedLanguage, textReader)) {
331        p.ParseMethodBodies = false;
332        p.Parse();
333        newCompilationUnit = ConvertCompilationUnit(p.CompilationUnit);
334      }
335      projectContent.UpdateCompilationUnit(lastCompilationUnit, newCompilationUnit, DummyFileName);
336      lastCompilationUnit = newCompilationUnit;
337      parseInformation.SetCompilationUnit(newCompilationUnit);
338    }
339
340    Dom.ICompilationUnit ConvertCompilationUnit(NRefactory.Ast.CompilationUnit cu) {
341      Dom.NRefactoryResolver.NRefactoryASTConvertVisitor converter;
342      converter = new Dom.NRefactoryResolver.NRefactoryASTConvertVisitor(projectContent);
343      cu.AcceptVisitor(converter, null);
344      return converter.Cu;
345    }
346
347    private void ShowMessage(string m) {
348      if (this.Handle == null)
349        return;
350      BeginInvoke(new Action(() => parserThreadLabel.Text = m));
351    }
352
353    private void toolStripStatusLabel1_Click(object sender, EventArgs e) {
354      var proc = new Process();
355      proc.StartInfo.FileName = sharpDevelopLabel.Tag.ToString();
356      proc.Start();
357    }
358
359    private void CodeEditor_Resize(object sender, EventArgs e) {
360      var textArea = textEditor.ActiveTextAreaControl.TextArea;
361      var vScrollBar = textEditor.ActiveTextAreaControl.VScrollBar;
362      var hScrollBar = textEditor.ActiveTextAreaControl.HScrollBar;
363
364      textArea.SuspendLayout();
365      textArea.Width = textEditor.Width - vScrollBar.Width;
366      textArea.Height = textEditor.Height - hScrollBar.Height;
367      textArea.ResumeLayout();
368
369      vScrollBar.SuspendLayout();
370      vScrollBar.Location = new Point(textArea.Width, 0);
371      vScrollBar.Height = textArea.Height;
372      vScrollBar.ResumeLayout();
373
374      hScrollBar.SuspendLayout();
375      hScrollBar.Location = new Point(0, textArea.Height);
376      hScrollBar.Width = textArea.Width;
377      hScrollBar.ResumeLayout();
378    }
379  }
380}
Note: See TracBrowser for help on using the repository browser.