Skip to content
Merged
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
13 changes: 2 additions & 11 deletions Sharprompt/ConfirmOptions.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
using System;
namespace Sharprompt;

namespace Sharprompt;

public class ConfirmOptions
public class ConfirmOptions : PromptOptions
{
public string Message { get; set; } = null!;

public bool? DefaultValue { get; set; }

internal void EnsureOptions()
{
ArgumentNullException.ThrowIfNull(Message);
}
}
2 changes: 1 addition & 1 deletion Sharprompt/Forms/ConfirmForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Sharprompt.Forms;

internal class ConfirmForm : TextFormBase<bool>
{
public ConfirmForm(ConfirmOptions options)
public ConfirmForm(ConfirmOptions options, PromptConfiguration configuration) : base(configuration)
{
options.EnsureOptions();

Expand Down
14 changes: 9 additions & 5 deletions Sharprompt/Forms/FormBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@ namespace Sharprompt.Forms;

internal abstract class FormBase<T> : IDisposable
{
protected FormBase()
protected FormBase(PromptConfiguration configuration)
{
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FormBase stores and uses the passed PromptConfiguration but doesn't validate it for null. If a null configuration is ever passed, this will result in a NullReferenceException when accessing configuration.ConsoleDriverFactory() or later when reading _configuration.ThrowExceptionOnCancel. Consider adding ArgumentNullException.ThrowIfNull(configuration); at the start of the constructor to fail fast with a clear exception.

Suggested change
{
{
ArgumentNullException.ThrowIfNull(configuration);

Copilot uses AI. Check for mistakes.
_consoleDriver = Prompt.ConsoleDriverFactory() ?? throw new InvalidOperationException("ConsoleDriverFactory must return a non-null IConsoleDriver instance.");
_configuration = configuration;
_consoleDriver = configuration.ConsoleDriverFactory() ?? throw new InvalidOperationException("ConsoleDriverFactory must return a non-null IConsoleDriver instance.");

_consoleDriver.CancellationCallback = CancellationHandler;

_formRenderer = new FormRenderer(_consoleDriver);
_formRenderer = new FormRenderer(_consoleDriver, configuration);
}

private readonly IConsoleDriver _consoleDriver;
private readonly FormRenderer _formRenderer;
private readonly PromptConfiguration _configuration;

protected PromptConfiguration Configuration => _configuration;

protected TextInputBuffer InputBuffer { get; } = new();

Expand All @@ -31,7 +35,7 @@ protected FormBase()

protected int Height => _consoleDriver.WindowHeight;

public void Dispose() => _formRenderer.Dispose();
public void Dispose() => _consoleDriver.Dispose();

public T Start()
{
Expand Down Expand Up @@ -124,7 +128,7 @@ private void CancellationHandler()
{
_formRenderer.Cancel();

if (Prompt.ThrowExceptionOnCancel)
if (_configuration.ThrowExceptionOnCancel)
{
throw new PromptCanceledException(Resource.Message_PromptCanceled, GetType().Name);
}
Expand Down
6 changes: 2 additions & 4 deletions Sharprompt/Forms/FormRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@

namespace Sharprompt.Forms;

internal class FormRenderer(IConsoleDriver consoleDriver) : IDisposable
internal class FormRenderer(IConsoleDriver consoleDriver, PromptConfiguration configuration)
{
private readonly OffscreenBuffer _offscreenBuffer = new(consoleDriver);
private readonly OffscreenBuffer _offscreenBuffer = new(consoleDriver, configuration);

public string? ErrorMessage { get; set; }

public void Dispose() => _offscreenBuffer.Dispose();

public void Cancel() => _offscreenBuffer.Cancel();

public void Render(Action<OffscreenBuffer> template)
Expand Down
2 changes: 1 addition & 1 deletion Sharprompt/Forms/InputForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Sharprompt.Forms;

internal class InputForm<T> : TextFormBase<T>
{
public InputForm(InputOptions<T> options)
public InputForm(InputOptions<T> options, PromptConfiguration configuration) : base(configuration)
{
options.EnsureOptions();

Expand Down
2 changes: 1 addition & 1 deletion Sharprompt/Forms/ListForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Sharprompt.Forms;

internal class ListForm<T> : TextFormBase<IEnumerable<T>> where T : notnull
{
public ListForm(ListOptions<T> options)
public ListForm(ListOptions<T> options, PromptConfiguration configuration) : base(configuration)
{
options.EnsureOptions();

Expand Down
105 changes: 20 additions & 85 deletions Sharprompt/Forms/MultiSelectForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,59 +8,48 @@

namespace Sharprompt.Forms;

internal class MultiSelectForm<T> : FormBase<IEnumerable<T>> where T : notnull
internal class MultiSelectForm<T> : SelectFormBase<T, IEnumerable<T>> where T : notnull
{
public MultiSelectForm(MultiSelectOptions<T> options)
public MultiSelectForm(MultiSelectOptions<T> options, PromptConfiguration configuration) : base(configuration)
{
options.EnsureOptions();

_options = options;
_paginator = new Paginator<T>(options.Items, Math.Min(options.PageSize, Height - 2), Optional<T>.Empty, options.TextSelector)
{
LoopingSelection = options.LoopingSelection
};

InitializePaginator(options.Items, options.PageSize, Optional<T>.Empty, options.TextSelector, options.LoopingSelection);

foreach (var defaultValue in options.DefaultValues)
{
_selectedItems.Add(defaultValue);
}

KeyHandlerMaps = new()
{
[ConsoleKey.Spacebar] = HandleSpacebar,
[ConsoleKey.UpArrow] = HandleUpArrow,
[ConsoleKey.DownArrow] = HandleDownArrow,
[ConsoleKey.LeftArrow] = HandleLeftArrow,
[ConsoleKey.RightArrow] = HandleRightArrow,
[ConsoleKey.Backspace] = HandleBackspace,
[ConsoleKey.A] = HandleAWithControl,
[ConsoleKey.I] = HandleIWithControl,
};
KeyHandlerMaps[ConsoleKey.Spacebar] = HandleSpacebar;
KeyHandlerMaps[ConsoleKey.A] = HandleAWithControl;
KeyHandlerMaps[ConsoleKey.I] = HandleIWithControl;
}

private readonly MultiSelectOptions<T> _options;
private readonly Paginator<T> _paginator;

private readonly HashSet<T> _selectedItems = [];

protected override void InputTemplate(OffscreenBuffer offscreenBuffer)
{
_paginator.UpdatePageSize(Math.Min(_options.PageSize, Height - 2));
Paginator.UpdatePageSize(Math.Min(_options.PageSize, Height - 2));

offscreenBuffer.WritePrompt(_options.Message);
offscreenBuffer.Write(_paginator.FilterKeyword);
offscreenBuffer.Write(Paginator.FilterKeyword);

offscreenBuffer.PushCursor();

if (string.IsNullOrEmpty(_paginator.FilterKeyword))
if (string.IsNullOrEmpty(Paginator.FilterKeyword))
{
offscreenBuffer.WriteHint(Resource.MultiSelectForm_Message_Hint);
}

var hasSelected = _paginator.TryGetSelectedItem(out var selectedItem);
var hasSelected = Paginator.TryGetSelectedItem(out var selectedItem);
var comparer = EqualityComparer<T>.Default;

foreach (var item in _paginator.CurrentItems)
foreach (var item in Paginator.CurrentItems)
{
var value = _options.TextSelector(item);
var isChecked = _selectedItems.Contains(item);
Expand All @@ -69,26 +58,22 @@ protected override void InputTemplate(OffscreenBuffer offscreenBuffer)

if (hasSelected && comparer.Equals(item, selectedItem))
{
offscreenBuffer.WriteSelect($"{Prompt.Symbols.Selector} {(isChecked ? Prompt.Symbols.Selected : Prompt.Symbols.NotSelect)} {value}");
offscreenBuffer.WriteSelect($"{Configuration.Symbols.Selector} {(isChecked ? Configuration.Symbols.Selected : Configuration.Symbols.NotSelect)} {value}");
}
else
{
if (isChecked)
{
offscreenBuffer.WriteSelect($" {Prompt.Symbols.Selected} {value}");
offscreenBuffer.WriteSelect($" {Configuration.Symbols.Selected} {value}");
}
else
{
offscreenBuffer.Write($" {Prompt.Symbols.NotSelect} {value}");
offscreenBuffer.Write($" {Configuration.Symbols.NotSelect} {value}");
}
}
}

if (_paginator.PageCount > 1)
{
offscreenBuffer.WriteLine();
offscreenBuffer.WriteHint(_options.Pagination(_paginator.TotalCount, _paginator.CurrentPage + 1, _paginator.PageCount));
}
RenderPagination(offscreenBuffer, _options.Pagination);
}

protected override void FinishTemplate(OffscreenBuffer offscreenBuffer, IEnumerable<T> result)
Expand All @@ -115,18 +100,9 @@ protected override bool HandleEnter([NotNullWhen(true)] out IEnumerable<T>? resu
return false;
}

protected override bool HandleTextInput(ConsoleKeyInfo keyInfo)
{
base.HandleTextInput(keyInfo);

_paginator.UpdateFilter(InputBuffer.ToString());

return true;
}

private bool HandleSpacebar(ConsoleKeyInfo keyInfo)
{
if (!_paginator.TryGetSelectedItem(out var currentItem))
if (!Paginator.TryGetSelectedItem(out var currentItem))
{
return false;
}
Expand All @@ -146,61 +122,20 @@ private bool HandleSpacebar(ConsoleKeyInfo keyInfo)
return true;
}

private bool HandleUpArrow(ConsoleKeyInfo keyInfo)
{
_paginator.PreviousItem();

return true;
}

private bool HandleDownArrow(ConsoleKeyInfo keyInfo)
{
_paginator.NextItem();

return true;
}

private bool HandleLeftArrow(ConsoleKeyInfo keyInfo)
{
_paginator.PreviousPage();

return true;
}

private bool HandleRightArrow(ConsoleKeyInfo keyInfo)
{
_paginator.NextPage();

return true;
}

private bool HandleBackspace(ConsoleKeyInfo keyInfo)
{
if (InputBuffer.IsStart)
{
return false;
}

InputBuffer.Backspace();
_paginator.UpdateFilter(InputBuffer.ToString());

return true;
}

private bool HandleAWithControl(ConsoleKeyInfo keyInfo)
{
if (keyInfo.Modifiers != ConsoleModifiers.Control)
{
return false;
}

if (_selectedItems.Count == _paginator.TotalCount)
if (_selectedItems.Count == Paginator.TotalCount)
{
_selectedItems.Clear();
}
else
{
foreach (var item in _paginator)
foreach (var item in Paginator)
{
_selectedItems.Add(item);
}
Expand All @@ -216,7 +151,7 @@ private bool HandleIWithControl(ConsoleKeyInfo keyInfo)
return false;
}

var invertedItems = _paginator.Except(_selectedItems).ToArray();
var invertedItems = Paginator.Except(_selectedItems).ToArray();

_selectedItems.Clear();

Expand Down
2 changes: 1 addition & 1 deletion Sharprompt/Forms/PasswordForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Sharprompt.Forms;

internal class PasswordForm : FormBase<string>
{
public PasswordForm(PasswordOptions options)
public PasswordForm(PasswordOptions options, PromptConfiguration configuration) : base(configuration)
{
options.EnsureOptions();

Expand Down
Loading
Loading