🌙

Neovim の Lua で Vim script 製プラグインの設定をする

2023/06/10に公開

はじめに

lua から vim の関数を実行する方法として、vim.fn を使う方法があります。
例えば ddu.vim の設定を vim script で次のように行っているとします。

call ddu#custom#patch_global({
    \   'uiParams': {
    \     'ff': {
    \       'startFilter': v:true,
    \     },
    \   },
    \ })

autocmd FileType ddu-ff call s:ddu_my_settings()
function! s:ddu_my_settings() abort
  nnoremap <buffer><silent> <CR>
        \ <Cmd>call ddu#ui#ff#do_action('itemAction')<CR>
endfunction

これを lua に書き換えたかった場合、次のようにすることができます。

vim.fn["ddu#custom#patch_global"] {
    uiParams = {
        ff = {
	    startFilter = true,
	},
    },
}

local function ddu_my_settings()
    local map = vim.keymap.set
    map("n", "<CR>", function()
        vim.fn["ddu#ui#ff#do_action"] "itemAction"
    end, { buffer = true })
end

vim.api.nvim_create_autocmd("FileType", {
    pattern = "ddu-ff",
    callback = ddu_my_settings,
})

問題点

ただの慣れの問題だとは思いますが、少し読みづらくないですか?

\ が無いのは良いものの、ぱっと見では ddu#custom#patch_globalddu#ui#ff#do_action が関数なのかが分かりづらいですし、 "# を打つのも面倒です。

また、vim.keymap.set の引数にあるコールバック関数が Lua の関数で書くとかなり冗長になってしまっていて、短くするために vim script と同じように書くこともできるものの、 やはり出来れば Lua っぽく書きたいです。

解決策

と言う訳で、以下の関数を作ってみました。

local function call(module_name)
  local metatable = { func = module_name, children = {} }
  function metatable:__index(key)
    local meta = getmetatable(self)
    local child = call(meta.func .. '#' .. key)
    meta.children[key] = child
    setmetatable(self, meta)
    return child
  end
  function metatable:__call(...)
    local func_name = getmetatable(self).func ---@type string
    if func_name:match '.+#_fn$' then
      local args = { ... }
      return function()
        return vim.fn[func_name:gsub('#_fn$', '', 1)](unpack(args))
      end
    else
      return vim.fn[func_name](...)
    end
  end
  return setmetatable({}, metatable)
end

これを使うことで、上記の例を以下のように書き換えることができます。

local ddu = call "ddu"

ddu.custom.patch_global {
    uiParams = {
        ff = {
	    startFilter = true,
	},
    },
}

local function ddu_my_settings()
    local map = vim.keymap.set
    map("n", "<CR>", ddu.ui.ff.do_action._fn "itemAction", { buffer = true })
end

vim.api.nvim_create_autocmd("FileType", {
    pattern = "ddu-ff",
    callback = ddu_my_settings,
})

かなり読みやすくなったと思いませんか?
途中で _fn というものが出てきますが、これは関数でラッピングしたものを返します。

local ddu = call "ddu"
-- vim.fn["ddu#ui#ff#do_action"] "itemAction"
ddu.ui.ff.do_action "itemAction"

-- function() vim.fn["ddu#ui#ff#do_action"] "itemAction" end
ddu.ui.ff.do_action._fn "itemAction"

その代わりに ddu#ui#ff#do_action#_fn() は呼べなくなりますが、自分しか使わないので放置します。

おわりに

この記事を書いている途中で、vim-artemis という上位互換を見つけました。

https://github.com/tani/vim-artemis

変数の定義にも対応していて、本来の文法を拡張したような感じで書くことができるので、多分こちらを使った方がいいです()

Discussion