Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.CodeEditor/3.4/CodeEditor.cs @ 11836

Last change on this file since 11836 was 11836, checked in by jkarder, 9 years ago

#2077:

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