From e51cd2018ee234fe608c3b6dbeceba6aaec39ac5 Mon Sep 17 00:00:00 2001 From: malcomvetter Date: Mon, 2 Jul 2018 08:27:33 -0500 Subject: [PATCH] Improve screen repaint speed --- src/ReadLine.Demo/Program.cs | 5 +- src/ReadLine/KeyHandler.cs | 179 +++++++++++++------------ src/ReadLine/ReadLine.cs | 36 +++-- test/ReadLine.Tests/KeyHandlerTests.cs | 8 +- test/ReadLine.Tests/ReadLineTests.cs | 3 +- 5 files changed, 133 insertions(+), 98 deletions(-) diff --git a/src/ReadLine.Demo/Program.cs b/src/ReadLine.Demo/Program.cs index 29b484e..eec53f7 100755 --- a/src/ReadLine.Demo/Program.cs +++ b/src/ReadLine.Demo/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace ConsoleApplication { @@ -10,7 +11,7 @@ public static void Main(string[] args) Console.WriteLine("---------------------"); Console.WriteLine(); - string[] history = new string[] { "ls -a", "dotnet run", "git init" }; + var history = new List { "ls -a", "dotnet run", "git init" }; ReadLine.AddHistory(history); ReadLine.AutoCompletionHandler = new AutoCompletionHandler(); @@ -31,7 +32,7 @@ public string[] GetSuggestions(string text, int index) if (text.StartsWith("git ")) return new string[] { "init", "clone", "pull", "push" }; else - return null; + return new string[] { "git", "ls", "cd", "pwd" }; } } } diff --git a/src/ReadLine/KeyHandler.cs b/src/ReadLine/KeyHandler.cs index 046d11d..a7ef8bc 100644 --- a/src/ReadLine/KeyHandler.cs +++ b/src/ReadLine/KeyHandler.cs @@ -8,8 +8,7 @@ namespace Internal.ReadLine { internal class KeyHandler { - private int _cursorPos; - private int _cursorLimit; + private int _promptLength; private StringBuilder _text; private List _history; private int _historyIndex; @@ -20,9 +19,9 @@ internal class KeyHandler private int _completionsIndex; private IConsole Console2; - private bool IsStartOfLine() => _cursorPos == 0; + private bool IsStartOfLine() => Console2.CursorLeft == 0; - private bool IsEndOfLine() => _cursorPos == _cursorLimit; + private bool IsEndOfLine() => Console2.CursorLeft == _text.Length + _promptLength; private bool IsStartOfBuffer() => Console2.CursorLeft == 0; @@ -34,18 +33,18 @@ private void MoveCursorLeft() if (IsStartOfLine()) return; + if (Console2.CursorLeft == _promptLength) + return; + if (IsStartOfBuffer()) Console2.SetCursorPosition(Console2.BufferWidth - 1, Console2.CursorTop - 1); else Console2.SetCursorPosition(Console2.CursorLeft - 1, Console2.CursorTop); - - _cursorPos--; } private void MoveCursorHome() { - while (!IsStartOfLine()) - MoveCursorLeft(); + Console2.SetCursorPosition(_promptLength, Console2.CursorTop); } private string BuildKeyInput() @@ -57,14 +56,18 @@ private string BuildKeyInput() private void MoveCursorRight() { if (IsEndOfLine()) + { return; + } if (IsEndOfBuffer()) + { Console2.SetCursorPosition(0, Console2.CursorTop + 1); + } else + { Console2.SetCursorPosition(Console2.CursorLeft + 1, Console2.CursorTop); - - _cursorPos++; + } } private void MoveCursorEnd() @@ -75,140 +78,148 @@ private void MoveCursorEnd() private void ClearLine() { - MoveCursorEnd(); - while (!IsStartOfLine()) - Backspace(); + ClearLine(_promptLength); } - - private void WriteNewString(string str) + + private void ClearLine(int startPos) { - ClearLine(); - foreach (char character in str) - WriteChar(character); + var clear = ""; + for (var i = 0; i < Console2.BufferWidth - startPos; i++) + { + clear += " "; + } + Console2.SetCursorPosition(startPos, Console2.CursorTop); + Console2.Write(clear); + Console2.SetCursorPosition(startPos, Console2.CursorTop); } private void WriteString(string str) { - foreach (char character in str) - WriteChar(character); + _text.Clear(); + _text.Append(str); + Console2.Write(_text.ToString()); } private void WriteChar() => WriteChar(_keyInfo.KeyChar); private void WriteChar(char c) { - if (IsEndOfLine()) + if (Console2.CursorLeft >= _text.Length + _promptLength) { _text.Append(c); Console2.Write(c.ToString()); - _cursorPos++; } else { - int left = Console2.CursorLeft; - int top = Console2.CursorTop; - string str = _text.ToString().Substring(_cursorPos); - _text.Insert(_cursorPos, c); - Console2.Write(c.ToString() + str); - Console2.SetCursorPosition(left, top); - MoveCursorRight(); + int origPos = Console2.CursorLeft; + _text.Insert(origPos - _promptLength, c); + ClearLine(); + Console2.Write(_text.ToString()); + Console2.SetCursorPosition(origPos + 1, Console2.CursorTop); } - - _cursorLimit++; } private void Backspace() { if (IsStartOfLine()) + { + ResetAutoComplete(); return; - - MoveCursorLeft(); - int index = _cursorPos; - _text.Remove(index, 1); - string replacement = _text.ToString().Substring(index); - int left = Console2.CursorLeft; - int top = Console2.CursorTop; - Console2.Write(string.Format("{0} ", replacement)); - Console2.SetCursorPosition(left, top); - _cursorLimit--; + } + if (Console2.CursorLeft > _promptLength) + { + int origPos = Console2.CursorLeft; + _text.Remove(origPos - _promptLength - 1, 1); + ClearLine(); + WriteString(_text.ToString()); + Console2.SetCursorPosition(origPos -1, Console2.CursorTop); + } } private void Delete() { if (IsEndOfLine()) + { return; + } - int index = _cursorPos; - _text.Remove(index, 1); - string replacement = _text.ToString().Substring(index); - int left = Console2.CursorLeft; - int top = Console2.CursorTop; - Console2.Write(string.Format("{0} ", replacement)); - Console2.SetCursorPosition(left, top); - _cursorLimit--; + int origPos = Console2.CursorLeft; + if ((Console2.CursorLeft - _promptLength) < _text.Length) + { + _text.Remove(Console2.CursorLeft - _promptLength, 1); + ClearLine(); + WriteString(_text.ToString()); + Console2.SetCursorPosition(origPos, Console2.CursorTop); + } } private void TransposeChars() { - // local helper functions - bool almostEndOfLine() => (_cursorLimit - _cursorPos) == 1; + bool almostEndOfLine() => (Console2.BufferWidth - Console2.CursorLeft) == 1; int incrementIf(Func expression, int index) => expression() ? index + 1 : index; int decrementIf(Func expression, int index) => expression() ? index - 1 : index; if (IsStartOfLine()) { return; } - var firstIdx = decrementIf(IsEndOfLine, _cursorPos - 1); - var secondIdx = decrementIf(IsEndOfLine, _cursorPos); + var firstIdx = decrementIf(IsEndOfLine, Console2.CursorLeft - 1); + var secondIdx = decrementIf(IsEndOfLine, Console2.CursorLeft); var secondChar = _text[secondIdx]; _text[secondIdx] = _text[firstIdx]; _text[firstIdx] = secondChar; var left = incrementIf(almostEndOfLine, Console2.CursorLeft); - var cursorPosition = incrementIf(almostEndOfLine, _cursorPos); + var cursorPosition = incrementIf(almostEndOfLine, Console2.CursorLeft); - WriteNewString(_text.ToString()); + ClearLine(); + WriteString(_text.ToString()); Console2.SetCursorPosition(left, Console2.CursorTop); - _cursorPos = cursorPosition; - MoveCursorRight(); } private void StartAutoComplete() { - while (_cursorPos > _completionStart) - Backspace(); - + ClearLine(_completionStart + _promptLength); _completionsIndex = 0; - - WriteString(_completions[_completionsIndex]); + + WriteAutoComplete(); } private void NextAutoComplete() { - while (_cursorPos > _completionStart) - Backspace(); - + ClearLine(_completionStart + _promptLength); _completionsIndex++; if (_completionsIndex == _completions.Length) _completionsIndex = 0; - WriteString(_completions[_completionsIndex]); + WriteAutoComplete(); } private void PreviousAutoComplete() { - while (_cursorPos > _completionStart) - Backspace(); - + ClearLine(_completionStart + _promptLength); _completionsIndex--; if (_completionsIndex == -1) _completionsIndex = _completions.Length - 1; - WriteString(_completions[_completionsIndex]); + WriteAutoComplete(); + } + + private void WriteAutoComplete() + { + if (_text.ToString().Contains(" ")) + { + var separator = _text.ToString().LastIndexOf(' '); + ClearLine(); + WriteString(_text.ToString().Substring(0, separator) + " " + _completions[_completionsIndex]); + } + else + { + WriteString(_completions[_completionsIndex]); + } } private void PrevHistory() @@ -216,7 +227,8 @@ private void PrevHistory() if (_historyIndex > 0) { _historyIndex--; - WriteNewString(_history[_historyIndex]); + ClearLine(); + WriteString(_history[_historyIndex]); } } @@ -225,10 +237,11 @@ private void NextHistory() if (_historyIndex < _history.Count) { _historyIndex++; - if (_historyIndex == _history.Count) - ClearLine(); - else - WriteNewString(_history[_historyIndex]); + ClearLine(); + if (_historyIndex != _history.Count) + { + WriteString(_history[_historyIndex]); + } } } @@ -246,8 +259,9 @@ public string Text } } - public KeyHandler(IConsole console, List history, IAutoCompleteHandler autoCompleteHandler) + public KeyHandler(IConsole console, List history, IAutoCompleteHandler autoCompleteHandler, int promptLength) { + _promptLength = promptLength; Console2 = console; _history = history ?? new List(); @@ -280,14 +294,11 @@ public KeyHandler(IConsole console, List history, IAutoCompleteHandler a }; _keyActions["ControlK"] = () => { - int pos = _cursorPos; - MoveCursorEnd(); - while (_cursorPos > pos) - Backspace(); + ClearLine(Console2.CursorLeft); }; _keyActions["ControlW"] = () => { - while (!IsStartOfLine() && _text[_cursorPos - 1] != ' ') + while (!IsStartOfLine() && _text[Console2.CursorLeft - 1] != ' ') Backspace(); }; _keyActions["ControlT"] = TransposeChars; @@ -300,7 +311,7 @@ public KeyHandler(IConsole console, List history, IAutoCompleteHandler a } else { - if (autoCompleteHandler == null || !IsEndOfLine()) + if (autoCompleteHandler == null) return; string text = _text.ToString(); @@ -333,7 +344,9 @@ public void Handle(ConsoleKeyInfo keyInfo) // If in auto complete mode and Tab wasn't pressed if (IsInAutoCompleteMode() && _keyInfo.Key != ConsoleKey.Tab) + { ResetAutoComplete(); + } Action action; _keyActions.TryGetValue(BuildKeyInput(), out action); @@ -341,4 +354,4 @@ public void Handle(ConsoleKeyInfo keyInfo) action.Invoke(); } } -} +} \ No newline at end of file diff --git a/src/ReadLine/ReadLine.cs b/src/ReadLine/ReadLine.cs index 157cf66..61e3f18 100755 --- a/src/ReadLine/ReadLine.cs +++ b/src/ReadLine/ReadLine.cs @@ -1,6 +1,6 @@ using Internal.ReadLine; using Internal.ReadLine.Abstractions; - +using System; using System.Collections.Generic; namespace System @@ -14,7 +14,16 @@ static ReadLine() _history = new List(); } - public static void AddHistory(params string[] text) => _history.AddRange(text); + public static void AddHistory(List text) + { + _history.AddRange(text); + } + + public static void AddHistory(string text) + { + _history.Add(text); + } + public static List GetHistory() => _history; public static void ClearHistory() => _history = new List(); public static bool HistoryEnabled { get; set; } @@ -23,7 +32,7 @@ static ReadLine() public static string Read(string prompt = "", string @default = "") { Console.Write(prompt); - KeyHandler keyHandler = new KeyHandler(new Console2(), _history, AutoCompletionHandler); + KeyHandler keyHandler = new KeyHandler(new Console2(), _history, AutoCompletionHandler, prompt.Length); string text = GetText(keyHandler); if (String.IsNullOrWhiteSpace(text) && !String.IsNullOrWhiteSpace(@default)) @@ -32,18 +41,29 @@ public static string Read(string prompt = "", string @default = "") } else { - if (HistoryEnabled) - _history.Add(text); + if (HistoryEnabled && !string.IsNullOrWhiteSpace(text)) + { + if ((_history.Count == 0) || (_history[_history.Count-1] != text)) + { + _history.Add(text); + } + } } - return text; } public static string ReadPassword(string prompt = "") { Console.Write(prompt); - KeyHandler keyHandler = new KeyHandler(new Console2() { PasswordMode = true }, null, null); - return GetText(keyHandler); + KeyHandler keyHandler = new KeyHandler(new Console2() { PasswordMode = true }, null, null, prompt.Length); + return Reverse(GetText(keyHandler)); + } + + public static string Reverse( string s ) + { + char[] charArray = s.ToCharArray(); + Array.Reverse( charArray ); + return new string( charArray ); } private static string GetText(KeyHandler keyHandler) diff --git a/test/ReadLine.Tests/KeyHandlerTests.cs b/test/ReadLine.Tests/KeyHandlerTests.cs index 63fe29a..bc85298 100644 --- a/test/ReadLine.Tests/KeyHandlerTests.cs +++ b/test/ReadLine.Tests/KeyHandlerTests.cs @@ -23,10 +23,10 @@ public KeyHandlerTests() { _autoCompleteHandler = new AutoCompleteHandler(); _completions = _autoCompleteHandler.GetSuggestions("", 0); - _history = new List(new string[] { "dotnet run", "git init", "clear" }); + _history = new List() { "dotnet run", "git init", "clear" }; _console = new Console2(); - _keyHandler = new KeyHandler(_console, _history, null); + _keyHandler = new KeyHandler(_console, _history, null, 0); "Hello".Select(c => c.ToConsoleKeyInfo()) .ToList() @@ -315,7 +315,7 @@ public void TestTab() // Nothing happens when no auto complete handler is set Assert.Equal("Hello", _keyHandler.Text); - _keyHandler = new KeyHandler(new Console2(), _history, _autoCompleteHandler); + _keyHandler = new KeyHandler(new Console2(), _history, _autoCompleteHandler, 0); "Hi ".Select(c => c.ToConsoleKeyInfo()).ToList().ForEach(_keyHandler.Handle); @@ -333,7 +333,7 @@ public void TestBackwardsTab() // Nothing happens when no auto complete handler is set Assert.Equal("Hello", _keyHandler.Text); - _keyHandler = new KeyHandler(new Console2(), _history, _autoCompleteHandler); + _keyHandler = new KeyHandler(new Console2(), _history, _autoCompleteHandler, 0); "Hi ".Select(c => c.ToConsoleKeyInfo()).ToList().ForEach(_keyHandler.Handle); diff --git a/test/ReadLine.Tests/ReadLineTests.cs b/test/ReadLine.Tests/ReadLineTests.cs index 7debc6a..016cde2 100755 --- a/test/ReadLine.Tests/ReadLineTests.cs +++ b/test/ReadLine.Tests/ReadLineTests.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Collections.Generic; using Xunit; using static System.ReadLine; @@ -10,7 +11,7 @@ public class ReadLineTests : IDisposable { public ReadLineTests() { - string[] history = new string[] { "ls -a", "dotnet run", "git init" }; + var history = new List() { "ls -a", "dotnet run", "git init" }; AddHistory(history); }