DEV Community

Heiker
Heiker

Posted on

Creating a lua interface for minpac

I like minpac, it's a good plugin manager. It does what I want. Exactly what I want. Problem is it's written in vimscript, which is something I'm trying to avoid right now. I have no good reason to avoid vimscript... I just don't want to write any. By now I have read enough about lua and the neovim api to create a wrapper that would make minpac a little bit more "lua friendly".

Let me show you what we are running away from.

In the before times...

If you browse in the README file of minpac you'll notice this example code.

function! PackInit() abort
  packadd minpac

  call minpac#init()
  call minpac#add('k-takata/minpac', {'type': 'opt'})

  " Additional plugins here.
  call minpac#add('vim-jp/syntax-vim-ex')
  call minpac#add('tyru/open-browser.vim')
endfunction

" Define user commands for updating/cleaning the plugins.
" Each of them calls PackInit() to load minpac and register
" the information of plugins, then performs the task.
command! PackUpdate call PackInit() | call minpac#update()
command! PackClean  call PackInit() | call minpac#clean()
command! PackStatus packadd minpac | call minpac#status()
Enter fullscreen mode Exit fullscreen mode

We got this global function which does two things, it initializes minpac and registers the plugins. Then create we commands that actually use this function and call the action we want.

What I like about this is nothing gets executed until is needed. minpac really does get out of your way once your plugins are ready. Now, I want this in lua.

First try

What if we want to translate that snippet? How would it look like in lua? Here is the answer.

function PackInit()
  vim.cmd('packadd minpac')

  vim.call('minpac#init')
  local add = vim.fn['minpac#add']

  -- Additional plugins here.
  add('k-takata/minpac', {type = 'opt'})
  add('vim-jp/syntax-vim-ex')
  add('tyru/open-browser.vim')
end

-- Define user commands for updating/cleaning the plugins.
-- Each of them calls PackInit() to load minpac and register
-- the information of plugins, then performs the task.
vim.cmd [[
  command! PackUpdate lua PackInit(); vim.call('minpac#update')
  command! PackClean  lua PackInit(); vim.call('minpac#clean')
  command! PackStatus lua PackInit(); vim.call('minpac#status')
]]
Enter fullscreen mode Exit fullscreen mode

I don't hate this but I know we can do better. But what? What can we do? Hide things behind a pretty interface.

Modules to the rescue

Basically, what we will do is create a module that hides all the ugly things we don't want in the regular config.

local M = {}

-- Register the plugins
M.add = function(name, opts)
  opts = opts or vim.empty_dict()

  -- "do" is a keyword in lua.
  -- We make a "run" alias to save ourselves some troubles
  opts['do'] = opts.run
  opts.run = nil

  vim.call('minpac#add', name, opts)
end

-- The setup
M.init = function(opts)
  opts = opts or vim.empty_dict()
  vim.cmd('packadd minpac')
  vim.call('minpac#init', opts)
end

-- The heavy lifting happens here
M.run = function(action)
  -- Higher order functions FTW!
  M.use(M.add)
  vim.call(action)
end

M.setup_commands = function()
  vim.cmd [[
    command! PackUpdate lua require('usermod.minpac').run('minpac#update')
    command! PackStatus lua require('usermod.minpac').run('minpac#status')
    command! PackClean  lua require('usermod.minpac').run('minpac#clean')
  ]]
end

-- Just know that if you use `require` this will only be executed once.
-- Regardless, nothing bad will happens if you run this again.
M.setup_commands()

return M
Enter fullscreen mode Exit fullscreen mode

For the sake of this example let's say we have that module in this path: ~/.config/nvim/lua/usermod/minpac.lua. And we use it like this.

local minpac = require('usermod.minpac')

minpac.use = function(add)
  minpac.init()

  -- Additional plugins here.
  add('k-takata/minpac', {type = 'opt'})
  add('vim-jp/syntax-vim-ex')
  add('tyru/open-browser.vim')

  -- This a cool theme
  add('VonHeikemen/rubber-themes.vim', {
    type = 'opt',
    run = function()
      vim.opt.termguicolors = true
      vim.cmd('colorscheme rubber')
    end
  })
end
Enter fullscreen mode Exit fullscreen mode

The best part is, since we are taking add as an argument we can call it whatever we want. The following is also valid.

local minpac = require('usermod.minpac')

minpac.use = function(Plug)
  minpac.init()

  -- Additional plugins here.
  Plug('k-takata/minpac', {type = 'opt'})

  -- If you only need the first argument
  -- you can omit the parenthesis

  Plug 'vim-jp/syntax-vim-ex'
  Plug 'tyru/open-browser.vim'

  -- rest of the plugins...
end
Enter fullscreen mode Exit fullscreen mode

Thanks to the way lua works we can get a vim-plug-ish syntax for free.

To be fair we could also do that in the "first try" section.

local Plug = vim.fn['minpac#add']
Enter fullscreen mode Exit fullscreen mode

That would be enough.

Conclusion

We figure out we could use minpac directly in lua using just the neovim api, nothing special at first. Then we decided that wasn't enough, and we used lua modules to create a nice api that would great on fancy screenshots. Lastly, we discoreved some lua sourcery that allows us to write a syntax that is closed to the used by vim-plug.

Sources


Thank you for your time. If you find this article useful and want to support my efforts, buy me a coffee ☕.

buy me a coffee

Discussion (0)