source: branches/2520_PersistenceReintegration/HeuristicLab.CodeEditor/3.4/CodeEditor.cs @ 16529

Last change on this file since 16529 was 16529, checked in by gkronber, 21 months ago

#2520: merged r16491:16528 from trunk to persistence branch

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