dein.vimをNeovim Luaから使う

2022/12/03に公開

deinのtomlのような設定をlua tableで行う

deinではtomlで設定を記述できますが、deinではtomlでしか書けない記法はほぼ無く、dein#add(repo[, options])でoptionsにDictionaryを渡しているのと同じようになっているので

vim.fn['dein#add']('vim-jp/vimdoc-ja', { merged = false })

のようにすればluaでdeinの設定を記述することが可能なはずです。

dein.luaの用意

vim.fn['dein#add']()にプラグインの設定を渡せば良いです

local function add_plugin(repo, opts)
  if opts ~= nil then
    vim.fn['dein#add'](repo, opts)
  else
    vim.fn['dein#add'](repo)
  end
end

dein#load_toml({filename}, [{options}])と同じように使えるものを用意します。

local function load_configs(configs, shared_opts)
  -- { ['vim-jp/vimdoc-ja'] = { merged = false } }
  -- のようなテーブルがconfigsに渡されてくる想定
  for repo, opts in pairs(configs) do
    opts.repo = opts.repo or repo

    -- shared_optsが渡されたらoptsにマージするが、optsで同名
    -- の項目を入力していたらそれを優先したい
    if shared_opts ~= nil then
      for opt_name, value in pairs(shared_opts) do
        opts[opt_name] = opts[opt_name] or value
      end
    end

    add_plugin(repo, opts)
  end
end

dein-tomlなら[plugins.ftplugin]が書けると嬉しいです。

local gid = vim.api.nvim_create_augroup('__vimrc_ftplugin__', { clear = true })

--- @param ft string
--- @param plugin function
--- @param id string
local function add_ftplugin(ft, plugin, id)

  vim.api.nvim_create_autocmd('FileType', {
    group = gid,
    pattern = ft,
    callback = function(context)
      local did_ftplugin = vim.b.did_user_ftplugin or {}

      -- ftpluginでbuffer localなautocmdとか
      -- 定義しようとしたとき、何度も実行されると
      -- 困るのでdid_ftpluginを使う。
      -- 複数のプラグインが同じfiletypeに対して設定をする
      -- ことも考えたいのでdictionaryで持つ

      if not did_ftplugin[id] then
        plugin(context)

        -- vim.b.did_user_ftplugin[opts.name] = true
        -- ↑みたいに書いても動かなかった
        did_ftplugin[id] = true
        vim.b.did_user_ftplugin = did_ftplugin
      end
    end,
  })
end
local function load_configs(configs, shared_opts)
  -- { ['vim-jp/vimdoc-ja'] = { merged = false } }
  -- のようなテーブルがconfigsに渡されてくる想定
  for repo, opts in pairs(configs) do
    opts.repo = opts.repo or repo

+    opts.name = opts.name or
+        vim.fn.fnamemodify(repo, [[:s?/$??:t:s?\c\.git\s*$??]])

    -- shared_optsが渡されたらoptsにマージするが、optsで同名
    -- の項目を入力していたらそれを優先したい
    if shared_opts ~= nil then
      for opt_name, value in pairs(shared_opts) do
        opts[opt_name] = opts[opt_name] or value
      end
    end

+    if opts['ftplugin'] ~= nil then
+      --- @param ft string
+      --- @param plugin function
+      for ft, plugin in pairs(opts['ftplugin']) do
+        add_ftplugin(ft, function(context)
+
+          -- プラグインが読み込まれていないときは実行したくない
+          if vim.fn['dein#is_sourced'](opts.name) ~= 0 then
+            plugin(context)
+          end
+
+        end, opts.name)
+      end
+      opts['ftplugin'] = nil
+    end

    add_plugin(repo, opts)
  end
end

設定例

ここまで設定すれば、tomlで管理していたときと同じ雰囲気で設定を書けます。

load_configs()を呼び出すところの周りにvim.fn['dein#begin']()とか書いて関数を作り、その関数に設定が入っているtableを渡すように書いてやります。

以下は、fern.vimを設定している例です。

local plugins = {}

plugins['lambdalisue/fern.vim'] = {
  on_cmd = 'Fern',
  hook_add = function()
    vim.api.nvim_create_user_command('MapFern', function()
      nmap('<Plug>(filer:fs)', function()
        if vim.g.fern_use_drawer then
          vim.cmd.Fern { args = { '.', '-drawer', '-reveal=%' } }
          vim.cmd.wincmd '='
        else
          vim.cmd.Fern { args = { '.', '-reveal=%' } }
        end
      end)
    end, {})

    vim.g.fern_use_drawer = false
  end,
  hook_source = function()
    vim.g['fern#hide_cursor'] = true
    vim.g['fern#keepalt_on_edit'] = false
    vim.g['fern#keepjumps_on_edit'] = false
    vim.g['fern#default_hidden'] = true
    vim.g['fern#disable_default_mappings'] = true
  end,
  ftplugin = {
    ['fern'] = function(au_ctx)
      local buflocal = {
        buffer = au_ctx.buf,
        nowait = true,
        silent = true,
      }
      fn.map {
        mode = 'n',
        opts = buflocal,
        maps = {
          { '<Space>', '<Plug>(fern-action-mark:toggle)' },
          { '<C-l>', '<Plug>(fern-action-reload:all)' },
          { 'h', '<Plug>(fern-action-collapse)' },
          { '<Enter>', '<Plug>(fern-action-open)' },
          { 'y', '<Plug>(fern-action-yank:path)' },
          { '<C-c>', '<Plug>(fern-action-cancel)' },
          { 'N', '<Plug>(fern-action-new-file)' },
          { 'K', '<Plug>(fern-action-new-dir)' },
          { 'd', '<Plug>(fern-action-trash)' },
          { 'gr', '<Plug>(fern-action-grep)' },
          { 'r', '<Plug>(fern-action-rename)' },
        }
      }
      vim.cmd "nnoremap <buffer><nowait><silent><expr> l fern#smart#leaf('<Plug>(fern-action-open)', '<Plug>(fern-action-expand)')"

      if vim.g.fern_use_drawer then
        autocmd('WinLeave', {
          group = augroup('FernRc', { clear = true }),
          buffer = au_ctx.buf,
          command = 'close',
        })
        nmap('q', '<Cmd>Fern . -drawer -toggle<CR>', buflocal)
      else
        nmap('q', '<C-o>', buflocal)
      end
    end,
  },
}

return plugins

おわりに

ここまで紹介しておいてなんですが、実はこの設定をするとdein#save_state() & dein#load_state()が使えなくなるので、起動が基本的には遅くなります。
(僕のケースでは、init.lua化したことで多少速くなった分があっても40msくらい遅くなりました。)

しかし、sumneko-luafolke/neodev.nvimが使えるようになって、かなり快適に設定が書けるようになりました。

設定ファイルの携帯性は著しく下がりますが、興味のある方は是非。

Discussion