Scenario
命令行对程序员来说是极其有用的工具(集),惯用命令行的你一般会拥有较好的记忆力,来记住那么多的命令、参数,不然就得借助 man
、--help
或者跳出命令行打开浏览器寻找帮助说明。我们可以通过 Tab
键获得有限的 autocomplete 体验,或者安装 [zsh|bash]-autocomplete 实现交互式 autocomplete 体验。

zsh、bash 也为自定义 CLI 提供了标准的、较为便捷的通过如 complete
实现 autocomplete 的基本方法。
但是想要实现输入框 Suggestion 类似的效果,上边提到的 autocomplete 实现方式,就不能很好的支持了,比如我自己想做的一个 CLI 工具,其中一个功能就是可以用 --team
参数后输入的关键字去项目管理系统里去搜索任务并以 autocomplete 形式展示,并在选中特定任务的时候把任务 id 作为值填充到 --team
参数后。
xgit checkout -b feat-xxx --team keyword
后话是我的小伙伴提出了,想要在使用 git 命令输入分支名时,实现分段式 autocomplete 的诉求,比如输入 -b  
,提示 feat
或者 fix
,并在选中其中任一值后自动填充 feat-
or fix-
;最后可以通过输入 #
搜索并插入任务 id。(后话还有,有人希望在 git commit 命令的 msg 里可以 autocomplete 插入任务 id,毕竟如果不支持的话,就需要跳出命令行打开浏览器复制再回到命令行粘贴了…)
当然这些诉求,命令行自带的 autocomplete 是解决不了的——万事问谷歌“terminal autocomplete”,于是我就发现了 Fig。
What's Fig?
Fig 打的头牌就是“给你的命令行添加 IDE 体感的 autocomplete。让你润得更快”——事实上,这只是 Autocomplete 这 Fig 的一个插件做的事情,官方还有更大的野心,比如 DotFiles Management、Terminal Plugin System 等(我将其称之为“All in terminal”的战略),只是其他玩意儿,目前还是“coming soon”的状态。
我们可以通过 fig.io 下载并安装 Fig(注意:Fig 需要获取系统“辅助功能”权限,这可能是一个风险点,毕竟意味着包括你的输入等行为都能被监控到),Fig 支持 Mac 默认命令行、iterm、VS Code 内嵌命令行等。默认情况下,Fig 自带了包括系统命令,以及 aws、gcloud 等流程工具命令的 autocomplete 支持。在启动并注册 Fig 账号之后,就可以立即享受到这些开放命令在命令行里的 autocomplete 支持(可以在 github.com/withfig/aut… 查看所有开放的命令)。
但要满足前面提到的复杂诉求,我们还需要对 Fig Autocomplete 做一些扩展。
How to fig-autocomplete ur custom cli in terminal?
Fig 实现了一套 Autocomplete 机制,通过给定义并加载 spec 文件,比较便捷实现对自定义 CLI 的 Autocomplete 支持。
对于通过 commander、oclif 等工具库开发的 CLI,可以通过自动化工具(fig.io/docs/guides… spec。我的 CLI 工具要手写。
Fig 也提供了一套工具,来快速生成 spec 模板,执行以下命令,就可以创建好一个 xgit 自定义 CLI 的 autocomplete spec:
mkdit xgit
npm init;
tsc --init;
npm i @withfig/autocomplete-tools -D;
npm i @withfig/autocomplete-types -D; // 官方经常 break changes,一些工具的用法变化极快…
npx @withfig/autocomplete-tools create-spec xgit;
Fig autocomplete tools 会自动创建 spec 源文件 src/xgit.ts,使用 TypeScript 进行编写(可能需要把 "types": ["@withfig/autocomplete-types", "node"],
添加到 tsconfig.json 里…)。
执行 npx @withfig/autocomplete-tools dev
、npx @withfig/autocomplete-tools complie
分别可以进入开发者模式或构建 spec。
在开发者模式下,Fig 会加载 当前目录/build/
所有 spec 文件,这个时候,在命令行里,输入 xgit,就可以看到响应提示:
接下来在 src/xgit.ts
输入:
const completionSpec: Fig.Spec = {
name: "xgit",
description: "",
subcommands: [{
name: "my_subcommand",
description: "Example subcommand",
subcommands: [{
name: "my_nested_subcommand",
description: "Nested subcommand, example usage: 'xgit my_subcommand my_nested_subcommand'"
}],
}],
options: [{
name: "--team",
description: "关联一个任务",
isRepeatable: false,
args: {
name: "taskId",
generators: {
custom: async (tokens, executeShellCommand) => {
const out = await executeShellCommand(
`/usr/local/bin/xs-task ${encodeURIComponent(JSON.stringify(tokens))}`
);
const suggestions: Fig.BaseSuggestion[] = JSON.parse(out);
return suggestions;
},
cache: {
ttl: 0,
cacheByDirectory: false,
},
getQueryTerm: (token) => "",
},
isVariadic: true,
isOptional: false,
debounce: true,
suggestCurrentToken: true,
}
}],
// Only uncomment if xgit takes an argument
// args: {}
};
export default completionSpec;
里边有几个重要的配置: getQueryTerm
设置为恒返回空,即不使用 Fig autocomplete 的 Fuzzy Search 功能对结果进行过滤,这样在 --team
参数之后输入关键词就可以持续触发 generators.custom
去调用 xs-task
命令去搜索并返回格式化后的任务信息。
需要注意的是,虽然 spec 文件是 TypeScript 编写、最终转译成 JavaScript,所以 Fig 大概率内置了一个 JavaScript runtime 用来解析执行 spec 以返回动态的 Suggestion,但这个 runtime 是受限的,我们不能在里边动态的 require 任何系统或者第三方模块,所以只能通过 executeShellCommand
等接口执行特定的脚本以便从网络上获取数据。
所以我还需要实现 xs-task
,以便在被 Fig 调用时,动态的返回 Suggestion,这里只提供一个简单的 Demo:
console.log("[{\"name\":\"Task12345 xxx\",\"description\":\"测试任务:\",\"insertValue\":\"\\b\\b\\bTask12345\"}]")
其中 name
、description
、insertValue
都是标准字段,需要注意的是可以通过在 insertValue
里添加“\b”回退符,删除掉不需要字符输入,比如输入xxx
三个字符,对应 \\b\\b\\bTask12345
三个回退符就会把 xxx
三个字符删掉并插入 Task12345
。
当然,我们可以在 xs-task
里实现诸如对 feat|fix-TaskId
这样更复杂逻辑的 autocomplete 支持,这里不介绍了。
最后,我们可以通过以下命令把自定义 spec 注册(发布)到 Fig 上,这样就可以在非开发者模式下使用、并且在小于 4 人的团队之间进行分享了:
npm i -g @fig/publish-spec;
npx @fig/publish-spec --name xgit --spec-path 当前路径/xgit.js // --team 团队名
(嗨~之前只要把自定义 CLI 的 spec 文件拷贝到 ~/.fig/autocomplete/build/ 目录下就可以,但是官方后来修改为必须注册(发布)才可以使用,其实就是说“这个功能我要收钱了”,还挺贵的,一个人 5 刀每月、支持至多 15 个 devices。)
以上,就是我使用 Fig 强化命令行的经验,欢迎留言交流!