用 Carapace 接管 Nushell 外部补全
书接上回。早先配置 Nushell 补全时,我在 external_completer 里维护了一份白名单:Fish 补全较完善就走 fish_completer,否则走 carapace_completer。核心逻辑大概如下:match $spans.0 { nu | tv | bun | git | rclone => $fish_completer _ => $carapace_completer} | do $in $spans
这段配置可以工作。只是选择逻辑被写进了 Nushell 配置,其他 shell 无法复用;迁移到 Carapace 这个中间层,则不需要在每个 shell 里重复维护同一套分发规则。
当然,我大概率不会再使用其他 shell,有种为了折腾而折腾的感觉😇
从 brew services 说起 🍺
最近从 Clash Verge Rev 转向 mihomo 裸核,启动服务时会频繁用到 brew services start mihomo。
Carapace 暂时不支持为 brew services 子命令补全服务名;比较惊喜地发现 Fish 里可以补全:
按照前面的做法,只需要把 brew 的补全转发到 fish_completer 即可,形如:match $spans.0 { nu | tv | bun | git | rclone | brew => $fish_completer _ => $carapace_completer} | do $in $spans
这能修好当前问题,但补全策略还是散落在 Nushell 里。下面讲一下Carapace的做法
Carapace 的选择顺序 🔢
Carapace 现在把 completer 分成不同 group。以 macOS 为例,一个命令同时存在多个补全来源时,默认优先级大致是:
usersystemdarwinbsdunixcommonbridge
这解释了为什么只启用 Fish bridge(即设置了环境变量 CARAPACE_BRIDGES=fish)不一定能解决 brew 的问题;例如运行carapace brew nushell brew se | jq,尝试查看brew se的补全结果,会发现只有search子命令 —— services被漏掉了[ { "value": "search ", "display": "search", "description": "Perform a substring search of cask tokens and formula names for <text>", "style": { "fg": "blue" } }]
原因在于brew 已经有 Carapace 内置补全,属于 common;Fish bridge 如果通过 CARAPACE_BRIDGES 发现,则属于 bridge。默认情况下,common 会压过 bridge。
可以用 carapace --list brew 看实际顺序:{ "brew": [ { "name": "brew", "description": "The missing package manager for macOS", "group": "common", "package": "github.com/carapace-sh/carapace-bin/completers_release/common/brew_completer/cmd", "url": "https://brew.sh/" }, { "name": "brew", "description": "The missing package manager for macOS", "group": "bridge", "variant": "fish" } ]}
对此,可以使用carapace choices 调整命令补全来源的优先级,参考下面代码,将fish补全移到前面,再次运行carapace brew nushell brew se | jq .,可以发现除了search,还提供了services和setup-ruby的补全入口:$ carapace --choice brew/fish@bridge$ carapace brew nushell brew se | jq .[ { "value": "search ", "display": "search", "description": "Perform a substring search of cask tokens and formula names for text" }, { "value": "services ", "display": "services", "description": "Integrates Homebrew formulae with macOS's launchctl manager" }, { "value": "setup-ruby ", "display": "setup-ruby", "description": "Installs and configures Homebrew's Ruby" }]
另一种解决方案: 用 user spec 覆写 brew 👥
前面优先级中可以看到,user group 是优先级最高的补全来源。那么自然可以想到在user group中定义,让brew走fish的补全方案。
Carapace 会自动加载 配置目录 下的 specs。
个人设置了XDG_CONFIG_HOME = ~/.config,所以在~/.config/carapace/specs新建 brew.yaml:# yaml-language-server: $schema=https://carapace.sh/schemas/command.jsonname: brewdescription: The missing package manager for macOSparsing: disabledcompletion: positionalany: ["$carapace.bridge.Fish([brew])"]
注意name: brew 必须和文件名 brew.yaml 对上。
结束后,新开一个 shell,运行carapace brew nushell brew se | jq,会看到同上一节一致的输出:补全结果包含完整的三个子命令
Nushell 补全配置的清理 🫧
完成上述工作,Nushell 侧就不需要维护「哪些命令走 Fish」的白名单了。保留别名展开逻辑即可,最后统一交给 Carapace:let carapace_completer = {|spans: list<string>| CARAPACE_LENIENT=1 carapace $spans.0 nushell ...$spans | from json}let external_completer = {|spans| let expanded_alias = scope aliases | where name == $spans.0 | get -o 0.expansion let spans = if $expanded_alias != null { $spans | skip 1 | prepend ($expanded_alias | split row ' ' | take 1) } else { $spans } match $spans.0 { _ => $carapace_completer } | do $in $spans}$env.config.completions = { case_sensitive: false quick: true # set this to false to prevent auto-selecting completions when only one remains partial: true # set this to false to prevent partial filling of the prompt algorithm: "prefix" # prefix or fuzzy external: { enable: true completer: $external_completer } use_ls_colors: true}
