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
18 changes: 14 additions & 4 deletions src/PrettyPrompt/Panes/CompletionPane.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,26 @@ public CompletionPane(
FilteredView.SelectedItemChanged += SelectedItemChanged;
}

private void Open()
private async Task Open(CancellationToken cancellationToken)
{
bool wasOpen = IsOpen;
this.IsOpen = true;
this.allCompletions = Array.Empty<CompletionItem>();
if (!wasOpen)
{
await promptCallbacks.CompletionWindowOpenedAsync(codePane.Document.GetText(), codePane.Document.Caret, cancellationToken).ConfigureAwait(false);
}
}

private async Task Close(CancellationToken cancellationToken)
{
bool wasOpen = IsOpen;
IsOpen = false;
await FilteredView.Clear(cancellationToken).ConfigureAwait(false);
if (wasOpen)
{
await promptCallbacks.CompletionWindowClosedAsync(codePane.Document.GetText(), codePane.Document.Caret, cancellationToken).ConfigureAwait(false);
}
}

async Task IKeyPressHandler.OnKeyDown(KeyPress key, CancellationToken cancellationToken)
Expand Down Expand Up @@ -135,7 +145,7 @@ End or (_, End) or
{
if (codePane.Selection is null)
{
Open();
await Open(cancellationToken).ConfigureAwait(false);
}
key.Handled = true;
}
Expand All @@ -148,7 +158,7 @@ End or (_, End) or
if (completionListTriggered)
{
await Close(cancellationToken).ConfigureAwait(false);
Open();
await Open(cancellationToken).ConfigureAwait(false);
key.Handled = true;
}
return;
Expand Down Expand Up @@ -197,7 +207,7 @@ async Task IKeyPressHandler.OnKeyUp(KeyPress key, CancellationToken cancellation
!completionListTriggeredOnKeyDown &&
await promptCallbacks.ShouldOpenCompletionWindowAsync(codePane.Document.GetText(), codePane.Document.Caret, key, cancellationToken).ConfigureAwait(false))
{
Open();
await Open(cancellationToken).ConfigureAwait(false);
}
}

Expand Down
38 changes: 38 additions & 0 deletions src/PrettyPrompt/PromptCallbacks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ public interface IPromptCallbacks
/// <returns>A value indicating whether the completion window should automatically open.</returns>
Task<bool> ShouldOpenCompletionWindowAsync(string text, int caret, KeyPress keyPress, CancellationToken cancellationToken);

/// <summary>
/// Invoked after the completion window has been opened.
/// </summary>
/// <param name="text">The user's input text</param>
/// <param name="caret">The index of the text caret in the input text</param>
/// <param name="cancellationToken">Cancellation token</param>
Task CompletionWindowOpenedAsync(string text, int caret, CancellationToken cancellationToken);

/// <summary>
/// Invoked after the completion window has been closed.
/// </summary>
/// <param name="text">The user's input text</param>
/// <param name="caret">The index of the text caret in the input text</param>
/// <param name="cancellationToken">Cancellation token</param>
Task CompletionWindowClosedAsync(string text, int caret, CancellationToken cancellationToken);

/// <summary>
/// Optionaly transforms key presses to another ones.
/// </summary>
Expand Down Expand Up @@ -184,6 +200,20 @@ Task<bool> IPromptCallbacks.ShouldOpenCompletionWindowAsync(string text, int car
return ShouldOpenCompletionWindowAsync(text, caret, keyPress, cancellationToken);
}

Task IPromptCallbacks.CompletionWindowOpenedAsync(string text, int caret, CancellationToken cancellationToken)
{
Debug.Assert(caret >= 0 && caret <= text.Length);

return CompletionWindowOpenedAsync(text, caret, cancellationToken);
}

Task IPromptCallbacks.CompletionWindowClosedAsync(string text, int caret, CancellationToken cancellationToken)
{
Debug.Assert(caret >= 0 && caret <= text.Length);

return CompletionWindowClosedAsync(text, caret, cancellationToken);
}

Task<KeyPress> IPromptCallbacks.TransformKeyPressAsync(string text, int caret, KeyPress keyPress, CancellationToken cancellationToken)
{
Debug.Assert(caret >= 0 && caret <= text.Length);
Expand Down Expand Up @@ -284,6 +314,14 @@ protected virtual Task<bool> ShouldOpenCompletionWindowAsync(string text, int ca
return Task.FromResult(caret - 2 >= 0 && char.IsWhiteSpace(text[caret - 2]) && char.IsLetter(text[caret - 1]));
}

/// <inheritdoc cref="IPromptCallbacks.CompletionWindowOpenedAsync"/>
protected virtual Task CompletionWindowOpenedAsync(string text, int caret, CancellationToken cancellationToken)
=> Task.CompletedTask;

/// <inheritdoc cref="IPromptCallbacks.CompletionWindowClosedAsync"/>
protected virtual Task CompletionWindowClosedAsync(string text, int caret, CancellationToken cancellationToken)
=> Task.CompletedTask;

/// <inheritdoc cref="IPromptCallbacks.TransformKeyPressAsync(string, int, KeyPress, CancellationToken)"/>
protected virtual Task<KeyPress> TransformKeyPressAsync(string text, int caret, KeyPress keyPress, CancellationToken cancellationToken)
=> Task.FromResult(keyPress);
Expand Down
24 changes: 24 additions & 0 deletions tests/PrettyPrompt.Tests/CompletionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,30 @@ public async Task ReadLine_CompletionMenu_Closes()
Assert.Equal($"A", result2.Text);
}

[Fact]
public async Task ReadLine_CompletionMenu_InvokesOpenedAndClosedCallbacks()
{
var console = ConsoleStub.NewConsole();
int openedCount = 0;
int closedCount = 0;
var callbacks = new TestPromptCallbacks
{
CompletionCallback = new CompletionTestData().CompletionHandlerAsync,
CompletionWindowOpenedCallback = (_, _) => { openedCount++; return Task.CompletedTask; },
CompletionWindowClosedCallback = (_, _) => { closedCount++; return Task.CompletedTask; },
};
var prompt = new Prompt(callbacks: callbacks, console: console);

// typing 'A' auto-opens the completion menu, Escape closes it.
console.StubInput($"A{Escape}{Enter}");
var result = await prompt.ReadLineAsync();

Assert.True(result.IsSuccess);
Assert.Equal("A", result.Text);
Assert.Equal(1, openedCount);
Assert.Equal(1, closedCount);
}

[Fact]
public async Task ReadLine_CompletionMenu_Scrolls()
{
Expand Down
19 changes: 19 additions & 0 deletions tests/PrettyPrompt.Tests/TestPromptCallbacks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace PrettyPrompt.Tests;
internal delegate Task<TextSpan> SpanToReplaceByCompletionCallbackAsync(string text, int caret);
internal delegate Task<IReadOnlyList<CompletionItem>> CompletionCallbackAsync(string text, int caret, TextSpan spanToBeReplaced);
internal delegate Task<bool> OpenCompletionWindowCallbackAsync(string text, int caret);
internal delegate Task CompletionWindowStateChangedCallbackAsync(string text, int caret);
internal delegate Task<IReadOnlyCollection<FormatSpan>> HighlightCallbackAsync(string text);
internal delegate Task<KeyPress> TransformKeyPressAsyncCallbackAsync(string text, int caret, KeyPress keyPress);
internal delegate Task<(IReadOnlyList<OverloadItem>, int ArgumentIndex)> GetOverloadsCallbackAsync(string text, int caret);
Expand All @@ -23,6 +24,8 @@ internal class TestPromptCallbacks : PromptCallbacks
public SpanToReplaceByCompletionCallbackAsync? SpanToReplaceByCompletionCallback { get; set; }
public CompletionCallbackAsync? CompletionCallback { get; set; }
public OpenCompletionWindowCallbackAsync? OpenCompletionWindowCallback { get; set; }
public CompletionWindowStateChangedCallbackAsync? CompletionWindowOpenedCallback { get; set; }
public CompletionWindowStateChangedCallbackAsync? CompletionWindowClosedCallback { get; set; }
public HighlightCallbackAsync? HighlightCallback { get; set; }
public TransformKeyPressAsyncCallbackAsync? TransformKeyPressCallback { get; set; }
public GetOverloadsCallbackAsync? GetOverloadsCallback { get; set; }
Expand Down Expand Up @@ -58,6 +61,22 @@ OpenCompletionWindowCallback is null ?
OpenCompletionWindowCallback(text, caret);
}

protected override Task CompletionWindowOpenedAsync(string text, int caret, CancellationToken cancellationToken)
{
return
CompletionWindowOpenedCallback is null ?
base.CompletionWindowOpenedAsync(text, caret, cancellationToken) :
CompletionWindowOpenedCallback(text, caret);
}

protected override Task CompletionWindowClosedAsync(string text, int caret, CancellationToken cancellationToken)
{
return
CompletionWindowClosedCallback is null ?
base.CompletionWindowClosedAsync(text, caret, cancellationToken) :
CompletionWindowClosedCallback(text, caret);
}

protected override Task<IReadOnlyCollection<FormatSpan>> HighlightCallbackAsync(string text, CancellationToken cancellationToken)
{
return
Expand Down
Loading