Writing a custom sorting lua function


I’m trying to get into lua/neovim and have an actual use case that I’d imagine would make a good entry point.

I’d like to write a shopping list, prefix the items with a tag (“d” for dairy etc.) and then have it reformatted (to markdown) and ordered by grocery shop sections.

I’d like to write a function that I can bind to a key that does some formatting and sorting to a selection:

m chicken
p tomatoes
p cucumbers
d milk
d mozzerella
m ham

desired output (in place replacement of visual selection)

## meat
- [ ] chicken
- [ ] ham

## produce
- [ ] tomatoes
- [ ] cucumbers

## dairy
- [ ] milk
- [ ] mozzerella

Now I do have programming knowledge alongside some basic lua knowledge and writing the sorting and formatting part would be no big deal. But I have no idea about vim/lua scripting for (neo)vim.

I took a look at Getting started using Lua in Neovim but this seems to be more about using lua in neovim.

I have some general questions:

  • I’m assuming this can be achieved via a function written in your init.lua config file or outside it and then import/require it (so no need to write a plugin), right?
  • How would I be able to pass the visual selection to the function or retrieve it inside the function? I searched some docs and found stuff like nvim_win_get_cursor({window}) but couldn’t find information about how to get visual selection.
  • How would I go about replacing the visual selection with the reformatted code?

Any pointers on how to do this or where to find resources about it are greatly appreciated.


Yes, that is correct.

Here’s a quick-and-dirty implementation I came up with that mostly does what you ask which you can hopefully use as a reference/starting point:

function convert_to_list(category_map, category_len)
  category_len = category_len or 1

  -- Adapted from https://stackoverflow.com/a/6271254, for how to get visual styled
  -- selection. Not sure this is the best way but it seems to work.
  local _, lnum_start = unpack(vim.fn.getpos("'<"))
  local _, lnum_end = unpack(vim.fn.getpos("'>"))
  local lines = vim.api.nvim_buf_get_lines(0, lnum_start - 1, lnum_end, true)
  local categories = {}
  for _, line in ipairs(lines) do
    local category = line:sub(1, category_len)
    if category_map then
      category = category_map[category]

    -- +2 since we assume there's a space in between the category names
    -- and the items
    local item = line:sub(2 + category_len, -1)
    if not categories[category] then
      categories[category] = {
      table.insert(categories[category], item)

  local new_lines = {}
  for category, items in pairs(categories) do
    -- Here `##` is assumed to be used for headers and `- [ ]` for items, but
    -- you could pretty easily make that configurable via an `options` table param.
    table.insert(new_lines, '## ' .. category)
    for _, item in ipairs(items) do
      table.insert(new_lines, '- [ ] ' .. item)

    table.insert(new_lines, '')

  vim.api.nvim_buf_set_lines(0, lnum_start - 1, #new_lines, false, new_lines)

Note that there at the bottom you can see how the text is inserted, via nvim_buf_set_lines; see :help nvim_buf_set_lines() and you may also want to check out :help nvim_buf_get_lines().

You can try this out by first running :luafile % with a Lua file open and focused containing the above code, then switch to another file with your input in it, select it with visual mode, and then run :lua convert_to_list({ p = 'produce', d = 'dairy', m = 'meat' }, 1). Should result in basically the output that you wanted.

Of course, this likely doesn’t handle certain edge cases, do exactly what you want, etc., but hopefully it gives you a decent starting point. Happy to answer any questions about the above as I’m able (like how to turn this into a requireable module, for example, since defining a global function probably isn’t ideal).

EDIT: Found https://stackoverflow.com/a/6271254 so updated answer to use visual selection.

1 Like

Wow, cool. Thank you very much. I’ll check it out once I got a minute!