source: branches/CodeEditor/HeuristicLab.CodeEditor/3.4/CodeEditor.cs @ 11743

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

#2077: improved line moving

File size: 19.3 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.Search;
37using ICSharpCode.NRefactory.Editor;
38using ICSharpCode.NRefactory.TypeSystem;
39using ICSharpCode.SharpDevelop.Editor;
40using Forms = System.Windows.Forms;
41using Input = System.Windows.Input;
42using Media = System.Windows.Media;
43
44namespace HeuristicLab.CodeEditor {
45  public partial class CodeEditor : Forms.UserControl {
46    private static readonly Media.Color WarningColor = Media.Colors.Blue;
47    private static readonly Media.Color ErrorColor = Media.Colors.Red;
48    private static readonly Media.Color ReadOnlyColor = Media.Colors.Moccasin;
49
50    private const string DefaultDocumentFileName = "Document";
51    private const string DefaultTextEditorSyntaxHighlighting = "C#";
52    private const string DefaultTextEditorFontFamily = "Consolas";
53    private const double DefaultTextEditorFontSize = 13.0;
54    private const bool DefaultTextEditorShowLineNumbers = true;
55    private const bool DefaultTextEditorShowSpaces = true;
56    private const bool DefaultTextEditorShowTabs = true;
57    private const bool DefaultTextEditorConvertTabsToSpaces = true;
58    private const bool DefaultTextEditorHighlightCurrentLine = true;
59    private const int DefaultTextEditorIndentationSize = 2;
60
61    private AssemblyLoader assemblyLoader;
62    private ILanguageFeatures languageFeatures;
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) textMarkerService.Remove(prefixMarker);
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) textMarkerService.Remove(suffixMarker);
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
193      #endregion
194
195      #region ReadOnlySectionProvider
196      TextEditor.TextArea.ReadOnlySectionProvider = new MethodDefinitionReadOnlySectionProvider(this);
197      #endregion
198
199      #region SearchPanel
200      SearchPanel.Install(TextEditor);
201      #endregion
202
203      #region CompletionCommand
204      CompletionCommand = new Input.RoutedCommand();
205      CompletionCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.Space, Input.ModifierKeys.Control));
206      #endregion
207
208      #region MoveLinesUpCommand
209      var moveLinesUpCommand = new Input.RoutedCommand();
210      moveLinesUpCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.Up, Input.ModifierKeys.Alt));
211      var moveLinesUpCommandBinding = new Input.CommandBinding(moveLinesUpCommand, (sender, args) => MoveLines(MovementDirection.Up));
212      TextEditor.CommandBindings.Add(moveLinesUpCommandBinding);
213      #endregion
214
215      #region MoveLinesDownCommand
216      var moveLinesDownCommand = new Input.RoutedCommand();
217      moveLinesDownCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.Down, Input.ModifierKeys.Alt));
218      var moveLinesDownCommandBinding = new Input.CommandBinding(moveLinesDownCommand, (sender, args) => MoveLines(MovementDirection.Down));
219      TextEditor.CommandBindings.Add(moveLinesDownCommandBinding);
220      #endregion
221
222      TextEditorSyntaxHighlighting = DefaultTextEditorSyntaxHighlighting;
223      TextEditorShowLineNumbers = DefaultTextEditorShowLineNumbers;
224      TextEditorShowSpaces = DefaultTextEditorShowSpaces;
225      TextEditorShowTabs = DefaultTextEditorShowTabs;
226      TextEditorConvertTabsToSpaces = DefaultTextEditorConvertTabsToSpaces;
227      TextEditorHighlightCurrentLine = DefaultTextEditorHighlightCurrentLine;
228      TextEditorIndentationSize = DefaultTextEditorIndentationSize;
229
230      Doc.FileName = DefaultDocumentFileName;
231
232      TextEditor.FontFamily = new Media.FontFamily(DefaultTextEditorFontFamily);
233      TextEditor.FontSize = DefaultTextEditorFontSize;
234      TextEditor.Options.EnableVirtualSpace = true;
235
236      TextEditor.TextChanged += (sender, args) => {
237        textMarkerService.RemoveAll(x => x != prefixMarker && x != suffixMarker);
238        OnTextEditorTextChanged();
239      };
240    }
241
242    #region Assembly Management
243    public void AddAssembly(Assembly a) {
244      assemblyLoader.AddAssembly(a);
245    }
246
247    public void AddAssemblies(IEnumerable<Assembly> assemblies) {
248      assemblyLoader.AddAssemblies(assemblies);
249    }
250
251    public async Task AddAssembliesAsync(IEnumerable<Assembly> assemblies) {
252      await assemblyLoader.AddAssembliesAsync(assemblies);
253    }
254
255    public void RemoveAssembly(Assembly a) {
256      assemblyLoader.RemoveAssembly(a);
257    }
258    #endregion
259
260    public void ScrollToPosition(int line, int column) {
261      var segment = GetSegmentAtLocation(line, column);
262      TextEditor.CaretOffset = segment.Offset + segment.Length;
263      TextEditor.ScrollToLine(line);
264    }
265
266    public void ScrollAfterPrefix() {
267      var location = Doc.GetLocation(prefix.Length);
268      ScrollToPosition(location.Line, location.Column);
269    }
270
271    private enum MovementDirection { Up, Down }
272    private void MoveLines(MovementDirection movementDirection) {
273      var textArea = TextEditor.TextArea;
274      var selection = textArea.Selection;
275      var caret = textArea.Caret;
276
277      var selectionStartPosition = selection.StartPosition;
278      var selectionEndPosition = selection.EndPosition;
279      var caretPosition = caret.Position;
280      int caretOffset = caret.Offset;
281
282      bool advancedPositionCalcualtion = selection is RectangleSelection || !selection.IsEmpty;
283
284      int selectionStartOffset, selectionEndOffset;
285      if (advancedPositionCalcualtion) {
286        if (selectionStartPosition.CompareTo(selectionEndPosition) > 0) {
287          var temp = selectionStartPosition;
288          selectionStartPosition = selectionEndPosition;
289          selectionEndPosition = temp;
290        }
291        selectionStartOffset = Doc.GetOffset(selectionStartPosition.Location);
292        selectionEndOffset = Doc.GetOffset(selectionEndPosition.Location);
293      } else {
294        selectionStartOffset = selectionEndOffset = TextEditor.SelectionStart;
295      }
296
297      int selectionLength = selection.Length;
298
299      var startLine = Doc.GetLineByOffset(selectionStartOffset);
300      var endLine = Doc.GetLineByOffset(selectionEndOffset);
301
302      if (selection.IsMultiline && selectionEndOffset == endLine.Offset) {
303        if (movementDirection == MovementDirection.Down && endLine.TotalLength == 0) return;
304        endLine = endLine.PreviousLine;
305      }
306
307      if (movementDirection == MovementDirection.Up && startLine.LineNumber == 1) return;
308      if (movementDirection == MovementDirection.Down && endLine.LineNumber == Doc.LineCount) return;
309
310      int startOffset = startLine.Offset;
311      string primaryText = Doc.GetText(startOffset, endLine.EndOffset - startOffset);
312      string primaryDelimiter = Doc.GetText(endLine.EndOffset, endLine.DelimiterLength);
313
314      var secondaryLine = movementDirection == MovementDirection.Up ? startLine.PreviousLine : endLine.NextLine;
315      string secondaryText = Doc.GetText(secondaryLine.Offset, secondaryLine.Length);
316      string secondaryDelimiter = Doc.GetText(secondaryLine.EndOffset, secondaryLine.DelimiterLength);
317
318      if (string.IsNullOrEmpty(primaryText + primaryDelimiter) || string.IsNullOrEmpty(secondaryText + secondaryDelimiter)) return;
319
320      if (movementDirection == MovementDirection.Up) {
321        string replacementText = primaryText + secondaryDelimiter + secondaryText + primaryDelimiter;
322        Doc.Replace(secondaryLine.Offset, replacementText.Length, replacementText);
323        int correctionLength = secondaryText.Length + secondaryDelimiter.Length;
324        selectionStartOffset -= correctionLength;
325        caretOffset -= correctionLength;
326      } else {
327        string replacementText = secondaryText + primaryDelimiter + primaryText + secondaryDelimiter;
328        Doc.Replace(startLine.Offset, replacementText.Length, replacementText);
329        int correctionLength = secondaryText.Length + primaryDelimiter.Length;
330        selectionStartOffset += correctionLength;
331        caretOffset += correctionLength;
332      }
333
334      if (advancedPositionCalcualtion) {
335        var newSelectionStartLocation = Doc.GetLocation(selectionStartOffset);
336        int selectionLineOffset = newSelectionStartLocation.Line - Math.Min(selectionStartPosition.Line, selectionEndPosition.Line);
337        selectionStartPosition.Line += selectionLineOffset;
338        selectionEndPosition.Line += selectionLineOffset;
339        if (selectionEndPosition.Line > Doc.LineCount) {
340          var newLocation = Doc.GetLocation(Doc.TextLength);
341          selectionEndPosition.Line = newLocation.Line;
342          selectionEndPosition.Column = newLocation.Column;
343          selectionEndPosition.VisualColumn = newLocation.Column - 1;
344          selectionEndPosition.IsAtEndOfLine = selectionStartPosition.IsAtEndOfLine; // actual value does not matter; needed for comparison
345        }
346
347        if (selectionStartPosition == selectionEndPosition)
348          textArea.ClearSelection();
349        else {
350          if (selection is RectangleSelection)
351            textArea.Selection = new RectangleSelection(textArea, selectionStartPosition, selectionEndPosition);
352          else
353            textArea.Selection = new SimpleSelection(textArea, selectionStartPosition, selectionEndPosition);
354        }
355      } else {
356        TextEditor.SelectionStart = selectionStartOffset;
357        TextEditor.SelectionLength = selectionLength;
358      }
359
360      var newCaretLocation = Doc.GetLocation(Math.Min(caretOffset, Doc.TextLength));
361      var newCaretPosition = new TextViewPosition(newCaretLocation);
362      if (caretPosition.VisualColumn > caretPosition.Column) {
363        newCaretPosition.VisualColumn = caretPosition.VisualColumn;
364      }
365      caret.Position = newCaretPosition;
366    }
367
368    #region Compiler Errors
369    public void ShowCompileErrors(CompilerErrorCollection compilerErrors) {
370      if (compilerErrors == null) return;
371
372      textMarkerService.RemoveAll(x => x != prefixMarker && x != suffixMarker);
373
374      foreach (CompilerError error in compilerErrors) {
375        var startLocation = Doc.GetLocation(prefix.Length);
376        if (error.Line == 1) error.Column += startLocation.Column;
377        error.Line += startLocation.Line;
378        AddErrorMarker(error);
379      }
380    }
381
382    private void AddErrorMarker(CompilerError error) {
383      var segment = GetSegmentAtLocation(error.Line, error.Column);
384      var marker = textMarkerService.Create(segment.Offset, segment.Length);
385      marker.MarkerTypes = TextMarkerTypes.SquigglyUnderline;
386      marker.MarkerColor = error.IsWarning ? WarningColor : ErrorColor;
387    }
388
389    private ISegment GetSegmentAtLocation(int line, int column) {
390      line = Math.Max(Doc.GetLocation(prefix.Length).Line, line - 1);
391      line = Math.Min(Doc.GetLocation(Doc.TextLength - suffix.Length).Line, line);
392
393      var startOffset = Doc.GetOffset(line, column);
394      var lineEndOffset = Doc.GetLineByNumber(line).EndOffset;
395      var endOffset = TextUtilities.GetNextCaretPosition(Doc, startOffset, LogicalDirection.Forward, CaretPositioningMode.WordBorder);
396      if (endOffset < 0) endOffset = startOffset;
397      endOffset = Math.Min(lineEndOffset + 1, endOffset);
398
399      var segment = new TextSegment { StartOffset = startOffset, EndOffset = endOffset };
400
401      return segment;
402    }
403    #endregion
404
405    private void ApplyLanguageFeatures() {
406      switch (TextEditorSyntaxHighlighting) {
407        case "XML":
408          languageFeatures = new XmlLanguageFeatures(this);
409          break;
410        default:
411          languageFeatures = new CSharpLanguageFeatures(this);
412          break;
413      }
414    }
415
416    #region Events
417    public event EventHandler TextEditorTextChanged;
418    private void OnTextEditorTextChanged() {
419      var handler = TextEditorTextChanged;
420      if (handler != null) handler(this, EventArgs.Empty);
421    }
422
423    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesLoading;
424    private void OnAssembliesLoading(IEnumerable<Assembly> args) {
425      var handler = AssembliesLoading;
426      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
427    }
428
429    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesLoaded;
430    private void OnAssembliesLoaded(IEnumerable<Assembly> args) {
431      var handler = AssembliesLoaded;
432      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
433    }
434
435    public event EventHandler<EventArgs<IEnumerable<IUnresolvedAssembly>>> InternalAssembliesLoaded;
436    private void OnInternalAssembliesLoaded(IEnumerable<IUnresolvedAssembly> args) {
437      var handler = InternalAssembliesLoaded;
438      if (handler != null) handler(this, new EventArgs<IEnumerable<IUnresolvedAssembly>>(args));
439    }
440
441    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesUnloading;
442    private void OnAssembliesUnloading(IEnumerable<Assembly> args) {
443      var handler = AssembliesUnloading;
444      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
445    }
446
447    public event EventHandler<EventArgs<IEnumerable<Assembly>>> AssembliesUnloaded;
448    private void OnAssembliesUnloaded(IEnumerable<Assembly> args) {
449      var handler = AssembliesUnloaded;
450      if (handler != null) handler(this, new EventArgs<IEnumerable<Assembly>>(args));
451    }
452
453    public event EventHandler<EventArgs<IEnumerable<IUnresolvedAssembly>>> InternalAssembliesUnloaded;
454    private void OnInternalAssembliesUnloaded(IEnumerable<IUnresolvedAssembly> args) {
455      var handler = InternalAssembliesUnloaded;
456      if (handler != null) handler(this, new EventArgs<IEnumerable<IUnresolvedAssembly>>(args));
457    }
458    #endregion
459  }
460}
Note: See TracBrowser for help on using the repository browser.