ruvim

RuVim 拡張 / Plugin メモ(現状)

この文書は、現状の RuVim で「拡張っぽいもの」をどう書くかをまとめたものです。

現時点では正式な plugin manager / plugin API はありません。
実用上は、init.rb / ftplugin/*.rb に Ruby DSL で拡張を書きます。

重要:

どこに書くか

何ができるか(DSL)

ConfigDSL で主に次を定義できます。

DSL メソッドリファレンス(現状)

nmap(seq, command_id=nil, desc: ..., **opts, &block)

nmap "H", "user.hello"
nmap "gH", "user.echo", kwargs: { text: "hi" }

nmap "K", desc: "Show buffer name" do |ctx, **|
  ctx.editor.echo(ctx.buffer.display_name)
end

block 版の内部動作:

imap(seq, command_id=nil, desc: ..., **opts, &block)

imap "jk", "ui.clear_message"

注記:

map_global(seq, command_id=nil, mode: :normal, desc: ..., **opts, &block)

map_global "Q", "app.quit", mode: :normal
map_global ["<C-w>", "x"], "window.focus_next", mode: nil

map_global "?", mode: :normal, desc: "Show file name" do |ctx, **|
  ctx.editor.echo(ctx.buffer.display_name)
end

command(id, desc: ..., &block)

command "user.show_path", desc: "Show current path" do |ctx, **|
  ctx.editor.echo(ctx.buffer.path || "[No Name]")
end

ex_command(name, desc: ..., aliases: [], nargs: :any, bang: false, &block)

ex_command "BufName", desc: "Show current buffer name", nargs: 0 do |ctx, argv:, kwargs:, bang:, count:|
  ctx.editor.echo(ctx.buffer.display_name)
end

ex_command_call(name, command_id, ...)

command "user.hello" do |ctx, **|
  ctx.editor.echo("hello")
end

ex_command_call "Hello", "user.hello"

set(option_expr), setlocal(option_expr), setglobal(option_expr)

set "number"
set "relativenumber"
setlocal "tabstop=2"
setglobal "tabstop=8"

最小例

# ~/.config/ruvim/init.rb

command "user.hello", desc: "Say hello" do |ctx, argv:, kwargs:, bang:, count:|
  ctx.editor.echo("hello x#{count}")
end

nmap "H", "user.hello"
ex_command_call "Hello", "user.hello", desc: "Run hello"

これで:

nmap はどこに登録される?

nmap は「global map」ではなく、Normal mode 用のマップ に登録されます。

キーマップ解決順(優先順)

  1. filetype-local
  2. buffer-local(内部 API はある。DSL は未整備)
  3. mode mapnmap, imap など)
  4. global mapmap_global(..., mode: nil)

同じキーが複数定義されている場合、上にあるものが優先されます。

補足:

commandex_command の違い

command

内部コマンド(command ID)を定義します。
主に keymap から呼ぶ対象です。

command "user.bufname" do |ctx, **|
  ctx.editor.echo(ctx.buffer.display_name)
end

ex_command

: で呼べる Ex コマンドを定義します。

ex_command "BufName", desc: "Show current buffer name", nargs: 0 do |ctx, argv:, kwargs:, bang:, count:|
  ctx.editor.echo(ctx.buffer.display_name)
end

ex_command_call(おすすめ)

既存の内部コマンドを Ex から呼びたい時の薄い中継です。

ex_command_call "Hello", "user.hello", desc: "Run hello"

filetype ごとの拡張例

# ~/.config/ruvim/ftplugin/ruby.rb

setlocal "tabstop=2"

command "ruby.say_ft" do |ctx, **|
  ctx.editor.echo("ruby ftplugin")
end

nmap "K", "ruby.say_ft"

この nmap "K", ... は Ruby バッファでのみ有効です。

実行ブロックの引数

command / ex_command の block は、基本的に次の形で受けると扱いやすいです。

do |ctx, argv:, kwargs:, bang:, count:|
  # ...
end

ctx API リファレンス(現状)

block に渡される ctxRuVim::Context です。

注意:

ctx.editor

推奨:

ctx.editor よく使う API(推奨)

状態参照:

表示 / 通知:

mode 操作:

window / tab 操作:

buffer 操作:

option:

register:

jump / mark:

search / find 状態:

quickfix / location list:

終了:

ctx.editor の高度 / 内部寄り API(使うときは注意)

これらは便利ですが、UI 内部の都合と結びついているものもあります。

よく使う例:

ctx.editor.echo("hello")
ctx.editor.echo_error("something wrong")

ctx.buffer

ctx.buffer.display_name
ctx.buffer.path
ctx.buffer.lines
ctx.buffer.modified?

注意:

ctx.buffer API リファレンス(用途別)

状態参照:

行アクセス:

編集(低レベル):

範囲の読み取り:

undo / redo:

ファイル I/O:

特殊バッファ:

plugin で編集する時の基本パターン(推奨):

buf = ctx.buffer
win = ctx.window

buf.begin_change_group
begin
  buf.insert_text(win.cursor_y, win.cursor_x, "hello")
  win.cursor_x += 5
ensure
  buf.end_change_group
end

ctx.window

row = ctx.window.cursor_y
col = ctx.window.cursor_x

ctx.window API リファレンス(用途別)

状態(attr):

移動 / 位置補正:

plugin では通常:

例:

ctx.window.cursor_y += 10
ctx.window.clamp_to_buffer(ctx.buffer)

ctx.invocation

block で使う引数(argv, kwargs, bang, count

block は ctx 以外にも keyword 引数を受け取れます。

command "user.demo" do |ctx, argv:, kwargs:, bang:, count:|
  ctx.editor.echo("argv=#{argv.inspect} kwargs=#{kwargs.inspect} bang=#{bang} count=#{count}")
end

どこまで plugin と呼べる?

現状は「設定ファイルに書く Ruby 拡張」です。

ただし、init.rb から require して自分のファイル群に分割すれば、実用的なローカル plugin 構成は作れます。

# ~/.config/ruvim/init.rb
require File.expand_path("plugins/my_tools", __dir__)

注意点