Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2434_crossvalidation/HeuristicLab.CodeEditor/3.4/CodeEditor.cs @ 16044

Last change on this file since 16044 was 12473, checked in by ascheibe, 9 years ago

#2329 added a simple code editor for Linux

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