inshellisense使用说明

IDE style command line auto complete

以下inshellisense简称为is

安装

1
2
3
4
5
6
# 如果还未安装node,推荐nvm方式先安装node
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

nvm install 18
npm install -g @microsoft/inshellisense
npm install -g node-gyp

安装中出现的npm WARN EBADENGINE required: { node: '>=18' },不用担心,只是Fig规范推荐的18以上。is是支持16的,但推荐高版本

node<21

测试发现node21安装报错,原因是依赖的node-gyp不支持21。

使用

1
2
3
4
5
# 进入补全会话,进入后输入命令按空格可以唤起补全弹窗
is

# complete子命令获取补全JSON结果,rc-5还不支持
is complete "git l"
  1. 如果没有正常进入,看下是否有报错
  2. 正常进入后没有报错但不会唤起补全弹窗,一般都是因为提示词PS1检测问题,目前官方没有提供排查文档。
    • 个人遇到的场景是ZSH下的powerlevel10k/powerlevel9k会有问题,目前解决办法即更换主题

操作热键

目前inshellisense不支持自定义热键

Action Keybinding
Accept Current Suggestion tab
View Next Suggestion
View Previous Suggestion
Dismiss Suggestions esc

支持Shell

1
2
3
4
5
6
7
8
export enum Shell {
Bash = "bash",
Powershell = "powershell",
Pwsh = "pwsh",
Zsh = "zsh",
Fish = "fish",
Cmd = "cmd",
}

项目说明

https://github.com/microsoft/inshellisense

技术栈

1
2
3
"@withfig/autocomplete": "^2.633.0", # Fig补全规范
"node-pty": "^1.0.0", # Fork pseudoterminals in Node.JS
"xterm-headless": "^5.3.0" # xterm.js无头终端

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
├── shell
│   ├── bash-preexec.sh # for bash,拓展支持函数钩子
│   ├── shellIntegration-env.zsh # zsh
│   ├── shellIntegration-login.zsh
│   ├── shellIntegration-profile.zsh
│   ├── shellIntegration-rc.zsh
│   ├── shellIntegration.bash # bash
│   ├── shellIntegration.fish # fish
│   └── shellIntegration.ps1 # for windows Powershell/Pwsh
├── src
│   ├── commands # is等命令定义
│   ├── index.ts # 入口
│   ├── isterm # is下的终端模拟器
│   ├── runtime # 加载规范,根据输入获取suggestions
│   ├── tests # 测试
│   ├── ui # 终端绘制交互内容,比如补全/卸载提示等
│   └── utils

程序逻辑

process(<=>pty<=>xtermjs)

  1. 用户输入命令is,进入自动补全模式

  2. 加载is配置配置文件

  3. 确定Shell类型

    • is命令中用户可以指定Shell否则is中自动根据SHELL环境变量确定
  4. 初始化Shell配置

    • 如果是Zsh,补充以下文件到临时目录下
    1
    2
    3
    4
    - shellIntegration-env.zsh
    - shellIntegration-login.zsh
    - shellIntegration-profile.zsh
    - shellIntegration-rc.zsh
    • 如果是Bash,补充bash-preexec.sh到用户主目录
  5. is中加载全量Fig规范

  6. process.stdin.setRawMode设置为true,确保每个按键字符都触发data事件

  7. 执行清屏

  8. node-pty开启一个伪终端,加载对应Shell配置,同时创建xterm无头终端客户端。

  9. process.stdin.on(“data”)监听用户输入,之后不断写入pty

  10. pty监听输入,然后显示回显数据,同时写入xterm中

  11. xterm监听写入数据

    • 根据数据中的OSC,计算提示词起止位置。命令管理器同步终端情况

    • 补全管理器计算补全,确定是不是需要显示补全

    • 补全命令模块做命令的词法分析,确定命令情况,之后生成补全

    • 生成补全的依赖3点input,cwd,shell

      1
      2
      // input为当前的命令,this.#term.getCommandState().commandText,process.cwd()为当前路径
      getSuggestions(input, process.cwd())
    • 字符监听时针对热键也会做对应的处理,比如tab则写入补全的命令:

  12. process.stdout写入终端

补全计算逻辑

  1. 命令文本进行分析,确定token数组

    1
    2
    3
    4
    const lex = (command: string): CommandToken[] => {
    ...
    return tokens;
    };
  2. token数组第一个为rootToken,根据rootToken来获取补全文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const loadSpec = async (cmd: CommandToken[]): Promise<Fig.Spec | undefined> => {
    const rootToken = cmd.at(0);
    if (!rootToken?.complete) {
    return;
    }

    if (loadedSpecs[rootToken.token]) {
    return loadedSpecs[rootToken.token];
    }
    if (specSet[rootToken.token]) {
    const spec = (await import(specSet[rootToken.token])).default;
    loadedSpecs[rootToken.token] = spec;
    return spec;
    }
    };
  3. token数组的最后一个Token根据是否包含/来确定是不是路径

  4. token数组从第二个开始计算补全

  5. 补全筛选

Suggestion数据定义

1
2
3
4
5
6
7
8
export type Suggestion = {
name: string;
allNames: string[]; // fig下命令参数可以有多个name,比如git下的["-p", "--paginate"]
description?: string;
icon: string; // emoji font
priority: number; // 优先级,is中为降序
insertValue?: string; // 实际插入终端值
};

调试

  1. 直接执行npm ru debug,VSC下开启Attach to Node Process ⌘

  2. ~/.inshellisense/inshellisense.log可以查看日志

写在最后

  1. 个人觉得is这种设计方案下速度还行,但直接在终端中绘制补全也就带来问题即:位置计算错误的话,光标很容易位置错
  2. 期待is早日发正式