channel

channel.txt 适用于 Vim 9.2 版本。 最近更新: 2026年5月 VIM 参考手册 by Bram Moolenaar 译者: Willis 进 程 间 通 信 channel Vim 使用通道 (channel) 和其他进程通信。 通道使用套接字 (socket) 或管道 (pipe) 机制实现数据交互。 socket-interface 作业 (job) 用来启动进程并与之建立通信。 Netbeans 接口也基于通道实现。 netbeans 1. 总览 job-channel-overview 2. 通道演示 channel-demo 3. 打开通道 channel-open 4. 使用 JSON 或 JS 通道 channel-use 5. 通道命令 channel-commands 6. 使用 RAW 或 NL 通道 channel-raw 7. 更多通道函数 channel-more 8. 通道函数详情 channel-functions-details 9. 带通道启动作业 job-start 10. 不带通道启动作业 job-start-nochannel 11. 作业函数 job-functions-details 12. 作业选项 job-options 13. 作业控制 job-control 14. 使用提示缓冲区 prompt-buffer 15. 语言服务器协议 language-server-protocol 16. 调试适配器协议 debug-adapter-protocol E1277 {仅当编译时带有 +channel 特性时才有通道功能} 要查看是否支持 +channel 特性,可查看: `has('channel')` {仅当编译时带有 +job 特性时才有作业功能} 要查看是否支持 +job 我,可查看: `has('job')`

1. 总览 job-channel-overview

有四种主要的作业类型: 1. 守护进程 (daemon),为多个 Vim 实例提供服务。 Vim 通过套接字与其建立连接。 2. 和单个 Vim 实例绑定的单个作业,以异步方式运行。 通过套接字或管道方式通信。 3. 用于短期临时操作的一次性作业,以异步方式运行。 通过套接字或管道方式通信。 4. 用作过滤器,以同步方式运行。 仅通过管道通信。 套接字相关用法,请见 job-startjob-start-nochannelchannel-open 。 第 2 和 3 类作业使用管道的相关用法,可见 job-start 。第 4 类即 ":{range}!cmd" 命令使用管道的相关用法,可见 filter 。 套接字和管道支持以下传输协议: RAW 原始数据流,无规范,Vim 无法界定消息边界 NL 每条消息以 NL (换行符) 字符结尾 JSON JSON 编码格式,见 json_encode() JS JavaScript 风格的类 JSON 的编码格式,见 js_encode() LSP 语言服务器协议编码 language-server-protocol DAP 调试适配器协议编码 debug-adapter-protocol 常用组合有: - 管道通信 + NL 协议。例如运行代码风格检查工具,接收错误与警告信息。 - 套接字连接的守护进程 + JSON 协议。例如查询数据库中的代码交叉引用。

2. 通道演示 channel-demo demoserver.py

该演示需要 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)

3. 打开通道 channel-open

要打开通道: 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

4. 使用 JSON 或 JS 通道 channel-use

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() 。但调用者必须自行负责 编码和解码。

5. 通道命令 channel-commands

进程可通过 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"]]]

6. 使用 RAW 或 NL 通道 channel-raw

使用 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-bufferout_io-buffer

7. 更多通道函数 channel-more

获取通道状态: 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 通道,返回一条已解码的消息,包含序号。

8. 通道函数详情 channel-functions-details

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() 返回类型: jobString 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() 异步发送原始 StringBlob {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

9. 带通道启动作业 job-start job

要启动作业并为标准输入/输出/错误打开一个通道: 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" 只是举例,实际可换成更有意义的处理。

10. 不带通道启动作业 job-start-nochannel

要启动另一进程而不建立通道: 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() 使用的等待时间参数会给作业一秒时间来启动并开放端口。

11. 作业函数 job-functions-details

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

12. 作业选项 job-options

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() 修改权限。 如果文件已存在,会先清空文件内容。

13. 作业控制 job-control

要获取作业状态: echo job_status(job) 要停止作业的运行: job_stop(job) 这是结束作业的常规方式,在 Unix 上,会向作业发送 SIGTERM 信号。也可以用其它方 式来停止作业,甚至发送任意信号。例如,要强制 "杀死" 作业: job_stop(job, "kill") 更多选项可见 job_stop()

14. 使用提示缓冲区 prompt-buffer

如果希望在 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)。离开提示窗口时,插 入模式会停止。而回到提示窗口时,会自动复原插入模式。 任何会启动插入模式的命令,如 aiAI 等,光标会自动跳转到末行。 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

15. 语言服务器协议 language-server-protocol

可在这里获取语言服务器协议 (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 Dictchannel-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> }

16. 调试适配器协议 debug-adapter-protocol

调试适配器协议 (DAP) 与语言服务器协议 (LSP) 非常相似,主要区别在于不使用 JSON-RPC 格式。协议规范可在此查阅: https://microsoft.github.io/debug-adapter-protocol/specification 该协议使用与 LSP 协议相同的消息头格式。 在连接到调试适配器时将 channel-mode 设为 "dap",就可以将 Vim Dict 形式的 DAP 请求/通告消息编码封装为 JSON 消息,并发送至服务器,同时接收 JSON 格式的响 应/通告消息,并解码为 Vim Dictchannel-mode 设为 "dap" 时,Vim 会自动解析通道接收到消息的 HTTP 头部,并把 JSON 负载解码为 Vim Dict 类型。使用 ch_evalexpr()ch_sendexpr() 函数 向通道发送消息时,Vim 会自动附加 HTTP 头部,并把 Vim 表达式编码为 JSON。 Vim 会自动为 JSON DAP 消息添加 "seq" 字段,并为响应管理 "request_seq" 字段。但 不会自动添加 "type" 字段,该字段需要在 Dict 中手动指定。 除此之外,其他行为与 Vim 处理 "lsp" 通道模式的方式一致,详见 language-server-protocol 。 vim:tw=78:ts=8:noet:ft=help:norl: