diff options
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingManager.cs')
| -rw-r--r-- | Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingManager.cs | 388 |
1 files changed, 388 insertions, 0 deletions
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 + } +} |
