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

AutoCompletion Support #2 #1260

Open
wants to merge 62 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
71a5ec4
Migrated&Improved AutoCompletion & Tests
JKamsker Jul 19, 2023
be2c007
Fixed error & warning
JKamsker Jul 19, 2023
40ff744
My lion's name is angelika now
JKamsker Jul 19, 2023
3671b85
Fixed arithmetic error
JKamsker Jul 19, 2023
ad7f960
Resolved warnings
JKamsker Jul 19, 2023
f9f3854
Removed debug break for release
JKamsker Jul 19, 2023
2598c0e
Fixed bug when only one partial command was present
JKamsker Jul 20, 2023
59e0630
Renamed ICommandParameterCompleter to ICommandCompletable
JKamsker Jul 20, 2023
ff00259
Added position support(pwsh)
JKamsker Jul 20, 2023
055d99c
Support for async completions
JKamsker Jul 20, 2023
d04d706
Fix Tests
JKamsker Jul 20, 2023
d065d1b
Improved CommandTree resolution
JKamsker Jul 20, 2023
e39402a
Minor cleanup
JKamsker Jul 20, 2023
4fa0659
Minor refactorings
JKamsker Jul 20, 2023
3214dee
Highjacking console when completing
JKamsker Jul 20, 2023
a39517f
Implemented CompletionSuggestions
JKamsker Jul 20, 2023
48f1f5e
Cleanup
JKamsker Jul 20, 2023
52f321a
Resolved warnings
JKamsker Jul 20, 2023
a00ace8
Warnings
JKamsker Jul 20, 2023
cf5315f
Period
JKamsker Jul 20, 2023
7ac9ed0
Started Powershell integration
JKamsker Jul 21, 2023
3ee71f9
[Wip] Ps1 generation
JKamsker Jul 21, 2023
9d94f5d
Persistent powershell
JKamsker Jul 24, 2023
b8e281c
Do not call installer on pwsh startup
JKamsker Jul 24, 2023
cb146de
Resolved build errors
JKamsker Jul 24, 2023
f47cc86
Hiding completion branch
JKamsker Jul 24, 2023
09fdd7f
Prefering --install instead of --noInstall
Jul 24, 2023
466d82f
Fixed ParseStartArgs and added tests
JKamsker Jul 25, 2023
42d25eb
Added diagnostic command to pwsh integration
JKamsker Jul 25, 2023
ba231e9
Creating script folder
JKamsker Jul 25, 2023
0d4ef22
Improved AddLine
JKamsker Jul 25, 2023
ce39696
Local commands are prioritized
JKamsker Jul 25, 2023
d9b591a
Completion and relay to local command first
JKamsker Jul 25, 2023
ac7866b
Resolved warnings
JKamsker Jul 25, 2023
b4732f3
warning
JKamsker Jul 26, 2023
22b625b
Do not suggest options that have been entered already
JKamsker Jul 27, 2023
b5f4b18
Added test for CommandArgument, currently failing
Jul 28, 2023
04a0aca
Implement preventing of suggestion of Options when current option has…
JKamsker Jul 28, 2023
aa1eebe
Cleanup
JKamsker Jul 28, 2023
0735eb0
Removed warnings
JKamsker Jul 28, 2023
d8223ed
Refactor preprocessing into a separate class
JKamsker Jul 28, 2023
72f10b3
Resolve warnings
JKamsker Jul 29, 2023
10a5773
Warnings
JKamsker Jul 29, 2023
7dbfdce
[poc] CompletionServer
Jul 30, 2023
3806af8
Resolved warnings
Jul 30, 2023
b388906
Resovle warnings
Jul 30, 2023
e9aca16
Warnings
Jul 31, 2023
36c3a9e
What
Jul 31, 2023
955b2b6
Fix: Hide autogenerated suggestions again when dynamic sugggestion is…
JKamsker Jul 31, 2023
af72bb6
Merge branch 'main' into AutoCompletion
JKamsker Sep 15, 2023
af05b59
Added example
JKamsker Sep 15, 2023
fb4ade7
XmlDoc CompletionSuggestionsAttribute
JKamsker Sep 15, 2023
ca5d47c
Added more settings
JKamsker Sep 15, 2023
d4d8d72
Resolved issues
JKamsker Sep 15, 2023
be373fe
Add readme
JKamsker Sep 15, 2023
997f505
Refactor autocompletion documentation structure
JKamsker Sep 24, 2023
a911355
Refactor autocompletion documentation structure
JKamsker Sep 24, 2023
20ea81b
ITypeResolver is now implicitly registered to itself & Merge TypeReso…
JKamsker Sep 24, 2023
6001bdd
TypeResolver check can be safely removed because it is implicitly reg…
JKamsker Sep 24, 2023
7ea6b09
Add examples meatdata & Move AutoCompletion example to cli sln folder
JKamsker Sep 24, 2023
f74e582
Remove unrelated comments
JKamsker Sep 24, 2023
f60898f
Merge remote-tracking branch 'origin/main' into AutoCompletion
JKamsker Sep 24, 2023
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,5 @@ Thumbs.db

*.received.*

node_modules
node_modules
*.txt.bak
152 changes: 152 additions & 0 deletions docs/input/cli/autocompletion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
Title: Using AutoCompletion
Order: 14
Description: "How to use the AutoCompletion feature"
---
# Spectre.Console AutoCompletion

Spectre.Console.Cli includes auto completion for the shell.
It comes with suggestions for Options and Branches out of the box, but you can also add your own suggestions for option and argument values.

- [Shell integrations](#shell-integrations)
- [PowerShell](#powershell)
- [How integrations get the suggestions](#how-integrations-get-the-suggestions)
- [Customizations](#customizations)
- [Static Autocomplete](#static-autocomplete)
- [Dynamic Autocomplete](#dynamic-autocomplete)
- [Disabling the Module](#disabling-the-module)



## Shell integrations
1. [PowerShell](#powershell)
3. More to come...


### PowerShell

You can add autocomplete to PowerShell by running your application with the `completion powershell` command, as shown below:


```powershell
.\AutoCompletion.exe completion powershell | Out-String | Invoke-Expression
```

To add autocomplete to PowerShell permanently, use the `--install` flag:

```powershell
.\AutoCompletion.exe completion powershell --install | Out-String | Invoke-Expression
```

## How integrations get the suggestions

The shell integration uses the `cli complete` command to get the suggestions for the current command line like this:

```powershell
.\AutoCompletion.exe cli complete "Li"
```

## Customizations
1. [Static Autocomplete](#static-autocomplete)
2. [Dynamic Autocomplete](#dynamic-autocomplete)

### Static Autocomplete

Spectre.Console auto completion allows you to specify static autocomplete suggestions for your command arguments and options. This can be done using the `CompletionSuggestions` attribute in your command settings class.

Here's an example of how to add static autocomplete suggestions:

```csharp
public class LionSettings : CommandSettings
{
[CommandArgument(0, "<TEETH>")]
[Description("The number of teeth the lion has.")]
[CompletionSuggestions("10", "15", "20", "30")]
public int Teeth { get; set; }

[CommandOption("-a|--age <AGE>")]
public int Age { get; set; }

[CommandOption("-n|--name <NAME>")]
public string Name { get; set; }
}
```

### Dynamic Autocomplete

In addition to static autocomplete suggestions, you can also provide dynamic autocomplete suggestions based on the user's input. This can be done by implementing the `IAsyncCommandCompletable` interface in your command class and overriding the `GetSuggestionsAsync` method.

Here's an example of how to add dynamic autocomplete suggestions:

```csharp
[Description("The lion command.")]
public class LionCommand : Command<LionSettings>, IAsyncCommandCompletable
{
public override int Execute(CommandContext context, LionSettings settings)
{
return 0;
}

public async Task<CompletionResult> GetSuggestionsAsync(ICommandParameterInfo parameter, string? prefix)
{
if(string.IsNullOrEmpty(prefix))
{
return CompletionResult.None();
}

return await AsyncSuggestionMatcher
.Add(x => x.Age, (prefix) =>
{
if (prefix.Length != 0)
{
return FindNextEvenNumber(prefix);
}

return "16";
})
.Add(x => x.Name, prefix =>
{
var names = new List<string>
{
"angel", "angelika", "robert",
"jennifer", "michael", "lucy",
"david", "sarah", "john", "katherine",
"mark"
};

var bestMatches = names
.Where(name => name.StartsWith(prefix))
.ToList();

return new CompletionResult(bestMatches, bestMatches.Any());
})
.MatchAsync(parameter, prefix)
.WithPreventDefault();
}
}
```

## Disabling the Module

If you need to disable the autocomplete feature for any reason, you can do so by setting the `AutoCompletionModule` to `None` when configuring your application:

```csharp
var app = new CommandApp();
app.Configure(config => config.UseAutoComplete(AutoCompletionModule.None));

app.Run(args);
```

The `AutoCompletionModule` enum provides several options for enabling and disabling different aspects of the autocomplete feature:

```csharp
[Flags]
public enum AutoCompletionModule
{
None = 0, // No auto completion module is enabled.
Base = 1 << 0, // Basic auto completion functionality.
Powershell = 1 << 1, // Auto completion features specific to Powershell.
All = Base | Powershell, // All auto completion modules are enabled.
}
```

There is a working [example of the AutoCompletion feature](https://github.com/JKamsker/spectre.console/tree/AutoCompletion/examples/Cli/AutoCompletion) demonstrating this.
18 changes: 18 additions & 0 deletions examples/Cli/AutoCompletion/AutoCompletion.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ExampleName>AutoCompletion</ExampleName>
<ExampleDescription>Demonstrates the use of AutoComplete customizations</ExampleDescription>
<ExampleGroup>Cli</ExampleGroup>
<ExampleVisible>false</ExampleVisible>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Shared\Shared.csproj" />
</ItemGroup>

</Project>
80 changes: 80 additions & 0 deletions examples/Cli/AutoCompletion/LionCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System.ComponentModel;
using Spectre.Console.Cli;
using Spectre.Console.Cli.Completion;

namespace AutoCompletion;

[Description("The lion command.")]
public class LionCommand : Command<LionSettings>, IAsyncCommandCompletable
{
public override int Execute(CommandContext context, LionSettings settings)
{
return 0;
}

public async Task<CompletionResult> GetSuggestionsAsync(ICommandParameterInfo parameter, string? prefix)
{
if(string.IsNullOrEmpty(prefix))
{
return CompletionResult.None();
}

return await AsyncSuggestionMatcher
.Add(x => x.Legs, (prefix) =>
{
if (prefix.Length != 0)
{
return FindNextEvenNumber(prefix);
}

return "16";
})
.Add(x => x.Teeth, (prefix) =>
{
if (prefix.Length != 0)
{
return FindNextEvenNumber(prefix);
}

return "32";
})
.Add(x => x.Name, prefix =>
{
var names = new List<string>
{
"angel", "angelika", "robert",
"jennifer", "michael", "lucy",
"david", "sarah", "john", "katherine",
"mark"
};

var bestMatches = names
.Where(name => name.StartsWith(prefix))
.ToList();

return new CompletionResult(bestMatches, bestMatches.Any());
})
.MatchAsync(parameter, prefix)
.WithPreventDefault();
}

private static string FindNextEvenNumber(string input)
{
var number = int.Parse(input); // Parse the input string to an integer

// Find the next even number greater than the input number
var nextEvenNumber = number + (2 - (number % 2));

// Convert the number to string to check the prefix
var nextEvenNumberString = nextEvenNumber.ToString();

// Check if the prefix of the even number matches the input string
while (!nextEvenNumberString.StartsWith(input))
{
nextEvenNumber += 2; // Increment by 2 to find the next even number
nextEvenNumberString = nextEvenNumber.ToString(); // Update the string representation
}

return nextEvenNumber.ToString();
}
}
32 changes: 32 additions & 0 deletions examples/Cli/AutoCompletion/LionSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.ComponentModel;
using Spectre.Console.Cli;
using Spectre.Console.Cli.Completion;

namespace AutoCompletion;

public class LionSettings : CommandSettings
{
[CommandArgument(0, "<TEETH>")]
[Description("The number of teeth the lion has.")]
public int Teeth { get; set; }

[CommandArgument(1, "[LEGS]")]
[Description("The number of legs.")]
public int Legs { get; set; }

[CommandOption("-c <CHILDREN>")]
[Description("The number of children the lion has.")]
public int Children { get; set; }

[CommandOption("-d <DAY>")]
[Description("The days the lion goes hunting.")]
[DefaultValue(new[] { DayOfWeek.Monday, DayOfWeek.Thursday })]
public required DayOfWeek[] HuntDays { get; set; }

[CommandOption("-n|-p|--name|--pet-name <VALUE>")]
public required string Name { get; set; }

[CommandOption("-a|--age <AGE>")]
[CompletionSuggestions("10", "15", "20", "30")]
public int Age { get; set; }
}
29 changes: 29 additions & 0 deletions examples/Cli/AutoCompletion/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Diagnostics;
using Spectre.Console.Cli;

namespace AutoCompletion;

// Adding autocomplete to powershell:
// - .\AutoCompletion.exe completion powershell
//
// Adding autocomplete to powershell (permanent):
// - .\AutoCompletion.exe completion powershell --install
//
// Test completing:
// - .\AutoCompletion.exe cli complete "Li"
internal static class Program
{
private static void Main(string[] args)
{
// If we just want to test the completion with f5 in visual studio
if (Debugger.IsAttached)
{
args = new[] { "cli", "complete", "\"Li\"" };
}

var app = new CommandApp();
app.Configure(config => config.AddCommand<LionCommand>("lion"));

app.Run(args);
}
}
17 changes: 16 additions & 1 deletion examples/Examples.sln
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Json", "Console\Json\Json.c
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Json", "..\src\Spectre.Console.Json\Spectre.Console.Json.csproj", "{91A5637F-1F89-48B3-A0BA-6CC629807393}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Help", "Cli\Help\Help.csproj", "{BAB490D6-FF8D-462B-B2B0-933384D629DB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Help", "Cli\Help\Help.csproj", "{BAB490D6-FF8D-462B-B2B0-933384D629DB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Decorations", "Console\Decorations\Decorations.csproj", "{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoCompletion", "Cli\AutoCompletion\AutoCompletion.csproj", "{ED15824F-512E-4977-BB22-6122B625F960}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -577,6 +579,18 @@ Global
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Release|x64.Build.0 = Release|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Release|x86.ActiveCfg = Release|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Release|x86.Build.0 = Release|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Debug|x64.ActiveCfg = Debug|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Debug|x64.Build.0 = Debug|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Debug|x86.ActiveCfg = Debug|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Debug|x86.Build.0 = Debug|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Release|Any CPU.Build.0 = Release|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Release|x64.ActiveCfg = Release|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Release|x64.Build.0 = Release|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Release|x86.ActiveCfg = Release|Any CPU
{ED15824F-512E-4977-BB22-6122B625F960}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -593,6 +607,7 @@ Global
{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7} = {2571F1BD-6556-4F96-B27B-B6190E1BF13A}
{91A5637F-1F89-48B3-A0BA-6CC629807393} = {2571F1BD-6556-4F96-B27B-B6190E1BF13A}
{BAB490D6-FF8D-462B-B2B0-933384D629DB} = {4682E9B7-B54C-419D-B92F-470DA4E5674C}
{ED15824F-512E-4977-BB22-6122B625F960} = {4682E9B7-B54C-419D-B92F-470DA4E5674C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3EE724C5-CAB4-410D-AC63-8D4260EF83ED}
Expand Down
13 changes: 13 additions & 0 deletions src/Spectre.Console.Cli/AsyncCommandOfT.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Spectre.Console.Cli.Completion;

namespace Spectre.Console.Cli;

/// <summary>
Expand Down Expand Up @@ -44,4 +46,15 @@ Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings
{
return ExecuteAsync(context, settings);
}

/// <summary>
/// Gets a new suggestion matcher for this command.
/// </summary>
/// <returns>A suggestion matcher.</returns>
public virtual AsyncCommandParameterMatcher<TSettings> AsyncSuggestionMatcher => new();

/// <summary>
/// Gets a new suggestion matcher for this command.
/// </summary>
public virtual CommandParameterMatcher<TSettings> SuggestionMatcher => new();
}
Loading
Loading