GNU Global 是一套標籤程式,幫你記錄程式碼中函式等定義的座標——檔名、行號、引用位置。透過其 gtags
指令可以建立索引,然後透過其 global
指令查詢。
原本愛用的 Ctags 索引功能較陽春,雖支援較多程式語言,還完美配合 vim 內建快捷鍵,如: <ctrl-]>
)等,但缺少搜尋 Reference 功能,所以就改用 Gtags。
若在 VIM 中想要索引 Gtags 就必須額外安裝套件。例如 LeaderF 就是一套出色的套件,其模糊搜尋速度飛快,原本也用得好好的。但人生就是這個但是。換成 Shift-JIS 編碼桌面系統之後,預覽全部給我顯示問號!氣死。另一個類似的模糊查找套件 Telescope 就可以指定編碼,能正常顯示預覽。但 Telescope 內建不支援 Gtags 啊啊啊啊~~~(Orz
只好自搞。以下正文開始。
Telescope Plugin Development
好在 Telescope 強大且完善的外掛生態,讓我們不必重新製造模糊搜介面的輪子。只需專注於資料搜集,餵給 Telescope 的 Picker 即可。
基本資料夾結構
我們的目的是新增 Gtags 指令。也就是在 vim 中輸入:
:Telescope gtags<CR>
為此,須要的檔案配置如下:
./
└── lua/
├── telescope/
│ └── _extensions/
│ ├── gtags.lua (register a telescope extension)
│ └── ...
├── telescope-gtags/ (register a lua module)
│ └── init.lua (register lua setup function)
└── ...
其中:
lua/telescope-gtags/init.lua
讓我們能夠在 Lua script 中使用require("telescope-gtags")
lua/telescope/_extensions/gtags.lua
則是用來註冊 Telescope 外掛
寫一些測試內容,以便確認檔案配置 OK。
return {
setup = function(opts)
print("Lua OK!")
end,
}
local test = function(opts)
print("'Telescope gtags' is run!")
end
return require("telescope").register_extension {
setup = function(ext_config, cfg)
-- access extension config and user config
print("Telescope Ext Setup!")
end,
exports = {
gtags = module.run_symbols_picker,
},
}
加入 VIM 配置
假如使用 Lazy.vim:
{
"ukyouz/telescope-gtags",
dependencies = {
"nvim-telescope/telescope.nvim",
},
}
進入 VIM,在 Command mode 中輸入以下指令,確認 Telescope 有認到 Gtags
指令。
:Telescope gtags<CR>
這時候應該可以透過 :messages
依序看到以下輸出:
Lua OK!
Telescope Ext Setup!
'Telescope gtags' is run!
殼有了,接著把功能補齊即可!以下紀錄一些實作上遇到的問題及解法。
關於 Telescope 的 picker 使用方式,本文會使用到以下兩種:
- 執行 shell script 一次取得所有候選資料,餵給 picker。
- 隨使用者每次輸入,重複執行 shell script 取得候選資料。
第一種方法呼叫 finders.new_oneshot_job
,寫起來是這樣:
local pickers = require "telescope.pickers"
local finders = require "telescope.finders"
local args = {
"<your command>",
... -- arguments
}
pickers.new(opts, {
prompt_title = "xxx",
finder = finders.new_oneshot_job(args, opts),
}):find()
第二種方法使用 finders.new_job
, 寫起來是底下這樣。new_job
的第一個參數,會用來產出每次想要執行的 Shell Script 指令。
local pickers = require "telescope.pickers"
local finders = require "telescope.finders"
pickers.new(opts, {
prompt_title = "xxx",
finder = finders.new_job(function(prompt)
-- Get user input or command argument 'query'
-- Execute 'global <prompt> --result=ctags' command
return {"global", prompt}
end, opts.entry_maker, opts.max_results, opts.cwd),
}):find()
查找 Symbol
串接以下 Shell Script 指令。
$ global -i <query> --result=ctags
其中 -i
代表忽視大小寫,--result=ctags
代表以 Ctags 相容的格式輸出,這樣就可以串接 Telescope 內建的 Ctags Previewer。
不過,若直接拿使用者輸入作為 query 字串去查,會很難用。因為只有當輸入完全相符時,global 指令才找得到該 Tag。因此實作上有動一些手腳,將使用者輸入轉成正規表達式,以允許模糊查找。例如輸入 gud
就會變成 .*g.*u.*d.*
,這樣假如有個 function 名稱是 GetUserData
就找得到。轉換正規表達式的 Lua 程式如下:
-- convert prompt string 'gud' to chars ['g','u','d']
local chars = {}
prompt:gsub(".", function(c) table.insert(chars, c) end)
-- join chars with '.*' and add a trailing ".*"
-- so query become 'g.*u.*d.*'
local query = table.concat(chars, ".*") .. ".*"
跳轉References
串接以下 Shell Script 指令。
$ global -r <query> --result=grep
這邊改用 grep 格式輸出,是因為這樣可以預覽結果所在行。由於需要查找 References 的時候,通常 Tag 名稱已經確定,可以直接使用 VIM 內建的 <cword>
。
word = vim.fn.expand("<cword>")
接著只需要使用 new_oneshot_job
即可搞定。
跳轉 Definition
這裡串接的 global 和查找 Symbol 相同。只是由於這時已經確定 Tag 名稱,所以可以直接使用 VIM 內建的 <cword>。
這項功能困難的地方在於,通常 Definition 只有一個。此時就應該直接跳轉過去,而不是打開 picker 還要使用者再按一次 Enter。但假若真的遇到多重定義,還是想要保留 picker 功能。所以實作上就複雜了一點。
首先自己敲 globa 指令拿取 Definitions 列表。
-- get the word under cursor
local query = vim.fn.expand("<cword>")
-- run 'global <cword> --result=ctags' command and get the stdout as result
local handle = io.popen("global " .. query .. " --result=ctags")
local result = handle:read("*a")
handle:close()
-- loop line by line and insert each line to 'items'
local items = {}
for s in result:gmatch("[^\r\n]+") do
table.insert(items, s)
end
這樣就可以藉由判斷 items
數量,決定要直接跳轉還是開啟 picker 讓使用者選擇。
if #items == 1 and opts.jump_type ~= "never" then
-- directly jump the cursor to definition
...
else
-- open telescope picker for user to choose one
pickers.new(opts, {
prompt_title = "GTAGS Definitions - " .. query,
previewer = previewers.ctags.new(opts),
finder = finders.new_table {
results = items,
entry_maker = entry_maker,
},
}):find()
end
詳細實作建議直接看 Github 上 Repo,這裡只稍微解說概念。
外掛成果
很多實作其實也都是直接看 Telesope 原始碼照抄。總之終於搞定。歡迎大家安裝來玩看看。
ukyouz/syntax-highlighted-cursor.nvim