Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2839_HiveProjectManagement/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Folding/FoldingManager.cs @ 15761

Last change on this file since 15761 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 13.9 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Collections.Generic;
21using System.Collections.ObjectModel;
22using System.Linq;
23using System.Windows;
24
25using ICSharpCode.AvalonEdit.Document;
26using ICSharpCode.AvalonEdit.Editing;
27using ICSharpCode.AvalonEdit.Rendering;
28using ICSharpCode.AvalonEdit.Utils;
29
30namespace ICSharpCode.AvalonEdit.Folding
31{
32  /// <summary>
33  /// Stores a list of foldings for a specific TextView and TextDocument.
34  /// </summary>
35  public class FoldingManager : IWeakEventListener
36  {
37    internal readonly TextDocument document;
38   
39    internal readonly List<TextView> textViews = new List<TextView>();
40    readonly TextSegmentCollection<FoldingSection> foldings;
41    bool isFirstUpdate = true;
42   
43    #region Constructor
44    /// <summary>
45    /// Creates a new FoldingManager instance.
46    /// </summary>
47    public FoldingManager(TextDocument document)
48    {
49      if (document == null)
50        throw new ArgumentNullException("document");
51      this.document = document;
52      this.foldings = new TextSegmentCollection<FoldingSection>();
53      document.VerifyAccess();
54      TextDocumentWeakEventManager.Changed.AddListener(document, this);
55    }
56    #endregion
57   
58    #region ReceiveWeakEvent
59    /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
60    protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
61    {
62      if (managerType == typeof(TextDocumentWeakEventManager.Changed)) {
63        OnDocumentChanged((DocumentChangeEventArgs)e);
64        return true;
65      }
66      return false;
67    }
68   
69    bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
70    {
71      return ReceiveWeakEvent(managerType, sender, e);
72    }
73   
74    void OnDocumentChanged(DocumentChangeEventArgs e)
75    {
76      foldings.UpdateOffsets(e);
77      int newEndOffset = e.Offset + e.InsertionLength;
78      // extend end offset to the end of the line (including delimiter)
79      var endLine = document.GetLineByOffset(newEndOffset);
80      newEndOffset = endLine.Offset + endLine.TotalLength;
81      foreach (var affectedFolding in foldings.FindOverlappingSegments(e.Offset, newEndOffset - e.Offset)) {
82        if (affectedFolding.Length == 0) {
83          RemoveFolding(affectedFolding);
84        } else {
85          affectedFolding.ValidateCollapsedLineSections();
86        }
87      }
88    }
89    #endregion
90   
91    #region Manage TextViews
92    internal void AddToTextView(TextView textView)
93    {
94      if (textView == null || textViews.Contains(textView))
95        throw new ArgumentException();
96      textViews.Add(textView);
97      foreach (FoldingSection fs in foldings) {
98        if (fs.collapsedSections != null) {
99          Array.Resize(ref fs.collapsedSections, textViews.Count);
100          fs.ValidateCollapsedLineSections();
101        }
102      }
103    }
104   
105    internal void RemoveFromTextView(TextView textView)
106    {
107      int pos = textViews.IndexOf(textView);
108      if (pos < 0)
109        throw new ArgumentException();
110      textViews.RemoveAt(pos);
111      foreach (FoldingSection fs in foldings) {
112        if (fs.collapsedSections != null) {
113          var c = new CollapsedLineSection[textViews.Count];
114          Array.Copy(fs.collapsedSections, 0, c, 0, pos);
115          fs.collapsedSections[pos].Uncollapse();
116          Array.Copy(fs.collapsedSections, pos + 1, c, pos, c.Length - pos);
117          fs.collapsedSections = c;
118        }
119      }
120    }
121   
122    internal void Redraw()
123    {
124      foreach (TextView textView in textViews)
125        textView.Redraw();
126    }
127   
128    internal void Redraw(FoldingSection fs)
129    {
130      foreach (TextView textView in textViews)
131        textView.Redraw(fs);
132    }
133    #endregion
134   
135    #region Create / Remove / Clear
136    /// <summary>
137    /// Creates a folding for the specified text section.
138    /// </summary>
139    public FoldingSection CreateFolding(int startOffset, int endOffset)
140    {
141      if (startOffset >= endOffset)
142        throw new ArgumentException("startOffset must be less than endOffset");
143      if (startOffset < 0 || endOffset > document.TextLength)
144        throw new ArgumentException("Folding must be within document boundary");
145      FoldingSection fs = new FoldingSection(this, startOffset, endOffset);
146      foldings.Add(fs);
147      Redraw(fs);
148      return fs;
149    }
150   
151    /// <summary>
152    /// Removes a folding section from this manager.
153    /// </summary>
154    public void RemoveFolding(FoldingSection fs)
155    {
156      if (fs == null)
157        throw new ArgumentNullException("fs");
158      fs.IsFolded = false;
159      foldings.Remove(fs);
160      Redraw(fs);
161    }
162   
163    /// <summary>
164    /// Removes all folding sections.
165    /// </summary>
166    public void Clear()
167    {
168      document.VerifyAccess();
169      foreach (FoldingSection s in foldings)
170        s.IsFolded = false;
171      foldings.Clear();
172      Redraw();
173    }
174    #endregion
175   
176    #region Get...Folding
177    /// <summary>
178    /// Gets all foldings in this manager.
179    /// The foldings are returned sorted by start offset;
180    /// for multiple foldings at the same offset the order is undefined.
181    /// </summary>
182    public IEnumerable<FoldingSection> AllFoldings {
183      get { return foldings; }
184    }
185   
186    /// <summary>
187    /// Gets the first offset greater or equal to <paramref name="startOffset"/> where a folded folding starts.
188    /// Returns -1 if there are no foldings after <paramref name="startOffset"/>.
189    /// </summary>
190    public int GetNextFoldedFoldingStart(int startOffset)
191    {
192      FoldingSection fs = foldings.FindFirstSegmentWithStartAfter(startOffset);
193      while (fs != null && !fs.IsFolded)
194        fs = foldings.GetNextSegment(fs);
195      return fs != null ? fs.StartOffset : -1;
196    }
197   
198    /// <summary>
199    /// Gets the first folding with a <see cref="TextSegment.StartOffset"/> greater or equal to
200    /// <paramref name="startOffset"/>.
201    /// Returns null if there are no foldings after <paramref name="startOffset"/>.
202    /// </summary>
203    public FoldingSection GetNextFolding(int startOffset)
204    {
205      // TODO: returns the longest folding instead of any folding at the first position after startOffset
206      return foldings.FindFirstSegmentWithStartAfter(startOffset);
207    }
208   
209    /// <summary>
210    /// Gets all foldings that start exactly at <paramref name="startOffset"/>.
211    /// </summary>
212    public ReadOnlyCollection<FoldingSection> GetFoldingsAt(int startOffset)
213    {
214      List<FoldingSection> result = new List<FoldingSection>();
215      FoldingSection fs = foldings.FindFirstSegmentWithStartAfter(startOffset);
216      while (fs != null && fs.StartOffset == startOffset) {
217        result.Add(fs);
218        fs = foldings.GetNextSegment(fs);
219      }
220      return result.AsReadOnly();
221    }
222   
223    /// <summary>
224    /// Gets all foldings that contain <paramref name="offset" />.
225    /// </summary>
226    public ReadOnlyCollection<FoldingSection> GetFoldingsContaining(int offset)
227    {
228      return foldings.FindSegmentsContaining(offset);
229    }
230    #endregion
231   
232    #region UpdateFoldings
233    /// <summary>
234    /// Updates the foldings in this <see cref="FoldingManager"/> using the given new foldings.
235    /// This method will try to detect which new foldings correspond to which existing foldings; and will keep the state
236    /// (<see cref="FoldingSection.IsFolded"/>) for existing foldings.
237    /// </summary>
238    /// <param name="newFoldings">The new set of foldings. These must be sorted by starting offset.</param>
239    /// <param name="firstErrorOffset">The first position of a parse error. Existing foldings starting after
240    /// this offset will be kept even if they don't appear in <paramref name="newFoldings"/>.
241    /// Use -1 for this parameter if there were no parse errors.</param>
242    public void UpdateFoldings(IEnumerable<NewFolding> newFoldings, int firstErrorOffset)
243    {
244      if (newFoldings == null)
245        throw new ArgumentNullException("newFoldings");
246     
247      if (firstErrorOffset < 0)
248        firstErrorOffset = int.MaxValue;
249     
250      var oldFoldings = this.AllFoldings.ToArray();
251      int oldFoldingIndex = 0;
252      int previousStartOffset = 0;
253      // merge new foldings into old foldings so that sections keep being collapsed
254      // both oldFoldings and newFoldings are sorted by start offset
255      foreach (NewFolding newFolding in newFoldings) {
256        // ensure newFoldings are sorted correctly
257        if (newFolding.StartOffset < previousStartOffset)
258          throw new ArgumentException("newFoldings must be sorted by start offset");
259        previousStartOffset = newFolding.StartOffset;
260       
261        int startOffset = newFolding.StartOffset.CoerceValue(0, document.TextLength);
262        int endOffset = newFolding.EndOffset.CoerceValue(0, document.TextLength);
263       
264        if (newFolding.StartOffset == newFolding.EndOffset)
265          continue; // ignore zero-length foldings
266       
267        // remove old foldings that were skipped
268        while (oldFoldingIndex < oldFoldings.Length && newFolding.StartOffset > oldFoldings[oldFoldingIndex].StartOffset) {
269          this.RemoveFolding(oldFoldings[oldFoldingIndex++]);
270        }
271        FoldingSection section;
272        // reuse current folding if its matching:
273        if (oldFoldingIndex < oldFoldings.Length && newFolding.StartOffset == oldFoldings[oldFoldingIndex].StartOffset) {
274          section = oldFoldings[oldFoldingIndex++];
275          section.Length = newFolding.EndOffset - newFolding.StartOffset;
276        } else {
277          // no matching current folding; create a new one:
278          section = this.CreateFolding(newFolding.StartOffset, newFolding.EndOffset);
279          // auto-close #regions only when opening the document
280          if (isFirstUpdate) {
281            section.IsFolded = newFolding.DefaultClosed;
282            isFirstUpdate = false;
283          }
284          section.Tag = newFolding;
285        }
286        section.Title = newFolding.Name;
287      }
288      // remove all outstanding old foldings:
289      while (oldFoldingIndex < oldFoldings.Length) {
290        FoldingSection oldSection = oldFoldings[oldFoldingIndex++];
291        if (oldSection.StartOffset >= firstErrorOffset)
292          break;
293        this.RemoveFolding(oldSection);
294      }
295    }
296    #endregion
297   
298    #region Install
299    /// <summary>
300    /// Adds Folding support to the specified text area.
301    /// Warning: The folding manager is only valid for the text area's current document. The folding manager
302    /// must be uninstalled before the text area is bound to a different document.
303    /// </summary>
304    /// <returns>The <see cref="FoldingManager"/> that manages the list of foldings inside the text area.</returns>
305    public static FoldingManager Install(TextArea textArea)
306    {
307      if (textArea == null)
308        throw new ArgumentNullException("textArea");
309      return new FoldingManagerInstallation(textArea);
310    }
311   
312    /// <summary>
313    /// Uninstalls the folding manager.
314    /// </summary>
315    /// <exception cref="ArgumentException">The specified manager was not created using <see cref="Install"/>.</exception>
316    public static void Uninstall(FoldingManager manager)
317    {
318      if (manager == null)
319        throw new ArgumentNullException("manager");
320      FoldingManagerInstallation installation = manager as FoldingManagerInstallation;
321      if (installation != null) {
322        installation.Uninstall();
323      } else {
324        throw new ArgumentException("FoldingManager was not created using FoldingManager.Install");
325      }
326    }
327   
328    sealed class FoldingManagerInstallation : FoldingManager
329    {
330      TextArea textArea;
331      FoldingMargin margin;
332      FoldingElementGenerator generator;
333     
334      public FoldingManagerInstallation(TextArea textArea) : base(textArea.Document)
335      {
336        this.textArea = textArea;
337        margin = new FoldingMargin() { FoldingManager = this };
338        generator = new FoldingElementGenerator() { FoldingManager = this };
339        textArea.LeftMargins.Add(margin);
340        textArea.TextView.Services.AddService(typeof(FoldingManager), this);
341        // HACK: folding only works correctly when it has highest priority
342        textArea.TextView.ElementGenerators.Insert(0, generator);
343        textArea.Caret.PositionChanged += textArea_Caret_PositionChanged;
344      }
345     
346      /*
347      void DemoMode()
348      {
349        foldingGenerator = new FoldingElementGenerator() { FoldingManager = fm };
350        foldingMargin = new FoldingMargin { FoldingManager = fm };
351        foldingMarginBorder = new Border {
352          Child = foldingMargin,
353          Background = new LinearGradientBrush(Colors.White, Colors.Transparent, 0)
354        };
355        foldingMarginBorder.SizeChanged += UpdateTextViewClip;
356        textEditor.TextArea.TextView.ElementGenerators.Add(foldingGenerator);
357        textEditor.TextArea.LeftMargins.Add(foldingMarginBorder);
358      }
359     
360      void UpdateTextViewClip(object sender, SizeChangedEventArgs e)
361      {
362        textEditor.TextArea.TextView.Clip = new RectangleGeometry(
363          new Rect(-foldingMarginBorder.ActualWidth,
364                   0,
365                   textEditor.TextArea.TextView.ActualWidth + foldingMarginBorder.ActualWidth,
366                   textEditor.TextArea.TextView.ActualHeight));
367      }
368       */
369     
370      public void Uninstall()
371      {
372        Clear();
373        if (textArea != null) {
374          textArea.Caret.PositionChanged -= textArea_Caret_PositionChanged;
375          textArea.LeftMargins.Remove(margin);
376          textArea.TextView.ElementGenerators.Remove(generator);
377          textArea.TextView.Services.RemoveService(typeof(FoldingManager));
378          margin = null;
379          generator = null;
380          textArea = null;
381        }
382      }
383     
384      void textArea_Caret_PositionChanged(object sender, EventArgs e)
385      {
386        // Expand Foldings when Caret is moved into them.
387        int caretOffset = textArea.Caret.Offset;
388        foreach (FoldingSection s in GetFoldingsContaining(caretOffset)) {
389          if (s.IsFolded && s.StartOffset < caretOffset && caretOffset < s.EndOffset) {
390            s.IsFolded = false;
391          }
392        }
393      }
394    }
395    #endregion
396  }
397}
Note: See TracBrowser for help on using the repository browser.