作為一款歷史悠久的編輯器,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):
- 通過
Vim::evaluate(expr)
的方式執行任意 Vimscript 表示式並獲得其結果。這種方法用於獲取 Vim 變數,比如:ruby p Vim::evaluate(g:maplocalleader)
。 - 通過
Vim::Buffer
和Vim::Window
兩個子模組,獲取 Buffer 或 Window 的各種狀態。比如:ruby p Vim::Buffer.current[1]
會返回第一行的內容。可惜的是,沒有 Tab 模組。
對應有兩種方法可以修改當前 Vim 狀態(輸出 API):
- 通過
Vim::command(cmd)
的方式執行任意命令。其效果等同於:cmd
。比如:ruby Vim::command(`set paste`)
其實就是:set paste
的意思。 - 通過
Vim::Buffer
和Vim::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 了。