该演示需要 Python 支持。演示程序位于 $VIMRUNTIME/tools/demoserver.py。
先在一个终端 (记为 T1) 运行该程序。
在另一个终端中运行 Vim。使用以下命令连接到上述的演示服务器:
let channel = ch_open('localhost:8765')
在 T1 中,应该可见:
=== socket opened ===
现在可以给该服务器发送消息:
echo ch_evalexpr(channel, 'hello!')
消息会在 T1 中被接收,并返回响应给 Vim。
T1 中可见 Vim 实际发送的原始消息:
[1,"hello!"]
而服务器发回的响应会是:
[1,"got it"]
每次发送一条新消息时,该序号都会递增。
服务器也可向 Vim 发送命令。在 T1 上直接输入 (按文本,包括引号):
["ex","echo 'hi there'"]
此时 Vim 中可以看到该消息。要正向移动光标一个单词:
["normal","w"]
要处理异步通信,需要提供回调函数:
func MyHandler(channel, msg)
echo "来自回调函数: " .. a:msg
endfunc
call ch_sendexpr(channel, 'hello!', {'callback': "MyHandler"})
Vim 不会等待响应。服务器稍后发送响应时,MyHandler 会自动被调用。
也可在打开通道时就指定回调,而不是每次发送都指定:
call ch_close(channel)
let channel = ch_open('localhost:8765', {'callback': "MyHandler"})
call ch_sendexpr(channel, 'hello!')
调试通道时,记录日志很有用:
call ch_logfile('channellog', 'w')
见 ch_logfile() 。
也可以通过 ch_listen() 使 Vim 自己充当服务器。无需依赖外部程序。
channel-listen-demo
启动 Vim 并创建监听通道:
func OnAccept(channel, clientaddr)
" 记录连接信息
echomsg "已接受来自" .. a:clientaddr .. "的连接"
" 获取当前时间并发送给客户端
let current_time = strftime("%Y-%m-%d %H:%M:%S")
call ch_sendraw(a:channel, "Vim 服务器时间: " .. current_time .. "\n")
" 可选: 仅发送一次时间后立即关闭连接
call ch_close(a:channel)
endfunc
" 在 8765 端口启动监听
let server = ch_listen('8765', {"callback": "OnAccept"})
从另一个 Vim 实例 (或其他程序) 连接该服务器:
let channel = ch_open('localhost:8765')
使用完毕后,关闭服务器通道:
call ch_close(server)
要打开通道:
let channel = ch_open({address} [, {options}])
if ch_status(channel) == "open"
" 通道已打开,可使用该通道
要检查通道是否已成功打开,可用 ch_status() 。
channel-address
{address} 可以是域名或 IP 地址后跟端口号,也可以是带 "unix:" 前缀的 Unix-域套
接字路径。例如
www.example.com:80 " 域名 + 端口
127.0.0.1:1234 " IPv4 + 端口
[2001:db8::1]:8765 " IPv6 + 端口
unix:/tmp/my-socket " Unix-域套接字路径
当域名能解析到多个地址时 (如同时包含 IPv6 和 IPv4),Vim 会按顺序依次尝试每个地
址。如果某个连接缓慢或不可用,会迅速切换到下一个地址。这在网络中某种 IP 协议
(IPv6 或 IPv4) 不能正常工作时尤为有用。
{options} 是包含以下可选项的字典: channel-open-options
"mode" 可以是: channel-mode
"json" - JSON,见下;这是最便捷的方式。缺省。
"js" - JS (JavaScript) 编码,比 JSON 经济。
"nl" - 以 Nl 字符结尾的消息
"raw" - 原始消息
"blob" - 原始消息,并以 Blob 形式传递回调数据
"lsp" - 使用语言服务器协议编码
"dap" - 使用调用适配器协议编码
channel-callback E921
"callback" 收到尚未处理的消息时 (如 ID 为零的 JSON 消息),本回调函数会被
调用。它接受两个参数: 通道对象和收到的消息。例如:
func Handle(channel, msg)
echo '收到: ' .. a:msg
endfunc
let channel = ch_open("localhost:8765", {"callback": "Handle"})
"mode" 为 "json"、"js"、"lsp" 或 "dap" 时,"msg" 参数包含已转
换为 Vim 类型的接收消息正文。
"mode" 为 "nl" 时,"msg" 参数为单条消息,不包含换行符 NL。
"mode" 为 "raw" 时,"msg" 参数为字符串类型的完整消息内容。
对所有回调: 可用 function() 将回调绑定到参数和/或字典。也可
用 "dict.function" 形式绑定到字典。
仅在 "安全" 状态时才会触发回调函数,这通常是指 Vim 等待用户输
入字符时。Vim 不支持多线程。
close_cb
"close_cb" 通道关闭时 (除非是通过 ch_close() 手动关闭通道),会自动调用
本回调函数。函数定义如下:
func MyCloseHandler(channel)
Vim 会先触发消息回调处理所有数据,再触发关闭回调 close_cb。本
回调被触发时,不会再有新数据发送到消息回调。不过,如果消息回调
仍在处理消息中,关闭回调可能已被调用。插件必须妥善处理这种情
况,不过明确知道不会再有新数据到达,仍然很有帮助。如果不确定有
无消息需要处理,可用 try/catch 块:
try
let msg = ch_readraw(a:channel)
catch
let msg = 'no message'
endtry
try
let err = ch_readraw(a:channel, #{part: 'err'})
catch
let err = 'no error'
endtry
channel-drop
"drop" 指定丢弃消息的时机:
"auto" 未给出任何回调时。这包括消息回调和关闭回调。
"never" 保留所有消息,永不丢弃。
channel-noblock
"noblock" 和 job-noblock 等效。只影响写操作。
waittime
"waittime" 等待连接完成的超时时间,以毫秒为单位。负数代表无限等待。
缺省为零,即不等待。适用于已在运行的本地服务器。在 Unix 上,
Vim 实际使用 1 毫秒等待时间,这在多数系统上是必需的。连接到远
程服务服务器时,建议设置更大的值,例如至少 10 毫秒。
channel-timeout
"timeout" 请求阻塞时 (如 ch_evalexpr() ),等待响应的超时时间,以毫秒为
单位。缺省值为 2000 (相当于 2 秒)。
"mode" 为 "json" 或 "js" 时,"callback" 可选。省略时,默认仅在主动发送一条消息
后,才会接收到对方的消息。
要在通道打开后修改通道选项,可用 ch_setoptions() 。接受的参数和 ch_open()
基本相同,但不能指定 "waittime",因为该参数仅在通道建立时生效。
例如,可以新增或更改回调:
call ch_setoptions(channel, {'callback': callback})
"callback" 为空 (零或空串) 时,则删除原有回调。
回调执行后,Vim 会自动刷新屏幕,并复原光标原有位置。因此回调本身无须执行
:redraw 。
也可更改 "timeout":
call ch_setoptions(channel, {'timeout': msec})
channel-close E906
通道使用完毕后,可通过以下方式关闭:
call ch_close(channel)
使用套接字时,这会关闭双向连接。使用管道 (stdin/stdout/stderr) 时,会关闭所有
管道。这可能不符合用户预期!用 job_stop() 停止作业可能更合适。
关闭通道后,放弃所有预读取数据,不再触发任何回调。
注意 通道关闭分三个阶段:
- I/O 终止,日志记录: "Closing channel"。队列中可能仍有待处理消息或回调。
- 清理预读取数据,日志记录: "Clearing channel"。部分变量可能仍在引用该通道。
- 释放通道资源,日志记录: "Freeing channel"。
如果通道打开失败,会报错。MS-Windows 和 Unix 略有差异。Unix 上如果打不开端口,
ch_open() 会快速失败。MS-Windows 上则会等待 "waittime" 时长再报错。
E898 E901 E902
通道读写时,如果发生错误,会自动关闭通道。
E630 E631
JSON 协议可同步发送消息:
let response = ch_evalexpr(channel, {expr})
此时,会等待对方回复响应。
JS 模式也是如此,区别在于用 JavasScript 编码消息。两者区别可见 js_encode() 。
如果只需发送消息,不立即处理响应、或交由通道回调处理:
call ch_sendexpr(channel, {expr})
如果要发送消息并通过指定函数异步处理响应:
call ch_sendexpr(channel, {expr}, {'callback': Handler})
Vim 会通过消息 ID 匹配请求和响应。收到响应后立即执行回调。相同 ID 的后续响应会
被忽略。如果服务器需要返回多个响应,必须使用零 ID 值发送,此时,所有消息都会交
由通道回调处理。
{expr} 会被转换为 JSON 格式,并使用数组形式包装。例如 {expr} 为字符串
"hello",接收方收到的消息形如:
[12,"hello"]
一般地,JSON 发送格式为:
[{number},{expr}]
{number} 每次都自动生成新值。返回的响应 (如有) 必须使用相同数值:
[{number},{response}]
这样即使消息到达顺序打乱,Vim 也能正确匹配,并触发回调。
JSON 文本以换行符结尾。用于分隔消息。例如在 Python 中:
splitidx = read_text.find('\n')
message = read_text[:splitidx]
rest = read_text[splitidx + 1:]
发送者必须确保给 Vim 发送合法 JSON。Vim 会通过解析 JSON 来确认消息完整后才接
收。结尾换行符可选。
进程如果没有先收到消息,而要主动向 Vim 发送消息,应使用零值 ID:
[0,{response}]
通道回调此时会把 {response} 转为 Vim 类型。如果通道没有设置回调,该消息会被直
接丢弃。
JSON 或 JS 通道也可以用 ch_sendraw() 和 ch_evalraw() 。但调用者必须自行负责
编码和解码。
进程可通过 JSON 通道向 Vim 发送 Vim 内置命令,无需为此提供回调函数。
支持以下命令: E903 E904 E905
["redraw", {forced}]
["ex", {Ex 命令}]
["normal", {普通模式命令}]
["expr", {expression}, {number}]
["expr", {expression}]
["call", {函数名}, {参数列表}, {number}]
["call", {函数名}, {参数列表}]
使用这些命令时,务必谨慎!它们很容易干扰用户正在进行的操作。为避免问题,执行命
令前,应使用 mode() 检查编辑器是否处于命令期待的状态。例如,要发送作为文本插
入,而非作为命令执行的键序列:
["ex","if mode() == 'i' | call feedkeys('ClassName') | endif"]
这些命令产生的错误通常不向用户报告,以免扰乱界面显示。如果确实需要查看错误信
息,可将 'verbose' 选项设为 3 或更高。
"redraw" 命令
其它命令特意不刷新屏幕,以便用户发送一连串命令,而不会看到光标来回移动。不过,
有些命令可能仍会附带屏幕重绘的副作用。除此以外,要显示所有修改后的文本,并将光
标移动到正确位置,必须在最后用 'redraw" 命令来强制重绘。
参数通常是空串:
["redraw", ""]
如果需要先清屏,传入 "force":
["redraw", "force"]
"ex" 命令
"ex" 命令的参数会像普通 Ex 命令一样执行。执行完成或报错时不会返回响应。可以调
用 autoload 脚本内的函数:
["ex","call myscript#MyFunc(arg)"]
也可用 "call feedkeys() " 插入任意键序列。
有错时,错误信息会写入通道日志 (如有设置)。同时 v:errmsg 会被设为错误信息。
"normal" 命令
"normal" 命令的执行方式与 :normal! 相同,即按键不会被映射。例如,要打开光标
下的折叠:
["normal" "zO"]
带响应的 "expr" 命令
"expr" 命令可用于计算表达式求值。例如,要获取当前缓冲区的总行数:
["expr","line('$')", -2]
它会返回表达式计算结果:
[-2, "last line"]
格式是:
[{number}, {result}]
{number} 会和请求中的 {number} 值 (第三个参数) 一致。建议使用负数,避免和 Vim
主动发送的消息有冲突。每个请求应使用不同编号,以便正确匹配请求与响应。
{result} 是表达式计算的结果,使用 JSON 编码。如果计算失败,或者结果无法以 JSON
编码,则返回字符串 "ERROR"。
无需响应的 "expr" 命令
该命令和上面的 "expr" 命令类似,但无需返回任何响应。例如:
["expr","setline('$', ['one', 'two', 'three'])"]
注意请求中没有第三个参数。
"call" 命令
和 "expr" 类似,但不是把整个表达式作为一个字符串传递,而是分别传递函数名和一个
参数列表。这样有助于避免把参数转字符串、转义与合并等一系列步骤的麻烦。例如:
["call", "line", ["$"], -2]
如果无需响应,省略第四个参数:
["call", "setline", ["$", ["one", "two", "three"]]]
使用 RAW 或 NL 协议时,可以这样发送消息并等待响应:
let response = ch_evalraw(channel, {string})
{string} 会按原样传送。响应也是从通道立即读取的内容。由于 Vim 无法识别消息边
界,调用者需要自行判断消息何时结束。超时仅作用于首个字节的读取,之后不会再等待
任何数据。
使用 "nl" 协议时,可以用类似方式发送消息。发送时每个消息末尾应手动添加 NL。这
样可以一次发送多条以 NL 结尾的消息。响应会返回直到第一个 NL (包含 NL) 为止的文
本。空响应可以只包括一个 NL。如果通道超时前未读到 NL,将返回空串。
要发送消息并不等待响应:
call ch_sendraw(channel, {string})
进程可以选择发回响应,会触发通道回调进行处理。
channel-onetime-callback
要发送消息并异步用指定函数处理响应:
call ch_sendraw(channel, {string}, {'callback': 'MyHandler'})
{string} 也可以是用 json_encode() 创建的 JSON 格式,在回调中可用
json_decode() 解析接收的 JSON 消息。
在 RAW 通道上,不能使用 ch_evalexpr() 或 ch_sendexpr() 。
Vim 中的字符串不能包含 NUL 字符。要收发 NUL 字符,请见 in_io-buffer 和
out_io-buffer 。
获取通道状态: ch_status(channel) 。可能值包括:
"fail" 通道打开失败。
"open" 通道可用。
"buffered" 通道已关闭,但仍有数据待读取。
"closed" 通道已关闭。
获取与通道相关联的作业: ch_getjob(channel)
从通道读取一条消息:
let output = ch_read(channel)
这里使用通道的默认 timeout。要仅读取当前已有的消息而完全不等待:
let output = ch_read(channel, {'timeout': 0})
无消息可读时,在 JSON 或 JS 模式下返回 v:none ,在 RAW 或 NL 模式下返回空串。
可用 ch_canread() 检查是否还有消息可读。
注意 未设置回调时,消息会被丢弃。可通过添加关闭回调避免此问题。
读取 RAW 通道中所有可用的标准输出:
let output = ch_readraw(channel)
读取 RAW 通道中所有可用的错误输出:
let output = ch_readraw(channel, {"part": "err"})
注意 使用 NL 模式时, ch_readraw() 每次调用仅返回一行。
ch_read() 和 ch_readraw() 默认使用通道 timeout。超时无数据可读则返回空串。
要为该调用自定义超时,可指定 "timeout" 选项 (以毫秒为单位):
{"timeout": 123}
要读取错误输出,可使用 "part" 选项:
{"part": "err"}
要在 JS 或 JSON 模式下读取指定 ID 的消息,可用 "id" 选项:
{"id": 99}
ID 省略或为 -1 时,默认返回首条消息。这些函数优先级高于等待该消息的回调。
对于 RAW 通道,返回所有可用数据,因为 Vim 无法识别消息边界。
对于 NL 通道,返回一条完整消息。
对于 JS 或 JSON 通道,返回一条已解码的消息,包含序号。
ch_canread({handle}) ch_canread()
{handle} 有数据可读则返回非零。
{handle} 可以是通道,或关联了通道的作业。
用于在合适的时机 (如定时器中) 才从通道读取数据的场合。
注意 通道未设置回调时,消息会被丢弃。可通过添加关闭回调避免此
问题。
也可用作 method :
GetChannel()->ch_canread()
返回类型: Number
ch_close({handle}) ch_close()
关闭 {handle}。见 channel-close 。
{handle} 可以是通道,或关联了通道的作业。
不会触发关闭回调函数。
也可用作 method :
GetChannel()->ch_close()
返回类型: 无
ch_close_in({handle}) ch_close_in()
只关闭 {handle} 的 "in" (输入) 部分。见 channel-close-in 。
{handle} 可以是通道,或关联了通道的作业。
不会触发关闭回调函数。
也可用作 method :
GetChannel()->ch_close_in()
返回类型: 无
ch_evalexpr({handle}, {expr} [, {options}]) ch_evalexpr()
发送表达式并同步等待响应。
发送 {expr} 到 {handle}。{expr} 会根据通道类型自动编码。但不可
用于 RAW 模式。见 channel-use 。
{handle} 可以是通道,或关联了通道的作业。
使用 "lsp" 或 "dap" 模式时,{expr} 必须为 Dict 。
E917
{options} 必须是字典。不支持 "callback" 项目。支持 "timeout"
项目,指明此特定请求的超时。
本函数等待响应,并返回经解码的表达式。如果有错或超时,返回空
String ,除非使用 "lsp" 或 "dap" 模式,此时会返回空 Dict 。
注意 等待响应期间,Vim 仍会处理其他信息。需要确保这不会引发问
题。
也可用作 method :
GetChannel()->ch_evalexpr(expr)
返回类型: dict<any> 或 String
ch_evalraw({handle}, {string} [, {options}]) ch_evalraw()
发送原始字符串并同步等待响应。
发送 {expr} 到 {handle}。
{handle} 可以是通道,或关联了通道的作业。
和 ch_evalexpr() 类似,但不做任何请求编码/响应解码处理。调用
者须自行确保数据正确。NL 模式下也不会自动附加换行符,同样需调
用者手动处理。不过,会自动清除响应中的 NL。
注意 Vim 无法识别 RAW 数据的消息边界。本函数只会返回第一部分,
需要用 ch_readraw() 读取其余部分。
见 channel-use 。
也可用作 method :
GetChannel()->ch_evalraw(rawstring)
返回类型: dict<any> 或 String
ch_getbufnr({handle}, {what}) ch_getbufnr()
获取 {handle} 指定输出 {what} 使用的缓冲区号。
{handle} 可以是通道,或关联了通道的作业。
{what} 可为代表标准错误的 "err"、代表标准输出的 "out"、或代表
套接字输出的空串。
如果没有相应缓冲区,返回 -1。
也可用作 method :
GetChannel()->ch_getbufnr(what)
返回类型: Number
ch_getjob({channel}) ch_getjob()
获取与 {channel} 关联的作业。
如果没有关联作业,在返回的 Job 对象上调用 job_status() 会报
告 "fail"。
也可用作 method :
GetChannel()->ch_getjob()
返回类型: job 或 String
ch_info({handle}) ch_info()
返回包含 {handle} 详细信息的字典。通用项目:
"id" 通道号
"status" "open"、"buffered" 或 "closed",见
ch_status()
对 ch_open() 创建的通道,有以下额外项目:
"port" 环回端口号
"path" Unix-域套接字路径
"sock_status" 套接字状态: "open" 或 "closed"
"sock_mode" 套接字模式: "NL"、"RAW"、"JSON" 或 "JS"
"sock_io" "socket"
"sock_timeout" 以毫秒为单位的超时
注意 "path" 项仅在使用 Unix-域套接字时出现,对于常规地址,则只
会包含 "hostname" 和 "port" 项。
对于 job_start() 创建的通道,有以下额外项目:
"out_status" 输出状态: "open"、"buffered" 或 "closed"
"out_mode" 输出模式: "NL"、"RAW"、"JSON" 或 "JS"
"out_io" 输出连接: "null"、"pipe"、"file" 或 "buffer"
"out_timeout" 输出以毫秒为单位的超时
"err_status" 错误状态: "open"、"buffered" 或 "closed"
"err_mode" 错误模式: "NL"、"RAW"、"JSON" 或 "JS"
"err_io" 错误连接: "out"、"null"、"pipe"、"file" 或
"buffer"
"err_timeout" 错误以毫秒为单位的超时
"in_status" 输入状态: "open" 或 "closed"
"in_mode" 输入模式: "NL"、"RAW"、"JSON"、"JS"、"LSP"
或 "DAP"
"in_io" 输入连接: "null"、"pipe"、"file" 或 "buffer"
"in_timeout" 输入以毫秒为单位的超时
也可用作 method :
GetChannel()->ch_info()
返回类型: dict<any>
ch_listen({address} [, {options}]) E1573 E1574 ch_listen()
在 {address} 指定的回环端口或 UNIX 域套接字上监听通道连接。与
用于连接到现有服务器的 ch_open() 不同,本函数创建新服务器端
通道。
返回新建通道。可使用 ch_status() 检查通道创建是否失败。
{address} 为字符串,支持的格式见 channel-address 。不过,对于
TCP 套接字,出于安全考虑,仅允许指定端口,并自动绑定到回环地
址。
注意: 目前不支持 IPv6。
{options} 可选,给出时类型必须为 Dictionary 。
可用选项见 channel-open-options 。
新连接被接受后,会触发 {options} 中的 "callback" 回调。它接收
两个参数: 新建立的通道对象、以及字符串形式的客户端地址
(如 "127.0.0.1:12345")。
要连接到已有服务器,请用 ch_open() 。
示例见 channel-listen-demo 。
也可用作 method :
GetAddress()->ch_listen()
{仅当编译时带有 +channel 特性时才有通道功能}
返回类型: channel
ch_log({msg} [, {handle}]) ch_log()
用 ch_logfile() 打开日志后,将自定义消息字符串 {msg} 写入通
道日志文件。
在消息前会自动附加前缀文本 "ch_log():",以明确消息是来自于本此
函数调用,方便在日志文件中查找。
传入 {handle} 时,会在消息中记录该通道号。
{handle} 可以是通道,或关联了通道的作业。通道必须已经打开,才
会记录指定的通道号。
也可用作 method :
'did something'->ch_log()
返回类型: 无
ch_logfile({fname} [, {mode}]) ch_logfile()
启动通道活动记录,指定 {fname} 为日志文件。
{fname} 为空串时,停止日志。
{mode} 省略或包含 "a" 或 "o" 时,追加到已有文件内容。
{mode} 包含 "w" 而不包含 "a" 时,清空原有内容。
{mode} 包含 "o" 时,记录所有终端输出。否则,只记录部分有意义的
终端输出。
要写入日志信息,可用 ch_log() 。记录完每条消息后都会刷新文
件,Unix 上可用 "tail -f" 实时监控。
要尽早打开日志,以便在启动时能察看终端上的接收内容,可用
--log (此时会使用 "ao" 模式) 参数:
vim --log logfile
此函数在 sandbox 中不可用。
注意: 日志文件中会保存通道通信内容,请注意其中可能包含机密和敏
感信息,例如在终端窗口中输入的密码等。
也可用作 method :
'logfile'->ch_logfile('w')
返回类型: 无
ch_open({address} [, {options}]) ch_open()
打开通道到指定地址 {address} 的服务器。见 channel 。
返回通道对象。可用 ch_status() 检查是否有错误。
{address} 为字符串,支持的格式见 channel-address 。
使用 IPv6 地址时,需用方括号包围。如 "[2001:db8::1]:8765"。
{options} 可选,给出时类型必须为 Dictionary 。
可用选项见 channel-open-options 。
要监听传入的连接,可用 ch_listen() 而非本函数。
也可用作 method :
GetAddress()->ch_open()
返回类型: channel
ch_read({handle} [, {options}]) ch_read()
从 {handle} 读取并返回一条完整的消息。
{handle} 可以是通道,或关联了通道的作业。
使用 NL 模式时,会等待换行符,除非已无内容可读 (通道被关闭)。
更多说明及 {options} 可用选项见 channel-more 。
也可用作 method :
GetChannel()->ch_read()
返回类型: String
ch_readblob({handle} [, {options}]) ch_readblob()
和 ch_read() 类似,但读取二进制数据并返回 Blob 。
更多说明及 {options} 可用选项见 channel-more 。
也可用作 method :
GetChannel()->ch_readblob()
返回类型: Blob
ch_readraw({handle} [, {options}]) ch_readraw()
和 ch_read() 类似,但使用 JS 和 JSON 模式时,不对消息解码。
使用 NL 模式时,不阻塞等待换行符。
更多说明及 {options} 可用选项见 channel-more 。
也可用作 method :
GetChannel()->ch_readraw()
返回类型: String
ch_sendexpr({handle}, {expr} [, {options}]) ch_sendexpr()
异步发送 {expr} 到 {handle}。{expr} 会根据通道类型自动编码。但
不可用于 RAW 模式。
见 channel-use 。 E912
{handle} 可以是通道,或关联了通道的作业。
使用 "lsp" 或 "dap" 模式时,{expr} 必须为 Dict 。
使用 "lsp" 或 "dap" 模式时,会返回字典。否则返回空串。
{options} 里给出 "callback" 项目时,返回字典里会包含请求消息的
ID。有需要时,可用此 ID 向 LSP 服务器或调试适配器发送撤销请
求。如果出错,返回空字典。
如果 {expr} 无需响应消息,{options} 里不要指定 "callback" 项
目。
也可用作 method :
GetChannel()->ch_sendexpr(expr)
返回类型: dict<any> 或 String
ch_sendraw({handle}, {expr} [, {options}]) ch_sendraw()
异步发送原始 String 或 Blob {expr} 数据到 {handle}。
和 ch_sendexpr() 类似,但不做任何请求编码/响应解码处理。调用
者须自行确保数据正确。NL 模式下也不会自动附加换行符,同样需调
用者手动处理。不过,会自动清除响应中的 NL。
见 channel-use 。
也可用作 method :
GetChannel()->ch_sendraw(rawexpr)
返回类型: 无
ch_setoptions({handle}, {options}) ch_setoptions()
修改已打开通道 {handle} 的选项:
"callback" 通道回调函数
"timeout" 读操作以毫秒为单位的缺省超时
"mode" 用于整个通道的模式
详见 ch_open() 。
{handle} 可以是通道,或关联了通道的作业。
注意 修改模式可能会导致队列消息丢失。
以下选项不可修改:
"waittime" 只可在 ch_open() 调用时设置
也可用作 method :
GetChannel()->ch_setoptions(options)
返回类型: 无
ch_status({handle} [, {options}]) ch_status()
返回 {handle} 的状态:
"fail" 通道打开失败
"open" 通道可用
"buffered" 通道已关闭 (不可写),但仍有数据待读取
"closed" 通道已关闭
{handle} 可以是通道,或关联了通道的作业。
"buffered" 状态下已关闭通道的剩余数据可用 ch_read() 读取。
{options} 中可指定 "part" 条目,可能取值为 "out" 或 "err",分
别用于查看输出/错误流状态。例如,要获取错误流状态:
ch_status(job, {"part": "err"})
也可用作 method :
GetChannel()->ch_status()
返回类型: String
要启动作业并为标准输入/输出/错误打开一个通道:
let job = job_start(command, {options})
要获取关联通道:
let channel = job_getchannel(job)
通道默认使用 NL 模式。如果想用其它模式,最好直接在 {options} 里指定。如果之后
再改变模式,部分文本可能已经被接收,导致解析错误。
要处理命令生成的输出行,可提供标准输出回调:
let job = job_start(command, {"out_cb": "MyHandler"})
该函数会被传入通道对象和消息文本。定义方式如下:
func MyHandler(channel, msg)
如果不设置回调,也可通过 ch_read() 或 ch_readraw() 读取输出。可在关闭回调
中执行此操作,见 read-in-close-cb 。
注意 如果在读取输出结果前,作业已经退出,输出内容可能会丢失。这取决于系统
(Unix 上就是如此,因为关闭管道的写入端会导致读取端读到 EOF)。要避免此问题,可
让作业在退出前短暂休眠。
"out_cb" 定义的回调并不处理错误输出。如需单独处理错误输出,请添加 "err_cb" 回
调:
let job = job_start(command, {"out_cb": "MyHandler",
\ "err_cb": "ErrHandler"})
如果要用相同函数同时处理标准输出和错误输出,可用 "callback" 选项:
let job = job_start(command, {"callback": "MyHandler"})
根据系统不同,启动作业时可能会将 Vim 放到后台,使新启动的作业获得焦点。如果不
想将 Vim 放到后台,可用 foreground() 函数。但太早调用可能无效,建议在回调中
调用,或使用定时器推迟到作业启动后再调用。
可用 ch_evalraw() 向命令发送信息。通道使用 JSON 或 JS 模式时,也可用
ch_evalexpr() 。
作业的可用选项见 job-options 。
例如,要启动作业并将输出写入名为 "dummy" 的缓冲区:
let logjob = job_start("tail -f /tmp/log",
\ {'out_io': 'buffer', 'out_name': 'dummy'})
sbuf dummy
从缓冲区输入作业
in_io-buffer
要启动从缓冲区读取输入的作业:
let job = job_start({command},
\ {'in_io': 'buffer', 'in_name': '缓冲区名'})
E915 E918
缓冲区通过名称查找,用法见 bufnr() 。在 job_start() 调用时,该缓冲区必须己
存在且加载。
缺省读入整个缓冲区。可通过 "in_top" 和 "in_bot" 选项修改读取范围。
一个特殊模式是将 "in_top" 设为零而不设置 "in_bot": 每当缓冲区添加新行时,倒数
第二行会发送给作业的标准输入。这样可以编辑末行,按下回车时发送该行。
channel-close-in
非特殊模式下,写入末行后会自动关闭管道或套接字。通知读入端输入结束。也可用
ch_close_in() 提前关闭输入。
文本中的 NUL 字节会直接传递给作业 (Vim 内部会将其存储为 NL 字节)。
在关闭回调中读取作业输出
read-in-close-cb
如果作业运行耗时较长,且无需中间结果,可以添加关闭回调,在其中读取输出:
func! CloseHandler(channel)
while ch_status(a:channel, {'part': 'out'}) == 'buffered'
echomsg ch_read(a:channel)
endwhile
endfunc
let job = job_start(command, {'close_cb': 'CloseHandler'})
"echomsg" 只是举例,实际可换成更有意义的处理。
要启动另一进程而不建立通道:
let job = job_start(command,
\ {"in_io": "null", "out_io": "null", "err_io": "null"})
{command} 会在后台启动,Vim 不会等待其运行结束。
Vim 检测到标准输入、标准输和或标准错误都未被连接时,不会建立通道。通常需要在命
令中包含重定向,避免进程被卡住。
作业的可用选项见 job-options 。
job-start-if-needed
按需启动作业 - 仅当连接目标地址失败时,才启动服务相应地址的服务器进程:
let channel = ch_open(address, {"waittime": 0})
if ch_status(channel) == "fail"
let job = job_start(command)
let channel = ch_open(address, {"waittime": 1000})
endif
注意 ch_open() 使用的等待时间参数会给作业一秒时间来启动并开放端口。
job_getchannel({job}) job_getchannel()
获取 {job} 使用的通道句柄。
要确认作业没有关联通道:
if string(job_getchannel(job)) == 'channel fail'
也可用作 method :
GetJob()->job_getchannel()
返回类型: channel
job_info([{job}]) job_info()
返回包含 {job} 详细信息的字典:
"status" 作业状态: 同 job_status() 返回值
"channel" 关联通道: 同 job_getchannel() 返回值
"cmd" 启动作业的命令行参数列表
"process" 进程 ID
"tty_in" 终端输入名,无则为空
"tty_out" 终端输出名,无则为空
"exitval" 退出码: 仅当 "status" 为 "dead" 时有效
"exit_cb" 退出回调
"stoponexit" Vim 退出时给作业发送的信号 job-stoponexit
仅 Unix 的额外项目:
"termsig" 终止了作业的信号值 (相关值见 job_stop() )
仅当 "status" 为 "dead" 时有效
仅 MS-Windows 的额外项目:
"tty_type" 采用的虚拟控制台类型。
可选值为 "winpty" 或 "conpty"。
见 'termwintype'。
{job} 省略时,返回所有作业对象信息组成的列表。
也可用作 method :
GetJob()->job_info()
返回类型: dict<any> 或 list<job>,取决于是否给出 {job}
job_setoptions({job}, {options}) job_setoptions()
修改 {job} 使用的选项。支持的选项包括:
"stoponexit" Vim 退出时给作业发送的信号 job-stoponexit
"exit_cb" 退出回调 job-exit_cb
也可用作 method :
GetJob()->job_setoptions(options)
返回类型: 无
job_start({command} [, {options}]) job_start()
启动作业并返回 Job 对象。不同于 system() 和 :!cmd ,不等待
作业执行完成。
要在终端窗口中启动作业,可见 term_start() 。
如果作业启动失败,对返回的 Job 对象调用 job_status() 会报告
"fail",而且不会触发任何回调。
{command} 可以是字符串。在 MS-Windows 上效果最佳。在 Unix 上,
会按空白字符分割,传递给 execvp()。因为,而双引号包围的参数内
可能包含空格,这可能会导致问题。
{command} 也可以是列表,首项是可执行文件,后面诸项为参数。所有
项目都会转为字符串。这对 Unix 效果最佳。
MS-Windows 上,本函数会隐藏 GUI 应用窗口。要使之可见,需用
:!start 代替。
命令直接执行,不通过外壳,也不使用 'shell' 选项。要通过外壳间
接执行:
let job = job_start(["/bin/sh", "-c", "echo hello"])
或:
let job = job_start('/bin/sh -c "echo hello"')
注意 这会启动两个过程,外壳及其启动的命令。如果不希望如此,可
使用外壳 "exec" 命令。
Unix 上,仅当命令本身不包含斜杠时,才会通过 $PATH 搜索可执行文
件。
作业会和 Vim 共享终端。如果直接从标准输入读取,作业会和 Vim 会
争夺输入,这不可行。必须重定向标准输入和输出避免此问题:
let job = job_start(['sh', '-c', "myserver </dev/null >/dev/null"])
在返回的 Job 对象上,可用 job_status() 获取状态,用
job_stop() 停止作业。
注意 如果没有变量引用作业对象,它会被自动删除。此时标准输入和
输出会被关闭,可能导致作业运行失败报错。因此,必须保留作业引用
的变量。以下是错误写法:
call job_start('my-command')
正确写法是:
let myjob = job_start('my-command')
仅当不再需要该作业,或已越过其可能失败的阶段 (如启动时输出显示
信息之后),才可以执行 unlet "myjob"。考虑到函数返回后,函数局
部变量不复存在,建议使用脚本局部变量来长期保存作业引用:
let s:myjob = job_start('my-command')
{options} 必须是字典。包含大量可选设置,见 job-options 。
也可用作 method :
BuildCommand()->job_start()
返回类型: job
job_status({job}) job_status() E916
返回表示作业 {job} 状态的字符串:
"run" 作业正在运行
"fail" 作业启动失败
"dead" 作业启动后已结束或被终止
在 Unix 上,运行不存在的命令会报告 "dead" 而非 "fail",因为在
检测到失败之前,已经执行了分叉 (fork) 操作。
在 Vim9 脚本里,已声明但未赋值的作业类型变量传递给本函数会返回
"fail"。
检测到作业处于 "dead" 状态时,会触发通过 "exit_cb" 选项设置的
退出回调 (如有)。
详见 job_info() 。
也可用作 method :
GetJob()->job_status()
返回类型: String
job_stop({job} [, {how}]) job_stop()
停止指定作业 {job}。也可用于向指定作业发送信号。
{how} 省略或为 "term" 时,终止作业。在 Unix 上,这通过发送
SIGTERM 实现。在 MS-Windows 上,作业被强制终止 (没有 "温和" 方
式)。
信号会发送到整个进程组,因而子进程同样会受影响。
Unix 上可用信号:
"term" SIGTERM (缺省)
"hup" SIGHUP
"quit" SIGQUIT
"int" SIGINT
"kill" SIGKILL (最强终止方式)
数值 直接对应信号编号
MS-Windows 上可用效果:
"term" 强制终止进程 (缺省)
"hup" CTRL_BREAK
"quit" CTRL_BREAK
"int" CTRL_C
"kill" 强制终止进程
其他值 CTRL_BREAK
Unix 上,因为信号会发送到整个进程组,所以当作业执行
"sh -c command" 时,外壳进程和命令进程会同时受影响。
返回值类型为数值: 操作执行成功则返回 1,否则,如果系统不支持
"how" 效果,则返回 0。
注意 即使操作执行成功,仍需用 job_status() 确认作业是否真正
已停止。
如果作业状态已是 "dead",不会发送信号。避免杀死错误作业 (尤其
是 Unix 会循环利用进程号)。
使用 "kill" 时,Vim 会直接假定作业已停止,因而会关闭相关通道。
也可用作 method :
GetJob()->job_stop()
返回类型: Number
job_start() 的 {options} 参数是一个字典。其中所有项目均为可选。有些选项可在
作业启动后,通过 job_setoptions(job, {options}) 设置。还有许多和作业相关联的通
道选项,可通过 ch_setoptions(channel, {options}) 设置。见 job_setoptions()
和 ch_setoptions() 。
in_mode out_mode err_mode
"in_mode" 标准输入 (stdin) 专用模式,仅适用于管道方式。
"out_mode" 标准输出 (stdout) 专用模式,仅适用于管道方式。
"err_mode" 标准错误 (stderr) 专用模式,仅适用于管道方式。
可用值参见 channel-mode 。
注意: 设置 "mode" 会覆盖各流的专用模式。因此,应先设置
"mode",再设置流专用模式。
备注: 写入文件或缓冲区以及从缓冲区读取时,缺省使用 NL
模式。
job-noblock
"noblock": 1 写入时使用非阻塞写调用。避免 Vim 在写入过程中需要处理
其他信息 (如作业向 Vim 回传数据) 时的长时间等待。这意
味着 ch_sendraw() 返回时,未必全部数据都已写完。
该选项在 8.1.0350 补丁中加入,可如此测试:
if has("patch-8.1.350")
let options['noblock'] = 1
endif
job-callback
"callback": handler 通道任意流有数据可读时触发的统一回调。
job-out_cb out_cb
"out_cb": handler 标准输出有数据可读时触发的回调。仅适用于管道方式。省略
时,默认使用通道统一回调。
接受两个参数: 通道对象和消息文本。
job-err_cb err_cb
"err_cb": handler 标准错误有数据可读时触发的回调。仅适用于管道方式。省略
时,默认使用通道统一回调。
接受两个参数: 通道对象和消息文本。
job-close_cb
"close_cb": handler 通道关闭时触发的回调。与 ch_open() 的 "close_cb" 相
同,见 close_cb 。
job-drop
"drop": when 指定丢弃消息的时机。与 ch_open() 的 "drop" 相同,见
channel-drop 。"auto" 模式下,不考虑 exit_cb。
job-exit_cb
"exit_cb": handler 作业结束时触发的回调。接受参数: 作业对象和退出码。
Vim 每秒最多检查十次作业是否结束。调用 job_status()
也会触发检查,此时可能会执行本回调。
备注 作业结束后,缓冲数据仍可能触发数据回调。
job-timeout
"timeout": time 请求阻塞时 (如 ch_evalexpr() ) 等待响应的超时时间,以
毫秒为单位。缺省值为 2000 (相当于 2 秒)。
out_timeout err_timeout
"out_timeout": time 标准输出专用超时。仅适用于管道方式。
"err_timeout": time 标准错误所用超时。仅适用于管道方式。
注意: 设置 "timeout" 会覆盖各流的专用超时。因此,应先
设置 "timeout",再设置流专用超时。
job-stoponexit
"stoponexit": {signal} Vim 退出时给作业发送的信号 {signal}。可选值见
job_stop() 。
"stoponexit": "" Vim 退出时不停止作业。
缺省值为 "term"。
job-term
"term": "open" 在新窗口中启动终端,并将作业的标准输入/输出/错误流重定
向到该终端。和 :terminal 类似。
注意: 尚未实现!
"channel": {channel} 使用现有通道,而不是重新创建。
通道会断开之前的连接,以连接新作业流。
如果该通道仍被其他作业使用,可能导致 I/O 错误。
原有回调和其它设置被保留。
"pty": 1 尽可能使用 pty (伪终端) 而非管道。多用于配合终端窗口使
用,见 terminal 。
{仅在类 Unix 系统上有效}
job-in_io in_top in_bot in_name in_buf
标准输入 (stdin) 相关:
"in_io": "null" 断开标准输入 (从 /dev/null 读取)
"in_io": "pipe" 标准输入连接到通道 (缺省)
"in_io": "file" 从文件读取标准输入
"in_io": "buffer" 从缓冲区读取标准输入
"in_top": number 使用 "buffer" 模式时: 指定开始行号 (缺省: 1)
"in_bot": number 使用 "buffer" 模式时: 指定结束行号 (缺省: 末行)
"in_name": "/path/file" 指定要读取的文件路径或缓冲区名
"in_buf": number 指定要读取的缓冲区号
job-out_io out_name out_buf
标准输出 (stdout) 相关:
"out_io": "null" 断开标准输出 (写到 /dev/null)
"out_io": "pipe" 标准输出连接到通道 (缺省)
"out_io": "file" 标准输出写入文件
"out_io": "buffer" 标准输出追加内容到缓冲区 (见下)
"out_name": "/path/file" 指定输出目标文件路径或缓冲区名
"out_buf": number 指定输出目标缓冲区号
"out_modifiable": 0 写入缓冲区后,关闭其 'modifiable' 值 (见下)
"out_msg": 0 写入缓冲区时,不显示首行提示文字
"Reading from channel output..."
job-err_io err_name err_buf
标准错误 (stderr) 相关:
"err_io": "out" 错误信息合并到标准输出
"err_io": "null" 断开标准错误 (写到 /dev/null)
"err_io": "pipe" 标准错误连接到通道 (缺省)
"err_io": "file" 标准错误写入文件
"err_io": "buffer" 标准错误追加内容到缓冲区 (见下)
"err_name": "/path/file" 指定错误输出目标文件路径或缓冲区名
"err_buf": number 指定错误输出目标缓冲区号
"err_modifiable": 0 写入到缓冲区时,关闭其 'modifiable' 值 (见下)
"err_msg": 0 写入新缓冲区时,不显示首行提示文字
"Reading from channel error..."
"block_write": number 仅用于测试: 模拟向标准输入的写入操作中,每隔一次都会发
生阻塞
"env": dict 设置新进程的环境变量
"cwd": "/path/to/dir" 设置新进程的工作目录;如果该目录不存在,报错
写入缓冲区
out_io-buffer
out_io 或 err_io 设置为 "buffer" 且给出回调时,文本先追加到缓冲区,再触发回
调。
同一个缓冲区同时用于输入和输出时,输出行会被插入在尾行的上方,因为尾行用于写入
通道输入的内容。否则,输出行会追加到尾行之后。
在 JS 或 JSON 模式下使用 "buffer" 时,只有 ID 为零或负数的消息会被解码和编码后
写入缓冲区。ID 为 正数的消息则由回调处理。相关命令照常处理。
"out_name" 或 "err_name" 指定的缓冲区名会和现有缓冲区的完整名称相比较,包括展
开当前目录后的名称。例如,指定缓冲区名为 "somename" 时,会使用
`:edit somename` 创建的缓冲区。
如果没有匹配到缓冲区,会自动新建一个。使用空名称可以强制创建新缓冲区。可通过
ch_getbufnr() 获取新缓冲区的编号。
新缓冲区的 'buftype' 会自动设为 "nofile",而 'bufhidden' 设为 "hide"。如果希望
使用其他设置,先手动创建缓冲区,再传入该缓冲区号。
out_modifiable err_modifiable
"out_modifiable" 和 "err_modifiable" 选项可用于关闭缓冲区的 'modifiable' 选
项,或向已关闭 'modifiable' 的缓冲区写入内容。此时,新行会被附加到缓冲区,但用
户无法轻易修改缓冲区内容。缺省是打开该选项,设置为 0 则关闭该选项。
如果要试图写入已有缓冲区,但其 'modifiable' 已关闭并且 "out_modifiable" 或
"err_modifiable" 选项非零 (缺省),报错且拒绝写入。
out_msg err_msg
"out_msg" 和 "err_msg" 选项可用来指定新缓冲区是否将首行设为提示文字 "Reading
from channel output (error)..."。缺省是显示首行提示文字,设置为 0 则不显示。
被写入的缓冲区正在窗口中显示,并且光标位于末行的首列时,光标会自动移动到新增行
上,有必要时窗口会自动滚动以显示光标。
每新增一行,都会同步一次撤销历史。接受 NUL 字节 (Vim 内部存为 NL 字节)。
支持输出中的 NUL 字节 (Vim 内部会将其存储为 NL 字节)。
写入文件
E920
新建文件的缺省权限为 600 (仅用户可读写,其它用户无法访问)。可用 setfperm()
修改权限。
如果文件已存在,会先清空文件内容。
要获取作业状态:
echo job_status(job)
要停止作业的运行:
job_stop(job)
这是结束作业的常规方式,在 Unix 上,会向作业发送 SIGTERM 信号。也可以用其它方
式来停止作业,甚至发送任意信号。例如,要强制 "杀死" 作业:
job_stop(job, "kill")
更多选项可见 job_stop() 。
如果希望在 Vim 窗口中为作业提供输入,有以下几种选择:
- 使用普通缓冲区,并自行处理所有可能的命令。这很复杂,因为可用的命令太多。
- 使用终端窗口。此方式适合直接在窗口中键入作业输入,而作业输出直接在窗口中显
示的情况。见 terminal-window 。
- 使用提示缓冲区窗口。此方式适用直接在 Vim 中键入作业输入行,而作业输出 (可能
经过过滤) 在窗口中显示的情况。
通过将 'buftype' 设置为 "prompt",可创立提示缓冲区。通常只在新建缓冲区时执行此
操作。
用户可在缓冲区的末行 (提示行) 编辑并输入一行文本。在提示行上输入 Enter 后,会
触发 prompt_setcallback() 设置的回调。该回调通常它会将输入行内容发送给作业。
而另一个回调会接收作业输出,并将其显示在缓冲区中当前提示之下 (但在下一个提示的
上方)。
只有末行中提示符之后的文本可进行编辑。缓冲区其余部分无法用普通模式命令修改。可
以通过调用 append() 等函数来修改缓冲区内容。使用其他命令可能会搞乱缓冲区。
将 'buftype' 设为 "prompt" 时,Vim 并不会自动进入插入模式。如果希望用户可以直
接开始输入,可用 :startinsert 命令。
要设置提示符文本,可用 prompt_setprompt() 函数。如果未设置,缺省使用 "% "。
要获取缓冲区实际生效的提示符文本,可用 prompt_getprompt() 。
用户可以进入普通模式,浏览缓冲区内容,以便查看历史输出或复制文本。
CTRL-W 键可用于开启窗口命令 (如 CTRL-W w 可切换到下一个窗口)。即使在插入模式下
也有效 (此时如需删除单词 i_CTRL-W ,可改用 Shift-CTRL-W)。离开提示窗口时,插
入模式会停止。而回到提示窗口时,会自动复原插入模式。
任何会启动插入模式的命令,如 a 、 i 、 A 和 I 等,光标会自动跳转到末行。
A 会跳转到行尾,而 I 会跳转到行首。
以下是适用于 Unix 的示例。它在后台启动外壳,并提示输入下一条外壳命令。外壳输出
会显示在提示行上方。
" 建立通道日志,以记录发生的事件。
call ch_logfile('logfile', 'w')
" 处理键入文本行的函数。
func TextEntered(text)
" 给文本附加 Enter,发送到外壳。
call ch_sendraw(g:shell_job, a:text .. "\n")
endfunc
" 处理外壳输出的函数: 附加到提示行上方。
func GotOutput(channel, msg)
call append(line("$") - 1, "- " .. a:msg)
endfunc
" 处理外壳退出的函数: 关闭窗口。
func JobExit(job, status)
quit!
endfunc
" 在后台启动外壳。
let shell_job = job_start(["/bin/sh"], #{
\ out_cb: function('GotOutput'),
\ err_cb: function('GotOutput'),
\ exit_cb: function('JobExit'),
\ })
new
set buftype=prompt
let buf = bufnr('')
call prompt_setcallback(buf, function("TextEntered"))
eval prompt_setprompt(buf, "shell command: ")
" 开始接受外壳命令的输入
startinsert
同一例子,但使用 Vim9 脚本:
vim9script
# 建立通道日志,以记录发生的事件。
ch_logfile('logfile', 'w')
var shell_job: job
# 处理键入文本行的函数。
def TextEntered(text: string)
# 给文本附加 Enter,发送到外壳。
ch_sendraw(shell_job, text .. "\n")
enddef
# 处理外壳输出的函数: 附加到提示行上方。
def GotOutput(channel: channel, msg: string)
append(line("$") - 1, "- " .. msg)
enddef
# 处理外壳退出的函数: 关闭窗口。
def JobExit(job: job, status: number)
quit!
enddef
# 在后台启动外壳。
shell_job = job_start(["/bin/sh"], {
out_cb: GotOutput,
err_cb: GotOutput,
exit_cb: JobExit,
})
new
set buftype=prompt
var buf = bufnr('')
prompt_setcallback(buf, TextEntered)
prompt_setprompt(buf, "shell command: ")
# 开始接受外壳命令的输入
startinsert
可在这里获取语言服务器协议 (LSP) 规范:
https://microsoft.github.io/language-server-protocol/specification
每个 LSP 协议消息都以简单 HTTP 请求头开始,后跟 JSON-RPC 格式编码的负载。
JSON-RPC 格式规范可见:
https://www.jsonrpc.org/specification
在连接到 LSP 服务器时将 channel-mode 设为 "lsp",就可以将 Vim Dict 形式的
LSP 请求/通告消息编码封装为 LSP JSON-RPC 消息,并发送至服务器,同时接收 LSP
JSON-RPC 格式的响应/通告消息,并解码为 Vim Dict 。
channel-mode 设为 "lsp" 时,Vim 会自动解析通道接收到消息的 HTTP 头部,并把
JSON-RPC 负载解码为 Vim Dict 类型。然后触发 channel-callback 回调或指定
的 channel-onetime-callback 一次性回调。使用 ch_evalexpr() 或
ch_sendexpr() 函数向通道发送消息时,Vim 会自动附加 HTTP 头部,并把 Vim 表达
式编码为 JSON。关于 Vim 内建类型与 JSON 间的编码与解码,详见 json_encode()
和 json_decode() 。
要用 "lsp" 模式打开通道,在调用 ch_open() 时,将 {options} 参数的 "mode" 项
设为 "lsp"。示例:
let ch = ch_open(..., #{mode: 'lsp'})
要用 "lsp" 模式打开作业通道,在调用 job_start() 时,将 {options} 参数的
"in_mode" 和 "out_mode" 项设为 "lsp"。示例:
let cmd = ['clangd', '--background-index', '--clang-tidy']
let opts = {}
let opts.in_mode = 'lsp'
let opts.out_mode = 'lsp'
let opts.err_mode = 'nl'
let opts.out_cb = function('LspOutCallback')
let opts.err_cb = function('LspErrCallback')
let opts.exit_cb = function('LspExitCallback')
let job = job_start(cmd, opts)
注意 如果作业在标准输出发送 LSP 消息,而在标准错误输出非 LSP 消息,通道回调必
须能正确处理两种消息格式,也可像前文所示,分别指定 "out_cb" 和 "err_cb" 回调以
单独处理不同格式。
要同步向服务器发送 JSON-RPC 请求,可用 ch_evalexpr() 函数。该函数会阻塞等
待,并返回经解码的服务器响应消息。可以通过 channel-timeout 或 {options} 参数
里的 "timeout" 字段,控制等待响应的时长。如果请求超时,会返回空 Dict 。
示例:
let req = {}
let req.method = 'textDocument/definition'
let req.params = {}
let req.params.textDocument = #{uri: 'a.c'}
let req.params.position = #{line: 10, character: 3}
let defs = ch_evalexpr(ch, req, #{timeout: 100})
if defs->empty()
... <处理失败>
endif
注意 请求消息中不应手动指定 "id" 字段。即使指定,Vim 也会用内部生成的标识符将
其覆盖。Vim 目前只支持数值类型的 "id" 字段。
不管 RPC 请求是成功还是失败,回调都会被触发。
要向服务器发送 JSON-RPC 请求并异步处理响应,可用 ch_sendexpr() 函数并传入
回调函数。如果请求消息里指定了 "id" 字段,Vim 同样会用内部生成的标识符将其覆
盖。该函数返回字典,其中包含消息实际使用的标识符。这可用于向 LSP 服务器发送撤
销请求 (如有需要)。例如:
let req = {}
let req.method = 'textDocument/hover'
let req.id = 200
let req.params = {}
let req.params.textDocument = #{uri: 'a.c'}
let req.params.position = #{line: 10, character: 3}
let resp = ch_sendexpr(ch, req, #{callback: 'HoverFunc'})
要撤销已通过 ch_sendexpr() 函数发送给服务器、尚未完成的异步 LSP 请求,可用该
函数返回的消息 ID,再次调用 ch_sendexpr() 向服务器发送撤销消息。例如:
" 发送 completion 请求
let req = {}
let req.method = 'textDocument/completion'
let req.params = {}
let req.params.textDocument = #{uri: 'a.c'}
let req.params.position = #{line: 10, character: 3}
let reqstatus = ch_sendexpr(ch, req, #{callback: 'LspComplete'})
" 发送撤销通告
let notif = {}
let notif.method = '$/cancelRequest'
let notif.id = reqstatus.id
call ch_sendexpr(ch, notif)
要向服务器发送 JSON-RPC 通告消息,也可用 ch_sendexpr() 函数。因为服务器不会
对通告返回响应,无需指定 "callback" 项。例如:
call ch_sendexpr(ch, #{method: 'initialized'})
要响应服务器发送的 JSON-RPC 请求消息,同样可用 ch_sendexpr() 函数。在响应消
息里,复制服务器请求消息里的 "id" 字段。例如:
let resp = {}
let resp.id = req.id
let resp.result = 1
call ch_sendexpr(ch, resp)
服务器发来的 JSON-RPC 通告消息会经由 channel-callback 回调分发。
取决于用例,可在同一通道上混用 ch_evalexpr() 、 ch_sendexpr() 和
ch_sendraw() 函数。
LSP 请求消息的格式如下 (以 Vim 字典形式表示)。"params" 字段可选:
{
"jsonrpc": "2.0",
"id": <number>,
"method": <string>,
"params": <list|dict>
}
LSP 响应消息的格式如下 (以 Vim 字典形式表示)。"result" 和 "error" 字段可选:
{
"jsonrpc": "2.0",
"id": <number>,
"result": <vim type>
"error": <dict>
}
LSP 通告消息的格式如下 (以 Vim 字典形式表示)。"params" 字段可选:
{
"jsonrpc": "2.0",
"method": <string>,
"params": <list|dict>
}