aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/AbstractFoldingStrategy.cs30
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/BraceFoldingStrategy.cs159
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingElementGenerator.cs194
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingManager.cs388
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMargin.cs343
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMarginMarker.cs90
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingSection.cs186
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/NewFolding.cs70
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/XmlFoldingStrategy.cs215
9 files changed, 1675 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/AbstractFoldingStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/AbstractFoldingStrategy.cs
new file mode 100644
index 000000000..42eddba47
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/AbstractFoldingStrategy.cs
@@ -0,0 +1,30 @@
+// 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;
+using System.Collections.Generic;
+
+namespace Tango.Scripting.Editors.Folding
+{
+ /// <summary>
+ /// Base class for folding strategies.
+ /// </summary>
+ public abstract class AbstractFoldingStrategy
+ {
+ /// <summary>
+ /// Create <see cref="NewFolding"/>s for the specified document and updates the folding manager with them.
+ /// </summary>
+ public void UpdateFoldings(FoldingManager manager, TextDocument document)
+ {
+ int firstErrorOffset;
+ IEnumerable<NewFolding> foldings = CreateNewFoldings(document, out firstErrorOffset);
+ manager.UpdateFoldings(foldings, firstErrorOffset);
+ }
+
+ /// <summary>
+ /// Create <see cref="NewFolding"/>s for the specified document.
+ /// </summary>
+ public abstract IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset);
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/BraceFoldingStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/BraceFoldingStrategy.cs
new file mode 100644
index 000000000..0b26184ee
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/BraceFoldingStrategy.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Folding
+{
+ /// <summary>
+ /// Allows producing foldings from a document based on braces.
+ /// </summary>
+ public class BraceFoldingStrategy
+ {
+ private class FoldingDefinition
+ {
+ public String Open { get; set; }
+ public String Close { get; set; }
+
+ public FoldingDefinition(String open, String close)
+ {
+ Open = open;
+ Close = close;
+ }
+ }
+
+ private class FoldingOffset
+ {
+ public FoldingDefinition Definition { get; set; }
+ public int Offset { get; set; }
+ }
+
+ private List<FoldingDefinition> foldings = new List<FoldingDefinition>()
+ {
+ new FoldingDefinition("namespace","}"),
+ new FoldingDefinition("public","}"),
+ new FoldingDefinition("private","}"),
+ new FoldingDefinition("internal","}"),
+ new FoldingDefinition("if","}"),
+ new FoldingDefinition("class","}"),
+ new FoldingDefinition("while","}"),
+ new FoldingDefinition("static void","}"),
+ new FoldingDefinition("/// <summary>","/// </summary>"),
+ };
+
+ /// <summary>
+ /// Gets/Sets the opening brace. The default value is '{'.
+ /// </summary>
+ public char OpeningBrace { get; set; }
+
+ /// <summary>
+ /// Gets/Sets the closing brace. The default value is '}'.
+ /// </summary>
+ public char ClosingBrace { get; set; }
+
+ /// <summary>
+ /// Creates a new BraceFoldingStrategy.
+ /// </summary>
+ public BraceFoldingStrategy()
+ {
+ this.OpeningBrace = '{';
+ this.ClosingBrace = '}';
+ }
+
+ public void UpdateFoldings(FoldingManager manager, TextDocument document)
+ {
+ int firstErrorOffset;
+ IEnumerable<NewFolding> newFoldings = CreateNewFoldings(document, out firstErrorOffset);
+ manager.UpdateFoldings(newFoldings, firstErrorOffset);
+ }
+
+ /// <summary>
+ /// Create <see cref="NewFolding"/>s for the specified document.
+ /// </summary>
+ public IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset)
+ {
+ firstErrorOffset = -1;
+ return CreateNewFoldings(document);
+ }
+
+ /// <summary>
+ /// Create <see cref="NewFolding"/>s for the specified document.
+ /// </summary>
+ public IEnumerable<NewFolding> CreateNewFoldings(ITextSource document)
+ {
+ List<NewFolding> newFoldings = new List<NewFolding>();
+
+ Stack<FoldingOffset> startOffsets = new Stack<FoldingOffset>();
+ int lastNewLineOffset = 0;
+ char openingBrace = this.OpeningBrace;
+ char closingBrace = this.ClosingBrace;
+
+ var doc = document as TextDocument;
+
+ String current_line = String.Empty;
+
+ foreach (var line in doc.Lines)
+ {
+ String lineTextFull = doc.GetText(line);
+ String lineText = lineTextFull.TrimStart('\t', ' ').TrimEnd('\t', ' ');
+
+ var open_definition = foldings.SingleOrDefault(x => lineText.StartsWith(x.Open + " ") || lineText == x.Open);
+
+ if (open_definition != null)
+ {
+ current_line = lineTextFull;
+ startOffsets.Push(new FoldingOffset()
+ {
+ Definition = open_definition,
+ Offset = line.EndOffset
+ });
+ }
+ else if (foldings.Any(x => lineText.EndsWith(x.Close)))
+ {
+ if (startOffsets.Count > 0)
+ {
+ var startOffset = startOffsets.Pop();
+ //if (startOffset < lastNewLineOffset)
+ //{
+ newFoldings.Add(new NewFolding(startOffset.Offset, Math.Min(line.EndOffset, doc.TextLength))
+ {
+ //Name = current_line,
+ });
+ }
+ //}
+ }
+ else if (lineText.EndsWith("\n") || lineText.EndsWith("\r"))
+ {
+ lastNewLineOffset = line.Offset + 1;
+ }
+ }
+
+ //for (int i = 0; i < document.TextLength; i++)
+ //{
+ // char c = document.GetCharAt(i);
+ // if (c == openingBrace)
+ // {
+ // startOffsets.Push(i);
+ // }
+ // else if (c == closingBrace && startOffsets.Count > 0)
+ // {
+ // int startOffset = startOffsets.Pop();
+ // // don't fold if opening and closing brace are on the same line
+ // if (startOffset < lastNewLineOffset)
+ // {
+ // newFoldings.Add(new NewFolding(startOffset, i + 1));
+ // }
+ // }
+ // else if (c == '\n' || c == '\r')
+ // {
+ // lastNewLineOffset = i + 1;
+ // }
+ //}
+
+ newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset));
+ return newFoldings;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingElementGenerator.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingElementGenerator.cs
new file mode 100644
index 000000000..76a1da101
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingElementGenerator.cs
@@ -0,0 +1,194 @@
+// 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 System.Windows.Media;
+using System.Windows.Media.TextFormatting;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Folding
+{
+ /// <summary>
+ /// A <see cref="VisualLineElementGenerator"/> that produces line elements for folded <see cref="FoldingSection"/>s.
+ /// </summary>
+ public sealed class FoldingElementGenerator : VisualLineElementGenerator, ITextViewConnect
+ {
+ readonly List<TextView> textViews = new List<TextView>();
+ FoldingManager foldingManager;
+
+ #region FoldingManager property / connecting with TextView
+ /// <summary>
+ /// Gets/Sets the folding manager from which the foldings should be shown.
+ /// </summary>
+ public FoldingManager FoldingManager {
+ get {
+ return foldingManager;
+ }
+ set {
+ if (foldingManager != value) {
+ if (foldingManager != null) {
+ foreach (TextView v in textViews)
+ foldingManager.RemoveFromTextView(v);
+ }
+ foldingManager = value;
+ if (foldingManager != null) {
+ foreach (TextView v in textViews)
+ foldingManager.AddToTextView(v);
+ }
+ }
+ }
+ }
+
+ void ITextViewConnect.AddToTextView(TextView textView)
+ {
+ textViews.Add(textView);
+ if (foldingManager != null)
+ foldingManager.AddToTextView(textView);
+ }
+
+ void ITextViewConnect.RemoveFromTextView(TextView textView)
+ {
+ textViews.Remove(textView);
+ if (foldingManager != null)
+ foldingManager.RemoveFromTextView(textView);
+ }
+ #endregion
+
+ /// <inheritdoc/>
+ public override void StartGeneration(ITextRunConstructionContext context)
+ {
+ base.StartGeneration(context);
+ if (foldingManager != null) {
+ if (!foldingManager.textViews.Contains(context.TextView))
+ throw new ArgumentException("Invalid TextView");
+ if (context.Document != foldingManager.document)
+ throw new ArgumentException("Invalid document");
+ }
+ }
+
+ /// <inheritdoc/>
+ public override int GetFirstInterestedOffset(int startOffset)
+ {
+ if (foldingManager != null) {
+ foreach (FoldingSection fs in foldingManager.GetFoldingsContaining(startOffset)) {
+ // Test whether we're currently within a folded folding (that didn't just end).
+ // If so, create the fold marker immediately.
+ // This is necessary if the actual beginning of the fold marker got skipped due to another VisualElementGenerator.
+ if (fs.IsFolded && fs.EndOffset > startOffset) {
+ //return startOffset;
+ }
+ }
+ return foldingManager.GetNextFoldedFoldingStart(startOffset);
+ } else {
+ return -1;
+ }
+ }
+
+ /// <inheritdoc/>
+ public override VisualLineElement ConstructElement(int offset)
+ {
+ if (foldingManager == null)
+ return null;
+ int foldedUntil = -1;
+ FoldingSection foldingSection = null;
+ foreach (FoldingSection fs in foldingManager.GetFoldingsContaining(offset)) {
+ if (fs.IsFolded) {
+ if (fs.EndOffset > foldedUntil) {
+ foldedUntil = fs.EndOffset;
+ foldingSection = fs;
+ }
+ }
+ }
+ if (foldedUntil > offset && foldingSection != null) {
+ // Handle overlapping foldings: if there's another folded folding
+ // (starting within the foldingSection) that continues after the end of the folded section,
+ // then we'll extend our fold element to cover that overlapping folding.
+ bool foundOverlappingFolding;
+ do {
+ foundOverlappingFolding = false;
+ foreach (FoldingSection fs in FoldingManager.GetFoldingsContaining(foldedUntil)) {
+ if (fs.IsFolded && fs.EndOffset > foldedUntil) {
+ foldedUntil = fs.EndOffset;
+ foundOverlappingFolding = true;
+ }
+ }
+ } while (foundOverlappingFolding);
+
+ string title = foldingSection.Title;
+ if (string.IsNullOrEmpty(title))
+ title = "...";
+ var p = new VisualLineElementTextRunProperties(CurrentContext.GlobalTextRunProperties);
+ p.SetForegroundBrush(textBrush);
+ var textFormatter = TextFormatterFactory.Create(CurrentContext.TextView);
+ var text = FormattedTextElement.PrepareText(textFormatter, title, p);
+ return new FoldingLineElement(foldingSection, text, foldedUntil - offset) { textBrush = textBrush };
+ } else {
+ return null;
+ }
+ }
+
+ sealed class FoldingLineElement : FormattedTextElement
+ {
+ readonly FoldingSection fs;
+
+ internal Brush textBrush;
+
+ public FoldingLineElement(FoldingSection fs, TextLine text, int documentLength) : base(text, documentLength)
+ {
+ this.fs = fs;
+ }
+
+ public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
+ {
+ return new FoldingLineTextRun(this, this.TextRunProperties) { textBrush = textBrush };
+ }
+
+ protected internal override void OnMouseDown(MouseButtonEventArgs e)
+ {
+ if (e.ClickCount == 2 && e.ChangedButton == MouseButton.Left) {
+ fs.IsFolded = false;
+ e.Handled = true;
+ } else {
+ base.OnMouseDown(e);
+ }
+ }
+ }
+
+ sealed class FoldingLineTextRun : FormattedTextRun
+ {
+ internal Brush textBrush;
+
+ public FoldingLineTextRun(FormattedTextElement element, TextRunProperties properties)
+ : base(element, properties)
+ {
+ }
+
+ public override void Draw(DrawingContext drawingContext, Point origin, bool rightToLeft, bool sideways)
+ {
+ var metrics = Format(double.PositiveInfinity);
+ Rect r = new Rect(origin.X, origin.Y - metrics.Baseline, metrics.Width, metrics.Height);
+ drawingContext.DrawRectangle(null, new Pen(textBrush, 1), r);
+ base.Draw(drawingContext, origin, rightToLeft, sideways);
+ }
+ }
+
+ /// <summary>
+ /// Default brush for folding element text. Value: Brushes.Gray
+ /// </summary>
+ public static readonly Brush DefaultTextBrush = Brushes.Gray;
+
+ static Brush textBrush = DefaultTextBrush;
+
+ /// <summary>
+ /// Gets/sets the brush used for folding element text.
+ /// </summary>
+ public static Brush TextBrush {
+ get { return textBrush; }
+ set { textBrush = value; }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingManager.cs
new file mode 100644
index 000000000..4035928b4
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingManager.cs
@@ -0,0 +1,388 @@
+// 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.Linq;
+using System.Windows;
+using System.Windows.Threading;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Editing;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Folding
+{
+ /// <summary>
+ /// Stores a list of foldings for a specific TextView and TextDocument.
+ /// </summary>
+ public class FoldingManager : IWeakEventListener
+ {
+ internal readonly TextDocument document;
+
+ internal readonly List<TextView> textViews = new List<TextView>();
+ readonly TextSegmentCollection<FoldingSection> foldings;
+
+ #region Constructor
+ /// <summary>
+ /// Creates a new FoldingManager instance.
+ /// </summary>
+ public FoldingManager(TextDocument document)
+ {
+ if (document == null)
+ throw new ArgumentNullException("document");
+ this.document = document;
+ this.foldings = new TextSegmentCollection<FoldingSection>();
+ document.VerifyAccess();
+ TextDocumentWeakEventManager.Changed.AddListener(document, this);
+ }
+
+ /// <summary>
+ /// Creates a new FoldingManager instance.
+ /// </summary>
+ [Obsolete("Use the (TextDocument) constructor instead.")]
+ public FoldingManager(TextView textView, TextDocument document)
+ : this(document)
+ {
+ }
+ #endregion
+
+ #region ReceiveWeakEvent
+ /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
+ protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
+ {
+ if (managerType == typeof(TextDocumentWeakEventManager.Changed)) {
+ OnDocumentChanged((DocumentChangeEventArgs)e);
+ return true;
+ }
+ return false;
+ }
+
+ bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
+ {
+ return ReceiveWeakEvent(managerType, sender, e);
+ }
+
+ void OnDocumentChanged(DocumentChangeEventArgs e)
+ {
+ foldings.UpdateOffsets(e);
+ int newEndOffset = e.Offset + e.InsertionLength;
+ // extend end offset to the end of the line (including delimiter)
+ var endLine = document.GetLineByOffset(newEndOffset);
+ newEndOffset = endLine.Offset + endLine.TotalLength;
+ foreach (var affectedFolding in foldings.FindOverlappingSegments(e.Offset, newEndOffset - e.Offset)) {
+ if (affectedFolding.Length == 0) {
+ RemoveFolding(affectedFolding);
+ } else {
+ affectedFolding.ValidateCollapsedLineSections();
+ }
+ }
+ }
+ #endregion
+
+ #region Manage TextViews
+ internal void AddToTextView(TextView textView)
+ {
+ if (textView == null || textViews.Contains(textView))
+ throw new ArgumentException();
+ textViews.Add(textView);
+ foreach (FoldingSection fs in foldings) {
+ if (fs.collapsedSections != null) {
+ Array.Resize(ref fs.collapsedSections, textViews.Count);
+ fs.ValidateCollapsedLineSections();
+ }
+ }
+ }
+
+ internal void RemoveFromTextView(TextView textView)
+ {
+ int pos = textViews.IndexOf(textView);
+ if (pos < 0)
+ throw new ArgumentException();
+ textViews.RemoveAt(pos);
+ foreach (FoldingSection fs in foldings) {
+ if (fs.collapsedSections != null) {
+ var c = new CollapsedLineSection[textViews.Count];
+ Array.Copy(fs.collapsedSections, 0, c, 0, pos);
+ fs.collapsedSections[pos].Uncollapse();
+ Array.Copy(fs.collapsedSections, pos + 1, c, pos, c.Length - pos);
+ fs.collapsedSections = c;
+ }
+ }
+ }
+
+ internal void Redraw()
+ {
+ foreach (TextView textView in textViews)
+ textView.Redraw();
+ }
+
+ internal void Redraw(FoldingSection fs)
+ {
+ foreach (TextView textView in textViews)
+ textView.Redraw(fs);
+ }
+ #endregion
+
+ #region Create / Remove / Clear
+ /// <summary>
+ /// Creates a folding for the specified text section.
+ /// </summary>
+ public FoldingSection CreateFolding(int startOffset, int endOffset)
+ {
+ if (startOffset >= endOffset)
+ throw new ArgumentException("startOffset must be less than endOffset");
+ if (startOffset < 0 || endOffset > document.TextLength)
+ throw new ArgumentException("Folding must be within document boundary");
+ FoldingSection fs = new FoldingSection(this, startOffset, endOffset);
+ foldings.Add(fs);
+ Redraw(fs);
+ return fs;
+ }
+
+ /// <summary>
+ /// Removes a folding section from this manager.
+ /// </summary>
+ public void RemoveFolding(FoldingSection fs)
+ {
+ if (fs == null)
+ throw new ArgumentNullException("fs");
+ fs.IsFolded = false;
+ foldings.Remove(fs);
+ Redraw(fs);
+ }
+
+ /// <summary>
+ /// Removes all folding sections.
+ /// </summary>
+ public void Clear()
+ {
+ document.VerifyAccess();
+ foreach (FoldingSection s in foldings)
+ s.IsFolded = false;
+ foldings.Clear();
+ Redraw();
+ }
+ #endregion
+
+ #region Get...Folding
+ /// <summary>
+ /// Gets all foldings in this manager.
+ /// The foldings are returned sorted by start offset;
+ /// for multiple foldings at the same offset the order is undefined.
+ /// </summary>
+ public IEnumerable<FoldingSection> AllFoldings {
+ get { return foldings; }
+ }
+
+ /// <summary>
+ /// Gets the first offset greater or equal to <paramref name="startOffset"/> where a folded folding starts.
+ /// Returns -1 if there are no foldings after <paramref name="startOffset"/>.
+ /// </summary>
+ public int GetNextFoldedFoldingStart(int startOffset)
+ {
+ FoldingSection fs = foldings.FindFirstSegmentWithStartAfter(startOffset);
+ while (fs != null && !fs.IsFolded)
+ fs = foldings.GetNextSegment(fs);
+ return fs != null ? fs.StartOffset : -1;
+ }
+
+ /// <summary>
+ /// Gets the first folding with a <see cref="TextSegment.StartOffset"/> greater or equal to
+ /// <paramref name="startOffset"/>.
+ /// Returns null if there are no foldings after <paramref name="startOffset"/>.
+ /// </summary>
+ public FoldingSection GetNextFolding(int startOffset)
+ {
+ // TODO: returns the longest folding instead of any folding at the first position after startOffset
+ return foldings.FindFirstSegmentWithStartAfter(startOffset);
+ }
+
+ /// <summary>
+ /// Gets all foldings that start exactly at <paramref name="startOffset"/>.
+ /// </summary>
+ public ReadOnlyCollection<FoldingSection> GetFoldingsAt(int startOffset)
+ {
+ List<FoldingSection> result = new List<FoldingSection>();
+ FoldingSection fs = foldings.FindFirstSegmentWithStartAfter(startOffset);
+ while (fs != null && fs.StartOffset == startOffset) {
+ result.Add(fs);
+ fs = foldings.GetNextSegment(fs);
+ }
+ return result.AsReadOnly();
+ }
+
+ /// <summary>
+ /// Gets all foldings that contain <paramref name="offset" />.
+ /// </summary>
+ public ReadOnlyCollection<FoldingSection> GetFoldingsContaining(int offset)
+ {
+ return foldings.FindSegmentsContaining(offset);
+ }
+ #endregion
+
+ #region UpdateFoldings
+ /// <summary>
+ /// Updates the foldings in this <see cref="FoldingManager"/> using the given new foldings.
+ /// This method will try to detect which new foldings correspond to which existing foldings; and will keep the state
+ /// (<see cref="FoldingSection.IsFolded"/>) for existing foldings.
+ /// </summary>
+ /// <param name="newFoldings">The new set of foldings. These must be sorted by starting offset.</param>
+ /// <param name="firstErrorOffset">The first position of a parse error. Existing foldings starting after
+ /// this offset will be kept even if they don't appear in <paramref name="newFoldings"/>.
+ /// Use -1 for this parameter if there were no parse errors.</param>
+ public void UpdateFoldings(IEnumerable<NewFolding> newFoldings, int firstErrorOffset)
+ {
+ if (newFoldings == null)
+ throw new ArgumentNullException("newFoldings");
+
+ if (firstErrorOffset < 0)
+ firstErrorOffset = int.MaxValue;
+
+ var oldFoldings = this.AllFoldings.ToArray();
+ int oldFoldingIndex = 0;
+ int previousStartOffset = 0;
+ // merge new foldings into old foldings so that sections keep being collapsed
+ // both oldFoldings and newFoldings are sorted by start offset
+ foreach (NewFolding newFolding in newFoldings) {
+ // ensure newFoldings are sorted correctly
+ if (newFolding.StartOffset < previousStartOffset)
+ throw new ArgumentException("newFoldings must be sorted by start offset");
+ previousStartOffset = newFolding.StartOffset;
+
+ int startOffset = newFolding.StartOffset.CoerceValue(0, document.TextLength);
+ int endOffset = newFolding.EndOffset.CoerceValue(0, document.TextLength);
+
+ if (newFolding.StartOffset == newFolding.EndOffset)
+ continue; // ignore zero-length foldings
+
+ // remove old foldings that were skipped
+ while (oldFoldingIndex < oldFoldings.Length && newFolding.StartOffset > oldFoldings[oldFoldingIndex].StartOffset) {
+ this.RemoveFolding(oldFoldings[oldFoldingIndex++]);
+ }
+ FoldingSection section;
+ // reuse current folding if its matching:
+ if (oldFoldingIndex < oldFoldings.Length && newFolding.StartOffset == oldFoldings[oldFoldingIndex].StartOffset) {
+ section = oldFoldings[oldFoldingIndex++];
+ section.Length = newFolding.EndOffset - newFolding.StartOffset;
+ } else {
+ // no matching current folding; create a new one:
+ section = this.CreateFolding(newFolding.StartOffset, newFolding.EndOffset);
+ // auto-close #regions only when opening the document
+ section.IsFolded = newFolding.DefaultClosed;
+ section.Tag = newFolding;
+ }
+ section.Title = newFolding.Name;
+ }
+ // remove all outstanding old foldings:
+ while (oldFoldingIndex < oldFoldings.Length) {
+ FoldingSection oldSection = oldFoldings[oldFoldingIndex++];
+ if (oldSection.StartOffset >= firstErrorOffset)
+ break;
+ this.RemoveFolding(oldSection);
+ }
+ }
+ #endregion
+
+ #region Install
+ /// <summary>
+ /// Adds Folding support to the specified text area.
+ /// Warning: The folding manager is only valid for the text area's current document. The folding manager
+ /// must be uninstalled before the text area is bound to a different document.
+ /// </summary>
+ /// <returns>The <see cref="FoldingManager"/> that manages the list of foldings inside the text area.</returns>
+ public static FoldingManager Install(TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ return new FoldingManagerInstallation(textArea);
+ }
+
+ /// <summary>
+ /// Uninstalls the folding manager.
+ /// </summary>
+ /// <exception cref="ArgumentException">The specified manager was not created using <see cref="Install"/>.</exception>
+ public static void Uninstall(FoldingManager manager)
+ {
+ if (manager == null)
+ throw new ArgumentNullException("manager");
+ FoldingManagerInstallation installation = manager as FoldingManagerInstallation;
+ if (installation != null) {
+ installation.Uninstall();
+ } else {
+ throw new ArgumentException("FoldingManager was not created using FoldingManager.Install");
+ }
+ }
+
+ sealed class FoldingManagerInstallation : FoldingManager
+ {
+ TextArea textArea;
+ FoldingMargin margin;
+ FoldingElementGenerator generator;
+
+ public FoldingManagerInstallation(TextArea textArea) : base(textArea.Document)
+ {
+ this.textArea = textArea;
+ margin = new FoldingMargin() { FoldingManager = this };
+ generator = new FoldingElementGenerator() { FoldingManager = this };
+ textArea.LeftMargins.Add(margin);
+ textArea.TextView.Services.AddService(typeof(FoldingManager), this);
+ // HACK: folding only works correctly when it has highest priority
+ textArea.TextView.ElementGenerators.Insert(0, generator);
+ textArea.Caret.PositionChanged += textArea_Caret_PositionChanged;
+ }
+
+ /*
+ void DemoMode()
+ {
+ foldingGenerator = new FoldingElementGenerator() { FoldingManager = fm };
+ foldingMargin = new FoldingMargin { FoldingManager = fm };
+ foldingMarginBorder = new Border {
+ Child = foldingMargin,
+ Background = new LinearGradientBrush(Colors.White, Colors.Transparent, 0)
+ };
+ foldingMarginBorder.SizeChanged += UpdateTextViewClip;
+ textEditor.TextArea.TextView.ElementGenerators.Add(foldingGenerator);
+ textEditor.TextArea.LeftMargins.Add(foldingMarginBorder);
+ }
+
+ void UpdateTextViewClip(object sender, SizeChangedEventArgs e)
+ {
+ textEditor.TextArea.TextView.Clip = new RectangleGeometry(
+ new Rect(-foldingMarginBorder.ActualWidth,
+ 0,
+ textEditor.TextArea.TextView.ActualWidth + foldingMarginBorder.ActualWidth,
+ textEditor.TextArea.TextView.ActualHeight));
+ }
+ */
+
+ public void Uninstall()
+ {
+ Clear();
+ if (textArea != null) {
+ textArea.Caret.PositionChanged -= textArea_Caret_PositionChanged;
+ textArea.LeftMargins.Remove(margin);
+ textArea.TextView.ElementGenerators.Remove(generator);
+ textArea.TextView.Services.RemoveService(typeof(FoldingManager));
+ margin = null;
+ generator = null;
+ textArea = null;
+ }
+ }
+
+ void textArea_Caret_PositionChanged(object sender, EventArgs e)
+ {
+ // Expand Foldings when Caret is moved into them.
+ int caretOffset = textArea.Caret.Offset;
+ foreach (FoldingSection s in GetFoldingsContaining(caretOffset)) {
+ if (s.IsFolded && s.StartOffset < caretOffset && caretOffset < s.EndOffset) {
+ s.IsFolded = false;
+ }
+ }
+ }
+ }
+ #endregion
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMargin.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMargin.cs
new file mode 100644
index 000000000..c3d03646c
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMargin.cs
@@ -0,0 +1,343 @@
+// 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.Controls;
+using System.Windows.Media;
+using System.Windows.Media.TextFormatting;
+using Tango.Scripting.Editors.Editing;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Folding
+{
+ /// <summary>
+ /// A margin that shows markers for foldings and allows to expand/collapse the foldings.
+ /// </summary>
+ public class FoldingMargin : AbstractMargin
+ {
+ /// <summary>
+ /// Gets/Sets the folding manager from which the foldings should be shown.
+ /// </summary>
+ public FoldingManager FoldingManager { get; set; }
+
+ internal const double SizeFactor = Constants.PixelPerPoint;
+
+ #region Brushes
+ /// <summary>
+ /// FoldingMarkerBrush dependency property.
+ /// </summary>
+ public static readonly DependencyProperty FoldingMarkerBrushProperty =
+ DependencyProperty.RegisterAttached("FoldingMarkerBrush", typeof(Brush), typeof(FoldingMargin),
+ new FrameworkPropertyMetadata(Brushes.Gainsboro));
+
+ /// <summary>
+ /// Gets/sets the Brush used for displaying the lines of folding markers.
+ /// </summary>
+ public Brush FoldingMarkerBrush {
+ get { return (Brush)GetValue(FoldingMarkerBrushProperty); }
+ set { SetValue(FoldingMarkerBrushProperty, value); }
+ }
+
+ /// <summary>
+ /// FoldingMarkerBackgroundBrush dependency property.
+ /// </summary>
+ public static readonly DependencyProperty FoldingMarkerBackgroundBrushProperty =
+ DependencyProperty.RegisterAttached("FoldingMarkerBackgroundBrush", typeof(Brush), typeof(FoldingMargin),
+ new FrameworkPropertyMetadata(Brushes.Transparent));
+
+ /// <summary>
+ /// Gets/sets the Brush used for displaying the background of folding markers.
+ /// </summary>
+ public Brush FoldingMarkerBackgroundBrush {
+ get { return (Brush)GetValue(FoldingMarkerBackgroundBrushProperty); }
+ set { SetValue(FoldingMarkerBackgroundBrushProperty, value); }
+ }
+
+ /// <summary>
+ /// SelectedFoldingMarkerBrush dependency property.
+ /// </summary>
+ public static readonly DependencyProperty SelectedFoldingMarkerBrushProperty =
+ DependencyProperty.RegisterAttached("SelectedFoldingMarkerBrush",
+ typeof(Brush), typeof(FoldingMargin),
+ new FrameworkPropertyMetadata(Brushes.Gainsboro));
+
+ /// <summary>
+ /// Gets/sets the Brush used for displaying the lines of selected folding markers.
+ /// </summary>
+ public Brush SelectedFoldingMarkerBrush {
+ get { return (Brush)GetValue(SelectedFoldingMarkerBrushProperty); }
+ set { SetValue(SelectedFoldingMarkerBrushProperty, value); }
+ }
+
+ /// <summary>
+ /// SelectedFoldingMarkerBackgroundBrush dependency property.
+ /// </summary>
+ public static readonly DependencyProperty SelectedFoldingMarkerBackgroundBrushProperty =
+ DependencyProperty.RegisterAttached("SelectedFoldingMarkerBackgroundBrush",
+ typeof(Brush), typeof(FoldingMargin),
+ new FrameworkPropertyMetadata(Brushes.Transparent));
+
+ /// <summary>
+ /// Gets/sets the Brush used for displaying the background of selected folding markers.
+ /// </summary>
+ public Brush SelectedFoldingMarkerBackgroundBrush {
+ get { return (Brush)GetValue(SelectedFoldingMarkerBackgroundBrushProperty); }
+ set { SetValue(SelectedFoldingMarkerBackgroundBrushProperty, value); }
+ }
+
+ static void OnUpdateBrushes(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ FoldingMargin m = null;
+ if (d is FoldingMargin)
+ m = (FoldingMargin)d;
+ else if (d is TextEditor)
+ m = ((TextEditor)d).TextArea.LeftMargins.FirstOrDefault(c => c is FoldingMargin) as FoldingMargin;
+ if (m == null) return;
+ if (e.Property.Name == FoldingMarkerBrushProperty.Name)
+ m.foldingControlPen = MakeFrozenPen((Brush)e.NewValue);
+ if (e.Property.Name == SelectedFoldingMarkerBrushProperty.Name)
+ m.selectedFoldingControlPen = MakeFrozenPen((Brush)e.NewValue);
+ }
+ #endregion
+
+ /// <inheritdoc/>
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ foreach (FoldingMarginMarker m in markers) {
+ m.Measure(availableSize);
+ }
+ double width = SizeFactor * (double)GetValue(TextBlock.FontSizeProperty);
+ return new Size(PixelSnapHelpers.RoundToOdd(width, PixelSnapHelpers.GetPixelSize(this).Width), 0);
+ }
+
+ /// <inheritdoc/>
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ Size pixelSize = PixelSnapHelpers.GetPixelSize(this);
+ foreach (FoldingMarginMarker m in markers) {
+ int visualColumn = m.VisualLine.GetVisualColumn(m.FoldingSection.StartOffset - m.VisualLine.FirstDocumentLine.Offset);
+ TextLine textLine = m.VisualLine.GetTextLine(visualColumn);
+ double yPos = m.VisualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextMiddle) - TextView.VerticalOffset;
+ yPos -= m.DesiredSize.Height / 2;
+ double xPos = (finalSize.Width - m.DesiredSize.Width) / 2;
+ m.Arrange(new Rect(PixelSnapHelpers.Round(new Point(xPos, yPos), pixelSize), m.DesiredSize));
+ }
+ return base.ArrangeOverride(finalSize);
+ }
+
+ /// <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;
+ }
+ TextViewVisualLinesChanged(null, null);
+ }
+
+ List<FoldingMarginMarker> markers = new List<FoldingMarginMarker>();
+
+ void TextViewVisualLinesChanged(object sender, EventArgs e)
+ {
+ foreach (FoldingMarginMarker m in markers) {
+ RemoveVisualChild(m);
+ }
+ markers.Clear();
+ InvalidateVisual();
+ if (TextView != null && FoldingManager != null && TextView.VisualLinesValid) {
+ foreach (VisualLine line in TextView.VisualLines) {
+ FoldingSection fs = FoldingManager.GetNextFolding(line.FirstDocumentLine.Offset);
+ if (fs == null)
+ continue;
+ if (fs.StartOffset <= line.LastDocumentLine.Offset + line.LastDocumentLine.Length) {
+ FoldingMarginMarker m = new FoldingMarginMarker {
+ IsExpanded = !fs.IsFolded,
+ VisualLine = line,
+ FoldingSection = fs
+ };
+
+ markers.Add(m);
+ AddVisualChild(m);
+
+ m.IsMouseDirectlyOverChanged += delegate { InvalidateVisual(); };
+
+ InvalidateMeasure();
+ continue;
+ }
+ }
+ }
+ }
+
+ /// <inheritdoc/>
+ protected override int VisualChildrenCount {
+ get { return markers.Count; }
+ }
+
+ /// <inheritdoc/>
+ protected override Visual GetVisualChild(int index)
+ {
+ return markers[index];
+ }
+
+ Pen foldingControlPen = MakeFrozenPen((Brush)FoldingMarkerBrushProperty.DefaultMetadata.DefaultValue);
+ Pen selectedFoldingControlPen = MakeFrozenPen((Brush)SelectedFoldingMarkerBrushProperty.DefaultMetadata.DefaultValue);
+
+ static Pen MakeFrozenPen(Brush brush)
+ {
+ Pen pen = new Pen(brush, 1);
+ pen.Freeze();
+ return pen;
+ }
+
+ /// <inheritdoc/>
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ if (TextView == null || !TextView.VisualLinesValid)
+ return;
+ if (TextView.VisualLines.Count == 0 || FoldingManager == null)
+ return;
+
+ var allTextLines = TextView.VisualLines.SelectMany(vl => vl.TextLines).ToList();
+ Pen[] colors = new Pen[allTextLines.Count + 1];
+ Pen[] endMarker = new Pen[allTextLines.Count];
+
+ CalculateFoldLinesForFoldingsActiveAtStart(allTextLines, colors, endMarker);
+ CalculateFoldLinesForMarkers(allTextLines, colors, endMarker);
+ DrawFoldLines(drawingContext, colors, endMarker);
+
+ base.OnRender(drawingContext);
+ }
+
+ /// <summary>
+ /// Calculates fold lines for all folding sections that start in front of the current view
+ /// and run into the current view.
+ /// </summary>
+ void CalculateFoldLinesForFoldingsActiveAtStart(List<TextLine> allTextLines, Pen[] colors, Pen[] endMarker)
+ {
+ int viewStartOffset = TextView.VisualLines[0].FirstDocumentLine.Offset;
+ int viewEndOffset = TextView.VisualLines.Last().LastDocumentLine.EndOffset;
+ var foldings = FoldingManager.GetFoldingsContaining(viewStartOffset);
+ int maxEndOffset = 0;
+ foreach (FoldingSection fs in foldings) {
+ int end = fs.EndOffset;
+ if (end <= viewEndOffset && !fs.IsFolded) {
+ int textLineNr = GetTextLineIndexFromOffset(allTextLines, end);
+ if (textLineNr >= 0) {
+ endMarker[textLineNr] = foldingControlPen;
+ }
+ }
+ if (end > maxEndOffset && fs.StartOffset < viewStartOffset) {
+ maxEndOffset = end;
+ }
+ }
+ if (maxEndOffset > 0) {
+ if (maxEndOffset > viewEndOffset) {
+ for (int i = 0; i < colors.Length; i++) {
+ colors[i] = foldingControlPen;
+ }
+ } else {
+ int maxTextLine = GetTextLineIndexFromOffset(allTextLines, maxEndOffset);
+ for (int i = 0; i <= maxTextLine; i++) {
+ colors[i] = foldingControlPen;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Calculates fold lines for all folding sections that start inside the current view
+ /// </summary>
+ void CalculateFoldLinesForMarkers(List<TextLine> allTextLines, Pen[] colors, Pen[] endMarker)
+ {
+ foreach (FoldingMarginMarker marker in markers) {
+ int end = marker.FoldingSection.EndOffset;
+ int endTextLineNr = GetTextLineIndexFromOffset(allTextLines, end);
+ if (!marker.FoldingSection.IsFolded && endTextLineNr >= 0) {
+ if (marker.IsMouseDirectlyOver)
+ endMarker[endTextLineNr] = selectedFoldingControlPen;
+ else if (endMarker[endTextLineNr] == null)
+ endMarker[endTextLineNr] = foldingControlPen;
+ }
+ int startTextLineNr = GetTextLineIndexFromOffset(allTextLines, marker.FoldingSection.StartOffset);
+ if (startTextLineNr >= 0) {
+ for (int i = startTextLineNr + 1; i < colors.Length && i - 1 != endTextLineNr; i++) {
+ if (marker.IsMouseDirectlyOver)
+ colors[i] = selectedFoldingControlPen;
+ else if (colors[i] == null)
+ colors[i] = foldingControlPen;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Draws the lines for the folding sections (vertical line with 'color', horizontal lines with 'endMarker')
+ /// Each entry in the input arrays corresponds to one TextLine.
+ /// </summary>
+ void DrawFoldLines(DrawingContext drawingContext, Pen[] colors, Pen[] endMarker)
+ {
+ // Because we are using PenLineCap.Flat (the default), for vertical lines,
+ // Y coordinates must be on pixel boundaries, whereas the X coordinate must be in the
+ // middle of a pixel. (and the other way round for horizontal lines)
+ Size pixelSize = PixelSnapHelpers.GetPixelSize(this);
+ double markerXPos = PixelSnapHelpers.PixelAlign(RenderSize.Width / 2, pixelSize.Width);
+ double startY = 0;
+ Pen currentPen = colors[0];
+
+ Pen p = new Pen(FoldingMarkerBrush, 1);
+
+ int tlNumber = 0;
+ foreach (VisualLine vl in TextView.VisualLines) {
+ foreach (TextLine tl in vl.TextLines) {
+ if (endMarker[tlNumber] != null) {
+ double visualPos = GetVisualPos(vl, tl, pixelSize.Height);
+ drawingContext.DrawLine(p, new Point(markerXPos - pixelSize.Width / 2, visualPos), new Point(RenderSize.Width - 4, visualPos));
+ }
+ if (colors[tlNumber + 1] != currentPen) {
+ double visualPos = GetVisualPos(vl, tl, pixelSize.Height);
+ if (currentPen != null) {
+ drawingContext.DrawLine(p, new Point(markerXPos, startY + pixelSize.Height), new Point(markerXPos, visualPos - pixelSize.Height / 2));
+ }
+ currentPen = colors[tlNumber + 1];
+ startY = visualPos;
+ }
+ tlNumber++;
+ }
+ }
+ if (currentPen != null) {
+ drawingContext.DrawLine(currentPen, new Point(markerXPos, startY + pixelSize.Height / 2), new Point(markerXPos, RenderSize.Height));
+ }
+ }
+
+ double GetVisualPos(VisualLine vl, TextLine tl, double pixelHeight)
+ {
+ double pos = vl.GetTextLineVisualYPosition(tl, VisualYPosition.TextMiddle) - TextView.VerticalOffset;
+ return PixelSnapHelpers.PixelAlign(pos, pixelHeight);
+ }
+
+ int GetTextLineIndexFromOffset(List<TextLine> textLines, int offset)
+ {
+ int lineNumber = TextView.Document.GetLineByOffset(offset).LineNumber;
+ VisualLine vl = TextView.GetVisualLine(lineNumber);
+ if (vl != null) {
+ int relOffset = offset - vl.FirstDocumentLine.Offset;
+ TextLine line = vl.GetTextLine(vl.GetVisualColumn(relOffset));
+ return textLines.IndexOf(line);
+ }
+ return -1;
+ }
+
+ static FoldingMargin()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(FoldingMargin), new FrameworkPropertyMetadata(typeof(FoldingMargin)));
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMarginMarker.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMarginMarker.cs
new file mode 100644
index 000000000..5b69d7485
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMarginMarker.cs
@@ -0,0 +1,90 @@
+// 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.Input;
+using System.Windows.Media;
+
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Folding
+{
+ sealed class FoldingMarginMarker : UIElement
+ {
+ internal VisualLine VisualLine;
+ internal FoldingSection FoldingSection;
+
+ bool isExpanded;
+
+ public bool IsExpanded {
+ get { return isExpanded; }
+ set {
+ if (isExpanded != value) {
+ isExpanded = value;
+ InvalidateVisual();
+ }
+ if (FoldingSection != null)
+ FoldingSection.IsFolded = !value;
+ }
+ }
+
+ protected override void OnMouseDown(MouseButtonEventArgs e)
+ {
+ base.OnMouseDown(e);
+ if (!e.Handled) {
+ if (e.ChangedButton == MouseButton.Left) {
+ IsExpanded = !IsExpanded;
+ e.Handled = true;
+ }
+ }
+ }
+
+ const double MarginSizeFactor = 0.7;
+
+ protected override Size MeasureCore(Size availableSize)
+ {
+ double size = MarginSizeFactor * FoldingMargin.SizeFactor * (double)GetValue(TextBlock.FontSizeProperty);
+ size = PixelSnapHelpers.RoundToOdd(size, PixelSnapHelpers.GetPixelSize(this).Width);
+ return new Size(size, size);
+ }
+
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ FoldingMargin margin = VisualParent as FoldingMargin;
+ Pen activePen = new Pen(margin.SelectedFoldingMarkerBrush, 1);
+
+ Pen inactivePen = new Pen(margin.FoldingMarkerBrush, 1);
+ activePen.StartLineCap = inactivePen.StartLineCap = PenLineCap.Square;
+ activePen.EndLineCap = inactivePen.EndLineCap = PenLineCap.Square;
+ Size pixelSize = PixelSnapHelpers.GetPixelSize(this);
+ Rect rect = new Rect(pixelSize.Width / 2,
+ pixelSize.Height / 2,
+ this.RenderSize.Width - pixelSize.Width - 2,
+ this.RenderSize.Height - pixelSize.Height - 2);
+
+ drawingContext.DrawRectangle(
+ IsMouseDirectlyOver ? margin.FoldingMarkerBackgroundBrush : margin.FoldingMarkerBackgroundBrush,
+ IsMouseDirectlyOver ? activePen : inactivePen, rect);
+ double middleX = rect.Left + rect.Width / 2;
+ double middleY = rect.Top + rect.Height / 2;
+ double space = PixelSnapHelpers.Round(rect.Width / 8, pixelSize.Width) + pixelSize.Width;
+ drawingContext.DrawLine(activePen,
+ new Point(rect.Left + space, middleY),
+ new Point(rect.Right - space, middleY));
+ if (!isExpanded) {
+ drawingContext.DrawLine(activePen,
+ new Point(middleX, rect.Top + space),
+ new Point(middleX, rect.Bottom - space));
+ }
+ }
+
+ protected override void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e)
+ {
+ base.OnIsMouseDirectlyOverChanged(e);
+ InvalidateVisual();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingSection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingSection.cs
new file mode 100644
index 000000000..fd27e9b3b
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingSection.cs
@@ -0,0 +1,186 @@
+// 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.Text;
+using System.Windows.Threading;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Folding
+{
+ /// <summary>
+ /// A section that can be folded.
+ /// </summary>
+ public sealed class FoldingSection : TextSegment
+ {
+ readonly FoldingManager manager;
+ bool isFolded;
+ internal CollapsedLineSection[] collapsedSections;
+ string title;
+
+ /// <summary>
+ /// Gets/sets if the section is folded.
+ /// </summary>
+ public bool IsFolded {
+ get { return isFolded; }
+ set {
+ if (isFolded != value) {
+ isFolded = value;
+ ValidateCollapsedLineSections(); // create/destroy CollapsedLineSection
+ manager.Redraw(this);
+ }
+ }
+ }
+
+ internal void ValidateCollapsedLineSections()
+ {
+ if (!isFolded) {
+ RemoveCollapsedLineSection();
+ return;
+ }
+ // It is possible that StartOffset/EndOffset get set to invalid values via the property setters in TextSegment,
+ // so we coerce those values into the valid range.
+ DocumentLine startLine = manager.document.GetLineByOffset(StartOffset.CoerceValue(0, manager.document.TextLength));
+ DocumentLine endLine = manager.document.GetLineByOffset(EndOffset.CoerceValue(0, manager.document.TextLength));
+ if (startLine == endLine) {
+ RemoveCollapsedLineSection();
+ } else {
+ if (collapsedSections == null)
+ collapsedSections = new CollapsedLineSection[manager.textViews.Count];
+ // Validate collapsed line sections
+ DocumentLine startLinePlusOne = startLine.NextLine;
+ for (int i = 0; i < collapsedSections.Length; i++) {
+ var collapsedSection = collapsedSections[i];
+ if (collapsedSection == null || collapsedSection.Start != startLinePlusOne || collapsedSection.End != endLine) {
+ // recreate this collapsed section
+ if (collapsedSection != null) {
+ Debug.WriteLine("CollapsedLineSection validation - recreate collapsed section from " + startLinePlusOne + " to " + endLine);
+ collapsedSection.Uncollapse();
+ }
+ collapsedSections[i] = manager.textViews[i].CollapseLines(startLinePlusOne, endLine);
+ }
+ }
+ }
+ }
+
+ /// <inheritdoc/>
+ protected override void OnSegmentChanged()
+ {
+ ValidateCollapsedLineSections();
+ base.OnSegmentChanged();
+ // don't redraw if the FoldingSection wasn't added to the FoldingManager's collection yet
+ if (IsConnectedToCollection)
+ manager.Redraw(this);
+ }
+
+ /// <summary>
+ /// Gets/Sets the text used to display the collapsed version of the folding section.
+ /// </summary>
+ public string Title {
+ get {
+ return title;
+ }
+ set {
+ if (title != value) {
+ title = value;
+ if (this.IsFolded)
+ manager.Redraw(this);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the content of the collapsed lines as text.
+ /// </summary>
+ public string TextContent {
+ get {
+ return manager.document.GetText(StartOffset, EndOffset - StartOffset);
+ }
+ }
+
+ /// <summary>
+ /// Gets the content of the collapsed lines as tooltip text.
+ /// </summary>
+ [Obsolete]
+ public string TooltipText {
+ get {
+ // This fixes SD-1394:
+ // Each line is checked for leading indentation whitespaces. If
+ // a line has the same or more indentation than the first line,
+ // it is reduced. If a line is less indented than the first line
+ // the indentation is removed completely.
+ //
+ // See the following example:
+ // line 1
+ // line 2
+ // line 3
+ // line 4
+ //
+ // is reduced to:
+ // line 1
+ // line 2
+ // line 3
+ // line 4
+
+ var startLine = manager.document.GetLineByOffset(StartOffset);
+ var endLine = manager.document.GetLineByOffset(EndOffset);
+ var builder = new StringBuilder();
+
+ var current = startLine;
+ ISegment startIndent = TextUtilities.GetLeadingWhitespace(manager.document, startLine);
+
+ while (current != endLine.NextLine) {
+ ISegment currentIndent = TextUtilities.GetLeadingWhitespace(manager.document, current);
+
+ if (current == startLine && current == endLine)
+ builder.Append(manager.document.GetText(StartOffset, EndOffset - StartOffset));
+ else if (current == startLine) {
+ if (current.EndOffset - StartOffset > 0)
+ builder.AppendLine(manager.document.GetText(StartOffset, current.EndOffset - StartOffset).TrimStart());
+ } else if (current == endLine) {
+ if (startIndent.Length <= currentIndent.Length)
+ builder.Append(manager.document.GetText(current.Offset + startIndent.Length, EndOffset - current.Offset - startIndent.Length));
+ else
+ builder.Append(manager.document.GetText(current.Offset + currentIndent.Length, EndOffset - current.Offset - currentIndent.Length));
+ } else {
+ if (startIndent.Length <= currentIndent.Length)
+ builder.AppendLine(manager.document.GetText(current.Offset + startIndent.Length, current.Length - startIndent.Length));
+ else
+ builder.AppendLine(manager.document.GetText(current.Offset + currentIndent.Length, current.Length - currentIndent.Length));
+ }
+
+ current = current.NextLine;
+ }
+
+ return builder.ToString();
+ }
+ }
+
+ /// <summary>
+ /// Gets/Sets an additional object associated with this folding section.
+ /// </summary>
+ public object Tag { get; set; }
+
+ internal FoldingSection(FoldingManager manager, int startOffset, int endOffset)
+ {
+ Debug.Assert(manager != null);
+ this.manager = manager;
+ this.StartOffset = startOffset;
+ this.Length = endOffset - startOffset;
+ }
+
+ void RemoveCollapsedLineSection()
+ {
+ if (collapsedSections != null) {
+ foreach (var collapsedSection in collapsedSections) {
+ if (collapsedSection != null && collapsedSection.Start != null)
+ collapsedSection.Uncollapse();
+ }
+ collapsedSections = null;
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/NewFolding.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/NewFolding.cs
new file mode 100644
index 000000000..7f397c82e
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/NewFolding.cs
@@ -0,0 +1,70 @@
+// 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.Windows;
+using System.Windows.Threading;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Editing;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Folding
+{
+ /// <summary>
+ /// Helper class used for <see cref="FoldingManager.UpdateFoldings"/>.
+ /// </summary>
+ public class NewFolding : ISegment
+ {
+ /// <summary>
+ /// Gets/Sets the start offset.
+ /// </summary>
+ public int StartOffset { get; set; }
+
+ /// <summary>
+ /// Gets/Sets the end offset.
+ /// </summary>
+ public int EndOffset { get; set; }
+
+ /// <summary>
+ /// Gets/Sets the name displayed for the folding.
+ /// </summary>
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Gets/Sets whether the folding is closed by default.
+ /// </summary>
+ public bool DefaultClosed { get; set; }
+
+ /// <summary>
+ /// Creates a new NewFolding instance.
+ /// </summary>
+ public NewFolding()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new NewFolding instance.
+ /// </summary>
+ public NewFolding(int start, int end)
+ {
+ if (!(start <= end))
+ throw new ArgumentException("'start' must be less than 'end'");
+ this.StartOffset = start;
+ this.EndOffset = end;
+ this.Name = null;
+ this.DefaultClosed = false;
+ }
+
+ int ISegment.Offset {
+ get { return this.StartOffset; }
+ }
+
+ int ISegment.Length {
+ get { return this.EndOffset - this.StartOffset; }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/XmlFoldingStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/XmlFoldingStrategy.cs
new file mode 100644
index 000000000..0f888a572
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/XmlFoldingStrategy.cs
@@ -0,0 +1,215 @@
+// 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 System.Text;
+using System.Xml;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Folding
+{
+ /// <summary>
+ /// Holds information about the start of a fold in an xml string.
+ /// </summary>
+ sealed class XmlFoldStart : NewFolding
+ {
+ internal int StartLine;
+ }
+
+ /// <summary>
+ /// Determines folds for an xml string in the editor.
+ /// </summary>
+ public class XmlFoldingStrategy : AbstractFoldingStrategy
+ {
+ /// <summary>
+ /// Flag indicating whether attributes should be displayed on folded
+ /// elements.
+ /// </summary>
+ public bool ShowAttributesWhenFolded { get; set; }
+
+ /// <summary>
+ /// Create <see cref="NewFolding"/>s for the specified document.
+ /// </summary>
+ public override IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset)
+ {
+ try {
+ XmlTextReader reader = new XmlTextReader(document.CreateReader());
+ reader.XmlResolver = null; // don't resolve DTDs
+ return CreateNewFoldings(document, reader, out firstErrorOffset);
+ } catch (XmlException) {
+ firstErrorOffset = 0;
+ return Enumerable.Empty<NewFolding>();
+ }
+ }
+
+ /// <summary>
+ /// Create <see cref="NewFolding"/>s for the specified document.
+ /// </summary>
+ public IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, XmlReader reader, out int firstErrorOffset)
+ {
+ Stack<XmlFoldStart> stack = new Stack<XmlFoldStart>();
+ List<NewFolding> foldMarkers = new List<NewFolding>();
+ try {
+ while (reader.Read()) {
+ switch (reader.NodeType) {
+ case XmlNodeType.Element:
+ if (!reader.IsEmptyElement) {
+ XmlFoldStart newFoldStart = CreateElementFoldStart(document, reader);
+ stack.Push(newFoldStart);
+ }
+ break;
+
+ case XmlNodeType.EndElement:
+ XmlFoldStart foldStart = stack.Pop();
+ CreateElementFold(document, foldMarkers, reader, foldStart);
+ break;
+
+ case XmlNodeType.Comment:
+ CreateCommentFold(document, foldMarkers, reader);
+ break;
+ }
+ }
+ firstErrorOffset = -1;
+ } catch (XmlException ex) {
+ // ignore errors at invalid positions (prevent ArgumentOutOfRangeException)
+ if (ex.LineNumber >= 1 && ex.LineNumber <= document.LineCount)
+ firstErrorOffset = document.GetOffset(ex.LineNumber, ex.LinePosition);
+ else
+ firstErrorOffset = 0;
+ }
+ foldMarkers.Sort((a,b) => a.StartOffset.CompareTo(b.StartOffset));
+ return foldMarkers;
+ }
+
+ static int GetOffset(TextDocument document, XmlReader reader)
+ {
+ IXmlLineInfo info = reader as IXmlLineInfo;
+ if (info != null && info.HasLineInfo()) {
+ return document.GetOffset(info.LineNumber, info.LinePosition);
+ } else {
+ throw new ArgumentException("XmlReader does not have positioning information.");
+ }
+ }
+
+ /// <summary>
+ /// Creates a comment fold if the comment spans more than one line.
+ /// </summary>
+ /// <remarks>The text displayed when the comment is folded is the first
+ /// line of the comment.</remarks>
+ static void CreateCommentFold(TextDocument document, List<NewFolding> foldMarkers, XmlReader reader)
+ {
+ string comment = reader.Value;
+ if (comment != null) {
+ int firstNewLine = comment.IndexOf('\n');
+ if (firstNewLine >= 0) {
+
+ // Take off 4 chars to get the actual comment start (takes
+ // into account the <!-- chars.
+
+ int startOffset = GetOffset(document, reader) - 4;
+ int endOffset = startOffset + comment.Length + 7;
+
+ string foldText = String.Concat("<!--", comment.Substring(0, firstNewLine).TrimEnd('\r') , "-->");
+ foldMarkers.Add(new NewFolding(startOffset, endOffset) { Name = foldText } );
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates an XmlFoldStart for the start tag of an element.
+ /// </summary>
+ XmlFoldStart CreateElementFoldStart(TextDocument document, XmlReader reader)
+ {
+ // Take off 1 from the offset returned
+ // from the xml since it points to the start
+ // of the element name and not the beginning
+ // tag.
+ //XmlFoldStart newFoldStart = new XmlFoldStart(reader.Prefix, reader.LocalName, reader.LineNumber - 1, reader.LinePosition - 2);
+ XmlFoldStart newFoldStart = new XmlFoldStart();
+
+ IXmlLineInfo lineInfo = (IXmlLineInfo)reader;
+ newFoldStart.StartLine = lineInfo.LineNumber;
+ newFoldStart.StartOffset = document.GetOffset(newFoldStart.StartLine, lineInfo.LinePosition - 1);
+
+ if (this.ShowAttributesWhenFolded && reader.HasAttributes) {
+ newFoldStart.Name = String.Concat("<", reader.Name, " ", GetAttributeFoldText(reader), ">");
+ } else {
+ newFoldStart.Name = String.Concat("<", reader.Name, ">");
+ }
+
+ return newFoldStart;
+ }
+
+ /// <summary>
+ /// Create an element fold if the start and end tag are on
+ /// different lines.
+ /// </summary>
+ static void CreateElementFold(TextDocument document, List<NewFolding> foldMarkers, XmlReader reader, XmlFoldStart foldStart)
+ {
+ IXmlLineInfo lineInfo = (IXmlLineInfo)reader;
+ int endLine = lineInfo.LineNumber;
+ if (endLine > foldStart.StartLine) {
+ int endCol = lineInfo.LinePosition + reader.Name.Length + 1;
+ foldStart.EndOffset = document.GetOffset(endLine, endCol);
+ foldMarkers.Add(foldStart);
+ }
+ }
+
+ /// <summary>
+ /// Gets the element's attributes as a string on one line that will
+ /// be displayed when the element is folded.
+ /// </summary>
+ /// <remarks>
+ /// Currently this puts all attributes from an element on the same
+ /// line of the start tag. It does not cater for elements where attributes
+ /// are not on the same line as the start tag.
+ /// </remarks>
+ static string GetAttributeFoldText(XmlReader reader)
+ {
+ StringBuilder text = new StringBuilder();
+
+ for (int i = 0; i < reader.AttributeCount; ++i) {
+ reader.MoveToAttribute(i);
+
+ text.Append(reader.Name);
+ text.Append("=");
+ text.Append(reader.QuoteChar.ToString());
+ text.Append(XmlEncodeAttributeValue(reader.Value, reader.QuoteChar));
+ text.Append(reader.QuoteChar.ToString());
+
+ // Append a space if this is not the
+ // last attribute.
+ if (i < reader.AttributeCount - 1) {
+ text.Append(" ");
+ }
+ }
+
+ return text.ToString();
+ }
+
+ /// <summary>
+ /// Xml encode the attribute string since the string returned from
+ /// the XmlTextReader is the plain unencoded string and .NET
+ /// does not provide us with an xml encode method.
+ /// </summary>
+ static string XmlEncodeAttributeValue(string attributeValue, char quoteChar)
+ {
+ StringBuilder encodedValue = new StringBuilder(attributeValue);
+
+ encodedValue.Replace("&", "&amp;");
+ encodedValue.Replace("<", "&lt;");
+ encodedValue.Replace(">", "&gt;");
+
+ if (quoteChar == '"') {
+ encodedValue.Replace("\"", "&quot;");
+ } else {
+ encodedValue.Replace("'", "&apos;");
+ }
+
+ return encodedValue.ToString();
+ }
+ }
+}