From 338edba081dba2a2aefb634811be1cc84ec93d64 Mon Sep 17 00:00:00 2001 From: Avi Levkovich Date: Tue, 25 Aug 2020 10:08:01 +0300 Subject: merge --- .../BreakPointSymbolPressedEventArgs.cs | 16 + .../Editing/BreakPointMargin.cs | 285 +++ .../Highlighting/Resources/CSharp-Mode.xshd | 2 +- .../Images/break_point_arrow.png | Bin 0 -> 453 bytes .../Intellisense/HideIntellisenseAttribute.cs | 12 + .../Intellisense/KnownType.cs | 11 +- .../Tango.Scripting.Editors/ScriptEditor.cs | 302 +++ .../Tango.Scripting.Editors.csproj | 6 + .../Tango.Scripting.Editors_di35u2uj_wpftmp.csproj | 628 ++++++ .../Tango.Scripting.Editors/TextEditor.cs | 2343 ++++++++++---------- 10 files changed, 2482 insertions(+), 1123 deletions(-) create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/BreakPointSymbolPressedEventArgs.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/BreakPointMargin.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/break_point_arrow.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/HideIntellisenseAttribute.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors_di35u2uj_wpftmp.csproj (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors') diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/BreakPointSymbolPressedEventArgs.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/BreakPointSymbolPressedEventArgs.cs new file mode 100644 index 000000000..1728bb565 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/BreakPointSymbolPressedEventArgs.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Tango.Scripting.Core; + +namespace Tango.Scripting.Editors +{ + public class BreakPointSymbolPressedEventArgs : EventArgs + { + public ScriptBreakPointSymbol BreakPointSymbol { get; set; } + public Point Position { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/BreakPointMargin.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/BreakPointMargin.cs new file mode 100644 index 000000000..e566e6aa9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/BreakPointMargin.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Core; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + public class BreakPointMargin : AbstractMargin, IWeakEventListener + { + private TextArea textArea; + private int maxLineNumberLength = 1; + private BitmapSource _arrowBitmap; + private ScriptEditor _editor; + + public ObservableCollection BreakPoints { get; set; } + + public Brush Background + { + get { return (Brush)GetValue(BackgroundProperty); } + set { SetValue(BackgroundProperty, value); } + } + public static readonly DependencyProperty BackgroundProperty = + DependencyProperty.Register("Background", typeof(Brush), typeof(BreakPointMargin), new PropertyMetadata(new SolidColorBrush(Color.FromRgb(50, 50, 50)))); + + public Brush Foreground + { + get { return (Brush)GetValue(ForegroundProperty); } + set { SetValue(ForegroundProperty, value); } + } + public static readonly DependencyProperty ForegroundProperty = + DependencyProperty.Register("Foreground", typeof(Brush), typeof(BreakPointMargin), new PropertyMetadata(Brushes.Red)); + + static BreakPointMargin() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(BreakPointMargin), + new FrameworkPropertyMetadata(typeof(BreakPointMargin))); + } + + public BreakPointMargin(ScriptEditor editor) + { + _editor = editor; + BreakPoints = new ObservableCollection(); + BreakPoints.CollectionChanged += BreakPoints_CollectionChanged; + RenderOptions.SetEdgeMode(this, EdgeMode.Unspecified); + + _arrowBitmap = new BitmapImage(new Uri($"pack://application:,,,/Tango.Scripting.Editors;component/Images/break_point_arrow.png", UriKind.Absolute)); + } + + private void BreakPoints_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + InvalidateVisual(); + } + + protected override Size MeasureOverride(Size availableSize) + { + return new Size(20, 0); + } + + protected override void OnRender(DrawingContext drawingContext) + { + TextView textView = this.TextView; + Size renderSize = this.RenderSize; + if (textView != null && textView.VisualLinesValid) + { + drawingContext.DrawRectangle(Background, new Pen(Background, 1), new Rect(0, 0, ActualWidth, ActualHeight)); + + var foreground = Foreground; + foreach (VisualLine line in textView.VisualLines) + { + int lineNumber = line.FirstDocumentLine.LineNumber; + + BreakPoint b = BreakPoints.FirstOrDefault(x => x.LineNumber == lineNumber); + + if (b != null) + { + double y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop); + drawingContext.DrawEllipse(Foreground, new Pen(Brushes.Gainsboro, 1), new Point(10, y - textView.VerticalOffset + 8), 6, 6); + + if (b.IsActive) + { + drawingContext.DrawImage(_arrowBitmap, new Rect(6, y - textView.VerticalOffset + 2.5, 8.5, 10)); + } + } + } + } + } + + protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView) + { + if (oldTextView != null) + { + oldTextView.VisualLinesChanged -= TextViewVisualLinesChanged; + } + base.OnTextViewChanged(oldTextView, newTextView); + if (newTextView != null) + { + newTextView.VisualLinesChanged += TextViewVisualLinesChanged; + + // find the text area belonging to the new text view + textArea = newTextView.Services.GetService(typeof(TextArea)) as TextArea; + } + else + { + textArea = null; + } + InvalidateVisual(); + } + + protected override void OnDocumentChanged(TextDocument oldDocument, TextDocument newDocument) + { + if (oldDocument != null) + { + PropertyChangedEventManager.RemoveListener(oldDocument, this, "LineCount"); + } + base.OnDocumentChanged(oldDocument, newDocument); + if (newDocument != null) + { + PropertyChangedEventManager.AddListener(newDocument, this, "LineCount"); + } + OnDocumentLineCountChanged(); + } + + protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(PropertyChangedEventManager)) + { + OnDocumentLineCountChanged(); + return true; + } + return false; + } + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + return ReceiveWeakEvent(managerType, sender, e); + } + + private void OnDocumentLineCountChanged() + { + int documentLineCount = Document != null ? Document.LineCount : 1; + int newLength = documentLineCount.ToString(CultureInfo.CurrentCulture).Length; + + foreach (var breakPoint in BreakPoints.ToList()) + { + if (breakPoint.LineNumber > documentLineCount) + { + BreakPoints.Remove(breakPoint); + } + else + { + try + { + var line = Document.GetLineByNumber(breakPoint.LineNumber); + if (line != null) + { + String lineText = Document.GetText(line.Offset, line.Length); + if (!IsBreakPointValid(lineText)) + { + BreakPoints.Remove(breakPoint); + } + } + } + catch { } + } + } + + // The margin looks too small when there is only one digit, so always reserve space for + // at least two digits + if (newLength < 2) + newLength = 2; + + if (newLength != maxLineNumberLength) + { + maxLineNumberLength = newLength; + InvalidateMeasure(); + } + } + + private void TextViewVisualLinesChanged(object sender, EventArgs e) + { + InvalidateVisual(); + } + + protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) + { + // accept clicks even when clicking on the background + return new PointHitTestResult(this, hitTestParameters.HitPoint); + } + + private VisualLine GetLineNumberByMousePosition(MouseEventArgs e) + { + Point pos = e.GetPosition(TextView); + pos.X = 0; + pos.Y += TextView.VerticalOffset; + VisualLine vl = TextView.GetVisualLineFromVisualTop(pos.Y); + return vl; + } + + private bool IsBreakPointValid(String lineText) + { + if (lineText.EndsWith(";") && !lineText.StartsWith("using")) + { + return true; + } + + return false; + } + + protected override void OnPreviewMouseMove(MouseEventArgs e) + { + base.OnPreviewMouseMove(e); + + if (_editor.DisableBreakPoints) + { + Cursor = Cursors.No; + } + else + { + Cursor = Cursors.Arrow; + } + } + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + + if (_editor.DisableBreakPoints) + { + return; + } + + try + { + if (!e.Handled && TextView != null && textArea != null) + { + e.Handled = true; + textArea.Focus(); + + var visualLine = GetLineNumberByMousePosition(e); + + int? lineNumber = visualLine != null ? (int?)visualLine.FirstDocumentLine.LineNumber : null; + + if (lineNumber != null) + { + var breakPoint = BreakPoints.FirstOrDefault(x => x.LineNumber == lineNumber.Value); + if (breakPoint != null) + { + BreakPoints.Remove(breakPoint); + } + else + { + var lineText = Document.GetText(visualLine.FirstDocumentLine.Offset, visualLine.FirstDocumentLine.Length).Trim(); + + if (IsBreakPointValid(lineText)) + { + BreakPoint newBreakPoint = new BreakPoint(); + newBreakPoint.LineNumber = lineNumber.Value; + BreakPoints.Add(newBreakPoint); + } + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd index 6f400c4f5..1f6139ff6 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd @@ -11,7 +11,7 @@ - + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/break_point_arrow.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/break_point_arrow.png new file mode 100644 index 000000000..e8d367028 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/break_point_arrow.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/HideIntellisenseAttribute.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/HideIntellisenseAttribute.cs new file mode 100644 index 000000000..548bd909e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/HideIntellisenseAttribute.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class HideIntellisenseAttribute : Attribute + { + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownType.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownType.cs index 3dc796152..c2e7ac422 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownType.cs +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownType.cs @@ -137,6 +137,8 @@ namespace Tango.Scripting.Editors.Intellisense { var method = methods[i]; + if (method.GetCustomAttribute() != null) continue; + KnownTypeMethod m = new KnownTypeMethod(this); m.Name = method.Name; m.ReturnType = method.ReturnType; @@ -183,17 +185,14 @@ namespace Tango.Scripting.Editors.Intellisense //Load Properties { - if (Type == typeof(Color)) - { - - } - var properties = Type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static).ToList(); for (int i = 0; i < properties.Count; i++) { var property = properties[i]; + if (property.GetCustomAttribute() != null) continue; + KnownTypeProperty p = new KnownTypeProperty(this); p.Name = property.Name; p.ReturnType = property.PropertyType; @@ -211,6 +210,8 @@ namespace Tango.Scripting.Editors.Intellisense { var ev = events[i]; + if (ev.GetCustomAttribute() != null) continue; + KnownTypeEvent p = new KnownTypeEvent(this); p.Name = ev.Name; diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs index 0802cf456..6c248b63d 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs @@ -47,7 +47,10 @@ namespace Tango.Scripting.Editors private char[] word_separators = { ' ', '\t', '\n', '.', '(', ',', '-', '*', '/', '+', '$', '=', '<', '>' }; private string[] _blocking_type_words = { "class", "void" }; + public event EventHandler BreakPointSymbolPressed; + private DispatcherTimer _update_timer; + private BreakPointMargin breakPointMargin; private Popup _popup; private FoldingManager foldingManager; private BraceFoldingStrategy foldingStrategy; @@ -58,6 +61,11 @@ namespace Tango.Scripting.Editors private List _declaredTypes; private bool _isLoadingTypes; private TextMarkerService errorMarkerService; + private List _breakPointSymbols; + private int _breakPointLineNumber; + private ScriptBreakPointSymbol _currentBreakPointSymbol; + private Point _currentBreakPointSymbolPosition; + private static JsonSerializerSettings _jsonSettings; private static Dictionary _knownTypesCache; private static String KNOWN_TYPES_CACHE_FOLDER; @@ -191,6 +199,47 @@ namespace Tango.Scripting.Editors public static readonly DependencyProperty ColorizeBrushProperty = DependencyProperty.Register("ColorizeBrush", typeof(Brush), typeof(ScriptEditor), new PropertyMetadata(new SolidColorBrush(Colors.YellowGreen) { Opacity = 0.5 })); + public Brush ErrorLineBrush + { + get { return (Brush)GetValue(ErrorLineBrushProperty); } + set { SetValue(ErrorLineBrushProperty, value); } + } + public static readonly DependencyProperty ErrorLineBrushProperty = + DependencyProperty.Register("ErrorLineBrush", typeof(Brush), typeof(ScriptEditor), new PropertyMetadata(new SolidColorBrush(Colors.Red) { Opacity = 0.5 })); + + public Brush DebugLineBrush + { + get { return (Brush)GetValue(DebugLineBrushProperty); } + set { SetValue(DebugLineBrushProperty, value); } + } + public static readonly DependencyProperty DebugLineBrushProperty = + DependencyProperty.Register("DebugLineBrush", typeof(Brush), typeof(ScriptEditor), new PropertyMetadata(new SolidColorBrush(Colors.Gray) { Opacity = 0.5 })); + + public Brush BreakPointLineBrush + { + get { return (Brush)GetValue(BreakPointLineBrushProperty); } + set { SetValue(BreakPointLineBrushProperty, value); } + } + public static readonly DependencyProperty BreakPointLineBrushProperty = + DependencyProperty.Register("BreakPointLineBrush", typeof(Brush), typeof(ScriptEditor), new PropertyMetadata(new SolidColorBrush(Colors.Yellow) { Opacity = 0.5 })); + + public IScriptSource ScriptSource + { + get { return (IScriptSource)GetValue(ScriptSourceProperty); } + set { SetValue(ScriptSourceProperty, value); } + } + public static readonly DependencyProperty ScriptSourceProperty = + DependencyProperty.Register("ScriptSource", typeof(IScriptSource), typeof(ScriptEditor), new PropertyMetadata(null)); + + public bool DisableBreakPoints + { + get { return (bool)GetValue(DisableBreakPointsProperty); } + set { SetValue(DisableBreakPointsProperty, value); } + } + public static readonly DependencyProperty DisableBreakPointsProperty = + DependencyProperty.Register("DisableBreakPoints", typeof(bool), typeof(ScriptEditor), new PropertyMetadata(false)); + + #endregion #region Constructors @@ -329,6 +378,17 @@ namespace Tango.Scripting.Editors TextArea.TextView.LineTransformers.Add(errorMarkerService); Unloaded += ScriptEditor_Unloaded; + + breakPointMargin = new BreakPointMargin(this); + _breakPointSymbols = new List(); + Loaded += ScriptEditor_Loaded; + + MouseMove += ScriptEditor_MouseMove; + } + + private void ScriptEditor_Loaded(object sender, RoutedEventArgs e) + { + TextArea.LeftMargins.Insert(0, breakPointMargin); } private void ScriptEditor_Unloaded(object sender, RoutedEventArgs e) @@ -492,6 +552,8 @@ namespace Tango.Scripting.Editors private void TextArea_TextEntered(object sender, TextCompositionEventArgs e) { + if (IsReadOnly) return; + try { List items = new List(); @@ -2486,6 +2548,246 @@ namespace Tango.Scripting.Editors errorMarkerService.RemoveAll(m => true); } + public void HighlightErrorLine(int lineNumber) + { + Document.BeginUpdate(); + + var line = Document.GetLineByNumber(lineNumber); + OffsetColorizer errorLineColrizer = new OffsetColorizer(line, line.Offset, line.EndOffset, ErrorLineBrush); + TextArea.TextView.LineTransformers.Add(errorLineColrizer); + + Document.EndUpdate(); + } + + public void HighlightDebugLine(int lineNumber) + { + Document.BeginUpdate(); + + var line = Document.GetLineByNumber(lineNumber); + OffsetColorizer errorLineColrizer = new OffsetColorizer(line, line.Offset, line.EndOffset, DebugLineBrush); + TextArea.TextView.LineTransformers.Add(errorLineColrizer); + + Document.EndUpdate(); + } + + public void HighlightBreakPoint(int lineNumber, List symbols) + { + _breakPointLineNumber = lineNumber; + _breakPointSymbols = symbols.ToList(); + _currentBreakPointSymbol = null; + + Document.BeginUpdate(); + + var line = Document.GetLineByNumber(lineNumber); + OffsetColorizer errorLineColrizer = new OffsetColorizer(line, line.Offset, line.EndOffset, BreakPointLineBrush); + TextArea.TextView.LineTransformers.Add(errorLineColrizer); + + var breakPoint = breakPointMargin.BreakPoints.FirstOrDefault(x => x.LineNumber == lineNumber); + breakPoint.IsActive = true; + breakPointMargin.InvalidateVisual(); + + Document.EndUpdate(); + } + + public void ResetBreakPointLine() + { + _breakPointSymbols = new List(); + _currentBreakPointSymbol = null; + ResetColorizationByKeyword(); + breakPointMargin.BreakPoints.ToList().ForEach(x => x.IsActive = false); + Mouse.OverrideCursor = null; + ClearErrors(); + breakPointMargin.InvalidateVisual(); + } + + public Point? GetLineVisualPosition(int lineNumber) + { + double top = TextArea.TextView.GetVisualTopByDocumentLine(lineNumber); + var visualLine = TextArea.TextView.GetVisualLine(lineNumber); + + if (visualLine != null) + { + var textLine = visualLine.GetTextLine(0); + var x = visualLine.GetTextLineVisualXPosition(textLine, visualLine.VisualLengthWithEndOfLineMarker); + var left = visualLine.VisualLengthWithEndOfLineMarker; + return new Point(x, top); + } + + return null; + } + + public List GetBreakPoints() + { + List breakPoints = new List(); + + foreach (var b in breakPointMargin.BreakPoints) + { + ScriptBreakPoint breakPoint = new ScriptBreakPoint(); + breakPoint.Script = ScriptSource; + breakPoint.LineNumber = b.LineNumber; + + var line = Document.GetLineByNumber(b.LineNumber); + breakPoint.LineStartOffset = line.Offset; + breakPoint.LineEndOffset = line.EndOffset; + + var symbols = _parser.GetContextSymbols(Document.Text, line.Offset); + + foreach (var symbol in symbols.Where(x => (x.Kind == SymbolKind.Property || x.Kind == SymbolKind.Field || x.Kind == SymbolKind.Local || x.Kind == SymbolKind.Parameter) && !x.IsUnassigned)) + { + if (symbol.Offset < line.Offset) + { + breakPoint.ContextSymbols.Add(new ScriptBreakPointSymbol() + { + Name = symbol.Name, + Offset = symbol.Offset, + Length = symbol.Length, + }); + } + } + + breakPoints.Add(breakPoint); + } + + return breakPoints; + } + + #endregion + + #region BreakPoint Symbols Search + + private void ScriptEditor_MouseMove(object sender, MouseEventArgs e) + { + if (IsReadOnly && _breakPointSymbols.Count > 0) + { + try + { + var word_separators_plus = word_separators.ToList(); + word_separators_plus.Add(')'); + word_separators_plus.Add(';'); + + var textView = TextArea.TextView; + Point position = e.GetPosition(textView); + position.Y += textView.VerticalOffset; + VisualLine visualLine = textView.GetVisualLineFromVisualTop(position.Y); + int columnIndex = visualLine.GetVisualColumnFloor(position, false); + String line = Document.GetText(visualLine.FirstDocumentLine.Offset, visualLine.FirstDocumentLine.Length); + if (columnIndex < line.Length) + { + int wordStartIndex = columnIndex; + int wordEndIndex = columnIndex; + + while (wordStartIndex > 0) + { + if (word_separators_plus.Contains(line[wordStartIndex])) break; + wordStartIndex--; + } + + while (wordEndIndex < line.Length) + { + if (word_separators_plus.Contains(line[wordEndIndex])) break; + wordEndIndex++; + } + + if (wordStartIndex > 0) + { + wordStartIndex++; + } + + String word = line.Substring(wordStartIndex, wordEndIndex - wordStartIndex); + + var breakPointSymbol = _breakPointSymbols.FirstOrDefault(x => x.Name == word); + + if (breakPointSymbol != null) + { + int wordStartOffset = visualLine.FirstDocumentLine.Offset + wordStartIndex; + + ClearErrors(); + ITextMarker marker = errorMarkerService.Create(wordStartOffset, word.Length); + marker.MarkerTypes = TextMarkerTypes.NormalUnderline; + marker.MarkerColor = Colors.Yellow; + Mouse.OverrideCursor = Cursors.Hand; + + _currentBreakPointSymbol = breakPointSymbol; + _currentBreakPointSymbolPosition = visualLine.GetVisualPosition(wordEndIndex, VisualYPosition.LineTop); + } + else + { + _currentBreakPointSymbol = null; + Mouse.OverrideCursor = null; + ClearErrors(); + } + } + else + { + _currentBreakPointSymbol = null; + Mouse.OverrideCursor = null; + ClearErrors(); + } + } + catch (Exception ex) + { + _currentBreakPointSymbol = null; + Mouse.OverrideCursor = null; + ClearErrors(); + Debug.WriteLine(ex.Message); + } + } + } + + protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e) + { + base.OnPreviewMouseLeftButtonUp(e); + + if (_currentBreakPointSymbol != null) + { + Mouse.OverrideCursor = null; + Debug.WriteLine($"Pressed on break point symbol: {_currentBreakPointSymbol.Name}"); + BreakPointSymbolPressed?.Invoke(this, new BreakPointSymbolPressedEventArgs() + { + BreakPointSymbol = _currentBreakPointSymbol, + Position = _currentBreakPointSymbolPosition + }); + } + } + + public String GetCaretWord() + { + try + { + var word_separators_plus = word_separators.ToList(); + word_separators_plus.Add(')'); + word_separators_plus.Add(';'); + + int wordStartOffset = CaretOffset; + int wordEndOffset = CaretOffset; + + while (wordStartOffset > 0) + { + if (word_separators_plus.Contains(Document.Text[wordStartOffset])) break; + wordStartOffset--; + } + + while (wordEndOffset < Document.Text.Length) + { + if (word_separators_plus.Contains(Document.Text[wordEndOffset])) break; + wordEndOffset++; + } + + if (wordStartOffset > 0) + { + wordStartOffset++; + } + + String word = Document.Text.Substring(wordStartOffset, wordEndOffset - wordStartOffset); + + return word; + } + catch + { + return null; + } + } + #endregion } } diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj index a70bbf3de..11e023f86 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj @@ -180,6 +180,7 @@ GlobalVersionInfo.cs + @@ -196,6 +197,7 @@ + @@ -207,6 +209,7 @@ + @@ -651,6 +654,9 @@ + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors_di35u2uj_wpftmp.csproj b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors_di35u2uj_wpftmp.csproj new file mode 100644 index 000000000..70a4840c4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors_di35u2uj_wpftmp.csproj @@ -0,0 +1,628 @@ + + + + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0} + Debug + AnyCPU + Library + Tango.Scripting.Editors + Tango.Scripting.Editors + v4.6.1 + Properties + "C:\Program Files\SharpDevelop\3.0\bin\..\AddIns\AddIns\Misc\SourceAnalysis\Settings.SourceAnalysis" + False + False + 4 + false + false + ICSharpCode.AvalonEdit.snk + False + File + False + -Microsoft.Design#CA1020;-Microsoft.Design#CA1033;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810 + ..\bin\$(Configuration) + ..\bin\$(Configuration)\ICSharpCode.AvalonEdit.xml + 1607 + + + SAK + SAK + SAK + SAK + + + true + Full + False + True + DEBUG;TRACE;DOTNET4 + + + false + PdbOnly + True + False + TRACE;DOTNET4 + + + False + Auto + 4194304 + AnyCPU + 4096 + + + ..\..\Build\Scripting\Debug\ + + + false + TRACE;DEBUG + + + false + ..\..\Build\Scripting\Release\ + + + + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + + + GlobalVersionInfo.cs + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UndoStack.cs + + + + + + UndoStack.cs + + + DocumentLine.cs + + + + + TextDocument.cs + + + + + TextAnchor.cs + + + TextAnchor.cs + + + + + + UndoStack.cs + + + + + ILineTracker.cs + + + + + + + + + + + + + Selection.cs + + + + + + + + + + + + + + + IReadOnlySectionProvider.cs + + + Selection.cs + + + + Selection.cs + + + Selection.cs + + + Selection.cs + + + Selection.cs + + + + + + IReadOnlySectionProvider.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IBackgroundRenderer.cs + + + HeightTree.cs + + + IVisualLineTransformer.cs + + + + + IVisualLineTransformer.cs + + + + TextView.cs + + + + HeightTree.cs + + + HeightTree.cs + + + + + VisualLineElementGenerator.cs + + + TextView.cs + + + + TextView.cs + + + TextView.cs + + + + + FormattedTextElement.cs + + + + TextView.cs + + + + + TextView.cs + + + + + + + VisualLine.cs + + + + + + VisualLine.cs + + + VisualLineElementGenerator.cs + + + VisualLine.cs + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + TextDocument.cs + + + TextDocument.cs + + + + DocumentLine.cs + + + + + TextEditor.cs + + + + + ObserveAddRemoveCollection.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AXmlParser.cs + + + + + + + + + AXmlParser.cs + + + AXmlParser.cs + + + AXmlText.cs + + + AXmlParser.cs + + + + + + + + + + + + + + + + + + + + + + + + + + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} + Tango.Core + + + {5812E1C6-ABAA-4066-94AC-971C27B4F46A} + Tango.Scripting.Core + + + {8d8f06ed-7f75-4933-b0c5-829b0ff654d0} + Tango.Scripting.Formatting + + + {1e938fd2-c669-4738-98c9-77f96ce4d451} + Tango.Scripting + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs index d2fc9e02b..cd9977520 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -24,1121 +25,1229 @@ using Tango.Scripting.Editors.Utils; namespace Tango.Scripting.Editors { - /// - /// The text editor control. - /// Contains a scrollable TextArea. - /// - [Localizability(LocalizationCategory.Text), ContentProperty("Text")] - public class TextEditor : Control, ITextEditorComponent, IServiceProvider, IWeakEventListener - { - #region Constructors - static TextEditor() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(TextEditor), - new FrameworkPropertyMetadata(typeof(TextEditor))); - FocusableProperty.OverrideMetadata(typeof(TextEditor), - new FrameworkPropertyMetadata(Boxes.True)); - } - - /// - /// Creates a new TextEditor instance. - /// - public TextEditor() : this(new TextArea()) - { - } - - /// - /// Creates a new TextEditor instance. - /// - protected TextEditor(TextArea textArea) - { - if (textArea == null) - throw new ArgumentNullException("textArea"); - this.textArea = textArea; - - textArea.TextView.Services.AddService(typeof(TextEditor), this); - - SetCurrentValue(OptionsProperty, textArea.Options); - SetCurrentValue(DocumentProperty, new TextDocument()); - } - - #endregion - - /// - protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() - { - return new TextEditorAutomationPeer(this); - } - - /// Forward focus to TextArea. - /// - protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) - { - base.OnGotKeyboardFocus(e); - if (e.NewFocus == this) { - Keyboard.Focus(this.TextArea); - e.Handled = true; - } - } - - #region Document property - /// - /// Document property. - /// - public static readonly DependencyProperty DocumentProperty - = TextView.DocumentProperty.AddOwner( - typeof(TextEditor), new FrameworkPropertyMetadata(OnDocumentChanged)); - - /// - /// Gets/Sets the document displayed by the text editor. - /// This is a dependency property. - /// - public TextDocument Document { - get { return (TextDocument)GetValue(DocumentProperty); } - set { SetValue(DocumentProperty, value); } - } - - /// - /// Occurs when the document property has changed. - /// - public event EventHandler DocumentChanged; - - /// - /// Raises the event. - /// - protected virtual void OnDocumentChanged(EventArgs e) - { - if (DocumentChanged != null) { - DocumentChanged(this, e); - } - } - - static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) - { - ((TextEditor)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue); - } - - void OnDocumentChanged(TextDocument oldValue, TextDocument newValue) - { - if (oldValue != null) { - TextDocumentWeakEventManager.TextChanged.RemoveListener(oldValue, this); - PropertyChangedEventManager.RemoveListener(oldValue.UndoStack, this, "IsOriginalFile"); - } - textArea.Document = newValue; - if (newValue != null) { - TextDocumentWeakEventManager.TextChanged.AddListener(newValue, this); - PropertyChangedEventManager.AddListener(newValue.UndoStack, this, "IsOriginalFile"); - } - OnDocumentChanged(EventArgs.Empty); - OnTextChanged(EventArgs.Empty); - } - #endregion - - #region Options property - /// - /// Options property. - /// - public static readonly DependencyProperty OptionsProperty - = TextView.OptionsProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(OnOptionsChanged)); - - /// - /// Gets/Sets the options currently used by the text editor. - /// - public TextEditorOptions Options { - get { return (TextEditorOptions)GetValue(OptionsProperty); } - set { SetValue(OptionsProperty, value); } - } - - /// - /// Occurs when a text editor option has changed. - /// - public event PropertyChangedEventHandler OptionChanged; - - /// - /// Raises the event. - /// - protected virtual void OnOptionChanged(PropertyChangedEventArgs e) - { - if (OptionChanged != null) { - OptionChanged(this, e); - } - } - - static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) - { - ((TextEditor)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue); - } - - void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue) - { - if (oldValue != null) { - PropertyChangedWeakEventManager.RemoveListener(oldValue, this); - } - textArea.Options = newValue; - if (newValue != null) { - PropertyChangedWeakEventManager.AddListener(newValue, this); - } - OnOptionChanged(new PropertyChangedEventArgs(null)); - } - - /// - protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) - { - if (managerType == typeof(PropertyChangedWeakEventManager)) { - OnOptionChanged((PropertyChangedEventArgs)e); - return true; - } else if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) { - OnTextChanged(e); - return true; - } else if (managerType == typeof(PropertyChangedEventManager)) { - return HandleIsOriginalChanged((PropertyChangedEventArgs)e); - } - return false; - } - - bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) - { - return ReceiveWeakEvent(managerType, sender, e); - } - #endregion - - #region Text property - /// - /// Gets/Sets the text of the current document. - /// - [Localizability(LocalizationCategory.Text), DefaultValue("")] - public string Text { - get { - TextDocument document = this.Document; - return document != null ? document.Text : string.Empty; - } - set { - TextDocument document = GetDocument(); - document.Text = value ?? string.Empty; - // after replacing the full text, the caret is positioned at the end of the document - // - reset it to the beginning. - this.CaretOffset = 0; - document.UndoStack.ClearAll(); - } - } - - TextDocument GetDocument() - { - TextDocument document = this.Document; - if (document == null) - throw ThrowUtil.NoDocumentAssigned(); - return document; - } - - /// - /// Occurs when the Text property changes. - /// - public event EventHandler TextChanged; - - /// - /// Raises the event. - /// - protected virtual void OnTextChanged(EventArgs e) - { - if (TextChanged != null) { - TextChanged(this, e); - } - } - #endregion - - #region TextArea / ScrollViewer properties - readonly TextArea textArea; - ScrollViewer scrollViewer; - - /// - /// Is called after the template was applied. - /// - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - scrollViewer = (ScrollViewer)Template.FindName("PART_ScrollViewer", this); - } - - /// - /// Gets the text area. - /// - public TextArea TextArea { - get { - return textArea; - } - } - - /// - /// Gets the scroll viewer used by the text editor. - /// This property can return null if the template has not been applied / does not contain a scroll viewer. - /// - internal ScrollViewer ScrollViewer { - get { return scrollViewer; } - } - - bool CanExecute(RoutedUICommand command) - { - TextArea textArea = this.TextArea; - if (textArea == null) - return false; - else - return command.CanExecute(null, textArea); - } - - void Execute(RoutedUICommand command) - { - TextArea textArea = this.TextArea; - if (textArea != null) - command.Execute(null, textArea); - } - #endregion - - #region Syntax highlighting - /// - /// The property. - /// - public static readonly DependencyProperty SyntaxHighlightingProperty = - DependencyProperty.Register("SyntaxHighlighting", typeof(IHighlightingDefinition), typeof(TextEditor), - new FrameworkPropertyMetadata(OnSyntaxHighlightingChanged)); - - - /// - /// Gets/sets the syntax highlighting definition used to colorize the text. - /// - public IHighlightingDefinition SyntaxHighlighting { - get { return (IHighlightingDefinition)GetValue(SyntaxHighlightingProperty); } - set { SetValue(SyntaxHighlightingProperty, value); } - } - - IVisualLineTransformer colorizer; - - static void OnSyntaxHighlightingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((TextEditor)d).OnSyntaxHighlightingChanged(e.NewValue as IHighlightingDefinition); - } - - void OnSyntaxHighlightingChanged(IHighlightingDefinition newValue) - { - if (colorizer != null) { - this.TextArea.TextView.LineTransformers.Remove(colorizer); - colorizer = null; - } - if (newValue != null) { - colorizer = CreateColorizer(newValue); - this.TextArea.TextView.LineTransformers.Insert(0, colorizer); - } - } - - /// - /// Creates the highlighting colorizer for the specified highlighting definition. - /// Allows derived classes to provide custom colorizer implementations for special highlighting definitions. - /// - /// - protected virtual IVisualLineTransformer CreateColorizer(IHighlightingDefinition highlightingDefinition) - { - if (highlightingDefinition == null) - throw new ArgumentNullException("highlightingDefinition"); - return new HighlightingColorizer(highlightingDefinition.MainRuleSet); - } - #endregion - - #region WordWrap - /// - /// Word wrap dependency property. - /// - public static readonly DependencyProperty WordWrapProperty = - DependencyProperty.Register("WordWrap", typeof(bool), typeof(TextEditor), - new FrameworkPropertyMetadata(Boxes.False)); - - /// - /// Specifies whether the text editor uses word wrapping. - /// - /// - /// Setting WordWrap=true has the same effect as setting HorizontalScrollBarVisibility=Disabled and will override the - /// HorizontalScrollBarVisibility setting. - /// - public bool WordWrap { - get { return (bool)GetValue(WordWrapProperty); } - set { SetValue(WordWrapProperty, Boxes.Box(value)); } - } - #endregion - - #region IsReadOnly - /// - /// IsReadOnly dependency property. - /// - public static readonly DependencyProperty IsReadOnlyProperty = - DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(TextEditor), - new FrameworkPropertyMetadata(Boxes.False, OnIsReadOnlyChanged)); - - /// - /// Specifies whether the user can change the text editor content. - /// Setting this property will replace the - /// TextArea.ReadOnlySectionProvider. - /// - public bool IsReadOnly { - get { return (bool)GetValue(IsReadOnlyProperty); } - set { SetValue(IsReadOnlyProperty, Boxes.Box(value)); } - } - - static void OnIsReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - TextEditor editor = d as TextEditor; - if (editor != null) { - if ((bool)e.NewValue) - editor.TextArea.ReadOnlySectionProvider = ReadOnlyDocument.Instance; - else - editor.TextArea.ReadOnlySectionProvider = NoReadOnlySections.Instance; - - TextEditorAutomationPeer peer = TextEditorAutomationPeer.FromElement(editor) as TextEditorAutomationPeer; - if (peer != null) { - peer.RaiseIsReadOnlyChanged((bool)e.OldValue, (bool)e.NewValue); - } - } - } - #endregion - - #region IsModified - /// - /// Dependency property for - /// - public static readonly DependencyProperty IsModifiedProperty = - DependencyProperty.Register("IsModified", typeof(bool), typeof(TextEditor), - new FrameworkPropertyMetadata(Boxes.False, OnIsModifiedChanged)); - - /// - /// Gets/Sets the 'modified' flag. - /// - public bool IsModified { - get { return (bool)GetValue(IsModifiedProperty); } - set { SetValue(IsModifiedProperty, Boxes.Box(value)); } - } - - static void OnIsModifiedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - TextEditor editor = d as TextEditor; - if (editor != null) { - TextDocument document = editor.Document; - if (document != null) { - UndoStack undoStack = document.UndoStack; - if ((bool)e.NewValue) { - if (undoStack.IsOriginalFile) - undoStack.DiscardOriginalFileMarker(); - } else { - undoStack.MarkAsOriginalFile(); - } - } - } - } - - bool HandleIsOriginalChanged(PropertyChangedEventArgs e) - { - if (e.PropertyName == "IsOriginalFile") { - TextDocument document = this.Document; - if (document != null) { - SetCurrentValue(IsModifiedProperty, Boxes.Box(!document.UndoStack.IsOriginalFile)); - } - return true; - } else { - return false; - } - } - #endregion - - #region ShowLineNumbers - /// - /// ShowLineNumbers dependency property. - /// - public static readonly DependencyProperty ShowLineNumbersProperty = - DependencyProperty.Register("ShowLineNumbers", typeof(bool), typeof(TextEditor), - new FrameworkPropertyMetadata(Boxes.False, OnShowLineNumbersChanged)); - - /// - /// Specifies whether line numbers are shown on the left to the text view. - /// - public bool ShowLineNumbers { - get { return (bool)GetValue(ShowLineNumbersProperty); } - set { SetValue(ShowLineNumbersProperty, Boxes.Box(value)); } - } - - static void OnShowLineNumbersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - TextEditor editor = (TextEditor)d; - var leftMargins = editor.TextArea.LeftMargins; - if ((bool)e.NewValue) { - LineNumberMargin lineNumbers = new LineNumberMargin(); - Line line = (Line)DottedLineMargin.Create(); - leftMargins.Insert(0, lineNumbers); - leftMargins.Insert(1, line); - var lineNumbersForeground = new Binding("LineNumbersForeground") { Source = editor }; - line.SetBinding(Line.StrokeProperty, lineNumbersForeground); - lineNumbers.SetBinding(Control.ForegroundProperty, lineNumbersForeground); - } else { - for (int i = 0; i < leftMargins.Count; i++) { - if (leftMargins[i] is LineNumberMargin) { - leftMargins.RemoveAt(i); - if (i < leftMargins.Count && DottedLineMargin.IsDottedLineMargin(leftMargins[i])) { - leftMargins.RemoveAt(i); - } - break; - } - } - } - } - #endregion - - #region LineNumbersForeground - /// - /// LineNumbersForeground dependency property. - /// - public static readonly DependencyProperty LineNumbersForegroundProperty = - DependencyProperty.Register("LineNumbersForeground", typeof(Brush), typeof(TextEditor), - new FrameworkPropertyMetadata(Brushes.Gray, OnLineNumbersForegroundChanged)); - - /// - /// Gets/sets the Brush used for displaying the foreground color of line numbers. - /// - public Brush LineNumbersForeground { - get { return (Brush)GetValue(LineNumbersForegroundProperty); } - set { SetValue(LineNumbersForegroundProperty, value); } - } - - static void OnLineNumbersForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - TextEditor editor = (TextEditor)d; - var lineNumberMargin = editor.TextArea.LeftMargins.FirstOrDefault(margin => margin is LineNumberMargin) as LineNumberMargin;; - - if (lineNumberMargin != null) { - lineNumberMargin.SetValue(Control.ForegroundProperty, e.NewValue); - } - } - #endregion - - #region TextBoxBase-like methods - /// - /// Appends text to the end of the document. - /// - public void AppendText(string textData) - { - var document = GetDocument(); - document.Insert(document.TextLength, textData); - } - - /// - /// Begins a group of document changes. - /// - public void BeginChange() - { - GetDocument().BeginUpdate(); - } - - /// - /// Copies the current selection to the clipboard. - /// - public void Copy() - { - Execute(ApplicationCommands.Copy); - } - - /// - /// Removes the current selection and copies it to the clipboard. - /// - public void Cut() - { - Execute(ApplicationCommands.Cut); - } - - /// - /// Begins a group of document changes and returns an object that ends the group of document - /// changes when it is disposed. - /// - public IDisposable DeclareChangeBlock() - { - return GetDocument().RunUpdate(); - } - - /// - /// Ends the current group of document changes. - /// - public void EndChange() - { - GetDocument().EndUpdate(); - } - - /// - /// Scrolls one line down. - /// - public void LineDown() - { - if (scrollViewer != null) - scrollViewer.LineDown(); - } - - /// - /// Scrolls to the left. - /// - public void LineLeft() - { - if (scrollViewer != null) - scrollViewer.LineLeft(); - } - - /// - /// Scrolls to the right. - /// - public void LineRight() - { - if (scrollViewer != null) - scrollViewer.LineRight(); - } - - /// - /// Scrolls one line up. - /// - public void LineUp() - { - if (scrollViewer != null) - scrollViewer.LineUp(); - } - - /// - /// Scrolls one page down. - /// - public void PageDown() - { - if (scrollViewer != null) - scrollViewer.PageDown(); - } - - /// - /// Scrolls one page up. - /// - public void PageUp() - { - if (scrollViewer != null) - scrollViewer.PageUp(); - } - - /// - /// Scrolls one page left. - /// - public void PageLeft() - { - if (scrollViewer != null) - scrollViewer.PageLeft(); - } - - /// - /// Scrolls one page right. - /// - public void PageRight() - { - if (scrollViewer != null) - scrollViewer.PageRight(); - } - - /// - /// Pastes the clipboard content. - /// - public void Paste() - { - Execute(ApplicationCommands.Paste); - } - - /// - /// Redoes the most recent undone command. - /// - /// True is the redo operation was successful, false is the redo stack is empty. - public bool Redo() - { - if (CanExecute(ApplicationCommands.Redo)) { - Execute(ApplicationCommands.Redo); - return true; - } - return false; - } - - /// - /// Scrolls to the end of the document. - /// - public void ScrollToEnd() - { - ApplyTemplate(); // ensure scrollViewer is created - if (scrollViewer != null) - scrollViewer.ScrollToEnd(); - } - - /// - /// Scrolls to the start of the document. - /// - public void ScrollToHome() - { - ApplyTemplate(); // ensure scrollViewer is created - if (scrollViewer != null) - scrollViewer.ScrollToHome(); - } - - /// - /// Scrolls to the specified position in the document. - /// - public void ScrollToHorizontalOffset(double offset) - { - ApplyTemplate(); // ensure scrollViewer is created - if (scrollViewer != null) - scrollViewer.ScrollToHorizontalOffset(offset); - } - - /// - /// Scrolls to the specified position in the document. - /// - public void ScrollToVerticalOffset(double offset) - { - ApplyTemplate(); // ensure scrollViewer is created - if (scrollViewer != null) - scrollViewer.ScrollToVerticalOffset(offset); - } - - /// - /// Selects the entire text. - /// - public void SelectAll() - { - Execute(ApplicationCommands.SelectAll); - } - - /// - /// Undoes the most recent command. - /// - /// True is the undo operation was successful, false is the undo stack is empty. - public bool Undo() - { - if (CanExecute(ApplicationCommands.Undo)) { - Execute(ApplicationCommands.Undo); - return true; - } - return false; - } - - /// - /// Gets if the most recent undone command can be redone. - /// - public bool CanRedo { - get { return CanExecute(ApplicationCommands.Redo); } - } - - /// - /// Gets if the most recent command can be undone. - /// - public bool CanUndo { - get { return CanExecute(ApplicationCommands.Undo); } - } - - /// - /// Gets the vertical size of the document. - /// - public double ExtentHeight { - get { - return scrollViewer != null ? scrollViewer.ExtentHeight : 0; - } - } - - /// - /// Gets the horizontal size of the current document region. - /// - public double ExtentWidth { - get { - return scrollViewer != null ? scrollViewer.ExtentWidth : 0; - } - } - - /// - /// Gets the horizontal size of the viewport. - /// - public double ViewportHeight { - get { - return scrollViewer != null ? scrollViewer.ViewportHeight : 0; - } - } - - /// - /// Gets the horizontal size of the viewport. - /// - public double ViewportWidth { - get { - return scrollViewer != null ? scrollViewer.ViewportWidth : 0; - } - } - - /// - /// Gets the vertical scroll position. - /// - public double VerticalOffset { - get { - return scrollViewer != null ? scrollViewer.VerticalOffset : 0; - } - } - - /// - /// Gets the horizontal scroll position. - /// - public double HorizontalOffset { - get { - return scrollViewer != null ? scrollViewer.HorizontalOffset : 0; - } - } - #endregion - - #region TextBox methods - /// - /// Gets/Sets the selected text. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public string SelectedText { - get { - TextArea textArea = this.TextArea; - // We'll get the text from the whole surrounding segment. - // This is done to ensure that SelectedText.Length == SelectionLength. - if (textArea != null && textArea.Document != null && !textArea.Selection.IsEmpty) - return textArea.Document.GetText(textArea.Selection.SurroundingSegment); - else - return string.Empty; - } - set { - if (value == null) - throw new ArgumentNullException("value"); - TextArea textArea = this.TextArea; - if (textArea != null && textArea.Document != null) { - int offset = this.SelectionStart; - int length = this.SelectionLength; - textArea.Document.Replace(offset, length, value); - // keep inserted text selected - textArea.Selection = SimpleSelection.Create(textArea, offset, offset + value.Length); - } - } - } - - /// - /// Gets/sets the caret position. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int CaretOffset { - get { - TextArea textArea = this.TextArea; - if (textArea != null) - return textArea.Caret.Offset; - else - return 0; - } - set { - TextArea textArea = this.TextArea; - if (textArea != null) - textArea.Caret.Offset = value; - } - } - - /// - /// Gets/sets the start position of the selection. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int SelectionStart { - get { - TextArea textArea = this.TextArea; - if (textArea != null) { - if (textArea.Selection.IsEmpty) - return textArea.Caret.Offset; - else - return textArea.Selection.SurroundingSegment.Offset; - } else { - return 0; - } - } - set { - Select(value, SelectionLength); - } - } - - /// - /// Gets/sets the length of the selection. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int SelectionLength { - get { - TextArea textArea = this.TextArea; - if (textArea != null && !textArea.Selection.IsEmpty) - return textArea.Selection.SurroundingSegment.Length; - else - return 0; - } - set { - Select(SelectionStart, value); - } - } - - /// - /// Selects the specified text section. - /// - public void Select(int start, int length) - { - int documentLength = Document != null ? Document.TextLength : 0; - if (start < 0 || start > documentLength) - throw new ArgumentOutOfRangeException("start", start, "Value must be between 0 and " + documentLength); - if (length < 0 || start + length > documentLength) - throw new ArgumentOutOfRangeException("length", length, "Value must be between 0 and " + (documentLength - length)); - textArea.Selection = SimpleSelection.Create(textArea, start, start + length); - textArea.Caret.Offset = start + length; - } - - /// - /// Gets the number of lines in the document. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int LineCount { - get { - TextDocument document = this.Document; - if (document != null) - return document.LineCount; - else - return 1; - } - } - - /// - /// Clears the text. - /// - public void Clear() - { - this.Text = string.Empty; - } - #endregion - - #region Loading from stream - /// - /// Loads the text from the stream, auto-detecting the encoding. - /// - /// - /// This method sets to false. - /// - public void Load(Stream stream) - { - using (StreamReader reader = FileReader.OpenStream(stream, this.Encoding ?? Encoding.UTF8)) { - this.Text = reader.ReadToEnd(); - this.Encoding = reader.CurrentEncoding; // assign encoding after ReadToEnd() so that the StreamReader can autodetect the encoding - } - this.IsModified = false; - } - - /// - /// Loads the text from the stream, auto-detecting the encoding. - /// - public void Load(string fileName) - { - if (fileName == null) - throw new ArgumentNullException("fileName"); - using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { - Load(fs); - } - } - - /// - /// Gets/sets the encoding used when the file is saved. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public Encoding Encoding { get; set; } - - /// - /// Saves the text to the stream. - /// - /// - /// This method sets to false. - /// - public void Save(Stream stream) - { - if (stream == null) - throw new ArgumentNullException("stream"); - StreamWriter writer = new StreamWriter(stream, this.Encoding ?? Encoding.UTF8); - writer.Write(this.Text); - writer.Flush(); - // do not close the stream - this.IsModified = false; - } - - /// - /// Saves the text to the file. - /// - public void Save(string fileName) - { - if (fileName == null) - throw new ArgumentNullException("fileName"); - using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) { - Save(fs); - } - } - #endregion - - #region MouseHover events - /// - /// The PreviewMouseHover event. - /// - public static readonly RoutedEvent PreviewMouseHoverEvent = - TextView.PreviewMouseHoverEvent.AddOwner(typeof(TextEditor)); - - /// - /// The MouseHover event. - /// - public static readonly RoutedEvent MouseHoverEvent = - TextView.MouseHoverEvent.AddOwner(typeof(TextEditor)); - - - /// - /// The PreviewMouseHoverStopped event. - /// - public static readonly RoutedEvent PreviewMouseHoverStoppedEvent = - TextView.PreviewMouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); - - /// - /// The MouseHoverStopped event. - /// - public static readonly RoutedEvent MouseHoverStoppedEvent = - TextView.MouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); - - - /// - /// Occurs when the mouse has hovered over a fixed location for some time. - /// - public event MouseEventHandler PreviewMouseHover { - add { AddHandler(PreviewMouseHoverEvent, value); } - remove { RemoveHandler(PreviewMouseHoverEvent, value); } - } - - /// - /// Occurs when the mouse has hovered over a fixed location for some time. - /// - public event MouseEventHandler MouseHover { - add { AddHandler(MouseHoverEvent, value); } - remove { RemoveHandler(MouseHoverEvent, value); } - } - - /// - /// Occurs when the mouse had previously hovered but now started moving again. - /// - public event MouseEventHandler PreviewMouseHoverStopped { - add { AddHandler(PreviewMouseHoverStoppedEvent, value); } - remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); } - } - - /// - /// Occurs when the mouse had previously hovered but now started moving again. - /// - public event MouseEventHandler MouseHoverStopped { - add { AddHandler(MouseHoverStoppedEvent, value); } - remove { RemoveHandler(MouseHoverStoppedEvent, value); } - } - #endregion - - #region ScrollBarVisibility - /// - /// Dependency property for - /// - public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty = ScrollViewer.HorizontalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); - - /// - /// Gets/Sets the horizontal scroll bar visibility. - /// - public ScrollBarVisibility HorizontalScrollBarVisibility { - get { return (ScrollBarVisibility)GetValue(HorizontalScrollBarVisibilityProperty); } - set { SetValue(HorizontalScrollBarVisibilityProperty, value); } - } - - /// - /// Dependency property for - /// - public static readonly DependencyProperty VerticalScrollBarVisibilityProperty = ScrollViewer.VerticalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); - - /// - /// Gets/Sets the vertical scroll bar visibility. - /// - public ScrollBarVisibility VerticalScrollBarVisibility { - get { return (ScrollBarVisibility)GetValue(VerticalScrollBarVisibilityProperty); } - set { SetValue(VerticalScrollBarVisibilityProperty, value); } - } - #endregion - - object IServiceProvider.GetService(Type serviceType) - { - return textArea.GetService(serviceType); - } - - /// - /// Gets the text view position from a point inside the editor. - /// - /// The position, relative to top left - /// corner of TextEditor control - /// The text view position, or null if the point is outside the document. - public TextViewPosition? GetPositionFromPoint(Point point) - { - if (this.Document == null) - return null; - TextView textView = this.TextArea.TextView; - return textView.GetPosition(TranslatePoint(point, textView) + textView.ScrollOffset); - } - - /// - /// Scrolls to the specified line. - /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). - /// - public void ScrollToLine(int line) - { - ScrollTo(line, -1); - } - - /// - /// Scrolls to the specified line/column. - /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). - /// - public void ScrollTo(int line, int column) - { - const double MinimumScrollPercentage = 0.3; - - TextView textView = textArea.TextView; - TextDocument document = textView.Document; - if (scrollViewer != null && document != null) { - if (line < 1) - line = 1; - if (line > document.LineCount) - line = document.LineCount; - - IScrollInfo scrollInfo = textView; - if (!scrollInfo.CanHorizontallyScroll) { - // Word wrap is enabled. Ensure that we have up-to-date info about line height so that we scroll - // to the correct position. - // This avoids that the user has to repeat the ScrollTo() call several times when there are very long lines. - VisualLine vl = textView.GetOrConstructVisualLine(document.GetLineByNumber(line)); - double remainingHeight = scrollViewer.ViewportHeight / 2; - while (remainingHeight > 0) { - DocumentLine prevLine = vl.FirstDocumentLine.PreviousLine; - if (prevLine == null) - break; - vl = textView.GetOrConstructVisualLine(prevLine); - remainingHeight -= vl.Height; - } - } - - Point p = textArea.TextView.GetVisualPosition(new TextViewPosition(line, Math.Max(1, column)), VisualYPosition.LineMiddle); - double verticalPos = p.Y - scrollViewer.ViewportHeight / 2; - if (Math.Abs(verticalPos - scrollViewer.VerticalOffset) > MinimumScrollPercentage * scrollViewer.ViewportHeight) { - scrollViewer.ScrollToVerticalOffset(Math.Max(0, verticalPos)); - } - if (column > 0) { - if (p.X > scrollViewer.ViewportWidth - Caret.MinimumDistanceToViewBorder * 2) { - double horizontalPos = Math.Max(0, p.X - scrollViewer.ViewportWidth / 2); - if (Math.Abs(horizontalPos - scrollViewer.HorizontalOffset) > MinimumScrollPercentage * scrollViewer.ViewportWidth) { - scrollViewer.ScrollToHorizontalOffset(horizontalPos); - } - } else { - scrollViewer.ScrollToHorizontalOffset(0); - } - } - } - } - } + /// + /// The text editor control. + /// Contains a scrollable TextArea. + /// + [Localizability(LocalizationCategory.Text), ContentProperty("Text")] + public class TextEditor : Control, ITextEditorComponent, IServiceProvider, IWeakEventListener + { + #region Constructors + static TextEditor() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TextEditor), + new FrameworkPropertyMetadata(typeof(TextEditor))); + FocusableProperty.OverrideMetadata(typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.True)); + } + + /// + /// Creates a new TextEditor instance. + /// + public TextEditor() : this(new TextArea()) + { + } + + /// + /// Creates a new TextEditor instance. + /// + protected TextEditor(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.textArea = textArea; + + textArea.TextView.Services.AddService(typeof(TextEditor), this); + + SetCurrentValue(OptionsProperty, textArea.Options); + SetCurrentValue(DocumentProperty, new TextDocument()); + } + + #endregion + + /// + protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() + { + return new TextEditorAutomationPeer(this); + } + + /// Forward focus to TextArea. + /// + protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + base.OnGotKeyboardFocus(e); + if (e.NewFocus == this) + { + Keyboard.Focus(this.TextArea); + e.Handled = true; + } + } + + #region Document property + /// + /// Document property. + /// + public static readonly DependencyProperty DocumentProperty + = TextView.DocumentProperty.AddOwner( + typeof(TextEditor), new FrameworkPropertyMetadata(OnDocumentChanged)); + + /// + /// Gets/Sets the document displayed by the text editor. + /// This is a dependency property. + /// + public TextDocument Document + { + get { return (TextDocument)GetValue(DocumentProperty); } + set { SetValue(DocumentProperty, value); } + } + + /// + /// Occurs when the document property has changed. + /// + public event EventHandler DocumentChanged; + + /// + /// Raises the event. + /// + protected virtual void OnDocumentChanged(EventArgs e) + { + if (DocumentChanged != null) + { + DocumentChanged(this, e); + } + } + + static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + ((TextEditor)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue); + } + + void OnDocumentChanged(TextDocument oldValue, TextDocument newValue) + { + if (oldValue != null) + { + TextDocumentWeakEventManager.TextChanged.RemoveListener(oldValue, this); + PropertyChangedEventManager.RemoveListener(oldValue.UndoStack, this, "IsOriginalFile"); + } + textArea.Document = newValue; + if (newValue != null) + { + TextDocumentWeakEventManager.TextChanged.AddListener(newValue, this); + PropertyChangedEventManager.AddListener(newValue.UndoStack, this, "IsOriginalFile"); + } + OnDocumentChanged(EventArgs.Empty); + OnTextChanged(EventArgs.Empty); + } + #endregion + + #region Options property + /// + /// Options property. + /// + public static readonly DependencyProperty OptionsProperty + = TextView.OptionsProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(OnOptionsChanged)); + + /// + /// Gets/Sets the options currently used by the text editor. + /// + public TextEditorOptions Options + { + get { return (TextEditorOptions)GetValue(OptionsProperty); } + set { SetValue(OptionsProperty, value); } + } + + /// + /// Occurs when a text editor option has changed. + /// + public event PropertyChangedEventHandler OptionChanged; + + /// + /// Raises the event. + /// + protected virtual void OnOptionChanged(PropertyChangedEventArgs e) + { + if (OptionChanged != null) + { + OptionChanged(this, e); + } + } + + static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + ((TextEditor)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue); + } + + void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue) + { + if (oldValue != null) + { + PropertyChangedWeakEventManager.RemoveListener(oldValue, this); + } + textArea.Options = newValue; + if (newValue != null) + { + PropertyChangedWeakEventManager.AddListener(newValue, this); + } + OnOptionChanged(new PropertyChangedEventArgs(null)); + } + + /// + protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(PropertyChangedWeakEventManager)) + { + OnOptionChanged((PropertyChangedEventArgs)e); + return true; + } + else if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) + { + OnTextChanged(e); + return true; + } + else if (managerType == typeof(PropertyChangedEventManager)) + { + return HandleIsOriginalChanged((PropertyChangedEventArgs)e); + } + return false; + } + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + return ReceiveWeakEvent(managerType, sender, e); + } + #endregion + + #region Text property + /// + /// Gets/Sets the text of the current document. + /// + [Localizability(LocalizationCategory.Text), DefaultValue("")] + public string Text + { + get + { + TextDocument document = this.Document; + return document != null ? document.Text : string.Empty; + } + set + { + TextDocument document = GetDocument(); + document.Text = value ?? string.Empty; + // after replacing the full text, the caret is positioned at the end of the document + // - reset it to the beginning. + this.CaretOffset = 0; + document.UndoStack.ClearAll(); + } + } + + TextDocument GetDocument() + { + TextDocument document = this.Document; + if (document == null) + throw ThrowUtil.NoDocumentAssigned(); + return document; + } + + /// + /// Occurs when the Text property changes. + /// + public event EventHandler TextChanged; + + /// + /// Raises the event. + /// + protected virtual void OnTextChanged(EventArgs e) + { + if (TextChanged != null) + { + TextChanged(this, e); + } + } + #endregion + + #region TextArea / ScrollViewer properties + readonly TextArea textArea; + ScrollViewer scrollViewer; + + /// + /// Is called after the template was applied. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + scrollViewer = (ScrollViewer)Template.FindName("PART_ScrollViewer", this); + } + + /// + /// Gets the text area. + /// + public TextArea TextArea + { + get + { + return textArea; + } + } + + /// + /// Gets the scroll viewer used by the text editor. + /// This property can return null if the template has not been applied / does not contain a scroll viewer. + /// + internal ScrollViewer ScrollViewer + { + get { return scrollViewer; } + } + + bool CanExecute(RoutedUICommand command) + { + TextArea textArea = this.TextArea; + if (textArea == null) + return false; + else + return command.CanExecute(null, textArea); + } + + void Execute(RoutedUICommand command) + { + TextArea textArea = this.TextArea; + if (textArea != null) + command.Execute(null, textArea); + } + #endregion + + #region Syntax highlighting + /// + /// The property. + /// + public static readonly DependencyProperty SyntaxHighlightingProperty = + DependencyProperty.Register("SyntaxHighlighting", typeof(IHighlightingDefinition), typeof(TextEditor), + new FrameworkPropertyMetadata(OnSyntaxHighlightingChanged)); + + + /// + /// Gets/sets the syntax highlighting definition used to colorize the text. + /// + public IHighlightingDefinition SyntaxHighlighting + { + get { return (IHighlightingDefinition)GetValue(SyntaxHighlightingProperty); } + set { SetValue(SyntaxHighlightingProperty, value); } + } + + IVisualLineTransformer colorizer; + + static void OnSyntaxHighlightingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((TextEditor)d).OnSyntaxHighlightingChanged(e.NewValue as IHighlightingDefinition); + } + + void OnSyntaxHighlightingChanged(IHighlightingDefinition newValue) + { + if (colorizer != null) + { + this.TextArea.TextView.LineTransformers.Remove(colorizer); + colorizer = null; + } + if (newValue != null) + { + colorizer = CreateColorizer(newValue); + this.TextArea.TextView.LineTransformers.Insert(0, colorizer); + } + } + + /// + /// Creates the highlighting colorizer for the specified highlighting definition. + /// Allows derived classes to provide custom colorizer implementations for special highlighting definitions. + /// + /// + protected virtual IVisualLineTransformer CreateColorizer(IHighlightingDefinition highlightingDefinition) + { + if (highlightingDefinition == null) + throw new ArgumentNullException("highlightingDefinition"); + return new HighlightingColorizer(highlightingDefinition.MainRuleSet); + } + #endregion + + #region WordWrap + /// + /// Word wrap dependency property. + /// + public static readonly DependencyProperty WordWrapProperty = + DependencyProperty.Register("WordWrap", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False)); + + /// + /// Specifies whether the text editor uses word wrapping. + /// + /// + /// Setting WordWrap=true has the same effect as setting HorizontalScrollBarVisibility=Disabled and will override the + /// HorizontalScrollBarVisibility setting. + /// + public bool WordWrap + { + get { return (bool)GetValue(WordWrapProperty); } + set { SetValue(WordWrapProperty, Boxes.Box(value)); } + } + #endregion + + #region IsReadOnly + /// + /// IsReadOnly dependency property. + /// + public static readonly DependencyProperty IsReadOnlyProperty = + DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False, OnIsReadOnlyChanged)); + + /// + /// Specifies whether the user can change the text editor content. + /// Setting this property will replace the + /// TextArea.ReadOnlySectionProvider. + /// + public bool IsReadOnly + { + get { return (bool)GetValue(IsReadOnlyProperty); } + set { SetValue(IsReadOnlyProperty, Boxes.Box(value)); } + } + + static void OnIsReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TextEditor editor = d as TextEditor; + if (editor != null) + { + if ((bool)e.NewValue) + editor.TextArea.ReadOnlySectionProvider = ReadOnlyDocument.Instance; + else + editor.TextArea.ReadOnlySectionProvider = NoReadOnlySections.Instance; + + TextEditorAutomationPeer peer = TextEditorAutomationPeer.FromElement(editor) as TextEditorAutomationPeer; + if (peer != null) + { + peer.RaiseIsReadOnlyChanged((bool)e.OldValue, (bool)e.NewValue); + } + } + } + #endregion + + #region IsModified + /// + /// Dependency property for + /// + public static readonly DependencyProperty IsModifiedProperty = + DependencyProperty.Register("IsModified", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False, OnIsModifiedChanged)); + + /// + /// Gets/Sets the 'modified' flag. + /// + public bool IsModified + { + get { return (bool)GetValue(IsModifiedProperty); } + set { SetValue(IsModifiedProperty, Boxes.Box(value)); } + } + + static void OnIsModifiedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TextEditor editor = d as TextEditor; + if (editor != null) + { + TextDocument document = editor.Document; + if (document != null) + { + UndoStack undoStack = document.UndoStack; + if ((bool)e.NewValue) + { + if (undoStack.IsOriginalFile) + undoStack.DiscardOriginalFileMarker(); + } + else + { + undoStack.MarkAsOriginalFile(); + } + } + } + } + + bool HandleIsOriginalChanged(PropertyChangedEventArgs e) + { + if (e.PropertyName == "IsOriginalFile") + { + TextDocument document = this.Document; + if (document != null) + { + SetCurrentValue(IsModifiedProperty, Boxes.Box(!document.UndoStack.IsOriginalFile)); + } + return true; + } + else + { + return false; + } + } + #endregion + + #region ShowLineNumbers + /// + /// ShowLineNumbers dependency property. + /// + public static readonly DependencyProperty ShowLineNumbersProperty = + DependencyProperty.Register("ShowLineNumbers", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False, OnShowLineNumbersChanged)); + + /// + /// Specifies whether line numbers are shown on the left to the text view. + /// + public bool ShowLineNumbers + { + get { return (bool)GetValue(ShowLineNumbersProperty); } + set { SetValue(ShowLineNumbersProperty, Boxes.Box(value)); } + } + + static void OnShowLineNumbersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TextEditor editor = (TextEditor)d; + var leftMargins = editor.TextArea.LeftMargins; + if ((bool)e.NewValue) + { + LineNumberMargin lineNumbers = new LineNumberMargin(); + Line line = (Line)DottedLineMargin.Create(); + leftMargins.Insert(0, lineNumbers); + leftMargins.Insert(1, line); + var lineNumbersForeground = new Binding("LineNumbersForeground") { Source = editor }; + line.SetBinding(Line.StrokeProperty, lineNumbersForeground); + lineNumbers.SetBinding(Control.ForegroundProperty, lineNumbersForeground); + } + else + { + for (int i = 0; i < leftMargins.Count; i++) + { + if (leftMargins[i] is LineNumberMargin) + { + leftMargins.RemoveAt(i); + if (i < leftMargins.Count && DottedLineMargin.IsDottedLineMargin(leftMargins[i])) + { + leftMargins.RemoveAt(i); + } + break; + } + } + } + } + #endregion + + #region LineNumbersForeground + /// + /// LineNumbersForeground dependency property. + /// + public static readonly DependencyProperty LineNumbersForegroundProperty = + DependencyProperty.Register("LineNumbersForeground", typeof(Brush), typeof(TextEditor), + new FrameworkPropertyMetadata(Brushes.Gray, OnLineNumbersForegroundChanged)); + + /// + /// Gets/sets the Brush used for displaying the foreground color of line numbers. + /// + public Brush LineNumbersForeground + { + get { return (Brush)GetValue(LineNumbersForegroundProperty); } + set { SetValue(LineNumbersForegroundProperty, value); } + } + + static void OnLineNumbersForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TextEditor editor = (TextEditor)d; + var lineNumberMargin = editor.TextArea.LeftMargins.FirstOrDefault(margin => margin is LineNumberMargin) as LineNumberMargin; ; + + if (lineNumberMargin != null) + { + lineNumberMargin.SetValue(Control.ForegroundProperty, e.NewValue); + } + } + #endregion + + #region TextBoxBase-like methods + /// + /// Appends text to the end of the document. + /// + public void AppendText(string textData) + { + var document = GetDocument(); + document.Insert(document.TextLength, textData); + } + + /// + /// Begins a group of document changes. + /// + public void BeginChange() + { + GetDocument().BeginUpdate(); + } + + /// + /// Copies the current selection to the clipboard. + /// + public void Copy() + { + Execute(ApplicationCommands.Copy); + } + + /// + /// Removes the current selection and copies it to the clipboard. + /// + public void Cut() + { + Execute(ApplicationCommands.Cut); + } + + /// + /// Begins a group of document changes and returns an object that ends the group of document + /// changes when it is disposed. + /// + public IDisposable DeclareChangeBlock() + { + return GetDocument().RunUpdate(); + } + + /// + /// Ends the current group of document changes. + /// + public void EndChange() + { + GetDocument().EndUpdate(); + } + + /// + /// Scrolls one line down. + /// + public void LineDown() + { + if (scrollViewer != null) + scrollViewer.LineDown(); + } + + /// + /// Scrolls to the left. + /// + public void LineLeft() + { + if (scrollViewer != null) + scrollViewer.LineLeft(); + } + + /// + /// Scrolls to the right. + /// + public void LineRight() + { + if (scrollViewer != null) + scrollViewer.LineRight(); + } + + /// + /// Scrolls one line up. + /// + public void LineUp() + { + if (scrollViewer != null) + scrollViewer.LineUp(); + } + + /// + /// Scrolls one page down. + /// + public void PageDown() + { + if (scrollViewer != null) + scrollViewer.PageDown(); + } + + /// + /// Scrolls one page up. + /// + public void PageUp() + { + if (scrollViewer != null) + scrollViewer.PageUp(); + } + + /// + /// Scrolls one page left. + /// + public void PageLeft() + { + if (scrollViewer != null) + scrollViewer.PageLeft(); + } + + /// + /// Scrolls one page right. + /// + public void PageRight() + { + if (scrollViewer != null) + scrollViewer.PageRight(); + } + + /// + /// Pastes the clipboard content. + /// + public void Paste() + { + Execute(ApplicationCommands.Paste); + } + + /// + /// Redoes the most recent undone command. + /// + /// True is the redo operation was successful, false is the redo stack is empty. + public bool Redo() + { + if (CanExecute(ApplicationCommands.Redo)) + { + Execute(ApplicationCommands.Redo); + return true; + } + return false; + } + + /// + /// Scrolls to the end of the document. + /// + public void ScrollToEnd() + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToEnd(); + } + + /// + /// Scrolls to the start of the document. + /// + public void ScrollToHome() + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToHome(); + } + + /// + /// Scrolls to the specified position in the document. + /// + public void ScrollToHorizontalOffset(double offset) + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToHorizontalOffset(offset); + } + + /// + /// Scrolls to the specified position in the document. + /// + public void ScrollToVerticalOffset(double offset) + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToVerticalOffset(offset); + } + + /// + /// Selects the entire text. + /// + public void SelectAll() + { + Execute(ApplicationCommands.SelectAll); + } + + /// + /// Undoes the most recent command. + /// + /// True is the undo operation was successful, false is the undo stack is empty. + public bool Undo() + { + if (CanExecute(ApplicationCommands.Undo)) + { + Execute(ApplicationCommands.Undo); + return true; + } + return false; + } + + /// + /// Gets if the most recent undone command can be redone. + /// + public bool CanRedo + { + get { return CanExecute(ApplicationCommands.Redo); } + } + + /// + /// Gets if the most recent command can be undone. + /// + public bool CanUndo + { + get { return CanExecute(ApplicationCommands.Undo); } + } + + /// + /// Gets the vertical size of the document. + /// + public double ExtentHeight + { + get + { + return scrollViewer != null ? scrollViewer.ExtentHeight : 0; + } + } + + /// + /// Gets the horizontal size of the current document region. + /// + public double ExtentWidth + { + get + { + return scrollViewer != null ? scrollViewer.ExtentWidth : 0; + } + } + + /// + /// Gets the horizontal size of the viewport. + /// + public double ViewportHeight + { + get + { + return scrollViewer != null ? scrollViewer.ViewportHeight : 0; + } + } + + /// + /// Gets the horizontal size of the viewport. + /// + public double ViewportWidth + { + get + { + return scrollViewer != null ? scrollViewer.ViewportWidth : 0; + } + } + + /// + /// Gets the vertical scroll position. + /// + public double VerticalOffset + { + get + { + return scrollViewer != null ? scrollViewer.VerticalOffset : 0; + } + } + + /// + /// Gets the horizontal scroll position. + /// + public double HorizontalOffset + { + get + { + return scrollViewer != null ? scrollViewer.HorizontalOffset : 0; + } + } + #endregion + + #region TextBox methods + /// + /// Gets/Sets the selected text. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public string SelectedText + { + get + { + TextArea textArea = this.TextArea; + // We'll get the text from the whole surrounding segment. + // This is done to ensure that SelectedText.Length == SelectionLength. + if (textArea != null && textArea.Document != null && !textArea.Selection.IsEmpty) + return textArea.Document.GetText(textArea.Selection.SurroundingSegment); + else + return string.Empty; + } + set + { + if (value == null) + throw new ArgumentNullException("value"); + TextArea textArea = this.TextArea; + if (textArea != null && textArea.Document != null) + { + int offset = this.SelectionStart; + int length = this.SelectionLength; + textArea.Document.Replace(offset, length, value); + // keep inserted text selected + textArea.Selection = SimpleSelection.Create(textArea, offset, offset + value.Length); + } + } + } + + /// + /// Gets/sets the caret position. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int CaretOffset + { + get + { + TextArea textArea = this.TextArea; + if (textArea != null) + return textArea.Caret.Offset; + else + return 0; + } + set + { + TextArea textArea = this.TextArea; + if (textArea != null) + textArea.Caret.Offset = value; + } + } + + /// + /// Gets/sets the start position of the selection. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int SelectionStart + { + get + { + TextArea textArea = this.TextArea; + if (textArea != null) + { + if (textArea.Selection.IsEmpty) + return textArea.Caret.Offset; + else + return textArea.Selection.SurroundingSegment.Offset; + } + else + { + return 0; + } + } + set + { + Select(value, SelectionLength); + } + } + + /// + /// Gets/sets the length of the selection. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int SelectionLength + { + get + { + TextArea textArea = this.TextArea; + if (textArea != null && !textArea.Selection.IsEmpty) + return textArea.Selection.SurroundingSegment.Length; + else + return 0; + } + set + { + Select(SelectionStart, value); + } + } + + /// + /// Selects the specified text section. + /// + public void Select(int start, int length) + { + int documentLength = Document != null ? Document.TextLength : 0; + + if (start < 0 || start > documentLength) + { + Debug.WriteLine(new ArgumentOutOfRangeException("start", start, "Value must be between 0 and " + documentLength)); + return; + } + + if (length < 0 || start + length > documentLength) + { + Debug.WriteLine(new ArgumentOutOfRangeException("length", length, "Value must be between 0 and " + (documentLength - length))); + return; + } + + textArea.Selection = SimpleSelection.Create(textArea, start, start + length); + textArea.Caret.Offset = start + length; + } + + /// + /// Gets the number of lines in the document. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int LineCount + { + get + { + TextDocument document = this.Document; + if (document != null) + return document.LineCount; + else + return 1; + } + } + + /// + /// Clears the text. + /// + public void Clear() + { + this.Text = string.Empty; + } + #endregion + + #region Loading from stream + /// + /// Loads the text from the stream, auto-detecting the encoding. + /// + /// + /// This method sets to false. + /// + public void Load(Stream stream) + { + using (StreamReader reader = FileReader.OpenStream(stream, this.Encoding ?? Encoding.UTF8)) + { + this.Text = reader.ReadToEnd(); + this.Encoding = reader.CurrentEncoding; // assign encoding after ReadToEnd() so that the StreamReader can autodetect the encoding + } + this.IsModified = false; + } + + /// + /// Loads the text from the stream, auto-detecting the encoding. + /// + public void Load(string fileName) + { + if (fileName == null) + throw new ArgumentNullException("fileName"); + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + Load(fs); + } + } + + /// + /// Gets/sets the encoding used when the file is saved. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Encoding Encoding { get; set; } + + /// + /// Saves the text to the stream. + /// + /// + /// This method sets to false. + /// + public void Save(Stream stream) + { + if (stream == null) + throw new ArgumentNullException("stream"); + StreamWriter writer = new StreamWriter(stream, this.Encoding ?? Encoding.UTF8); + writer.Write(this.Text); + writer.Flush(); + // do not close the stream + this.IsModified = false; + } + + /// + /// Saves the text to the file. + /// + public void Save(string fileName) + { + if (fileName == null) + throw new ArgumentNullException("fileName"); + using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) + { + Save(fs); + } + } + #endregion + + #region MouseHover events + /// + /// The PreviewMouseHover event. + /// + public static readonly RoutedEvent PreviewMouseHoverEvent = + TextView.PreviewMouseHoverEvent.AddOwner(typeof(TextEditor)); + + /// + /// The MouseHover event. + /// + public static readonly RoutedEvent MouseHoverEvent = + TextView.MouseHoverEvent.AddOwner(typeof(TextEditor)); + + + /// + /// The PreviewMouseHoverStopped event. + /// + public static readonly RoutedEvent PreviewMouseHoverStoppedEvent = + TextView.PreviewMouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); + + /// + /// The MouseHoverStopped event. + /// + public static readonly RoutedEvent MouseHoverStoppedEvent = + TextView.MouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); + + + /// + /// Occurs when the mouse has hovered over a fixed location for some time. + /// + public event MouseEventHandler PreviewMouseHover + { + add { AddHandler(PreviewMouseHoverEvent, value); } + remove { RemoveHandler(PreviewMouseHoverEvent, value); } + } + + /// + /// Occurs when the mouse has hovered over a fixed location for some time. + /// + public event MouseEventHandler MouseHover + { + add { AddHandler(MouseHoverEvent, value); } + remove { RemoveHandler(MouseHoverEvent, value); } + } + + /// + /// Occurs when the mouse had previously hovered but now started moving again. + /// + public event MouseEventHandler PreviewMouseHoverStopped + { + add { AddHandler(PreviewMouseHoverStoppedEvent, value); } + remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); } + } + + /// + /// Occurs when the mouse had previously hovered but now started moving again. + /// + public event MouseEventHandler MouseHoverStopped + { + add { AddHandler(MouseHoverStoppedEvent, value); } + remove { RemoveHandler(MouseHoverStoppedEvent, value); } + } + #endregion + + #region ScrollBarVisibility + /// + /// Dependency property for + /// + public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty = ScrollViewer.HorizontalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); + + /// + /// Gets/Sets the horizontal scroll bar visibility. + /// + public ScrollBarVisibility HorizontalScrollBarVisibility + { + get { return (ScrollBarVisibility)GetValue(HorizontalScrollBarVisibilityProperty); } + set { SetValue(HorizontalScrollBarVisibilityProperty, value); } + } + + /// + /// Dependency property for + /// + public static readonly DependencyProperty VerticalScrollBarVisibilityProperty = ScrollViewer.VerticalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); + + /// + /// Gets/Sets the vertical scroll bar visibility. + /// + public ScrollBarVisibility VerticalScrollBarVisibility + { + get { return (ScrollBarVisibility)GetValue(VerticalScrollBarVisibilityProperty); } + set { SetValue(VerticalScrollBarVisibilityProperty, value); } + } + #endregion + + object IServiceProvider.GetService(Type serviceType) + { + return textArea.GetService(serviceType); + } + + /// + /// Gets the text view position from a point inside the editor. + /// + /// The position, relative to top left + /// corner of TextEditor control + /// The text view position, or null if the point is outside the document. + public TextViewPosition? GetPositionFromPoint(Point point) + { + if (this.Document == null) + return null; + TextView textView = this.TextArea.TextView; + return textView.GetPosition(TranslatePoint(point, textView) + textView.ScrollOffset); + } + + /// + /// Scrolls to the specified line. + /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). + /// + public void ScrollToLine(int line) + { + ScrollTo(line, -1); + } + + /// + /// Scrolls to the specified line/column. + /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). + /// + public void ScrollTo(int line, int column) + { + const double MinimumScrollPercentage = 0.3; + + TextView textView = textArea.TextView; + TextDocument document = textView.Document; + if (scrollViewer != null && document != null) + { + if (line < 1) + line = 1; + if (line > document.LineCount) + line = document.LineCount; + + IScrollInfo scrollInfo = textView; + if (!scrollInfo.CanHorizontallyScroll) + { + // Word wrap is enabled. Ensure that we have up-to-date info about line height so that we scroll + // to the correct position. + // This avoids that the user has to repeat the ScrollTo() call several times when there are very long lines. + VisualLine vl = textView.GetOrConstructVisualLine(document.GetLineByNumber(line)); + double remainingHeight = scrollViewer.ViewportHeight / 2; + while (remainingHeight > 0) + { + DocumentLine prevLine = vl.FirstDocumentLine.PreviousLine; + if (prevLine == null) + break; + vl = textView.GetOrConstructVisualLine(prevLine); + remainingHeight -= vl.Height; + } + } + + Point p = textArea.TextView.GetVisualPosition(new TextViewPosition(line, Math.Max(1, column)), VisualYPosition.LineMiddle); + double verticalPos = p.Y - scrollViewer.ViewportHeight / 2; + if (Math.Abs(verticalPos - scrollViewer.VerticalOffset) > MinimumScrollPercentage * scrollViewer.ViewportHeight) + { + scrollViewer.ScrollToVerticalOffset(Math.Max(0, verticalPos)); + } + if (column > 0) + { + if (p.X > scrollViewer.ViewportWidth - Caret.MinimumDistanceToViewBorder * 2) + { + double horizontalPos = Math.Max(0, p.X - scrollViewer.ViewportWidth / 2); + if (Math.Abs(horizontalPos - scrollViewer.HorizontalOffset) > MinimumScrollPercentage * scrollViewer.ViewportWidth) + { + scrollViewer.ScrollToHorizontalOffset(horizontalPos); + } + } + else + { + scrollViewer.ScrollToHorizontalOffset(0); + } + } + } + } + } } \ No newline at end of file -- cgit v1.3.1