Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Sharprompt/Forms/InputForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ public InputForm(InputOptions<T> options)
_options = options;

_defaultValue = Optional<T>.Create(options.DefaultValue);

_navi = InputHistory.Shared.CreateHandler(this);
}

private readonly InputOptions<T> _options;
private readonly Optional<T> _defaultValue;

private readonly TextInputBuffer _textInputBuffer = new();
private InputHistory.Handler<T> _navi;

internal TextInputBuffer GetInput() => _textInputBuffer;

protected override bool TryGetResult(out T result)
{
Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -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
{
Expand Down
286 changes: 286 additions & 0 deletions Sharprompt/Internal/InputHistory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
using System;
using System.Diagnostics;
using CircularBuffer;

using Sharprompt.Forms;

namespace Sharprompt.Internal;

/// <summary>
/// Ringbuffer-cache used to allow navigation of recent input.
/// </summary>
internal sealed class InputHistory
{
internal static int DefaultCapacity => 512;

private static readonly Lazy<InputHistory> s_shared = new(() => new());

public static InputHistory Shared => s_shared.Value;

private CircularBuffer<Entry> _buffer;
private Entry _current;
private int _version;

public InputHistory() : this(DefaultCapacity) {}

public InputHistory(int capacity)
{
_buffer = new(capacity);
}

public int Count => _buffer.Size;

/// <summary>
/// Sets the current (-1) history entry.
/// </summary>
public void SetCurrent(string value)
{
_current = String.IsNullOrEmpty(value) ? default : new(value.GetHashCode(), value);
}

/// <summary>
/// Commits the current history entry (-1) to the history, and clears the current entry
/// </summary>
public void AddCurrent()
{
Add(_current);
_current = default;
}

/// <summary>
/// Adds a history entry. If value is null or empty does nothing.
/// </summary>
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++;
}

/// <summary>
/// Gets the history entry at a position relative to the current history
/// </summary>
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<T> CreateHandler<T>(InputForm<T> 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;
}
}

/// <summary>
/// Allows navigating the current history
/// </summary>
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<T>
{
private Navigator _navi;
private readonly InputForm<T> _form;

internal Handler(Navigator navi, InputForm<T> 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();
}

}
}
1 change: 1 addition & 0 deletions Sharprompt/Sharprompt.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="CircularBuffer" Version="1.3.0" />
</ItemGroup>

<ItemGroup>
Expand Down