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 | |
---|
19 | using System; |
---|
20 | using System.Collections.Generic; |
---|
21 | using System.Diagnostics; |
---|
22 | using System.Linq; |
---|
23 | using ICSharpCode.NRefactory.Editor; |
---|
24 | |
---|
25 | namespace 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 | } |
---|