Skip to content

A picker for bullet journal style checkboxes in Neovim

Published: in 41d521d

Ever since I discovered Obsidian a few years ago, I’ve been using with the Minimal theme. The only reason being that this theme supports alternate checkboxes which provide a bunch of icons similar to the ones you would have in a bullet journal.

Obsidian is great, but in the last few months, I’ve been moving most of my note-taking to the terminal. After discovering render-markdown.nvim, a Neovim plugin which also renders alternate checkboxes, I decided to move my note-taking workflow to Neovim. And since I already commit to only use Neovim, why not write some Lua to make it easier to work with checkboxes?

I wanted to create a simple automation to easily change between checkboxes. First, I created a table containing the all the custom checkboxes I use. For my use case, I’m sticking with a few similar to the ones that come with the minimal theme.

-- This is using nerd fonts, so you might not be able to see the icons.
local checkboxes = {
  { char = ' ', icon = '', label = 'to-do' },
  { char = '/', icon = '', label = 'incomplete' },
  { char = 'x', icon = '', label = 'done' },
  { char = '>', icon = '󰒊', label = 'forwarded' },
  { char = '<', icon = '󰃭', label = 'scheduling' },
  { char = 'l', icon = '', label = 'location' },
  { char = 'b', icon = '', label = 'bookmark' },
  { char = 'u', icon = '󰔵', label = 'up' },
  { char = 'd', icon = '󰔳', label = 'down' },
}

Then I wrote a function. The logic is pretty simply. First, I’m running a couple of checks to make sure I’m in a markdown file and that the current line contains a checkbox. Then, I’m using vim.ui.select with the table of checkboxes as the options. Finally, if there is a selection, I’m changing the checkbox in the current line using string.gsub. That’s it. Not a lot of code.

local function select_checkbox()
  if vim.bo.filetype ~= 'markdown' then
    return
  end

  local pattern = '%- %[.-%] '

  local line = vim.api.nvim_get_current_line()
  if not line:match(pattern) then
    return
  end

  vim.ui.select(checkboxes, {
    prompt = 'Checkboxes:',
    format_item = function(item)
      return string.format('%s %s', item.icon, item.label)
    end,
  }, function(choice)
    if not choice then
      return
    end

    local checkbox = string.format('- [%s] ', choice.char)
    local modified_line = line:gsub(pattern, checkbox, 1)
    vim.api.nvim_set_current_line(modified_line)
  end)
end

I think this can be improved by making use of tree-sitter, but this is more than enough for me at the moment. Now all we need to do is to create a keybinding.

vim.keymap.set('n', '<leader>tt', select_checkbox)

Comments