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

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

#2077: added <Alt> + <Up> and <Alt> + <Down> keystrokes to move lines

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