Skip to content

Commit da3be10

Browse files
authored
Merge pull request #2621 from Widthdom/fix-issue1693
Fix subcommand help usage
2 parents 84c46d8 + a7a4a6f commit da3be10

5 files changed

Lines changed: 128 additions & 13 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 1693
5+
affected:
6+
- src/CodeIndex/Cli/CliFlagSchema.cs
7+
- src/CodeIndex/Cli/ConsoleUi.cs
8+
- src/CodeIndex/Cli/ProgramRunner.cs
9+
- tests/CodeIndex.Tests/ProgramCliTests.cs
10+
---
11+
12+
## English
13+
14+
- **Subcommand help now prints command-specific usage (#1693)**`cdidx <command> --help` now shows the matching command's synopsis instead of the full top-level help block.
15+
16+
## 日本語
17+
18+
- **サブコマンド help がコマンド別の usage を表示するようになりました (#1693)**`cdidx <command> --help` はトップレベル help 全体ではなく、対象コマンドの synopsis を表示するようになりました。

src/CodeIndex/Cli/CliFlagSchema.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ internal static class CliFlagSchema
5454
[
5555
"index", "backfill-fold", "optimize", "search", "definition", "references", "callers", "callees",
5656
"symbols", "files", "find", "excerpt", "map", "inspect", "outline", "status",
57-
"validate", "deps", "impact", "unused", "hotspots", "languages", "batch", "mcp", "db", "vacuum", "report", "license",
57+
"validate", "deps", "impact", "unused", "hotspots", "languages", "batch", "mcp", "completions", "db", "vacuum", "report", "license",
5858
];
5959

6060
// Commands that accept the `--` end-of-options marker so a user can pass a literal

src/CodeIndex/Cli/ConsoleUi.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ private static readonly (string Command, string Usage)[] CommandUsageLines =
9393
("languages", "cdidx languages [--json]"),
9494
("batch", "cdidx batch [--db <path>] # reads JSON string arrays from stdin, one query command per line"),
9595
("mcp", "cdidx mcp [--db <path>]"),
96+
("completions", "cdidx completions <shell>"),
9697
("license", "cdidx license"),
9798
];
9899

@@ -794,6 +795,35 @@ public static void PrintLicenseSummary()
794795
return null;
795796
}
796797

798+
public static bool PrintCommandUsage(string command)
799+
{
800+
var usages = GetCommandUsageLines(command);
801+
if (usages.Count == 0)
802+
return false;
803+
804+
Console.WriteLine("Usage:");
805+
foreach (var usage in usages)
806+
Console.WriteLine($" {usage}");
807+
Console.WriteLine();
808+
Console.WriteLine("Run `cdidx --help` to show all commands and shared options.");
809+
return true;
810+
}
811+
812+
private static IReadOnlyList<string> GetCommandUsageLines(string command)
813+
{
814+
var usages = new List<string>();
815+
foreach (var (name, usage) in CommandUsageLines)
816+
{
817+
if (string.Equals(name, command, StringComparison.Ordinal)
818+
|| string.Equals(command, "index", StringComparison.Ordinal) && name.StartsWith("index-", StringComparison.Ordinal))
819+
{
820+
usages.Add(usage);
821+
}
822+
}
823+
824+
return usages;
825+
}
826+
797827
// --- Did-you-mean / もしかして ---
798828

799829
/// <summary>
@@ -910,7 +940,7 @@ private static int DamerauLevenshteinDistance(string s, string t)
910940
[
911941
"index", "backfill-fold", "optimize", "search", "definition", "references", "callers", "callees",
912942
"symbols", "files", "find", "excerpt", "map", "inspect", "outline", "status",
913-
"validate", "deps", "impact", "unused", "hotspots", "languages", "batch", "mcp", "db", "vacuum", "report", "license",
943+
"validate", "deps", "impact", "unused", "hotspots", "languages", "batch", "mcp", "completions", "db", "vacuum", "report", "license",
914944
];
915945

916946
/// <summary>

src/CodeIndex/Cli/ProgramRunner.cs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,23 +87,40 @@ internal static int Run(
8787

8888
if (args[0] is "--license" or "license")
8989
{
90+
if (args[0] == "license" && args.Length > 1 && ArgHelper.WantsHelp(args.AsSpan(1)))
91+
{
92+
ConsoleUi.PrintCommandUsage("license");
93+
GlobalToolLog.Info($"command_complete exit_code={CommandExitCodes.Success} subcommand_help=true");
94+
EmitCommandMetric("license", args, commandStartTimestamp, commandStopwatch, CommandExitCodes.Success);
95+
return CommandExitCodes.Success;
96+
}
97+
9098
ConsoleUi.PrintLicenseSummary();
9199
GlobalToolLog.Info($"command_complete exit_code={CommandExitCodes.Success} license_only=true");
92100
EmitCommandMetric("license", args, commandStartTimestamp, commandStopwatch, CommandExitCodes.Success);
93101
return CommandExitCodes.Success;
94102
}
95103

96-
if (args[0] == "--completions")
104+
if (args[0] is "--completions" or "completions")
97105
{
98-
var exitCode = RunCompletions(args[1..]);
106+
if (args[0] == "completions" && args.Length > 1 && ArgHelper.WantsHelp(args.AsSpan(1)))
107+
{
108+
ConsoleUi.PrintCommandUsage("completions");
109+
GlobalToolLog.Info($"command_complete exit_code={CommandExitCodes.Success} subcommand_help=true");
110+
EmitCommandMetric("completions", args, commandStartTimestamp, commandStopwatch, CommandExitCodes.Success);
111+
return CommandExitCodes.Success;
112+
}
113+
114+
var exitCode = RunCompletions(args[1..], args[0] == "completions" ? "completions" : "--completions");
99115
GlobalToolLog.Info($"command_complete exit_code={exitCode} command=completions");
100116
EmitCommandMetric("completions", args, commandStartTimestamp, commandStopwatch, exitCode);
101117
return exitCode;
102118
}
103119

104120
if (args.Length > 1 && ArgHelper.WantsHelp(args.AsSpan(1)))
105121
{
106-
ConsoleUi.PrintUsage(showBanner: true);
122+
if (!ConsoleUi.PrintCommandUsage(args[0]))
123+
ConsoleUi.PrintUsage(showBanner: true);
107124
GlobalToolLog.Info($"command_complete exit_code={CommandExitCodes.Success} subcommand_help=true");
108125
EmitCommandMetric(args[0], args, commandStartTimestamp, commandStopwatch, CommandExitCodes.Success);
109126
return CommandExitCodes.Success;
@@ -1078,28 +1095,29 @@ internal static string FormatVersionLine(ConsoleUi.BuildMetadata metadata, strin
10781095
return $"cdidx v{metadata.Version} (commit {commit}, built {buildDate}, {dirty}){suffix}";
10791096
}
10801097

1081-
private static int RunCompletions(string[] cmdArgs)
1098+
private static int RunCompletions(string[] cmdArgs, string commandName = "--completions")
10821099
{
1100+
var usage = $"cdidx {commandName} <shell>";
10831101
if (cmdArgs.Length == 0)
10841102
return CommandErrorWriter.Write(
1085-
"--completions requires a shell value.",
1103+
$"{commandName} requires a shell value.",
10861104
CommandExitCodes.UsageError,
10871105
"rerun with one of `bash`, `zsh`, `fish`, or `powershell`.",
1088-
"cdidx --completions <shell>");
1106+
usage);
10891107

10901108
if (cmdArgs[0].StartsWith("-", StringComparison.Ordinal))
10911109
return CommandErrorWriter.Write(
1092-
$"--completions requires a shell value, got option-like token '{cmdArgs[0]}'.",
1110+
$"{commandName} requires a shell value, got option-like token '{cmdArgs[0]}'.",
10931111
CommandExitCodes.UsageError,
10941112
"rerun with one of `bash`, `zsh`, `fish`, or `powershell`.",
1095-
"cdidx --completions <shell>");
1113+
usage);
10961114

10971115
if (cmdArgs.Length > 1)
10981116
return CommandErrorWriter.Write(
1099-
$"--completions accepts exactly one shell value, got extra {ConsoleUi.Counted(cmdArgs.Length - 1, "argument")}: {string.Join(", ", cmdArgs.Skip(1).Select(arg => $"`{arg}`"))}.",
1117+
$"{commandName} accepts exactly one shell value, got extra {ConsoleUi.Counted(cmdArgs.Length - 1, "argument")}: {string.Join(", ", cmdArgs.Skip(1).Select(arg => $"`{arg}`"))}.",
11001118
CommandExitCodes.UsageError,
11011119
"rerun with exactly one shell name: `bash`, `zsh`, `fish`, or `powershell`.",
1102-
"cdidx --completions <shell>");
1120+
usage);
11031121

11041122
if (ConsoleUi.PrintCompletions(cmdArgs[0]))
11051123
return CommandExitCodes.Success;
@@ -1108,7 +1126,7 @@ private static int RunCompletions(string[] cmdArgs)
11081126
$"unsupported completion shell `{cmdArgs[0]}`.",
11091127
CommandExitCodes.UsageError,
11101128
"rerun with one of `bash`, `zsh`, `fish`, or `powershell`.",
1111-
"cdidx --completions <shell>");
1129+
usage);
11121130
}
11131131

11141132
private static string StripErrorPrefix(string message)

tests/CodeIndex.Tests/ProgramCliTests.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,55 @@ public void Completions_OptionLikeShellTokenReturnsUsageError()
128128
Assert.DoesNotContain("Unknown shell", stderr);
129129
}
130130

131+
[Theory]
132+
[InlineData("index", "cdidx index <projectPath>")]
133+
[InlineData("search", "cdidx search <query>")]
134+
[InlineData("references", "cdidx references <query>")]
135+
[InlineData("callers", "cdidx callers <query>")]
136+
[InlineData("callees", "cdidx callees <query>")]
137+
[InlineData("impact", "cdidx impact <query>")]
138+
[InlineData("unused", "cdidx unused")]
139+
[InlineData("validate", "cdidx validate")]
140+
[InlineData("backfill-fold", "cdidx backfill-fold")]
141+
[InlineData("outline", "cdidx outline <path>")]
142+
[InlineData("inspect", "cdidx inspect <query>")]
143+
[InlineData("definition", "cdidx definition <query>")]
144+
[InlineData("find", "cdidx find <query>")]
145+
[InlineData("excerpt", "cdidx excerpt <path>")]
146+
[InlineData("hotspots", "cdidx hotspots")]
147+
[InlineData("deps", "cdidx deps")]
148+
[InlineData("map", "cdidx map")]
149+
[InlineData("status", "cdidx status")]
150+
[InlineData("completions", "cdidx completions <shell>")]
151+
[InlineData("license", "cdidx license")]
152+
public void SubcommandHelp_PrintsCommandSpecificUsage(string command, string expectedUsage)
153+
{
154+
var (exitCode, stdout, stderr) = RunCliInSubprocess([command, "--help"]);
155+
156+
Assert.Equal(0, exitCode);
157+
Assert.Equal(string.Empty, stderr);
158+
Assert.Contains("Usage:", stdout);
159+
Assert.Contains(expectedUsage, stdout);
160+
Assert.Contains("Run `cdidx --help`", stdout);
161+
Assert.DoesNotContain("Commands:", stdout);
162+
Assert.DoesNotContain("Index and update options:", stdout);
163+
Assert.DoesNotContain("██████╗", stdout);
164+
}
165+
166+
[Theory]
167+
[InlineData("completions")]
168+
[InlineData("completions", "--json")]
169+
[InlineData("completions", "bash", "extra")]
170+
public void CompletionsCommand_ErrorsUseCommandUsage(params string[] args)
171+
{
172+
var (exitCode, stdout, stderr) = RunCliInSubprocess(args);
173+
174+
Assert.Equal(1, exitCode);
175+
Assert.Equal(string.Empty, stdout);
176+
Assert.Contains("Usage: cdidx completions <shell>", stderr);
177+
Assert.DoesNotContain("Usage: cdidx --completions <shell>", stderr);
178+
}
179+
131180
[Fact]
132181
public void Completions_ExtraArgsReturnUsageError()
133182
{

0 commit comments

Comments
 (0)