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

Why

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

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

What

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

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

How

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

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

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

    Warp 中執(zhí)行 ssh xxx 登錄服務(wù)器,實(shí)際是執(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)計(jì)之類砸王。
    • 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ù)中實(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 等選項(xiàng),若有則說明不是交互式的诵肛,直接返回 1( 非交互 )屹培。

    • 判斷 ssh 命令中是否帶有目標(biāo)機(jī)器 [[ ${#ARGS[@]} -ne 1 ]],若沒有目標(biāo)機(jī)器,也認(rèn)為不是交互式的褪秀,返回 1( 非交互 )蓄诽。

    • trzsz ssh ( tssh ) 支持不帶參數(shù)運(yùn)行,會列出所有服務(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)部邏輯,可以忽略沛豌,實(shí)測不輸出也不影響的趋箩,其定義如下:

    warp_send_json_message ()
    {
        encoded_message=$(warp_hex_encode_string "$1");
        printf $DCS_START$DCS_JSON_MARKER$encoded_message$DCS_END
    }
    
    • 其實(shí)就是先進(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ù)實(shí)現(xiàn) blocks featuretab 補(bǔ)全等功能,其定義如下:

    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_bash芍锦、init_shell_zshzsh_env_script 先忽略竹勉,不是本文重點(diǎn),重點(diǎn)是 command ssh ... 那行娄琉。
    • 通過 -o ControlMaster=yes 啟用了 ssh 多路復(fù)用次乓,Warp 就可以通過同一個(gè)連接,在服務(wù)器上執(zhí)行命令孽水,獲取當(dāng)前目錄下有哪些文件等票腰,tab 相關(guān)功能就是靠這實(shí)現(xiàn)的。
    • 通過 -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID 指定多路復(fù)用的 socket 路徑女气,是長 ~/.ssh/170252756912781 這樣子的杏慰。
    • 通過 -t 選項(xiàng)強(qiáng)制分配一個(gè)偽終端,因?yàn)楹竺嬷付说卿浐笠跏蓟瘓?zhí)行的腳本炼鞠,沒有 -t 選項(xiàng)就會默認(rèn)禁止分配偽終端缘滥,就影響用戶使用了。
    • 參數(shù) "${@:1}" 就是要登錄的目標(biāo)機(jī)器簇搅,從前面 ssh 命令行傳遞過來的完域。
    • 最后這一大段腳本,就是登錄后要初始化執(zhí)行的瘩将,下文再詳細(xì)介紹。這里要改成用 -o RemoteCommand 實(shí)現(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
    
    • 其實(shí)就是通過 shell 獲取一些信息异旧,然后通過 Device Control String 進(jìn)行輸出,用戶看不見提佣,但是 Warp 可以解釋并獲取到吮蛹。
    • Warp 獲取到這些信息之后,就會生成另一段腳本拌屏,(模擬用戶輸入)直接發(fā)送到服務(wù)器執(zhí)行潮针,修改一些 shell 的設(shè)置等,從而感知到每一個(gè)命令倚喂,實(shí)現(xiàn) blocks feature 等每篷。
    • 由于篇幅和時(shí)間關(guān)系,先介紹到這端圈。是不是很簡單焦读?你學(xué)會了嗎?歡迎留言評論舱权。

Btw

我給 Warp 提了個(gè) feature request https://github.com/warpdotdev/Warp/issues/3960矗晃,解決 tssh xxx 直接登錄可以支持 blocks feature, 而 tssh 搜索和選擇服務(wù)器登錄卻不支持 的問題宴倍。有需要的朋友去幫忙點(diǎn)個(gè)贊张症,提高下優(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)系作者
  • 序言:七十年代末啊楚,一起剝皮案震驚了整個(gè)濱河市吠冤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌恭理,老刑警劉巖拯辙,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異颜价,居然都是意外死亡涯保,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門周伦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夕春,“玉大人,你說我怎么就攤上這事专挪〖爸荆” “怎么了片排?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長速侈。 經(jīng)常有香客問我率寡,道長,這世上最難降的妖魔是什么倚搬? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任冶共,我火速辦了婚禮,結(jié)果婚禮上每界,老公的妹妹穿的比我還像新娘捅僵。我一直安慰自己,他們只是感情好眨层,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布庙楚。 她就那樣靜靜地躺著,像睡著了一般谐岁。 火紅的嫁衣襯著肌膚如雪醋奠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天伊佃,我揣著相機(jī)與錄音窜司,去河邊找鬼。 笑死航揉,一個(gè)胖子當(dāng)著我的面吹牛塞祈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帅涂,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼议薪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了媳友?” 一聲冷哼從身側(cè)響起斯议,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎醇锚,沒想到半個(gè)月后哼御,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焊唬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年恋昼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赶促。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡液肌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸥滨,到底是詐尸還是另有隱情嗦哆,我是刑警寧澤谤祖,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站吝秕,受9級特大地震影響泊脐,放射性物質(zhì)發(fā)生泄漏空幻。R本人自食惡果不足惜烁峭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秕铛。 院中可真熱鬧约郁,春花似錦、人聲如沸但两。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谨湘。三九已至绽快,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間紧阔,已是汗流浹背坊罢。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留擅耽,地道東北人活孩。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像乖仇,于是被迫代替她去往敵國和親憾儒。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容