Skip to content

Commit 9be92ea

Browse files
committed
Speed up pasting from console
Pasting from the console was slow for 2 primary reasons: * StringBuilder is pathologically slow if you "Insert" at the end * Rendering requires parsing, this was happening for every character The first issue is trival to fix - call Append when truly appending. To address the second issue, I'm queueing up characters when we want to read a character. If enough characters are queued up, we ignore requests to render if we've rendered recently. This is fine because we know we'll render soon because queued characters will come along that will force us to render again.
1 parent 964b513 commit 9be92ea

File tree

1 file changed

+53
-9
lines changed

1 file changed

+53
-9
lines changed

PSReadLine/ReadLine.cs

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Management.Automation;
88
using System.Management.Automation.Language;
99
using System.Text;
10+
using System.Threading;
1011

1112
namespace PSConsoleUtilities
1213
{
@@ -134,6 +135,8 @@ static KeyHandler MakeKeyHandler(Action<ConsoleKeyInfo?, object> action, string
134135
private ConsoleColor _initialBackgroundColor;
135136
private ConsoleColor _initialForegroundColor;
136137
private CHAR_INFO _space;
138+
private readonly Queue<ConsoleKeyInfo> _queuedKeys;
139+
private DateTime _lastRenderTime;
137140

138141
// History state
139142
private HistoryQueue<HistoryItem> _history;
@@ -184,7 +187,20 @@ public PSConsoleReadlineOptions Options
184187
[ExcludeFromCodeCoverage]
185188
private static ConsoleKeyInfo ReadKey()
186189
{
187-
var key = Console.ReadKey(true);
190+
var start = DateTime.Now;
191+
while (Console.KeyAvailable)
192+
{
193+
_singleton._queuedKeys.Enqueue(Console.ReadKey(true));
194+
if ((DateTime.Now - start).Milliseconds > 2)
195+
{
196+
// Don't spend too long in this loop if there are lots of queued keys
197+
break;
198+
}
199+
}
200+
201+
var key = _singleton._queuedKeys.Count > 0
202+
? _singleton._queuedKeys.Dequeue()
203+
: Console.ReadKey(true);
188204
_singleton._log.Key(key.KeyChar, key.Key, key.Modifiers);
189205
return key;
190206
}
@@ -426,8 +442,9 @@ private PSConsoleReadLine()
426442
_dispatchTable = new Dictionary<ConsoleKeyInfo, KeyHandler>(_cmdKeyMap);
427443
_chordDispatchTable = new Dictionary<ConsoleKeyInfo, Dictionary<ConsoleKeyInfo, KeyHandler>>();
428444

429-
_buffer = new StringBuilder();
445+
_buffer = new StringBuilder(8 * 1024);
430446
_savedCurrentLine = new HistoryItem();
447+
_queuedKeys = new Queue<ConsoleKeyInfo>();
431448

432449
_pushedEditGroupCount = new Stack<int>();
433450

@@ -468,6 +485,7 @@ private void Initialize()
468485
_tabCommandCount = 0;
469486

470487
_consoleBuffer = ReadBufferLines(_initialY, 1 + Options.ExtraPromptLineCount);
488+
_lastRenderTime = DateTime.Now;
471489
}
472490

473491
private static void Chord(ConsoleKeyInfo? key = null, object arg = null)
@@ -1394,7 +1412,16 @@ private void MoveCursor(int offset)
13941412
public static void Insert(char c)
13951413
{
13961414
_singleton.SaveEditItem(EditItemInsertChar.Create(c, _singleton._current));
1397-
_singleton._buffer.Insert(_singleton._current, c);
1415+
1416+
// Use Append if possible because Insert at end makes StringBuilder quite slow.
1417+
if (_singleton._current == _singleton._buffer.Length)
1418+
{
1419+
_singleton._buffer.Append(c);
1420+
}
1421+
else
1422+
{
1423+
_singleton._buffer.Insert(_singleton._current, c);
1424+
}
13981425
_singleton._current += 1;
13991426
_singleton.Render();
14001427
}
@@ -1406,7 +1433,16 @@ public static void Insert(char c)
14061433
public static void Insert(string s)
14071434
{
14081435
_singleton.SaveEditItem(EditItemInsertString.Create(s, _singleton._current));
1409-
_singleton._buffer.Insert(_singleton._current, s);
1436+
1437+
// Use Append if possible because Insert at end makes StringBuilder quite slow.
1438+
if (_singleton._current == _singleton._buffer.Length)
1439+
{
1440+
_singleton._buffer.Append(s);
1441+
}
1442+
else
1443+
{
1444+
_singleton._buffer.Insert(_singleton._current, s);
1445+
}
14101446
_singleton._current += s.Length;
14111447
_singleton.Render();
14121448
}
@@ -1650,8 +1686,12 @@ private string ParseInput()
16501686

16511687
private void Render()
16521688
{
1653-
// This function is not very effecient when pasting large chunks of text
1654-
// into the console.
1689+
// If there are a bunch of keys queued up, skip rendering if we've rendered
1690+
// recently.
1691+
if (_queuedKeys.Count > 10 && (DateTime.Now - _lastRenderTime).Milliseconds < 50)
1692+
{
1693+
return;
1694+
}
16551695

16561696
_renderForDemoNeeded = false;
16571697

@@ -1805,6 +1845,8 @@ private void Render()
18051845
{
18061846
Console.WindowTop = _initialY + bufferLineCount + 1 - Console.WindowHeight;
18071847
}
1848+
1849+
_lastRenderTime = DateTime.Now;
18081850
}
18091851

18101852
private static void WriteBufferLines(CHAR_INFO[] buffer, ref int top)
@@ -1968,16 +2010,18 @@ private COORD ConvertOffsetToCoordinates(int offset)
19682010
int y = _initialY + Options.ExtraPromptLineCount;
19692011

19702012
int bufferWidth = Console.BufferWidth;
2013+
var continuationPromptLength = Options.ContinuationPrompt.Length;
19712014
for (int i = 0; i < offset; i++)
19722015
{
1973-
if (_buffer[i] == '\n')
2016+
char c = _buffer[i];
2017+
if (c == '\n')
19742018
{
19752019
y += 1;
1976-
x = Options.ContinuationPrompt.Length;
2020+
x = continuationPromptLength;
19772021
}
19782022
else
19792023
{
1980-
x += char.IsControl(_buffer[i]) ? 2 : 1;
2024+
x += char.IsControl(c) ? 2 : 1;
19812025
// Wrap? No prompt when wrapping
19822026
if (x >= bufferWidth)
19832027
{

0 commit comments

Comments
 (0)