Creating a lua interface for minpac
Tags: neovim, vim, shell
Published: 2021-08-13 03:48:50

---
title: Creating a lua interface for minpac
published: true
description: let's make minpac a little bit more lua friendly
tags: #neovim #vim #shell
//cover_image: https://direct_url_to_image.jpg
---

I like [minpac](https://github.com/k-takata/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.

```vim
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()
```

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.

```lua
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')
]]
```

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.

```lua
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, arg)
  -- Higher order functions FTW!
  M.use(M.add)
  vim.call(action, arg)
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
```

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.

```lua
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
```

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

```lua
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
```

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.

```lua
local Plug = vim.fn['minpac#add']
```

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

* [learn x in y minutes: where X=lua](https://learnxinyminutes.com/docs/lua/)
* [nvim-lua-guide](https://github.com/nanotee/nvim-lua-guide)

---

Thank you for your time. If you find this article useful and want to support my efforts, [buy me a coffee ☕](https://www.buymeacoffee.com/vonheikemen).

[![buy me a coffee](https://res.cloudinary.com/vonheikemen/image/upload/v1618466522/buy-me-coffee_ah0uzh.png)](https://www.buymeacoffee.com/vonheikemen)