My First Neovim Plugin: Neorg Worklog

Published Mon Jul 29 2024

Today I finished and released my first Neovim plugin! It is called Neorg Worklog, a external module for Neorg that extends the daily journal module.

I wanted my daily note to include a list of the notes I edited that day. This was surprisingly more difficult than I originally expected, but I am very happy with the result. When set up, the plugin will add notes to your daily journal like this:

* Worklog
** workspace-name
   - [metadata title]{:/Absolute/path/to/file.norg:}
** journal
   - [2024-07-29]{:/journals/2024-07-29:}

Creating a Neorg Module

While the Neorg docs could use some updating, I found them easier to navigate than the Neovim docs. Thankfully a helpful guide for creating external modules was published on GitHub by Ben Lubas here. My local development workflow could use improvement, I wrote the initial version out of my ~/.local/share/nvim directory.

Using Treesitter

Treesitter was a big help with getting this functionality to work. I used some simple queries to check for the presence of headings and avoid linking to a file multiple times. nvim-treesitter includes some awesome commands to experiment with treesitter live. :InspectTree and :EditQuery are incredibly hepful here. These queries are written as S-expressions, feeling very similar to lisp syntax.

((heading1 title: (paragraph_segment) @title)
  (#eq? @title "%s"))

This query selects heading1 nodes with a paragraph_segment title attribute, providing this title to the @title capture group. The #eq predicate is used for comparison, %s is for the Lua code to interpolate the title we are searching for using string.format. I use the same query to search for workspace headings, here is the complete Lua code using Neorg’s treesitter module:

local workspace_title_tmpl = [[
  ((heading2 title: (paragraph_segment) @workspace)
    (#eq? @workspace "%s"))
]]

treesitter.execute_query(
  string.format(workspace_title_tmpl, workspace),
  function(query, id, node, metadata)
    local text = treesitter.get_node_text(node)
    workspace_title_line = treesitter.get_node_range(node).row_start
  end,
  bufnr)

The base functionality is complete, but I have a couple changes I want to add eventually:

  • Separate modified and created files
  • Sort the logged note lists under each workspace
  • Log .norg files written outside of your Neorg workspaces

My plugin can be installed through the GitHub path: bottd/neorg-worklog, or if you are super cool you can install it from Luarocks neorg-worklog.

As is I am proud of this v1.0.0, it feels great to contribute a small gift to a community I’ve received so much for free from :)