Code Coverage Statistics for Source File

c:\Tools\SD3\src\Libraries\ICSharpCode.TextEditor\Project\Src\Document\LineManager\LineManager.cs

Sequence Point Coverage
N/A
0 of 0
Branch Coverage
N/A
0 of 0
Lines
364
Highlight: Uncovered Code Covered Code
L V Source
1
// <file>
2
//     <copyright see="prj:///doc/copyright.txt"/>
3
//     <license see="prj:///doc/license.txt"/>
4
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
5
//     <version>$Revision: 2691 $</version>
6
// </file>
7
8
using System;
9
using System.Collections.Generic;
10
using System.Diagnostics;
11
12
namespace ICSharpCode.TextEditor.Document
13
{
14
	internal sealed class LineManager
15
	{
16
		LineSegmentTree lineCollection = new LineSegmentTree();
17
		
18
		IDocument document;
19
		IHighlightingStrategy highlightingStrategy;
20
		
21
		public IList<LineSegment> LineSegmentCollection {
22
			get {
23
				return lineCollection;
24
			}
25
		}
26
		
27
		public int TotalNumberOfLines {
28
			get {
29
				return lineCollection.Count;
30
			}
31
		}
32
		
33
		public IHighlightingStrategy HighlightingStrategy {
34
			get {
35
				return highlightingStrategy;
36
			}
37
			set {
38
				if (highlightingStrategy != value) {
39
					highlightingStrategy = value;
40
					if (highlightingStrategy != null) {
41
						highlightingStrategy.MarkTokens(document);
42
					}
43
				}
44
			}
45
		}
46
		
47
		public LineManager(IDocument document, IHighlightingStrategy highlightingStrategy)
48
		{
49
			this.document = document;
50
			this.highlightingStrategy = highlightingStrategy;
51
		}
52
		
53
		public int GetLineNumberForOffset(int offset)
54
		{
55
			return GetLineSegmentForOffset(offset).LineNumber;
56
		}
57
		
58
		public LineSegment GetLineSegmentForOffset(int offset)
59
		{
60
			return lineCollection.GetByOffset(offset);
61
		}
62
		
63
		public LineSegment GetLineSegment(int lineNr)
64
		{
65
			return lineCollection[lineNr];
66
		}
67
		
68
		public void Insert(int offset, string text)
69
		{
70
			Replace(offset, 0, text);
71
		}
72
		
73
		public void Remove(int offset, int length)
74
		{
75
			Replace(offset, length, String.Empty);
76
		}
77
		
78
		public void Replace(int offset, int length, string text)
79
		{
80
//			Console.WriteLine("Replace offset="+offset+" length="+length+" text.Length="+text.Length);
81
			int lineStart = GetLineNumberForOffset(offset);
82
			int oldNumberOfLines = this.TotalNumberOfLines;
83
			List<LineSegment> removedLines = RemoveInternal(offset, length);
84
			int numberOfLinesAfterRemoving = this.TotalNumberOfLines;
85
			if (!string.IsNullOrEmpty(text)) {
86
				InsertInternal(offset, text);
87
			}
88
//			#if DEBUG
89
//			Console.WriteLine("New line collection:");
90
//			Console.WriteLine(lineCollection.GetTreeAsString());
91
//			Console.WriteLine("New text:");
92
//			Console.WriteLine("'" + document.TextContent + "'");
93
//			#endif
94
			RunHighlighter(lineStart, 1 + Math.Max(0, this.TotalNumberOfLines - numberOfLinesAfterRemoving));
95
			if (removedLines != null) {
96
				foreach (LineSegment ls in removedLines)
97
					OnLineDeleted(new LineEventArgs(document, ls));
98
			}
99
			if (this.TotalNumberOfLines != oldNumberOfLines) {
100
				OnLineCountChanged(new LineCountChangeEventArgs(document, lineStart, this.TotalNumberOfLines - oldNumberOfLines));
101
			}
102
		}
103
		
104
		List<LineSegment> RemoveInternal(int offset, int length)
105
		{
106
			Debug.Assert(length >= 0);
107
			if (length == 0) return null;
108
			LineSegmentTree.Enumerator it = lineCollection.GetEnumeratorForOffset(offset);
109
			LineSegment startSegment = it.Current;
110
			int startSegmentOffset = startSegment.Offset;
111
			if (offset + length < startSegmentOffset + startSegment.TotalLength) {
112
				// just removing a part of this line segment
113
				startSegment.RemovedLinePart(offset - startSegmentOffset, length);
114
				SetSegmentLength(startSegment, startSegment.TotalLength - length);
115
				return null;
116
			}
117
			// merge startSegment with another line segment because startSegment's delimiter was deleted
118
			// possibly remove lines in between if multiple delimiters were deleted
119
			int charactersRemovedInStartLine = startSegmentOffset + startSegment.TotalLength - offset;
120
			Debug.Assert(charactersRemovedInStartLine > 0);
121
			startSegment.RemovedLinePart(offset - startSegmentOffset, charactersRemovedInStartLine);
122
			
123
			
124
			LineSegment endSegment = lineCollection.GetByOffset(offset + length);
125
			if (endSegment == startSegment) {
126
				// special case: we are removing a part of the last line up to the
127
				// end of the document
128
				SetSegmentLength(startSegment, startSegment.TotalLength - length);
129
				return null;
130
			}
131
			int endSegmentOffset = endSegment.Offset;
132
			int charactersLeftInEndLine = endSegmentOffset + endSegment.TotalLength - (offset + length);
133
			endSegment.RemovedLinePart(0, endSegment.TotalLength - charactersLeftInEndLine);
134
			startSegment.MergedWith(endSegment, offset - startSegmentOffset);
135
			SetSegmentLength(startSegment, startSegment.TotalLength - charactersRemovedInStartLine + charactersLeftInEndLine);
136
			startSegment.DelimiterLength = endSegment.DelimiterLength;
137
			// remove all segments between startSegment (excl.) and endSegment (incl.)
138
			it.MoveNext();
139
			List<LineSegment> removedLines = new List<LineSegment>();
140
			LineSegment segmentToRemove;
141
			do {
142
				segmentToRemove = it.Current;
143
				it.MoveNext();
144
				lineCollection.RemoveSegment(segmentToRemove);
145
				removedLines.Add(segmentToRemove);
146
				segmentToRemove.Deleted();
147
			} while (segmentToRemove != endSegment);
148
			return removedLines;
149
		}
150
		
151
		void InsertInternal(int offset, string text)
152
		{
153
			LineSegment segment = lineCollection.GetByOffset(offset);
154
			DelimiterSegment ds = NextDelimiter(text, 0);
155
			if (ds == null) {
156
				// no newline is being inserted, all text is inserted in a single line
157
				segment.InsertedLinePart(offset - segment.Offset, text.Length);
158
				SetSegmentLength(segment, segment.TotalLength + text.Length);
159
				return;
160
			}
161
			LineSegment firstLine = segment;
162
			firstLine.InsertedLinePart(offset - firstLine.Offset, ds.Offset);
163
			int lastDelimiterEnd = 0;
164
			while (ds != null) {
165
				// split line segment at line delimiter
166
				int lineBreakOffset = offset + ds.Offset + ds.Length;
167
				int segmentOffset = segment.Offset;
168
				int lengthAfterInsertionPos = segmentOffset + segment.TotalLength - (offset + lastDelimiterEnd);
169
				lineCollection.SetSegmentLength(segment, lineBreakOffset - segmentOffset);
170
				LineSegment newSegment = lineCollection.InsertSegmentAfter(segment, lengthAfterInsertionPos);
171
				segment.DelimiterLength = ds.Length;
172
				
173
				segment = newSegment;
174
				lastDelimiterEnd = ds.Offset + ds.Length;
175
				
176
				ds = NextDelimiter(text, lastDelimiterEnd);
177
			}
178
			firstLine.SplitTo(segment);
179
			// insert rest after last delimiter
180
			if (lastDelimiterEnd != text.Length) {
181
				segment.InsertedLinePart(0, text.Length - lastDelimiterEnd);
182
				SetSegmentLength(segment, segment.TotalLength + text.Length - lastDelimiterEnd);
183
			}
184
		}
185
		
186
		void SetSegmentLength(LineSegment segment, int newTotalLength)
187
		{
188
			int delta = newTotalLength - segment.TotalLength;
189
			if (delta != 0) {
190
				lineCollection.SetSegmentLength(segment, newTotalLength);
191
				OnLineLengthChanged(new LineLengthChangeEventArgs(document, segment, delta));
192
			}
193
		}
194
		
195
		void RunHighlighter(int firstLine, int lineCount)
196
		{
197
			if (highlightingStrategy != null) {
198
				List<LineSegment> markLines = new List<LineSegment>();
199
				LineSegmentTree.Enumerator it = lineCollection.GetEnumeratorForIndex(firstLine);
200
				for (int i = 0; i < lineCount && it.IsValid; i++) {
201
					markLines.Add(it.Current);
202
					it.MoveNext();
203
				}
204
				highlightingStrategy.MarkTokens(document, markLines);
205
			}
206
		}
207
		
208
		public void SetContent(string text)
209
		{
210
			lineCollection.Clear();
211
			if (text != null) {
212
				Replace(0, 0, text);
213
			}
214
		}
215
		
216
		public int GetVisibleLine(int logicalLineNumber)
217
		{
218
			if (!document.TextEditorProperties.EnableFolding) {
219
				return logicalLineNumber;
220
			}
221
			
222
			int visibleLine = 0;
223
			int foldEnd = 0;
224
			List<FoldMarker> foldings = document.FoldingManager.GetTopLevelFoldedFoldings();
225
			foreach (FoldMarker fm in foldings) {
226
				if (fm.StartLine >= logicalLineNumber) {
227
					break;
228
				}
229
				if (fm.StartLine >= foldEnd) {
230
					visibleLine += fm.StartLine - foldEnd;
231
					if (fm.EndLine > logicalLineNumber) {
232
						return visibleLine;
233
					}
234
					foldEnd = fm.EndLine;
235
				}
236
			}
237
//			Debug.Assert(logicalLineNumber >= foldEnd);
238
			visibleLine += logicalLineNumber - foldEnd;
239
			return visibleLine;
240
		}
241
		
242
		public int GetFirstLogicalLine(int visibleLineNumber)
243
		{
244
			if (!document.TextEditorProperties.EnableFolding) {
245
				return visibleLineNumber;
246
			}
247
			int v = 0;
248
			int foldEnd = 0;
249
			List<FoldMarker> foldings = document.FoldingManager.GetTopLevelFoldedFoldings();
250
			foreach (FoldMarker fm in foldings) {
251
				if (fm.StartLine >= foldEnd) {
252
					if (v + fm.StartLine - foldEnd >= visibleLineNumber) {
253
						break;
254
					}
255
					v += fm.StartLine - foldEnd;
256
					foldEnd = fm.EndLine;
257
				}
258
			}
259
			// help GC
260
			foldings.Clear();
261
			foldings = null;
262
			return foldEnd + visibleLineNumber - v;
263
		}
264
		
265
		public int GetLastLogicalLine(int visibleLineNumber)
266
		{
267
			if (!document.TextEditorProperties.EnableFolding) {
268
				return visibleLineNumber;
269
			}
270
			return GetFirstLogicalLine(visibleLineNumber + 1) - 1;
271
		}
272
		
273
		// TODO : speedup the next/prev visible line search
274
		// HOW? : save the foldings in a sorted list and lookup the
275
		//        line numbers in this list
276
		public int GetNextVisibleLineAbove(int lineNumber, int lineCount)
277
		{
278
			int curLineNumber = lineNumber;
279
			if (document.TextEditorProperties.EnableFolding) {
280
				for (int i = 0; i < lineCount && curLineNumber < TotalNumberOfLines; ++i) {
281
					++curLineNumber;
282
					while (curLineNumber < TotalNumberOfLines && (curLineNumber >= lineCollection.Count || !document.FoldingManager.IsLineVisible(curLineNumber))) {
283
						++curLineNumber;
284
					}
285
				}
286
			} else {
287
				curLineNumber += lineCount;
288
			}
289
			return Math.Min(TotalNumberOfLines - 1, curLineNumber);
290
		}
291
		
292
		public int GetNextVisibleLineBelow(int lineNumber, int lineCount)
293
		{
294
			int curLineNumber = lineNumber;
295
			if (document.TextEditorProperties.EnableFolding) {
296
				for (int i = 0; i < lineCount; ++i) {
297
					--curLineNumber;
298
					while (curLineNumber >= 0 && !document.FoldingManager.IsLineVisible(curLineNumber)) {
299
						--curLineNumber;
300
					}
301
				}
302
			} else {
303
				curLineNumber -= lineCount;
304
			}
305
			return Math.Max(0, curLineNumber);
306
		}
307
		
308
		// use always the same DelimiterSegment object for the NextDelimiter
309
		DelimiterSegment delimiterSegment = new DelimiterSegment();
310
		
311
		DelimiterSegment NextDelimiter(string text, int offset)
312
		{
313
			for (int i = offset; i < text.Length; i++) {
314
				switch (text[i]) {
315
					case '\r':
316
						if (i + 1 < text.Length) {
317
							if (text[i + 1] == '\n') {
318
								delimiterSegment.Offset = i;
319
								delimiterSegment.Length = 2;
320
								return delimiterSegment;
321
							}
322
						}
323
						goto case '\n';
324
					case '\n':
325
						delimiterSegment.Offset = i;
326
						delimiterSegment.Length = 1;
327
						return delimiterSegment;
328
				}
329
			}
330
			return null;
331
		}
332
		
333
		void OnLineCountChanged(LineCountChangeEventArgs e)
334
		{
335
			if (LineCountChanged != null) {
336
				LineCountChanged(this, e);
337
			}
338
		}
339
		
340
		void OnLineLengthChanged(LineLengthChangeEventArgs e)
341
		{
342
			if (LineLengthChanged != null) {
343
				LineLengthChanged(this, e);
344
			}
345
		}
346
		
347
		void OnLineDeleted(LineEventArgs e)
348
		{
349
			if (LineDeleted != null) {
350
				LineDeleted(this, e);
351
			}
352
		}
353
		
354
		public event EventHandler<LineLengthChangeEventArgs> LineLengthChanged;
355
		public event EventHandler<LineCountChangeEventArgs> LineCountChanged;
356
		public event EventHandler<LineEventArgs> LineDeleted;
357
		
358
		sealed class DelimiterSegment
359
		{
360
			internal int Offset;
361
			internal int Length;
362
		}
363
	}
364
}