aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/AbstractMargin.cs102
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs472
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretLayer.cs86
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs375
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretWeakEventHandler.cs33
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DottedLineMargin.cs63
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DragDropException.cs46
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EditingCommandHandler.cs578
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EmptySelection.cs85
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/IReadOnlySectionProvider.cs32
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeNativeWrapper.cs206
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeSupport.cs150
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/LineNumberMargin.cs243
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/NoReadOnlySections.cs50
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/RectangleSelection.cs396
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Selection.cs268
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionColorizer.cs58
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionLayer.cs53
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionMouseHandler.cs631
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionSegment.cs89
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SimpleSelection.cs144
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextArea.cs1048
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaDefaultInputHandlers.cs120
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaInputHandler.cs242
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextSegmentReadOnlySectionProvider.cs80
25 files changed, 5650 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/AbstractMargin.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/AbstractMargin.cs
new file mode 100644
index 000000000..c82ff94a0
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/AbstractMargin.cs
@@ -0,0 +1,102 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Diagnostics;
+using System.Windows;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Base class for margins.
+ /// Margins don't have to derive from this class, it just helps maintaining a reference to the TextView
+ /// and the TextDocument.
+ /// AbstractMargin derives from FrameworkElement, so if you don't want to handle visual children and rendering
+ /// on your own, choose another base class for your margin!
+ /// </summary>
+ public abstract class AbstractMargin : FrameworkElement, ITextViewConnect
+ {
+ /// <summary>
+ /// TextView property.
+ /// </summary>
+ public static readonly DependencyProperty TextViewProperty =
+ DependencyProperty.Register("TextView", typeof(TextView), typeof(AbstractMargin),
+ new FrameworkPropertyMetadata(OnTextViewChanged));
+
+ /// <summary>
+ /// Gets/sets the text view for which line numbers are displayed.
+ /// </summary>
+ /// <remarks>Adding a margin to <see cref="TextArea.LeftMargins"/> will automatically set this property to the text area's TextView.</remarks>
+ public TextView TextView {
+ get { return (TextView)GetValue(TextViewProperty); }
+ set { SetValue(TextViewProperty, value); }
+ }
+
+ static void OnTextViewChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
+ {
+ AbstractMargin margin = (AbstractMargin)dp;
+ margin.wasAutoAddedToTextView = false;
+ margin.OnTextViewChanged((TextView)e.OldValue, (TextView)e.NewValue);
+ }
+
+ // automatically set/unset TextView property using ITextViewConnect
+ bool wasAutoAddedToTextView;
+
+ void ITextViewConnect.AddToTextView(TextView textView)
+ {
+ if (this.TextView == null) {
+ this.TextView = textView;
+ wasAutoAddedToTextView = true;
+ } else if (this.TextView != textView) {
+ throw new InvalidOperationException("This margin belongs to a different TextView.");
+ }
+ }
+
+ void ITextViewConnect.RemoveFromTextView(TextView textView)
+ {
+ if (wasAutoAddedToTextView && this.TextView == textView) {
+ this.TextView = null;
+ Debug.Assert(!wasAutoAddedToTextView); // setting this.TextView should have unset this flag
+ }
+ }
+
+ TextDocument document;
+
+ /// <summary>
+ /// Gets the document associated with the margin.
+ /// </summary>
+ public TextDocument Document {
+ get { return document; }
+ }
+
+ /// <summary>
+ /// Called when the <see cref="TextView"/> is changing.
+ /// </summary>
+ protected virtual void OnTextViewChanged(TextView oldTextView, TextView newTextView)
+ {
+ if (oldTextView != null) {
+ oldTextView.DocumentChanged -= TextViewDocumentChanged;
+ }
+ if (newTextView != null) {
+ newTextView.DocumentChanged += TextViewDocumentChanged;
+ }
+ TextViewDocumentChanged(null, null);
+ }
+
+ void TextViewDocumentChanged(object sender, EventArgs e)
+ {
+ OnDocumentChanged(document, TextView != null ? TextView.Document : null);
+ }
+
+ /// <summary>
+ /// Called when the <see cref="Document"/> is changing.
+ /// </summary>
+ protected virtual void OnDocumentChanged(TextDocument oldDocument, TextDocument newDocument)
+ {
+ document = newDocument;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs
new file mode 100644
index 000000000..fb21448e5
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs
@@ -0,0 +1,472 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Media;
+using System.Windows.Media.TextFormatting;
+using System.Windows.Threading;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Helper class with caret-related methods.
+ /// </summary>
+ public sealed class Caret
+ {
+ readonly TextArea textArea;
+ readonly TextView textView;
+ readonly CaretLayer caretAdorner;
+ bool visible;
+
+ internal Caret(TextArea textArea)
+ {
+ this.textArea = textArea;
+ this.textView = textArea.TextView;
+ position = new TextViewPosition(1, 1, 0);
+
+ caretAdorner = new CaretLayer(textView);
+ textView.InsertLayer(caretAdorner, KnownLayer.Caret, LayerInsertionPosition.Replace);
+ textView.VisualLinesChanged += TextView_VisualLinesChanged;
+ textView.ScrollOffsetChanged += TextView_ScrollOffsetChanged;
+ }
+
+ void TextView_VisualLinesChanged(object sender, EventArgs e)
+ {
+ if (visible) {
+ Show();
+ }
+ // required because the visual columns might have changed if the
+ // element generators did something differently than on the last run
+ // (e.g. a FoldingSection was collapsed)
+ InvalidateVisualColumn();
+ }
+
+ void TextView_ScrollOffsetChanged(object sender, EventArgs e)
+ {
+ if (caretAdorner != null) {
+ caretAdorner.InvalidateVisual();
+ }
+ }
+
+ double desiredXPos = double.NaN;
+ TextViewPosition position;
+
+ /// <summary>
+ /// Gets/Sets the position of the caret.
+ /// Retrieving this property will validate the visual column (which can be expensive).
+ /// Use the <see cref="Location"/> property instead if you don't need the visual column.
+ /// </summary>
+ public TextViewPosition Position {
+ get {
+ ValidateVisualColumn();
+ return position;
+ }
+ set {
+ if (position != value) {
+ position = value;
+
+ storedCaretOffset = -1;
+
+ //Debug.WriteLine("Caret position changing to " + value);
+
+ ValidatePosition();
+ InvalidateVisualColumn();
+ RaisePositionChanged();
+ Log("Caret position changed to " + value);
+ if (visible)
+ Show();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the caret position without validating it.
+ /// </summary>
+ internal TextViewPosition NonValidatedPosition {
+ get {
+ return position;
+ }
+ }
+
+ /// <summary>
+ /// Gets/Sets the location of the caret.
+ /// The getter of this property is faster than <see cref="Position"/> because it doesn't have
+ /// to validate the visual column.
+ /// </summary>
+ public TextLocation Location {
+ get {
+ return position.Location;
+ }
+ set {
+ this.Position = new TextViewPosition(value);
+ }
+ }
+
+ /// <summary>
+ /// Gets/Sets the caret line.
+ /// </summary>
+ public int Line {
+ get { return position.Line; }
+ set {
+ this.Position = new TextViewPosition(value, position.Column);
+ }
+ }
+
+ /// <summary>
+ /// Gets/Sets the caret column.
+ /// </summary>
+ public int Column {
+ get { return position.Column; }
+ set {
+ this.Position = new TextViewPosition(position.Line, value);
+ }
+ }
+
+ /// <summary>
+ /// Gets/Sets the caret visual column.
+ /// </summary>
+ public int VisualColumn {
+ get {
+ ValidateVisualColumn();
+ return position.VisualColumn;
+ }
+ set {
+ this.Position = new TextViewPosition(position.Line, position.Column, value);
+ }
+ }
+
+ bool isInVirtualSpace;
+
+ /// <summary>
+ /// Gets whether the caret is in virtual space.
+ /// </summary>
+ public bool IsInVirtualSpace {
+ get {
+ ValidateVisualColumn();
+ return isInVirtualSpace;
+ }
+ }
+
+ int storedCaretOffset;
+
+ internal void OnDocumentChanging()
+ {
+ storedCaretOffset = this.Offset;
+ InvalidateVisualColumn();
+ }
+
+ internal void OnDocumentChanged(DocumentChangeEventArgs e)
+ {
+ InvalidateVisualColumn();
+ if (storedCaretOffset >= 0) {
+ int newCaretOffset = e.GetNewOffset(storedCaretOffset, AnchorMovementType.Default);
+ TextDocument document = textArea.Document;
+ if (document != null) {
+ // keep visual column
+ this.Position = new TextViewPosition(document.GetLocation(newCaretOffset), position.VisualColumn);
+ }
+ }
+ storedCaretOffset = -1;
+ }
+
+ /// <summary>
+ /// Gets/Sets the caret offset.
+ /// Setting the caret offset has the side effect of setting the <see cref="DesiredXPos"/> to NaN.
+ /// </summary>
+ public int Offset {
+ get {
+ TextDocument document = textArea.Document;
+ if (document == null) {
+ return 0;
+ } else {
+ return document.GetOffset(position.Location);
+ }
+ }
+ set {
+ TextDocument document = textArea.Document;
+ if (document != null) {
+ this.Position = new TextViewPosition(document.GetLocation(value));
+ this.DesiredXPos = double.NaN;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets/Sets the desired x-position of the caret, in device-independent pixels.
+ /// This property is NaN if the caret has no desired position.
+ /// </summary>
+ public double DesiredXPos {
+ get { return desiredXPos; }
+ set { desiredXPos = value; }
+ }
+
+ void ValidatePosition()
+ {
+ if (position.Line < 1)
+ position.Line = 1;
+ if (position.Column < 1)
+ position.Column = 1;
+ if (position.VisualColumn < -1)
+ position.VisualColumn = -1;
+ TextDocument document = textArea.Document;
+ if (document != null) {
+ if (position.Line > document.LineCount) {
+ position.Line = document.LineCount;
+ position.Column = document.GetLineByNumber(position.Line).Length + 1;
+ position.VisualColumn = -1;
+ } else {
+ DocumentLine line = document.GetLineByNumber(position.Line);
+ if (position.Column > line.Length + 1) {
+ position.Column = line.Length + 1;
+ position.VisualColumn = -1;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Event raised when the caret position has changed.
+ /// If the caret position is changed inside a document update (between BeginUpdate/EndUpdate calls),
+ /// the PositionChanged event is raised only once at the end of the document update.
+ /// </summary>
+ public event EventHandler PositionChanged;
+
+ bool raisePositionChangedOnUpdateFinished;
+
+ void RaisePositionChanged()
+ {
+ if (textArea.Document != null && textArea.Document.IsInUpdate) {
+ raisePositionChangedOnUpdateFinished = true;
+ } else {
+ if (PositionChanged != null) {
+ PositionChanged(this, EventArgs.Empty);
+ }
+ }
+ }
+
+ internal void OnDocumentUpdateFinished()
+ {
+ if (raisePositionChangedOnUpdateFinished) {
+ if (PositionChanged != null) {
+ PositionChanged(this, EventArgs.Empty);
+ }
+ }
+ }
+
+ bool visualColumnValid;
+
+ void ValidateVisualColumn()
+ {
+ if (!visualColumnValid) {
+ TextDocument document = textArea.Document;
+ if (document != null) {
+ //Debug.WriteLine("Explicit validation of caret column");
+ var documentLine = document.GetLineByNumber(position.Line);
+ RevalidateVisualColumn(textView.GetOrConstructVisualLine(documentLine));
+ }
+ }
+ }
+
+ void InvalidateVisualColumn()
+ {
+ visualColumnValid = false;
+ }
+
+ /// <summary>
+ /// Validates the visual column of the caret using the specified visual line.
+ /// The visual line must contain the caret offset.
+ /// </summary>
+ void RevalidateVisualColumn(VisualLine visualLine)
+ {
+ if (visualLine == null)
+ throw new ArgumentNullException("visualLine");
+
+ // mark column as validated
+ visualColumnValid = true;
+
+ int caretOffset = textView.Document.GetOffset(position.Location);
+ int firstDocumentLineOffset = visualLine.FirstDocumentLine.Offset;
+ position.VisualColumn = visualLine.ValidateVisualColumn(position, textArea.Selection.EnableVirtualSpace);
+
+ // search possible caret positions
+ int newVisualColumnForwards = visualLine.GetNextCaretPosition(position.VisualColumn - 1, LogicalDirection.Forward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace);
+ // If position.VisualColumn was valid, we're done with validation.
+ if (newVisualColumnForwards != position.VisualColumn) {
+ // also search backwards so that we can pick the better match
+ int newVisualColumnBackwards = visualLine.GetNextCaretPosition(position.VisualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace);
+
+ if (newVisualColumnForwards < 0 && newVisualColumnBackwards < 0)
+ throw ThrowUtil.NoValidCaretPosition();
+
+ // determine offsets for new visual column positions
+ int newOffsetForwards;
+ if (newVisualColumnForwards >= 0)
+ newOffsetForwards = visualLine.GetRelativeOffset(newVisualColumnForwards) + firstDocumentLineOffset;
+ else
+ newOffsetForwards = -1;
+ int newOffsetBackwards;
+ if (newVisualColumnBackwards >= 0)
+ newOffsetBackwards = visualLine.GetRelativeOffset(newVisualColumnBackwards) + firstDocumentLineOffset;
+ else
+ newOffsetBackwards = -1;
+
+ int newVisualColumn, newOffset;
+ // if there's only one valid position, use it
+ if (newVisualColumnForwards < 0) {
+ newVisualColumn = newVisualColumnBackwards;
+ newOffset = newOffsetBackwards;
+ } else if (newVisualColumnBackwards < 0) {
+ newVisualColumn = newVisualColumnForwards;
+ newOffset = newOffsetForwards;
+ } else {
+ // two valid positions: find the better match
+ if (Math.Abs(newOffsetBackwards - caretOffset) < Math.Abs(newOffsetForwards - caretOffset)) {
+ // backwards is better
+ newVisualColumn = newVisualColumnBackwards;
+ newOffset = newOffsetBackwards;
+ } else {
+ // forwards is better
+ newVisualColumn = newVisualColumnForwards;
+ newOffset = newOffsetForwards;
+ }
+ }
+ this.Position = new TextViewPosition(textView.Document.GetLocation(newOffset), newVisualColumn);
+ }
+ isInVirtualSpace = (position.VisualColumn > visualLine.VisualLength);
+ }
+
+ Rect CalcCaretRectangle(VisualLine visualLine)
+ {
+ if (!visualColumnValid) {
+ RevalidateVisualColumn(visualLine);
+ }
+
+ TextLine textLine = visualLine.GetTextLine(position.VisualColumn);
+ double xPos = visualLine.GetTextLineVisualXPosition(textLine, position.VisualColumn);
+ double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop);
+ double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom);
+
+ return new Rect(xPos,
+ lineTop,
+ SystemParameters.CaretWidth,
+ lineBottom - lineTop);
+ }
+
+ /// <summary>
+ /// Returns the caret rectangle. The coordinate system is in device-independent pixels from the top of the document.
+ /// </summary>
+ public Rect CalculateCaretRectangle()
+ {
+ if (textView != null && textView.Document != null) {
+ VisualLine visualLine = textView.GetOrConstructVisualLine(textView.Document.GetLineByNumber(position.Line));
+ return CalcCaretRectangle(visualLine);
+ } else {
+ return Rect.Empty;
+ }
+ }
+
+ /// <summary>
+ /// Minimum distance of the caret to the view border.
+ /// </summary>
+ internal const double MinimumDistanceToViewBorder = 30;
+
+ /// <summary>
+ /// Scrolls the text view so that the caret is visible.
+ /// </summary>
+ public void BringCaretToView()
+ {
+ BringCaretToView(MinimumDistanceToViewBorder);
+ }
+
+ internal void BringCaretToView(double border)
+ {
+ Rect caretRectangle = CalculateCaretRectangle();
+ if (!caretRectangle.IsEmpty) {
+ caretRectangle.Inflate(border, border);
+ textView.MakeVisible(caretRectangle);
+ }
+ }
+
+ /// <summary>
+ /// Makes the caret visible and updates its on-screen position.
+ /// </summary>
+ public void Show()
+ {
+ Log("Caret.Show()");
+ visible = true;
+ if (!showScheduled) {
+ showScheduled = true;
+ textArea.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(ShowInternal));
+ }
+ }
+
+ bool showScheduled;
+ bool hasWin32Caret;
+
+ void ShowInternal()
+ {
+ showScheduled = false;
+
+ // if show was scheduled but caret hidden in the meantime
+ if (!visible)
+ return;
+
+ if (caretAdorner != null && textView != null) {
+ VisualLine visualLine = textView.GetVisualLine(position.Line);
+ if (visualLine != null) {
+ Rect caretRect = CalcCaretRectangle(visualLine);
+ // Create Win32 caret so that Windows knows where our managed caret is. This is necessary for
+ // features like 'Follow text editing' in the Windows Magnifier.
+ if (!hasWin32Caret) {
+ hasWin32Caret = Win32.CreateCaret(textView, caretRect.Size);
+ }
+ if (hasWin32Caret) {
+ Win32.SetCaretPosition(textView, caretRect.Location - textView.ScrollOffset);
+ }
+ caretAdorner.Show(caretRect);
+ textArea.ime.UpdateCompositionWindow();
+ } else {
+ caretAdorner.Hide();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Makes the caret invisible.
+ /// </summary>
+ public void Hide()
+ {
+ Log("Caret.Hide()");
+ visible = false;
+ if (hasWin32Caret) {
+ Win32.DestroyCaret();
+ hasWin32Caret = false;
+ }
+ if (caretAdorner != null) {
+ caretAdorner.Hide();
+ }
+ }
+
+ [Conditional("DEBUG")]
+ static void Log(string text)
+ {
+ // commented out to make debug output less noisy - add back if there are any problems with the caret
+ //Debug.WriteLine(text);
+ }
+
+ /// <summary>
+ /// Gets/Sets the color of the caret.
+ /// </summary>
+ public Brush CaretBrush {
+ get { return caretAdorner.CaretBrush; }
+ set { caretAdorner.CaretBrush = value; }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretLayer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretLayer.cs
new file mode 100644
index 000000000..105f38d16
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretLayer.cs
@@ -0,0 +1,86 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Threading;
+
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ sealed class CaretLayer : Layer
+ {
+ bool isVisible;
+ Rect caretRectangle;
+
+ DispatcherTimer caretBlinkTimer = new DispatcherTimer();
+ bool blink;
+
+ public CaretLayer(TextView textView) : base(textView, KnownLayer.Caret)
+ {
+ this.IsHitTestVisible = false;
+ caretBlinkTimer.Tick += new EventHandler(caretBlinkTimer_Tick);
+ }
+
+ void caretBlinkTimer_Tick(object sender, EventArgs e)
+ {
+ blink = !blink;
+ InvalidateVisual();
+ }
+
+ public void Show(Rect caretRectangle)
+ {
+ this.caretRectangle = caretRectangle;
+ this.isVisible = true;
+ StartBlinkAnimation();
+ InvalidateVisual();
+ }
+
+ public void Hide()
+ {
+ if (isVisible) {
+ isVisible = false;
+ StopBlinkAnimation();
+ InvalidateVisual();
+ }
+ }
+
+ void StartBlinkAnimation()
+ {
+ TimeSpan blinkTime = Win32.CaretBlinkTime;
+ blink = true; // the caret should visible initially
+ // This is important if blinking is disabled (system reports a negative blinkTime)
+ if (blinkTime.TotalMilliseconds > 0) {
+ caretBlinkTimer.Interval = blinkTime;
+ caretBlinkTimer.Start();
+ }
+ }
+
+ void StopBlinkAnimation()
+ {
+ caretBlinkTimer.Stop();
+ }
+
+ internal Brush CaretBrush;
+
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ base.OnRender(drawingContext);
+ if (isVisible && blink) {
+ Brush caretBrush = this.CaretBrush;
+ if (caretBrush == null)
+ caretBrush = (Brush)textView.GetValue(TextBlock.ForegroundProperty);
+ Rect r = new Rect(caretRectangle.X - textView.HorizontalOffset,
+ caretRectangle.Y - textView.VerticalOffset,
+ caretRectangle.Width,
+ caretRectangle.Height);
+ drawingContext.DrawRectangle(caretBrush, null, PixelSnapHelpers.Round(r, PixelSnapHelpers.GetPixelSize(this)));
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs
new file mode 100644
index 000000000..d1b812b66
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs
@@ -0,0 +1,375 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media.TextFormatting;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ static class CaretNavigationCommandHandler
+ {
+ /// <summary>
+ /// Creates a new <see cref="TextAreaInputHandler"/> for the text area.
+ /// </summary>
+ public static TextAreaInputHandler Create(TextArea textArea)
+ {
+ TextAreaInputHandler handler = new TextAreaInputHandler(textArea);
+ handler.CommandBindings.AddRange(CommandBindings);
+ handler.InputBindings.AddRange(InputBindings);
+ return handler;
+ }
+
+ static readonly List<CommandBinding> CommandBindings = new List<CommandBinding>();
+ static readonly List<InputBinding> InputBindings = new List<InputBinding>();
+
+ static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler)
+ {
+ CommandBindings.Add(new CommandBinding(command, handler));
+ InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key));
+ }
+
+ static CaretNavigationCommandHandler()
+ {
+ const ModifierKeys None = ModifierKeys.None;
+ const ModifierKeys Ctrl = ModifierKeys.Control;
+ const ModifierKeys Shift = ModifierKeys.Shift;
+ const ModifierKeys Alt = ModifierKeys.Alt;
+
+ AddBinding(EditingCommands.MoveLeftByCharacter, None, Key.Left, OnMoveCaret(CaretMovementType.CharLeft));
+ AddBinding(EditingCommands.SelectLeftByCharacter, Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.CharLeft));
+ AddBinding(RectangleSelection.BoxSelectLeftByCharacter, Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.CharLeft));
+ AddBinding(EditingCommands.MoveRightByCharacter, None, Key.Right, OnMoveCaret(CaretMovementType.CharRight));
+ AddBinding(EditingCommands.SelectRightByCharacter, Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.CharRight));
+ AddBinding(RectangleSelection.BoxSelectRightByCharacter, Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.CharRight));
+
+ AddBinding(EditingCommands.MoveLeftByWord, Ctrl, Key.Left, OnMoveCaret(CaretMovementType.WordLeft));
+ AddBinding(EditingCommands.SelectLeftByWord, Ctrl | Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.WordLeft));
+ AddBinding(RectangleSelection.BoxSelectLeftByWord, Ctrl | Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.WordLeft));
+ AddBinding(EditingCommands.MoveRightByWord, Ctrl, Key.Right, OnMoveCaret(CaretMovementType.WordRight));
+ AddBinding(EditingCommands.SelectRightByWord, Ctrl | Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.WordRight));
+ AddBinding(RectangleSelection.BoxSelectRightByWord, Ctrl | Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.WordRight));
+
+ AddBinding(EditingCommands.MoveUpByLine, None, Key.Up, OnMoveCaret(CaretMovementType.LineUp));
+ AddBinding(EditingCommands.SelectUpByLine, Shift, Key.Up, OnMoveCaretExtendSelection(CaretMovementType.LineUp));
+ AddBinding(RectangleSelection.BoxSelectUpByLine, Alt | Shift, Key.Up, OnMoveCaretBoxSelection(CaretMovementType.LineUp));
+ AddBinding(EditingCommands.MoveDownByLine, None, Key.Down, OnMoveCaret(CaretMovementType.LineDown));
+ AddBinding(EditingCommands.SelectDownByLine, Shift, Key.Down, OnMoveCaretExtendSelection(CaretMovementType.LineDown));
+ AddBinding(RectangleSelection.BoxSelectDownByLine, Alt | Shift, Key.Down, OnMoveCaretBoxSelection(CaretMovementType.LineDown));
+
+ AddBinding(EditingCommands.MoveDownByPage, None, Key.PageDown, OnMoveCaret(CaretMovementType.PageDown));
+ AddBinding(EditingCommands.SelectDownByPage, Shift, Key.PageDown, OnMoveCaretExtendSelection(CaretMovementType.PageDown));
+ AddBinding(EditingCommands.MoveUpByPage, None, Key.PageUp, OnMoveCaret(CaretMovementType.PageUp));
+ AddBinding(EditingCommands.SelectUpByPage, Shift, Key.PageUp, OnMoveCaretExtendSelection(CaretMovementType.PageUp));
+
+ AddBinding(EditingCommands.MoveToLineStart, None, Key.Home, OnMoveCaret(CaretMovementType.LineStart));
+ AddBinding(EditingCommands.SelectToLineStart, Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.LineStart));
+ AddBinding(RectangleSelection.BoxSelectToLineStart, Alt | Shift, Key.Home, OnMoveCaretBoxSelection(CaretMovementType.LineStart));
+ AddBinding(EditingCommands.MoveToLineEnd, None, Key.End, OnMoveCaret(CaretMovementType.LineEnd));
+ AddBinding(EditingCommands.SelectToLineEnd, Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.LineEnd));
+ AddBinding(RectangleSelection.BoxSelectToLineEnd, Alt | Shift, Key.End, OnMoveCaretBoxSelection(CaretMovementType.LineEnd));
+
+ AddBinding(EditingCommands.MoveToDocumentStart, Ctrl, Key.Home, OnMoveCaret(CaretMovementType.DocumentStart));
+ AddBinding(EditingCommands.SelectToDocumentStart, Ctrl | Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.DocumentStart));
+ AddBinding(EditingCommands.MoveToDocumentEnd, Ctrl, Key.End, OnMoveCaret(CaretMovementType.DocumentEnd));
+ AddBinding(EditingCommands.SelectToDocumentEnd, Ctrl | Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.DocumentEnd));
+
+ CommandBindings.Add(new CommandBinding(ApplicationCommands.SelectAll, OnSelectAll));
+
+ TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings);
+ }
+
+ static void OnSelectAll(object target, ExecutedRoutedEventArgs args)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ args.Handled = true;
+ textArea.Caret.Offset = textArea.Document.TextLength;
+ textArea.Selection = SimpleSelection.Create(textArea, 0, textArea.Document.TextLength);
+ }
+ }
+
+ static TextArea GetTextArea(object target)
+ {
+ return target as TextArea;
+ }
+
+ enum CaretMovementType
+ {
+ CharLeft,
+ CharRight,
+ WordLeft,
+ WordRight,
+ LineUp,
+ LineDown,
+ PageUp,
+ PageDown,
+ LineStart,
+ LineEnd,
+ DocumentStart,
+ DocumentEnd
+ }
+
+ static ExecutedRoutedEventHandler OnMoveCaret(CaretMovementType direction)
+ {
+ return (target, args) => {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ args.Handled = true;
+ textArea.ClearSelection();
+ MoveCaret(textArea, direction);
+ textArea.Caret.BringCaretToView();
+ }
+ };
+ }
+
+ static ExecutedRoutedEventHandler OnMoveCaretExtendSelection(CaretMovementType direction)
+ {
+ return (target, args) => {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ args.Handled = true;
+ TextViewPosition oldPosition = textArea.Caret.Position;
+ MoveCaret(textArea, direction);
+ textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
+ if (!textArea.Document.IsInUpdate) // if we're inside a larger update (e.g. called by EditingCommandHandler.OnDelete()), avoid calculating the caret rectangle now
+ textArea.Caret.BringCaretToView();
+ }
+ };
+ }
+
+ static ExecutedRoutedEventHandler OnMoveCaretBoxSelection(CaretMovementType direction)
+ {
+ return (target, args) => {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ args.Handled = true;
+ // First, convert the selection into a rectangle selection
+ // (this is required so that virtual space gets enabled for the caret movement)
+ if (textArea.Options.EnableRectangularSelection && !(textArea.Selection is RectangleSelection)) {
+ if (textArea.Selection.IsEmpty) {
+ textArea.Selection = new RectangleSelection(textArea, textArea.Caret.Position, textArea.Caret.Position);
+ } else {
+ // Convert normal selection to rectangle selection
+ textArea.Selection = new RectangleSelection(textArea, textArea.Selection.StartPosition, textArea.Caret.Position);
+ }
+ }
+ // Now move the caret and extend the selection
+ TextViewPosition oldPosition = textArea.Caret.Position;
+ MoveCaret(textArea, direction);
+ textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
+ textArea.Caret.BringCaretToView();
+ }
+ };
+ }
+
+ #region Caret movement
+ static void MoveCaret(TextArea textArea, CaretMovementType direction)
+ {
+ DocumentLine caretLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
+ VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(caretLine);
+ TextViewPosition caretPosition = textArea.Caret.Position;
+ TextLine textLine = visualLine.GetTextLine(caretPosition.VisualColumn);
+ switch (direction) {
+ case CaretMovementType.CharLeft:
+ MoveCaretLeft(textArea, caretPosition, visualLine, CaretPositioningMode.Normal);
+ break;
+ case CaretMovementType.CharRight:
+ MoveCaretRight(textArea, caretPosition, visualLine, CaretPositioningMode.Normal);
+ break;
+ case CaretMovementType.WordLeft:
+ MoveCaretLeft(textArea, caretPosition, visualLine, CaretPositioningMode.WordStart);
+ break;
+ case CaretMovementType.WordRight:
+ MoveCaretRight(textArea, caretPosition, visualLine, CaretPositioningMode.WordStart);
+ break;
+ case CaretMovementType.LineUp:
+ case CaretMovementType.LineDown:
+ case CaretMovementType.PageUp:
+ case CaretMovementType.PageDown:
+ MoveCaretUpDown(textArea, direction, visualLine, textLine, caretPosition.VisualColumn);
+ break;
+ case CaretMovementType.DocumentStart:
+ SetCaretPosition(textArea, 0, 0);
+ break;
+ case CaretMovementType.DocumentEnd:
+ SetCaretPosition(textArea, -1, textArea.Document.TextLength);
+ break;
+ case CaretMovementType.LineStart:
+ MoveCaretToStartOfLine(textArea, visualLine);
+ break;
+ case CaretMovementType.LineEnd:
+ MoveCaretToEndOfLine(textArea, visualLine);
+ break;
+ default:
+ throw new NotSupportedException(direction.ToString());
+ }
+ }
+ #endregion
+
+ #region Home/End
+ static void MoveCaretToStartOfLine(TextArea textArea, VisualLine visualLine)
+ {
+ int newVC = visualLine.GetNextCaretPosition(-1, LogicalDirection.Forward, CaretPositioningMode.WordStart, textArea.Selection.EnableVirtualSpace);
+ if (newVC < 0)
+ throw ThrowUtil.NoValidCaretPosition();
+ // when the caret is already at the start of the text, jump to start before whitespace
+ if (newVC == textArea.Caret.VisualColumn)
+ newVC = 0;
+ int offset = visualLine.FirstDocumentLine.Offset + visualLine.GetRelativeOffset(newVC);
+ SetCaretPosition(textArea, newVC, offset);
+ }
+
+ static void MoveCaretToEndOfLine(TextArea textArea, VisualLine visualLine)
+ {
+ int newVC = visualLine.VisualLength;
+ int offset = visualLine.FirstDocumentLine.Offset + visualLine.GetRelativeOffset(newVC);
+ SetCaretPosition(textArea, newVC, offset);
+ }
+ #endregion
+
+ #region By-character / By-word movement
+ static void MoveCaretRight(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode)
+ {
+ int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace);
+ if (pos >= 0) {
+ SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset);
+ } else {
+ // move to start of next line
+ DocumentLine nextDocumentLine = visualLine.LastDocumentLine.NextLine;
+ if (nextDocumentLine != null) {
+ VisualLine nextLine = textArea.TextView.GetOrConstructVisualLine(nextDocumentLine);
+ pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace);
+ if (pos < 0)
+ throw ThrowUtil.NoValidCaretPosition();
+ SetCaretPosition(textArea, pos, nextLine.GetRelativeOffset(pos) + nextLine.FirstDocumentLine.Offset);
+ } else {
+ // at end of document
+ Debug.Assert(visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength == textArea.Document.TextLength);
+ SetCaretPosition(textArea, -1, textArea.Document.TextLength);
+ }
+ }
+ }
+
+ static void MoveCaretLeft(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode)
+ {
+ int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace);
+ if (pos >= 0) {
+ SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset);
+ } else {
+ // move to end of previous line
+ DocumentLine previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine;
+ if (previousDocumentLine != null) {
+ VisualLine previousLine = textArea.TextView.GetOrConstructVisualLine(previousDocumentLine);
+ pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace);
+ if (pos < 0)
+ throw ThrowUtil.NoValidCaretPosition();
+ SetCaretPosition(textArea, pos, previousLine.GetRelativeOffset(pos) + previousLine.FirstDocumentLine.Offset);
+ } else {
+ // at start of document
+ Debug.Assert(visualLine.FirstDocumentLine.Offset == 0);
+ SetCaretPosition(textArea, 0, 0);
+ }
+ }
+ }
+ #endregion
+
+ #region Line+Page up/down
+ static void MoveCaretUpDown(TextArea textArea, CaretMovementType direction, VisualLine visualLine, TextLine textLine, int caretVisualColumn)
+ {
+ // moving up/down happens using the desired visual X position
+ double xPos = textArea.Caret.DesiredXPos;
+ if (double.IsNaN(xPos))
+ xPos = visualLine.GetTextLineVisualXPosition(textLine, caretVisualColumn);
+ // now find the TextLine+VisualLine where the caret will end up in
+ VisualLine targetVisualLine = visualLine;
+ TextLine targetLine;
+ int textLineIndex = visualLine.TextLines.IndexOf(textLine);
+ switch (direction) {
+ case CaretMovementType.LineUp:
+ {
+ // Move up: move to the previous TextLine in the same visual line
+ // or move to the last TextLine of the previous visual line
+ int prevLineNumber = visualLine.FirstDocumentLine.LineNumber - 1;
+ if (textLineIndex > 0) {
+ targetLine = visualLine.TextLines[textLineIndex - 1];
+ } else if (prevLineNumber >= 1) {
+ DocumentLine prevLine = textArea.Document.GetLineByNumber(prevLineNumber);
+ targetVisualLine = textArea.TextView.GetOrConstructVisualLine(prevLine);
+ targetLine = targetVisualLine.TextLines[targetVisualLine.TextLines.Count - 1];
+ } else {
+ targetLine = null;
+ }
+ break;
+ }
+ case CaretMovementType.LineDown:
+ {
+ // Move down: move to the next TextLine in the same visual line
+ // or move to the first TextLine of the next visual line
+ int nextLineNumber = visualLine.LastDocumentLine.LineNumber + 1;
+ if (textLineIndex < visualLine.TextLines.Count - 1) {
+ targetLine = visualLine.TextLines[textLineIndex + 1];
+ } else if (nextLineNumber <= textArea.Document.LineCount) {
+ DocumentLine nextLine = textArea.Document.GetLineByNumber(nextLineNumber);
+ targetVisualLine = textArea.TextView.GetOrConstructVisualLine(nextLine);
+ targetLine = targetVisualLine.TextLines[0];
+ } else {
+ targetLine = null;
+ }
+ break;
+ }
+ case CaretMovementType.PageUp:
+ case CaretMovementType.PageDown:
+ {
+ // Page up/down: find the target line using its visual position
+ double yPos = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineMiddle);
+ if (direction == CaretMovementType.PageUp)
+ yPos -= textArea.TextView.RenderSize.Height;
+ else
+ yPos += textArea.TextView.RenderSize.Height;
+ DocumentLine newLine = textArea.TextView.GetDocumentLineByVisualTop(yPos);
+ targetVisualLine = textArea.TextView.GetOrConstructVisualLine(newLine);
+ targetLine = targetVisualLine.GetTextLineByVisualYPosition(yPos);
+ break;
+ }
+ default:
+ throw new NotSupportedException(direction.ToString());
+ }
+ if (targetLine != null) {
+ double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle);
+ int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos), textArea.Selection.EnableVirtualSpace);
+ SetCaretPosition(textArea, targetVisualLine, targetLine, newVisualColumn, false);
+ textArea.Caret.DesiredXPos = xPos;
+ }
+ }
+ #endregion
+
+ #region SetCaretPosition
+ static void SetCaretPosition(TextArea textArea, VisualLine targetVisualLine, TextLine targetLine,
+ int newVisualColumn, bool allowWrapToNextLine)
+ {
+ int targetLineStartCol = targetVisualLine.GetTextLineVisualStartColumn(targetLine);
+ if (!allowWrapToNextLine && newVisualColumn >= targetLineStartCol + targetLine.Length) {
+ if (newVisualColumn <= targetVisualLine.VisualLength)
+ newVisualColumn = targetLineStartCol + targetLine.Length - 1;
+ }
+ int newOffset = targetVisualLine.GetRelativeOffset(newVisualColumn) + targetVisualLine.FirstDocumentLine.Offset;
+ SetCaretPosition(textArea, newVisualColumn, newOffset);
+ }
+
+ static void SetCaretPosition(TextArea textArea, int newVisualColumn, int newOffset)
+ {
+ textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(newOffset), newVisualColumn);
+ textArea.Caret.DesiredXPos = double.NaN;
+ }
+ #endregion
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretWeakEventHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretWeakEventHandler.cs
new file mode 100644
index 000000000..ebf5e4947
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretWeakEventHandler.cs
@@ -0,0 +1,33 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using Tango.Scripting.Editors.Utils;
+using System;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Contains classes for handling weak events on the Caret class.
+ /// </summary>
+ public static class CaretWeakEventManager
+ {
+ /// <summary>
+ /// Handles the Caret.PositionChanged event.
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
+ public sealed class PositionChanged : WeakEventManagerBase<PositionChanged, Caret>
+ {
+ /// <inheritdoc/>
+ protected override void StartListening(Caret source)
+ {
+ source.PositionChanged += DeliverEvent;
+ }
+
+ /// <inheritdoc/>
+ protected override void StopListening(Caret source)
+ {
+ source.PositionChanged -= DeliverEvent;
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DottedLineMargin.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DottedLineMargin.cs
new file mode 100644
index 000000000..3de848dda
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DottedLineMargin.cs
@@ -0,0 +1,63 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Media;
+using System.Windows.Shapes;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Margin for use with the text area.
+ /// A vertical dotted line to separate the line numbers from the text view.
+ /// </summary>
+ public static class DottedLineMargin
+ {
+ static readonly object tag = new object();
+
+ /// <summary>
+ /// Creates a vertical dotted line to separate the line numbers from the text view.
+ /// </summary>
+ public static UIElement Create()
+ {
+ Line line = new Line {
+ //X1 = 0, Y1 = 0, X2 = 0, Y2 = 1,
+ //StrokeDashArray = { 0, 2 },
+ //Stretch = Stretch.Fill,
+ //StrokeThickness = 1,
+ //StrokeDashCap = PenLineCap.Round,
+ //Margin = new Thickness(2, 0, 2, 0),
+ //Tag = tag
+ };
+
+ return line;
+ }
+
+ /// <summary>
+ /// Creates a vertical dotted line to separate the line numbers from the text view.
+ /// </summary>
+ [Obsolete("This method got published accidentally; and will be removed again in a future version. Use the parameterless overload instead.")]
+ public static UIElement Create(TextEditor editor)
+ {
+ Line line = (Line)Create();
+
+ line.SetBinding(
+ Line.StrokeProperty,
+ new Binding("LineNumbersForeground") { Source = editor }
+ );
+
+ return line;
+ }
+
+ /// <summary>
+ /// Gets whether the specified UIElement is the result of a DottedLineMargin.Create call.
+ /// </summary>
+ public static bool IsDottedLineMargin(UIElement element)
+ {
+ Line l = element as Line;
+ return l != null && l.Tag == tag;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DragDropException.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DragDropException.cs
new file mode 100644
index 000000000..2ba8e41ec
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DragDropException.cs
@@ -0,0 +1,46 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Runtime.Serialization;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Wraps exceptions that occur during drag'n'drop.
+ /// Exceptions during drag'n'drop might
+ /// get swallowed by WPF/COM, so AvalonEdit catches them and re-throws them later
+ /// wrapped in a DragDropException.
+ /// </summary>
+ [Serializable()]
+ public class DragDropException : Exception
+ {
+ /// <summary>
+ /// Creates a new DragDropException.
+ /// </summary>
+ public DragDropException() : base()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new DragDropException.
+ /// </summary>
+ public DragDropException(string message) : base(message)
+ {
+ }
+
+ /// <summary>
+ /// Creates a new DragDropException.
+ /// </summary>
+ public DragDropException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ /// <summary>
+ /// Deserializes a DragDropException.
+ /// </summary>
+ protected DragDropException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EditingCommandHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EditingCommandHandler.cs
new file mode 100644
index 000000000..685e7ae31
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EditingCommandHandler.cs
@@ -0,0 +1,578 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Input;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Highlighting;
+using Tango.Scripting.Editors.Search;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// We re-use the CommandBinding and InputBinding instances between multiple text areas,
+ /// so this class is static.
+ /// </summary>
+ static class EditingCommandHandler
+ {
+ /// <summary>
+ /// Creates a new <see cref="TextAreaInputHandler"/> for the text area.
+ /// </summary>
+ public static TextAreaInputHandler Create(TextArea textArea)
+ {
+ TextAreaInputHandler handler = new TextAreaInputHandler(textArea);
+ handler.CommandBindings.AddRange(CommandBindings);
+ handler.InputBindings.AddRange(InputBindings);
+ return handler;
+ }
+
+ static readonly List<CommandBinding> CommandBindings = new List<CommandBinding>();
+ static readonly List<InputBinding> InputBindings = new List<InputBinding>();
+
+ static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler)
+ {
+ CommandBindings.Add(new CommandBinding(command, handler));
+ InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key));
+ }
+
+ static EditingCommandHandler()
+ {
+ CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, OnDelete(ApplicationCommands.NotACommand), CanDelete));
+ AddBinding(EditingCommands.Delete, ModifierKeys.None, Key.Delete, OnDelete(EditingCommands.SelectRightByCharacter));
+ AddBinding(EditingCommands.DeleteNextWord, ModifierKeys.Control, Key.Delete, OnDelete(EditingCommands.SelectRightByWord));
+ AddBinding(EditingCommands.Backspace, ModifierKeys.None, Key.Back, OnDelete(EditingCommands.SelectLeftByCharacter));
+ InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(EditingCommands.Backspace, ModifierKeys.Shift, Key.Back)); // make Shift-Backspace do the same as plain backspace
+ AddBinding(EditingCommands.DeletePreviousWord, ModifierKeys.Control, Key.Back, OnDelete(EditingCommands.SelectLeftByWord));
+ AddBinding(EditingCommands.EnterParagraphBreak, ModifierKeys.None, Key.Enter, OnEnter);
+ AddBinding(EditingCommands.EnterLineBreak, ModifierKeys.Shift, Key.Enter, OnEnter);
+ AddBinding(EditingCommands.TabForward, ModifierKeys.None, Key.Tab, OnTab);
+ AddBinding(EditingCommands.TabBackward, ModifierKeys.Shift, Key.Tab, OnShiftTab);
+
+ CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopy, CanCutOrCopy));
+ CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCut, CanCutOrCopy));
+ CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPaste, CanPaste));
+
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.DeleteLine, OnDeleteLine));
+
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveLeadingWhitespace, OnRemoveLeadingWhitespace));
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveTrailingWhitespace, OnRemoveTrailingWhitespace));
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToUppercase, OnConvertToUpperCase));
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToLowercase, OnConvertToLowerCase));
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToTitleCase, OnConvertToTitleCase));
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.InvertCase, OnInvertCase));
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertTabsToSpaces, OnConvertTabsToSpaces));
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertSpacesToTabs, OnConvertSpacesToTabs));
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingTabsToSpaces, OnConvertLeadingTabsToSpaces));
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingSpacesToTabs, OnConvertLeadingSpacesToTabs));
+ CommandBindings.Add(new CommandBinding(AvalonEditCommands.IndentSelection, OnIndentSelection));
+
+ TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings);
+ }
+
+ static TextArea GetTextArea(object target)
+ {
+ return target as TextArea;
+ }
+
+ #region Text Transformation Helpers
+ enum DefaultSegmentType
+ {
+ None,
+ WholeDocument,
+ CurrentLine
+ }
+
+ /// <summary>
+ /// Calls transformLine on all lines in the selected range.
+ /// transformLine needs to handle read-only segments!
+ /// </summary>
+ static void TransformSelectedLines(Action<TextArea, DocumentLine> transformLine, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ using (textArea.Document.RunUpdate()) {
+ DocumentLine start, end;
+ if (textArea.Selection.IsEmpty) {
+ if (defaultSegmentType == DefaultSegmentType.CurrentLine) {
+ start = end = textArea.Document.GetLineByNumber(textArea.Caret.Line);
+ } else if (defaultSegmentType == DefaultSegmentType.WholeDocument) {
+ start = textArea.Document.Lines.First();
+ end = textArea.Document.Lines.Last();
+ } else {
+ start = end = null;
+ }
+ } else {
+ ISegment segment = textArea.Selection.SurroundingSegment;
+ start = textArea.Document.GetLineByOffset(segment.Offset);
+ end = textArea.Document.GetLineByOffset(segment.EndOffset);
+ // don't include the last line if no characters on it are selected
+ if (start != end && end.Offset == segment.EndOffset)
+ end = end.PreviousLine;
+ }
+ if (start != null) {
+ transformLine(textArea, start);
+ while (start != end) {
+ start = start.NextLine;
+ transformLine(textArea, start);
+ }
+ }
+ }
+ textArea.Caret.BringCaretToView();
+ args.Handled = true;
+ }
+ }
+
+ /// <summary>
+ /// Calls transformLine on all writable segment in the selected range.
+ /// </summary>
+ static void TransformSelectedSegments(Action<TextArea, ISegment> transformSegment, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ using (textArea.Document.RunUpdate()) {
+ IEnumerable<ISegment> segments;
+ if (textArea.Selection.IsEmpty) {
+ if (defaultSegmentType == DefaultSegmentType.CurrentLine) {
+ segments = new ISegment[] { textArea.Document.GetLineByNumber(textArea.Caret.Line) };
+ } else if (defaultSegmentType == DefaultSegmentType.WholeDocument) {
+ segments = textArea.Document.Lines.Cast<ISegment>();
+ } else {
+ segments = null;
+ }
+ } else {
+ segments = textArea.Selection.Segments.Cast<ISegment>();
+ }
+ if (segments != null) {
+ foreach (ISegment segment in segments.Reverse()) {
+ foreach (ISegment writableSegment in textArea.GetDeletableSegments(segment).Reverse()) {
+ transformSegment(textArea, writableSegment);
+ }
+ }
+ }
+ }
+ textArea.Caret.BringCaretToView();
+ args.Handled = true;
+ }
+ }
+ #endregion
+
+ #region EnterLineBreak
+ static void OnEnter(object target, ExecutedRoutedEventArgs args)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.IsKeyboardFocused) {
+ textArea.PerformTextInput("\n");
+ args.Handled = true;
+ }
+ }
+ #endregion
+
+ #region Tab
+ static void OnTab(object target, ExecutedRoutedEventArgs args)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ using (textArea.Document.RunUpdate()) {
+ if (textArea.Selection.IsMultiline) {
+ var segment = textArea.Selection.SurroundingSegment;
+ DocumentLine start = textArea.Document.GetLineByOffset(segment.Offset);
+ DocumentLine end = textArea.Document.GetLineByOffset(segment.EndOffset);
+ // don't include the last line if no characters on it are selected
+ if (start != end && end.Offset == segment.EndOffset)
+ end = end.PreviousLine;
+ DocumentLine current = start;
+ while (true) {
+ int offset = current.Offset;
+ if (textArea.ReadOnlySectionProvider.CanInsert(offset))
+ textArea.Document.Replace(offset, 0, textArea.Options.IndentationString, OffsetChangeMappingType.KeepAnchorBeforeInsertion);
+ if (current == end)
+ break;
+ current = current.NextLine;
+ }
+ } else {
+ string indentationString = textArea.Options.GetIndentationString(textArea.Caret.Column);
+ textArea.ReplaceSelectionWithText(indentationString);
+ }
+ }
+ textArea.Caret.BringCaretToView();
+ args.Handled = true;
+ }
+ }
+
+ static void OnShiftTab(object target, ExecutedRoutedEventArgs args)
+ {
+ TransformSelectedLines(
+ delegate (TextArea textArea, DocumentLine line) {
+ int offset = line.Offset;
+ ISegment s = TextUtilities.GetSingleIndentationSegment(textArea.Document, offset, textArea.Options.IndentationSize);
+ if (s.Length > 0) {
+ s = textArea.GetDeletableSegments(s).FirstOrDefault();
+ if (s != null && s.Length > 0) {
+ textArea.Document.Remove(s.Offset, s.Length);
+ }
+ }
+ }, target, args, DefaultSegmentType.CurrentLine);
+ }
+ #endregion
+
+ #region Delete
+ static ExecutedRoutedEventHandler OnDelete(RoutedUICommand selectingCommand)
+ {
+ return (target, args) => {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ // call BeginUpdate before running the 'selectingCommand'
+ // so that undoing the delete does not select the deleted character
+ using (textArea.Document.RunUpdate()) {
+ if (textArea.Selection.IsEmpty) {
+ TextViewPosition oldCaretPosition = textArea.Caret.Position;
+ if (textArea.Caret.IsInVirtualSpace && selectingCommand == EditingCommands.SelectRightByCharacter)
+ EditingCommands.SelectRightByWord.Execute(args.Parameter, textArea);
+ else
+ selectingCommand.Execute(args.Parameter, textArea);
+ bool hasSomethingDeletable = false;
+ foreach (ISegment s in textArea.Selection.Segments) {
+ if (textArea.GetDeletableSegments(s).Length > 0) {
+ hasSomethingDeletable = true;
+ break;
+ }
+ }
+ if (!hasSomethingDeletable) {
+ // If nothing in the selection is deletable; then reset caret+selection
+ // to the previous value. This prevents the caret from moving through read-only sections.
+ textArea.Caret.Position = oldCaretPosition;
+ textArea.ClearSelection();
+ }
+ }
+ textArea.RemoveSelectedText();
+ }
+ textArea.Caret.BringCaretToView();
+ args.Handled = true;
+ }
+ };
+ }
+
+ static void CanDelete(object target, CanExecuteRoutedEventArgs args)
+ {
+ // HasSomethingSelected for delete command
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ args.CanExecute = !textArea.Selection.IsEmpty;
+ args.Handled = true;
+ }
+ }
+ #endregion
+
+ #region Clipboard commands
+ static void CanCutOrCopy(object target, CanExecuteRoutedEventArgs args)
+ {
+ // HasSomethingSelected for copy and cut commands
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ args.CanExecute = textArea.Options.CutCopyWholeLine || !textArea.Selection.IsEmpty;
+ args.Handled = true;
+ }
+ }
+
+ static void OnCopy(object target, ExecutedRoutedEventArgs args)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine) {
+ DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
+ CopyWholeLine(textArea, currentLine);
+ } else {
+ CopySelectedText(textArea);
+ }
+ args.Handled = true;
+ }
+ }
+
+ static void OnCut(object target, ExecutedRoutedEventArgs args)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine) {
+ DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
+ CopyWholeLine(textArea, currentLine);
+ ISegment[] segmentsToDelete = textArea.GetDeletableSegments(new SimpleSegment(currentLine.Offset, currentLine.TotalLength));
+ for (int i = segmentsToDelete.Length - 1; i >= 0; i--) {
+ textArea.Document.Remove(segmentsToDelete[i]);
+ }
+ } else {
+ CopySelectedText(textArea);
+ textArea.RemoveSelectedText();
+ }
+ textArea.Caret.BringCaretToView();
+ args.Handled = true;
+ }
+ }
+
+ static void CopySelectedText(TextArea textArea)
+ {
+ var data = textArea.Selection.CreateDataObject(textArea);
+
+ try {
+ Clipboard.SetDataObject(data, true);
+ } catch (ExternalException) {
+ // Apparently this exception sometimes happens randomly.
+ // The MS controls just ignore it, so we'll do the same.
+ return;
+ }
+
+ string text = textArea.Selection.GetText();
+ text = TextUtilities.NormalizeNewLines(text, Environment.NewLine);
+ textArea.OnTextCopied(new TextEventArgs(text));
+ }
+
+ const string LineSelectedType = "MSDEVLineSelect"; // This is the type VS 2003 and 2005 use for flagging a whole line copy
+
+ static void CopyWholeLine(TextArea textArea, DocumentLine line)
+ {
+ ISegment wholeLine = new SimpleSegment(line.Offset, line.TotalLength);
+ string text = textArea.Document.GetText(wholeLine);
+ // Ensure we use the appropriate newline sequence for the OS
+ text = TextUtilities.NormalizeNewLines(text, Environment.NewLine);
+ DataObject data = new DataObject(text);
+
+ // Also copy text in HTML format to clipboard - good for pasting text into Word
+ // or to the SharpDevelop forums.
+ IHighlighter highlighter = textArea.GetService(typeof(IHighlighter)) as IHighlighter;
+ HtmlClipboard.SetHtml(data, HtmlClipboard.CreateHtmlFragment(textArea.Document, highlighter, wholeLine, new HtmlOptions(textArea.Options)));
+
+ MemoryStream lineSelected = new MemoryStream(1);
+ lineSelected.WriteByte(1);
+ data.SetData(LineSelectedType, lineSelected, false);
+
+ try {
+ Clipboard.SetDataObject(data, true);
+ } catch (ExternalException) {
+ // Apparently this exception sometimes happens randomly.
+ // The MS controls just ignore it, so we'll do the same.
+ return;
+ }
+ textArea.OnTextCopied(new TextEventArgs(text));
+ }
+
+ static void CanPaste(object target, CanExecuteRoutedEventArgs args)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ args.CanExecute = textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset)
+ && Clipboard.ContainsText();
+ // WPF Clipboard.ContainsText() is safe to call without catching ExternalExceptions
+ // because it doesn't try to lock the clipboard - it just peeks inside with IsClipboardFormatAvailable().
+ args.Handled = true;
+ }
+ }
+
+ static void OnPaste(object target, ExecutedRoutedEventArgs args)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ IDataObject dataObject;
+ try {
+ dataObject = Clipboard.GetDataObject();
+ } catch (ExternalException) {
+ return;
+ }
+ if (dataObject == null)
+ return;
+ Debug.WriteLine( dataObject.GetData(DataFormats.Html) as string );
+
+ // convert text back to correct newlines for this document
+ string newLine = TextUtilities.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line);
+ string text;
+ try {
+ text = (string)dataObject.GetData(DataFormats.UnicodeText);
+ text = TextUtilities.NormalizeNewLines(text, newLine);
+ } catch (OutOfMemoryException) {
+ return;
+ }
+
+ if (!string.IsNullOrEmpty(text)) {
+ bool fullLine = textArea.Options.CutCopyWholeLine && dataObject.GetDataPresent(LineSelectedType);
+ bool rectangular = dataObject.GetDataPresent(RectangleSelection.RectangularSelectionDataType);
+ if (fullLine) {
+ DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
+ if (textArea.ReadOnlySectionProvider.CanInsert(currentLine.Offset)) {
+ textArea.Document.Insert(currentLine.Offset, text);
+ }
+ } else if (rectangular && textArea.Selection.IsEmpty && !(textArea.Selection is RectangleSelection)) {
+ if (!RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, false))
+ textArea.ReplaceSelectionWithText(text);
+ } else {
+ textArea.ReplaceSelectionWithText(text);
+ }
+ }
+ textArea.Caret.BringCaretToView();
+ args.Handled = true;
+ }
+ }
+ #endregion
+
+ #region DeleteLine
+ static void OnDeleteLine(object target, ExecutedRoutedEventArgs args)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
+ textArea.Selection = Selection.Create(textArea, currentLine.Offset, currentLine.Offset + currentLine.TotalLength);
+ textArea.RemoveSelectedText();
+ args.Handled = true;
+ }
+ }
+ #endregion
+
+ #region Remove..Whitespace / Convert Tabs-Spaces
+ static void OnRemoveLeadingWhitespace(object target, ExecutedRoutedEventArgs args)
+ {
+ TransformSelectedLines(
+ delegate (TextArea textArea, DocumentLine line) {
+ textArea.Document.Remove(TextUtilities.GetLeadingWhitespace(textArea.Document, line));
+ }, target, args, DefaultSegmentType.WholeDocument);
+ }
+
+ static void OnRemoveTrailingWhitespace(object target, ExecutedRoutedEventArgs args)
+ {
+ TransformSelectedLines(
+ delegate (TextArea textArea, DocumentLine line) {
+ textArea.Document.Remove(TextUtilities.GetTrailingWhitespace(textArea.Document, line));
+ }, target, args, DefaultSegmentType.WholeDocument);
+ }
+
+ static void OnConvertTabsToSpaces(object target, ExecutedRoutedEventArgs args)
+ {
+ TransformSelectedSegments(ConvertTabsToSpaces, target, args, DefaultSegmentType.WholeDocument);
+ }
+
+ static void OnConvertLeadingTabsToSpaces(object target, ExecutedRoutedEventArgs args)
+ {
+ TransformSelectedLines(
+ delegate (TextArea textArea, DocumentLine line) {
+ ConvertTabsToSpaces(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line));
+ }, target, args, DefaultSegmentType.WholeDocument);
+ }
+
+ static void ConvertTabsToSpaces(TextArea textArea, ISegment segment)
+ {
+ TextDocument document = textArea.Document;
+ int endOffset = segment.EndOffset;
+ string indentationString = new string(' ', textArea.Options.IndentationSize);
+ for (int offset = segment.Offset; offset < endOffset; offset++) {
+ if (document.GetCharAt(offset) == '\t') {
+ document.Replace(offset, 1, indentationString, OffsetChangeMappingType.CharacterReplace);
+ endOffset += indentationString.Length - 1;
+ }
+ }
+ }
+
+ static void OnConvertSpacesToTabs(object target, ExecutedRoutedEventArgs args)
+ {
+ TransformSelectedSegments(ConvertSpacesToTabs, target, args, DefaultSegmentType.WholeDocument);
+ }
+
+ static void OnConvertLeadingSpacesToTabs(object target, ExecutedRoutedEventArgs args)
+ {
+ TransformSelectedLines(
+ delegate (TextArea textArea, DocumentLine line) {
+ ConvertSpacesToTabs(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line));
+ }, target, args, DefaultSegmentType.WholeDocument);
+ }
+
+ static void ConvertSpacesToTabs(TextArea textArea, ISegment segment)
+ {
+ TextDocument document = textArea.Document;
+ int endOffset = segment.EndOffset;
+ int indentationSize = textArea.Options.IndentationSize;
+ int spacesCount = 0;
+ for (int offset = segment.Offset; offset < endOffset; offset++) {
+ if (document.GetCharAt(offset) == ' ') {
+ spacesCount++;
+ if (spacesCount == indentationSize) {
+ document.Replace(offset - (indentationSize - 1), indentationSize, "\t", OffsetChangeMappingType.CharacterReplace);
+ spacesCount = 0;
+ offset -= indentationSize - 1;
+ endOffset -= indentationSize - 1;
+ }
+ } else {
+ spacesCount = 0;
+ }
+ }
+ }
+ #endregion
+
+ #region Convert...Case
+ static void ConvertCase(Func<string, string> transformText, object target, ExecutedRoutedEventArgs args)
+ {
+ TransformSelectedSegments(
+ delegate (TextArea textArea, ISegment segment) {
+ string oldText = textArea.Document.GetText(segment);
+ string newText = transformText(oldText);
+ textArea.Document.Replace(segment.Offset, segment.Length, newText, OffsetChangeMappingType.CharacterReplace);
+ }, target, args, DefaultSegmentType.WholeDocument);
+ }
+
+ static void OnConvertToUpperCase(object target, ExecutedRoutedEventArgs args)
+ {
+ ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToUpper, target, args);
+ }
+
+ static void OnConvertToLowerCase(object target, ExecutedRoutedEventArgs args)
+ {
+ ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToLower, target, args);
+ }
+
+ static void OnConvertToTitleCase(object target, ExecutedRoutedEventArgs args)
+ {
+ ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToTitleCase, target, args);
+ }
+
+ static void OnInvertCase(object target, ExecutedRoutedEventArgs args)
+ {
+ ConvertCase(InvertCase, target, args);
+ }
+
+ static string InvertCase(string text)
+ {
+ CultureInfo culture = CultureInfo.CurrentCulture;
+ char[] buffer = text.ToCharArray();
+ for (int i = 0; i < buffer.Length; ++i) {
+ char c = buffer[i];
+ buffer[i] = char.IsUpper(c) ? char.ToLower(c, culture) : char.ToUpper(c, culture);
+ }
+ return new string(buffer);
+ }
+ #endregion
+
+ static void OnIndentSelection(object target, ExecutedRoutedEventArgs args)
+ {
+ TextArea textArea = GetTextArea(target);
+ if (textArea != null && textArea.Document != null) {
+ using (textArea.Document.RunUpdate()) {
+ int start, end;
+ if (textArea.Selection.IsEmpty) {
+ start = 1;
+ end = textArea.Document.LineCount;
+ } else {
+ start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset).LineNumber;
+ end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.EndOffset).LineNumber;
+ }
+ textArea.IndentationStrategy.IndentLines(textArea.Document, start, end);
+ }
+ textArea.Caret.BringCaretToView();
+ args.Handled = true;
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EmptySelection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EmptySelection.cs
new file mode 100644
index 000000000..6cca63ecb
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EmptySelection.cs
@@ -0,0 +1,85 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ sealed class EmptySelection : Selection
+ {
+ public EmptySelection(TextArea textArea) : base(textArea)
+ {
+ }
+
+ public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e)
+ {
+ return this;
+ }
+
+ public override TextViewPosition StartPosition {
+ get { return new TextViewPosition(TextLocation.Empty); }
+ }
+
+ public override TextViewPosition EndPosition {
+ get { return new TextViewPosition(TextLocation.Empty); }
+ }
+
+ public override ISegment SurroundingSegment {
+ get { return null; }
+ }
+
+ public override Selection SetEndpoint(TextViewPosition endPosition)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition)
+ {
+ var document = textArea.Document;
+ if (document == null)
+ throw ThrowUtil.NoDocumentAssigned();
+ return Create(textArea, startPosition, endPosition);
+ }
+
+ public override IEnumerable<SelectionSegment> Segments {
+ get { return Empty<SelectionSegment>.Array; }
+ }
+
+ public override string GetText()
+ {
+ return string.Empty;
+ }
+
+ public override void ReplaceSelectionWithText(string newText)
+ {
+ if (newText == null)
+ throw new ArgumentNullException("newText");
+ newText = AddSpacesIfRequired(newText, textArea.Caret.Position, textArea.Caret.Position);
+ if (newText.Length > 0) {
+ if (textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset)) {
+ textArea.Document.Insert(textArea.Caret.Offset, newText);
+ }
+ }
+ textArea.Caret.VisualColumn = -1;
+ }
+
+ public override int Length {
+ get { return 0; }
+ }
+
+ // Use reference equality because there's only one EmptySelection per text area.
+ public override int GetHashCode()
+ {
+ return RuntimeHelpers.GetHashCode(this);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return this == obj;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/IReadOnlySectionProvider.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/IReadOnlySectionProvider.cs
new file mode 100644
index 000000000..fc775baf3
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/IReadOnlySectionProvider.cs
@@ -0,0 +1,32 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Determines whether the document can be modified.
+ /// </summary>
+ public interface IReadOnlySectionProvider
+ {
+ /// <summary>
+ /// Gets whether insertion is possible at the specified offset.
+ /// </summary>
+ bool CanInsert(int offset);
+
+ /// <summary>
+ /// Gets the deletable segments inside the given segment.
+ /// </summary>
+ /// <remarks>
+ /// All segments in the result must be within the given segment, and they must be returned in order
+ /// (e.g. if two segments are returned, EndOffset of first segment must be less than StartOffset of second segment).
+ ///
+ /// For replacements, the last segment being returned will be replaced with the new text. If an empty list is returned,
+ /// no replacement will be done.
+ /// </remarks>
+ IEnumerable<ISegment> GetDeletableSegments(ISegment segment);
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeNativeWrapper.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeNativeWrapper.cs
new file mode 100644
index 000000000..bc2c6f5e4
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeNativeWrapper.cs
@@ -0,0 +1,206 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Interop;
+using System.Windows.Media;
+using System.Windows.Media.TextFormatting;
+
+using Tango.Scripting.Editors;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+using Draw = System.Drawing;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Native API required for IME support.
+ /// </summary>
+ static class ImeNativeWrapper
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ struct CompositionForm
+ {
+ public int dwStyle;
+ public POINT ptCurrentPos;
+ public RECT rcArea;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct POINT
+ {
+ public int x;
+ public int y;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct RECT
+ {
+ public int left;
+ public int top;
+ public int right;
+ public int bottom;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
+ struct LOGFONT
+ {
+ public int lfHeight;
+ public int lfWidth;
+ public int lfEscapement;
+ public int lfOrientation;
+ public int lfWeight;
+ public byte lfItalic;
+ public byte lfUnderline;
+ public byte lfStrikeOut;
+ public byte lfCharSet;
+ public byte lfOutPrecision;
+ public byte lfClipPrecision;
+ public byte lfQuality;
+ public byte lfPitchAndFamily;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string lfFaceName;
+ }
+
+ const int CPS_CANCEL = 0x4;
+ const int NI_COMPOSITIONSTR = 0x15;
+ const int GCS_COMPSTR = 0x0008;
+
+ public const int WM_IME_COMPOSITION = 0x10F;
+ public const int WM_IME_SETCONTEXT = 0x281;
+ public const int WM_INPUTLANGCHANGE = 0x51;
+
+ [DllImport("imm32.dll")]
+ public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
+ [DllImport("imm32.dll")]
+ internal static extern IntPtr ImmGetContext(IntPtr hWnd);
+ [DllImport("imm32.dll")]
+ internal static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd);
+ [DllImport("imm32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
+ [DllImport("imm32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ static extern bool ImmNotifyIME(IntPtr hIMC, int dwAction, int dwIndex, int dwValue = 0);
+ [DllImport("imm32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref CompositionForm form);
+ [DllImport("imm32.dll", CharSet = CharSet.Unicode)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT font);
+ [DllImport("imm32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ static extern bool ImmGetCompositionFont(IntPtr hIMC, out LOGFONT font);
+
+ [DllImport("msctf.dll")]
+ static extern int TF_CreateThreadMgr(out ITfThreadMgr threadMgr);
+
+ [ThreadStatic] static bool textFrameworkThreadMgrInitialized;
+ [ThreadStatic] static ITfThreadMgr textFrameworkThreadMgr;
+
+ public static ITfThreadMgr GetTextFrameworkThreadManager()
+ {
+ if (!textFrameworkThreadMgrInitialized) {
+ textFrameworkThreadMgrInitialized = true;
+ TF_CreateThreadMgr(out textFrameworkThreadMgr);
+ }
+ return textFrameworkThreadMgr;
+ }
+
+ public static bool NotifyIme(IntPtr hIMC)
+ {
+ return ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL);
+ }
+
+ public static bool SetCompositionWindow(HwndSource source, IntPtr hIMC, TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ Rect textViewBounds = textArea.TextView.GetBounds(source);
+ Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source);
+ CompositionForm form = new CompositionForm();
+ form.dwStyle = 0x0020;
+ form.ptCurrentPos.x = (int)Math.Max(characterBounds.Left, textViewBounds.Left);
+ form.ptCurrentPos.y = (int)Math.Max(characterBounds.Top, textViewBounds.Top);
+ form.rcArea.left = (int)textViewBounds.Left;
+ form.rcArea.top = (int)textViewBounds.Top;
+ form.rcArea.right = (int)textViewBounds.Right;
+ form.rcArea.bottom = (int)textViewBounds.Bottom;
+ return ImmSetCompositionWindow(hIMC, ref form);
+ }
+
+ public static bool SetCompositionFont(HwndSource source, IntPtr hIMC, TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ LOGFONT lf = new LOGFONT();
+ Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source);
+ lf.lfFaceName = textArea.FontFamily.Source;
+ lf.lfHeight = (int)characterBounds.Height;
+ return ImmSetCompositionFont(hIMC, ref lf);
+ }
+
+ static Rect GetBounds(this TextView textView, HwndSource source)
+ {
+ // this may happen during layout changes in AvalonDock, so we just return an empty rectangle
+ // in those cases. It should be refreshed immediately.
+ if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView))
+ return EMPTY_RECT;
+ Rect displayRect = new Rect(0, 0, textView.ActualWidth, textView.ActualHeight);
+ return textView
+ .TransformToAncestor(source.RootVisual).TransformBounds(displayRect) // rect on root visual
+ .TransformToDevice(source.RootVisual); // rect on HWND
+ }
+
+ static readonly Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
+
+ static Rect GetCharacterBounds(this TextView textView, TextViewPosition pos, HwndSource source)
+ {
+ VisualLine vl = textView.GetVisualLine(pos.Line);
+ if (vl == null)
+ return EMPTY_RECT;
+ // this may happen during layout changes in AvalonDock, so we just return an empty rectangle
+ // in those cases. It should be refreshed immediately.
+ if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView))
+ return EMPTY_RECT;
+ TextLine line = vl.GetTextLine(pos.VisualColumn);
+ Rect displayRect;
+ // calculate the display rect for the current character
+ if (pos.VisualColumn < vl.VisualLengthWithEndOfLineMarker) {
+ displayRect = line.GetTextBounds(pos.VisualColumn, 1).First().Rectangle;
+ displayRect.Offset(0, vl.GetTextLineVisualYPosition(line, VisualYPosition.LineTop));
+ } else {
+ // if we are in virtual space, we just use one wide-space as character width
+ displayRect = new Rect(vl.GetVisualPosition(pos.VisualColumn, VisualYPosition.TextTop),
+ new Size(textView.WideSpaceWidth, textView.DefaultLineHeight));
+ }
+ // adjust to current scrolling
+ displayRect.Offset(-textView.ScrollOffset);
+ return textView
+ .TransformToAncestor(source.RootVisual).TransformBounds(displayRect) // rect on root visual
+ .TransformToDevice(source.RootVisual); // rect on HWND
+ }
+ }
+
+ [ComImport, Guid("aa80e801-2021-11d2-93e0-0060b067b86e"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ interface ITfThreadMgr
+ {
+ void Activate(out int clientId);
+ void Deactivate();
+ void CreateDocumentMgr(out IntPtr docMgr);
+ void EnumDocumentMgrs(out IntPtr enumDocMgrs);
+ void GetFocus(out IntPtr docMgr);
+ void SetFocus(IntPtr docMgr);
+ void AssociateFocus(IntPtr hwnd, IntPtr newDocMgr, out IntPtr prevDocMgr);
+ void IsThreadFocus([MarshalAs(UnmanagedType.Bool)] out bool isFocus);
+ void GetFunctionProvider(ref Guid classId, out IntPtr funcProvider);
+ void EnumFunctionProviders(out IntPtr enumProviders);
+ void GetGlobalCompartment(out IntPtr compartmentMgr);
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeSupport.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeSupport.cs
new file mode 100644
index 000000000..ad4c22e10
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeSupport.cs
@@ -0,0 +1,150 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Interop;
+using System.Windows.Media;
+using System.Windows.Media.TextFormatting;
+using Tango.Scripting.Editors;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ class ImeSupport
+ {
+ readonly TextArea textArea;
+ IntPtr currentContext;
+ IntPtr previousContext;
+ IntPtr defaultImeWnd;
+ HwndSource hwndSource;
+ EventHandler requerySuggestedHandler; // we need to keep the event handler instance alive because CommandManager.RequerySuggested uses weak references
+ bool isReadOnly;
+
+ public ImeSupport(TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ this.textArea = textArea;
+ InputMethod.SetIsInputMethodSuspended(this.textArea, textArea.Options.EnableImeSupport);
+ // We listen to CommandManager.RequerySuggested for both caret offset changes and changes to the set of read-only sections.
+ // This is because there's no dedicated event for read-only section changes; but RequerySuggested needs to be raised anyways
+ // to invalidate the Paste command.
+ requerySuggestedHandler = OnRequerySuggested;
+ CommandManager.RequerySuggested += requerySuggestedHandler;
+ textArea.OptionChanged += TextAreaOptionChanged;
+ }
+
+ void OnRequerySuggested(object sender, EventArgs e)
+ {
+ UpdateImeEnabled();
+ }
+
+ void TextAreaOptionChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "EnableImeSupport") {
+ InputMethod.SetIsInputMethodSuspended(this.textArea, textArea.Options.EnableImeSupport);
+ UpdateImeEnabled();
+ }
+ }
+
+ public void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
+ {
+ UpdateImeEnabled();
+ }
+
+ public void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
+ {
+ if (e.OldFocus == textArea && currentContext != IntPtr.Zero)
+ ImeNativeWrapper.NotifyIme(currentContext);
+ ClearContext();
+ }
+
+ void UpdateImeEnabled()
+ {
+ if (textArea.Options.EnableImeSupport && textArea.IsKeyboardFocused) {
+ bool newReadOnly = !textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset);
+ if (hwndSource == null || isReadOnly != newReadOnly) {
+ ClearContext(); // clear existing context (on read-only change)
+ isReadOnly = newReadOnly;
+ CreateContext();
+ }
+ } else {
+ ClearContext();
+ }
+ }
+
+ void ClearContext()
+ {
+ if (hwndSource != null) {
+ ImeNativeWrapper.ImmAssociateContext(hwndSource.Handle, previousContext);
+ ImeNativeWrapper.ImmReleaseContext(defaultImeWnd, currentContext);
+ currentContext = IntPtr.Zero;
+ defaultImeWnd = IntPtr.Zero;
+ hwndSource.RemoveHook(WndProc);
+ hwndSource = null;
+ }
+ }
+
+ void CreateContext()
+ {
+ hwndSource = (HwndSource)PresentationSource.FromVisual(this.textArea);
+ if (hwndSource != null) {
+ if (isReadOnly) {
+ defaultImeWnd = IntPtr.Zero;
+ currentContext = IntPtr.Zero;
+ } else {
+ defaultImeWnd = ImeNativeWrapper.ImmGetDefaultIMEWnd(IntPtr.Zero);
+ currentContext = ImeNativeWrapper.ImmGetContext(defaultImeWnd);
+ }
+ previousContext = ImeNativeWrapper.ImmAssociateContext(hwndSource.Handle, currentContext);
+ hwndSource.AddHook(WndProc);
+ // UpdateCompositionWindow() will be called by the caret becoming visible
+
+ var threadMgr = ImeNativeWrapper.GetTextFrameworkThreadManager();
+ if (threadMgr != null) {
+ // Even though the docu says passing null is invalid, this seems to help
+ // activating the IME on the default input context that is shared with WPF
+ threadMgr.SetFocus(IntPtr.Zero);
+ }
+ }
+ }
+
+ IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
+ {
+ switch (msg) {
+ case ImeNativeWrapper.WM_INPUTLANGCHANGE:
+ // Don't mark the message as handled; other windows
+ // might want to handle it as well.
+
+ // If we have a context, recreate it
+ if (hwndSource != null) {
+ ClearContext();
+ CreateContext();
+ }
+ break;
+ case ImeNativeWrapper.WM_IME_COMPOSITION:
+ UpdateCompositionWindow();
+ break;
+ }
+ return IntPtr.Zero;
+ }
+
+ public void UpdateCompositionWindow()
+ {
+ if (currentContext != IntPtr.Zero) {
+ ImeNativeWrapper.SetCompositionFont(hwndSource, currentContext, textArea);
+ ImeNativeWrapper.SetCompositionWindow(hwndSource, currentContext, textArea);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/LineNumberMargin.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/LineNumberMargin.cs
new file mode 100644
index 000000000..3da96b08c
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/LineNumberMargin.cs
@@ -0,0 +1,243 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.TextFormatting;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Margin showing line numbers.
+ /// </summary>
+ public class LineNumberMargin : AbstractMargin, IWeakEventListener
+ {
+ public Brush Foreground
+ {
+ get { return (Brush)GetValue(ForegroundProperty); }
+ set { SetValue(ForegroundProperty, value); }
+ }
+ public static readonly DependencyProperty ForegroundProperty =
+ DependencyProperty.Register("Foreground", typeof(Brush), typeof(LineNumberMargin), new PropertyMetadata(Brushes.Gray));
+
+
+
+ static LineNumberMargin()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(LineNumberMargin),
+ new FrameworkPropertyMetadata(typeof(LineNumberMargin)));
+ }
+
+ TextArea textArea;
+
+ Typeface typeface;
+ double emSize;
+
+ /// <inheritdoc/>
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ typeface = this.CreateTypeface();
+ emSize = (double)GetValue(TextBlock.FontSizeProperty);
+
+ FormattedText text = TextFormatterFactory.CreateFormattedText(
+ this,
+ new string('9', maxLineNumberLength),
+ typeface,
+ emSize,
+ Foreground
+ );
+ return new Size(text.Width, 0);
+ }
+
+ /// <inheritdoc/>
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ TextView textView = this.TextView;
+ Size renderSize = this.RenderSize;
+ if (textView != null && textView.VisualLinesValid) {
+ var foreground = Foreground;
+ foreach (VisualLine line in textView.VisualLines) {
+ int lineNumber = line.FirstDocumentLine.LineNumber;
+ FormattedText text = TextFormatterFactory.CreateFormattedText(
+ this,
+ lineNumber.ToString(CultureInfo.CurrentCulture),
+ typeface, emSize, foreground
+ );
+ double y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop);
+ drawingContext.DrawText(text, new Point((renderSize.Width / 2) - text.Width + 5, y - textView.VerticalOffset));
+ }
+ }
+ }
+
+ /// <inheritdoc/>
+ 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();
+ }
+
+ /// <inheritdoc/>
+ 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();
+ }
+
+ /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
+ 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);
+ }
+
+ int maxLineNumberLength = 1;
+
+ void OnDocumentLineCountChanged()
+ {
+ int documentLineCount = Document != null ? Document.LineCount : 1;
+ int newLength = documentLineCount.ToString(CultureInfo.CurrentCulture).Length;
+
+ // 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();
+ }
+ }
+
+ void TextViewVisualLinesChanged(object sender, EventArgs e)
+ {
+ InvalidateVisual();
+ }
+
+ AnchorSegment selectionStart;
+ bool selecting;
+
+ /// <inheritdoc/>
+ protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
+ {
+ base.OnMouseLeftButtonDown(e);
+ if (!e.Handled && TextView != null && textArea != null) {
+ e.Handled = true;
+ textArea.Focus();
+
+ SimpleSegment currentSeg = GetTextLineSegment(e);
+ if (currentSeg == SimpleSegment.Invalid)
+ return;
+ textArea.Caret.Offset = currentSeg.Offset + currentSeg.Length;
+ if (CaptureMouse()) {
+ selecting = true;
+ selectionStart = new AnchorSegment(Document, currentSeg.Offset, currentSeg.Length);
+ if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) {
+ SimpleSelection simpleSelection = textArea.Selection as SimpleSelection;
+ if (simpleSelection != null)
+ selectionStart = new AnchorSegment(Document, simpleSelection.SurroundingSegment);
+ }
+ textArea.Selection = Selection.Create(textArea, selectionStart);
+ if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) {
+ ExtendSelection(currentSeg);
+ }
+ }
+ }
+ }
+
+ SimpleSegment GetTextLineSegment(MouseEventArgs e)
+ {
+ Point pos = e.GetPosition(TextView);
+ pos.X = 0;
+ pos.Y += TextView.VerticalOffset;
+ VisualLine vl = TextView.GetVisualLineFromVisualTop(pos.Y);
+ if (vl == null)
+ return SimpleSegment.Invalid;
+ TextLine tl = vl.GetTextLineByVisualYPosition(pos.Y);
+ int visualStartColumn = vl.GetTextLineVisualStartColumn(tl);
+ int visualEndColumn = visualStartColumn + tl.Length;
+ int relStart = vl.FirstDocumentLine.Offset;
+ int startOffset = vl.GetRelativeOffset(visualStartColumn) + relStart;
+ int endOffset = vl.GetRelativeOffset(visualEndColumn) + relStart;
+ if (endOffset == vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length)
+ endOffset += vl.LastDocumentLine.DelimiterLength;
+ return new SimpleSegment(startOffset, endOffset - startOffset);
+ }
+
+ void ExtendSelection(SimpleSegment currentSeg)
+ {
+ if (currentSeg.Offset < selectionStart.Offset) {
+ textArea.Caret.Offset = currentSeg.Offset;
+ textArea.Selection = Selection.Create(textArea, currentSeg.Offset, selectionStart.Offset + selectionStart.Length);
+ } else {
+ textArea.Caret.Offset = currentSeg.Offset + currentSeg.Length;
+ textArea.Selection = Selection.Create(textArea, selectionStart.Offset, currentSeg.Offset + currentSeg.Length);
+ }
+ }
+
+ /// <inheritdoc/>
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ if (selecting && textArea != null && TextView != null) {
+ e.Handled = true;
+ SimpleSegment currentSeg = GetTextLineSegment(e);
+ if (currentSeg == SimpleSegment.Invalid)
+ return;
+ ExtendSelection(currentSeg);
+ }
+ base.OnMouseMove(e);
+ }
+
+ /// <inheritdoc/>
+ protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
+ {
+ if (selecting) {
+ selecting = false;
+ selectionStart = null;
+ ReleaseMouseCapture();
+ e.Handled = true;
+ }
+ base.OnMouseLeftButtonUp(e);
+ }
+
+ /// <inheritdoc/>
+ protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
+ {
+ // accept clicks even when clicking on the background
+ return new PointHitTestResult(this, hitTestParameters.HitPoint);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/NoReadOnlySections.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/NoReadOnlySections.cs
new file mode 100644
index 000000000..35cd1dd1b
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/NoReadOnlySections.cs
@@ -0,0 +1,50 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// <see cref="IReadOnlySectionProvider"/> that has no read-only sections; all text is editable.
+ /// </summary>
+ sealed class NoReadOnlySections : IReadOnlySectionProvider
+ {
+ public static readonly NoReadOnlySections Instance = new NoReadOnlySections();
+
+ public bool CanInsert(int offset)
+ {
+ return true;
+ }
+
+ public IEnumerable<ISegment> GetDeletableSegments(ISegment segment)
+ {
+ if (segment == null)
+ throw new ArgumentNullException("segment");
+ // the segment is always deletable
+ return ExtensionMethods.Sequence(segment);
+ }
+ }
+
+ /// <summary>
+ /// <see cref="IReadOnlySectionProvider"/> that completely disables editing.
+ /// </summary>
+ sealed class ReadOnlyDocument : IReadOnlySectionProvider
+ {
+ public static readonly ReadOnlyDocument Instance = new ReadOnlyDocument();
+
+ public bool CanInsert(int offset)
+ {
+ return false;
+ }
+
+ public IEnumerable<ISegment> GetDeletableSegments(ISegment segment)
+ {
+ return Enumerable.Empty<ISegment>();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/RectangleSelection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/RectangleSelection.cs
new file mode 100644
index 000000000..2d87d7934
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/RectangleSelection.cs
@@ -0,0 +1,396 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media.TextFormatting;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Rectangular selection ("box selection").
+ /// </summary>
+ public sealed class RectangleSelection : Selection
+ {
+ #region Commands
+ /// <summary>
+ /// Expands the selection left by one character, creating a rectangular selection.
+ /// Key gesture: Alt+Shift+Left
+ /// </summary>
+ public static readonly RoutedUICommand BoxSelectLeftByCharacter = Command("BoxSelectLeftByCharacter");
+
+ /// <summary>
+ /// Expands the selection right by one character, creating a rectangular selection.
+ /// Key gesture: Alt+Shift+Right
+ /// </summary>
+ public static readonly RoutedUICommand BoxSelectRightByCharacter = Command("BoxSelectRightByCharacter");
+
+ /// <summary>
+ /// Expands the selection left by one word, creating a rectangular selection.
+ /// Key gesture: Ctrl+Alt+Shift+Left
+ /// </summary>
+ public static readonly RoutedUICommand BoxSelectLeftByWord = Command("BoxSelectLeftByWord");
+
+ /// <summary>
+ /// Expands the selection left by one word, creating a rectangular selection.
+ /// Key gesture: Ctrl+Alt+Shift+Right
+ /// </summary>
+ public static readonly RoutedUICommand BoxSelectRightByWord = Command("BoxSelectRightByWord");
+
+ /// <summary>
+ /// Expands the selection up by one line, creating a rectangular selection.
+ /// Key gesture: Alt+Shift+Up
+ /// </summary>
+ public static readonly RoutedUICommand BoxSelectUpByLine = Command("BoxSelectUpByLine");
+
+ /// <summary>
+ /// Expands the selection up by one line, creating a rectangular selection.
+ /// Key gesture: Alt+Shift+Down
+ /// </summary>
+ public static readonly RoutedUICommand BoxSelectDownByLine = Command("BoxSelectDownByLine");
+
+ /// <summary>
+ /// Expands the selection to the start of the line, creating a rectangular selection.
+ /// Key gesture: Alt+Shift+Home
+ /// </summary>
+ public static readonly RoutedUICommand BoxSelectToLineStart = Command("BoxSelectToLineStart");
+
+ /// <summary>
+ /// Expands the selection to the end of the line, creating a rectangular selection.
+ /// Key gesture: Alt+Shift+End
+ /// </summary>
+ public static readonly RoutedUICommand BoxSelectToLineEnd = Command("BoxSelectToLineEnd");
+
+ static RoutedUICommand Command(string name)
+ {
+ return new RoutedUICommand(name, name, typeof(RectangleSelection));
+ }
+ #endregion
+
+ TextDocument document;
+ readonly int startLine, endLine;
+ readonly double startXPos, endXPos;
+ readonly int topLeftOffset, bottomRightOffset;
+ readonly TextViewPosition start, end;
+
+ readonly List<SelectionSegment> segments = new List<SelectionSegment>();
+
+ #region Constructors
+ /// <summary>
+ /// Creates a new rectangular selection.
+ /// </summary>
+ public RectangleSelection(TextArea textArea, TextViewPosition start, TextViewPosition end)
+ : base(textArea)
+ {
+ InitDocument();
+ this.startLine = start.Line;
+ this.endLine = end.Line;
+ this.startXPos = GetXPos(textArea, start);
+ this.endXPos = GetXPos(textArea, end);
+ CalculateSegments();
+ this.topLeftOffset = this.segments.First().StartOffset;
+ this.bottomRightOffset = this.segments.Last().EndOffset;
+
+ this.start = start;
+ this.end = end;
+ }
+
+ private RectangleSelection(TextArea textArea, int startLine, double startXPos, TextViewPosition end)
+ : base(textArea)
+ {
+ InitDocument();
+ this.startLine = startLine;
+ this.endLine = end.Line;
+ this.startXPos = startXPos;
+ this.endXPos = GetXPos(textArea, end);
+ CalculateSegments();
+ this.topLeftOffset = this.segments.First().StartOffset;
+ this.bottomRightOffset = this.segments.Last().EndOffset;
+
+ this.start = GetStart();
+ this.end = end;
+ }
+
+ private RectangleSelection(TextArea textArea, TextViewPosition start, int endLine, double endXPos)
+ : base(textArea)
+ {
+ InitDocument();
+ this.startLine = start.Line;
+ this.endLine = endLine;
+ this.startXPos = GetXPos(textArea, start);
+ this.endXPos = endXPos;
+ CalculateSegments();
+ this.topLeftOffset = this.segments.First().StartOffset;
+ this.bottomRightOffset = this.segments.Last().EndOffset;
+
+ this.start = start;
+ this.end = GetEnd();
+ }
+
+ void InitDocument()
+ {
+ document = textArea.Document;
+ if (document == null)
+ throw ThrowUtil.NoDocumentAssigned();
+ }
+
+ static double GetXPos(TextArea textArea, TextViewPosition pos)
+ {
+ DocumentLine documentLine = textArea.Document.GetLineByNumber(pos.Line);
+ VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(documentLine);
+ int vc = visualLine.ValidateVisualColumn(pos, true);
+ TextLine textLine = visualLine.GetTextLine(vc);
+ return visualLine.GetTextLineVisualXPosition(textLine, vc);
+ }
+
+ void CalculateSegments()
+ {
+ DocumentLine nextLine = document.GetLineByNumber(Math.Min(startLine, endLine));
+ do {
+ VisualLine vl = textArea.TextView.GetOrConstructVisualLine(nextLine);
+ int startVC = vl.GetVisualColumn(new Point(startXPos, 0), true);
+ int endVC = vl.GetVisualColumn(new Point(endXPos, 0), true);
+
+ int baseOffset = vl.FirstDocumentLine.Offset;
+ int startOffset = baseOffset + vl.GetRelativeOffset(startVC);
+ int endOffset = baseOffset + vl.GetRelativeOffset(endVC);
+ segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC));
+
+ nextLine = vl.LastDocumentLine.NextLine;
+ } while (nextLine != null && nextLine.LineNumber <= Math.Max(startLine, endLine));
+ }
+
+ TextViewPosition GetStart()
+ {
+ SelectionSegment segment = (startLine < endLine ? segments.First() : segments.Last());
+ if (startXPos < endXPos) {
+ return new TextViewPosition(document.GetLocation(segment.StartOffset), segment.StartVisualColumn);
+ } else {
+ return new TextViewPosition(document.GetLocation(segment.EndOffset), segment.EndVisualColumn);
+ }
+ }
+
+ TextViewPosition GetEnd()
+ {
+ SelectionSegment segment = (startLine < endLine ? segments.Last() : segments.First());
+ if (startXPos < endXPos) {
+ return new TextViewPosition(document.GetLocation(segment.EndOffset), segment.EndVisualColumn);
+ } else {
+ return new TextViewPosition(document.GetLocation(segment.StartOffset), segment.StartVisualColumn);
+ }
+ }
+ #endregion
+
+ /// <inheritdoc/>
+ public override string GetText()
+ {
+ StringBuilder b = new StringBuilder();
+ foreach (ISegment s in this.Segments) {
+ if (b.Length > 0)
+ b.AppendLine();
+ b.Append(document.GetText(s));
+ }
+ return b.ToString();
+ }
+
+ /// <inheritdoc/>
+ public override Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition)
+ {
+ return SetEndpoint(endPosition);
+ }
+
+ /// <inheritdoc/>
+ public override int Length {
+ get {
+ return this.Segments.Sum(s => s.Length);
+ }
+ }
+
+ /// <inheritdoc/>
+ public override bool EnableVirtualSpace {
+ get { return true; }
+ }
+
+ /// <inheritdoc/>
+ public override ISegment SurroundingSegment {
+ get {
+ return new SimpleSegment(topLeftOffset, bottomRightOffset - topLeftOffset);
+ }
+ }
+
+ /// <inheritdoc/>
+ public override IEnumerable<SelectionSegment> Segments {
+ get { return segments; }
+ }
+
+ /// <inheritdoc/>
+ public override TextViewPosition StartPosition {
+ get { return start; }
+ }
+
+ /// <inheritdoc/>
+ public override TextViewPosition EndPosition {
+ get { return end; }
+ }
+
+ /// <inheritdoc/>
+ public override bool Equals(object obj)
+ {
+ RectangleSelection r = obj as RectangleSelection;
+ return r != null && r.textArea == this.textArea
+ && r.topLeftOffset == this.topLeftOffset && r.bottomRightOffset == this.bottomRightOffset
+ && r.startLine == this.startLine && r.endLine == this.endLine
+ && r.startXPos == this.startXPos && r.endXPos == this.endXPos;
+ }
+
+ /// <inheritdoc/>
+ public override int GetHashCode()
+ {
+ return topLeftOffset ^ bottomRightOffset;
+ }
+
+ /// <inheritdoc/>
+ public override Selection SetEndpoint(TextViewPosition endPosition)
+ {
+ return new RectangleSelection(textArea, startLine, startXPos, endPosition);
+ }
+
+ int GetVisualColumnFromXPos(int line, double xPos)
+ {
+ var vl = textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(line));
+ return vl.GetVisualColumn(new Point(xPos, 0), true);
+ }
+
+ /// <inheritdoc/>
+ public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e)
+ {
+ TextLocation newStartLocation = textArea.Document.GetLocation(e.GetNewOffset(topLeftOffset, AnchorMovementType.AfterInsertion));
+ TextLocation newEndLocation = textArea.Document.GetLocation(e.GetNewOffset(bottomRightOffset, AnchorMovementType.BeforeInsertion));
+
+ return new RectangleSelection(textArea,
+ new TextViewPosition(newStartLocation, GetVisualColumnFromXPos(newStartLocation.Line, startXPos)),
+ new TextViewPosition(newEndLocation, GetVisualColumnFromXPos(newEndLocation.Line, endXPos)));
+ }
+
+ /// <inheritdoc/>
+ public override void ReplaceSelectionWithText(string newText)
+ {
+ if (newText == null)
+ throw new ArgumentNullException("newText");
+ using (textArea.Document.RunUpdate()) {
+ TextViewPosition start = new TextViewPosition(document.GetLocation(topLeftOffset), GetVisualColumnFromXPos(startLine, startXPos));
+ TextViewPosition end = new TextViewPosition(document.GetLocation(bottomRightOffset), GetVisualColumnFromXPos(endLine, endXPos));
+ int insertionLength;
+ int totalInsertionLength = 0;
+ int firstInsertionLength = 0;
+ int editOffset = Math.Min(topLeftOffset, bottomRightOffset);
+ TextViewPosition pos;
+ if (NewLineFinder.NextNewLine(newText, 0) == SimpleSegment.Invalid) {
+ // insert same text into every line
+ foreach (SelectionSegment lineSegment in this.Segments.Reverse()) {
+ ReplaceSingleLineText(textArea, lineSegment, newText, out insertionLength);
+ totalInsertionLength += insertionLength;
+ firstInsertionLength = insertionLength;
+ }
+
+ int newEndOffset = editOffset + totalInsertionLength;
+ pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength));
+
+ textArea.Selection = new RectangleSelection(textArea, pos, Math.Max(startLine, endLine), GetXPos(textArea, pos));
+ } else {
+ string[] lines = newText.Split(NewLineFinder.NewlineStrings, segments.Count, StringSplitOptions.None);
+ int line = Math.Min(startLine, endLine);
+ for (int i = lines.Length - 1; i >= 0; i--) {
+ ReplaceSingleLineText(textArea, segments[i], lines[i], out insertionLength);
+ firstInsertionLength = insertionLength;
+ }
+ pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength));
+ textArea.ClearSelection();
+ }
+ textArea.Caret.Position = textArea.TextView.GetPosition(new Point(GetXPos(textArea, pos), textArea.TextView.GetVisualTopByDocumentLine(Math.Max(startLine, endLine)))).GetValueOrDefault();
+ }
+ }
+
+ void ReplaceSingleLineText(TextArea textArea, SelectionSegment lineSegment, string newText, out int insertionLength)
+ {
+ if (lineSegment.Length == 0) {
+ if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.StartOffset)) {
+ newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(document.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn));
+ textArea.Document.Insert(lineSegment.StartOffset, newText);
+ }
+ } else {
+ ISegment[] segmentsToDelete = textArea.GetDeletableSegments(lineSegment);
+ for (int i = segmentsToDelete.Length - 1; i >= 0; i--) {
+ if (i == segmentsToDelete.Length - 1) {
+ if (segmentsToDelete[i].Offset == SurroundingSegment.Offset && segmentsToDelete[i].Length == SurroundingSegment.Length) {
+ newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(document.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn));
+ }
+ textArea.Document.Replace(segmentsToDelete[i], newText);
+ } else {
+ textArea.Document.Remove(segmentsToDelete[i]);
+ }
+ }
+ }
+ insertionLength = newText.Length;
+ }
+
+ /// <summary>
+ /// Performs a rectangular paste operation.
+ /// </summary>
+ public static bool PerformRectangularPaste(TextArea textArea, TextViewPosition startPosition, string text, bool selectInsertedText)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ if (text == null)
+ throw new ArgumentNullException("text");
+ int newLineCount = text.Count(c => c == '\n'); // TODO might not work in all cases, but single \r line endings are really rare today.
+ TextLocation endLocation = new TextLocation(startPosition.Line + newLineCount, startPosition.Column);
+ if (endLocation.Line <= textArea.Document.LineCount) {
+ int endOffset = textArea.Document.GetOffset(endLocation);
+ if (textArea.Selection.EnableVirtualSpace || textArea.Document.GetLocation(endOffset) == endLocation) {
+ RectangleSelection rsel = new RectangleSelection(textArea, startPosition, endLocation.Line, GetXPos(textArea, startPosition));
+ rsel.ReplaceSelectionWithText(text);
+ if (selectInsertedText && textArea.Selection is RectangleSelection) {
+ RectangleSelection sel = (RectangleSelection)textArea.Selection;
+ textArea.Selection = new RectangleSelection(textArea, startPosition, sel.endLine, sel.endXPos);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Gets the name of the entry in the DataObject that signals rectangle selections.
+ /// </summary>
+ public const string RectangularSelectionDataType = "AvalonEditRectangularSelection";
+
+ /// <inheritdoc/>
+ public override System.Windows.DataObject CreateDataObject(TextArea textArea)
+ {
+ var data = base.CreateDataObject(textArea);
+
+ MemoryStream isRectangle = new MemoryStream(1);
+ isRectangle.WriteByte(1);
+ data.SetData(RectangularSelectionDataType, isRectangle, false);
+ return data;
+ }
+
+ /// <inheritdoc/>
+ public override string ToString()
+ {
+ // It's possible that ToString() gets called on old (invalid) selections, e.g. for "change from... to..." debug message
+ // make sure we don't crash even when the desired locations don't exist anymore.
+ return string.Format("[RectangleSelection {0} {1} {2} to {3} {4} {5}]", startLine, topLeftOffset, startXPos, endLine, bottomRightOffset, endXPos);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Selection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Selection.cs
new file mode 100644
index 000000000..014fa8680
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Selection.cs
@@ -0,0 +1,268 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Highlighting;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Base class for selections.
+ /// </summary>
+ public abstract class Selection
+ {
+ /// <summary>
+ /// Creates a new simple selection that selects the text from startOffset to endOffset.
+ /// </summary>
+ public static Selection Create(TextArea textArea, int startOffset, int endOffset)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ if (startOffset == endOffset)
+ return textArea.emptySelection;
+ else
+ return new SimpleSelection(textArea,
+ new TextViewPosition(textArea.Document.GetLocation(startOffset)),
+ new TextViewPosition(textArea.Document.GetLocation(endOffset)));
+ }
+
+ internal static Selection Create(TextArea textArea, TextViewPosition start, TextViewPosition end)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ if (textArea.Document.GetOffset(start.Location) == textArea.Document.GetOffset(end.Location) && start.VisualColumn == end.VisualColumn)
+ return textArea.emptySelection;
+ else
+ return new SimpleSelection(textArea, start, end);
+ }
+
+ /// <summary>
+ /// Creates a new simple selection that selects the text in the specified segment.
+ /// </summary>
+ public static Selection Create(TextArea textArea, ISegment segment)
+ {
+ if (segment == null)
+ throw new ArgumentNullException("segment");
+ return Create(textArea, segment.Offset, segment.EndOffset);
+ }
+
+ internal readonly TextArea textArea;
+
+ /// <summary>
+ /// Constructor for Selection.
+ /// </summary>
+ protected Selection(TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ this.textArea = textArea;
+ }
+
+ /// <summary>
+ /// Gets the start position of the selection.
+ /// </summary>
+ public abstract TextViewPosition StartPosition { get; }
+
+ /// <summary>
+ /// Gets the end position of the selection.
+ /// </summary>
+ public abstract TextViewPosition EndPosition { get; }
+
+ /// <summary>
+ /// Gets the selected text segments.
+ /// </summary>
+ public abstract IEnumerable<SelectionSegment> Segments { get; }
+
+ /// <summary>
+ /// Gets the smallest segment that contains all segments in this selection.
+ /// May return null if the selection is empty.
+ /// </summary>
+ public abstract ISegment SurroundingSegment { get; }
+
+ /// <summary>
+ /// Replaces the selection with the specified text.
+ /// </summary>
+ public abstract void ReplaceSelectionWithText(string newText);
+
+ internal string AddSpacesIfRequired(string newText, TextViewPosition start, TextViewPosition end)
+ {
+ if (EnableVirtualSpace && InsertVirtualSpaces(newText, start, end)) {
+ var line = textArea.Document.GetLineByNumber(start.Line);
+ string lineText = textArea.Document.GetText(line);
+ var vLine = textArea.TextView.GetOrConstructVisualLine(line);
+ int colDiff = start.VisualColumn - vLine.VisualLengthWithEndOfLineMarker;
+ if (colDiff > 0) {
+ string additionalSpaces = "";
+ if (!textArea.Options.ConvertTabsToSpaces && lineText.Trim('\t').Length == 0) {
+ int tabCount = (int)(colDiff / textArea.Options.IndentationSize);
+ additionalSpaces = new string('\t', tabCount);
+ colDiff -= tabCount * textArea.Options.IndentationSize;
+ }
+ additionalSpaces += new string(' ', colDiff);
+ return additionalSpaces + newText;
+ }
+ }
+ return newText;
+ }
+
+ bool InsertVirtualSpaces(string newText, TextViewPosition start, TextViewPosition end)
+ {
+ return (!string.IsNullOrEmpty(newText) || !(IsInVirtualSpace(start) && IsInVirtualSpace(end)))
+ && newText != "\r\n"
+ && newText != "\n"
+ && newText != "\r";
+ }
+
+ bool IsInVirtualSpace(TextViewPosition pos)
+ {
+ return pos.VisualColumn > textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(pos.Line)).VisualLength;
+ }
+
+ /// <summary>
+ /// Updates the selection when the document changes.
+ /// </summary>
+ public abstract Selection UpdateOnDocumentChange(DocumentChangeEventArgs e);
+
+ /// <summary>
+ /// Gets whether the selection is empty.
+ /// </summary>
+ public virtual bool IsEmpty {
+ get { return Length == 0; }
+ }
+
+ /// <summary>
+ /// Gets whether virtual space is enabled for this selection.
+ /// </summary>
+ public virtual bool EnableVirtualSpace {
+ get { return textArea.Options.EnableVirtualSpace; }
+ }
+
+ /// <summary>
+ /// Gets the selection length.
+ /// </summary>
+ public abstract int Length { get; }
+
+ /// <summary>
+ /// Returns a new selection with the changed end point.
+ /// </summary>
+ /// <exception cref="NotSupportedException">Cannot set endpoint for empty selection</exception>
+ public abstract Selection SetEndpoint(TextViewPosition endPosition);
+
+ /// <summary>
+ /// If this selection is empty, starts a new selection from <paramref name="startPosition"/> to
+ /// <paramref name="endPosition"/>, otherwise, changes the endpoint of this selection.
+ /// </summary>
+ public abstract Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition);
+
+ /// <summary>
+ /// Gets whether the selection is multi-line.
+ /// </summary>
+ public virtual bool IsMultiline {
+ get {
+ ISegment surroundingSegment = this.SurroundingSegment;
+ if (surroundingSegment == null)
+ return false;
+ int start = surroundingSegment.Offset;
+ int end = start + surroundingSegment.Length;
+ var document = textArea.Document;
+ if (document == null)
+ throw ThrowUtil.NoDocumentAssigned();
+ return document.GetLineByOffset(start) != document.GetLineByOffset(end);
+ }
+ }
+
+ /// <summary>
+ /// Gets the selected text.
+ /// </summary>
+ public virtual string GetText()
+ {
+ var document = textArea.Document;
+ if (document == null)
+ throw ThrowUtil.NoDocumentAssigned();
+ StringBuilder b = null;
+ string text = null;
+ foreach (ISegment s in Segments) {
+ if (text != null) {
+ if (b == null)
+ b = new StringBuilder(text);
+ else
+ b.Append(text);
+ }
+ text = document.GetText(s);
+ }
+ if (b != null) {
+ if (text != null) b.Append(text);
+ return b.ToString();
+ } else {
+ return text ?? string.Empty;
+ }
+ }
+
+ /// <summary>
+ /// Creates a HTML fragment for the selected text.
+ /// </summary>
+ public string CreateHtmlFragment(HtmlOptions options)
+ {
+ if (options == null)
+ throw new ArgumentNullException("options");
+ IHighlighter highlighter = textArea.GetService(typeof(IHighlighter)) as IHighlighter;
+ StringBuilder html = new StringBuilder();
+ bool first = true;
+ foreach (ISegment selectedSegment in this.Segments) {
+ if (first)
+ first = false;
+ else
+ html.AppendLine("<br>");
+ html.Append(HtmlClipboard.CreateHtmlFragment(textArea.Document, highlighter, selectedSegment, options));
+ }
+ return html.ToString();
+ }
+
+ /// <inheritdoc/>
+ public abstract override bool Equals(object obj);
+
+ /// <inheritdoc/>
+ public abstract override int GetHashCode();
+
+ /// <summary>
+ /// Gets whether the specified offset is included in the selection.
+ /// </summary>
+ /// <returns>True, if the selection contains the offset (selection borders inclusive);
+ /// otherwise, false.</returns>
+ public virtual bool Contains(int offset)
+ {
+ if (this.IsEmpty)
+ return false;
+ if (this.SurroundingSegment.Contains(offset)) {
+ foreach (ISegment s in this.Segments) {
+ if (s.Contains(offset)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Creates a data object containing the selection's text.
+ /// </summary>
+ public virtual DataObject CreateDataObject(TextArea textArea)
+ {
+ string text = GetText();
+ // Ensure we use the appropriate newline sequence for the OS
+ DataObject data = new DataObject(TextUtilities.NormalizeNewLines(text, Environment.NewLine));
+ // we cannot use DataObject.SetText - then we cannot drag to SciTe
+ // (but dragging to Word works in both cases)
+
+ // Also copy text in HTML format to clipboard - good for pasting text into Word
+ // or to the SharpDevelop forums.
+ HtmlClipboard.SetHtml(data, CreateHtmlFragment(new HtmlOptions(textArea.Options)));
+ return data;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionColorizer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionColorizer.cs
new file mode 100644
index 000000000..857c4c060
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionColorizer.cs
@@ -0,0 +1,58 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Windows;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ sealed class SelectionColorizer : ColorizingTransformer
+ {
+ TextArea textArea;
+
+ public SelectionColorizer(TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ this.textArea = textArea;
+ }
+
+ protected override void Colorize(ITextRunConstructionContext context)
+ {
+ // if SelectionForeground is null, keep the existing foreground color
+ if (textArea.SelectionForeground == null)
+ return;
+
+ int lineStartOffset = context.VisualLine.FirstDocumentLine.Offset;
+ int lineEndOffset = context.VisualLine.LastDocumentLine.Offset + context.VisualLine.LastDocumentLine.TotalLength;
+
+ foreach (SelectionSegment segment in textArea.Selection.Segments) {
+ int segmentStart = segment.StartOffset;
+ int segmentEnd = segment.EndOffset;
+ if (segmentEnd <= lineStartOffset)
+ continue;
+ if (segmentStart >= lineEndOffset)
+ continue;
+ int startColumn;
+ if (segmentStart < lineStartOffset)
+ startColumn = 0;
+ else
+ startColumn = context.VisualLine.ValidateVisualColumn(segment.StartOffset, segment.StartVisualColumn, textArea.Selection.EnableVirtualSpace);
+
+ int endColumn;
+ if (segmentEnd > lineEndOffset)
+ endColumn = textArea.Selection.EnableVirtualSpace ? int.MaxValue : context.VisualLine.VisualLengthWithEndOfLineMarker;
+ else
+ endColumn = context.VisualLine.ValidateVisualColumn(segment.EndOffset, segment.EndVisualColumn, textArea.Selection.EnableVirtualSpace);
+
+ ChangeVisualElements(
+ startColumn, endColumn,
+ element => {
+ element.TextRunProperties.SetForegroundBrush(textArea.SelectionForeground);
+ });
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionLayer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionLayer.cs
new file mode 100644
index 000000000..28631affe
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionLayer.cs
@@ -0,0 +1,53 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Windows;
+using System.Windows.Media;
+
+using Tango.Scripting.Editors.Rendering;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ sealed class SelectionLayer : Layer, IWeakEventListener
+ {
+ readonly TextArea textArea;
+
+ public SelectionLayer(TextArea textArea) : base(textArea.TextView, KnownLayer.Selection)
+ {
+ this.IsHitTestVisible = false;
+
+ this.textArea = textArea;
+ TextViewWeakEventManager.VisualLinesChanged.AddListener(textView, this);
+ TextViewWeakEventManager.ScrollOffsetChanged.AddListener(textView, this);
+ }
+
+ bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
+ {
+ if (managerType == typeof(TextViewWeakEventManager.VisualLinesChanged)
+ || managerType == typeof(TextViewWeakEventManager.ScrollOffsetChanged))
+ {
+ InvalidateVisual();
+ return true;
+ }
+ return false;
+ }
+
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ base.OnRender(drawingContext);
+
+ BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
+ geoBuilder.AlignToMiddleOfPixels = true;
+ geoBuilder.ExtendToFullWidthAtLineEnd = textArea.Selection.EnableVirtualSpace;
+ geoBuilder.CornerRadius = textArea.SelectionCornerRadius;
+ foreach (var segment in textArea.Selection.Segments) {
+ geoBuilder.AddSegment(textView, segment);
+ }
+ Geometry geometry = geoBuilder.CreateGeometry();
+ if (geometry != null) {
+ drawingContext.DrawGeometry(textArea.SelectionBrush, textArea.SelectionBorder, geometry);
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionMouseHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionMouseHandler.cs
new file mode 100644
index 000000000..3c5ec51dc
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionMouseHandler.cs
@@ -0,0 +1,631 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media.TextFormatting;
+using System.Windows.Threading;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Handles selection of text using the mouse.
+ /// </summary>
+ sealed class SelectionMouseHandler : ITextAreaInputHandler
+ {
+ #region enum SelectionMode
+ enum SelectionMode
+ {
+ /// <summary>
+ /// no selection (no mouse button down)
+ /// </summary>
+ None,
+ /// <summary>
+ /// left mouse button down on selection, might be normal click
+ /// or might be drag'n'drop
+ /// </summary>
+ PossibleDragStart,
+ /// <summary>
+ /// dragging text
+ /// </summary>
+ Drag,
+ /// <summary>
+ /// normal selection (click+drag)
+ /// </summary>
+ Normal,
+ /// <summary>
+ /// whole-word selection (double click+drag or ctrl+click+drag)
+ /// </summary>
+ WholeWord,
+ /// <summary>
+ /// whole-line selection (triple click+drag)
+ /// </summary>
+ WholeLine,
+ /// <summary>
+ /// rectangular selection (alt+click+drag)
+ /// </summary>
+ Rectangular
+ }
+ #endregion
+
+ readonly TextArea textArea;
+
+ SelectionMode mode;
+ AnchorSegment startWord;
+ Point possibleDragStartMousePos;
+
+ #region Constructor + Attach + Detach
+ public SelectionMouseHandler(TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ this.textArea = textArea;
+ }
+
+ public TextArea TextArea {
+ get { return textArea; }
+ }
+
+ public void Attach()
+ {
+ textArea.MouseLeftButtonDown += textArea_MouseLeftButtonDown;
+ textArea.MouseMove += textArea_MouseMove;
+ textArea.MouseLeftButtonUp += textArea_MouseLeftButtonUp;
+ textArea.QueryCursor += textArea_QueryCursor;
+ textArea.OptionChanged += textArea_OptionChanged;
+
+ enableTextDragDrop = textArea.Options.EnableTextDragDrop;
+ if (enableTextDragDrop) {
+ AttachDragDrop();
+ }
+ }
+
+ public void Detach()
+ {
+ mode = SelectionMode.None;
+ textArea.MouseLeftButtonDown -= textArea_MouseLeftButtonDown;
+ textArea.MouseMove -= textArea_MouseMove;
+ textArea.MouseLeftButtonUp -= textArea_MouseLeftButtonUp;
+ textArea.QueryCursor -= textArea_QueryCursor;
+ textArea.OptionChanged -= textArea_OptionChanged;
+ if (enableTextDragDrop) {
+ DetachDragDrop();
+ }
+ }
+
+ void AttachDragDrop()
+ {
+ textArea.AllowDrop = true;
+ textArea.GiveFeedback += textArea_GiveFeedback;
+ textArea.QueryContinueDrag += textArea_QueryContinueDrag;
+ textArea.DragEnter += textArea_DragEnter;
+ textArea.DragOver += textArea_DragOver;
+ textArea.DragLeave += textArea_DragLeave;
+ textArea.Drop += textArea_Drop;
+ }
+
+ void DetachDragDrop()
+ {
+ textArea.AllowDrop = false;
+ textArea.GiveFeedback -= textArea_GiveFeedback;
+ textArea.QueryContinueDrag -= textArea_QueryContinueDrag;
+ textArea.DragEnter -= textArea_DragEnter;
+ textArea.DragOver -= textArea_DragOver;
+ textArea.DragLeave -= textArea_DragLeave;
+ textArea.Drop -= textArea_Drop;
+ }
+
+ bool enableTextDragDrop;
+
+ void textArea_OptionChanged(object sender, PropertyChangedEventArgs e)
+ {
+ bool newEnableTextDragDrop = textArea.Options.EnableTextDragDrop;
+ if (newEnableTextDragDrop != enableTextDragDrop) {
+ enableTextDragDrop = newEnableTextDragDrop;
+ if (newEnableTextDragDrop)
+ AttachDragDrop();
+ else
+ DetachDragDrop();
+ }
+ }
+ #endregion
+
+ #region Dropping text
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ void textArea_DragEnter(object sender, DragEventArgs e)
+ {
+ try {
+ e.Effects = GetEffect(e);
+ textArea.Caret.Show();
+ } catch (Exception ex) {
+ OnDragException(ex);
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ void textArea_DragOver(object sender, DragEventArgs e)
+ {
+ try {
+ e.Effects = GetEffect(e);
+ } catch (Exception ex) {
+ OnDragException(ex);
+ }
+ }
+
+ DragDropEffects GetEffect(DragEventArgs e)
+ {
+ if (e.Data.GetDataPresent(DataFormats.UnicodeText, true)) {
+ e.Handled = true;
+ int visualColumn;
+ int offset = GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn);
+ if (offset >= 0) {
+ textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn);
+ textArea.Caret.DesiredXPos = double.NaN;
+ if (textArea.ReadOnlySectionProvider.CanInsert(offset)) {
+ if ((e.AllowedEffects & DragDropEffects.Move) == DragDropEffects.Move
+ && (e.KeyStates & DragDropKeyStates.ControlKey) != DragDropKeyStates.ControlKey)
+ {
+ return DragDropEffects.Move;
+ } else {
+ return e.AllowedEffects & DragDropEffects.Copy;
+ }
+ }
+ }
+ }
+ return DragDropEffects.None;
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ void textArea_DragLeave(object sender, DragEventArgs e)
+ {
+ try {
+ e.Handled = true;
+ if (!textArea.IsKeyboardFocusWithin)
+ textArea.Caret.Hide();
+ } catch (Exception ex) {
+ OnDragException(ex);
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ void textArea_Drop(object sender, DragEventArgs e)
+ {
+ try {
+ DragDropEffects effect = GetEffect(e);
+ e.Effects = effect;
+ if (effect != DragDropEffects.None) {
+ string text = e.Data.GetData(DataFormats.UnicodeText, true) as string;
+ if (text != null) {
+ int start = textArea.Caret.Offset;
+ if (mode == SelectionMode.Drag && textArea.Selection.Contains(start)) {
+ Debug.WriteLine("Drop: did not drop: drop target is inside selection");
+ e.Effects = DragDropEffects.None;
+ } else {
+ Debug.WriteLine("Drop: insert at " + start);
+
+ bool rectangular = e.Data.GetDataPresent(RectangleSelection.RectangularSelectionDataType);
+
+ string newLine = TextUtilities.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line);
+ text = TextUtilities.NormalizeNewLines(text, newLine);
+
+ // Mark the undo group with the currentDragDescriptor, if the drag
+ // is originating from the same control. This allows combining
+ // the undo groups when text is moved.
+ textArea.Document.UndoStack.StartUndoGroup(this.currentDragDescriptor);
+ try {
+ if (rectangular && RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, true)) {
+
+ } else {
+ textArea.Document.Insert(start, text);
+ textArea.Selection = Selection.Create(textArea, start, start + text.Length);
+ }
+ } finally {
+ textArea.Document.UndoStack.EndUndoGroup();
+ }
+ }
+ e.Handled = true;
+ }
+ }
+ } catch (Exception ex) {
+ OnDragException(ex);
+ }
+ }
+
+ void OnDragException(Exception ex)
+ {
+ // WPF swallows exceptions during drag'n'drop or reports them incorrectly, so
+ // we re-throw them later to allow the application's unhandled exception handler
+ // to catch them
+ textArea.Dispatcher.BeginInvoke(
+ DispatcherPriority.Normal,
+ new Action(delegate {
+ throw new DragDropException("Exception during drag'n'drop", ex);
+ }));
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ void textArea_GiveFeedback(object sender, GiveFeedbackEventArgs e)
+ {
+ try {
+ e.UseDefaultCursors = true;
+ e.Handled = true;
+ } catch (Exception ex) {
+ OnDragException(ex);
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ void textArea_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
+ {
+ try {
+ if (e.EscapePressed) {
+ e.Action = DragAction.Cancel;
+ } else if ((e.KeyStates & DragDropKeyStates.LeftMouseButton) != DragDropKeyStates.LeftMouseButton) {
+ e.Action = DragAction.Drop;
+ } else {
+ e.Action = DragAction.Continue;
+ }
+ e.Handled = true;
+ } catch (Exception ex) {
+ OnDragException(ex);
+ }
+ }
+ #endregion
+
+ #region Start Drag
+ object currentDragDescriptor;
+
+ void StartDrag()
+ {
+ // prevent nested StartDrag calls
+ mode = SelectionMode.Drag;
+
+ // mouse capture and Drag'n'Drop doesn't mix
+ textArea.ReleaseMouseCapture();
+
+ DataObject dataObject = textArea.Selection.CreateDataObject(textArea);
+
+ DragDropEffects allowedEffects = DragDropEffects.All;
+ var deleteOnMove = textArea.Selection.Segments.Select(s => new AnchorSegment(textArea.Document, s)).ToList();
+ foreach (ISegment s in deleteOnMove) {
+ ISegment[] result = textArea.GetDeletableSegments(s);
+ if (result.Length != 1 || result[0].Offset != s.Offset || result[0].EndOffset != s.EndOffset) {
+ allowedEffects &= ~DragDropEffects.Move;
+ }
+ }
+
+ object dragDescriptor = new object();
+ this.currentDragDescriptor = dragDescriptor;
+
+ DragDropEffects resultEffect;
+ using (textArea.AllowCaretOutsideSelection()) {
+ var oldCaretPosition = textArea.Caret.Position;
+ try {
+ Debug.WriteLine("DoDragDrop with allowedEffects=" + allowedEffects);
+ resultEffect = DragDrop.DoDragDrop(textArea, dataObject, allowedEffects);
+ Debug.WriteLine("DoDragDrop done, resultEffect=" + resultEffect);
+ } catch (COMException ex) {
+ // ignore COM errors - don't crash on badly implemented drop targets
+ Debug.WriteLine("DoDragDrop failed: " + ex.ToString());
+ return;
+ }
+ if (resultEffect == DragDropEffects.None) {
+ // reset caret if drag was aborted
+ textArea.Caret.Position = oldCaretPosition;
+ }
+ }
+
+ this.currentDragDescriptor = null;
+
+ if (deleteOnMove != null && resultEffect == DragDropEffects.Move && (allowedEffects & DragDropEffects.Move) == DragDropEffects.Move) {
+ bool draggedInsideSingleDocument = (dragDescriptor == textArea.Document.UndoStack.LastGroupDescriptor);
+ if (draggedInsideSingleDocument)
+ textArea.Document.UndoStack.StartContinuedUndoGroup(null);
+ textArea.Document.BeginUpdate();
+ try {
+ foreach (ISegment s in deleteOnMove) {
+ textArea.Document.Remove(s.Offset, s.Length);
+ }
+ } finally {
+ textArea.Document.EndUpdate();
+ if (draggedInsideSingleDocument)
+ textArea.Document.UndoStack.EndUndoGroup();
+ }
+ }
+ }
+ #endregion
+
+ #region QueryCursor
+ // provide the IBeam Cursor for the text area
+ void textArea_QueryCursor(object sender, QueryCursorEventArgs e)
+ {
+ if (!e.Handled) {
+ if (mode != SelectionMode.None || !enableTextDragDrop) {
+ e.Cursor = Cursors.IBeam;
+ e.Handled = true;
+ } else if (textArea.TextView.VisualLinesValid) {
+ // Only query the cursor if the visual lines are valid.
+ // If they are invalid, the cursor will get re-queried when the visual lines
+ // get refreshed.
+ Point p = e.GetPosition(textArea.TextView);
+ if (p.X >= 0 && p.Y >= 0 && p.X <= textArea.TextView.ActualWidth && p.Y <= textArea.TextView.ActualHeight) {
+ int visualColumn;
+ int offset = GetOffsetFromMousePosition(e, out visualColumn);
+ if (textArea.Selection.Contains(offset))
+ e.Cursor = Cursors.Arrow;
+ else
+ e.Cursor = Cursors.IBeam;
+ e.Handled = true;
+ }
+ }
+ }
+ }
+ #endregion
+
+ #region LeftButtonDown
+ void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ mode = SelectionMode.None;
+ if (!e.Handled && e.ChangedButton == MouseButton.Left) {
+ ModifierKeys modifiers = Keyboard.Modifiers;
+ bool shift = (modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
+ if (enableTextDragDrop && e.ClickCount == 1 && !shift) {
+ int visualColumn;
+ int offset = GetOffsetFromMousePosition(e, out visualColumn);
+ if (textArea.Selection.Contains(offset)) {
+ if (textArea.CaptureMouse()) {
+ mode = SelectionMode.PossibleDragStart;
+ possibleDragStartMousePos = e.GetPosition(textArea);
+ }
+ e.Handled = true;
+ return;
+ }
+ }
+
+ var oldPosition = textArea.Caret.Position;
+ SetCaretOffsetToMousePosition(e);
+
+
+ if (!shift) {
+ textArea.ClearSelection();
+ }
+ if (textArea.CaptureMouse()) {
+ if ((modifiers & ModifierKeys.Alt) == ModifierKeys.Alt && textArea.Options.EnableRectangularSelection) {
+ mode = SelectionMode.Rectangular;
+ if (shift && textArea.Selection is RectangleSelection) {
+ textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
+ }
+ } else if (e.ClickCount == 1 && ((modifiers & ModifierKeys.Control) == 0)) {
+ mode = SelectionMode.Normal;
+ if (shift && !(textArea.Selection is RectangleSelection)) {
+ textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
+ }
+ } else {
+ SimpleSegment startWord;
+ if (e.ClickCount == 3) {
+ mode = SelectionMode.WholeLine;
+ startWord = GetLineAtMousePosition(e);
+ } else {
+ mode = SelectionMode.WholeWord;
+ startWord = GetWordAtMousePosition(e);
+ }
+ if (startWord == SimpleSegment.Invalid) {
+ mode = SelectionMode.None;
+ textArea.ReleaseMouseCapture();
+ return;
+ }
+ if (shift && !textArea.Selection.IsEmpty) {
+ if (startWord.Offset < textArea.Selection.SurroundingSegment.Offset) {
+ textArea.Selection = textArea.Selection.SetEndpoint(new TextViewPosition(textArea.Document.GetLocation(startWord.Offset)));
+ } else if (startWord.EndOffset > textArea.Selection.SurroundingSegment.EndOffset) {
+ textArea.Selection = textArea.Selection.SetEndpoint(new TextViewPosition(textArea.Document.GetLocation(startWord.EndOffset)));
+ }
+ this.startWord = new AnchorSegment(textArea.Document, textArea.Selection.SurroundingSegment);
+ } else {
+ textArea.Selection = Selection.Create(textArea, startWord.Offset, startWord.EndOffset);
+ this.startWord = new AnchorSegment(textArea.Document, startWord.Offset, startWord.Length);
+ }
+ }
+ }
+ }
+ e.Handled = true;
+ }
+ #endregion
+
+ #region Mouse Position <-> Text coordinates
+ SimpleSegment GetWordAtMousePosition(MouseEventArgs e)
+ {
+ TextView textView = textArea.TextView;
+ if (textView == null) return SimpleSegment.Invalid;
+ Point pos = e.GetPosition(textView);
+ if (pos.Y < 0)
+ pos.Y = 0;
+ if (pos.Y > textView.ActualHeight)
+ pos.Y = textView.ActualHeight;
+ pos += textView.ScrollOffset;
+ VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
+ if (line != null) {
+ int visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace);
+ int wordStartVC = line.GetNextCaretPosition(visualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.WordStartOrSymbol, textArea.Selection.EnableVirtualSpace);
+ if (wordStartVC == -1)
+ wordStartVC = 0;
+ int wordEndVC = line.GetNextCaretPosition(wordStartVC, LogicalDirection.Forward, CaretPositioningMode.WordBorderOrSymbol, textArea.Selection.EnableVirtualSpace);
+ if (wordEndVC == -1)
+ wordEndVC = line.VisualLength;
+ int relOffset = line.FirstDocumentLine.Offset;
+ int wordStartOffset = line.GetRelativeOffset(wordStartVC) + relOffset;
+ int wordEndOffset = line.GetRelativeOffset(wordEndVC) + relOffset;
+ return new SimpleSegment(wordStartOffset, wordEndOffset - wordStartOffset);
+ } else {
+ return SimpleSegment.Invalid;
+ }
+ }
+
+ SimpleSegment GetLineAtMousePosition(MouseEventArgs e)
+ {
+ TextView textView = textArea.TextView;
+ if (textView == null) return SimpleSegment.Invalid;
+ Point pos = e.GetPosition(textView);
+ if (pos.Y < 0)
+ pos.Y = 0;
+ if (pos.Y > textView.ActualHeight)
+ pos.Y = textView.ActualHeight;
+ pos += textView.ScrollOffset;
+ VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
+ if (line != null) {
+ return new SimpleSegment(line.StartOffset, line.LastDocumentLine.EndOffset - line.StartOffset);
+ } else {
+ return SimpleSegment.Invalid;
+ }
+ }
+
+ int GetOffsetFromMousePosition(MouseEventArgs e, out int visualColumn)
+ {
+ return GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn);
+ }
+
+ int GetOffsetFromMousePosition(Point positionRelativeToTextView, out int visualColumn)
+ {
+ visualColumn = 0;
+ TextView textView = textArea.TextView;
+ Point pos = positionRelativeToTextView;
+ if (pos.Y < 0)
+ pos.Y = 0;
+ if (pos.Y > textView.ActualHeight)
+ pos.Y = textView.ActualHeight;
+ pos += textView.ScrollOffset;
+ if (pos.Y > textView.DocumentHeight)
+ pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon;
+ VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
+ if (line != null) {
+ visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace);
+ return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
+ }
+ return -1;
+ }
+
+ int GetOffsetFromMousePositionFirstTextLineOnly(Point positionRelativeToTextView, out int visualColumn)
+ {
+ visualColumn = 0;
+ TextView textView = textArea.TextView;
+ Point pos = positionRelativeToTextView;
+ if (pos.Y < 0)
+ pos.Y = 0;
+ if (pos.Y > textView.ActualHeight)
+ pos.Y = textView.ActualHeight;
+ pos += textView.ScrollOffset;
+ if (pos.Y > textView.DocumentHeight)
+ pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon;
+ VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
+ if (line != null) {
+ visualColumn = line.GetVisualColumn(line.TextLines.First(), pos.X, textArea.Selection.EnableVirtualSpace);
+ return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
+ }
+ return -1;
+ }
+ #endregion
+
+ #region MouseMove
+ void textArea_MouseMove(object sender, MouseEventArgs e)
+ {
+ if (e.Handled)
+ return;
+ if (mode == SelectionMode.Normal || mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine || mode == SelectionMode.Rectangular) {
+ e.Handled = true;
+ if (textArea.TextView.VisualLinesValid) {
+ // If the visual lines are not valid, don't extend the selection.
+ // Extending the selection forces a VisualLine refresh, and it is sufficient
+ // to do that on MouseUp, we don't have to do it every MouseMove.
+ ExtendSelectionToMouse(e);
+ }
+ } else if (mode == SelectionMode.PossibleDragStart) {
+ e.Handled = true;
+ Vector mouseMovement = e.GetPosition(textArea) - possibleDragStartMousePos;
+ if (Math.Abs(mouseMovement.X) > SystemParameters.MinimumHorizontalDragDistance
+ || Math.Abs(mouseMovement.Y) > SystemParameters.MinimumVerticalDragDistance)
+ {
+ StartDrag();
+ }
+ }
+ }
+ #endregion
+
+ #region ExtendSelection
+ void SetCaretOffsetToMousePosition(MouseEventArgs e)
+ {
+ SetCaretOffsetToMousePosition(e, null);
+ }
+
+ void SetCaretOffsetToMousePosition(MouseEventArgs e, ISegment allowedSegment)
+ {
+ int visualColumn;
+ int offset;
+ if (mode == SelectionMode.Rectangular)
+ offset = GetOffsetFromMousePositionFirstTextLineOnly(e.GetPosition(textArea.TextView), out visualColumn);
+ else
+ offset = GetOffsetFromMousePosition(e, out visualColumn);
+ if (allowedSegment != null) {
+ offset = offset.CoerceValue(allowedSegment.Offset, allowedSegment.EndOffset);
+ }
+ if (offset >= 0) {
+ textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn);
+ textArea.Caret.DesiredXPos = double.NaN;
+ }
+ }
+
+ void ExtendSelectionToMouse(MouseEventArgs e)
+ {
+ TextViewPosition oldPosition = textArea.Caret.Position;
+ if (mode == SelectionMode.Normal || mode == SelectionMode.Rectangular) {
+ SetCaretOffsetToMousePosition(e);
+ if (mode == SelectionMode.Normal && textArea.Selection is RectangleSelection)
+ textArea.Selection = new SimpleSelection(textArea, oldPosition, textArea.Caret.Position);
+ else if (mode == SelectionMode.Rectangular && !(textArea.Selection is RectangleSelection))
+ textArea.Selection = new RectangleSelection(textArea, oldPosition, textArea.Caret.Position);
+ else
+ textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
+ } else if (mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine) {
+ var newWord = (mode == SelectionMode.WholeLine) ? GetLineAtMousePosition(e) : GetWordAtMousePosition(e);
+ if (newWord != SimpleSegment.Invalid) {
+ textArea.Selection = Selection.Create(textArea,
+ Math.Min(newWord.Offset, startWord.Offset),
+ Math.Max(newWord.EndOffset, startWord.EndOffset));
+ // Set caret offset, but limit the caret to stay inside the selection.
+ // in whole-word selection, it's otherwise possible that we get the caret outside the
+ // selection - but the TextArea doesn't like that and will reset the selection, causing
+ // flickering.
+ SetCaretOffsetToMousePosition(e, textArea.Selection.SurroundingSegment);
+ }
+ }
+ textArea.Caret.BringCaretToView(5.0);
+ }
+ #endregion
+
+ #region MouseLeftButtonUp
+ void textArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+ {
+ if (mode == SelectionMode.None || e.Handled)
+ return;
+ e.Handled = true;
+ if (mode == SelectionMode.PossibleDragStart) {
+ // -> this was not a drag start (mouse didn't move after mousedown)
+ SetCaretOffsetToMousePosition(e);
+ textArea.ClearSelection();
+ } else if (mode == SelectionMode.Normal || mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine || mode == SelectionMode.Rectangular) {
+ ExtendSelectionToMouse(e);
+ }
+ mode = SelectionMode.None;
+ textArea.ReleaseMouseCapture();
+ }
+ #endregion
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionSegment.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionSegment.cs
new file mode 100644
index 000000000..46c560b34
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionSegment.cs
@@ -0,0 +1,89 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Represents a selected segment.
+ /// </summary>
+ public class SelectionSegment : ISegment
+ {
+ readonly int startOffset, endOffset;
+ readonly int startVC, endVC;
+
+ /// <summary>
+ /// Creates a SelectionSegment from two offsets.
+ /// </summary>
+ public SelectionSegment(int startOffset, int endOffset)
+ {
+ this.startOffset = Math.Min(startOffset, endOffset);
+ this.endOffset = Math.Max(startOffset, endOffset);
+ this.startVC = this.endVC = -1;
+ }
+
+ /// <summary>
+ /// Creates a SelectionSegment from two offsets and visual columns.
+ /// </summary>
+ public SelectionSegment(int startOffset, int startVC, int endOffset, int endVC)
+ {
+ if (startOffset < endOffset || (startOffset == endOffset && startVC <= endVC)) {
+ this.startOffset = startOffset;
+ this.startVC = startVC;
+ this.endOffset = endOffset;
+ this.endVC = endVC;
+ } else {
+ this.startOffset = endOffset;
+ this.startVC = endVC;
+ this.endOffset = startOffset;
+ this.endVC = startVC;
+ }
+ }
+
+ /// <summary>
+ /// Gets the start offset.
+ /// </summary>
+ public int StartOffset {
+ get { return startOffset; }
+ }
+
+ /// <summary>
+ /// Gets the end offset.
+ /// </summary>
+ public int EndOffset {
+ get { return endOffset; }
+ }
+
+ /// <summary>
+ /// Gets the start visual column.
+ /// </summary>
+ public int StartVisualColumn {
+ get { return startVC; }
+ }
+
+ /// <summary>
+ /// Gets the end visual column.
+ /// </summary>
+ public int EndVisualColumn {
+ get { return endVC; }
+ }
+
+ /// <inheritdoc/>
+ int ISegment.Offset {
+ get { return startOffset; }
+ }
+
+ /// <inheritdoc/>
+ public int Length {
+ get { return endOffset - startOffset; }
+ }
+
+ /// <inheritdoc/>
+ public override string ToString()
+ {
+ return string.Format("[SelectionSegment StartOffset={0}, EndOffset={1}, StartVC={2}, EndVC={3}]", startOffset, endOffset, startVC, endVC);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SimpleSelection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SimpleSelection.cs
new file mode 100644
index 000000000..132b09e20
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SimpleSelection.cs
@@ -0,0 +1,144 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// A simple selection.
+ /// </summary>
+ sealed class SimpleSelection : Selection
+ {
+ readonly TextViewPosition start, end;
+ readonly int startOffset, endOffset;
+
+ /// <summary>
+ /// Creates a new SimpleSelection instance.
+ /// </summary>
+ internal SimpleSelection(TextArea textArea, TextViewPosition start, TextViewPosition end)
+ : base(textArea)
+ {
+ this.start = start;
+ this.end = end;
+ this.startOffset = textArea.Document.GetOffset(start.Location);
+ this.endOffset = textArea.Document.GetOffset(end.Location);
+ }
+
+ /// <inheritdoc/>
+ public override IEnumerable<SelectionSegment> Segments {
+ get {
+ return ExtensionMethods.Sequence<SelectionSegment>(new SelectionSegment(startOffset, start.VisualColumn, endOffset, end.VisualColumn));
+ }
+ }
+
+ /// <inheritdoc/>
+ public override ISegment SurroundingSegment {
+ get {
+ return new SelectionSegment(startOffset, endOffset);
+ }
+ }
+
+ /// <inheritdoc/>
+ public override void ReplaceSelectionWithText(string newText)
+ {
+ if (newText == null)
+ throw new ArgumentNullException("newText");
+ using (textArea.Document.RunUpdate()) {
+ ISegment[] segmentsToDelete = textArea.GetDeletableSegments(this.SurroundingSegment);
+ for (int i = segmentsToDelete.Length - 1; i >= 0; i--) {
+ if (i == segmentsToDelete.Length - 1) {
+ if (segmentsToDelete[i].Offset == SurroundingSegment.Offset && segmentsToDelete[i].Length == SurroundingSegment.Length) {
+ newText = AddSpacesIfRequired(newText, start, end);
+ }
+ int vc = textArea.Caret.VisualColumn;
+ textArea.Caret.Offset = segmentsToDelete[i].EndOffset;
+ if (string.IsNullOrEmpty(newText))
+ textArea.Caret.VisualColumn = vc;
+ textArea.Document.Replace(segmentsToDelete[i], newText);
+ } else {
+ textArea.Document.Remove(segmentsToDelete[i]);
+ }
+ }
+ if (segmentsToDelete.Length != 0) {
+ textArea.ClearSelection();
+ }
+ }
+ }
+
+ public override TextViewPosition StartPosition {
+ get { return start; }
+ }
+
+ public override TextViewPosition EndPosition {
+ get { return end; }
+ }
+
+ /// <inheritdoc/>
+ public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e)
+ {
+ if (e == null)
+ throw new ArgumentNullException("e");
+ return Selection.Create(
+ textArea,
+ new TextViewPosition(textArea.Document.GetLocation(e.GetNewOffset(startOffset, AnchorMovementType.Default)), start.VisualColumn),
+ new TextViewPosition(textArea.Document.GetLocation(e.GetNewOffset(endOffset, AnchorMovementType.Default)), end.VisualColumn)
+ );
+ }
+
+ /// <inheritdoc/>
+ public override bool IsEmpty {
+ get { return startOffset == endOffset; }
+ }
+
+ /// <inheritdoc/>
+ public override int Length {
+ get {
+ return Math.Abs(endOffset - startOffset);
+ }
+ }
+
+ /// <inheritdoc/>
+ public override Selection SetEndpoint(TextViewPosition endPosition)
+ {
+ return Create(textArea, start, endPosition);
+ }
+
+ public override Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition)
+ {
+ var document = textArea.Document;
+ if (document == null)
+ throw ThrowUtil.NoDocumentAssigned();
+ return Create(textArea, start, endPosition);
+ }
+
+ /// <inheritdoc/>
+ public override int GetHashCode()
+ {
+ unchecked {
+ return startOffset * 27811 + endOffset + textArea.GetHashCode();
+ }
+ }
+
+ /// <inheritdoc/>
+ public override bool Equals(object obj)
+ {
+ SimpleSelection other = obj as SimpleSelection;
+ if (other == null) return false;
+ return this.start.Equals(other.start) && this.end.Equals(other.end)
+ && this.startOffset == other.startOffset && this.endOffset == other.endOffset
+ && this.textArea == other.textArea;
+ }
+
+ /// <inheritdoc/>
+ public override string ToString()
+ {
+ return "[SimpleSelection Start=" + start + " End=" + end + "]";
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextArea.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextArea.cs
new file mode 100644
index 000000000..393a3e29c
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextArea.cs
@@ -0,0 +1,1048 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Shapes;
+using System.Windows.Threading;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Indentation;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Control that wraps a TextView and adds support for user input and the caret.
+ /// </summary>
+ public class TextArea : Control, IScrollInfo, IWeakEventListener, ITextEditorComponent, IServiceProvider
+ {
+ internal readonly ImeSupport ime;
+
+ #region Constructor
+ static TextArea()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(TextArea),
+ new FrameworkPropertyMetadata(typeof(TextArea)));
+ KeyboardNavigation.IsTabStopProperty.OverrideMetadata(
+ typeof(TextArea), new FrameworkPropertyMetadata(Boxes.True));
+ KeyboardNavigation.TabNavigationProperty.OverrideMetadata(
+ typeof(TextArea), new FrameworkPropertyMetadata(KeyboardNavigationMode.None));
+ FocusableProperty.OverrideMetadata(
+ typeof(TextArea), new FrameworkPropertyMetadata(Boxes.True));
+ }
+
+ /// <summary>
+ /// Creates a new TextArea instance.
+ /// </summary>
+ public TextArea() : this(new TextView())
+ {
+ }
+
+ /// <summary>
+ /// Creates a new TextArea instance.
+ /// </summary>
+ protected TextArea(TextView textView)
+ {
+ if (textView == null)
+ throw new ArgumentNullException("textView");
+ this.textView = textView;
+ this.Options = textView.Options;
+
+ selection = emptySelection = new EmptySelection(this);
+
+ textView.Services.AddService(typeof(TextArea), this);
+
+ textView.LineTransformers.Add(new SelectionColorizer(this));
+ textView.InsertLayer(new SelectionLayer(this), KnownLayer.Selection, LayerInsertionPosition.Replace);
+
+ caret = new Caret(this);
+ caret.PositionChanged += (sender, e) => RequestSelectionValidation();
+ ime = new ImeSupport(this);
+
+ leftMargins.CollectionChanged += leftMargins_CollectionChanged;
+
+ this.DefaultInputHandler = new TextAreaDefaultInputHandler(this);
+ this.ActiveInputHandler = this.DefaultInputHandler;
+ }
+ #endregion
+
+ #region InputHandler management
+ /// <summary>
+ /// Gets the default input handler.
+ /// </summary>
+ /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
+ public TextAreaDefaultInputHandler DefaultInputHandler { get; private set; }
+
+ ITextAreaInputHandler activeInputHandler;
+ bool isChangingInputHandler;
+
+ /// <summary>
+ /// Gets/Sets the active input handler.
+ /// This property does not return currently active stacked input handlers. Setting this property detached all stacked input handlers.
+ /// </summary>
+ /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
+ public ITextAreaInputHandler ActiveInputHandler {
+ get { return activeInputHandler; }
+ set {
+ if (value != null && value.TextArea != this)
+ throw new ArgumentException("The input handler was created for a different text area than this one.");
+ if (isChangingInputHandler)
+ throw new InvalidOperationException("Cannot set ActiveInputHandler recursively");
+ if (activeInputHandler != value) {
+ isChangingInputHandler = true;
+ try {
+ // pop the whole stack
+ PopStackedInputHandler(stackedInputHandlers.LastOrDefault());
+ Debug.Assert(stackedInputHandlers.IsEmpty);
+
+ if (activeInputHandler != null)
+ activeInputHandler.Detach();
+ activeInputHandler = value;
+ if (value != null)
+ value.Attach();
+ } finally {
+ isChangingInputHandler = false;
+ }
+ if (ActiveInputHandlerChanged != null)
+ ActiveInputHandlerChanged(this, EventArgs.Empty);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Occurs when the ActiveInputHandler property changes.
+ /// </summary>
+ public event EventHandler ActiveInputHandlerChanged;
+
+ ImmutableStack<TextAreaStackedInputHandler> stackedInputHandlers = ImmutableStack<TextAreaStackedInputHandler>.Empty;
+
+ /// <summary>
+ /// Gets the list of currently active stacked input handlers.
+ /// </summary>
+ /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
+ public ImmutableStack<TextAreaStackedInputHandler> StackedInputHandlers {
+ get { return stackedInputHandlers; }
+ }
+
+ /// <summary>
+ /// Pushes an input handler onto the list of stacked input handlers.
+ /// </summary>
+ /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
+ public void PushStackedInputHandler(TextAreaStackedInputHandler inputHandler)
+ {
+ if (inputHandler == null)
+ throw new ArgumentNullException("inputHandler");
+ stackedInputHandlers = stackedInputHandlers.Push(inputHandler);
+ inputHandler.Attach();
+ }
+
+ /// <summary>
+ /// Pops the stacked input handler (and all input handlers above it).
+ /// If <paramref name="inputHandler"/> is not found in the currently stacked input handlers, or is null, this method
+ /// does nothing.
+ /// </summary>
+ /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
+ public void PopStackedInputHandler(TextAreaStackedInputHandler inputHandler)
+ {
+ if (stackedInputHandlers.Any(i => i == inputHandler)) {
+ ITextAreaInputHandler oldHandler;
+ do {
+ oldHandler = stackedInputHandlers.Peek();
+ stackedInputHandlers = stackedInputHandlers.Pop();
+ oldHandler.Detach();
+ } while (oldHandler != inputHandler);
+ }
+ }
+ #endregion
+
+ #region Document property
+ /// <summary>
+ /// Document property.
+ /// </summary>
+ public static readonly DependencyProperty DocumentProperty
+ = TextView.DocumentProperty.AddOwner(typeof(TextArea), new FrameworkPropertyMetadata(OnDocumentChanged));
+
+ /// <summary>
+ /// Gets/Sets the document displayed by the text editor.
+ /// </summary>
+ public TextDocument Document {
+ get { return (TextDocument)GetValue(DocumentProperty); }
+ set { SetValue(DocumentProperty, value); }
+ }
+
+ /// <inheritdoc/>
+ public event EventHandler DocumentChanged;
+
+ static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
+ {
+ ((TextArea)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue);
+ }
+
+ void OnDocumentChanged(TextDocument oldValue, TextDocument newValue)
+ {
+ if (oldValue != null) {
+ TextDocumentWeakEventManager.Changing.RemoveListener(oldValue, this);
+ TextDocumentWeakEventManager.Changed.RemoveListener(oldValue, this);
+ TextDocumentWeakEventManager.UpdateStarted.RemoveListener(oldValue, this);
+ TextDocumentWeakEventManager.UpdateFinished.RemoveListener(oldValue, this);
+ }
+ textView.Document = newValue;
+ if (newValue != null) {
+ TextDocumentWeakEventManager.Changing.AddListener(newValue, this);
+ TextDocumentWeakEventManager.Changed.AddListener(newValue, this);
+ TextDocumentWeakEventManager.UpdateStarted.AddListener(newValue, this);
+ TextDocumentWeakEventManager.UpdateFinished.AddListener(newValue, this);
+ }
+ // Reset caret location and selection: this is necessary because the caret/selection might be invalid
+ // in the new document (e.g. if new document is shorter than the old document).
+ caret.Location = new TextLocation(1, 1);
+ this.ClearSelection();
+ if (DocumentChanged != null)
+ DocumentChanged(this, EventArgs.Empty);
+ CommandManager.InvalidateRequerySuggested();
+ }
+ #endregion
+
+ #region Options property
+ /// <summary>
+ /// Options property.
+ /// </summary>
+ public static readonly DependencyProperty OptionsProperty
+ = TextView.OptionsProperty.AddOwner(typeof(TextArea), new FrameworkPropertyMetadata(OnOptionsChanged));
+
+ /// <summary>
+ /// Gets/Sets the document displayed by the text editor.
+ /// </summary>
+ public TextEditorOptions Options {
+ get { return (TextEditorOptions)GetValue(OptionsProperty); }
+ set { SetValue(OptionsProperty, value); }
+ }
+
+ /// <summary>
+ /// Occurs when a text editor option has changed.
+ /// </summary>
+ public event PropertyChangedEventHandler OptionChanged;
+
+ /// <summary>
+ /// Raises the <see cref="OptionChanged"/> event.
+ /// </summary>
+ protected virtual void OnOptionChanged(PropertyChangedEventArgs e)
+ {
+ if (OptionChanged != null) {
+ OptionChanged(this, e);
+ }
+ }
+
+ static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
+ {
+ ((TextArea)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue);
+ }
+
+ void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue)
+ {
+ if (oldValue != null) {
+ PropertyChangedWeakEventManager.RemoveListener(oldValue, this);
+ }
+ textView.Options = newValue;
+ if (newValue != null) {
+ PropertyChangedWeakEventManager.AddListener(newValue, this);
+ }
+ OnOptionChanged(new PropertyChangedEventArgs(null));
+ }
+ #endregion
+
+ #region ReceiveWeakEvent
+ /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
+ protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
+ {
+ if (managerType == typeof(TextDocumentWeakEventManager.Changing)) {
+ OnDocumentChanging();
+ return true;
+ } else if (managerType == typeof(TextDocumentWeakEventManager.Changed)) {
+ OnDocumentChanged((DocumentChangeEventArgs)e);
+ return true;
+ } else if (managerType == typeof(TextDocumentWeakEventManager.UpdateStarted)) {
+ OnUpdateStarted();
+ return true;
+ } else if (managerType == typeof(TextDocumentWeakEventManager.UpdateFinished)) {
+ OnUpdateFinished();
+ return true;
+ } else if (managerType == typeof(PropertyChangedWeakEventManager)) {
+ OnOptionChanged((PropertyChangedEventArgs)e);
+ return true;
+ }
+ return false;
+ }
+
+ bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
+ {
+ return ReceiveWeakEvent(managerType, sender, e);
+ }
+ #endregion
+
+ #region Caret handling on document changes
+ void OnDocumentChanging()
+ {
+ caret.OnDocumentChanging();
+ }
+
+ void OnDocumentChanged(DocumentChangeEventArgs e)
+ {
+ caret.OnDocumentChanged(e);
+ this.Selection = selection.UpdateOnDocumentChange(e);
+ }
+
+ void OnUpdateStarted()
+ {
+ Document.UndoStack.PushOptional(new RestoreCaretAndSelectionUndoAction(this));
+ }
+
+ void OnUpdateFinished()
+ {
+ caret.OnDocumentUpdateFinished();
+ }
+
+ sealed class RestoreCaretAndSelectionUndoAction : IUndoableOperation
+ {
+ // keep textarea in weak reference because the IUndoableOperation is stored with the document
+ WeakReference textAreaReference;
+ TextViewPosition caretPosition;
+ Selection selection;
+
+ public RestoreCaretAndSelectionUndoAction(TextArea textArea)
+ {
+ this.textAreaReference = new WeakReference(textArea);
+ // Just save the old caret position, no need to validate here.
+ // If we restore it, we'll validate it anyways.
+ this.caretPosition = textArea.Caret.NonValidatedPosition;
+ this.selection = textArea.Selection;
+ }
+
+ public void Undo()
+ {
+ TextArea textArea = (TextArea)textAreaReference.Target;
+ if (textArea != null) {
+ textArea.Caret.Position = caretPosition;
+ textArea.Selection = selection;
+ }
+ }
+
+ public void Redo()
+ {
+ // redo=undo: we just restore the caret/selection state
+ Undo();
+ }
+ }
+ #endregion
+
+ #region TextView property
+ readonly TextView textView;
+ IScrollInfo scrollInfo;
+
+ /// <summary>
+ /// Gets the text view used to display text in this text area.
+ /// </summary>
+ public TextView TextView {
+ get {
+ return textView;
+ }
+ }
+ /// <inheritdoc/>
+ public override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+ scrollInfo = textView;
+ ApplyScrollInfo();
+ }
+ #endregion
+
+ #region Selection property
+ internal readonly Selection emptySelection;
+ Selection selection;
+
+ /// <summary>
+ /// Occurs when the selection has changed.
+ /// </summary>
+ public event EventHandler SelectionChanged;
+
+ /// <summary>
+ /// Gets/Sets the selection in this text area.
+ /// </summary>
+
+ public Selection Selection {
+ get { return selection; }
+ set {
+ if (value == null)
+ throw new ArgumentNullException("value");
+ if (value.textArea != this)
+ throw new ArgumentException("Cannot use a Selection instance that belongs to another text area.");
+ if (!object.Equals(selection, value)) {
+// Debug.WriteLine("Selection change from " + selection + " to " + value);
+ if (textView != null) {
+ ISegment oldSegment = selection.SurroundingSegment;
+ ISegment newSegment = value.SurroundingSegment;
+ if (!Selection.EnableVirtualSpace && (selection is SimpleSelection && value is SimpleSelection && oldSegment != null && newSegment != null)) {
+ // perf optimization:
+ // When a simple selection changes, don't redraw the whole selection, but only the changed parts.
+ int oldSegmentOffset = oldSegment.Offset;
+ int newSegmentOffset = newSegment.Offset;
+ if (oldSegmentOffset != newSegmentOffset) {
+ textView.Redraw(Math.Min(oldSegmentOffset, newSegmentOffset),
+ Math.Abs(oldSegmentOffset - newSegmentOffset),
+ DispatcherPriority.Background);
+ }
+ int oldSegmentEndOffset = oldSegment.EndOffset;
+ int newSegmentEndOffset = newSegment.EndOffset;
+ if (oldSegmentEndOffset != newSegmentEndOffset) {
+ textView.Redraw(Math.Min(oldSegmentEndOffset, newSegmentEndOffset),
+ Math.Abs(oldSegmentEndOffset - newSegmentEndOffset),
+ DispatcherPriority.Background);
+ }
+ } else {
+ textView.Redraw(oldSegment, DispatcherPriority.Background);
+ textView.Redraw(newSegment, DispatcherPriority.Background);
+ }
+ }
+ selection = value;
+ if (SelectionChanged != null)
+ SelectionChanged(this, EventArgs.Empty);
+ // a selection change causes commands like copy/paste/etc. to change status
+ CommandManager.InvalidateRequerySuggested();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Clears the current selection.
+ /// </summary>
+ public void ClearSelection()
+ {
+ this.Selection = emptySelection;
+ }
+
+ /// <summary>
+ /// The <see cref="SelectionBrush"/> property.
+ /// </summary>
+ public static readonly DependencyProperty SelectionBrushProperty =
+ DependencyProperty.Register("SelectionBrush", typeof(Brush), typeof(TextArea));
+
+ /// <summary>
+ /// Gets/Sets the background brush used for the selection.
+ /// </summary>
+ public Brush SelectionBrush {
+ get { return (Brush)GetValue(SelectionBrushProperty); }
+ set { SetValue(SelectionBrushProperty, value); }
+ }
+
+ /// <summary>
+ /// The <see cref="SelectionForeground"/> property.
+ /// </summary>
+ public static readonly DependencyProperty SelectionForegroundProperty =
+ DependencyProperty.Register("SelectionForeground", typeof(Brush), typeof(TextArea));
+
+ /// <summary>
+ /// Gets/Sets the foreground brush used selected text.
+ /// </summary>
+ public Brush SelectionForeground {
+ get { return (Brush)GetValue(SelectionForegroundProperty); }
+ set { SetValue(SelectionForegroundProperty, value); }
+ }
+
+ /// <summary>
+ /// The <see cref="SelectionBorder"/> property.
+ /// </summary>
+ public static readonly DependencyProperty SelectionBorderProperty =
+ DependencyProperty.Register("SelectionBorder", typeof(Pen), typeof(TextArea));
+
+ /// <summary>
+ /// Gets/Sets the background brush used for the selection.
+ /// </summary>
+ public Pen SelectionBorder {
+ get { return (Pen)GetValue(SelectionBorderProperty); }
+ set { SetValue(SelectionBorderProperty, value); }
+ }
+
+ /// <summary>
+ /// The <see cref="SelectionCornerRadius"/> property.
+ /// </summary>
+ public static readonly DependencyProperty SelectionCornerRadiusProperty =
+ DependencyProperty.Register("SelectionCornerRadius", typeof(double), typeof(TextArea),
+ new FrameworkPropertyMetadata(3.0));
+
+ /// <summary>
+ /// Gets/Sets the corner radius of the selection.
+ /// </summary>
+ public double SelectionCornerRadius {
+ get { return (double)GetValue(SelectionCornerRadiusProperty); }
+ set { SetValue(SelectionCornerRadiusProperty, value); }
+ }
+ #endregion
+
+ #region Force caret to stay inside selection
+ bool ensureSelectionValidRequested;
+ int allowCaretOutsideSelection;
+
+ void RequestSelectionValidation()
+ {
+ if (!ensureSelectionValidRequested && allowCaretOutsideSelection == 0) {
+ ensureSelectionValidRequested = true;
+ Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(EnsureSelectionValid));
+ }
+ }
+
+ /// <summary>
+ /// Code that updates only the caret but not the selection can cause confusion when
+ /// keys like 'Delete' delete the (possibly invisible) selected text and not the
+ /// text around the caret.
+ ///
+ /// So we'll ensure that the caret is inside the selection.
+ /// (when the caret is not in the selection, we'll clear the selection)
+ ///
+ /// This method is invoked using the Dispatcher so that code may temporarily violate this rule
+ /// (e.g. most 'extend selection' methods work by first setting the caret, then the selection),
+ /// it's sufficient to fix it after any event handlers have run.
+ /// </summary>
+ void EnsureSelectionValid()
+ {
+ ensureSelectionValidRequested = false;
+ if (allowCaretOutsideSelection == 0) {
+ if (!selection.IsEmpty && !selection.Contains(caret.Offset)) {
+ Debug.WriteLine("Resetting selection because caret is outside");
+ this.ClearSelection();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Temporarily allows positioning the caret outside the selection.
+ /// Dispose the returned IDisposable to revert the allowance.
+ /// </summary>
+ /// <remarks>
+ /// The text area only forces the caret to be inside the selection when other events
+ /// have finished running (using the dispatcher), so you don't have to use this method
+ /// for temporarily positioning the caret in event handlers.
+ /// This method is only necessary if you want to run the WPF dispatcher, e.g. if you
+ /// perform a drag'n'drop operation.
+ /// </remarks>
+ public IDisposable AllowCaretOutsideSelection()
+ {
+ VerifyAccess();
+ allowCaretOutsideSelection++;
+ return new CallbackOnDispose(
+ delegate {
+ VerifyAccess();
+ allowCaretOutsideSelection--;
+ RequestSelectionValidation();
+ });
+ }
+ #endregion
+
+ #region Properties
+ readonly Caret caret;
+
+ /// <summary>
+ /// Gets the Caret used for this text area.
+ /// </summary>
+ public Caret Caret {
+ get { return caret; }
+ }
+
+ ObservableCollection<UIElement> leftMargins = new ObservableCollection<UIElement>();
+
+ /// <summary>
+ /// Gets the collection of margins displayed to the left of the text view.
+ /// </summary>
+ public ObservableCollection<UIElement> LeftMargins {
+ get {
+ return leftMargins;
+ }
+ }
+
+ void leftMargins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.OldItems != null) {
+ foreach (ITextViewConnect c in e.OldItems.OfType<ITextViewConnect>()) {
+ c.RemoveFromTextView(textView);
+ }
+ }
+ if (e.NewItems != null) {
+ foreach (ITextViewConnect c in e.NewItems.OfType<ITextViewConnect>()) {
+ c.AddToTextView(textView);
+ }
+ }
+ }
+
+ IReadOnlySectionProvider readOnlySectionProvider = NoReadOnlySections.Instance;
+
+ /// <summary>
+ /// Gets/Sets an object that provides read-only sections for the text area.
+ /// </summary>
+ public IReadOnlySectionProvider ReadOnlySectionProvider {
+ get { return readOnlySectionProvider; }
+ set {
+ if (value == null)
+ throw new ArgumentNullException("value");
+ readOnlySectionProvider = value;
+ CommandManager.InvalidateRequerySuggested(); // the read-only status effects Paste.CanExecute and the IME
+ }
+ }
+ #endregion
+
+ #region IScrollInfo implementation
+ ScrollViewer scrollOwner;
+ bool canVerticallyScroll, canHorizontallyScroll;
+
+ void ApplyScrollInfo()
+ {
+ if (scrollInfo != null) {
+ scrollInfo.ScrollOwner = scrollOwner;
+ scrollInfo.CanVerticallyScroll = canVerticallyScroll;
+ scrollInfo.CanHorizontallyScroll = canHorizontallyScroll;
+ scrollOwner = null;
+ }
+ }
+
+ bool IScrollInfo.CanVerticallyScroll {
+ get { return scrollInfo != null ? scrollInfo.CanVerticallyScroll : false; }
+ set {
+ canVerticallyScroll = value;
+ if (scrollInfo != null)
+ scrollInfo.CanVerticallyScroll = value;
+ }
+ }
+
+ bool IScrollInfo.CanHorizontallyScroll {
+ get { return scrollInfo != null ? scrollInfo.CanHorizontallyScroll : false; }
+ set {
+ canHorizontallyScroll = value;
+ if (scrollInfo != null)
+ scrollInfo.CanHorizontallyScroll = value;
+ }
+ }
+
+ double IScrollInfo.ExtentWidth {
+ get { return scrollInfo != null ? scrollInfo.ExtentWidth : 0; }
+ }
+
+ double IScrollInfo.ExtentHeight {
+ get { return scrollInfo != null ? scrollInfo.ExtentHeight : 0; }
+ }
+
+ double IScrollInfo.ViewportWidth {
+ get { return scrollInfo != null ? scrollInfo.ViewportWidth : 0; }
+ }
+
+ double IScrollInfo.ViewportHeight {
+ get { return scrollInfo != null ? scrollInfo.ViewportHeight : 0; }
+ }
+
+ double IScrollInfo.HorizontalOffset {
+ get { return scrollInfo != null ? scrollInfo.HorizontalOffset : 0; }
+ }
+
+ double IScrollInfo.VerticalOffset {
+ get { return scrollInfo != null ? scrollInfo.VerticalOffset : 0; }
+ }
+
+ ScrollViewer IScrollInfo.ScrollOwner {
+ get { return scrollInfo != null ? scrollInfo.ScrollOwner : null; }
+ set {
+ if (scrollInfo != null)
+ scrollInfo.ScrollOwner = value;
+ else
+ scrollOwner = value;
+ }
+ }
+
+ void IScrollInfo.LineUp()
+ {
+ if (scrollInfo != null) scrollInfo.LineUp();
+ }
+
+ void IScrollInfo.LineDown()
+ {
+ if (scrollInfo != null) scrollInfo.LineDown();
+ }
+
+ void IScrollInfo.LineLeft()
+ {
+ if (scrollInfo != null) scrollInfo.LineLeft();
+ }
+
+ void IScrollInfo.LineRight()
+ {
+ if (scrollInfo != null) scrollInfo.LineRight();
+ }
+
+ void IScrollInfo.PageUp()
+ {
+ if (scrollInfo != null) scrollInfo.PageUp();
+ }
+
+ void IScrollInfo.PageDown()
+ {
+ if (scrollInfo != null) scrollInfo.PageDown();
+ }
+
+ void IScrollInfo.PageLeft()
+ {
+ if (scrollInfo != null) scrollInfo.PageLeft();
+ }
+
+ void IScrollInfo.PageRight()
+ {
+ if (scrollInfo != null) scrollInfo.PageRight();
+ }
+
+ void IScrollInfo.MouseWheelUp()
+ {
+ if (scrollInfo != null) scrollInfo.MouseWheelUp();
+ }
+
+ void IScrollInfo.MouseWheelDown()
+ {
+ if (scrollInfo != null) scrollInfo.MouseWheelDown();
+ }
+
+ void IScrollInfo.MouseWheelLeft()
+ {
+ if (scrollInfo != null) scrollInfo.MouseWheelLeft();
+ }
+
+ void IScrollInfo.MouseWheelRight()
+ {
+ if (scrollInfo != null) scrollInfo.MouseWheelRight();
+ }
+
+ void IScrollInfo.SetHorizontalOffset(double offset)
+ {
+ if (scrollInfo != null) scrollInfo.SetHorizontalOffset(offset);
+ }
+
+ void IScrollInfo.SetVerticalOffset(double offset)
+ {
+ if (scrollInfo != null) scrollInfo.SetVerticalOffset(offset);
+ }
+
+ Rect IScrollInfo.MakeVisible(System.Windows.Media.Visual visual, Rect rectangle)
+ {
+ if (scrollInfo != null)
+ return scrollInfo.MakeVisible(visual, rectangle);
+ else
+ return Rect.Empty;
+ }
+ #endregion
+
+ #region Focus Handling (Show/Hide Caret)
+ /// <inheritdoc/>
+ protected override void OnMouseDown(MouseButtonEventArgs e)
+ {
+ base.OnMouseDown(e);
+ Focus();
+ }
+
+ /// <inheritdoc/>
+ protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
+ {
+ base.OnGotKeyboardFocus(e);
+ // First activate IME, then show caret
+ ime.OnGotKeyboardFocus(e);
+ caret.Show();
+ }
+
+ /// <inheritdoc/>
+ protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
+ {
+ base.OnLostKeyboardFocus(e);
+ caret.Hide();
+ ime.OnLostKeyboardFocus(e);
+ }
+ #endregion
+
+ #region OnTextInput / RemoveSelectedText / ReplaceSelectionWithText
+ /// <summary>
+ /// Occurs when the TextArea receives text input.
+ /// This is like the <see cref="UIElement.TextInput"/> event,
+ /// but occurs immediately before the TextArea handles the TextInput event.
+ /// </summary>
+ public event TextCompositionEventHandler TextEntering;
+
+ /// <summary>
+ /// Occurs when the TextArea receives text input.
+ /// This is like the <see cref="UIElement.TextInput"/> event,
+ /// but occurs immediately after the TextArea handles the TextInput event.
+ /// </summary>
+ public event TextCompositionEventHandler TextEntered;
+
+ /// <summary>
+ /// Raises the TextEntering event.
+ /// </summary>
+ protected virtual void OnTextEntering(TextCompositionEventArgs e)
+ {
+ if (TextEntering != null) {
+ TextEntering(this, e);
+ }
+ }
+
+ /// <summary>
+ /// Raises the TextEntered event.
+ /// </summary>
+ protected virtual void OnTextEntered(TextCompositionEventArgs e)
+ {
+ if (TextEntered != null) {
+ TextEntered(this, e);
+ }
+ }
+
+ /// <inheritdoc/>
+ protected override void OnTextInput(TextCompositionEventArgs e)
+ {
+ //Debug.WriteLine("TextInput: Text='" + e.Text + "' SystemText='" + e.SystemText + "' ControlText='" + e.ControlText + "'");
+ base.OnTextInput(e);
+ if (!e.Handled && this.Document != null) {
+ if (string.IsNullOrEmpty(e.Text) || e.Text == "\x1b" || e.Text == "\b") {
+ // ASCII 0x1b = ESC.
+ // WPF produces a TextInput event with that old ASCII control char
+ // when Escape is pressed. We'll just ignore it.
+
+ // A deadkey followed by backspace causes a textinput event for the BS character.
+
+ // Similarly, some shortcuts like Alt+Space produce an empty TextInput event.
+ // We have to ignore those (not handle them) to keep the shortcut working.
+ return;
+ }
+ PerformTextInput(e);
+ e.Handled = true;
+ }
+ }
+
+ /// <summary>
+ /// Performs text input.
+ /// This raises the <see cref="TextEntering"/> event, replaces the selection with the text,
+ /// and then raises the <see cref="TextEntered"/> event.
+ /// </summary>
+ public void PerformTextInput(string text)
+ {
+ TextComposition textComposition = new TextComposition(InputManager.Current, this, text);
+ TextCompositionEventArgs e = new TextCompositionEventArgs(Keyboard.PrimaryDevice, textComposition);
+ e.RoutedEvent = TextInputEvent;
+ PerformTextInput(e);
+ }
+
+ /// <summary>
+ /// Performs text input.
+ /// This raises the <see cref="TextEntering"/> event, replaces the selection with the text,
+ /// and then raises the <see cref="TextEntered"/> event.
+ /// </summary>
+ public void PerformTextInput(TextCompositionEventArgs e)
+ {
+ if (e == null)
+ throw new ArgumentNullException("e");
+ if (this.Document == null)
+ throw ThrowUtil.NoDocumentAssigned();
+ OnTextEntering(e);
+ if (!e.Handled) {
+ if (e.Text == "\n" || e.Text == "\r" || e.Text == "\r\n")
+ ReplaceSelectionWithNewLine();
+ else
+ ReplaceSelectionWithText(e.Text);
+ OnTextEntered(e);
+ caret.BringCaretToView();
+ }
+ }
+
+ void ReplaceSelectionWithNewLine()
+ {
+ string newLine = TextUtilities.GetNewLineFromDocument(this.Document, this.Caret.Line);
+ using (this.Document.RunUpdate()) {
+ ReplaceSelectionWithText(newLine);
+ if (this.IndentationStrategy != null) {
+ DocumentLine line = this.Document.GetLineByNumber(this.Caret.Line);
+ ISegment[] deletable = GetDeletableSegments(line);
+ if (deletable.Length == 1 && deletable[0].Offset == line.Offset && deletable[0].Length == line.Length) {
+ // use indentation strategy only if the line is not read-only
+ this.IndentationStrategy.IndentLine(this.Document, line);
+ }
+ }
+ }
+ }
+
+ internal void RemoveSelectedText()
+ {
+ if (this.Document == null)
+ throw ThrowUtil.NoDocumentAssigned();
+ selection.ReplaceSelectionWithText(string.Empty);
+ #if DEBUG
+ if (!selection.IsEmpty) {
+ foreach (ISegment s in selection.Segments) {
+ Debug.Assert(this.ReadOnlySectionProvider.GetDeletableSegments(s).Count() == 0);
+ }
+ }
+ #endif
+ }
+
+ internal void ReplaceSelectionWithText(string newText)
+ {
+ if (newText == null)
+ throw new ArgumentNullException("newText");
+ if (this.Document == null)
+ throw ThrowUtil.NoDocumentAssigned();
+ selection.ReplaceSelectionWithText(newText);
+ }
+
+ internal ISegment[] GetDeletableSegments(ISegment segment)
+ {
+ var deletableSegments = this.ReadOnlySectionProvider.GetDeletableSegments(segment);
+ if (deletableSegments == null)
+ throw new InvalidOperationException("ReadOnlySectionProvider.GetDeletableSegments returned null");
+ var array = deletableSegments.ToArray();
+ int lastIndex = segment.Offset;
+ for (int i = 0; i < array.Length; i++) {
+ if (array[i].Offset < lastIndex)
+ throw new InvalidOperationException("ReadOnlySectionProvider returned incorrect segments (outside of input segment / wrong order)");
+ lastIndex = array[i].EndOffset;
+ }
+ if (lastIndex > segment.EndOffset)
+ throw new InvalidOperationException("ReadOnlySectionProvider returned incorrect segments (outside of input segment / wrong order)");
+ return array;
+ }
+ #endregion
+
+ #region IndentationStrategy property
+ /// <summary>
+ /// IndentationStrategy property.
+ /// </summary>
+ public static readonly DependencyProperty IndentationStrategyProperty =
+ DependencyProperty.Register("IndentationStrategy", typeof(IIndentationStrategy), typeof(TextArea),
+ new FrameworkPropertyMetadata(new DefaultIndentationStrategy()));
+
+ /// <summary>
+ /// Gets/Sets the indentation strategy used when inserting new lines.
+ /// </summary>
+ public IIndentationStrategy IndentationStrategy {
+ get { return (IIndentationStrategy)GetValue(IndentationStrategyProperty); }
+ set { SetValue(IndentationStrategyProperty, value); }
+ }
+ #endregion
+
+ #region OnKeyDown/OnKeyUp
+ /// <inheritdoc/>
+ protected override void OnPreviewKeyDown(KeyEventArgs e)
+ {
+ base.OnPreviewKeyDown(e);
+ foreach (TextAreaStackedInputHandler h in stackedInputHandlers) {
+ if (e.Handled)
+ break;
+ h.OnPreviewKeyDown(e);
+ }
+ }
+
+ /// <inheritdoc/>
+ protected override void OnPreviewKeyUp(KeyEventArgs e)
+ {
+ base.OnPreviewKeyUp(e);
+ foreach (TextAreaStackedInputHandler h in stackedInputHandlers) {
+ if (e.Handled)
+ break;
+ h.OnPreviewKeyUp(e);
+ }
+ }
+
+ // Make life easier for text editor extensions that use a different cursor based on the pressed modifier keys.
+ /// <inheritdoc/>
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+ TextView.InvalidateCursor();
+ }
+
+ /// <inheritdoc/>
+ protected override void OnKeyUp(KeyEventArgs e)
+ {
+ base.OnKeyUp(e);
+ TextView.InvalidateCursor();
+ }
+ #endregion
+
+ /// <inheritdoc/>
+ protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
+ {
+ // accept clicks even where the text area draws no background
+ return new PointHitTestResult(this, hitTestParameters.HitPoint);
+ }
+
+ /// <inheritdoc/>
+ protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
+ {
+ base.OnPropertyChanged(e);
+ if (e.Property == SelectionBrushProperty
+ || e.Property == SelectionBorderProperty
+ || e.Property == SelectionForegroundProperty
+ || e.Property == SelectionCornerRadiusProperty)
+ {
+ textView.Redraw();
+ }
+ }
+
+ /// <summary>
+ /// Gets the requested service.
+ /// </summary>
+ /// <returns>Returns the requested service instance, or null if the service cannot be found.</returns>
+ public virtual object GetService(Type serviceType)
+ {
+ return textView.Services.GetService(serviceType);
+ }
+
+ /// <summary>
+ /// Occurs when text inside the TextArea was copied.
+ /// </summary>
+ public event EventHandler<TextEventArgs> TextCopied;
+
+ internal void OnTextCopied(TextEventArgs e)
+ {
+ if (TextCopied != null)
+ TextCopied(this, e);
+ }
+ }
+
+ /// <summary>
+ /// EventArgs with text.
+ /// </summary>
+ [Serializable]
+ public class TextEventArgs : EventArgs
+ {
+ string text;
+
+ /// <summary>
+ /// Gets the text.
+ /// </summary>
+ public string Text {
+ get {
+ return text;
+ }
+ }
+
+ /// <summary>
+ /// Creates a new TextEventArgs instance.
+ /// </summary>
+ public TextEventArgs(string text)
+ {
+ if (text == null)
+ throw new ArgumentNullException("text");
+ this.text = text;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaDefaultInputHandlers.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaDefaultInputHandlers.cs
new file mode 100644
index 000000000..7101d16c8
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaDefaultInputHandlers.cs
@@ -0,0 +1,120 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Input;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Contains the predefined input handlers.
+ /// </summary>
+ public class TextAreaDefaultInputHandler : TextAreaInputHandler
+ {
+ /// <summary>
+ /// Gets the caret navigation input handler.
+ /// </summary>
+ public TextAreaInputHandler CaretNavigation { get; private set; }
+
+ /// <summary>
+ /// Gets the editing input handler.
+ /// </summary>
+ public TextAreaInputHandler Editing { get; private set; }
+
+ /// <summary>
+ /// Gets the mouse selection input handler.
+ /// </summary>
+ public ITextAreaInputHandler MouseSelection { get; private set; }
+
+ /// <summary>
+ /// Creates a new TextAreaDefaultInputHandler instance.
+ /// </summary>
+ public TextAreaDefaultInputHandler(TextArea textArea) : base(textArea)
+ {
+ this.NestedInputHandlers.Add(CaretNavigation = CaretNavigationCommandHandler.Create(textArea));
+ this.NestedInputHandlers.Add(Editing = EditingCommandHandler.Create(textArea));
+ this.NestedInputHandlers.Add(MouseSelection = new SelectionMouseHandler(textArea));
+
+ this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, ExecuteUndo, CanExecuteUndo));
+ this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Redo, ExecuteRedo, CanExecuteRedo));
+ }
+
+ internal static KeyBinding CreateFrozenKeyBinding(ICommand command, ModifierKeys modifiers, Key key)
+ {
+ KeyBinding kb = new KeyBinding(command, key, modifiers);
+ // Mark KeyBindings as frozen because they're shared between multiple editor instances.
+ // KeyBinding derives from Freezable only in .NET 4, so we have to use this little trick:
+ Freezable f = ((object)kb) as Freezable;
+ if (f != null)
+ f.Freeze();
+ return kb;
+ }
+
+ internal static void WorkaroundWPFMemoryLeak(List<InputBinding> inputBindings)
+ {
+ // Work around WPF memory leak:
+ // KeyBinding retains a reference to whichever UIElement it is used in first.
+ // Using a dummy element for this purpose ensures that we don't leak
+ // a real text editor (with a potentially large document).
+ UIElement dummyElement = new UIElement();
+ dummyElement.InputBindings.AddRange(inputBindings);
+ }
+
+ #region Undo / Redo
+ UndoStack GetUndoStack()
+ {
+ TextDocument document = this.TextArea.Document;
+ if (document != null)
+ return document.UndoStack;
+ else
+ return null;
+ }
+
+ void ExecuteUndo(object sender, ExecutedRoutedEventArgs e)
+ {
+ var undoStack = GetUndoStack();
+ if (undoStack != null) {
+ if (undoStack.CanUndo) {
+ undoStack.Undo();
+ this.TextArea.Caret.BringCaretToView();
+ }
+ e.Handled = true;
+ }
+ }
+
+ void CanExecuteUndo(object sender, CanExecuteRoutedEventArgs e)
+ {
+ var undoStack = GetUndoStack();
+ if (undoStack != null) {
+ e.Handled = true;
+ e.CanExecute = undoStack.CanUndo;
+ }
+ }
+
+ void ExecuteRedo(object sender, ExecutedRoutedEventArgs e)
+ {
+ var undoStack = GetUndoStack();
+ if (undoStack != null) {
+ if (undoStack.CanRedo) {
+ undoStack.Redo();
+ this.TextArea.Caret.BringCaretToView();
+ }
+ e.Handled = true;
+ }
+ }
+
+ void CanExecuteRedo(object sender, CanExecuteRoutedEventArgs e)
+ {
+ var undoStack = GetUndoStack();
+ if (undoStack != null) {
+ e.Handled = true;
+ e.CanExecute = undoStack.CanRedo;
+ }
+ }
+ #endregion
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaInputHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaInputHandler.cs
new file mode 100644
index 000000000..3256875be
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaInputHandler.cs
@@ -0,0 +1,242 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using Tango.Scripting.Editors.Utils;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// A set of input bindings and event handlers for the text area.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// There is one active input handler per text area (<see cref="Editing.TextArea.ActiveInputHandler"/>), plus
+ /// a number of active stacked input handlers.
+ /// </para>
+ /// <para>
+ /// The text area also stores a reference to a default input handler, but that is not necessarily active.
+ /// </para>
+ /// <para>
+ /// Stacked input handlers work in addition to the set of currently active handlers (without detaching them).
+ /// They are detached in the reverse order of being attached.
+ /// </para>
+ /// </remarks>
+ public interface ITextAreaInputHandler
+ {
+ /// <summary>
+ /// Gets the text area that the input handler belongs to.
+ /// </summary>
+ TextArea TextArea {
+ get;
+ }
+
+ /// <summary>
+ /// Attaches an input handler to the text area.
+ /// </summary>
+ void Attach();
+
+ /// <summary>
+ /// Detaches the input handler from the text area.
+ /// </summary>
+ void Detach();
+ }
+
+ /// <summary>
+ /// Stacked input handler.
+ /// Uses OnEvent-methods instead of registering event handlers to ensure that the events are handled in the correct order.
+ /// </summary>
+ public abstract class TextAreaStackedInputHandler : ITextAreaInputHandler
+ {
+ readonly TextArea textArea;
+
+ /// <inheritdoc/>
+ public TextArea TextArea {
+ get { return textArea; }
+ }
+
+ /// <summary>
+ /// Creates a new TextAreaInputHandler.
+ /// </summary>
+ protected TextAreaStackedInputHandler(TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ this.textArea = textArea;
+ }
+
+ /// <inheritdoc/>
+ public virtual void Attach()
+ {
+ }
+
+ /// <inheritdoc/>
+ public virtual void Detach()
+ {
+ }
+
+ /// <summary>
+ /// Called for the PreviewKeyDown event.
+ /// </summary>
+ public virtual void OnPreviewKeyDown(KeyEventArgs e)
+ {
+ }
+
+ /// <summary>
+ /// Called for the PreviewKeyUp event.
+ /// </summary>
+ public virtual void OnPreviewKeyUp(KeyEventArgs e)
+ {
+ }
+ }
+
+ /// <summary>
+ /// Default-implementation of <see cref="ITextAreaInputHandler"/>.
+ /// </summary>
+ /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
+ public class TextAreaInputHandler : ITextAreaInputHandler
+ {
+ readonly ObserveAddRemoveCollection<CommandBinding> commandBindings;
+ readonly ObserveAddRemoveCollection<InputBinding> inputBindings;
+ readonly ObserveAddRemoveCollection<ITextAreaInputHandler> nestedInputHandlers;
+ readonly TextArea textArea;
+ bool isAttached;
+
+ /// <summary>
+ /// Creates a new TextAreaInputHandler.
+ /// </summary>
+ public TextAreaInputHandler(TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ this.textArea = textArea;
+ commandBindings = new ObserveAddRemoveCollection<CommandBinding>(CommandBinding_Added, CommandBinding_Removed);
+ inputBindings = new ObserveAddRemoveCollection<InputBinding>(InputBinding_Added, InputBinding_Removed);
+ nestedInputHandlers = new ObserveAddRemoveCollection<ITextAreaInputHandler>(NestedInputHandler_Added, NestedInputHandler_Removed);
+ }
+
+ /// <inheritdoc/>
+ public TextArea TextArea {
+ get { return textArea; }
+ }
+
+ /// <summary>
+ /// Gets whether the input handler is currently attached to the text area.
+ /// </summary>
+ public bool IsAttached {
+ get { return isAttached; }
+ }
+
+ #region CommandBindings / InputBindings
+ /// <summary>
+ /// Gets the command bindings of this input handler.
+ /// </summary>
+ public ICollection<CommandBinding> CommandBindings {
+ get { return commandBindings; }
+ }
+
+ void CommandBinding_Added(CommandBinding commandBinding)
+ {
+ if (isAttached)
+ textArea.CommandBindings.Add(commandBinding);
+ }
+
+ void CommandBinding_Removed(CommandBinding commandBinding)
+ {
+ if (isAttached)
+ textArea.CommandBindings.Remove(commandBinding);
+ }
+
+ /// <summary>
+ /// Gets the input bindings of this input handler.
+ /// </summary>
+ public ICollection<InputBinding> InputBindings {
+ get { return inputBindings; }
+ }
+
+ void InputBinding_Added(InputBinding inputBinding)
+ {
+ if (isAttached)
+ textArea.InputBindings.Add(inputBinding);
+ }
+
+ void InputBinding_Removed(InputBinding inputBinding)
+ {
+ if (isAttached)
+ textArea.InputBindings.Remove(inputBinding);
+ }
+
+ /// <summary>
+ /// Adds a command and input binding.
+ /// </summary>
+ /// <param name="command">The command ID.</param>
+ /// <param name="modifiers">The modifiers of the keyboard shortcut.</param>
+ /// <param name="key">The key of the keyboard shortcut.</param>
+ /// <param name="handler">The event handler to run when the command is executed.</param>
+ public void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler)
+ {
+ this.CommandBindings.Add(new CommandBinding(command, handler));
+ this.InputBindings.Add(new KeyBinding(command, key, modifiers));
+ }
+ #endregion
+
+ #region NestedInputHandlers
+ /// <summary>
+ /// Gets the collection of nested input handlers. NestedInputHandlers are activated and deactivated
+ /// together with this input handler.
+ /// </summary>
+ public ICollection<ITextAreaInputHandler> NestedInputHandlers {
+ get { return nestedInputHandlers; }
+ }
+
+ void NestedInputHandler_Added(ITextAreaInputHandler handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException("handler");
+ if (handler.TextArea != textArea)
+ throw new ArgumentException("The nested handler must be working for the same text area!");
+ if (isAttached)
+ handler.Attach();
+ }
+
+ void NestedInputHandler_Removed(ITextAreaInputHandler handler)
+ {
+ if (isAttached)
+ handler.Detach();
+ }
+ #endregion
+
+ #region Attach/Detach
+ /// <inheritdoc/>
+ public virtual void Attach()
+ {
+ if (isAttached)
+ throw new InvalidOperationException("Input handler is already attached");
+ isAttached = true;
+
+ textArea.CommandBindings.AddRange(commandBindings);
+ textArea.InputBindings.AddRange(inputBindings);
+ foreach (ITextAreaInputHandler handler in nestedInputHandlers)
+ handler.Attach();
+ }
+
+ /// <inheritdoc/>
+ public virtual void Detach()
+ {
+ if (!isAttached)
+ throw new InvalidOperationException("Input handler is not attached");
+ isAttached = false;
+
+ foreach (CommandBinding b in commandBindings)
+ textArea.CommandBindings.Remove(b);
+ foreach (InputBinding b in inputBindings)
+ textArea.InputBindings.Remove(b);
+ foreach (ITextAreaInputHandler handler in nestedInputHandlers)
+ handler.Detach();
+ }
+ #endregion
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextSegmentReadOnlySectionProvider.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextSegmentReadOnlySectionProvider.cs
new file mode 100644
index 000000000..c582301d4
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextSegmentReadOnlySectionProvider.cs
@@ -0,0 +1,80 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Editing
+{
+ /// <summary>
+ /// Implementation for <see cref="IReadOnlySectionProvider"/> that stores the segments
+ /// in a <see cref="TextSegmentCollection{T}"/>.
+ /// </summary>
+ public class TextSegmentReadOnlySectionProvider<T> : IReadOnlySectionProvider where T : TextSegment
+ {
+ readonly TextSegmentCollection<T> segments;
+
+ /// <summary>
+ /// Gets the collection storing the read-only segments.
+ /// </summary>
+ public TextSegmentCollection<T> Segments {
+ get { return segments; }
+ }
+
+ /// <summary>
+ /// Creates a new TextSegmentReadOnlySectionProvider instance for the specified document.
+ /// </summary>
+ public TextSegmentReadOnlySectionProvider(TextDocument textDocument)
+ {
+ segments = new TextSegmentCollection<T>(textDocument);
+ }
+
+ /// <summary>
+ /// Creates a new TextSegmentReadOnlySectionProvider instance using the specified TextSegmentCollection.
+ /// </summary>
+ public TextSegmentReadOnlySectionProvider(TextSegmentCollection<T> segments)
+ {
+ if (segments == null)
+ throw new ArgumentNullException("segments");
+ this.segments = segments;
+ }
+
+ /// <summary>
+ /// Gets whether insertion is possible at the specified offset.
+ /// </summary>
+ public virtual bool CanInsert(int offset)
+ {
+ foreach (TextSegment segment in segments.FindSegmentsContaining(offset)) {
+ if (segment.StartOffset < offset && offset < segment.EndOffset)
+ return false;
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Gets the deletable segments inside the given segment.
+ /// </summary>
+ public virtual IEnumerable<ISegment> GetDeletableSegments(ISegment segment)
+ {
+ if (segment == null)
+ throw new ArgumentNullException("segment");
+
+ int readonlyUntil = segment.Offset;
+ foreach (TextSegment ts in segments.FindOverlappingSegments(segment)) {
+ int start = ts.StartOffset;
+ int end = start + ts.Length;
+ if (start > readonlyUntil) {
+ yield return new SimpleSegment(readonlyUntil, start - readonlyUntil);
+ }
+ if (end > readonlyUntil) {
+ readonlyUntil = end;
+ }
+ }
+ int endOffset = segment.EndOffset;
+ if (readonlyUntil < endOffset) {
+ yield return new SimpleSegment(readonlyUntil, endOffset - readonlyUntil);
+ }
+ }
+ }
+}