簡單分享一下 Warp 終端 blocks feature(分塊)的實現(xiàn)原理

Why

背景:我開源了一個 ssh 客戶端,叫 trzsz-ssh ( tssh ),定制了一些網(wǎng)友需要的功能,解決了一些 ssh 相關(guān)的痛點,具體詳看開源地址:https://github.com/trzsz/trzsz-ssh

起因:在 Warp 終端中,為什么原生的 ssh 客戶端就可以支持 blocks feature,而我自己寫的 tssh 客戶端就不行呢?于是我一步步地深挖了其實現(xiàn)原理。

What

Warp 終端,當(dāng)你 ssh 登錄到服務(wù)器上,默認(rèn)情況下,你在服務(wù)器上執(zhí)行的每條命令以及其輸出就會被 Warp 分別定義成一個個 block 塊,你可以一塊塊地選中和移動,非常的酷。如果不支持,那整個 ssh 登錄后的所有命令及輸出就會被 Warp 定義成同一個 block 塊,選中和移動都是整個登錄后的所有命令及其輸出,那就沒那么酷了。

另外,當(dāng)你在服務(wù)器上輸入命令按 tab 鍵時,Warp 終端會彈出一個浮層顯示可選的目錄或文件,也很帥。如果不支持,那 tab 鍵也不能正常地進(jìn)行補全了,這對我來說簡直不能忍。

How

言歸正傳,Warp 終端是怎么實現(xiàn) blocks feature 和自定義 tab 行為等功能的呢?

Wrap 終端中,內(nèi)置了一些 shell 函數(shù),bash 可以通過 type 函數(shù)名 進(jìn)行查看函數(shù)定義,zsh 可以通過 which 函數(shù)名 進(jìn)行查看函數(shù)定義。

  • Warp 定義了個 ssh 函數(shù)

    Warp 中執(zhí)行 ssh xxx 登錄服務(wù)器,實際是執(zhí)行同名的 ssh 函數(shù),其定義如下:

    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 函數(shù)判斷是否為交互式的 ssh 登錄。
    • 若不是交互式的 ssh 登錄,則直接調(diào)用原生的 ssh 命令 command ssh "$@"。
    • 若是交互式的 ssh 登錄,則調(diào)用 warp_send_json_message 函數(shù),輸出一串用戶看不見的 json,Warp 可能會做一些統(tǒng)計之類。
    • WARP_USE_SSH_WRAPPER 環(huán)境變量不是 1,則直接調(diào)用原生的 ssh 命令 command ssh "$@"。默認(rèn)是 1 的。
    • 調(diào)試相關(guān)的 TRACE_FLAG_IF_WARP_DEBUG_MODEWARP_DEBUG_MODE 可以忽略,默認(rèn)是不調(diào)試的。
    • 核心邏輯在 warp_ssh_helper 函數(shù)中實現(xiàn) warp_ssh_helper "$@",下文再詳細(xì)介紹。
  • 判斷是否為交互式的 ssh 登錄

    Warp 中通過 is_interactive_ssh_session 函數(shù)判斷是否為交互式 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 命令中是否帶有目標(biāo)機器 [[ ${#ARGS[@]} -ne 1 ]],若沒有目標(biāo)機器,也認(rèn)為不是交互式的,返回 1( 非交互 )。

    • trzsz ssh ( tssh ) 支持不帶參數(shù)運行,會列出所有服務(wù)器的列表,支持搜索和選擇進(jìn)行登錄,這里需要調(diào)整才能支持 blocks feature

      # 注意里面的 `command` 關(guān)鍵字,若沒有它,就會循環(huán)調(diào)用 `ssh` 函數(shù),而不是執(zhí)行 `ssh` 命令了。不要問我怎么知道的。
      if [[ ${#ARGS[@]} -ne 1 ]] && [[ $(command ssh -V 2>&1) != "trzsz ssh"* ]]; then
          return 1;
      fi
      
  • 輸出一段用戶看不見的 json 內(nèi)容

    Warp 中通過 warp_send_json_message 輸出一段用戶看不見的 json 內(nèi)容,這是 Warp 的內(nèi)部邏輯,可以忽略,實測不輸出也不影響的,其定義如下:

    warp_send_json_message ()
    {
        encoded_message=$(warp_hex_encode_string "$1");
        printf $DCS_START$DCS_JSON_MARKER$encoded_message$DCS_END
    }
    
    • 其實就是先進(jìn)行 hex 編碼,然后加上 \x1bP$d 開頭,加上 \x9c 結(jié)尾,最終輸出的內(nèi)容如下:
    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 函數(shù)

    Warp 中通過 warp_ssh_helper 函數(shù)實現(xiàn) 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 多路復(fù)用,Warp 就可以通過同一個連接,在服務(wù)器上執(zhí)行命令,獲取當(dāng)前目錄下有哪些文件等,tab 相關(guān)功能就是靠這實現(xiàn)的。
    • 通過 -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID 指定多路復(fù)用的 socket 路徑,是長 ~/.ssh/170252756912781 這樣子的。
    • 通過 -t 選項強制分配一個偽終端,因為后面指定了登錄后要初始化執(zhí)行的腳本,沒有 -t 選項就會默認(rèn)禁止分配偽終端,就影響用戶使用了。
    • 參數(shù) "${@:1}" 就是要登錄的目標(biāo)機器,從前面 ssh 命令行傳遞過來的。
    • 最后這一大段腳本,就是登錄后要初始化執(zhí)行的,下文再詳細(xì)介紹。這里要改成用 -o RemoteCommand 實現(xiàn),才能兼容 trzsz ssh ( tssh ) 的搜索模式。
  • 在服務(wù)器執(zhí)行的初始化腳本

    前面說到,在 Warpssh 登錄到服務(wù)器之后,會執(zhí)行一大段腳本,以 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 進(jìn)行輸出,用戶看不見,但是 Warp 可以解釋并獲取到。
    • Warp 獲取到這些信息之后,就會生成另一段腳本,(模擬用戶輸入)直接發(fā)送到服務(wù)器執(zhí)行,修改一些 shell 的設(shè)置等,從而感知到每一個命令,實現(xiàn) blocks feature 等。
    • 由于篇幅和時間關(guān)系,先介紹到這。是不是很簡單?你學(xué)會了嗎?歡迎留言評論。

Btw

我給 Warp 提了個 feature request https://github.com/warpdotdev/Warp/issues/3960,解決 tssh xxx 直接登錄可以支持 blocks feature, 而 tssh 搜索和選擇服務(wù)器登錄卻不支持 的問題。有需要的朋友去幫忙點個贊,提高下優(yōu)先級。

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

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

# Usage
ssh xxx
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容