-
Notifications
You must be signed in to change notification settings - Fork 27
5. 插件开发(plugin develop)
VimL 虽然已经支持异步,但它的异步很弱,容易卡死,所以在触发 complete() 的时机上,选择使用 vim 自带的事件队列,插件的注册都是基于事件来完成,而不是直接函数调用。
为了尽可能保持简单,插件没有基于 RPC,高计算量的部分尽量用 lua 来做,在兼容性基础上也保障了基本性能上的体验。
为了避免过于频繁的lsp调用,匹配逻辑做了区分。分为首次匹配(TextChangedI),和菜单匹配(TextChangedP),lsp 请求只出现在首次匹配。
源的调用顺序为:path(同步)→lsp(异步)→Tabnine(异步)→snip(同步)→buf(同步)→dict(同步)。
所有内置插件都在 sources 目录下。 插件包含几个关键方法(以 go 为例):
-
easycomplete#sources#go#constructor:插件初始化,这里主要是注册 lsp server。 -
easycomplete#sources#go#completor:补全主方法 -
easycomplete#sources#go#GotoDefinition:跳转到定义 -
easycomplete#sources#go#filter:对补全的返回结果做一层过滤(可留空)
一个插件只需要最少实现前三个方法即可。前三个方法重点是第一个 constructor 方法,用作初始化 lsp server,补全方法和跳转到定义通常情况下不同语言不会有差异,直接调用系统默认的方法即可,但如果 lsp 的实现不够标准,则需要重写这两个方法。
实现了插件主体之后,需要将插件注册,注册方法是监听easycomplete_default_plugin事件:
au User easycomplete_default_plugin call easycomplete#RegisterSource({
\ 'name': 'go',
\ 'whitelist': easycomplete#FileTypes("go", ["go"]),
\ 'completor': 'easycomplete#sources#go#completor',
\ 'constructor' :'easycomplete#sources#go#constructor',
\ 'gotodefinition': 'easycomplete#sources#go#GotoDefinition',
\ "root_uri_patterns": [
\ "go.mod",
\ ],
\ 'command': 'gopls'
\ })最后是 lsp 服务的安装脚本,安装脚本在 installer 目录下,保存为和插件同名的脚本。
当然也可以不提供 lsp 的安装脚本,可以通过 mason.nvim 来安装管理 lsp 服务。
同样,lua 形式的插件也需要同样的方式注册(以 easycomplete-docker 为例):
文件结构:
.
├── after
│ └── plugin
│ └── init.lua
└─── lua
└── easycomplete_docker.lua
注册插件:
-- lua/easycomplete_docker.lua
easycomplete.register_source({
name = "docker",
whitelist = {"docker", "dockerfile"},
completor = require('easycomplete_docker').completor,
constructor = require('easycomplete_docker').constructor,
gotodefinition = require('easycomplete_docker').goto_definition,
filter = require('easycomplete_docker').filter,
command = "docker-langserver"
})插件注册完成后同样需要定义四个函数,可以是任意位置,我这里就放一起了:
require('easycomplete_docker').completorrequire('easycomplete_docker').constructorrequire('easycomplete_docker').goto_definitionrequire('easycomplete_docker').filter
然后在启动脚本中绑定easycomplete_default_plugin事件来完成插件的注册。
-- after/plugin/init.lua
vim.api.nvim_create_autocmd("User", {
pattern = "easycomplete_default_plugin",
callback = function()
require("easycomplete_docker").setup()
end,
})lsp 服务 docker-langserver 通过 mason 来安装即可。
local esy_cmp = require("easycomplete")
插件生命周期相关的方法
配置函数,配置方法参照这里
其中:
require("easycomplete").config({nerd_font = 1})
等价于
vim.g.easycomplete_nerd_font = 1
可以通过 config() 中配置 setup 函数,来写其他的绑定map等逻辑:
local esy_cmp = require("easycomplete")
esy_cmp.config({
nerd_font = 1,
setup = function()
-- 初始化的代码
end
})初始化代码,可以跟 config() 分开来使用,这段代码和上面的例子功能一样:
local esy_cmp = require("easycomplete")
esy_cmp.setup(function()
vim.g.easycomplete_nerd_font = 1
-- 其他初始化代码
end)fuzzy 比对,比对成功返回 true,比对失败返回 false。
vim.fn.matchfuzzy 的重新实现,只返回结果,不返回分数
注册插件,参照上文“插件结构”
注册lsp server,参照上文“插件结构”
根据 传入的 plugin_name 返回 lsp server 的命令。
获得vim-easycomplete插件的根目录。
执行lsp的complete查询,返回 nil。
执行goto跳转到定义处,返回 nil
常用的工具函数
参数是一个匹配选项实例,返回这个匹配项来自哪个源,返回源的名字 plugin_name,比如返回 "vim"、"py"、"go" 等
获得当前光标处正在敲入的单词
根据当前正在输入的单词,获得当前 缓冲区中的匹配的单词列表
检查 noice.nvim 是否安装并启用了,返回 true 或者 false
把一个字符串的前缀空格去掉,返回去掉前缀空格的字符串。
打印日志,返回值 nil,日志位置在~/.config/vim-easycomplete/debuglog,通过tail -f ~/.config/vim-easycomplete/debuglog 来查看日志输出
把超长的匹配词结果根据长度限制进行截断,返回截断的字符串,比如 abcdefghijklmn → abcdefghij...。长度限制取自vim.g.easycomplete_pum_maxlength。
zizz() 执行一个 30ms 的定时器,返回 nil,zizzing()检查这个定时器还在的话就返回 true,否则返回 false
类似 vim.fn.filter 的过滤函数,t 为待过滤的表,func 是判定函数,返回过滤完成后的表。
表的去重,items 是字符串组成的表{"abc","def","ghi",...},返回去重后的表。
用 word 过滤补全的表 matching_res,返回过滤好的表,表元素同时完成了 fuzzy 匹配结果的位置标记。
判断给定的 item 是否是 tn 类型,是 返回 true,否 返回 false。
获得当前 lsp 所在插件的上下文。
获得当前 lsp 服务的名称
获得当前 lsp 插件安装的根目录。
获得当前文件所使用的 lsp 源插件的名字。
补全窗口(菜单)相关的函数
补全函数,参数和功能同 vim 原生 complete() 函数
关闭补全窗口
获得补全窗口关联的 buffer id
获得补全窗口的窗口id
判断补全窗口是否可见
判断补全窗口是否有选中项,返回 true 或者 false
获得当前选中的补全项
选中补全窗口中的某一项,index 从 1 开始计数
补全窗口选中下一个
补全窗口选中上一个
获得当前位置可匹配出的目录和文件列表
自定义插件 lsp 的安装建议用 mason。