之前学习了如何写 Lua 程序,又因为 NeoVim 的插件可以用 Lua 写,所以想学习一下插件开发。

插件目录结构

我们的插件应当有至少两个目录:plugin用于放main代码文件,lua放其余的所有代码。当然,如果我们真的想把所有代码放在同一个文件里面,这也是可以的。但是请不要这样。因此,使用plugin/whid.vimlua/whid.lua这两个文件就可以了。

" plugin/whid.vim 文件
if exists('g:loaded_whid') | finish | endif " 防止插件加载两次

let s:save_cpo = &cpo " 保存用户自定义选项
set cpo&vim " 重置到默认选项

" 运行插件
command! Whid lua require'whid'.whid()

let &cpo = s:save_cpo " 恢复用户自定义选项
unlet s:save_cpo

let g:loaded_whid = 1

let s:save_cpo = &cpo是防止自定义选项干扰插件的常见做法。依据我们要编写的代码,缺少这一行可能不会有什么坏处,但是这是一种好的习惯(至少根据 Vim 帮助文件是这样)。command! Whid lua require 'whid'.whid()需要插件的 Lua 模块并调用它的主函数。

浮动窗口

NeoVim 和如今的新版 Vim 都有浮动窗口的特性。

-- lua/whid.lua 文件

local api = vim.api
local buf, win

local function open_window()
  buf = api.nvim_create_buf(false, true) -- 创建一个空缓冲区

  api.nvim_buf_set_option(buf, 'bufhidden', 'wipe')

  -- 获取窗口的行和列
  local width = api.nvim_get_option("columns")
  local height = api.nvim_get_option("lines")

  -- 计算出浮动窗口的行和列
  local win_height = math.ceil(height * 0.8 - 4)
  local win_width = math.ceil(width * 0.8)

  -- 计算出浮动窗口的开始位置
  local row = math.ceil((height - win_height) / 2 - 1)
  local col = math.ceil((width - win_width) / 2)

  -- 设置选项
  local opts = {
    style = "minimal",
    relative = "editor",
    width = win_width,
    height = win_height,
    row = row,
    col = col
  }

  -- 最后在一个附加的缓冲区创建浮动窗口
  win = api.nvim_open_win(buf, true, opts)
end

nvim_create_buf函数用于创建一个缓冲区,第一个参数是listed第二个参数是scratch,分别被设置成falsetrue,创建了一个未列出的缓冲区,并且是 throwaway 的(隐藏时将会被删除)。

使用nvim_open_win可以创建一个新窗口同时也可以将之前创建的buf绑定其上,第二个bool类型参数表示这个窗口是否获得焦点。第三个参数opt是一个表,其中元素widthheight的作用不言自明;rowcol是窗口的开始位置(编辑器的左上角开始算)。relative="editor"style="minimal"是配置窗口外观的选项,这样设置可以禁用一些不需要的选项,例如行号和拼写错误提示。

现在我们创建了一个浮动窗口,但是我们可以把它变得更加好看。NeoVim 当前还不支持边框等小部件,但是我们可以自己做出来。这其实相当简单,我们可以弄一个新的窗口,比原来的窗口稍微大一圈,并放置在底部。

local border_opts = {
  style = "minimal",
  relative = "editor",
  width = win_width + 2,
  height = win_height + 2,
  row = row - 1,
  col = col - 1
}

我们可以用边框字符让它看起来更好看。

local border_buf = api.nvim_create_buf(false, true)

local border_lines = { '╔' .. string.rep('═', win_width) .. '╗' }
local middle_line = '║' .. string.rep(' ', win_width) .. '║'
for i=1, win_height do
  table.insert(border_lines, middle_line)
end
table.insert(border_lines, '╚' .. string.rep('═', win_width) .. '╝')

api.nvim_buf_set_lines(border_buf, 0, -1, false, border_lines)
-- 函数原型为 nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing}, {replacement})
-- 设置缓冲区(border_buf)填充范围为第一行(0)到最后一行(-1)
-- 忽略越界错误(false)并填充(border_lines)

显然我们必须按照正确的顺序打开窗口,并且一起关闭窗口。当前最好的解决办法是使用viml autocomand

local border_win = api.nvim_open_win(border_buf, true, border_opts)
win = api.nvim_open_win(buf, true, opts)
api.nvim_command('au BufWipeout <buffer> exe "silent bwipeout! "'..border_buf)

获取数据

我们的插件用于展示我们处理过的最新文件。可以使用 git 命令达到这样的效果。

git diff-tree --no-commit-id -name-only -r HEAD

创建函数将数据插入到窗口之中。

用户输入

公共函数

完整代码

参考