在影片中看到安東尼哥使用的文字編輯器,游標背景色竟然會隨著 Syntax Highlight 變化!覺得超酷,Google 了一下貌似沒有類似的外掛,於是乎——效仿安東尼哥自己寫了一套文字編輯器 babi ——就自己寫了。
順便寫個簡單版 Neovim 的 Lua 語言外掛開發教學。
Lua 是啥?
Lua 可以想成是一種超輕量 Python,也是直譯式語言,用來寫一些簡單腳本。因為 Vimscript 語法不慎友善(絕對不是我懶得學),所以 Neovim 開始引入 Lua 支援。
來介紹一些重點 Lua 語法。
Table(陣列、字典)
Lua 只有 Table。陣列(Array)、字典都是用 Table 實現,差別在於 Key 是否存在。
字典
-- example of dictionary
local foo = {
bar = 1, -- usually without quotes, but you can if you want
["key with space"] = 2 -- if key contains space, use ["xxx"] syntax
}
print(foo.bar) -- 1, you can also use foo['bar']
print(foo["key with space"]) -- 2, in this case you can only get value with []
留意若 Key 包含空白,需要額外的中括號跟引號。
陣列(Array)
-- example of list
local foo = {1, 2, "123"}
print(foo[1]) -- 1, yes! index starts at 1
print(foo[#foo]) -- "123", the way you get size of array is by prefixing #
你可以大雜燴,放入各種型別的內容。特別注意第一個 Index 從 1 開始。井字號返回陣列大小。
其他詳細 Syntax 可以參考 Lua 語法教學。
開發流程
初步了解 Lua 之後就可以開始開發囉!
開發流程——我預設你有使用 Plugin 管理外掛——大致為:
- 在你的 Neovim 的 Plugin Directory 中新增資料夾,以收集所有程式碼。
- 放入空白 Lua 檔案。
- 這個檔案我是會塞入一個
print("hi")
以確保外掛有掛上。
- 這個檔案我是會塞入一個
- 確認你的 Plugin 管理外掛設定有載入你的外掛。
- 啟動 Neovim,檢查功能。
- 此時我就可以確認是否有印出
hi
- 此時我就可以確認是否有印出
- 修改新外掛原始碼,重複步驟 4。直到滿意。
空白 Lua 外掛
一個最簡單的 Lua 外掛資料夾結構如下:
./
└── lua/
└── module-name/
└── init.lua
特別留意 module-name 資料夾名稱,這決定了你要用什麼名稱來引入此 Lua 模組。
現在在 init.lua
中加入以下基本框架。
local function _setup(params)
print(1) -- test code to see if plugin is successfully loaded
end
return {
setup = _setup,
}
Neovim 慣例上使用 setup
函式做外掛初始化。
與 Vim 互通有無
Neovim 提供 vim
模組,不用呼叫 require
內建就有引入,讓你可以使用 Lua 語法來操控 Neovim 功能。如果你想在 Neovim 視窗中嘗試,可以在 Normal Mode 下鍵入 :lua vim.cmd.echo(123)
。你就可以用 Lua 呼叫 Vim 的 echo
指令來印出 123
。
回到我們的外掛。如果你想印出 Table 的內容,請善用 vim.inspect(foo)
。關於 Vim 模組的用法,可以參考〈在 neovim 中使用 Lua〉這篇文章。
Syntax Highlighted Cursor
由於安東尼哥的編輯器是自己寫的,想怎麼寫都可以。但我不是 Neovim 核心開發者,所以最簡單又快速的方式就是外掛套件上去。實現步驟如下:
- 偵測目前游標所在位置 Highlight Group
- 取得該 Highlight Group 的顏色值
- 將游標背景顏色改為 Highlight Group 的前景色
- 游標移動時,重複步驟 1
大部分功能只要 Google 就可以查到方法,以下簡單介紹。
取得 Cursor 底下的 Highlight Group
local posInfo = vim.inspect_pos() -- get position info at current cursor
local syntax_groups = posInfo.syntax
local treesitter_groups = posInfo.treesitter
Highlight Group 返回陣列,表示所有符合的樣式,若返回 nil
則表示沒有該規則下符合的 Highlight。而 Neovim 會使用最後一個來決定其顏色。
取得 Highlight Group 顏色
local cursorHi = vim.api.nvim_command_output("hi Cursor")
這步就是呼叫 Vim 原生語法,獲取 Cursor 這個 Group 的顏色設定,並將輸出字串存至變數 cursorHi
。
修改游標顏色
這裡使用 guicursor
設定。MacOS 內建的 Terminal 不知道為何改不了顏色。我另外安裝的 nvim-qt 就可以,所以才有本篇文章,不然可能就會永遠以為不能改顏色 XD
先設定 guicursor
使用 Cursor 這個 Highlight Group。(這個設定我是用 Vimscript 直接寫的。當然也可以呼叫 vim.cmd
寫在 Lua 那邊)
set guicursor=n-v-c:block-Cursor-blinkon0
set guicursor+=i:ver30-Cursor
再修改 Cursor 這個 Highlight Group 的顏色。這裡使用 Vim API,第一個參數 0
代表目前的 Buffer。
vim.api.nvim_set_hl(0, "Cursor", { fg=colors.guibg, bg=colors.guifg, })
游標移動時更新
這功能使用 Vim 的 Auto Command 來實現。我包了一個函式 augroup
,以幫你產生 Auto Command Group。
function augroup (group_name, autocmds)
-- autocmds = list of {
-- events = {},
-- opts = {
-- pattern = {"*"},
-- desc = "",
-- command = "",
-- },
-- }
local group = vim.api.nvim_create_augroup(group_name, {
clear = true,
})
for _, autocmd in pairs(autocmds) do
local opts = autocmd["opts"]
opts["group"] = group
vim.api.nvim_create_autocmd(autocmd["events"], opts)
end
end
這樣就可以呼叫 augroup
來新增 Auto Command。每當游標移動後就執行 callback
。
augroup("SyntaxColorCursor", {
{
events = {"CursorMoved"},
opts = {
pattern = {"*"},
desc = "SyntaxColorCursor",
callback = function()
if updapte_cursor_color() then
if not vim.o.modifiable or vim.o.readonly then
return
end
-- HACK: to update cursor color immediately
-- just go to insert mode than back to normal mode
vim.api.nvim_feedkeys(t'a', 'm', false)
vim.api.nvim_feedkeys(t'<esc>','m', false)
end
end,
},
},
})
最 Hack 的地方來了。在我呼叫 updapte_cursor_color
改完 Cursor 顏色之後,做了一件神秘的事。
因為我發現改完 Cursor 顏色之後不會馬上套用,GUI 應該是有快取,所以還是沿用原本顏色。但不能馬上更新的話體驗就很差。推測要想辦法觸發 GUI 重新繪製——至少游標那小塊——才可以。網路上查不太到,試過 redraw
、updatetime
等等都無法解決此問題。直到我發現按下冒號進入 Command 模式、或切換 Insert Mode 之後再次回到 Normal Mode。賓果!顏色就更新了。
由於最終還是找不到單獨觸發重新繪製游標的 API,於是乎,就決定 Hack 了。一開始嘗試透過 API 模擬按下 :
進入命令列的方法,發現游標跳來跳去一度放棄了改用按下按鍵 a
及 <Esc>
來瞬間進入 Insert Mode 一下再回到 Normal Mode。後來發現這種方式會讓游標很不穩定,和其他套件的相容性很差。最終還是改成進入命令列的方式,但加上了 silent
關鍵字,以避免游標跳動。搞定!但畢竟頻繁進出命令列還是怪怪的,也會影響其他外掛使用。所以額外加入許多條件排除觸發更新的時機。
雖然寫法多少有點 Hack,但成果出奇讚。歡迎大家安裝來玩看看。
ukyouz/syntax-highlighted-cursor.nvim
References