チャネル

Nvim の:help ページ。 生成されたもので、ソースtree-sitter-vimdoc パーサーを使用して生成されています。


Nvim 非同期IO

1. はじめに channel-intro

チャネルは、nvim が外部プロセスと通信するための方法です。
チャネルを開く方法はいくつかあります。
1. nvim--headless オプションで起動され、スタートアップスクリプトまたは --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
各チャネルは、現在の Nvim セッションの存続期間において一意の整数 ID で識別されます。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_stdin、またはon_data オプションキーに割り当てられたコールバック関数を使用して、チャネルアクティビティ(受信データ)に対応できます。コールバックは高速にする必要があります。潜在的に遅く/高価な作業は避けてください。
パラメータ
{chan-id} チャネルハンドル。 channel-id
{data} チャネルから読み取られた生のデータ(readfile() スタイルの文字列のリスト)。EOF は単一項目のリストです:['']。最初と最後の項目は不完全な行である可能性があります! channel-lines
{name} ストリーム名(文字列)。例:「stdout」。同じ関数が複数のストリームを処理できるようにするためです。イベント名は、チャネルが開かれた方法とモード/プロトコルによって異なります。
channel-buffered
データが利用可能になるとすぐにコールバックが呼び出されます。単一項目のリスト['']はEOF(ストリームのクローズ)を示します。または、stdout_bufferedstderr_bufferedstdin_buffered、またはdata_buffered オプションキーを設定して、すべての出力が収集され、ストリームが閉じられた後にのみコールバックを呼び出すこともできます。E5210
コールバックなしでバッファリングモードを使用する場合、データはオプションディクショナリのストリーム{name}キーに保存されます。キーが存在する場合はエラーとなります。
channel-lines
ストリームイベントハンドラーは、OS からデータが利用可能になるとデータを受け取ります。そのため、{data}リストの最初と最後の項目は不完全な行である可能性があります。空文字列は前の不完全な行を完了します。例(EOF で出力される最後の['']は含まれません)
foobar['fo'], ['obar']として到着する可能性があります。
foo\nbar は次のように到着する可能性があります。
['foo','bar']
または['foo',''], ['bar']
または['foo'], ['','bar']
または['fo'], ['o','bar']
これに対処する方法は2つあります。
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. 標準 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 ウィンドウでジョブの入力を入力する場合は、いくつかのオプションがあります。
通常のバッファを使用し、考えられるすべてのコマンドを自分で処理します。非常に多くのコマンドがあるため、これは複雑になります。
ターミナルウィンドウを使用します。入力した内容がジョブに直接送られ、ジョブの出力はウィンドウに直接表示される場合、これはうまく機能します。terminal を参照してください。
プロンプトバッファ付きのウィンドウを使用します。これは、Vim でジョブの行を入力し、ジョブからの出力(フィルタリングされている可能性あり)を表示する場合にうまく機能します。
'buftype' を "prompt" に設定することでプロンプトバッファが作成されます。通常は、新しく作成されたバッファでのみこれを行います。
ユーザーは、バッファの最後にある行でテキストの1行を入力して編集できます。プロンプト行でEnterキーを押すと、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 の例を次に示します。バックグラウンドでシェルを起動し、次のシェルコマンドをプロンプト表示します。シェルの出力はプロンプトの上に表示されます。
" 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
メイン
コマンドインデックス
クイックリファレンス