この文書は、現状の RuVim で「拡張っぽいもの」をどう書くかをまとめたものです。
現時点では正式な plugin manager / plugin API はありません。
実用上は、init.rb / ftplugin/*.rb に Ruby DSL で拡張を書きます。
重要:
ctx API は、現状まだ未確定です$XDG_CONFIG_HOME/ruvim/init.rb~/.config/ruvim/init.rb$XDG_CONFIG_HOME/ruvim/ftplugin/<filetype>.rb~/.config/ruvim/ftplugin/<filetype>.rbConfigDSL で主に次を定義できます。
nmap(seq, command_id=nil, ..., &block)imap(seq, command_id=nil, ..., &block)map_global(seq, command_id=nil, mode: ..., ..., &block)command(id, &block)(内部コマンド)ex_command(name, &block)(Ex コマンド)ex_command_call(name, command_id, ...)(Ex -> 内部コマンドの中継)set, setlocal, setglobalnmap(seq, command_id=nil, desc: ..., **opts, &block)command_id 指定版init.rb では mode map (:normal)ftplugin/*.rb では filetype-local normal mapseq: キー列(例: "H", "gh")command_id: 内部コマンドID(例: "user.hello")desc: block 版で生成される内部コマンドの説明opts: argv:, kwargs:, bang: を指定可能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 版の内部動作:
CommandRegistry に登録imap(seq, command_id=nil, desc: ..., **opts, &block)init.rb では mode map (:insert)ftplugin/*.rb では filetype-local insert mapimap "jk", "ui.clear_message"
注記:
map_global(seq, command_id=nil, mode: :normal, desc: ..., **opts, &block)mode: nil で最下位フォールバック map を定義mode: を指定した場合: その mode の mapmode: nil の場合: global map(最下位フォールバック)nmap / imap を使う方が分かりやすいmap_global は「複数 mode で共有したい」「フォールバックを置きたい」場合向け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)RuVim::CommandRegistry(source=:user)ex_command_call の呼び先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):Name)を登録RuVim::ExCommandRegistry(source=:user)aliases, nargs, bang を指定できるnargs:
0, 1, :maybe_one, :anyex_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, ...)ex_command を作り、CommandRegistry の 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)option_expr の形式(現状):
"number"(ON)"nonumber"(OFF)"tabstop=4"(値設定)set: option 定義に応じて自動(global / buffer / window)setlocal: local 側(buffer または window)setglobal: global 側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"
これで:
H -> user.hello:Hello -> user.hellonmap はどこに登録される?nmap は「global map」ではなく、Normal mode 用のマップ に登録されます。
init.rb での nmap
ftplugin/*.rb での nmap
filetype-local Normal mapfiletype-localbuffer-local(内部 API はある。DSL は未整備)mode map(nmap, imap など)global map(map_global(..., mode: nil))同じキーが複数定義されている場合、上にあるものが優先されます。
補足:
map_global(..., mode: nil) は最後に評価されるため、普段の設定では出番は少なめです。nmap / imap で定義し、必要なときだけ map_global を使うのがおすすめです。command と ex_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"
# ~/.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.editorctx.bufferctx.windowargv(Ex 引数)kwargs(キーマップや内部呼び出しの named args)bang(Ex の !)count(Normal mode の count)ctx API リファレンス(現状)block に渡される ctx は RuVim::Context です。
注意:
ctx API リファレンスは「現時点で使えるもの」の記録ですctx.editorRuVim::Editorecho, echo_error)推奨:
ctx.editor, ctx.buffer, ctx.window を使うEditor の public メソッドは多いが、すべてが高水準 API とは限らないctx.editor よく使う API(推奨)状態参照:
current_buffer -> RuVim::Buffercurrent_window -> RuVim::Windowmode / mode=running?message, message_error?表示 / 通知:
echo(text) : 通常メッセージ表示echo_error(text) : エラーメッセージ表示(下段・強調)clear_messagemode 操作:
enter_normal_modeenter_insert_modeenter_visual(mode) (:visual_char, :visual_line, :visual_block)enter_command_line_mode(prefix) (":", "/", "?")cancel_command_linewindow / tab 操作:
split_current_window(layout: :horizontal|:vertical)close_current_windowfocus_next_window, focus_prev_windowfocus_window_direction(:left|:right|:up|:down)tabnew(path: nil), tabnext(step=1), tabprev(step=1)buffer 操作:
switch_to_buffer(buffer_id)open_path(path)buffers({id => Buffer})buffer_idsalternate_buffer_idoption:
effective_option(name, window: ..., buffer: ...)set_option(name, value, scope: :auto|:global|:buffer|:window)get_option(name, window: ..., buffer: ...)global_optionsregister:
get_register(name="\"")set_register(name="\"", text:, type: :charwise|:linewise)store_operator_register(name="\"", text:, type:, kind: :yank|:delete|:change)set_active_register(name), active_register_name, consume_active_registerjump / mark:
set_mark(name)mark_location(name, ...)push_jump_location(location=nil)jump_to_mark(name, linewise: false)jump_to_location(loc, linewise: false)jump_older(linewise: false), jump_newer(linewise: false)search / find 状態:
last_search, set_last_search(pattern:, direction:)last_find, set_last_find(char:, direction:, till:)quickfix / location list:
set_quickfix_list(items), quickfix_items, quickfix_index, current_quickfix_item, move_quickfix(step)set_location_list(items, window_id: ...), location_items(window_id), current_location_list_item(window_id), move_location_list(step, window_id: ...)終了:
request_quit!ctx.editor の高度 / 内部寄り API(使うときは注意)add_empty_buffer, add_buffer_from_file, add_virtual_bufferadd_window, close_window(id), focus_window(id)show_help_buffer!, show_intro_buffer_if_applicable!, materialize_intro_buffer!text_viewport_size, window_order, windows, tabpagesoption_def, option_default_scope, option_snapshotこれらは便利ですが、UI 内部の都合と結びついているものもあります。
よく使う例:
ctx.editor.echo("hello")
ctx.editor.echo_error("something wrong")
ctx.bufferRuVim::Bufferctx.buffer.display_name
ctx.buffer.path
ctx.buffer.lines
ctx.buffer.modified?
注意:
ctx.buffer を直接編集する場合は readonly / modifiable 制約に注意ctx.buffer API リファレンス(用途別)状態参照:
idpath, path=display_namekind(:file, :help, :intro, :quickfix, :location_list など)nameoptions(buffer-local option ストレージ)modified?, modified=readonly?, readonly=modifiable?, modifiable=file_buffer?, virtual_buffer?, intro_buffer?行アクセス:
lines(配列そのもの)line_countline_at(row)line_length(row)編集(低レベル):
insert_char(row, col, char)insert_text(row, col, text) -> [row, col]insert_newline(row, col) -> [row, col]backspace(row, col) -> [row, col]delete_char(row, col) -> true/falsedelete_line(row) -> deleted_linedelete_span(start_row, start_col, end_row, end_col) -> true/falseinsert_lines_at(index, new_lines)replace_all_lines!(new_lines)範囲の読み取り:
span_text(start_row, start_col, end_row, end_col) : charwise 範囲文字列line_block_text(start_row, count=1) : linewise 範囲文字列(末尾 \n 付き)undo / redo:
begin_change_groupend_change_groupcan_undo?, can_redo?undo!, redo!ファイル I/O:
write_to(path=nil) : 保存reload_from_file!(path=nil) : 再読込(undo/redo クリア)特殊バッファ:
configure_special!(kind:, name: nil, readonly: true, modifiable: false)become_normal_empty_buffer!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.windowRuVim::Windowrow = ctx.window.cursor_y
col = ctx.window.cursor_x
ctx.window API リファレンス(用途別)状態(attr):
idbuffer_id, buffer_id=cursor_x, cursor_x=cursor_y, cursor_y=row_offset, row_offset=col_offset, col_offset=options(window-local option ストレージ)移動 / 位置補正:
clamp_to_buffer(buffer) : カーソルをバッファ範囲に収めるmove_left(buffer, count=1) : grapheme 境界を考慮して左移動move_right(buffer, count=1) : grapheme 境界を考慮して右移動move_up(buffer, count=1)move_down(buffer, count=1)ensure_visible(buffer, height:, width:, tabstop: 2) : スクロール位置を調整plugin では通常:
cursor_x, cursor_y を更新clamp_to_buffer(ctx.buffer) を呼ぶ例:
ctx.window.cursor_y += 10
ctx.window.clamp_to_buffer(ctx.buffer)
ctx.invocationRuVim::CommandInvocation または nilctx.invocation&.countctx.invocation&.bangargv, 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
argv
kwargs
bang
! 付きで呼ばれたかどうかcount
1)現状は「設定ファイルに書く Ruby 拡張」です。
ただし、init.rb から require して自分のファイル群に分割すれば、実用的なローカル plugin 構成は作れます。
# ~/.config/ruvim/init.rb
require File.expand_path("plugins/my_tools", __dir__)