aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2019-04-09 01:47:48 +0300
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2019-04-09 01:47:48 +0300
commit080f1697e97e13461ec6df4d31c8924d01257a1b (patch)
treeb1fe0285de7bc9bc52e9e2195e66fe022bf8f5b3 /Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets
parent1608e69a417bc5e40a607c3958c4a60f19f66f1a (diff)
downloadTango-080f1697e97e13461ec6df4d31c8924d01257a1b.tar.gz
Tango-080f1697e97e13461ec6df4d31c8924d01257a1b.zip
MERGE
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/IActiveElement.cs35
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/InsertionContext.cs269
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/Snippet.cs47
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetAnchorElement.cs97
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetBoundElement.cs122
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetCaretElement.cs58
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetContainerElement.cs49
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetElement.cs32
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetEventArgs.cs57
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetInputHandler.cs80
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs213
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetSelectionElement.cs45
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetTextElement.cs39
13 files changed, 1143 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/IActiveElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/IActiveElement.cs
new file mode 100644
index 000000000..07275f273
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/IActiveElement.cs
@@ -0,0 +1,35 @@
+// 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.Media;
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// Represents an active element that allows the snippet to stay interactive after insertion.
+ /// </summary>
+ public interface IActiveElement
+ {
+ /// <summary>
+ /// Called when the all snippet elements have been inserted.
+ /// </summary>
+ void OnInsertionCompleted();
+
+ /// <summary>
+ /// Called when the interactive mode is deactivated.
+ /// </summary>
+ void Deactivate(SnippetEventArgs e);
+
+ /// <summary>
+ /// Gets whether this element is editable (the user will be able to select it with Tab).
+ /// </summary>
+ bool IsEditable { get; }
+
+ /// <summary>
+ /// Gets the segment associated with this element. May be null.
+ /// </summary>
+ ISegment Segment { get; }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/InsertionContext.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/InsertionContext.cs
new file mode 100644
index 000000000..d7de8311d
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/InsertionContext.cs
@@ -0,0 +1,269 @@
+// 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;
+using Tango.Scripting.Editors.Editing;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// Represents the context of a snippet insertion.
+ /// </summary>
+ public class InsertionContext : IWeakEventListener
+ {
+ enum Status
+ {
+ Insertion,
+ RaisingInsertionCompleted,
+ Interactive,
+ RaisingDeactivated,
+ Deactivated
+ }
+
+ Status currentStatus = Status.Insertion;
+
+ /// <summary>
+ /// Creates a new InsertionContext instance.
+ /// </summary>
+ public InsertionContext(TextArea textArea, int insertionPosition)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ this.TextArea = textArea;
+ this.Document = textArea.Document;
+ this.SelectedText = textArea.Selection.GetText();
+ this.InsertionPosition = insertionPosition;
+ this.startPosition = insertionPosition;
+
+ DocumentLine startLine = this.Document.GetLineByOffset(insertionPosition);
+ ISegment indentation = TextUtilities.GetWhitespaceAfter(this.Document, startLine.Offset);
+ this.Indentation = Document.GetText(indentation.Offset, Math.Min(indentation.EndOffset, insertionPosition) - indentation.Offset);
+ this.Tab = textArea.Options.IndentationString;
+
+ this.LineTerminator = TextUtilities.GetNewLineFromDocument(this.Document, startLine.LineNumber);
+ }
+
+ /// <summary>
+ /// Gets the text area.
+ /// </summary>
+ public TextArea TextArea { get; private set; }
+
+ /// <summary>
+ /// Gets the text document.
+ /// </summary>
+ public TextDocument Document { get; private set; }
+
+ /// <summary>
+ /// Gets the text that was selected before the insertion of the snippet.
+ /// </summary>
+ public string SelectedText { get; private set; }
+
+ /// <summary>
+ /// Gets the indentation at the insertion position.
+ /// </summary>
+ public string Indentation { get; private set; }
+
+ /// <summary>
+ /// Gets the indentation string for a single indentation level.
+ /// </summary>
+ public string Tab { get; private set; }
+
+ /// <summary>
+ /// Gets the line terminator at the insertion position.
+ /// </summary>
+ public string LineTerminator { get; private set; }
+
+ /// <summary>
+ /// Gets/Sets the insertion position.
+ /// </summary>
+ public int InsertionPosition { get; set; }
+
+ readonly int startPosition;
+ AnchorSegment wholeSnippetAnchor;
+ bool deactivateIfSnippetEmpty;
+
+ /// <summary>
+ /// Gets the start position of the snippet insertion.
+ /// </summary>
+ public int StartPosition {
+ get {
+ if (wholeSnippetAnchor != null)
+ return wholeSnippetAnchor.Offset;
+ else
+ return startPosition;
+ }
+ }
+
+ /// <summary>
+ /// Inserts text at the insertion position and advances the insertion position.
+ /// This method will add the current indentation to every line in <paramref name="text"/> and will
+ /// replace newlines with the expected newline for the document.
+ /// </summary>
+ public void InsertText(string text)
+ {
+ if (text == null)
+ throw new ArgumentNullException("text");
+ if (currentStatus != Status.Insertion)
+ throw new InvalidOperationException();
+
+ text = text.Replace("\t", this.Tab);
+
+ using (this.Document.RunUpdate()) {
+ int textOffset = 0;
+ SimpleSegment segment;
+ while ((segment = NewLineFinder.NextNewLine(text, textOffset)) != SimpleSegment.Invalid) {
+ string insertString = text.Substring(textOffset, segment.Offset - textOffset)
+ + this.LineTerminator + this.Indentation;
+ this.Document.Insert(InsertionPosition, insertString);
+ this.InsertionPosition += insertString.Length;
+ textOffset = segment.EndOffset;
+ }
+ string remainingInsertString = text.Substring(textOffset);
+ this.Document.Insert(InsertionPosition, remainingInsertString);
+ this.InsertionPosition += remainingInsertString.Length;
+ }
+ }
+
+ Dictionary<SnippetElement, IActiveElement> elementMap = new Dictionary<SnippetElement, IActiveElement>();
+ List<IActiveElement> registeredElements = new List<IActiveElement>();
+
+ /// <summary>
+ /// Registers an active element. Elements should be registered during insertion and will be called back
+ /// when insertion has completed.
+ /// </summary>
+ /// <param name="owner">The snippet element that created the active element.</param>
+ /// <param name="element">The active element.</param>
+ public void RegisterActiveElement(SnippetElement owner, IActiveElement element)
+ {
+ if (owner == null)
+ throw new ArgumentNullException("owner");
+ if (element == null)
+ throw new ArgumentNullException("element");
+ if (currentStatus != Status.Insertion)
+ throw new InvalidOperationException();
+ elementMap.Add(owner, element);
+ registeredElements.Add(element);
+ }
+
+ /// <summary>
+ /// Returns the active element belonging to the specified snippet element, or null if no such active element is found.
+ /// </summary>
+ public IActiveElement GetActiveElement(SnippetElement owner)
+ {
+ if (owner == null)
+ throw new ArgumentNullException("owner");
+ IActiveElement element;
+ if (elementMap.TryGetValue(owner, out element))
+ return element;
+ else
+ return null;
+ }
+
+ /// <summary>
+ /// Gets the list of active elements.
+ /// </summary>
+ public IEnumerable<IActiveElement> ActiveElements {
+ get { return registeredElements; }
+ }
+
+ /// <summary>
+ /// Calls the <see cref="IActiveElement.OnInsertionCompleted"/> method on all registered active elements
+ /// and raises the <see cref="InsertionCompleted"/> event.
+ /// </summary>
+ /// <param name="e">The EventArgs to use</param>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
+ Justification="There is an event and this method is raising it.")]
+ public void RaiseInsertionCompleted(EventArgs e)
+ {
+ if (currentStatus != Status.Insertion)
+ throw new InvalidOperationException();
+ if (e == null)
+ e = EventArgs.Empty;
+
+ currentStatus = Status.RaisingInsertionCompleted;
+ int endPosition = this.InsertionPosition;
+ this.wholeSnippetAnchor = new AnchorSegment(Document, startPosition, endPosition - startPosition);
+ TextDocumentWeakEventManager.UpdateFinished.AddListener(Document, this);
+ deactivateIfSnippetEmpty = (endPosition != startPosition);
+
+ foreach (IActiveElement element in registeredElements) {
+ element.OnInsertionCompleted();
+ }
+ if (InsertionCompleted != null)
+ InsertionCompleted(this, e);
+ currentStatus = Status.Interactive;
+ if (registeredElements.Count == 0) {
+ // deactivate immediately if there are no interactive elements
+ Deactivate(new SnippetEventArgs(DeactivateReason.NoActiveElements));
+ } else {
+ myInputHandler = new SnippetInputHandler(this);
+ // disable existing snippet input handlers - there can be only 1 active snippet
+ foreach (TextAreaStackedInputHandler h in TextArea.StackedInputHandlers) {
+ if (h is SnippetInputHandler)
+ TextArea.PopStackedInputHandler(h);
+ }
+ TextArea.PushStackedInputHandler(myInputHandler);
+ }
+ }
+
+ SnippetInputHandler myInputHandler;
+
+ /// <summary>
+ /// Occurs when the all snippet elements have been inserted.
+ /// </summary>
+ public event EventHandler InsertionCompleted;
+
+ /// <summary>
+ /// Calls the <see cref="IActiveElement.Deactivate"/> method on all registered active elements.
+ /// </summary>
+ /// <param name="e">The EventArgs to use</param>
+ public void Deactivate(SnippetEventArgs e)
+ {
+ if (currentStatus == Status.Deactivated || currentStatus == Status.RaisingDeactivated)
+ return;
+ if (currentStatus != Status.Interactive)
+ throw new InvalidOperationException("Cannot call Deactivate() until RaiseInsertionCompleted() has finished.");
+ if (e == null)
+ e = new SnippetEventArgs(DeactivateReason.Unknown);
+
+ TextDocumentWeakEventManager.UpdateFinished.RemoveListener(Document, this);
+ currentStatus = Status.RaisingDeactivated;
+ TextArea.PopStackedInputHandler(myInputHandler);
+ foreach (IActiveElement element in registeredElements) {
+ element.Deactivate(e);
+ }
+ if (Deactivated != null)
+ Deactivated(this, e);
+ currentStatus = Status.Deactivated;
+ }
+
+ /// <summary>
+ /// Occurs when the interactive mode is deactivated.
+ /// </summary>
+ public event EventHandler<SnippetEventArgs> Deactivated;
+
+ bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
+ {
+ return ReceiveWeakEvent(managerType, sender, e);
+ }
+
+ /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
+ protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
+ {
+ if (managerType == typeof(TextDocumentWeakEventManager.UpdateFinished)) {
+ // Deactivate if snippet is deleted. This is necessary for correctly leaving interactive
+ // mode if Undo is pressed after a snippet insertion.
+ if (wholeSnippetAnchor.Length == 0 && deactivateIfSnippetEmpty)
+ Deactivate(new SnippetEventArgs(DeactivateReason.Deleted));
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/Snippet.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/Snippet.cs
new file mode 100644
index 000000000..0006261fb
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/Snippet.cs
@@ -0,0 +1,47 @@
+// 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.Documents;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Editing;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// A code snippet that can be inserted into the text editor.
+ /// </summary>
+ [Serializable]
+ public class Snippet : SnippetContainerElement
+ {
+ /// <summary>
+ /// Inserts the snippet into the text area.
+ /// </summary>
+ public void Insert(TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+
+ ISegment selection = textArea.Selection.SurroundingSegment;
+ int insertionPosition = textArea.Caret.Offset;
+
+ if (selection != null) // if something is selected
+ // use selection start instead of caret position,
+ // because caret could be at end of selection or anywhere inside.
+ // Removal of the selected text causes the caret position to be invalid.
+ insertionPosition = selection.Offset + TextUtilities.GetWhitespaceAfter(textArea.Document, selection.Offset).Length;
+
+ InsertionContext context = new InsertionContext(textArea, insertionPosition);
+
+ using (context.Document.RunUpdate()) {
+ if (selection != null)
+ textArea.Document.Remove(insertionPosition, selection.EndOffset - insertionPosition);
+ Insert(context);
+ context.RaiseInsertionCompleted(EventArgs.Empty);
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetAnchorElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetAnchorElement.cs
new file mode 100644
index 000000000..cfce03b6a
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetAnchorElement.cs
@@ -0,0 +1,97 @@
+// 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.Snippets
+{
+ /// <summary>
+ /// Creates a named anchor that can be accessed by other SnippetElements.
+ /// </summary>
+ public sealed class SnippetAnchorElement : SnippetElement
+ {
+ /// <summary>
+ /// Gets or sets the name of the anchor.
+ /// </summary>
+ public string Name { get; private set; }
+
+ /// <summary>
+ /// Creates a SnippetAnchorElement with the supplied name.
+ /// </summary>
+ public SnippetAnchorElement(string name)
+ {
+ this.Name = name;
+ }
+
+ /// <inheritdoc />
+ public override void Insert(InsertionContext context)
+ {
+ TextAnchor start = context.Document.CreateAnchor(context.InsertionPosition);
+ start.MovementType = AnchorMovementType.BeforeInsertion;
+ start.SurviveDeletion = true;
+ AnchorSegment segment = new AnchorSegment(start, start);
+ context.RegisterActiveElement(this, new AnchorElement(segment, Name, context));
+ }
+ }
+
+ /// <summary>
+ /// AnchorElement created by SnippetAnchorElement.
+ /// </summary>
+ public sealed class AnchorElement : IActiveElement
+ {
+ /// <inheritdoc />
+ public bool IsEditable {
+ get { return false; }
+ }
+
+ AnchorSegment segment;
+ InsertionContext context;
+
+ /// <inheritdoc />
+ public ISegment Segment {
+ get { return segment; }
+ }
+
+ /// <summary>
+ /// Creates a new AnchorElement.
+ /// </summary>
+ public AnchorElement(AnchorSegment segment, string name, InsertionContext context)
+ {
+ this.segment = segment;
+ this.context = context;
+ this.Name = name;
+ }
+
+ /// <summary>
+ /// Gets or sets the text at the anchor.
+ /// </summary>
+ public string Text {
+ get { return context.Document.GetText(segment); }
+ set {
+ int offset = segment.Offset;
+ int length = segment.Length;
+ context.Document.Replace(offset, length, value);
+ if (length == 0) {
+ // replacing an empty anchor segment with text won't enlarge it, so we have to recreate it
+ segment = new AnchorSegment(context.Document, offset, value.Length);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the name of the anchor.
+ /// </summary>
+ public string Name { get; private set; }
+
+ /// <inheritdoc />
+ public void OnInsertionCompleted()
+ {
+ }
+
+ /// <inheritdoc />
+ public void Deactivate(SnippetEventArgs e)
+ {
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetBoundElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetBoundElement.cs
new file mode 100644
index 000000000..92f6d4fd4
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetBoundElement.cs
@@ -0,0 +1,122 @@
+// 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.Documents;
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// An element that binds to a <see cref="SnippetReplaceableTextElement"/> and displays the same text.
+ /// </summary>
+ [Serializable]
+ public class SnippetBoundElement : SnippetElement
+ {
+ SnippetReplaceableTextElement targetElement;
+
+ /// <summary>
+ /// Gets/Sets the target element.
+ /// </summary>
+ public SnippetReplaceableTextElement TargetElement {
+ get { return targetElement; }
+ set { targetElement = value; }
+ }
+
+ /// <summary>
+ /// Converts the text before copying it.
+ /// </summary>
+ public virtual string ConvertText(string input)
+ {
+ return input;
+ }
+
+ /// <inheritdoc/>
+ public override void Insert(InsertionContext context)
+ {
+ if (targetElement != null) {
+ TextAnchor start = context.Document.CreateAnchor(context.InsertionPosition);
+ start.MovementType = AnchorMovementType.BeforeInsertion;
+ start.SurviveDeletion = true;
+ string inputText = targetElement.Text;
+ if (inputText != null) {
+ context.InsertText(ConvertText(inputText));
+ }
+ TextAnchor end = context.Document.CreateAnchor(context.InsertionPosition);
+ end.MovementType = AnchorMovementType.BeforeInsertion;
+ end.SurviveDeletion = true;
+ AnchorSegment segment = new AnchorSegment(start, end);
+ context.RegisterActiveElement(this, new BoundActiveElement(context, targetElement, this, segment));
+ }
+ }
+
+ /// <inheritdoc/>
+ public override Inline ToTextRun()
+ {
+ if (targetElement != null) {
+ string inputText = targetElement.Text;
+ if (inputText != null) {
+ return new Italic(new Run(ConvertText(inputText)));
+ }
+ }
+ return base.ToTextRun();
+ }
+ }
+
+ sealed class BoundActiveElement : IActiveElement
+ {
+ InsertionContext context;
+ SnippetReplaceableTextElement targetSnippetElement;
+ SnippetBoundElement boundElement;
+ internal IReplaceableActiveElement targetElement;
+ AnchorSegment segment;
+
+ public BoundActiveElement(InsertionContext context, SnippetReplaceableTextElement targetSnippetElement, SnippetBoundElement boundElement, AnchorSegment segment)
+ {
+ this.context = context;
+ this.targetSnippetElement = targetSnippetElement;
+ this.boundElement = boundElement;
+ this.segment = segment;
+ }
+
+ public void OnInsertionCompleted()
+ {
+ targetElement = context.GetActiveElement(targetSnippetElement) as IReplaceableActiveElement;
+ if (targetElement != null) {
+ targetElement.TextChanged += targetElement_TextChanged;
+ }
+ }
+
+ void targetElement_TextChanged(object sender, EventArgs e)
+ {
+ // Don't copy text if the segments overlap (we would get an endless loop).
+ // This can happen if the user deletes the text between the replaceable element and the bound element.
+ if (segment.GetOverlap(targetElement.Segment) == SimpleSegment.Invalid) {
+ int offset = segment.Offset;
+ int length = segment.Length;
+ string text = boundElement.ConvertText(targetElement.Text);
+ if (length != text.Length || text != context.Document.GetText(offset, length)) {
+ // Call replace only if we're actually changing something.
+ // Without this check, we would generate an empty undo group when the user pressed undo.
+ context.Document.Replace(offset, length, text);
+ if (length == 0) {
+ // replacing an empty anchor segment with text won't enlarge it, so we have to recreate it
+ segment = new AnchorSegment(context.Document, offset, text.Length);
+ }
+ }
+ }
+ }
+
+ public void Deactivate(SnippetEventArgs e)
+ {
+ }
+
+ public bool IsEditable {
+ get { return false; }
+ }
+
+ public ISegment Segment {
+ get { return segment; }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetCaretElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetCaretElement.cs
new file mode 100644
index 000000000..1b33abf66
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetCaretElement.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.Runtime.Serialization;
+using System.Windows.Input;
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// Sets the caret position after interactive mode has finished.
+ /// </summary>
+ [Serializable]
+ public class SnippetCaretElement : SnippetElement
+ {
+ [OptionalField]
+ bool setCaretOnlyIfTextIsSelected;
+
+ /// <summary>
+ /// Creates a new SnippetCaretElement.
+ /// </summary>
+ public SnippetCaretElement()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new SnippetCaretElement.
+ /// </summary>
+ /// <param name="setCaretOnlyIfTextIsSelected">
+ /// If set to true, the caret is set only when some text was selected.
+ /// This is useful when both SnippetCaretElement and SnippetSelectionElement are used in the same snippet.
+ /// </param>
+ public SnippetCaretElement(bool setCaretOnlyIfTextIsSelected)
+ {
+ this.setCaretOnlyIfTextIsSelected = setCaretOnlyIfTextIsSelected;
+ }
+
+ /// <inheritdoc/>
+ public override void Insert(InsertionContext context)
+ {
+ if (!setCaretOnlyIfTextIsSelected || !string.IsNullOrEmpty(context.SelectedText))
+ SetCaret(context);
+ }
+
+ internal static void SetCaret(InsertionContext context)
+ {
+ TextAnchor pos = context.Document.CreateAnchor(context.InsertionPosition);
+ pos.MovementType = AnchorMovementType.BeforeInsertion;
+ pos.SurviveDeletion = true;
+ context.Deactivated += (sender, e) => {
+ if (e.Reason == DeactivateReason.ReturnPressed || e.Reason == DeactivateReason.NoActiveElements) {
+ context.TextArea.Caret.Offset = pos.Offset;
+ }
+ };
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetContainerElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetContainerElement.cs
new file mode 100644
index 000000000..066f1a870
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetContainerElement.cs
@@ -0,0 +1,49 @@
+// 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.Documents;
+
+using Tango.Scripting.Editors.Editing;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// A snippet element that has sub-elements.
+ /// </summary>
+ [Serializable]
+ public class SnippetContainerElement : SnippetElement
+ {
+ NullSafeCollection<SnippetElement> elements = new NullSafeCollection<SnippetElement>();
+
+ /// <summary>
+ /// Gets the list of child elements.
+ /// </summary>
+ public IList<SnippetElement> Elements {
+ get { return elements; }
+ }
+
+ /// <inheritdoc/>
+ public override void Insert(InsertionContext context)
+ {
+ foreach (SnippetElement e in this.Elements) {
+ e.Insert(context);
+ }
+ }
+
+ /// <inheritdoc/>
+ public override Inline ToTextRun()
+ {
+ Span span = new Span();
+ foreach (SnippetElement e in this.Elements) {
+ Inline r = e.ToTextRun();
+ if (r != null)
+ span.Inlines.Add(r);
+ }
+ return span;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetElement.cs
new file mode 100644
index 000000000..2e2c4556e
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetElement.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 System.Windows.Documents;
+
+using Tango.Scripting.Editors.Editing;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// An element inside a snippet.
+ /// </summary>
+ [Serializable]
+ public abstract class SnippetElement
+ {
+ /// <summary>
+ /// Performs insertion of the snippet.
+ /// </summary>
+ public abstract void Insert(InsertionContext context);
+
+ /// <summary>
+ /// Converts the snippet to text, with replaceable fields in italic.
+ /// </summary>
+ public virtual Inline ToTextRun()
+ {
+ return null;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetEventArgs.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetEventArgs.cs
new file mode 100644
index 000000000..cfc993485
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetEventArgs.cs
@@ -0,0 +1,57 @@
+// 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;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// Provides information about the event that occured during use of snippets.
+ /// </summary>
+ public class SnippetEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Gets the reason for deactivation.
+ /// </summary>
+ public DeactivateReason Reason { get; private set; }
+
+ /// <summary>
+ /// Creates a new SnippetEventArgs object, with a DeactivateReason.
+ /// </summary>
+ public SnippetEventArgs(DeactivateReason reason)
+ {
+ this.Reason = reason;
+ }
+ }
+
+ /// <summary>
+ /// Describes the reason for deactivation of a <see cref="SnippetElement" />.
+ /// </summary>
+ public enum DeactivateReason
+ {
+ /// <summary>
+ /// Unknown reason.
+ /// </summary>
+ Unknown,
+ /// <summary>
+ /// Snippet was deleted.
+ /// </summary>
+ Deleted,
+ /// <summary>
+ /// There are no active elements in the snippet.
+ /// </summary>
+ NoActiveElements,
+ /// <summary>
+ /// The SnippetInputHandler was detached.
+ /// </summary>
+ InputHandlerDetached,
+ /// <summary>
+ /// Return was pressed by the user.
+ /// </summary>
+ ReturnPressed,
+ /// <summary>
+ /// Escape was pressed by the user.
+ /// </summary>
+ EscapePressed
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetInputHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetInputHandler.cs
new file mode 100644
index 000000000..6a8de2e88
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetInputHandler.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.Linq;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Windows.Input;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Editing;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ sealed class SnippetInputHandler : TextAreaStackedInputHandler
+ {
+ readonly InsertionContext context;
+
+ public SnippetInputHandler(InsertionContext context)
+ : base(context.TextArea)
+ {
+ this.context = context;
+ }
+
+ public override void Attach()
+ {
+ base.Attach();
+
+ SelectElement(FindNextEditableElement(-1, false));
+ }
+
+ public override void Detach()
+ {
+ base.Detach();
+ context.Deactivate(new SnippetEventArgs(DeactivateReason.InputHandlerDetached));
+ }
+
+ public override void OnPreviewKeyDown(KeyEventArgs e)
+ {
+ base.OnPreviewKeyDown(e);
+ if (e.Key == Key.Escape) {
+ context.Deactivate(new SnippetEventArgs(DeactivateReason.EscapePressed));
+ e.Handled = true;
+ } else if (e.Key == Key.Return) {
+ context.Deactivate(new SnippetEventArgs(DeactivateReason.ReturnPressed));
+ e.Handled = true;
+ } else if (e.Key == Key.Tab) {
+ bool backwards = e.KeyboardDevice.Modifiers == ModifierKeys.Shift;
+ SelectElement(FindNextEditableElement(TextArea.Caret.Offset, backwards));
+ e.Handled = true;
+ }
+ }
+
+ void SelectElement(IActiveElement element)
+ {
+ if (element != null) {
+ TextArea.Selection = Selection.Create(TextArea, element.Segment);
+ TextArea.Caret.Offset = element.Segment.EndOffset;
+ }
+ }
+
+ IActiveElement FindNextEditableElement(int offset, bool backwards)
+ {
+ IEnumerable<IActiveElement> elements = context.ActiveElements.Where(e => e.IsEditable && e.Segment != null);
+ if (backwards) {
+ elements = elements.Reverse();
+ foreach (IActiveElement element in elements) {
+ if (offset > element.Segment.EndOffset)
+ return element;
+ }
+ } else {
+ foreach (IActiveElement element in elements) {
+ if (offset < element.Segment.Offset)
+ return element;
+ }
+ }
+ return elements.FirstOrDefault();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs
new file mode 100644
index 000000000..9bf872f11
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs
@@ -0,0 +1,213 @@
+// 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.Windows;
+using System.Windows.Documents;
+using System.Windows.Media;
+using System.Windows.Threading;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// Text element that is supposed to be replaced by the user.
+ /// Will register an <see cref="IReplaceableActiveElement"/>.
+ /// </summary>
+ [Serializable]
+ public class SnippetReplaceableTextElement : SnippetTextElement
+ {
+ /// <inheritdoc/>
+ public override void Insert(InsertionContext context)
+ {
+ int start = context.InsertionPosition;
+ base.Insert(context);
+ int end = context.InsertionPosition;
+ context.RegisterActiveElement(this, new ReplaceableActiveElement(context, start, end));
+ }
+
+ /// <inheritdoc/>
+ public override Inline ToTextRun()
+ {
+ return new Italic(base.ToTextRun());
+ }
+ }
+
+ /// <summary>
+ /// Interface for active element registered by <see cref="SnippetReplaceableTextElement"/>.
+ /// </summary>
+ public interface IReplaceableActiveElement : IActiveElement
+ {
+ /// <summary>
+ /// Gets the current text inside the element.
+ /// </summary>
+ string Text { get; }
+
+ /// <summary>
+ /// Occurs when the text inside the element changes.
+ /// </summary>
+ event EventHandler TextChanged;
+ }
+
+ sealed class ReplaceableActiveElement : IReplaceableActiveElement, IWeakEventListener
+ {
+ readonly InsertionContext context;
+ readonly int startOffset, endOffset;
+ TextAnchor start, end;
+
+ public ReplaceableActiveElement(InsertionContext context, int startOffset, int endOffset)
+ {
+ this.context = context;
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ }
+
+ void AnchorDeleted(object sender, EventArgs e)
+ {
+ context.Deactivate(new SnippetEventArgs(DeactivateReason.Deleted));
+ }
+
+ public void OnInsertionCompleted()
+ {
+ // anchors must be created in OnInsertionCompleted because they should move only
+ // due to user insertions, not due to insertions of other snippet parts
+ start = context.Document.CreateAnchor(startOffset);
+ start.MovementType = AnchorMovementType.BeforeInsertion;
+ end = context.Document.CreateAnchor(endOffset);
+ end.MovementType = AnchorMovementType.AfterInsertion;
+ start.Deleted += AnchorDeleted;
+ end.Deleted += AnchorDeleted;
+
+ // Be careful with references from the document to the editing/snippet layer - use weak events
+ // to prevent memory leaks when the text area control gets dropped from the UI while the snippet is active.
+ // The InsertionContext will keep us alive as long as the snippet is in interactive mode.
+ TextDocumentWeakEventManager.TextChanged.AddListener(context.Document, this);
+
+ background = new Renderer { Layer = KnownLayer.Background, element = this };
+ foreground = new Renderer { Layer = KnownLayer.Text, element = this };
+ context.TextArea.TextView.BackgroundRenderers.Add(background);
+ context.TextArea.TextView.BackgroundRenderers.Add(foreground);
+ context.TextArea.Caret.PositionChanged += Caret_PositionChanged;
+ Caret_PositionChanged(null, null);
+
+ this.Text = GetText();
+ }
+
+ public void Deactivate(SnippetEventArgs e)
+ {
+ TextDocumentWeakEventManager.TextChanged.RemoveListener(context.Document, this);
+ context.TextArea.TextView.BackgroundRenderers.Remove(background);
+ context.TextArea.TextView.BackgroundRenderers.Remove(foreground);
+ context.TextArea.Caret.PositionChanged -= Caret_PositionChanged;
+ }
+
+ bool isCaretInside;
+
+ void Caret_PositionChanged(object sender, EventArgs e)
+ {
+ ISegment s = this.Segment;
+ if (s != null) {
+ bool newIsCaretInside = s.Contains(context.TextArea.Caret.Offset);
+ if (newIsCaretInside != isCaretInside) {
+ isCaretInside = newIsCaretInside;
+ context.TextArea.TextView.InvalidateLayer(foreground.Layer);
+ }
+ }
+ }
+
+ Renderer background, foreground;
+
+ public string Text { get; private set; }
+
+ string GetText()
+ {
+ if (start.IsDeleted || end.IsDeleted)
+ return string.Empty;
+ else
+ return context.Document.GetText(start.Offset, Math.Max(0, end.Offset - start.Offset));
+ }
+
+ public event EventHandler TextChanged;
+
+ bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
+ {
+ if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) {
+ string newText = GetText();
+ if (this.Text != newText) {
+ this.Text = newText;
+ if (TextChanged != null)
+ TextChanged(this, e);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public bool IsEditable {
+ get { return true; }
+ }
+
+ public ISegment Segment {
+ get {
+ if (start.IsDeleted || end.IsDeleted)
+ return null;
+ else
+ return new SimpleSegment(start.Offset, Math.Max(0, end.Offset - start.Offset));
+ }
+ }
+
+ sealed class Renderer : IBackgroundRenderer
+ {
+ static readonly Brush backgroundBrush = CreateBackgroundBrush();
+ static readonly Pen activeBorderPen = CreateBorderPen();
+
+ static Brush CreateBackgroundBrush()
+ {
+ SolidColorBrush b = new SolidColorBrush(Colors.LimeGreen);
+ b.Opacity = 0.4;
+ b.Freeze();
+ return b;
+ }
+
+ static Pen CreateBorderPen()
+ {
+ Pen p = new Pen(Brushes.Black, 1);
+ p.DashStyle = DashStyles.Dot;
+ p.Freeze();
+ return p;
+ }
+
+ internal ReplaceableActiveElement element;
+
+ public KnownLayer Layer { get; set; }
+
+ public void Draw(TextView textView, System.Windows.Media.DrawingContext drawingContext)
+ {
+ ISegment s = element.Segment;
+ if (s != null) {
+ BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
+ geoBuilder.AlignToMiddleOfPixels = true;
+ if (Layer == KnownLayer.Background) {
+ geoBuilder.AddSegment(textView, s);
+ drawingContext.DrawGeometry(backgroundBrush, null, geoBuilder.CreateGeometry());
+ } else {
+ // draw foreground only if active
+ if (element.isCaretInside) {
+ geoBuilder.AddSegment(textView, s);
+ foreach (BoundActiveElement boundElement in element.context.ActiveElements.OfType<BoundActiveElement>()) {
+ if (boundElement.targetElement == element) {
+ geoBuilder.AddSegment(textView, boundElement.Segment);
+ geoBuilder.CloseFigure();
+ }
+ }
+ drawingContext.DrawGeometry(null, activeBorderPen, geoBuilder.CreateGeometry());
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetSelectionElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetSelectionElement.cs
new file mode 100644
index 000000000..4c073094b
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetSelectionElement.cs
@@ -0,0 +1,45 @@
+// 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.Text;
+using System.Windows.Input;
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// Inserts the previously selected text at the selection marker.
+ /// </summary>
+ [Serializable]
+ public class SnippetSelectionElement : SnippetElement
+ {
+ /// <summary>
+ /// Gets/Sets the new indentation of the selected text.
+ /// </summary>
+ public int Indentation { get; set; }
+
+ /// <inheritdoc/>
+ public override void Insert(InsertionContext context)
+ {
+ StringBuilder tabString = new StringBuilder();
+
+ for (int i = 0; i < Indentation; i++) {
+ tabString.Append(context.Tab);
+ }
+
+ string indent = tabString.ToString();
+
+ string text = context.SelectedText.TrimStart(' ', '\t');
+
+ text = text.Replace(context.LineTerminator,
+ context.LineTerminator + indent);
+
+ context.Document.Insert(context.InsertionPosition, text);
+ context.InsertionPosition += text.Length;
+
+ if (string.IsNullOrEmpty(context.SelectedText))
+ SnippetCaretElement.SetCaret(context);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetTextElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetTextElement.cs
new file mode 100644
index 000000000..decc18119
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetTextElement.cs
@@ -0,0 +1,39 @@
+// 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.Documents;
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Snippets
+{
+ /// <summary>
+ /// Represents a text element in a snippet.
+ /// </summary>
+ [Serializable]
+ public class SnippetTextElement : SnippetElement
+ {
+ string text;
+
+ /// <summary>
+ /// The text to be inserted.
+ /// </summary>
+ public string Text {
+ get { return text; }
+ set { text = value; }
+ }
+
+ /// <inheritdoc/>
+ public override void Insert(InsertionContext context)
+ {
+ if (text != null)
+ context.InsertText(text);
+ }
+
+ /// <inheritdoc/>
+ public override Inline ToTextRun()
+ {
+ return new Run(text ?? string.Empty);
+ }
+ }
+}