diff --git a/src/System.CommandLine.Tests/CompletionTests.cs b/src/System.CommandLine.Tests/CompletionTests.cs index ab238d6b68..bb9691b151 100644 --- a/src/System.CommandLine.Tests/CompletionTests.cs +++ b/src/System.CommandLine.Tests/CompletionTests.cs @@ -57,20 +57,25 @@ public void Command_GetCompletions_returns_available_option_aliases() [Fact] // https://github.com/dotnet/command-line-api/issues/1563 public void Command_GetCompletions_returns_available_option_aliases_for_global_options() { - var subcommand = new Command("command") + var subcommand2 = new Command("command2") { new Option("--one", "option one"), new Option("--two", "option two") }; + var subcommand1 = new Command("command1") + { + subcommand2 + }; + var rootCommand = new RootCommand { - subcommand + subcommand1 }; rootCommand.AddGlobalOption(new Option("--three", "option three")); - var completions = subcommand.GetCompletions(CompletionContext.Empty); + var completions = subcommand2.GetCompletions(CompletionContext.Empty); completions .Select(item => item.Label) diff --git a/src/System.CommandLine.Tests/ParseResultTests.cs b/src/System.CommandLine.Tests/ParseResultTests.cs index c8d87b6df4..d0f22af927 100644 --- a/src/System.CommandLine.Tests/ParseResultTests.cs +++ b/src/System.CommandLine.Tests/ParseResultTests.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Linq; using System.CommandLine.Parsing; using FluentAssertions; using Xunit; @@ -83,5 +84,42 @@ public void Command_will_not_accept_a_command_if_a_sibling_command_has_already_b result2.CommandResult.Command.Name.Should().Be("inner-two"); result2.Errors.Count.Should().Be(1); } + + [Fact] // https://github.com/dotnet/command-line-api/pull/2030#issuecomment-1400275332 + public void ParseResult_GetCompletions_returns_global_options_of_given_command_only() + { + var leafCommand = new Command("leafCommand") + { + new Option("--one", "option one"), + new Option("--two", "option two") + }; + + var midCommand1 = new Command("midCommand1") + { + leafCommand + }; + midCommand1.AddGlobalOption(new Option("--three1", "option three 1")); + + var midCommand2 = new Command("midCommand2") + { + leafCommand + }; + midCommand2.AddGlobalOption(new Option("--three2", "option three 2")); + + var rootCommand = new Command("root") + { + midCommand1, + midCommand2 + }; + + var result = new Parser(rootCommand).Parse("root midCommand2 leafCommand --"); + + var completions = result.GetCompletions(); + + completions + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--one", "--two", "--three2"); + } } } diff --git a/src/System.CommandLine/Command.cs b/src/System.CommandLine/Command.cs index 2c0a1adbfc..1bf406913a 100644 --- a/src/System.CommandLine/Command.cs +++ b/src/System.CommandLine/Command.cs @@ -192,20 +192,28 @@ public override IEnumerable GetCompletions(CompletionContext con ParentNode? parent = FirstParent; while (parent is not null) { - if (parent.Symbol is Command parentCommand && parentCommand.HasOptions) + Command parentCommand = (Command)parent.Symbol; + + if (context.IsEmpty || context.ParseResult.FindResultFor(parentCommand) is not null) { - for (var i = 0; i < parentCommand.Options.Count; i++) + if (parentCommand.HasOptions) { - var option = parentCommand.Options[i]; - - if (option.IsGlobal) + for (var i = 0; i < parentCommand.Options.Count; i++) { - AddCompletionsFor(option); + var option = parentCommand.Options[i]; + + if (option.IsGlobal) + { + AddCompletionsFor(option); + } } } + parent = parent.Symbol.FirstParent; + } + else + { + parent = parent.Next; } - - parent = parent.Next; } } diff --git a/src/System.CommandLine/Completions/CompletionContext.cs b/src/System.CommandLine/Completions/CompletionContext.cs index 0f8f928e9b..708027b836 100644 --- a/src/System.CommandLine/Completions/CompletionContext.cs +++ b/src/System.CommandLine/Completions/CompletionContext.cs @@ -31,6 +31,8 @@ internal CompletionContext(ParseResult parseResult, string wordToComplete) /// Can be used for testing purposes. public static CompletionContext Empty => _empty ??= new TokenCompletionContext(ParseResult.Empty()); + internal bool IsEmpty => ReferenceEquals(this, _empty); + /// /// Gets the text to be matched for completion, which can be used to filter a list of completions. ///