简单分享一下 Warp 终端 blocks feature(分块)的实现原理

Why

背景:我开源了一个 ssh 客户端,叫 trzsz-ssh ( tssh ),定制了一些网友需要的功能,解决了一些 ssh 相关的痛点,具体详看开源地址:github.com/trzsz/trzsz-ssh

起因:在 Warp 终端中,为什么原生的 ssh 客户端就可以支持 blocks feature,而我自己写的 tssh 客户端就不行呢?于是我一步步地深挖了其实现原理。

What

Warp 终端,当你 ssh 登录到服务器上,默认情况下,你在服务器上执行的每条命令以及其输出就会被 Warp 分别定义成一个个 block 块,你可以一块块地选中和移动,非常的酷。如果不支持,那整个 ssh 登录后的所有命令及输出就会被 Warp 定义成同一个 block 块,选中和移动都是整个登录后的所有命令及其输出,那就没那么酷了。

另外,当你在服务器上输入命令按 tab 键时,Warp 终端会弹出一个浮层显示可选的目录或文件,也很帅。如果不支持,那 tab 键也不能正常地进行补全了,这对我来说简直不能忍。

How

言归正传,Warp 终端是怎么实现 blocks feature 和自定义 tab 行为等功能的呢?

Wrap 终端中,内置了一些 shell 函数,bash 可以通过 type 函数名 进行查看函数定义,zsh 可以通过 which 函数名 进行查看函数定义。

  • Warp 定义了个 ssh 函数

    Warp 中执行 ssh xxx 登录服务器,实际是执行同名的 ssh 函数,其定义如下:

    ssh ()
    {
        if is_interactive_ssh_session "$@"; then
            warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {}}";
            if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then
                local TRACE_FLAG_IF_WARP_DEBUG_MODE="";
                if [[ "$WARP_DEBUG_MODE" == "1" ]]; then
                    TRACE_FLAG_IF_WARP_DEBUG_MODE="-x";
                fi;
                warp_ssh_helper "$@";
            else
                command ssh "$@";
            fi;
        else
            command ssh "$@";
        fi
    }
    • 通过 is_interactive_ssh_session 函数判断是否为交互式的 ssh 登录。
    • 若不是交互式的 ssh 登录,则直接调用原生的 ssh 命令 command ssh "$@"
    • 若是交互式的 ssh 登录,则调用 warp_send_json_message 函数,输出一串用户看不见的 json,Warp 可能会做一些统计之类。
    • WARP_USE_SSH_WRAPPER 环境变量不是 1,则直接调用原生的 ssh 命令 command ssh "$@"。默认是 1 的。
    • 调试相关的 TRACE_FLAG_IF_WARP_DEBUG_MODEWARP_DEBUG_MODE 可以忽略,默认是不调试的。
    • 核心逻辑在 warp_ssh_helper 函数中实现 warp_ssh_helper "$@",下文再详细介绍。
  • 判断是否为交互式的 ssh 登录

    Warp 中通过 is_interactive_ssh_session 函数判断是否为交互式 ssh 登录,其定义如下:

    is_interactive_ssh_session ()
    {
        ARGS=();
        while [ $# -gt 0 ]; do
            OPTIND=1;
            while getopts :1246AaCfgKkMNnqsTtVvXxYyb:c:D:e:F:i:L:l:m:O:o:p:R:S:W:w: OPTION; do
                case $OPTION in
                    T)
                        return 1
                    ;;
                    W)
                        return 1
                    ;;
                    \?)
                        return 1
                    ;;
                    :)
                        return 1
                    ;;
                esac;
            done;
            [ $? -eq 0 ] || return 2;
            [ $OPTIND -gt $# ] && break;
            shift "$((OPTIND - 1))";
            ARGS[${#ARGS[@]}]=$1;
            shift;
        done;
        if [[ ${#ARGS[@]} -ne 1 ]]; then
            return 1;
        fi
    }
    • 判断 ssh 命令中是否含有 -T-W 等选项,若有则说明不是交互式的,直接返回 1( 非交互 )。

    • 判断 ssh 命令中是否带有目标机器 [[ ${#ARGS[@]} -ne 1 ]],若没有目标机器,也认为不是交互式的,返回 1( 非交互 )。

    • trzsz ssh ( tssh ) 支持不带参数运行,会列出所有服务器的列表,支持搜索和选择进行登录,这里需要调整才能支持 blocks feature

      # 注意里面的 `command` 关键字,若没有它,就会循环调用 `ssh` 函数,而不是执行 `ssh` 命令了。不要问我怎么知道的。
      if [[ ${#ARGS[@]} -ne 1 ]] && [[ $(command ssh -V 2>&1) != "trzsz ssh"* ]]; then
          return 1;
      fi
  • 输出一段用户看不见的 json 内容

    Warp 中通过 warp_send_json_message 输出一段用户看不见的 json 内容,这是 Warp 的内部逻辑,可以忽略,实测不输出也不影响的,其定义如下:

    warp_send_json_message ()
    {
        encoded_message=$(warp_hex_encode_string "$1");
        printf $DCS_START$DCS_JSON_MARKER$encoded_message$DCS_END
    }
    • 其实就是先进行 hex 编码,然后加上 \x1bP$d 开头,加上 \x9c 结尾,最终输出的内容如下:
    00000000: 1b50 2464 3762 3232 3638 3666 3666 3662  .P$d7b22686f6f6b
    00000010: 3232 3361 3230 3232 3530 3732 3635 3439  223a202250726549
    00000020: 3665 3734 3635 3732 3631 3633 3734 3639  6e74657261637469
    00000030: 3736 3635 3533 3533 3438 3533 3635 3733  7665535348536573
    00000040: 3733 3639 3666 3665 3232 3263 3230 3232  73696f6e222c2022
    00000050: 3736 3631 3663 3735 3635 3232 3361 3230  76616c7565223a20
    00000060: 3762 3764 3764 3061 9c                   7b7d7d0a.
  • 核心逻辑 warp_ssh_helper 函数

    Warp 中通过 warp_ssh_helper 函数实现 blocks featuretab 补全等功能,其定义如下:

    warp_ssh_helper ()
    {
        init_shell_bash=$(init_shell_hook "bash");
        init_shell_zsh=$(init_shell_hook "zsh");
        local zsh_env_script=$(printf '%s' '...太长省略系列...');
        command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID -t "${@:1}" "
            # ...太长省略系列...
        "
    }
    • 前面 init_shell_bashinit_shell_zshzsh_env_script 先忽略,不是本文重点,重点是 command ssh ... 那行。
    • 通过 -o ControlMaster=yes 启用了 ssh 多路复用,Warp 就可以通过同一个连接,在服务器上执行命令,获取当前目录下有哪些文件等,tab 相关功能就是靠这实现的。
    • 通过 -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID 指定多路复用的 socket 路径,是长 ~/.ssh/170252756912781 这样子的。
    • 通过 -t 选项强制分配一个伪终端,因为后面指定了登录后要初始化执行的脚本,没有 -t 选项就会默认禁止分配伪终端,就影响用户使用了。
    • 参数 "${@:1}" 就是要登录的目标机器,从前面 ssh 命令行传递过来的。
    • 最后这一大段脚本,就是登录后要初始化执行的,下文再详细介绍。这里要改成用 -o RemoteCommand 实现,才能兼容 trzsz ssh ( tssh ) 的搜索模式。
  • 在服务器执行的初始化脚本

    前面说到,在 Warpssh 登录到服务器之后,会执行一大段脚本,以 bash 为例:

    export TERM_PROGRAM='WarpTerminal'
    hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
    printf '$DCS_START$DCS_JSON_MARKER%s$DCS_END' "'$hook'"
    # ...此处省略对 shell 类型的判断...
    exec -a bash bash --rcfile <(echo '"'
        command -p stty raw
        HISTCONTROL=ignorespace
        HISTIGNORE=" *"
        WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
        _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
        _user=$(command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER)
        _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"$_user\", \"hostname\": \"$_hostname\"}}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
        printf '\''"'\eP$d%s\x9c'"'\'' \""'$_msg'"\"')
    unset _hostname _user _msg
    • 其实就是通过 shell 获取一些信息,然后通过 Device Control String 进行输出,用户看不见,但是 Warp 可以解释并获取到。
    • Warp 获取到这些信息之后,就会生成另一段脚本,(模拟用户输入)直接发送到服务器执行,修改一些 shell 的设置等,从而感知到每一个命令,实现 blocks feature 等。
    • 由于篇幅和时间关系,先介绍到这。是不是很简单?你学会了吗?欢迎留言评论。

Btw

我给 Warp 提了个 feature request github.com/warpdotdev/Warp/issues/...,解决 tssh xxx 直接登录可以支持 blocks feature, 而 tssh 搜索和选择服务器登录却不支持 的问题。有需要的朋友去帮忙点个赞,提高下优先级。

附在 Warp 中正确安装和使用 trzsz ssh ( tssh ) github.com/trzsz/trzsz-ssh 的方法:

# Install
brew install trzsz-ssh
sudo ln -sv $(which tssh) /usr/local/bin/ssh

# Usage
ssh xxx
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 1

博主的钻研精神值得学习,也有无私奉献的开源精神,赞一个。 推荐大家使用 trzsz-ssh,很好用。

2个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!