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

Update Prompts #57

Merged
merged 1 commit into from
Mar 6, 2024
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
104 changes: 98 additions & 6 deletions src/Spectre.Console.Rx/Spectre.Console/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,106 @@ public static string Mask(this string value, char? mask)
throw new ArgumentNullException(nameof(value));
}

var output = string.Empty;

if (mask is null)
{
return output;
return string.Empty;
}

return new string(mask.Value, value.Length);
}

/// <summary>
/// Highlights the first text match in provided value.
/// </summary>
/// <param name="value">Input value.</param>
/// <param name="searchText">Text to search for.</param>
/// <param name="highlightStyle">The style to apply to the matched text.</param>
/// <returns>Markup of input with the first matched text highlighted.</returns>
internal static string Highlight(this string value, string searchText, Style? highlightStyle)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}

if (searchText is null)
{
throw new ArgumentNullException(nameof(searchText));
}

if (highlightStyle is null)
{
throw new ArgumentNullException(nameof(highlightStyle));
}

if (searchText.Length == 0)
{
return value;
}

foreach (var c in value)
var foundSearchPattern = false;
var builder = new StringBuilder();
using var tokenizer = new MarkupTokenizer(value);
while (tokenizer.MoveNext())
{
output += mask;
var token = tokenizer.Current!;

switch (token.Kind)
{
case MarkupTokenKind.Text:
{
var tokenValue = token.Value;
if (tokenValue.Length == 0)
{
break;
}

if (foundSearchPattern)
{
builder.Append(tokenValue);
break;
}

var index = tokenValue.IndexOf(searchText, StringComparison.OrdinalIgnoreCase);
if (index == -1)
{
builder.Append(tokenValue);
break;
}

foundSearchPattern = true;
var before = tokenValue.Substring(0, index);
var match = tokenValue.Substring(index, searchText.Length);
var after = tokenValue.Substring(index + searchText.Length);

builder
.Append(before)
.AppendWithStyle(highlightStyle, match)
.Append(after);

break;
}

case MarkupTokenKind.Open:
{
builder.Append("[" + token.Value + "]");
break;
}

case MarkupTokenKind.Close:
{
builder.Append("[/]");
break;
}

default:
{
throw new InvalidOperationException("Unknown markup token kind.");
}
}
}

return output;
return builder.ToString();
}

internal static string CapitalizeFirstLetter(this string? text, CultureInfo? culture = null)
Expand Down Expand Up @@ -202,6 +289,11 @@ internal static string Repeat(this string text, int count)
return string.Concat(Enumerable.Repeat(text, count));
}

#if NETSTANDARD2_0
internal static bool Contains(this string target, string value, StringComparison comparisonType) =>
target.IndexOf(value, comparisonType) != -1;
#endif

internal static string ReplaceExact(this string text, string oldValue, string? newValue) =>
#if NETSTANDARD2_0
text.Replace(oldValue, newValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@ public static IEnumerable<T> Repeat<T>(this IEnumerable<T> source, int count)
}

public static int IndexOf<T>(this IEnumerable<T> source, T item)
where T : class
{
var index = 0;
foreach (var candidate in source)
{
if (candidate == item)
if (Equals(candidate, item))
{
return index;
}
Expand Down Expand Up @@ -89,9 +88,9 @@ public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
throw new ArgumentNullException(nameof(source));
}

return DoEnumeration();
return EnumerateValues();

IEnumerable<(int Index, bool First, bool Last, T Item)> DoEnumeration()
IEnumerable<(int Index, bool First, bool Last, T Item)> EnumerateValues()
{
var first = true;
var last = !source.MoveNext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ internal interface IListPromptStrategy<T>
/// <param name="scrollable">Whether or not the list is scrollable.</param>
/// <param name="cursorIndex">The cursor index.</param>
/// <param name="items">The visible items.</param>
/// <param name="skipUnselectableItems">A value indicating whether or not the prompt should skip unselectable items.</param>
/// <param name="searchText">The search text.</param>
/// <returns>A <see cref="IRenderable"/> representing the items.</returns>
public IRenderable Render(IAnsiConsole console, bool scrollable, int cursorIndex, IEnumerable<(int Index, ListPromptItem<T> Node)> items);
}
public IRenderable Render(IAnsiConsole console, bool scrollable, int cursorIndex, IEnumerable<(int Index, ListPromptItem<T> Node)> items, bool skipUnselectableItems, string searchText);
}
30 changes: 21 additions & 9 deletions src/Spectre.Console.Rx/Spectre.Console/Prompts/List/ListPrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@

namespace Spectre.Console.Rx;

internal sealed class ListPrompt<T>(IAnsiConsole console, IListPromptStrategy<T> strategy)
internal sealed class ListPrompt<T>
where T : notnull
{
private readonly IAnsiConsole _console = console ?? throw new ArgumentNullException(nameof(console));
private readonly IListPromptStrategy<T> _strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
private readonly IAnsiConsole _console;
private readonly IListPromptStrategy<T> _strategy;

public ListPrompt(IAnsiConsole console, IListPromptStrategy<T> strategy)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
}

public async Task<ListPromptState<T>> Show(
ListPromptTree<T> tree,
CancellationToken cancellationToken,
int requestedPageSize = 15,
bool wrapAround = false)
SelectionMode selectionMode,
bool skipUnselectableItems,
bool searchEnabled,
int requestedPageSize,
bool wrapAround,
CancellationToken cancellationToken = default)
{
if (tree is null)
{
Expand All @@ -35,7 +44,7 @@ public async Task<ListPromptState<T>> Show(
}

var nodes = tree.Traverse().ToList();
var state = new ListPromptState<T>(nodes, _strategy.CalculatePageSize(_console, nodes.Count, requestedPageSize), wrapAround);
var state = new ListPromptState<T>(nodes, _strategy.CalculatePageSize(_console, nodes.Count, requestedPageSize), wrapAround, selectionMode, skipUnselectableItems, searchEnabled);
var hook = new ListPromptRenderHook<T>(_console, () => BuildRenderable(state));

using (new RenderHookScope(_console, hook))
Expand All @@ -45,6 +54,7 @@ public async Task<ListPromptState<T>> Show(

while (true)
{
cancellationToken.ThrowIfCancellationRequested();
var rawKey = await _console.Input.ReadKeyAsync(true, cancellationToken).ConfigureAwait(false);
if (rawKey == null)
{
Expand All @@ -58,7 +68,7 @@ public async Task<ListPromptState<T>> Show(
break;
}

if (state.Update(key.Key) || result == ListPromptInputResult.Refresh)
if (state.Update(key) || result == ListPromptInputResult.Refresh)
{
hook.Refresh();
}
Expand Down Expand Up @@ -107,6 +117,8 @@ private IRenderable BuildRenderable(ListPromptState<T> state)
scrollable,
cursorIndex,
state.Items.Skip(skip).Take(take)
.Select((node, index) => (index, node)));
.Select((node, index) => (index, node)),
state.SkipUnselectableItems,
state.SearchText);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ internal static class ListPromptConstants
public const string GroupSelectedCheckbox = "[[[grey]X[/]]]";
public const string InstructionsMarkup = "[grey](Press <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)[/]";
}
Loading
Loading