diff --git a/.changeset/fix-zsh-mixed-command-completions.md b/.changeset/fix-zsh-mixed-command-completions.md new file mode 100644 index 00000000000..67978c2bd0d --- /dev/null +++ b/.changeset/fix-zsh-mixed-command-completions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Zsh completions for CLI commands with both positional arguments and subcommands. diff --git a/packages/effect/src/unstable/cli/internal/completions/zsh.ts b/packages/effect/src/unstable/cli/internal/completions/zsh.ts index 84792df0a17..666be3f0048 100644 --- a/packages/effect/src/unstable/cli/internal/completions/zsh.ts +++ b/packages/effect/src/unstable/cli/internal/completions/zsh.ts @@ -133,8 +133,14 @@ const generateFunction = ( lines.push(` ${spec}`) } } - for (const arg of descriptor.arguments) { - lines.push(` ${argSpec(arg)}`) + if (descriptor.arguments.length > 0) { + lines.push(` -`) + lines.push(` parent-arguments`) + for (const arg of descriptor.arguments) { + lines.push(` ${argSpec(arg)}`) + } + lines.push(` -`) + lines.push(` subcommands`) } lines.push(` '1:command:->command'`) lines.push(` '*::arg:->args'`) diff --git a/packages/effect/test/unstable/cli/completions/completions.test.ts b/packages/effect/test/unstable/cli/completions/completions.test.ts index 474bd89a62f..88ff9158157 100644 --- a/packages/effect/test/unstable/cli/completions/completions.test.ts +++ b/packages/effect/test/unstable/cli/completions/completions.test.ts @@ -69,6 +69,17 @@ const withPaths = Command.make("process", { ) }).pipe(Command.withDescription("Process files")) +const withOptionalDirectoryAndSubcommands = Command.make("example", { + directory: Argument.directory("directory").pipe( + Argument.withDescription("Directory to start in"), + Argument.optional + ) +}).pipe( + Command.withSubcommands([ + Command.make("serve").pipe(Command.withDescription("Start the server")) + ]) +) + const nested3Levels = (() => { const leaf = Command.make("action", { dryRun: Flag.boolean("dry-run").pipe(Flag.withDescription("Dry run mode")) @@ -341,6 +352,27 @@ describe("Zsh completions", () => { assert.include(script, "(us-east eu-west ap-south)") }) + it("uses alternative argument sets for positional arguments and subcommands", () => { + const desc = fromCommand(withOptionalDirectoryAndSubcommands) + const script = Zsh.generate("example", desc) + + assert.include( + script, + ` - + parent-arguments + ':Directory to start in:_directories' + - + subcommands + '1:command:->command' + '*::arg:->args'` + ) + assert.notInclude( + script, + ` ':Directory to start in:_directories' + '1:command:->command'` + ) + }) + it("starts with #compdef directive", () => { const desc = fromCommand(simpleCmd) const script = Zsh.generate("greet", desc)