简单分享一下 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_MODE
和WARP_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 feature
和tab
补全等功能,其定义如下: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_zsh
和zsh_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 )
的搜索模式。
- 前面
在服务器执行的初始化脚本
前面说到,在
Warp
中ssh
登录到服务器之后,会执行一大段脚本,以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
等。- 由于篇幅和时间关系,先介绍到这。是不是很简单?你学会了吗?欢迎留言评论。
- 其实就是通过 shell 获取一些信息,然后通过
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 协议》,转载必须注明作者和本文链接
博主的钻研精神值得学习,也有无私奉献的开源精神,赞一个。 推荐大家使用 trzsz-ssh,很好用。