source: stable/HeuristicLab.CodeEditor/3.4/CodeEditor.cs @ 11937

Last change on this file since 11937 was 11937, checked in by jkarder, 7 years ago

#2077: merged r11807:11811, r11816, r11819, r11822, r11825, r11834, r11835, r11836, r11933 and r11936 into stable

File size: 20.8 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 TextMarkerService textMarkerService;
64
65    #region Properties
66    internal TextEditor TextEditor { get { return avalonEditWrapper.TextEditor; } }
67    internal Input.RoutedCommand CompletionCommand;
68    internal CompletionWindow CompletionWindow;
69    internal OverloadInsightWindow OverloadInsightWindow;
70
71    private TextDocument Doc { get { return TextEditor.Document; } }
72
73    private ITextMarker prefixMarker;
74    private string prefix = string.Empty;
75    public string Prefix {
76      get { return prefix; }
77      set {
78        if (value == null) value = string.Empty;
79        if (prefix == value) return;
80        if (prefixMarker != null) prefixMarker.Delete();
81        Doc.Remove(0, prefix.Length);
82        prefix = value;
83        if (value.Length > 0) {
84          Doc.Insert(0, prefix);
85          prefixMarker = textMarkerService.Create(0, prefix.Length);
86          prefixMarker.BackgroundColor = ReadOnlyColor;
87        }
88      }
89    }
90
91    private ITextMarker suffixMarker;
92    private string suffix = string.Empty;
93    public string Suffix {
94      get { return suffix; }
95      set {
96        if (value == null) value = string.Empty;
97        if (suffix == value) return;
98        if (suffixMarker != null) suffixMarker.Delete();
99        Doc.Remove(Doc.TextLength - suffix.Length, suffix.Length);
100        suffix = value;
101        if (value.Length > 0) {
102          int offset = Doc.TextLength;
103          Doc.Insert(offset, suffix);
104          suffixMarker = textMarkerService.Create(offset, suffix.Length);
105          suffixMarker.BackgroundColor = ReadOnlyColor;
106        }
107      }
108    }
109
110    public string UserCode {
111      get { return Doc.GetText(prefix.Length, Doc.TextLength - suffix.Length - prefix.Length); }
112      set {
113        if (Doc.Text == value) return;
114        Doc.Replace(prefix.Length, Doc.TextLength - suffix.Length - prefix.Length, 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 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 void AddAssembly(Assembly a) {
262      assemblyLoader.AddAssembly(a);
263    }
264
265    public void AddAssemblies(IEnumerable<Assembly> assemblies) {
266      assemblyLoader.AddAssemblies(assemblies);
267    }
268
269    public async Task AddAssembliesAsync(IEnumerable<Assembly> assemblies) {
270      await assemblyLoader.AddAssembliesAsync(assemblies);
271    }
272
273    public void RemoveAssembly(Assembly a) {
274      assemblyLoader.RemoveAssembly(a);
275    }
276    #endregion
277
278    public void ScrollToPosition(int line, int column) {
279      var segment = GetSegmentAtLocation(line, column);
280      TextEditor.CaretOffset = segment.Offset + segment.Length;
281      TextEditor.ScrollToLine(line);
282    }
283
284    public void ScrollAfterPrefix() {
285      var location = Doc.GetLocation(prefix.Length);
286      ScrollToPosition(location.Line, location.Column);
287    }
288
289    private enum MovementDirection { Up, Down }
290    private void ExecuteMoveLinesCommand(MovementDirection movementDirection) {
291      var textArea = TextEditor.TextArea;
292      var selection = textArea.Selection;
293      var caret = textArea.Caret;
294
295      var selectionStartPosition = selection.StartPosition;
296      var selectionEndPosition = selection.EndPosition;
297      var caretPosition = caret.Position;
298      int caretOffset = caret.Offset;
299
300      bool advancedPositionCalcualtion = selection is RectangleSelection || !selection.IsEmpty;
301
302      int selectionStartOffset, selectionEndOffset;
303      if (advancedPositionCalcualtion) {
304        if (selectionStartPosition.CompareTo(selectionEndPosition) > 0) {
305          var temp = selectionStartPosition;
306          selectionStartPosition = selectionEndPosition;
307          selectionEndPosition = temp;
308        }
309        selectionStartOffset = Doc.GetOffset(selectionStartPosition.Location);
310        selectionEndOffset = Doc.GetOffset(selectionEndPosition.Location);
311      } else {
312        selectionStartOffset = selectionEndOffset = TextEditor.SelectionStart;
313      }
314
315      int selectionLength = selection.Length;
316
317      var startLine = Doc.GetLineByOffset(selectionStartOffset);
318      var endLine = Doc.GetLineByOffset(selectionEndOffset);
319
320      if (selection.IsMultiline && selectionEndOffset == endLine.Offset) {
321        if (movementDirection == MovementDirection.Down && endLine.TotalLength == 0) return;
322        endLine = endLine.PreviousLine;
323      }
324
325      if (movementDirection == MovementDirection.Up && startLine.LineNumber == 1) return;
326      if (movementDirection == MovementDirection.Down && endLine.LineNumber == Doc.LineCount) return;
327
328      int startOffset = startLine.Offset;
329      string primaryText = Doc.GetText(startOffset, endLine.EndOffset - startOffset);
330      string primaryDelimiter = Doc.GetText(endLine.EndOffset, endLine.DelimiterLength);
331
332      var secondaryLine = movementDirection == MovementDirection.Up ? startLine.PreviousLine : endLine.NextLine;
333      string secondaryText = Doc.GetText(secondaryLine.Offset, secondaryLine.Length);
334      string secondaryDelimiter = Doc.GetText(secondaryLine.EndOffset, secondaryLine.DelimiterLength);
335
336      if (string.IsNullOrEmpty(primaryText + primaryDelimiter) || string.IsNullOrEmpty(secondaryText + secondaryDelimiter)) return;
337
338      if (movementDirection == MovementDirection.Up) {
339        string replacementText = primaryText + secondaryDelimiter + secondaryText + primaryDelimiter;
340        Doc.Replace(secondaryLine.Offset, replacementText.Length, replacementText);
341        int correctionLength = secondaryText.Length + secondaryDelimiter.Length;
342        selectionStartOffset -= correctionLength;
343        caretOffset -= correctionLength;
344      } else {
345        string replacementText = secondaryText + primaryDelimiter + primaryText + secondaryDelimiter;
346        Doc.Replace(startLine.Offset, replacementText.Length, replacementText);
347        int correctionLength = secondaryText.Length + primaryDelimiter.Length;
348        selectionStartOffset += correctionLength;
349        caretOffset += correctionLength;
350      }
351
352      if (advancedPositionCalcualtion) {
353        var newSelectionStartLocation = Doc.GetLocation(selectionStartOffset);
354        int selectionLineOffset = newSelectionStartLocation.Line - Math.Min(selectionStartPosition.Line, selectionEndPosition.Line);
355        selectionStartPosition.Line += selectionLineOffset;
356        selectionEndPosition.Line += selectionLineOffset;
357        if (selectionEndPosition.Line > Doc.LineCount) {
358          var newLocation = Doc.GetLocation(Doc.TextLength);
359          selectionEndPosition.Line = newLocation.Line;
360          selectionEndPosition.Column = newLocation.Column;
361          selectionEndPosition.VisualColumn = newLocation.Column - 1;
362          selectionEndPosition.IsAtEndOfLine = selectionStartPosition.IsAtEndOfLine; // actual value does not matter; needed for comparison
363        }
364
365        if (selectionStartPosition == selectionEndPosition)
366          textArea.ClearSelection();
367        else {
368          if (selection is RectangleSelection)
369            textArea.Selection = new RectangleSelection(textArea, selectionStartPosition, selectionEndPosition);
370          else
371            textArea.Selection = new SimpleSelection(textArea, selectionStartPosition, selectionEndPosition);
372        }
373      } else {
374        TextEditor.SelectionStart = selectionStartOffset;
375        TextEditor.SelectionLength = selectionLength;
376      }
377
378      var newCaretLocation = Doc.GetLocation(Math.Min(caretOffset, Doc.TextLength));
379      var newCaretPosition = new TextViewPosition(newCaretLocation);
380      if (caretPosition.VisualColumn > caretPosition.Column) {
381        newCaretPosition.VisualColumn = caretPosition.VisualColumn;
382      }
383      caret.Position = newCaretPosition;
384    }
385
386    private void ExecuteGoToLineCommand() {
387      using (var dlg = new GoToLineDialog(TextEditor)) {
388        var result = dlg.ShowDialog(this);
389        if (result == Forms.DialogResult.OK) {
390          int lineNumber = dlg.Line;
391          var line = Doc.GetLineByNumber(lineNumber);
392          int offset = line.Offset;
393          if (TextUtilities.GetLeadingWhitespace(Doc, line).Length > 0)
394            offset = TextUtilities.GetNextCaretPosition(Doc, offset, LogicalDirection.Forward, CaretPositioningMode.WordBorder);
395          TextEditor.CaretOffset = offset;
396        }
397      }
398    }
399
400    #region Compiler Errors
401    public void ShowCompileErrors(CompilerErrorCollection compilerErrors) {
402      if (compilerErrors == null) return;
403
404      textMarkerService.RemoveAll(x => x != prefixMarker && x != suffixMarker);
405
406      foreach (CompilerError error in compilerErrors) {
407        var startLocation = Doc.GetLocation(prefix.Length);
408        if (error.Line == 1) error.Column += startLocation.Column;
409        error.Line += startLocation.Line;
410        AddErrorMarker(error);
411      }
412    }
413
414    private void AddErrorMarker(CompilerError error) {
415      var segment = GetSegmentAtLocation(error.Line, error.Column);
416      var marker = textMarkerService.Create(segment.Offset, segment.Length);
417      marker.MarkerTypes = TextMarkerTypes.SquigglyUnderline;
418      marker.MarkerColor = error.IsWarning ? WarningColor : ErrorColor;
419      marker.Tag = segment.Length;
420    }
421
422    private ISegment GetSegmentAtLocation(int line, int column) {
423      line = Math.Max(Doc.GetLocation(prefix.Length).Line, line - 1);
424      line = Math.Min(Doc.GetLocation(Doc.TextLength - suffix.Length).Line, line);
425
426      var startOffset = Doc.GetOffset(line, column);
427      var lineEndOffset = Doc.GetLineByNumber(line).EndOffset;
428      var endOffset = TextUtilities.GetNextCaretPosition(Doc, startOffset, LogicalDirection.Forward, CaretPositioningMode.WordBorder);
429      if (endOffset < 0) endOffset = startOffset;
430      endOffset = Math.Min(lineEndOffset + 1, endOffset);
431
432      var segment = new TextSegment { StartOffset = startOffset, EndOffset = endOffset };
433
434      return segment;
435    }
436    #endregion
437
438    private void ApplyLanguageFeatures() {
439      switch (TextEditorSyntaxHighlighting) {
440        case "XML":
441          XmlLanguageFeatures.Apply(this);
442          break;
443        default:
444          CSharpLanguageFeatures.Apply(this);
445          break;
446      }
447    }
448
449    #region Events
450    public event EventHandler TextEditorTextChanged;
451    private void OnTextEditorTextChanged() {
452      var handler = TextEditorTextChanged;
453      if (handler != null) handler(this, EventArgs.Empty);
454    }
455
456    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesLoading;
457    private void OnAssembliesLoading(IEnumerable<Assembly> args) {
458      var handler = AssembliesLoading;
459      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
460    }
461
462    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesLoaded;
463    private void OnAssembliesLoaded(IEnumerable<Assembly> args) {
464      var handler = AssembliesLoaded;
465      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
466    }
467
468    public event EventHandler<EventArgs<IEnumerable<IUnresolvedAssembly>>> InternalAssembliesLoaded;
469    private void OnInternalAssembliesLoaded(IEnumerable<IUnresolvedAssembly> args) {
470      var handler = InternalAssembliesLoaded;
471      if (handler != null) handler(this, new EventArgs<IEnumerable<IUnresolvedAssembly>>(args));
472    }
473
474    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesUnloading;
475    private void OnAssembliesUnloading(IEnumerable<Assembly> args) {
476      var handler = AssembliesUnloading;
477      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
478    }
479
480    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesUnloaded;
481    private void OnAssembliesUnloaded(IEnumerable<Assembly> args) {
482      var handler = AssembliesUnloaded;
483      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
484    }
485
486    public event EventHandler<EventArgs<IEnumerable<IUnresolvedAssembly>>> InternalAssembliesUnloaded;
487    private void OnInternalAssembliesUnloaded(IEnumerable<IUnresolvedAssembly> args) {
488      var handler = InternalAssembliesUnloaded;
489      if (handler != null) handler(this, new EventArgs<IEnumerable<IUnresolvedAssembly>>(args));
490    }
491    #endregion
492  }
493}
Note: See TracBrowser for help on using the repository browser.