Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Document/LineManager.cs @ 13316

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

#2077: created branch and added first version

File size: 11.1 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.Diagnostics;
22using System.Linq;
23using ICSharpCode.NRefactory.Editor;
24
25namespace ICSharpCode.AvalonEdit.Document
26{
27  /// <summary>
28  /// Creates/Deletes lines when text is inserted/removed.
29  /// </summary>
30  sealed class LineManager
31  {
32    #region Constructor
33    readonly TextDocument document;
34    readonly DocumentLineTree documentLineTree;
35   
36    /// <summary>
37    /// A copy of the line trackers. We need a copy so that line trackers may remove themselves
38    /// while being notified (used e.g. by WeakLineTracker)
39    /// </summary>
40    ILineTracker[] lineTrackers;
41   
42    internal void UpdateListOfLineTrackers()
43    {
44      this.lineTrackers = document.LineTrackers.ToArray();
45    }
46   
47    public LineManager(DocumentLineTree documentLineTree, TextDocument document)
48    {
49      this.document = document;
50      this.documentLineTree = documentLineTree;
51      UpdateListOfLineTrackers();
52     
53      Rebuild();
54    }
55    #endregion
56   
57    #region Change events
58    /*
59    HashSet<DocumentLine> deletedLines = new HashSet<DocumentLine>();
60    readonly HashSet<DocumentLine> changedLines = new HashSet<DocumentLine>();
61    HashSet<DocumentLine> deletedOrChangedLines = new HashSet<DocumentLine>();
62   
63    /// <summary>
64    /// Gets the list of lines deleted since the last RetrieveChangedLines() call.
65    /// The returned list is unsorted.
66    /// </summary>
67    public ICollection<DocumentLine> RetrieveDeletedLines()
68    {
69      var r = deletedLines;
70      deletedLines = new HashSet<DocumentLine>();
71      return r;
72    }
73   
74    /// <summary>
75    /// Gets the list of lines changed since the last RetrieveChangedLines() call.
76    /// The returned list is sorted by line number and does not contain deleted lines.
77    /// </summary>
78    public List<DocumentLine> RetrieveChangedLines()
79    {
80      var list = (from line in changedLines
81                  where !line.IsDeleted
82                  let number = line.LineNumber
83                  orderby number
84                  select line).ToList();
85      changedLines.Clear();
86      return list;
87    }
88   
89    /// <summary>
90    /// Gets the list of lines changed since the last RetrieveDeletedOrChangedLines() call.
91    /// The returned list is not sorted.
92    /// </summary>
93    public ICollection<DocumentLine> RetrieveDeletedOrChangedLines()
94    {
95      var r = deletedOrChangedLines;
96      deletedOrChangedLines = new HashSet<DocumentLine>();
97      return r;
98    }
99     */
100    #endregion
101   
102    #region Rebuild
103    public void Rebuild()
104    {
105      // keep the first document line
106      DocumentLine ls = documentLineTree.GetByNumber(1);
107      // but mark all other lines as deleted, and detach them from the other nodes
108      for (DocumentLine line = ls.NextLine; line != null; line = line.NextLine) {
109        line.isDeleted = true;
110        line.parent = line.left = line.right = null;
111      }
112      // Reset the first line to detach it from the deleted lines
113      ls.ResetLine();
114      SimpleSegment ds = NewLineFinder.NextNewLine(document, 0);
115      List<DocumentLine> lines = new List<DocumentLine>();
116      int lastDelimiterEnd = 0;
117      while (ds != SimpleSegment.Invalid) {
118        ls.TotalLength = ds.Offset + ds.Length - lastDelimiterEnd;
119        ls.DelimiterLength = ds.Length;
120        lastDelimiterEnd = ds.Offset + ds.Length;
121        lines.Add(ls);
122       
123        ls = new DocumentLine(document);
124        ds = NewLineFinder.NextNewLine(document, lastDelimiterEnd);
125      }
126      ls.TotalLength = document.TextLength - lastDelimiterEnd;
127      lines.Add(ls);
128      documentLineTree.RebuildTree(lines);
129      foreach (ILineTracker lineTracker in lineTrackers)
130        lineTracker.RebuildDocument();
131    }
132    #endregion
133   
134    #region Remove
135    public void Remove(int offset, int length)
136    {
137      Debug.Assert(length >= 0);
138      if (length == 0) return;
139      DocumentLine startLine = documentLineTree.GetByOffset(offset);
140      int startLineOffset = startLine.Offset;
141     
142      Debug.Assert(offset < startLineOffset + startLine.TotalLength);
143      if (offset > startLineOffset + startLine.Length) {
144        Debug.Assert(startLine.DelimiterLength == 2);
145        // we are deleting starting in the middle of a delimiter
146       
147        // remove last delimiter part
148        SetLineLength(startLine, startLine.TotalLength - 1);
149        // remove remaining text
150        Remove(offset, length - 1);
151        return;
152      }
153     
154      if (offset + length < startLineOffset + startLine.TotalLength) {
155        // just removing a part of this line
156        //startLine.RemovedLinePart(ref deferredEventList, offset - startLineOffset, length);
157        SetLineLength(startLine, startLine.TotalLength - length);
158        return;
159      }
160      // merge startLine with another line because startLine's delimiter was deleted
161      // possibly remove lines in between if multiple delimiters were deleted
162      int charactersRemovedInStartLine = startLineOffset + startLine.TotalLength - offset;
163      Debug.Assert(charactersRemovedInStartLine > 0);
164      //startLine.RemovedLinePart(ref deferredEventList, offset - startLineOffset, charactersRemovedInStartLine);
165     
166     
167      DocumentLine endLine = documentLineTree.GetByOffset(offset + length);
168      if (endLine == startLine) {
169        // special case: we are removing a part of the last line up to the
170        // end of the document
171        SetLineLength(startLine, startLine.TotalLength - length);
172        return;
173      }
174      int endLineOffset = endLine.Offset;
175      int charactersLeftInEndLine = endLineOffset + endLine.TotalLength - (offset + length);
176      //endLine.RemovedLinePart(ref deferredEventList, 0, endLine.TotalLength - charactersLeftInEndLine);
177      //startLine.MergedWith(endLine, offset - startLineOffset);
178     
179      // remove all lines between startLine (excl.) and endLine (incl.)
180      DocumentLine tmp = startLine.NextLine;
181      DocumentLine lineToRemove;
182      do {
183        lineToRemove = tmp;
184        tmp = tmp.NextLine;
185        RemoveLine(lineToRemove);
186      } while (lineToRemove != endLine);
187     
188      SetLineLength(startLine, startLine.TotalLength - charactersRemovedInStartLine + charactersLeftInEndLine);
189    }
190
191    void RemoveLine(DocumentLine lineToRemove)
192    {
193      foreach (ILineTracker lt in lineTrackers)
194        lt.BeforeRemoveLine(lineToRemove);
195      documentLineTree.RemoveLine(lineToRemove);
196//      foreach (ILineTracker lt in lineTracker)
197//        lt.AfterRemoveLine(lineToRemove);
198//      deletedLines.Add(lineToRemove);
199//      deletedOrChangedLines.Add(lineToRemove);
200    }
201
202    #endregion
203   
204    #region Insert
205    public void Insert(int offset, ITextSource text)
206    {
207      DocumentLine line = documentLineTree.GetByOffset(offset);
208      int lineOffset = line.Offset;
209     
210      Debug.Assert(offset <= lineOffset + line.TotalLength);
211      if (offset > lineOffset + line.Length) {
212        Debug.Assert(line.DelimiterLength == 2);
213        // we are inserting in the middle of a delimiter
214       
215        // shorten line
216        SetLineLength(line, line.TotalLength - 1);
217        // add new line
218        line = InsertLineAfter(line, 1);
219        line = SetLineLength(line, 1);
220      }
221     
222      SimpleSegment ds = NewLineFinder.NextNewLine(text, 0);
223      if (ds == SimpleSegment.Invalid) {
224        // no newline is being inserted, all text is inserted in a single line
225        //line.InsertedLinePart(offset - line.Offset, text.Length);
226        SetLineLength(line, line.TotalLength + text.TextLength);
227        return;
228      }
229      //DocumentLine firstLine = line;
230      //firstLine.InsertedLinePart(offset - firstLine.Offset, ds.Offset);
231      int lastDelimiterEnd = 0;
232      while (ds != SimpleSegment.Invalid) {
233        // split line segment at line delimiter
234        int lineBreakOffset = offset + ds.Offset + ds.Length;
235        lineOffset = line.Offset;
236        int lengthAfterInsertionPos = lineOffset + line.TotalLength - (offset + lastDelimiterEnd);
237        line = SetLineLength(line, lineBreakOffset - lineOffset);
238        DocumentLine newLine = InsertLineAfter(line, lengthAfterInsertionPos);
239        newLine = SetLineLength(newLine, lengthAfterInsertionPos);
240       
241        line = newLine;
242        lastDelimiterEnd = ds.Offset + ds.Length;
243       
244        ds = NewLineFinder.NextNewLine(text, lastDelimiterEnd);
245      }
246      //firstLine.SplitTo(line);
247      // insert rest after last delimiter
248      if (lastDelimiterEnd != text.TextLength) {
249        //line.InsertedLinePart(0, text.Length - lastDelimiterEnd);
250        SetLineLength(line, line.TotalLength + text.TextLength - lastDelimiterEnd);
251      }
252    }
253   
254    DocumentLine InsertLineAfter(DocumentLine line, int length)
255    {
256      DocumentLine newLine = documentLineTree.InsertLineAfter(line, length);
257      foreach (ILineTracker lt in lineTrackers)
258        lt.LineInserted(line, newLine);
259      return newLine;
260    }
261    #endregion
262   
263    #region SetLineLength
264    /// <summary>
265    /// Sets the total line length and checks the delimiter.
266    /// This method can cause line to be deleted when it contains a single '\n' character
267    /// and the previous line ends with '\r'.
268    /// </summary>
269    /// <returns>Usually returns <paramref name="line"/>, but if line was deleted due to
270    /// the "\r\n" merge, returns the previous line.</returns>
271    DocumentLine SetLineLength(DocumentLine line, int newTotalLength)
272    {
273//      changedLines.Add(line);
274//      deletedOrChangedLines.Add(line);
275      int delta = newTotalLength - line.TotalLength;
276      if (delta != 0) {
277        foreach (ILineTracker lt in lineTrackers)
278          lt.SetLineLength(line, newTotalLength);
279        line.TotalLength = newTotalLength;
280        DocumentLineTree.UpdateAfterChildrenChange(line);
281      }
282      // determine new DelimiterLength
283      if (newTotalLength == 0) {
284        line.DelimiterLength = 0;
285      } else {
286        int lineOffset = line.Offset;
287        char lastChar = document.GetCharAt(lineOffset + newTotalLength - 1);
288        if (lastChar == '\r') {
289          line.DelimiterLength = 1;
290        } else if (lastChar == '\n') {
291          if (newTotalLength >= 2 && document.GetCharAt(lineOffset + newTotalLength - 2) == '\r') {
292            line.DelimiterLength = 2;
293          } else if (newTotalLength == 1 && lineOffset > 0 && document.GetCharAt(lineOffset - 1) == '\r') {
294            // we need to join this line with the previous line
295            DocumentLine previousLine = line.PreviousLine;
296            RemoveLine(line);
297            return SetLineLength(previousLine, previousLine.TotalLength + 1);
298          } else {
299            line.DelimiterLength = 1;
300          }
301        } else {
302          line.DelimiterLength = 0;
303        }
304      }
305      return line;
306    }
307    #endregion
308   
309    #region ChangeComplete
310    public void ChangeComplete(DocumentChangeEventArgs e)
311    {
312      foreach (ILineTracker lt in lineTrackers) {
313        lt.ChangeComplete(e);
314      }
315    }
316    #endregion
317  }
318}
Note: See TracBrowser for help on using the repository browser.