-
Notifications
You must be signed in to change notification settings - Fork 17
feat(completion): complete context names for positional args #424
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,14 +32,17 @@ export interface CompletionResult { | |
| export type DynamicCompleter = () => string[] | Promise<string[]> | ||
|
|
||
| /** | ||
| * Lookup table for dynamic completers keyed by flag long name (including `--`). | ||
| * Lookup table for dynamic completers keyed by flag long name (including `--`) | ||
| * or by command path (e.g. `"config current-context set"`). | ||
| * | ||
| * Returning `undefined` for an unknown flag is mandatory: the completion path | ||
| * Returning `undefined` for an unknown key is mandatory: the completion path | ||
| * MUST NOT throw or block, so completers that are unavailable for the current | ||
| * environment simply do not register themselves. | ||
| */ | ||
| export interface DynamicCompleterRegistry { | ||
| get(flagLong: string): DynamicCompleter | undefined | ||
| /** Returns a positional-argument completer for the given space-joined command path, if any. */ | ||
| getPositional?(commandPath: string): DynamicCompleter | undefined | ||
| } | ||
|
|
||
| // Commander stores hidden state on an internal `_hidden` field that is not | ||
|
|
@@ -110,6 +113,21 @@ function optionTakesArg (current: Command, root: Command, word: string): boolean | |
| return false | ||
| } | ||
|
|
||
| /** | ||
| * Builds a space-joined path for `cmd` excluding the root program name. | ||
| * e.g. the `set` subcommand under `elastic config current-context` → `"config current-context set"`. | ||
| */ | ||
| function getCommandPath (cmd: Command): string { | ||
| const parts: string[] = [] | ||
| let c: Command | null = cmd | ||
| while (c != null && c.parent != null) { | ||
| parts.push(c.name()) | ||
| c = c.parent | ||
| } | ||
| parts.reverse() | ||
| return parts.join(' ') | ||
| } | ||
|
|
||
| function prefixFilter (candidates: readonly string[], prefix: string): string[] { | ||
| if (prefix === '') return [...candidates] | ||
| return candidates.filter((c) => c.startsWith(prefix)) | ||
|
|
@@ -146,8 +164,9 @@ async function safeRun (fn: DynamicCompleter): Promise<string[]> { | |
| * 4. If the immediately previous token is a registered dynamic flag (e.g. | ||
| * `--use-context`), return that completer's candidates filtered by the | ||
| * current word. | ||
| * 5. Otherwise, return the subcommand names and aliases of the deepest matched | ||
| * command/group, filtered by the current word. | ||
| * 5. Otherwise, if the deepest command is a leaf (no subcommands) and a | ||
| * positional completer is registered for its path, return those candidates. | ||
| * Falls back to subcommand names and aliases (empty for leaf commands). | ||
| * | ||
| * Result is always returned (never thrown). `directive` always carries | ||
| * `DIRECTIVE_NO_FILE_COMP` so the shell does not fall back to filename | ||
|
|
@@ -219,9 +238,22 @@ export async function enumerate ( | |
| return { candidates: [], directive: DIRECTIVE_NO_FILE_COMP } | ||
| } | ||
|
|
||
| const cands = collectChildren(current) | ||
| const children = collectChildren(current) | ||
| // Leaf command (no subcommands): offer a registered positional completer. | ||
| // Note: this fires regardless of how many positional args are already typed, | ||
| // so a single-positional command re-offers candidates after the first. | ||
| if (children.length === 0 && registry != null) { | ||
| const positionalCompleter = registry.getPositional?.(getCommandPath(current)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doc nit: the function-level JSDoc above (the numbered algorithm, step 5) still describes the default branch as returning only "subcommand names and aliases of the deepest matched command/group." With this change, a leaf command that has a registered positional completer returns those candidates instead. The interface comment was updated, but the algorithm narrative — the canonical description of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both addressed in 935fffe — added the inline comment and updated the algorithm docstring in step 5. |
||
| if (positionalCompleter != null) { | ||
| const cands = await safeRun(positionalCompleter) | ||
| return { | ||
| candidates: prefixFilter(cands, incomplete), | ||
| directive: DIRECTIVE_NO_FILE_COMP, | ||
| } | ||
| } | ||
| } | ||
| return { | ||
| candidates: prefixFilter(cands, incomplete), | ||
| candidates: prefixFilter(children, incomplete), | ||
| directive: DIRECTIVE_NO_FILE_COMP, | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The branch offers positional candidates whenever the leaf has no children, regardless of how many positionals are already typed. For a single-positional command like
set,config current-context set prod <TAB>walks to thesetleaf (prodisn't a child, so the walk breaks on it) and context names are offered again for a second positional the command doesn't accept. Harmless in practice — but worth a one-line note documenting it as a conscious won't-fix, since matching positional depth would add real complexity:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤖 Claude Code
Both addressed in 935fffe — added the inline comment and updated the algorithm docstring in step 5.