When you have many variations of the same snippet, one option is to generate those with Lua code. The complete example is at the end.

I’ve recently moved to LuaSnip as my snippets plugin for Neovim. When I first started, I sticked to the simplest features of LuaSnip, in particular the SnipMate-like syntax for snippets. But I have now started to explore the more distinctive features of LuaSnip, like Lua-defined snippets. It turns out that generating snippets with code can save tedious repetition.

Markdown Journaling

I tend to take notes in Markdown documents. I usually set up one text file for each recurring themes or project. Then, there is a “h1” title per day, a bit like the Bullet Journal daily log. The resulting file looks like this:

# 2022-07-31 Sun

* Point 1
* Point 2
* Point 3

I have a snippet to generate the # 2022-07-31 Sun line, based on the current date and day of the week, as I explained last time. For reference, here is the snippet from back then:

# “Bullet Journal”-styled date for today
snippet bjtoday

Lua-Generated Snippets

But then, I also sometimes want to put notes for the next session. I usually know when that will be, because the meeting happens, say, every Thursday or every month. So it would be great to have snippets for those dates as well. Let’s build them step by step.

1. Computing the Date of “Next Friday”

The first step is to calculate the date and weekday from human concepts like “next Friday” or “next week”. Thankfully GNU date supports this, and it’s preinstalled on most GNU/Linux systems:

$ date -d 'next Friday' +'%F %a'
2022-08-05 Fri

2. Putting Those Dates in LuaSnip

The second step is to use the output of the date command in LuaSnip.

We could define our own variables like ${NEXT_MONDAY_DATE}. That would be similar to the ${CURRENT_DATE} we used before. But we would need many such variables, for the date and weekday of the next Monday, Tuesday, …, next week or next month. Plus I use those dates only in Markdown snippets, so it feels a bit wasteful to define those variables everywhere.

In the end, I chose to use function nodes to call the date command on the fly, when the snippet gets expanded. This node is inserted in a particular snippet definition and is thus contained to that particular snippet. This way, we avoid creating values everywhere, and we keep the global namespace clean. It looks like this:

f(function(args, snip, user_arg_1)
    return vim.fn.trim(vim.fn.system([[date -d ']] .. target_date .. [[' +'%F %a']]))
end, {}),

3. Generating Snippets Variants

The last step is to generate the variants of the base snippet: bj_today, bj_next_monday, …, bj_next_week and bj_next_month.

In LuaSnip, snippets defined using Lua are just a table calling some agreed-upon functions. Here is an example from the documentation:

ls.add_snippets("all", {
	s("ternary", {
		-- equivalent to "${1:cond} ? ${2:then} : ${3:else}"
		i(1, "cond"), t(" ? "), i(2, "then"), t(" : "), i(3, "else")

This creates a snippet (the s function) for all file types. This snippet will expand ternary into cond ? then : else, prompting the user to change the cond, then and else bits (function i) while ? and : are “static text” (function t).

We want to write a function that builds a table of date snippet definitions. That will be returned when the Lua module is loaded.

The Final Code

The final snippet looks like this:

 1-- Bullet Journal styled dates in the future
 2local function gen_date_snippets()
 3  local snippets = {}
 4  local target_dates = {
 5    "today",
 6    "tomorrow",
 7    "next monday",
 8    "next tuesday",
 9    "next wednesday",
10    "next thursday",
11    "next friday",
12    "next week",
13    "next month",
14  }
15  for _, target_date in pairs(target_dates) do
16    table.insert(
17      snippets,
18      s("bj_" .. target_date:gsub(" ", "_"), {
19        t "# ",
20        f(function(args, snip, user_arg_1)
21          return vim.fn.trim(vim.fn.system([[date -d ']] .. target_date .. [[' +'%F %a']]))
22        end, {}),
23      })
24    )
25  end
27  return snippets
30return gen_date_snippets()

This generates the snippets name (line 18), for instance bj_today or bj_next_tuesday from the arguments passed to the date command. The body of the snippet is made of text (t "# ") and our function node from earlier (line 20-22). All those snippets get collected in a table that is returned at the end of the Lua module (line 30). It’s then usable by LuaSnip.


Generating a bunch of similar snippets like this turned out to be an unanticipated benefit of using Lua to define snippets. Before using LuaSnip, I only had bj_today, bj_tomorrow and bj_next_monday. If I had wanted to expand this set further, I would have to do some more copying and pasting, which quickly becomes unmaintainable.

Of course for simpler or less repetitive snippets, it’s probably best to use the less verbose SnipMate-like syntax presented in the previous post.