通道

Nvim :help 页面,生成源代码 使用 tree-sitter-vimdoc 解析器。


Nvim 异步 I/O

1. 简介 channel-intro

通道是 nvim 与外部进程通信的方式。
有几种方法可以打开通道
1. 通过 stdin/stdout 当 nvim 使用 --headless 启动,并且启动脚本或 --cmd 命令使用 stdioopen() 打开 stdio 通道。
2. 通过 jobstart() 生成的进程的 stdin、stdout 和 stderr。
3. 通过使用 jobstart(..., {'pty': v:true})termopen() 打开的 PTY 的主端 PTY。
4. 通过 sockconnect() 连接到 TCP/IP 套接字或命名管道。
5. 通过另一个进程连接到 nvim 监听的套接字。这仅支持 RPC 通道,请参阅 rpc-connecting
通道支持多种模式或协议。在最基本的运行模式下,原始字节被读写到通道。基于 msgpack-rpc 标准的 RPC 协议,使 nvim 和另一端的进程能够相互发送远程调用和事件。内置的 终端模拟器 也是在 PTY 通道之上实现的。
通道 ID channel-id
每个通道都由一个整型 ID 标识,在当前 Nvim 会话的整个生命周期内都是唯一的。像 stdioopen() 这样的函数返回通道 ID;像 chansend() 这样的函数使用通道 ID。

2. 读取和写入原始字节 channel-bytes

默认情况下,由 Vimscript 函数打开的通道使用原始字节进行操作。对于使用 RPC 的作业通道,字节仍然可以通过其 stderr 读取。类似地,只能将字节写入 Nvim 自己的 stderr。
channel-callback
on_stdout({chan-id}, {data}, {name}) on_stdout
on_stderr({chan-id}, {data}, {name}) on_stderr
on_stdin({chan-id}, {data}, {name}) on_stdin
on_data({chan-id}, {data}, {name}) on_data
脚本可以通过分配给 on_stdouton_stderron_stdinon_data 选项键的回调函数来响应通道活动(接收到的数据)。回调应该很快:避免潜在的缓慢/昂贵的操作。
参数
{chan-id} 通道句柄。 channel-id
{data} 从通道读取的原始数据(readfile()-style 字符串列表)。EOF 是一个单项列表:['']。第一项和最后一项可能是部分行! channel-lines
{name} 流名称(字符串),如“stdout”,因此同一个函数可以处理多个流。事件名称取决于通道的打开方式以及其模式/协议。
channel-buffered
当数据可用时,会立即调用回调,其中单项列表 [''] 表示 EOF(流已关闭)。或者设置 stdout_bufferedstderr_bufferedstdin_buffereddata_buffered 选项键,仅在收集完所有输出并关闭流后调用回调。 E5210
如果使用缓冲模式但没有回调,则数据将保存在选项字典的流 {name} 键中。如果键存在,则会发生错误。
channel-lines
流事件处理程序会接收来自操作系统的可用数据,因此 {data} 列表中的第一项和最后一项可能是部分行。空字符串完成之前的部分行。示例(不包括在 EOF 时发出的最终 ['']
foobar 可能到达为 ['fo'], ['obar']
foo\nbar 可能到达为
['foo','bar']
['foo',''], ['bar']
['foo'], ['','bar']
['fo'], ['o','bar']
有两种方法可以解决这个问题
1. 要等待整个输出,请使用 channel-buffered 模式。
2. 要逐行读取,请使用以下代码
let s:lines = ['']
func! s:on_event(job_id, data, event) dict
  let eof = (a:data == [''])
  " Complete the previous line.
  let s:lines[-1] .= a:data[0]
  " Append (last item may be a partial line, until EOF).
  call extend(s:lines, a:data[1:])
endf
如果回调函数是 Dictionary-function,则 self 指的是包含回调的选项字典。 Partial 也可以用作回调。
可以使用 chansend() 函数将数据发送到通道。这是一个简单的示例,通过 cat 进程回显一些数据
function! s:OnEvent(id, data, event) dict
  let str = join(a:data, "\n")
  echomsg str
endfunction
let id = jobstart(['cat'], {'on_stdout': function('s:OnEvent') } )
call chansend(id, "hello!")
这是一个将缓冲区设置为 grep 结果的示例,但仅在处理完所有数据后进行
function! s:OnEvent(id, data, event) dict
  call nvim_buf_set_lines(2, 0, -1, v:true, a:data)
endfunction
let id = jobstart(['grep', '^[0-9]'], { 'on_stdout': function('s:OnEvent'),
                                      \ 'stdout_buffered':v:true } )
call chansend(id, "stuff\n10 PRINT \"NVIM\"\nxx")
" no output is received, buffer is empty
call chansend(id, "xx\n20 GOTO 10\nzz\n")
call chanclose(id, 'stdin')
" now buffer has result
有关作业的其他示例,请参阅 job-control
channel-pty
特殊情况:使用 jobstart(..., {'pty': v:true}) 打开的 PTY 通道不会预处理 ANSI 转义序列,这些序列将以原始形式发送到回调。但是,可以使用 jobresize() 将 PTY 大小更改信号发送到从属设备。另请参阅 terminal-emulator
用于 :terminal 和 PTY 通道的终端特性(termios)从主机 TTY 复制,或者如果 Nvim 是 --headless,它使用默认值
:echo system('nvim --headless +"te stty -a" +"sleep 1" +"1,/^$/print" +q')

3. 使用 msgpack-rpc 通信 channel-rpc

当使用 rpc 选项设置为 true 打开通道时,通道可以用于双向的远程方法调用,请参阅 msgpack-rpc。请注意,rpc 通道是隐式信任的,另一端的进程可以调用任何 API 函数!

4. 标准 I/O 通道 channel-stdio

Nvim 使用 stdin/stdout 通过终端界面 (TUI) 与用户交互。如果 Nvim 是 --headless,则不会启动 TUI,并且 stdin/stdout 可以用作通道。另请参阅 --embed
启动 期间调用 stdioopen() 以打开 stdio 通道作为 channel-id 1。Nvim 的 stderr 始终可用作 v:stderr,一个只写字节通道。
示例
func! OnEvent(id, data, event)
  if a:data == [""]
    quit
  end
  call chansend(a:id, map(a:data, {i,v -> toupper(v)}))
endfunc
call stdioopen({'on_stdin': 'OnEvent'})
将此放入 uppercase.vim 并运行
nvim --headless --cmd "source uppercase.vim"

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

如果您想在 Vim 窗口中为作业输入内容,您有几个选择
使用普通缓冲区并自己处理所有可能的命令。这将很复杂,因为有太多可能的命令。
使用终端窗口。如果键入的内容直接进入作业,并且作业输出直接显示在窗口中,则此方法效果很好。请参阅 terminal
使用带提示缓冲区的窗口。当在 Vim 中为作业输入一行,同时显示作业的(可能已过滤)输出时,此方法效果很好。
提示缓冲区是通过将 'buftype' 设置为“prompt”来创建的。您通常只会在新创建的缓冲区中执行此操作。
用户可以在缓冲区的最后一行编辑并输入一行文本。在提示行中按回车键时,将调用使用 prompt_setcallback() 设置的回调。它通常会将该行发送到作业。另一个回调将接收来自作业的输出并将其显示在缓冲区中,在提示下方(以及下一个提示上方)。
只有提示后的最后一行中的文本是可编辑的。缓冲区的其余部分无法使用普通模式命令修改。它可以通过调用函数来修改,例如 append()。使用其他命令可能会弄乱缓冲区。
'buftype' 设置为“prompt”后,Vim 不会自动启动插入模式,如果要进入插入模式,请使用 :startinsert,以便用户可以开始键入一行。
可以使用 prompt_setprompt() 函数设置提示文本。如果未使用 prompt_setprompt() 设置提示,则使用 "% "。您可以使用 prompt_getprompt() 获取缓冲区的有效提示文本。
用户可以进入普通模式并在缓冲区中导航。这对于查看旧的输出或复制文本很有用。
CTRL-W 键可用于启动窗口命令,例如 CTRL-W w 切换到下一个窗口。这在插入模式下也能正常工作(使用 Shift-CTRL-W 删除一个单词)。离开窗口时,将停止插入模式。返回提示窗口时,将恢复插入模式。
任何启动插入模式的命令,如“a”、“i”、“A”和“I”,都将光标移动到最后一行。“A”将移动到行尾,“I”将移动到行首。
以下是以 Unix 为例的示例。它在后台启动一个 shell,并提示输入下一个 shell 命令。来自 shell 的输出将显示在提示上方。
" Function handling a line of text that has been typed.
func TextEntered(text)
  " Send the text to a shell with Enter appended.
  call chansend(g:shell_job, [a:text, ''])
endfunc
" Function handling output from the shell: Add it above the prompt.
func GotOutput(channel, msg, name)
  call append(line("$") - 1, a:msg)
endfunc
" Function handling the shell exits: close the window.
func JobExit(job, status, event)
  quit!
endfunc
" Start a shell in the background.
let shell_job = jobstart(["/bin/sh"], #{
        \ on_stdout: function('GotOutput'),
        \ on_stderr: function('GotOutput'),
        \ on_exit: function('JobExit'),
        \ })
new
set buftype=prompt
let buf = bufnr('')
call prompt_setcallback(buf, function("TextEntered"))
call prompt_setprompt(buf, "shell command: ")
" start accepting shell commands
startinsert
主要
命令索引
快速参考