Skip to content

Commit a01d552

Browse files
author
Kapil Borle
committed
Add TextLines type for efficient line insertion and removal
Previous implementation used a List to maintain the lines of editable text. Inserting and removing at arbitrary points is expensive for Lists. So we use LinkedLists to store the lines, which makes inserting and removing lines a constant operation. However, this makes accessing a line through an index an expensive operation. The performance on this can easily be improved by maintaining a last accessed index/node.
1 parent f3f3b25 commit a01d552

1 file changed

Lines changed: 172 additions & 6 deletions

File tree

Engine/EditableText.cs

Lines changed: 172 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections;
23
using System.Collections.Generic;
34
using System.Linq;
45
using System.Management.Automation.Language;
@@ -53,9 +54,8 @@ public EditableText ApplyEdit1(TextEdit textEdit)
5354
{
5455
ValidateTextEdit(textEdit);
5556

56-
// Break up the change lines
57-
var changeLines = textEdit.Lines;
58-
var Lines = new List<String>(this.Lines);
57+
var editLines = textEdit.Lines;
58+
var Lines = new TextLines(this.Lines);
5959

6060
// Get the first fragment of the first line
6161
string firstLineFragment =
@@ -77,19 +77,19 @@ public EditableText ApplyEdit1(TextEdit textEdit)
7777

7878
// Build and insert the new lines
7979
int currentLineNumber = textEdit.StartLineNumber;
80-
for (int changeIndex = 0; changeIndex < changeLines.Length; changeIndex++)
80+
for (int changeIndex = 0; changeIndex < editLines.Length; changeIndex++)
8181
{
8282
// Since we split the lines above using \n, make sure to
8383
// trim the ending \r's off as well.
84-
string finalLine = changeLines[changeIndex].TrimEnd('\r');
84+
string finalLine = editLines[changeIndex].TrimEnd('\r');
8585

8686
// Should we add first or last line fragments?
8787
if (changeIndex == 0)
8888
{
8989
// Append the first line fragment
9090
finalLine = firstLineFragment + finalLine;
9191
}
92-
if (changeIndex == changeLines.Length - 1)
92+
if (changeIndex == editLines.Length - 1)
9393
{
9494
// Append the last line fragment
9595
finalLine = finalLine + lastLineFragment;
@@ -182,5 +182,171 @@ private int GetNumNewLineCharacters()
182182

183183
return numCharDiff / (Lines.Length - 1);
184184
}
185+
186+
private class TextLines : IList<String>
187+
{
188+
private LinkedList<String> lines;
189+
190+
private void ValidateIndex(int index)
191+
{
192+
if (index >= Count || index < 0)
193+
{
194+
throw new ArgumentOutOfRangeException(nameof(index));
195+
}
196+
}
197+
198+
private LinkedListNode<String> GetNodeAt(int index)
199+
{
200+
var node = lines.First;
201+
int count = 0;
202+
while (node != null)
203+
{
204+
if (count++ == index)
205+
{
206+
return node;
207+
}
208+
209+
node = node.Next;
210+
}
211+
212+
throw new InvalidOperationException();
213+
}
214+
215+
public string this[int index]
216+
{
217+
get
218+
{
219+
ValidateIndex(index);
220+
return GetNodeAt(index).Value;
221+
}
222+
set
223+
{
224+
ValidateIndex(index);
225+
Insert(index, value);
226+
RemoveAt(index);
227+
}
228+
}
229+
230+
public int Count { get; private set; }
231+
232+
public bool IsReadOnly => false;
233+
234+
public TextLines()
235+
{
236+
lines = new LinkedList<String>();
237+
Count = 0;
238+
}
239+
240+
public TextLines(IEnumerable<String> inputLines)
241+
{
242+
if (inputLines == null)
243+
{
244+
throw new ArgumentNullException(nameof(inputLines));
245+
}
246+
247+
if (inputLines.Any(line => line == null))
248+
{
249+
// todo localize
250+
throw new ArgumentException("Line element cannot be null.");
251+
}
252+
253+
lines = new LinkedList<String>(inputLines);
254+
Count = lines.Count;
255+
}
256+
257+
public void Add(string item)
258+
{
259+
if (item == null)
260+
{
261+
throw new ArgumentNullException(nameof(item));
262+
}
263+
264+
Insert(Count - 1, item);
265+
}
266+
267+
public void Clear()
268+
{
269+
lines.Clear();
270+
}
271+
272+
public bool Contains(string item)
273+
{
274+
return lines.Contains(item);
275+
}
276+
277+
public void CopyTo(string[] array, int arrayIndex)
278+
{
279+
lines.CopyTo(array, arrayIndex);
280+
}
281+
282+
public IEnumerator<string> GetEnumerator()
283+
{
284+
return lines.GetEnumerator();
285+
}
286+
287+
public int IndexOf(string item)
288+
{
289+
var llNode = lines.First;
290+
int count = 0;
291+
while (llNode != null)
292+
{
293+
if (llNode.Value.Equals(item))
294+
{
295+
return count;
296+
}
297+
298+
llNode = llNode.Next;
299+
count++;
300+
}
301+
302+
return -1;
303+
}
304+
305+
public void Insert(int index, string item)
306+
{
307+
ValidateIndex(index);
308+
if (index == 0)
309+
{
310+
lines.AddFirst(item);
311+
}
312+
else if (index == Count - 1)
313+
{
314+
lines.AddBefore(lines.Last, item);
315+
}
316+
else
317+
{
318+
lines.AddBefore(GetNodeAt(index), item);
319+
}
320+
321+
Count++;
322+
}
323+
324+
public bool Remove(string item)
325+
{
326+
if (lines.Remove(item))
327+
{
328+
Count--;
329+
return true;
330+
}
331+
332+
return false;
333+
}
334+
335+
public void RemoveAt(int index)
336+
{
337+
lines.Remove(GetNodeAt(index));
338+
Count--;
339+
}
340+
341+
IEnumerator IEnumerable.GetEnumerator()
342+
{
343+
return lines.GetEnumerator();
344+
}
345+
346+
public override string ToString()
347+
{
348+
return String.Join(Environment.NewLine, lines);
349+
}
350+
}
185351
}
186352
}

0 commit comments

Comments
 (0)