/ src / completion.ts
completion.ts
 1  /**
 2   * Shell tab-completion support for opencli.
 3   *
 4   * Provides:
 5   *  - Shell script generators for bash, zsh, and fish
 6   *  - Dynamic completion logic that returns candidates for the current cursor position
 7   */
 8  
 9  import { getRegistry } from './registry.js';
10  import { CliError } from './errors.js';
11  import {
12    BUILTIN_COMMANDS,
13    bashCompletionScript,
14    zshCompletionScript,
15    fishCompletionScript,
16  } from './completion-shared.js';
17  
18  // Re-export shell scripts so existing callers (cli.ts) don't break
19  export { bashCompletionScript, zshCompletionScript, fishCompletionScript };
20  
21  // ── Dynamic completion logic ───────────────────────────────────────────────
22  
23  /**
24   * Return completion candidates given the current command-line words and cursor index.
25   * Requires full CLI discovery to have been run (uses getRegistry()).
26   *
27   * @param words  - The argv after 'opencli' (words[0] is the first arg, e.g. site name)
28   * @param cursor - 1-based position of the word being completed (1 = first arg)
29   */
30  export function getCompletions(words: string[], cursor: number): string[] {
31    // cursor === 1 → completing the first argument (site name or built-in command)
32    if (cursor <= 1) {
33      const sites = new Set<string>();
34      for (const [, cmd] of getRegistry()) {
35        sites.add(cmd.site);
36      }
37      return [...BUILTIN_COMMANDS, ...sites].sort();
38    }
39  
40    const site = words[0];
41  
42    // If the first word is a built-in command, no further completion
43    if (BUILTIN_COMMANDS.includes(site)) {
44      return [];
45    }
46  
47    // cursor === 2 → completing the sub-command name under a site
48    if (cursor === 2) {
49      const subcommands: string[] = [];
50      for (const [, cmd] of getRegistry()) {
51        if (cmd.site === site) {
52          subcommands.push(cmd.name);
53          if (cmd.aliases?.length) subcommands.push(...cmd.aliases);
54        }
55      }
56      return [...new Set(subcommands)].sort();
57    }
58  
59    // cursor >= 3 → no further completion
60    return [];
61  }
62  
63  // ── Shell script generators ────────────────────────────────────────────────
64  
65  /**
66   * Print the completion script for the requested shell.
67   */
68  export function printCompletionScript(shell: string): void {
69    switch (shell) {
70      case 'bash':
71        process.stdout.write(bashCompletionScript());
72        break;
73      case 'zsh':
74        process.stdout.write(zshCompletionScript());
75        break;
76      case 'fish':
77        process.stdout.write(fishCompletionScript());
78        break;
79      default:
80        throw new CliError('UNSUPPORTED_SHELL', `Unsupported shell: ${shell}. Supported: bash, zsh, fish`);
81    }
82  }