From ea133bf03ac371d3472cf5c6328c2065b470426d Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 27 Sep 2022 23:54:34 +0200 Subject: [PATCH] Allow storing history for input --- Sharprompt/Forms/InputForm.cs | 12 ++ Sharprompt/Internal/InputHistory.cs | 286 ++++++++++++++++++++++++++++ Sharprompt/Sharprompt.csproj | 1 + 3 files changed, 299 insertions(+) create mode 100644 Sharprompt/Internal/InputHistory.cs diff --git a/Sharprompt/Forms/InputForm.cs b/Sharprompt/Forms/InputForm.cs index aae7b3fa..c5f28730 100644 --- a/Sharprompt/Forms/InputForm.cs +++ b/Sharprompt/Forms/InputForm.cs @@ -14,12 +14,17 @@ public InputForm(InputOptions options) _options = options; _defaultValue = Optional.Create(options.DefaultValue); + + _navi = InputHistory.Shared.CreateHandler(this); } private readonly InputOptions _options; private readonly Optional _defaultValue; private readonly TextInputBuffer _textInputBuffer = new(); + private InputHistory.Handler _navi; + + internal TextInputBuffer GetInput() => _textInputBuffer; protected override bool TryGetResult(out T result) { @@ -70,6 +75,12 @@ protected override bool TryGetResult(out T result) case ConsoleKey.Delete: ConsoleDriver.Beep(); break; + case ConsoleKey.UpArrow: + _navi.HistoryNext(); + break; + case ConsoleKey.DownArrow: + _navi.HistoryPrevious(); + break; default: if (!char.IsControl(keyInfo.KeyChar)) { @@ -116,6 +127,7 @@ protected override void FinishTemplate(OffscreenBuffer offscreenBuffer, T result private bool HandleEnter(out T result) { var input = _textInputBuffer.ToString(); + _navi.AddHistory(input); try { diff --git a/Sharprompt/Internal/InputHistory.cs b/Sharprompt/Internal/InputHistory.cs new file mode 100644 index 00000000..b1fc4015 --- /dev/null +++ b/Sharprompt/Internal/InputHistory.cs @@ -0,0 +1,286 @@ +using System; +using System.Diagnostics; +using CircularBuffer; + +using Sharprompt.Forms; + +namespace Sharprompt.Internal; + +/// +/// Ringbuffer-cache used to allow navigation of recent input. +/// +internal sealed class InputHistory +{ + internal static int DefaultCapacity => 512; + + private static readonly Lazy s_shared = new(() => new()); + + public static InputHistory Shared => s_shared.Value; + + private CircularBuffer _buffer; + private Entry _current; + private int _version; + + public InputHistory() : this(DefaultCapacity) {} + + public InputHistory(int capacity) + { + _buffer = new(capacity); + } + + public int Count => _buffer.Size; + + /// + /// Sets the current (-1) history entry. + /// + public void SetCurrent(string value) + { + _current = String.IsNullOrEmpty(value) ? default : new(value.GetHashCode(), value); + } + + /// + /// Commits the current history entry (-1) to the history, and clears the current entry + /// + public void AddCurrent() + { + Add(_current); + _current = default; + } + + /// + /// Adds a history entry. If value is null or empty does nothing. + /// + public void Add(string value) + { + if (string.IsNullOrEmpty(value)) + { + return; + } + Add(new Entry(value.GetHashCode(), value)); + } + + private void Add(in Entry e) + { + int idx = Find(e); + if (idx != -1) + { + Remove(idx); + } + _buffer.PushFront(e); + _version++; + } + + /// + /// Gets the history entry at a position relative to the current history + /// + public string Get(int pos) + { + if (pos == -1) + { + return _current.Value; + } + if (_buffer.Size < pos) + { + return _buffer[pos].Value; + } + + return null; + } + + private int Find(in Entry e) + { + int pos = -1; + foreach (Entry entry in _buffer) + { + pos += 1; + if (e.Hash != entry.Hash) { + continue; + } + + if (e.Value.Equals(entry.Value, StringComparison.Ordinal)) + { + return pos; + } + } + + return -1; + } + + private void Remove(int idx) + { + int newSize = _buffer.Size - 1; + if (newSize <= 0) + { + _buffer.Clear(); + return; + } + Entry[] tmp = new Entry[_buffer.Size - 1]; + for (int i = 0; i < idx; i++) + { + tmp[i] = _buffer[i]; + } + + for (int i = idx + 1; i < _buffer.Size; i++) + { + tmp[i - 1] = _buffer[i]; + } + + _buffer = new(_buffer.Capacity, tmp); + } + + public Navigator GetEnumerator() => new(this); + + public Handler CreateHandler(InputForm form) + { + return new(GetEnumerator(), form); + } + + [DebuggerDisplay("{Value},nq")] + readonly struct Entry + { + public readonly int Hash; + public readonly string Value; + + public Entry(int hash, string value) + { + Hash = hash; + Value = value; + } + } + + /// + /// Allows navigating the current history + /// + public struct Navigator + { + private readonly InputHistory _history; + private int _version; + private int _position; + + internal Navigator(InputHistory history) + { + _history = history; + _version = history._version; + _position = -1; + } + + public InputHistory History => _history; + + public int Position + { + get + { + EnsureVersion(); + return _position; + } + } + + public bool IsFront + { + get + { + EnsureVersion(); + return _position == -1; + } + } + + public bool IsEnd + { + get + { + EnsureVersion(); + return _position == _history.Count; + } + } + + public string Current + { + get + { + EnsureVersion(); + return _history.Get(_position); + } + } + + public bool MoveNext() + { + EnsureVersion(); + if (_position < _history.Count) + { + _position += 1; + return true; + } + + return false; + } + + public bool MovePrevious() + { + EnsureVersion(); + if (_position > -1) + { + _position -= 1; + return true; + } + + return false; + } + + private void EnsureVersion() + { + if (_version == _history._version) + { + _position = -1; + _version = _history._version; + } + } + } + + public struct Handler + { + private Navigator _navi; + private readonly InputForm _form; + + internal Handler(Navigator navi, InputForm form) + { + _navi = navi; + _form = form; + } + + public void HistoryNext() + { + if (_navi.IsFront) + { + _navi.History.SetCurrent(_form.GetInput().ToString()); + } + + if (_navi.MoveNext()) + { + // TODO: set input to _navi.Current + + } + } + + public void HistoryPrevious() + { + if (_navi.IsFront) + { + // TODO: clear input & set input to _navi.History.Current + return; + } + + if (_navi.MovePrevious()) + { + // TODO: clear input & set input to _navi.Current + } + } + + public void AddHistory(string input) + { + // Add item to history + _navi.History.SetCurrent(input); + _navi.History.AddCurrent(); + } + + } +} diff --git a/Sharprompt/Sharprompt.csproj b/Sharprompt/Sharprompt.csproj index 5107a703..257bdf38 100644 --- a/Sharprompt/Sharprompt.csproj +++ b/Sharprompt/Sharprompt.csproj @@ -20,6 +20,7 @@ +