sandlot/src/commands/completions.ts

73 lines
2.7 KiB
TypeScript

import type { Command, Option } from "commander"
/** Generate fish completions dynamically from the Commander program tree. */
export function action(program: Command) {
const lines: string[] = [
"# Fish completions for sandlot (auto-generated)",
"# Install: sandlot completions > ~/.config/fish/completions/sandlot.fish",
"",
"complete -c sandlot -f",
"",
"function __sandlot_sessions",
` command sandlot list --json 2>/dev/null | string match -r '"branch":\\s*"[^"]+"' | string replace -r '.*"branch":\\s*"([^"]+)".*' '$1'`,
"end",
"",
]
const branchCommands: string[] = []
for (const cmd of program.commands) {
const name = cmd.name()
const desc = cmd.description()
const subs = cmd.commands as Command[]
if (subs.length > 0) {
// Parent command (e.g. "vm") — register it plus its subcommands
lines.push(`complete -c sandlot -n __fish_use_subcommand -a ${name} -d ${esc(desc)}`)
const subNames = subs.filter(s => !(s as any)._hidden).map(s => s.name())
const guard = `"__fish_seen_subcommand_from ${name}; and not __fish_seen_subcommand_from ${subNames.join(" ")}"`
for (const sub of subs) {
if ((sub as any)._hidden) continue
lines.push(`complete -c sandlot -n ${guard} -a ${sub.name()} -d ${esc(sub.description())}`)
}
// Options on subcommands
for (const sub of subs) {
if ((sub as any)._hidden) continue
emitOptions(lines, `${name} ${sub.name()}`, sub.options)
}
} else {
// Leaf command
lines.push(`complete -c sandlot -n __fish_use_subcommand -a ${name} -d ${esc(desc)}`)
emitOptions(lines, name, cmd.options)
// Track commands that accept a <branch> / [branch] argument
const hasBranch = cmd.registeredArguments.some(a => a.name() === "branch")
if (hasBranch) branchCommands.push(name)
}
}
// Session completions for all branch-taking commands
if (branchCommands.length > 0) {
lines.push("")
lines.push(`complete -c sandlot -n "__fish_seen_subcommand_from ${branchCommands.join(" ")}" -xa "(__sandlot_sessions)"`)
}
lines.push("")
process.stdout.write(lines.join("\n") + "\n")
}
function esc(s: string): string {
return `"${s.replace(/"/g, '\\"')}"`
}
function emitOptions(lines: string[], cmdName: string, options: readonly Option[]) {
for (const opt of options) {
const parts = [`complete -c sandlot -n "__fish_seen_subcommand_from ${cmdName}"`]
if (opt.short) parts.push(`-s ${opt.short.replace("-", "")}`)
if (opt.long) parts.push(`-l ${opt.long.replace("--", "")}`)
parts.push(`-d ${esc(opt.description)}`)
if (opt.required) parts.push("-r")
lines.push(parts.join(" "))
}
}