通道

Nvim 的 :help 頁面,由 生成,其原始碼使用 tree-sitter-vimdoc 解析器。


Nvim 非同步 IO

1. 簡介 channel-intro

通道是 nvim 與外部程序溝通的方式。
開啟通道有幾種方式
1. 當 nvim--headless 啟動時,通過 stdin/stdout,以及啟動腳本或 --cmd 命令使用 stdioopen() 開啟 stdio 通道。
2. 通過 jobstart() 產生的進程的 stdin、stdout 和 stderr。
3. 通過使用 jobstart(..., {'pty': v:true})termopen() 開啟的 PTY 的主端。
4. 通過使用 sockconnect() 連接到 TCP/IP 套接字或具名管道。
5. 由另一個進程連接到 nvim 監聽的套接字。這僅支援 RPC 通道,請參閱 rpc-connecting
通道支援多種模式或協定。在最基本的操作模式中,原始位元組會讀寫到通道。基於 msgpack-rpc 標準的 RPC 協定,使 nvim 和另一端的進程能夠相互傳送遠端呼叫和事件。內建的 終端模擬器 也是在 PTY 通道之上實作的。
通道 ID channel-id
每個通道都由一個整數 ID 標識,該 ID 在當前 Nvim 會話的生命週期內是唯一的。stdioopen() 等函數會回傳通道 ID;chansend() 等函數會使用通道 ID。

2. 讀取和寫入原始位元組 channel-bytes

由 Vimscript 函數開啟的通道預設使用原始位元組。對於使用 RPC 的 job 通道,仍然可以通過其 stderr 讀取位元組。同樣,只有位元組可以寫入 Nvim 自己的 stderr。
通道回呼
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() 的字串列表)。EOF 是一個單一項目的列表:['']。第一個和最後一個項目可能是部分行!channel-lines
{name} 串流名稱(字串),例如 "stdout",以便同一個函數可以處理多個串流。事件名稱取決於通道的開啟方式以及模式/協定。
通道緩衝
當資料可用時,會立即調用回呼函數,其中單一項目列表 [''] 表示 EOF(串流已關閉)。或者,設定 stdout_bufferedstderr_bufferedstdin_buffereddata_buffered 選項鍵,以便僅在收集所有輸出並關閉串流後才調用回呼函數。 E5210
如果使用沒有回呼函數的緩衝模式,則資料會儲存在選項字典的串流 {name} 鍵中。如果該鍵存在,則會發生錯誤。
通道行
串流事件處理常式會接收來自作業系統的可用資料,因此 {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
如果回呼函數是 字典函數,則 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 的其他範例,請參閱 job-control
通道 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. 標準 IO 通道 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 視窗中為 job 輸入內容,您有幾個選項
使用普通緩衝區並自行處理所有可能的命令。這會很複雜,因為有太多可能的命令。
使用終端視窗。如果您輸入的內容直接傳送到 job,並且 job 輸出直接顯示在視窗中,這會很好用。請參閱 terminal
使用帶有提示緩衝區的視窗。當在 Vim 中輸入 job 的一行,同時顯示 job 的(可能經過篩選的)輸出時,這會很好用。
提示緩衝區是透過將 'buftype' 設定為 "prompt" 來建立的。您通常只會在新建立的緩衝區中執行此操作。
使用者可以在緩衝區的最後一行編輯並輸入一行文字。當在提示行中按下 Enter 時,將調用使用 prompt_setcallback() 設定的回呼函數。它通常會將該行傳送到 job。另一個回呼函數會接收來自 job 的輸出並將其顯示在緩衝區中,提示下方(以及下一個提示上方)。
只有最後一行中,提示之後的文字是可以編輯的。緩衝區的其餘部分無法使用一般模式命令修改。可以通過呼叫函數(例如 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
主要
命令索引
快速參考