LoginSignup
32
19

More than 5 years have passed since last update.

neovimで非同期プラグインの作成

Posted at
1 / 11

vimで非同期処理がしたい

  • vim自体で別threadたてれない
  • vimproc + autocmdで頑張る
    • コールバックの管理辛い
  • job/channel
    • 知らない子ですね(良さそう)
  • remote plugin
    • 今日はこの話

Remote Plugin in neovim

  • neovimの新機能
  • プラグインを別プロセスで起動
    • プラグインのプロセスが死んでもneovimは無事!
  • msgpack-rpcで相互通信(非同期)
  • 原理的にどんな言語でも書ける!
    • if_pythonとかif_luaとかとおさらば!!

msgpack-RPC?

  • msgpack ~= JSONのバイナリ版
  • RPC = remote procedure call
    • 関数が外部に公開されているint func(int)
    • クライアントは関数呼び出しっぽく呼べるa = client.call("func", 2);
    • サーバー・クライアントが別言語でもmsgpackに変換さえできればOK
    • 最近はgRPCの方が流行りかも?
  • neovimとpluginの両方が双方向に送受信する

neovim server

  • "The Nvim C API is automatically exposed to the RPC API by the build system"
    • なんでもできる(/・ω・)/
  • 試しに呼んでみよう
    • NVIM_LISTEN_ADDRESS=127.0.0.1:6666 nvimで起動
    • (python clientがUNIX socketに対応してない)
    • neovim自体もmsgpack-RPCクライアントになる -> 別記事
import mprpc
c = mprpc.RPCClient("localhost", 6666)
_, info = c.call("vim_get_api_info")
for f in info["functions"]:
    print(f["name"], f["parameters"], f["return_type"])

呼べる関数の一覧が取得できた:

buffer_line_count (('Buffer', 'buffer'),) Integer
buffer_get_line (('Buffer', 'buffer'), ('Integer', 'index')) String
buffer_set_line (('Buffer', 'buffer'), ('Integer', 'index'), ('String', 'line')) void
buffer_del_line (('Buffer', 'buffer'), ('Integer', 'index')) void
buffer_get_line_slice (('Buffer', 'buffer'), ('Integer', 'start'), ('Integer', 'end'), ('Boolean', 'include_start'), ('Boolean', 'include_end')) ArrayOf(String)
buffer_get_lines (('Buffer', 'buffer'), ('Integer', 'start'), ('Integer', 'end'), ('Boolean', 'strict_indexing')) ArrayOf(String)
buffer_set_line_slice (('Buffer', 'buffer'), ('Integer', 'start'), ('Integer', 'end'), ('Boolean', 'include_start'), ('Boolean', 'include_end'), ('ArrayOf(String)', 'replacement')) void
buffer_set_lines (('Buffer', 'buffer'), ('Integer', 'start'), ('Integer', 'end'), ('Boolean', 'strict_indexing'), ('ArrayOf(String)', 'replacement')) void
buffer_get_var (('Buffer', 'buffer'), ('String', 'name')) Object
buffer_set_var (('Buffer', 'buffer'), ('String', 'name'), ('Object', 'value')) Object
buffer_del_var (('Buffer', 'buffer'), ('String', 'name')) Object
buffer_get_option (('Buffer', 'buffer'), ('String', 'name')) Object
buffer_set_option (('Buffer', 'buffer'), ('String', 'name'), ('Object', 'value')) void
buffer_get_number (('Buffer', 'buffer'),) Integer
buffer_get_name (('Buffer', 'buffer'),) String
buffer_set_name (('Buffer', 'buffer'), ('String', 'name')) void
buffer_is_valid (('Buffer', 'buffer'),) Boolean
buffer_insert (('Buffer', 'buffer'), ('Integer', 'lnum'), ('ArrayOf(String)', 'lines')) void
buffer_get_mark (('Buffer', 'buffer'), ('String', 'name')) ArrayOf(Integer, 2)
buffer_add_highlight (('Buffer', 'buffer'), ('Integer', 'src_id'), ('String', 'hl_group'), ('Integer', 'line'), ('Integer', 'col_start'), ('Integer', 'col_end')) Integer
buffer_clear_highlight (('Buffer', 'buffer'), ('Integer', 'src_id'), ('Integer', 'line_start'), ('Integer', 'line_end')) void
tabpage_get_windows (('Tabpage', 'tabpage'),) ArrayOf(Window)
tabpage_get_var (('Tabpage', 'tabpage'), ('String', 'name')) Object
tabpage_set_var (('Tabpage', 'tabpage'), ('String', 'name'), ('Object', 'value')) Object
tabpage_del_var (('Tabpage', 'tabpage'), ('String', 'name')) Object
tabpage_get_window (('Tabpage', 'tabpage'),) Window
tabpage_is_valid (('Tabpage', 'tabpage'),) Boolean
vim_command (('String', 'str'),) void
vim_feedkeys (('String', 'keys'), ('String', 'mode'), ('Boolean', 'escape_csi')) void
vim_input (('String', 'keys'),) Integer
vim_replace_termcodes (('String', 'str'), ('Boolean', 'from_part'), ('Boolean', 'do_lt'), ('Boolean', 'special')) String
vim_command_output (('String', 'str'),) String
vim_eval (('String', 'str'),) Object
vim_call_function (('String', 'fname'), ('Array', 'args')) Object
vim_strwidth (('String', 'str'),) Integer
vim_list_runtime_paths () ArrayOf(String)
vim_change_directory (('String', 'dir'),) void
vim_get_current_line () String
vim_set_current_line (('String', 'line'),) void
vim_del_current_line () void
vim_get_var (('String', 'name'),) Object
vim_set_var (('String', 'name'), ('Object', 'value')) Object
vim_del_var (('String', 'name'),) Object
vim_get_vvar (('String', 'name'),) Object
vim_get_option (('String', 'name'),) Object
vim_set_option (('String', 'name'), ('Object', 'value')) void
vim_out_write (('String', 'str'),) void
vim_err_write (('String', 'str'),) void
vim_report_error (('String', 'str'),) void
vim_get_buffers () ArrayOf(Buffer)
vim_get_current_buffer () Buffer
vim_set_current_buffer (('Buffer', 'buffer'),) void
vim_get_windows () ArrayOf(Window)
vim_get_current_window () Window
vim_set_current_window (('Window', 'window'),) void
vim_get_tabpages () ArrayOf(Tabpage)
vim_get_current_tabpage () Tabpage
vim_set_current_tabpage (('Tabpage', 'tabpage'),) void
vim_subscribe (('String', 'event'),) void
vim_unsubscribe (('String', 'event'),) void
vim_name_to_color (('String', 'name'),) Integer
vim_get_color_map () Dictionary
vim_get_api_info () Array
window_get_buffer (('Window', 'window'),) Buffer
window_get_cursor (('Window', 'window'),) ArrayOf(Integer, 2)
window_set_cursor (('Window', 'window'), ('ArrayOf(Integer, 2)', 'pos')) void
window_get_height (('Window', 'window'),) Integer
window_set_height (('Window', 'window'), ('Integer', 'height')) void
window_get_width (('Window', 'window'),) Integer
window_set_width (('Window', 'window'), ('Integer', 'width')) void
window_get_var (('Window', 'window'), ('String', 'name')) Object
window_set_var (('Window', 'window'), ('String', 'name'), ('Object', 'value')) Object
window_del_var (('Window', 'window'), ('String', 'name')) Object
window_get_option (('Window', 'window'), ('String', 'name')) Object
window_set_option (('Window', 'window'), ('String', 'name'), ('Object', 'value')) void
window_get_position (('Window', 'window'),) ArrayOf(Integer, 2)
window_get_tabpage (('Window', 'window'),) Tabpage
window_is_valid (('Window', 'window'),) Boolean

msgpack-RPCわからないと書けないの?

そんなことはない:各言語のSDKが開発されている


neovim/python-clientでプラグインを作る

ここからが本題。toggl.nvimを作ってみて(途中だけど)調べたことをまとめる。

  • $runtimepath/rplugin/python3/にファイルを置く
    • モジュールを置く場合空ファイルを置かないとうまく動かない
rplugin/python3/toggl/...
                toggl.py  # 空ファイル
  • document少なめ

    • deoplete.nvimを参考に
    • python-clientのソース見る(割と見やすい)
  • RPC関係は全部neovim/python-clientがやってくれる|ω・)


python-clientの使い方

toggl.nvimより抜粋

@neovim.plugin
class Toggl(object):

    def __init__(self, nvim):
        self.nvim = nvim
        self.api_token = nvim.eval("g:toggl_api_token")
        self.api = TogglAPI(self.api_token)

    @neovim.autocmd("VimEnter")
    def update(self):
        try:
            self.wid = self.api.workspaces()[0]["id"]
            self.projects = self.get_projects([])
        except ConnectionError:
            self.echo("No network, toggl.nvim is disabled.")

    def echo(self, msg):
        self.nvim.command("echo '{}'".format(msg))

    @neovim.function("TogglAPIToken", sync=True)
    def api_token(self, args):
        return self.api_token

    @neovim.function("TogglGetCurrent", sync=True)
    def get_current(self, args):
        return self.api.time_entries.current()

デコレータ付きでクラス+関数を定義するだけでvimプラグインが書ける

  • neovim.pluginをclassにつけるとnvimインスタンスがもらえる
  • neovim.autocmdでautocmdを発行できる
  • neovim.commandでvimのコマンドつくれる(引数は自動的にPythonの変数に変換される)
  • neovim.functionでvimの関数つくれる
  • プラグイン側は別プロセスなので自由に状態を持てる
  • 同期な関数(sync=True)と非同期な関数(sync=False, default)の両方を同じようにかける
  • Pythonのライブラリ使える(requests)とか

Pythonかけるけどvim scriptはちょっと…な人にオススメ!


最後に

  • Qiita slide簡単で良い
  • osaka.vim楽しい
32
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
19