source: trunk/sources/HeuristicLab.CodeEditor/3.4/CodeEditor.cs @ 11825

Last change on this file since 11825 was 11825, checked in by jkarder, 6 years ago

#2077:

  • do not offer completion for inaccessible protected members
  • fixed go to line logic
  • fixed comment in CSharpScriptTemplate
File size: 20.5 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2014 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.CodeDom.Compiler;
24using System.Collections.Generic;
25using System.ComponentModel;
26using System.Reflection;
27using System.Threading.Tasks;
28using System.Windows.Documents;
29using HeuristicLab.Common;
30using ICSharpCode.AvalonEdit;
31using ICSharpCode.AvalonEdit.AddIn;
32using ICSharpCode.AvalonEdit.CodeCompletion;
33using ICSharpCode.AvalonEdit.Document;
34using ICSharpCode.AvalonEdit.Editing;
35using ICSharpCode.AvalonEdit.Highlighting;
36using ICSharpCode.AvalonEdit.Indentation.CSharp;
37using ICSharpCode.AvalonEdit.Search;
38using ICSharpCode.NRefactory.Editor;
39using ICSharpCode.NRefactory.TypeSystem;
40using ICSharpCode.SharpDevelop.Editor;
41using Forms = System.Windows.Forms;
42using Input = System.Windows.Input;
43using Media = System.Windows.Media;
44
45namespace HeuristicLab.CodeEditor {
46  public partial class CodeEditor : Forms.UserControl {
47    private static readonly Media.Color WarningColor = Media.Colors.Blue;
48    private static readonly Media.Color ErrorColor = Media.Colors.Red;
49    private static readonly Media.Color ReadOnlyColor = Media.Colors.Moccasin;
50
51    private const string DefaultDocumentFileName = "Document";
52    private const string DefaultTextEditorSyntaxHighlighting = "C#";
53    private const string DefaultTextEditorFontFamily = "Consolas";
54    private const double DefaultTextEditorFontSize = 13.0;
55    private const bool DefaultTextEditorShowLineNumbers = true;
56    private const bool DefaultTextEditorShowSpaces = true;
57    private const bool DefaultTextEditorShowTabs = true;
58    private const bool DefaultTextEditorConvertTabsToSpaces = true;
59    private const bool DefaultTextEditorHighlightCurrentLine = true;
60    private const int DefaultTextEditorIndentationSize = 2;
61
62    private AssemblyLoader assemblyLoader;
63    private ILanguageFeatures languageFeatures;
64    private TextMarkerService textMarkerService;
65
66    #region Properties
67    internal TextEditor TextEditor { get { return avalonEditWrapper.TextEditor; } }
68    internal Input.RoutedCommand CompletionCommand;
69    internal CompletionWindow CompletionWindow;
70    internal OverloadInsightWindow OverloadInsightWindow;
71
72    private TextDocument Doc { get { return TextEditor.Document; } }
73
74    private ITextMarker prefixMarker;
75    private string prefix = string.Empty;
76    public string Prefix {
77      get { return prefix; }
78      set {
79        if (value == null) value = string.Empty;
80        if (prefix == value) return;
81        if (prefixMarker != null) textMarkerService.Remove(prefixMarker);
82        Doc.Remove(0, prefix.Length);
83        prefix = value;
84        if (value.Length > 0) {
85          Doc.Insert(0, prefix);
86          prefixMarker = textMarkerService.Create(0, prefix.Length);
87          prefixMarker.BackgroundColor = ReadOnlyColor;
88        }
89      }
90    }
91
92    private ITextMarker suffixMarker;
93    private string suffix = string.Empty;
94    public string Suffix {
95      get { return suffix; }
96      set {
97        if (value == null) value = string.Empty;
98        if (suffix == value) return;
99        if (suffixMarker != null) textMarkerService.Remove(suffixMarker);
100        Doc.Remove(Doc.TextLength - suffix.Length, suffix.Length);
101        suffix = value;
102        if (value.Length > 0) {
103          int offset = Doc.TextLength;
104          Doc.Insert(offset, suffix);
105          suffixMarker = textMarkerService.Create(offset, suffix.Length);
106          suffixMarker.BackgroundColor = ReadOnlyColor;
107        }
108      }
109    }
110
111    public string UserCode {
112      get { return Doc.GetText(prefix.Length, Doc.TextLength - suffix.Length - prefix.Length); }
113      set {
114        if (Doc.Text == value) return;
115        Doc.Replace(prefix.Length, Doc.TextLength - suffix.Length - prefix.Length, value);
116      }
117    }
118
119    #region TextEditor
120    [DefaultValue(DefaultTextEditorSyntaxHighlighting)]
121    public string TextEditorSyntaxHighlighting {
122      get { return TextEditor.SyntaxHighlighting.Name; }
123      set {
124        TextEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition(value);
125        ApplyLanguageFeatures();
126      }
127    }
128
129    [DefaultValue(DefaultTextEditorShowLineNumbers)]
130    public bool TextEditorShowLineNumbers {
131      get { return TextEditor.ShowLineNumbers; }
132      set { TextEditor.ShowLineNumbers = value; }
133    }
134
135    [DefaultValue(DefaultTextEditorShowSpaces)]
136    public bool TextEditorShowSpaces {
137      get { return TextEditor.Options.ShowSpaces; }
138      set { TextEditor.Options.ShowSpaces = value; }
139    }
140
141    [DefaultValue(DefaultTextEditorShowTabs)]
142    public bool TextEditorShowTabs {
143      get { return TextEditor.Options.ShowTabs; }
144      set { TextEditor.Options.ShowTabs = value; }
145    }
146
147    [DefaultValue(DefaultTextEditorConvertTabsToSpaces)]
148    public bool TextEditorConvertTabsToSpaces {
149      get { return TextEditor.Options.ConvertTabsToSpaces; }
150      set { TextEditor.Options.ConvertTabsToSpaces = value; }
151    }
152
153    [DefaultValue(DefaultTextEditorHighlightCurrentLine)]
154    public bool TextEditorHighlightCurrentLine {
155      get { return TextEditor.Options.HighlightCurrentLine; }
156      set { TextEditor.Options.HighlightCurrentLine = value; }
157    }
158
159    [DefaultValue(DefaultTextEditorIndentationSize)]
160    public int TextEditorIndentationSize {
161      get { return TextEditor.Options.IndentationSize; }
162      set { TextEditor.Options.IndentationSize = value; }
163    }
164    #endregion
165
166    public bool ReadOnly {
167      get { return TextEditor.IsReadOnly; }
168      set { TextEditor.IsReadOnly = value; }
169    }
170    #endregion
171
172    public CodeEditor() {
173      InitializeComponent();
174      InitializeTextEditor();
175    }
176
177    private void InitializeTextEditor() {
178      #region AssemblyLoader
179      assemblyLoader = new AssemblyLoader();
180      assemblyLoader.AssembliesLoading += (sender, args) => OnAssembliesLoading(args.Value);
181      assemblyLoader.InternalAssembliesLoaded += (sender, args) => OnInternalAssembliesLoaded(args.Value);
182      assemblyLoader.AssembliesLoaded += (sender, args) => OnAssembliesLoaded(args.Value);
183      assemblyLoader.AssembliesUnloading += (sender, args) => OnAssembliesUnloading(args.Value);
184      assemblyLoader.InternalAssembliesUnloaded += (sender, args) => OnInternalAssembliesUnloaded(args.Value);
185      assemblyLoader.AssembliesUnloaded += (sender, args) => OnAssembliesUnloaded(args.Value);
186      #endregion
187
188      #region TextMarkerService
189      textMarkerService = new TextMarkerService(TextEditor.Document);
190      TextEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService);
191      TextEditor.TextArea.TextView.LineTransformers.Add(textMarkerService);
192      TextEditor.TextArea.TextView.Services.AddService(typeof(ITextMarkerService), textMarkerService);
193
194      #endregion
195
196      #region ReadOnlySectionProvider
197      TextEditor.TextArea.ReadOnlySectionProvider = new MethodDefinitionReadOnlySectionProvider(this);
198      #endregion
199
200      #region SearchPanel
201      SearchPanel.Install(TextEditor);
202      #endregion
203
204      #region CompletionCommand
205      CompletionCommand = new Input.RoutedCommand();
206      CompletionCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.Space, Input.ModifierKeys.Control));
207      #endregion
208
209      #region MoveLinesUpCommand
210      var moveLinesUpCommand = new Input.RoutedCommand();
211      moveLinesUpCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.Up, Input.ModifierKeys.Alt));
212      var moveLinesUpCommandBinding = new Input.CommandBinding(moveLinesUpCommand, (sender, args) => ExecuteMoveLinesCommand(MovementDirection.Up));
213      TextEditor.CommandBindings.Add(moveLinesUpCommandBinding);
214      #endregion
215
216      #region MoveLinesDownCommand
217      var moveLinesDownCommand = new Input.RoutedCommand();
218      moveLinesDownCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.Down, Input.ModifierKeys.Alt));
219      var moveLinesDownCommandBinding = new Input.CommandBinding(moveLinesDownCommand, (sender, args) => ExecuteMoveLinesCommand(MovementDirection.Down));
220      TextEditor.CommandBindings.Add(moveLinesDownCommandBinding);
221      #endregion
222
223      #region GoToLineCommand
224      var goToLineCommand = new Input.RoutedCommand();
225      goToLineCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.G, Input.ModifierKeys.Control));
226      var goToLineCommandBinding = new Input.CommandBinding(goToLineCommand, (sender, args) => ExecuteGoToLineCommand());
227      TextEditor.CommandBindings.Add(goToLineCommandBinding);
228      #endregion
229
230      TextEditorSyntaxHighlighting = DefaultTextEditorSyntaxHighlighting;
231      TextEditorShowLineNumbers = DefaultTextEditorShowLineNumbers;
232      TextEditorShowSpaces = DefaultTextEditorShowSpaces;
233      TextEditorShowTabs = DefaultTextEditorShowTabs;
234      TextEditorConvertTabsToSpaces = DefaultTextEditorConvertTabsToSpaces;
235      TextEditorHighlightCurrentLine = DefaultTextEditorHighlightCurrentLine;
236      TextEditorIndentationSize = DefaultTextEditorIndentationSize;
237
238      Doc.FileName = DefaultDocumentFileName;
239
240      TextEditor.FontFamily = new Media.FontFamily(DefaultTextEditorFontFamily);
241      TextEditor.FontSize = DefaultTextEditorFontSize;
242      TextEditor.Options.EnableVirtualSpace = true;
243      TextEditor.TextArea.IndentationStrategy = new CSharpIndentationStrategy(TextEditor.Options);
244
245      TextEditor.TextChanged += (sender, args) => {
246        textMarkerService.RemoveAll(x => x != prefixMarker && x != suffixMarker);
247        OnTextEditorTextChanged();
248      };
249    }
250
251    #region Assembly Management
252    public void AddAssembly(Assembly a) {
253      assemblyLoader.AddAssembly(a);
254    }
255
256    public void AddAssemblies(IEnumerable<Assembly> assemblies) {
257      assemblyLoader.AddAssemblies(assemblies);
258    }
259
260    public async Task AddAssembliesAsync(IEnumerable<Assembly> assemblies) {
261      await assemblyLoader.AddAssembliesAsync(assemblies);
262    }
263
264    public void RemoveAssembly(Assembly a) {
265      assemblyLoader.RemoveAssembly(a);
266    }
267    #endregion
268
269    public void ScrollToPosition(int line, int column) {
270      var segment = GetSegmentAtLocation(line, column);
271      TextEditor.CaretOffset = segment.Offset + segment.Length;
272      TextEditor.ScrollToLine(line);
273    }
274
275    public void ScrollAfterPrefix() {
276      var location = Doc.GetLocation(prefix.Length);
277      ScrollToPosition(location.Line, location.Column);
278    }
279
280    private enum MovementDirection { Up, Down }
281    private void ExecuteMoveLinesCommand(MovementDirection movementDirection) {
282      var textArea = TextEditor.TextArea;
283      var selection = textArea.Selection;
284      var caret = textArea.Caret;
285
286      var selectionStartPosition = selection.StartPosition;
287      var selectionEndPosition = selection.EndPosition;
288      var caretPosition = caret.Position;
289      int caretOffset = caret.Offset;
290
291      bool advancedPositionCalcualtion = selection is RectangleSelection || !selection.IsEmpty;
292
293      int selectionStartOffset, selectionEndOffset;
294      if (advancedPositionCalcualtion) {
295        if (selectionStartPosition.CompareTo(selectionEndPosition) > 0) {
296          var temp = selectionStartPosition;
297          selectionStartPosition = selectionEndPosition;
298          selectionEndPosition = temp;
299        }
300        selectionStartOffset = Doc.GetOffset(selectionStartPosition.Location);
301        selectionEndOffset = Doc.GetOffset(selectionEndPosition.Location);
302      } else {
303        selectionStartOffset = selectionEndOffset = TextEditor.SelectionStart;
304      }
305
306      int selectionLength = selection.Length;
307
308      var startLine = Doc.GetLineByOffset(selectionStartOffset);
309      var endLine = Doc.GetLineByOffset(selectionEndOffset);
310
311      if (selection.IsMultiline && selectionEndOffset == endLine.Offset) {
312        if (movementDirection == MovementDirection.Down && endLine.TotalLength == 0) return;
313        endLine = endLine.PreviousLine;
314      }
315
316      if (movementDirection == MovementDirection.Up && startLine.LineNumber == 1) return;
317      if (movementDirection == MovementDirection.Down && endLine.LineNumber == Doc.LineCount) return;
318
319      int startOffset = startLine.Offset;
320      string primaryText = Doc.GetText(startOffset, endLine.EndOffset - startOffset);
321      string primaryDelimiter = Doc.GetText(endLine.EndOffset, endLine.DelimiterLength);
322
323      var secondaryLine = movementDirection == MovementDirection.Up ? startLine.PreviousLine : endLine.NextLine;
324      string secondaryText = Doc.GetText(secondaryLine.Offset, secondaryLine.Length);
325      string secondaryDelimiter = Doc.GetText(secondaryLine.EndOffset, secondaryLine.DelimiterLength);
326
327      if (string.IsNullOrEmpty(primaryText + primaryDelimiter) || string.IsNullOrEmpty(secondaryText + secondaryDelimiter)) return;
328
329      if (movementDirection == MovementDirection.Up) {
330        string replacementText = primaryText + secondaryDelimiter + secondaryText + primaryDelimiter;
331        Doc.Replace(secondaryLine.Offset, replacementText.Length, replacementText);
332        int correctionLength = secondaryText.Length + secondaryDelimiter.Length;
333        selectionStartOffset -= correctionLength;
334        caretOffset -= correctionLength;
335      } else {
336        string replacementText = secondaryText + primaryDelimiter + primaryText + secondaryDelimiter;
337        Doc.Replace(startLine.Offset, replacementText.Length, replacementText);
338        int correctionLength = secondaryText.Length + primaryDelimiter.Length;
339        selectionStartOffset += correctionLength;
340        caretOffset += correctionLength;
341      }
342
343      if (advancedPositionCalcualtion) {
344        var newSelectionStartLocation = Doc.GetLocation(selectionStartOffset);
345        int selectionLineOffset = newSelectionStartLocation.Line - Math.Min(selectionStartPosition.Line, selectionEndPosition.Line);
346        selectionStartPosition.Line += selectionLineOffset;
347        selectionEndPosition.Line += selectionLineOffset;
348        if (selectionEndPosition.Line > Doc.LineCount) {
349          var newLocation = Doc.GetLocation(Doc.TextLength);
350          selectionEndPosition.Line = newLocation.Line;
351          selectionEndPosition.Column = newLocation.Column;
352          selectionEndPosition.VisualColumn = newLocation.Column - 1;
353          selectionEndPosition.IsAtEndOfLine = selectionStartPosition.IsAtEndOfLine; // actual value does not matter; needed for comparison
354        }
355
356        if (selectionStartPosition == selectionEndPosition)
357          textArea.ClearSelection();
358        else {
359          if (selection is RectangleSelection)
360            textArea.Selection = new RectangleSelection(textArea, selectionStartPosition, selectionEndPosition);
361          else
362            textArea.Selection = new SimpleSelection(textArea, selectionStartPosition, selectionEndPosition);
363        }
364      } else {
365        TextEditor.SelectionStart = selectionStartOffset;
366        TextEditor.SelectionLength = selectionLength;
367      }
368
369      var newCaretLocation = Doc.GetLocation(Math.Min(caretOffset, Doc.TextLength));
370      var newCaretPosition = new TextViewPosition(newCaretLocation);
371      if (caretPosition.VisualColumn > caretPosition.Column) {
372        newCaretPosition.VisualColumn = caretPosition.VisualColumn;
373      }
374      caret.Position = newCaretPosition;
375    }
376
377    private void ExecuteGoToLineCommand() {
378      using (var dlg = new GoToLineDialog(TextEditor)) {
379        var result = dlg.ShowDialog(this);
380        if (result == Forms.DialogResult.OK) {
381          int lineNumber = dlg.Line;
382          var line = Doc.GetLineByNumber(lineNumber);
383          int offset = line.Offset;
384          if (TextUtilities.GetLeadingWhitespace(Doc, line).Length > 0)
385            offset = TextUtilities.GetNextCaretPosition(Doc, offset, LogicalDirection.Forward, CaretPositioningMode.WordBorder);
386          TextEditor.CaretOffset = offset;
387        }
388      }
389    }
390
391    #region Compiler Errors
392    public void ShowCompileErrors(CompilerErrorCollection compilerErrors) {
393      if (compilerErrors == null) return;
394
395      textMarkerService.RemoveAll(x => x != prefixMarker && x != suffixMarker);
396
397      foreach (CompilerError error in compilerErrors) {
398        var startLocation = Doc.GetLocation(prefix.Length);
399        if (error.Line == 1) error.Column += startLocation.Column;
400        error.Line += startLocation.Line;
401        AddErrorMarker(error);
402      }
403    }
404
405    private void AddErrorMarker(CompilerError error) {
406      var segment = GetSegmentAtLocation(error.Line, error.Column);
407      var marker = textMarkerService.Create(segment.Offset, segment.Length);
408      marker.MarkerTypes = TextMarkerTypes.SquigglyUnderline;
409      marker.MarkerColor = error.IsWarning ? WarningColor : ErrorColor;
410    }
411
412    private ISegment GetSegmentAtLocation(int line, int column) {
413      line = Math.Max(Doc.GetLocation(prefix.Length).Line, line - 1);
414      line = Math.Min(Doc.GetLocation(Doc.TextLength - suffix.Length).Line, line);
415
416      var startOffset = Doc.GetOffset(line, column);
417      var lineEndOffset = Doc.GetLineByNumber(line).EndOffset;
418      var endOffset = TextUtilities.GetNextCaretPosition(Doc, startOffset, LogicalDirection.Forward, CaretPositioningMode.WordBorder);
419      if (endOffset < 0) endOffset = startOffset;
420      endOffset = Math.Min(lineEndOffset + 1, endOffset);
421
422      var segment = new TextSegment { StartOffset = startOffset, EndOffset = endOffset };
423
424      return segment;
425    }
426    #endregion
427
428    private void ApplyLanguageFeatures() {
429      switch (TextEditorSyntaxHighlighting) {
430        case "XML":
431          languageFeatures = new XmlLanguageFeatures(this);
432          break;
433        default:
434          languageFeatures = new CSharpLanguageFeatures(this);
435          break;
436      }
437    }
438
439    #region Events
440    public event EventHandler TextEditorTextChanged;
441    private void OnTextEditorTextChanged() {
442      var handler = TextEditorTextChanged;
443      if (handler != null) handler(this, EventArgs.Empty);
444    }
445
446    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesLoading;
447    private void OnAssembliesLoading(IEnumerable<Assembly> args) {
448      var handler = AssembliesLoading;
449      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
450    }
451
452    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesLoaded;
453    private void OnAssembliesLoaded(IEnumerable<Assembly> args) {
454      var handler = AssembliesLoaded;
455      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
456    }
457
458    public event EventHandler<EventArgs<IEnumerable<IUnresolvedAssembly>>> InternalAssembliesLoaded;
459    private void OnInternalAssembliesLoaded(IEnumerable<IUnresolvedAssembly> args) {
460      var handler = InternalAssembliesLoaded;
461      if (handler != null) handler(this, new EventArgs<IEnumerable<IUnresolvedAssembly>>(args));
462    }
463
464    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesUnloading;
465    private void OnAssembliesUnloading(IEnumerable<Assembly> args) {
466      var handler = AssembliesUnloading;
467      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
468    }
469
470    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesUnloaded;
471    private void OnAssembliesUnloaded(IEnumerable<Assembly> args) {
472      var handler = AssembliesUnloaded;
473      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
474    }
475
476    public event EventHandler<EventArgs<IEnumerable<IUnresolvedAssembly>>> InternalAssembliesUnloaded;
477    private void OnInternalAssembliesUnloaded(IEnumerable<IUnresolvedAssembly> args) {
478      var handler = InternalAssembliesUnloaded;
479      if (handler != null) handler(this, new EventArgs<IEnumerable<IUnresolvedAssembly>>(args));
480    }
481    #endregion
482  }
483}
Note: See TracBrowser for help on using the repository browser.