#region License Information
/* HeuristicLab
* Copyright (C) 2002-2018 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
#endregion
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Documents;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.AddIn;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Indentation.CSharp;
using ICSharpCode.AvalonEdit.Search;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.SharpDevelop.Editor;
using Forms = System.Windows.Forms;
using Input = System.Windows.Input;
using Media = System.Windows.Media;
namespace HeuristicLab.CodeEditor {
public partial class CodeEditor : CodeEditorBase {
private static readonly Media.Color WarningColor = Media.Colors.Blue;
private static readonly Media.Color ErrorColor = Media.Colors.Red;
private static readonly Media.Color ReadOnlyColor = Media.Colors.Moccasin;
private const string DefaultDocumentFileName = "Document";
private const string DefaultTextEditorSyntaxHighlighting = "C#";
private const string DefaultTextEditorFontFamily = "Consolas";
private const double DefaultTextEditorFontSize = 13.0;
private const bool DefaultTextEditorShowLineNumbers = true;
private const bool DefaultTextEditorShowSpaces = true;
private const bool DefaultTextEditorShowTabs = true;
private const bool DefaultTextEditorConvertTabsToSpaces = true;
private const bool DefaultTextEditorHighlightCurrentLine = true;
private const int DefaultTextEditorIndentationSize = 2;
private AssemblyLoader assemblyLoader;
private TextMarkerService textMarkerService;
#region Properties
internal TextEditor TextEditor { get { return avalonEditWrapper.TextEditor; } }
internal Input.RoutedCommand CompletionCommand;
internal CompletionWindow CompletionWindow;
internal OverloadInsightWindow OverloadInsightWindow;
private TextDocument Doc { get { return TextEditor.Document; } }
private ITextMarker prefixMarker;
private string prefix = string.Empty;
public override string Prefix {
get { return prefix; }
set {
if (value == null) value = string.Empty;
if (prefix == value) return;
if (prefixMarker != null) prefixMarker.Delete();
Doc.Remove(0, prefix.Length);
prefix = value;
if (value.Length > 0) {
Doc.Insert(0, prefix);
prefixMarker = textMarkerService.Create(0, prefix.Length);
prefixMarker.BackgroundColor = ReadOnlyColor;
}
}
}
private ITextMarker suffixMarker;
private string suffix = string.Empty;
public override string Suffix {
get { return suffix; }
set {
if (value == null) value = string.Empty;
if (suffix == value) return;
if (suffixMarker != null) suffixMarker.Delete();
Doc.Remove(Doc.TextLength - suffix.Length, suffix.Length);
suffix = value;
if (value.Length > 0) {
int offset = Doc.TextLength;
Doc.Insert(offset, suffix);
suffixMarker = textMarkerService.Create(offset, suffix.Length);
suffixMarker.BackgroundColor = ReadOnlyColor;
}
}
}
public override string UserCode {
get { return Doc.GetText(prefix.Length, Doc.TextLength - suffix.Length - prefix.Length); }
set {
var curLength = Doc.TextLength - suffix.Length - prefix.Length;
var curUserCode = Doc.GetText(prefix.Length, curLength);
if (curUserCode == value) return;
Doc.Replace(prefix.Length, curLength, value);
}
}
#region TextEditor
[DefaultValue(DefaultTextEditorSyntaxHighlighting)]
public string TextEditorSyntaxHighlighting {
get { return TextEditor.SyntaxHighlighting.Name; }
set {
TextEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition(value);
ApplyLanguageFeatures();
}
}
[DefaultValue(DefaultTextEditorShowLineNumbers)]
public bool TextEditorShowLineNumbers {
get { return TextEditor.ShowLineNumbers; }
set { TextEditor.ShowLineNumbers = value; }
}
[DefaultValue(DefaultTextEditorShowSpaces)]
public bool TextEditorShowSpaces {
get { return TextEditor.Options.ShowSpaces; }
set { TextEditor.Options.ShowSpaces = value; }
}
[DefaultValue(DefaultTextEditorShowTabs)]
public bool TextEditorShowTabs {
get { return TextEditor.Options.ShowTabs; }
set { TextEditor.Options.ShowTabs = value; }
}
[DefaultValue(DefaultTextEditorConvertTabsToSpaces)]
public bool TextEditorConvertTabsToSpaces {
get { return TextEditor.Options.ConvertTabsToSpaces; }
set { TextEditor.Options.ConvertTabsToSpaces = value; }
}
[DefaultValue(DefaultTextEditorHighlightCurrentLine)]
public bool TextEditorHighlightCurrentLine {
get { return TextEditor.Options.HighlightCurrentLine; }
set { TextEditor.Options.HighlightCurrentLine = value; }
}
[DefaultValue(DefaultTextEditorIndentationSize)]
public int TextEditorIndentationSize {
get { return TextEditor.Options.IndentationSize; }
set { TextEditor.Options.IndentationSize = value; }
}
#endregion
public override bool ReadOnly {
get { return TextEditor.IsReadOnly; }
set { TextEditor.IsReadOnly = value; }
}
#endregion
public CodeEditor() {
InitializeComponent();
InitializeTextEditor();
}
private void InitializeTextEditor() {
#region AssemblyLoader
assemblyLoader = new AssemblyLoader();
assemblyLoader.AssembliesLoading += (sender, args) => OnAssembliesLoading(args.Value);
assemblyLoader.InternalAssembliesLoaded += (sender, args) => OnInternalAssembliesLoaded(args.Value);
assemblyLoader.AssembliesLoaded += (sender, args) => OnAssembliesLoaded(args.Value);
assemblyLoader.AssembliesUnloading += (sender, args) => OnAssembliesUnloading(args.Value);
assemblyLoader.InternalAssembliesUnloaded += (sender, args) => OnInternalAssembliesUnloaded(args.Value);
assemblyLoader.AssembliesUnloaded += (sender, args) => OnAssembliesUnloaded(args.Value);
#endregion
#region TextMarkerService
textMarkerService = new TextMarkerService(TextEditor.Document);
TextEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService);
TextEditor.TextArea.TextView.LineTransformers.Add(textMarkerService);
TextEditor.TextArea.TextView.Services.AddService(typeof(ITextMarkerService), textMarkerService);
#endregion
#region ReadOnlySectionProvider
TextEditor.TextArea.ReadOnlySectionProvider = new MethodDefinitionReadOnlySectionProvider(this);
#endregion
#region SearchPanel
SearchPanel.Install(TextEditor);
#endregion
#region CompletionCommand
CompletionCommand = new Input.RoutedCommand();
CompletionCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.Space, Input.ModifierKeys.Control));
#endregion
#region MoveLinesUpCommand
var moveLinesUpCommand = new Input.RoutedCommand();
moveLinesUpCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.Up, Input.ModifierKeys.Alt));
var moveLinesUpCommandBinding = new Input.CommandBinding(moveLinesUpCommand, (sender, args) => ExecuteMoveLinesCommand(MovementDirection.Up));
TextEditor.CommandBindings.Add(moveLinesUpCommandBinding);
#endregion
#region MoveLinesDownCommand
var moveLinesDownCommand = new Input.RoutedCommand();
moveLinesDownCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.Down, Input.ModifierKeys.Alt));
var moveLinesDownCommandBinding = new Input.CommandBinding(moveLinesDownCommand, (sender, args) => ExecuteMoveLinesCommand(MovementDirection.Down));
TextEditor.CommandBindings.Add(moveLinesDownCommandBinding);
#endregion
#region GoToLineCommand
var goToLineCommand = new Input.RoutedCommand();
goToLineCommand.InputGestures.Add(new Input.KeyGesture(Input.Key.G, Input.ModifierKeys.Control));
var goToLineCommandBinding = new Input.CommandBinding(goToLineCommand, (sender, args) => ExecuteGoToLineCommand());
TextEditor.CommandBindings.Add(goToLineCommandBinding);
#endregion
TextEditorSyntaxHighlighting = DefaultTextEditorSyntaxHighlighting;
TextEditorShowLineNumbers = DefaultTextEditorShowLineNumbers;
TextEditorShowSpaces = DefaultTextEditorShowSpaces;
TextEditorShowTabs = DefaultTextEditorShowTabs;
TextEditorConvertTabsToSpaces = DefaultTextEditorConvertTabsToSpaces;
TextEditorHighlightCurrentLine = DefaultTextEditorHighlightCurrentLine;
TextEditorIndentationSize = DefaultTextEditorIndentationSize;
Doc.FileName = DefaultDocumentFileName;
TextEditor.FontFamily = new Media.FontFamily(DefaultTextEditorFontFamily);
TextEditor.FontSize = DefaultTextEditorFontSize;
TextEditor.Options.EnableVirtualSpace = true;
TextEditor.TextArea.IndentationStrategy = new CSharpIndentationStrategy(TextEditor.Options);
TextEditor.TextChanged += (sender, args) => {
foreach (var marker in textMarkerService.TextMarkers) {
if (marker == prefixMarker || marker == suffixMarker) continue;
if (marker.Length != (int)marker.Tag)
marker.Delete();
else {
int caretOffset = TextEditor.CaretOffset;
var line = Doc.GetLineByOffset(marker.StartOffset);
int lineEndOffset = line.EndOffset;
if (caretOffset == lineEndOffset) // special case for markers beyond line length
marker.Delete();
}
}
OnTextEditorTextChanged();
};
}
#region Assembly Management
public override void AddAssembly(Assembly a) {
assemblyLoader.AddAssembly(a);
}
public override void AddAssemblies(IEnumerable assemblies) {
assemblyLoader.AddAssemblies(assemblies);
}
public override async Task AddAssembliesAsync(IEnumerable assemblies) {
await assemblyLoader.AddAssembliesAsync(assemblies);
}
public override void RemoveAssembly(Assembly a) {
assemblyLoader.RemoveAssembly(a);
}
#endregion
public override void ClearEditHistory() {
Doc.UndoStack.ClearAll();
}
public override void ScrollToPosition(int line, int column) {
var segment = GetSegmentAtLocation(line, column);
TextEditor.CaretOffset = segment.Offset + segment.Length;
TextEditor.ScrollToLine(line);
}
public override void ScrollAfterPrefix() {
var location = Doc.GetLocation(prefix.Length);
ScrollToPosition(location.Line, location.Column);
}
private enum MovementDirection { Up, Down }
private void ExecuteMoveLinesCommand(MovementDirection movementDirection) {
var textArea = TextEditor.TextArea;
var selection = textArea.Selection;
var caret = textArea.Caret;
var selectionStartPosition = selection.StartPosition;
var selectionEndPosition = selection.EndPosition;
var caretPosition = caret.Position;
int caretOffset = caret.Offset;
bool advancedPositionCalcualtion = selection is RectangleSelection || !selection.IsEmpty;
int selectionStartOffset, selectionEndOffset;
if (advancedPositionCalcualtion) {
if (selectionStartPosition.CompareTo(selectionEndPosition) > 0) {
var temp = selectionStartPosition;
selectionStartPosition = selectionEndPosition;
selectionEndPosition = temp;
}
selectionStartOffset = Doc.GetOffset(selectionStartPosition.Location);
selectionEndOffset = Doc.GetOffset(selectionEndPosition.Location);
} else {
selectionStartOffset = selectionEndOffset = TextEditor.SelectionStart;
}
int selectionLength = selection.Length;
var startLine = Doc.GetLineByOffset(selectionStartOffset);
var endLine = Doc.GetLineByOffset(selectionEndOffset);
if (selection.IsMultiline && selectionEndOffset == endLine.Offset) {
if (movementDirection == MovementDirection.Down && endLine.TotalLength == 0) return;
endLine = endLine.PreviousLine;
}
if (movementDirection == MovementDirection.Up && startLine.LineNumber == 1) return;
if (movementDirection == MovementDirection.Down && endLine.LineNumber == Doc.LineCount) return;
int startOffset = startLine.Offset;
string primaryText = Doc.GetText(startOffset, endLine.EndOffset - startOffset);
string primaryDelimiter = Doc.GetText(endLine.EndOffset, endLine.DelimiterLength);
var secondaryLine = movementDirection == MovementDirection.Up ? startLine.PreviousLine : endLine.NextLine;
string secondaryText = Doc.GetText(secondaryLine.Offset, secondaryLine.Length);
string secondaryDelimiter = Doc.GetText(secondaryLine.EndOffset, secondaryLine.DelimiterLength);
if (string.IsNullOrEmpty(primaryText + primaryDelimiter) || string.IsNullOrEmpty(secondaryText + secondaryDelimiter)) return;
if (movementDirection == MovementDirection.Up) {
string replacementText = primaryText + secondaryDelimiter + secondaryText + primaryDelimiter;
Doc.Replace(secondaryLine.Offset, replacementText.Length, replacementText);
int correctionLength = secondaryText.Length + secondaryDelimiter.Length;
selectionStartOffset -= correctionLength;
caretOffset -= correctionLength;
} else {
string replacementText = secondaryText + primaryDelimiter + primaryText + secondaryDelimiter;
Doc.Replace(startLine.Offset, replacementText.Length, replacementText);
int correctionLength = secondaryText.Length + primaryDelimiter.Length;
selectionStartOffset += correctionLength;
caretOffset += correctionLength;
}
if (advancedPositionCalcualtion) {
var newSelectionStartLocation = Doc.GetLocation(selectionStartOffset);
int selectionLineOffset = newSelectionStartLocation.Line - Math.Min(selectionStartPosition.Line, selectionEndPosition.Line);
selectionStartPosition.Line += selectionLineOffset;
selectionEndPosition.Line += selectionLineOffset;
if (selectionEndPosition.Line > Doc.LineCount) {
var newLocation = Doc.GetLocation(Doc.TextLength);
selectionEndPosition.Line = newLocation.Line;
selectionEndPosition.Column = newLocation.Column;
selectionEndPosition.VisualColumn = newLocation.Column - 1;
selectionEndPosition.IsAtEndOfLine = selectionStartPosition.IsAtEndOfLine; // actual value does not matter; needed for comparison
}
if (selectionStartPosition == selectionEndPosition)
textArea.ClearSelection();
else {
if (selection is RectangleSelection)
textArea.Selection = new RectangleSelection(textArea, selectionStartPosition, selectionEndPosition);
else
textArea.Selection = new SimpleSelection(textArea, selectionStartPosition, selectionEndPosition);
}
} else {
TextEditor.SelectionStart = selectionStartOffset;
TextEditor.SelectionLength = selectionLength;
}
var newCaretLocation = Doc.GetLocation(Math.Min(caretOffset, Doc.TextLength));
var newCaretPosition = new TextViewPosition(newCaretLocation);
if (caretPosition.VisualColumn > caretPosition.Column) {
newCaretPosition.VisualColumn = caretPosition.VisualColumn;
}
caret.Position = newCaretPosition;
}
private void ExecuteGoToLineCommand() {
using (var dlg = new GoToLineDialog(TextEditor)) {
var result = dlg.ShowDialog(this);
if (result == Forms.DialogResult.OK) {
int lineNumber = dlg.Line;
var line = Doc.GetLineByNumber(lineNumber);
int offset = line.Offset;
if (TextUtilities.GetLeadingWhitespace(Doc, line).Length > 0)
offset = TextUtilities.GetNextCaretPosition(Doc, offset, LogicalDirection.Forward, CaretPositioningMode.WordBorder);
TextEditor.CaretOffset = offset;
}
}
}
#region Compiler Errors
public override void ShowCompileErrors(CompilerErrorCollection compilerErrors) {
if (compilerErrors == null) return;
textMarkerService.RemoveAll(x => x != prefixMarker && x != suffixMarker);
foreach (CompilerError error in compilerErrors) {
var startLocation = Doc.GetLocation(prefix.Length);
if (error.Line == 1) error.Column += startLocation.Column;
error.Line += startLocation.Line;
AddErrorMarker(error);
}
}
private void AddErrorMarker(CompilerError error) {
var segment = GetSegmentAtLocation(error.Line, error.Column);
var marker = textMarkerService.Create(segment.Offset, segment.Length);
marker.MarkerTypes = TextMarkerTypes.SquigglyUnderline;
marker.MarkerColor = error.IsWarning ? WarningColor : ErrorColor;
marker.Tag = segment.Length;
}
private ISegment GetSegmentAtLocation(int line, int column) {
line = Math.Max(Doc.GetLocation(prefix.Length).Line, line - 1);
line = Math.Min(Doc.GetLocation(Doc.TextLength - suffix.Length).Line, line);
var startOffset = Doc.GetOffset(line, column);
var lineEndOffset = Doc.GetLineByNumber(line).EndOffset;
var endOffset = TextUtilities.GetNextCaretPosition(Doc, startOffset, LogicalDirection.Forward, CaretPositioningMode.WordBorder);
if (endOffset < 0) endOffset = startOffset;
endOffset = Math.Min(lineEndOffset + 1, endOffset);
var segment = new TextSegment { StartOffset = startOffset, EndOffset = endOffset };
return segment;
}
#endregion
private void ApplyLanguageFeatures() {
switch (TextEditorSyntaxHighlighting) {
case "XML":
XmlLanguageFeatures.Apply(this);
break;
default:
CSharpLanguageFeatures.Apply(this);
break;
}
}
}
}