使用 Ruby 擴充 Vim

spacewander發表於2019-02-16

作為一款歷史悠久的編輯器,Vim 不僅支援用別具一格的 Vimscript 編寫外掛,還提供了 Python、Ruby、Lua 和 Perl 等語言對應的介面,甚至包括了對 Tcl 的支援,注意我說的是名為 Tcl 的程式語言,不是某家電品牌。通過這些語言,開發者可以擺脫 Vimscript 的限制,挑選自己最擅長的工具來擴充自己的編輯器。幾年前,我曾經心血來潮,學了一段時間 Vimscript,幫忙翻譯了《笨方法學Vimscript》一書。然而學到的知識如沙灘上的城堡,早已被時光之潮拍得支離破碎。所以現在要想寫點小效果,都會用 Vimscript 搭個腳手架,用其他語言實現具體的邏輯。考慮到 NeoVim 當前並不支援 Lua 和 Perl 等小眾語言,出於適用的目標,通常只會選擇用 Python 或者 Ruby 來實現。鑑於如何用 Ruby 擴充 Vim 的資料相對缺乏,我決定寫下本文,以供後來者參考。

前提

你所用的 Vim 可能不支援 Ruby 擴充。通過鍵入 :echo has(`ruby`),你可以瞭解 Ruby 擴充功能是否已啟用。幸運的是,從 Linux 包管理器上安裝的 Vim 預設是支援 Ruby 的。如果不支援,就只能自己重新編譯一份了。值得注意的是,NeoVim 還需要執行 gem install neovim 來下載對應的 Ruby Client。

Hello World

一切先從 Hello World 開始::ruby p `hello world`。你會看到一條 “hello world” 列印在編輯器下方。通常的做法是用 Vimscript 寫一個函式,在這個函式裡面呼叫 ruby 命令去執行 ruby 程式碼。像這樣:

function! Test()
ruby <<EOS
    p "hello world"
EOS
endfunction

command! Test :call Test()

這裡用到了 Vimscript 的 heredoc 語法,讓 ruby 命令執行一個多行的 Ruby 程式碼字串。最後一行把這個函式對映到 Test 命令上,這樣就能通過 :Test 的方式呼叫它。

如果要寫的 Ruby 程式碼比較多,推薦放到一個獨立的檔案裡面,然後再從 ruby 命令裡面 require 進來。記得處理下 ruby 檔案載入的路徑。

Vim 跟 Ruby 相關的 API 文件可以通過 vert help ruby 看到,整篇說明也不過一兩百行。功能是少了點,不過日常寫點小玩意,代替成段 Vimscript 還是能做到的。

IO

要想寫出超越 Hello World 的程式碼,不能不瞭解 Vim 提供的輸入輸出 API。
Vim 暴露在 Ruby 程式碼裡的 API,都在 Vim 這個模組下面。

有兩種方式可以獲取當前 Vim 狀態(輸入 API):

  1. 通過 Vim::evaluate(expr) 的方式執行任意 Vimscript 表示式並獲得其結果。這種方法用於獲取 Vim 變數,比如 :ruby p Vim::evaluate(g:maplocalleader)
  2. 通過 Vim::BufferVim::Window 兩個子模組,獲取 Buffer 或 Window 的各種狀態。比如 :ruby p Vim::Buffer.current[1] 會返回第一行的內容。可惜的是,沒有 Tab 模組。

對應有兩種方法可以修改當前 Vim 狀態(輸出 API):

  1. 通過 Vim::command(cmd) 的方式執行任意命令。其效果等同於 :cmd。比如 :ruby Vim::command(`set paste`) 其實就是 :set paste 的意思。
  2. 通過 Vim::BufferVim::Window 去設定 Buffer 或 Window 的狀態。比如 :ruby Vim::Buffer.current[1] = `ruby evaluation` 會把第一行變成 ruby evaluation

More

如果你對用 Ruby 擴充 Vim 感興趣,而又恰好使用 NeoVim,可以看下這個專案:https://github.com/alexgenco/…
這個專案提供了我們前面安裝的 neovim gem。除了 Vim 模組,這個 gem 還在 NeoVim 模組下面放了更多的 API。如果你在寫的 Ruby 擴充需要更多的 API,可以考慮給這個 gem 貢獻程式碼。當然,額外引入的新功能就不會相容原生 Vim 了。

相關文章