From 29a6475b101ea8e739ed8526ab5f5a140c56d96c Mon Sep 17 00:00:00 2001 From: Roger Vuistiner Date: Fri, 7 Nov 2025 16:57:54 +0100 Subject: [PATCH 1/2] Improve completion logic to support suggestion of subcommands. --- src/System.CommandLine/Command.cs | 13 +++++++------ .../Completions/CompletionAction.cs | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/System.CommandLine/Command.cs b/src/System.CommandLine/Command.cs index be565651d2..682408088d 100644 --- a/src/System.CommandLine/Command.cs +++ b/src/System.CommandLine/Command.cs @@ -268,9 +268,10 @@ public override IEnumerable GetCompletions(CompletionContext con if (HasSubcommands) { var commands = Subcommands; + var textToMatchIsCommandName = context.ParseResult.CommandResult.Command.Name.IndexOfCaseInsensitive(textToMatch) >= 0; for (int i = 0; i < commands.Count; i++) { - AddCompletionsFor(commands[i], commands[i]._aliases); + AddCompletionsFor(commands[i], commands[i]._aliases, textToMatchIsCommandName ? null : textToMatch); } } @@ -279,7 +280,7 @@ public override IEnumerable GetCompletions(CompletionContext con var options = Options; for (int i = 0; i < options.Count; i++) { - AddCompletionsFor(options[i], options[i]._aliases); + AddCompletionsFor(options[i], options[i]._aliases, textToMatch); } } @@ -314,7 +315,7 @@ public override IEnumerable GetCompletions(CompletionContext con if (option.Recursive) { - AddCompletionsFor(option, option._aliases); + AddCompletionsFor(option, option._aliases, textToMatch); } } } @@ -331,11 +332,11 @@ public override IEnumerable GetCompletions(CompletionContext con .OrderBy(item => item.SortText.IndexOfCaseInsensitive(context.WordToComplete)) .ThenBy(symbol => symbol.Label, StringComparer.OrdinalIgnoreCase); - void AddCompletionsFor(Symbol identifier, AliasSet? aliases) + void AddCompletionsFor(Symbol identifier, AliasSet? aliases, string? textToMatch) { if (!identifier.Hidden) { - if (identifier.Name.ContainsCaseInsensitive(textToMatch)) + if (textToMatch is null || identifier.Name.ContainsCaseInsensitive(textToMatch)) { completions.Add(new CompletionItem(identifier.Name, CompletionItem.KindKeyword, detail: identifier.Description)); } @@ -344,7 +345,7 @@ void AddCompletionsFor(Symbol identifier, AliasSet? aliases) { foreach (string alias in aliases) { - if (alias.ContainsCaseInsensitive(textToMatch)) + if (textToMatch is null || alias.ContainsCaseInsensitive(textToMatch)) { completions.Add(new CompletionItem(alias, CompletionItem.KindKeyword, detail: identifier.Description)); } diff --git a/src/System.CommandLine/Completions/CompletionAction.cs b/src/System.CommandLine/Completions/CompletionAction.cs index 44cee60f58..fc88a835ea 100644 --- a/src/System.CommandLine/Completions/CompletionAction.cs +++ b/src/System.CommandLine/Completions/CompletionAction.cs @@ -22,7 +22,7 @@ public override int Invoke(ParseResult parseResult) string? parsedValues = parseResult.GetResult(_directive)!.Values.SingleOrDefault(); string? rawInput = parseResult.CommandLineText; - int position = !string.IsNullOrEmpty(parsedValues) ? int.Parse(parsedValues) : rawInput?.Length ?? 0; + int? position = !string.IsNullOrEmpty(parsedValues) ? int.Parse(parsedValues) : rawInput?.Length; var commandLineToComplete = parseResult.Tokens.LastOrDefault(t => t.Type != TokenType.Directive)?.Value ?? ""; From 391ff810640cbb0ad45bc303366ba59c27c0d348 Mon Sep 17 00:00:00 2001 From: Roger Vuistiner Date: Fri, 7 Nov 2025 18:07:08 +0100 Subject: [PATCH 2/2] Generate completions in the context of the command being executed. # cherry-picked from commit c0a9f07a48279fb068e90f3dbbe5f3f080b05a53 # zou-flow - 14.4.0.2543 net9.0 --- src/System.CommandLine/Completions/CompletionAction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.CommandLine/Completions/CompletionAction.cs b/src/System.CommandLine/Completions/CompletionAction.cs index fc88a835ea..c8337f7928 100644 --- a/src/System.CommandLine/Completions/CompletionAction.cs +++ b/src/System.CommandLine/Completions/CompletionAction.cs @@ -26,7 +26,7 @@ public override int Invoke(ParseResult parseResult) var commandLineToComplete = parseResult.Tokens.LastOrDefault(t => t.Type != TokenType.Directive)?.Value ?? ""; - var completionParseResult = parseResult.RootCommandResult.Command.Parse(commandLineToComplete, parseResult.Configuration); + var completionParseResult = parseResult.CommandResult.Command.Parse(commandLineToComplete, parseResult.Configuration); var completions = completionParseResult.GetCompletions(position);