Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added useful search for larger ListPrompts #1703

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions src/Spectre.Console/Prompts/List/ListPromptConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ internal sealed class ListPromptConstants
public const string SelectedCheckbox = "[[[blue]X[/]]]";
public const string GroupSelectedCheckbox = "[[[grey]X[/]]]";
public const string InstructionsMarkup = "[grey](Press <space> to select, <enter> to accept)[/]";
public const string InstructionsSearchMarkup = "[grey](Press <ctrl + space> to select, <enter> to accept)[/]";
public const string MoreChoicesMarkup = "[grey](Move up and down to reveal more choices)[/]";
public const string SearchPlaceholderMarkup = "[grey](Type to search)[/]";

Expand Down
45 changes: 41 additions & 4 deletions src/Spectre.Console/Prompts/MultiSelectionPrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ public sealed class MultiSelectionPrompt<T> : IPrompt<List<T>>, IListPromptStrat
/// </summary>
public SelectionMode Mode { get; set; } = SelectionMode.Leaf;

/// <summary>
/// Gets or sets a value indicating whether or not search is enabled.
/// </summary>
public bool SearchEnabled { get; set; }

/// <summary>
/// Gets or sets the text that will be displayed when no search text has been entered.
/// </summary>
public string? SearchPlaceholderText { get; set; }

internal ListPromptTree<T> Tree { get; }

/// <summary>
Expand Down Expand Up @@ -95,7 +105,7 @@ public async Task<List<T>> ShowAsync(IAnsiConsole console, CancellationToken can
// Create the list prompt
var prompt = new ListPrompt<T>(console, this);
var converter = Converter ?? TypeConverterHelper.ConvertToString;
var result = await prompt.Show(Tree, converter, Mode, false, false, PageSize, WrapAround, cancellationToken).ConfigureAwait(false);
var result = await prompt.Show(Tree, converter, Mode, false, SearchEnabled, PageSize, WrapAround, cancellationToken).ConfigureAwait(false);

if (Mode == SelectionMode.Leaf)
{
Expand Down Expand Up @@ -161,7 +171,7 @@ ListPromptInputResult IListPromptStrategy<T>.HandleInput(ConsoleKeyInfo key, Lis
return ListPromptInputResult.Submit;
}

if (key.Key == ConsoleKey.Spacebar || key.Key == ConsoleKey.Packet)
if ((key.Key == ConsoleKey.Spacebar && key.Modifiers.HasFlag(ConsoleModifiers.Control)) || key.Key == ConsoleKey.Packet)
{
var current = state.Items[state.Index];
var select = !current.IsSelected;
Expand Down Expand Up @@ -213,6 +223,12 @@ int IListPromptStrategy<T>.CalculatePageSize(IAnsiConsole console, int totalItem
extra++;
}

if (SearchEnabled)
{
// Search input takes up one row
extra++;
}

var pageSize = requestedPageSize;
if (pageSize > console.Profile.Height - extra)
{
Expand All @@ -228,6 +244,7 @@ IRenderable IListPromptStrategy<T>.Render(IAnsiConsole console, bool scrollable,
{
var list = new List<IRenderable>();
var highlightStyle = HighlightStyle ?? Color.Blue;
var searchHighlightStyle = new Style(foreground: Color.Default, background: Color.Yellow, Decoration.Bold);

if (Title != null)
{
Expand All @@ -242,7 +259,15 @@ IRenderable IListPromptStrategy<T>.Render(IAnsiConsole console, bool scrollable,
grid.AddEmptyRow();
}

foreach (var item in items)
// Filter items based on search text
var filteredItems = items
.Where(item =>
{
var text = (Converter ?? TypeConverterHelper.ConvertToString)?.Invoke(item.Node.Data) ?? item.Node.Data.ToString() ?? "?";
return string.IsNullOrWhiteSpace(searchText) || text.Contains(searchText, StringComparison.OrdinalIgnoreCase);
});

foreach (var item in filteredItems)
{
var current = item.Index == cursorIndex;
var style = current ? highlightStyle : Style.Plain;
Expand All @@ -256,6 +281,11 @@ IRenderable IListPromptStrategy<T>.Render(IAnsiConsole console, bool scrollable,
text = text.RemoveMarkup().EscapeMarkup();
}

if (searchText.Length > 0)
{
text = text.Highlight(searchText, searchHighlightStyle);
}

var checkbox = item.Node.IsSelected
? ListPromptConstants.GetSelectedCheckbox(item.Node.IsGroup, Mode, HighlightStyle)
: ListPromptConstants.Checkbox;
Expand All @@ -272,8 +302,15 @@ IRenderable IListPromptStrategy<T>.Render(IAnsiConsole console, bool scrollable,
list.Add(new Markup(MoreChoicesText ?? ListPromptConstants.MoreChoicesMarkup));
}

if (SearchEnabled)
{
// Add search text input
list.Add(new Markup(
searchText.Length > 0 ? searchText.EscapeMarkup() : SearchPlaceholderText ?? ListPromptConstants.SearchPlaceholderMarkup));
}

// Instructions
list.Add(new Markup(InstructionsText ?? ListPromptConstants.InstructionsMarkup));
list.Add(new Markup(InstructionsText ?? (SearchEnabled ? ListPromptConstants.InstructionsSearchMarkup : ListPromptConstants.InstructionsMarkup)));

// Combine all items
return new Rows(list);
Expand Down
10 changes: 9 additions & 1 deletion src/Spectre.Console/Prompts/SelectionPrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,15 @@ IRenderable IListPromptStrategy<T>.Render(IAnsiConsole console, bool scrollable,
grid.AddEmptyRow();
}

foreach (var item in items)
// Filter items based on search text
var filteredItems = items
.Where(item =>
{
var text = (Converter ?? TypeConverterHelper.ConvertToString)?.Invoke(item.Node.Data) ?? item.Node.Data.ToString() ?? "?";
return string.IsNullOrWhiteSpace(searchText) || text.Contains(searchText, StringComparison.OrdinalIgnoreCase);
});

foreach (var item in filteredItems)
{
var current = item.Index == cursorIndex;
var prompt = item.Index == cursorIndex ? ListPromptConstants.Arrow : new string(' ', ListPromptConstants.Arrow.Length);
Expand Down