Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion src/Spectre.Console/Prompts/List/ListPrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public async Task<ListPromptState<T>> Show(

var key = rawKey.Value;
var result = _strategy.HandleInput(key, state);
if (result == ListPromptInputResult.Submit)
if (result == ListPromptInputResult.Submit || result == ListPromptInputResult.Abort)
{
break;
}
Expand Down
17 changes: 17 additions & 0 deletions src/Spectre.Console/Prompts/MultiSelectionPrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ public sealed class MultiSelectionPrompt<T> : IPrompt<List<T>>, IListPromptStrat

internal ListPromptTree<T> Tree { get; }

/// <summary>
/// Gets or sets a value indicating whether the prompt can be aborted.
/// </summary>
public bool AllowAbort { get; set; } = false;

/// <summary>
/// Gets a value indicating whether or not the prompt was aborted.
/// </summary>
public bool Aborted { get; private set; } = false;

/// <summary>
/// Initializes a new instance of the <see cref="MultiSelectionPrompt{T}"/> class.
/// </summary>
Expand Down Expand Up @@ -151,6 +161,7 @@ ListPromptInputResult IListPromptStrategy<T>.HandleInput(ConsoleKeyInfo key, Lis
}

// Submit
Aborted = false;
return ListPromptInputResult.Submit;
}

Expand Down Expand Up @@ -185,6 +196,12 @@ ListPromptInputResult IListPromptStrategy<T>.HandleInput(ConsoleKeyInfo key, Lis
return ListPromptInputResult.Refresh;
}

if (key.Key == ConsoleKey.Escape && AllowAbort)
{
Aborted = true;
return ListPromptInputResult.Abort;
}

return ListPromptInputResult.None;
}

Expand Down
19 changes: 19 additions & 0 deletions src/Spectre.Console/Prompts/MultiSelectionPromptExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -329,4 +329,23 @@ public static MultiSelectionPrompt<T> UseConverter<T>(this MultiSelectionPrompt<
obj.Converter = displaySelector;
return obj;
}

/// <summary>
/// Sets the value indicating whether the prompt can be aborted.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="allowAbort">Value indicating whether the prompt can be aborted.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static MultiSelectionPrompt<T> AllowAbort<T>(this MultiSelectionPrompt<T> obj, bool allowAbort)
where T : notnull
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}

obj.AllowAbort = allowAbort;
return obj;
}
}
17 changes: 17 additions & 0 deletions src/Spectre.Console/Prompts/SelectionPrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ public sealed class SelectionPrompt<T> : IPrompt<T>, IListPromptStrategy<T>
/// </summary>
public SelectionMode Mode { get; set; } = SelectionMode.Leaf;

/// <summary>
/// Gets or sets a value indicating whether the prompt can be aborted.
/// </summary>
public bool AllowAbort { get; set; } = false;

/// <summary>
/// Gets a value indicating whether or not the prompt was aborted.
/// </summary>
public bool Aborted { get; private set; } = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a fan of mutating the Prompt item.
I'm thinking that maybe it would be a good idea to have a method called ShowAbortable or similar, and set the AllowAbort as part of the context instead?

@phil-scott-78 @nils-a What do you think?

Choose a reason for hiding this comment

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

If we use ShowSortable then it should contain ShowSortableAsync too. I more like solution with AllowAbort method.

var item = await new SelectionPrompt<string>()
    .AllowAbort(true)
    .AddChoices( chocies )
    .ShowAsync( AnsiConsole.Console, CancellationToken.None );

Also, It will be interesting if this feature could be a part of IPrompt. I think that option to abort TextPrompt could be usefull. But I know that is bigger change. Adding it to selection without interface is still one step forward.


/// <summary>
/// Initializes a new instance of the <see cref="SelectionPrompt{T}"/> class.
/// </summary>
Expand Down Expand Up @@ -95,9 +105,16 @@ ListPromptInputResult IListPromptStrategy<T>.HandleInput(ConsoleKeyInfo key, Lis
return ListPromptInputResult.None;
}

Aborted = false;
return ListPromptInputResult.Submit;
}

if (key.Key == ConsoleKey.Escape && AllowAbort)
{
Aborted = true;
return ListPromptInputResult.Abort;
}

return ListPromptInputResult.None;
}

Expand Down
19 changes: 19 additions & 0 deletions src/Spectre.Console/Prompts/SelectionPromptExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,23 @@ public static SelectionPrompt<T> UseConverter<T>(this SelectionPrompt<T> obj, Fu
obj.Converter = displaySelector;
return obj;
}

/// <summary>
/// Sets the value indicating whether the prompt can be aborted.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="allowAbort">Value indicating whether the prompt can be aborted.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static SelectionPrompt<T> AllowAbort<T>(this SelectionPrompt<T> obj, bool allowAbort)
where T : notnull
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}

obj.AllowAbort = allowAbort;
return obj;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,45 @@ public void Should_Throw_When_Getting_Parents_Of_Non_Existing_Node()
// Then
action.ShouldThrow<ArgumentOutOfRangeException>();
}

[Fact]
public void Should_Set_Property_If_Aborted()
{
// Given
var console = new TestConsole();
console.Profile.Capabilities.Interactive = true;
console.Input.PushKey(ConsoleKey.Spacebar);
console.Input.PushKey(ConsoleKey.Escape);
var input = new string[] { "a", "b", "c" };

// When
var prompt = new MultiSelectionPrompt<string>()
.Title("Select some")
.AllowAbort(true)
.AddChoices(input);
prompt.Show(console);

// Then
prompt.Aborted.ShouldBe(true);
}

[Fact]
public void Should_Set_Property_If_Not_Aborted()
{
// Given
var console = new TestConsole();
console.Profile.Capabilities.Interactive = true;
console.Input.PushKey(ConsoleKey.Spacebar);
console.Input.PushKey(ConsoleKey.Enter);
var input = new string[] { "a", "b", "c" };

// When
var prompt = new MultiSelectionPrompt<string>()
.Title("Select some")
.AddChoices(input);
prompt.Show(console);

// Then
prompt.Aborted.ShouldBe(false);
}
}
39 changes: 39 additions & 0 deletions test/Spectre.Console.Tests/Unit/Prompts/SelectionPromptTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,43 @@ public void Should_Not_Throw_When_Selecting_An_Item_With_Escaped_Markup()
// Then
console.Output.ShouldContain(@"[red]This text will never be red[/]");
}

[Fact]
public void Should_Set_Property_If_Aborted()
{
// Given
var console = new TestConsole();
console.Profile.Capabilities.Interactive = true;
console.Input.PushKey(ConsoleKey.Escape);
var input = new string[] { "a", "b", "c" };

// When
var prompt = new SelectionPrompt<string>()
.Title("Select one")
.AllowAbort(true)
.AddChoices(input);
prompt.Show(console);

// Then
prompt.Aborted.ShouldBe(true);
}

[Fact]
public void Should_Set_Property_If_Not_Aborted()
{
// Given
var console = new TestConsole();
console.Profile.Capabilities.Interactive = true;
console.Input.PushKey(ConsoleKey.Enter);
var input = new string[] { "a", "b", "c" };

// When
var prompt = new SelectionPrompt<string>()
.Title("Select one")
.AddChoices(input);
prompt.Show(console);

// Then
prompt.Aborted.ShouldBe(false);
}
}