Tilia -- minimal plain-text and hierarchical todolist plugin for Neovim1.
The plugin doesn't do much. It only offers simples functions that toggle tasks (from -, incomplete, to x, complete, and vice versa), and somes functions. Every command are called as subcommands: :Tilia list ..., :Tilia add ..., etc.
Command list (:Tilia list <search...>) is used to show a list of incomplete tasks, sorted by due date and priority, in a floating window. While in the floating window, you can use <cr> to jump to the file and line where a task is defined (keymap is configurable, see below). In the floating window, lines order is changed, but hiearchy is still visible:
Due dates are inherited:
- this task @2.12 has an explicit due date
- this subtask has no explicit due date: it inherits from its supertask
- this subsubtask inherits tooArguments can be passed to list to filter searches. If first argument is an exclam sign, the tasks are sorted by priority over due date. If an argument is not, then the following argument is a reverse search term. (There is no or keyword.) If you want to list tasks under a project named build a table, skipping low-priority items:
:Tilia list /table not ?Command add (:Tilia add <desc...>) append a task to a project (if the description starts with "/" followed by a project search) or to a default file (defined in opts, see below).
Command pro either jump to a project (if an argument) or list all projects and their status: next due date, highest priority, number of tasks.
Command clean (:Tilia clean) remove all done tasks from the current file.
The keymap toggle toggles from - (todo) to x (done), and vice versa. If the task is currently set to - and any subtask isn't done, the function will fail. It avoids situations like:
x read a lot of books
- read one bookThe keymap do_recursive marks as done (i.e. x) current task and all subtasks, recursively.
Syntax is mostly based on common prose writing practices:
- Priority is signaled using
!outside comments and strings. A task's priority is equal to the number of exclams it contains. Thus, for a very high priority task, you can use- pay rent!!!!!!!!. Negative priority is signaled using?, with same logic as!. - Due dates have the format
@%d.%m.%y. If year is omitted, it's considered to be the current year -@2.7is equal to@02.07.25. - Lines starting with
/are project headers. Any indenting tasks below are included in the project, until a non-indented line is encountered. - Things in double quotes are considered titles and are colorized (by default in blue). Signs
?and!in quotes are not used to define priority. - Parentheses are comments, rendered as such. Their content are not parsed (priority or due) neither used for searches.
There are also two highlighted groups that have not (yet) special effect:
- Words directly preceded by colons, like
:thisare:tags(the colon must be preceded by space). - Words starting with an uppercase letter are considered names and (by default) rendered in bold. Has absolutely no effect for now.
The default folding method is indent. Therefore, if you use project, it's convenient to indent each task under the project:
/ Build a table
- :buy or find wood
- borrow tools
- draw the table
- :buy paper
- look at other tables to get ideas
- buy a table magazine
- watch a lot of movies :movies
- look for SF movies with tables :sfYou probably want to associate some extension (e.g. .tilia or .todo) with the plugin and its filetype (tilia):
-- e.g. init.lua
vim.api.nvim_create_autocmd({'BufEnter'}, {
pattern = "*.todo",
command = "setlocal ft=tilia"
})If commands are too long to type, you may want to use :cnoabbrev:
:cnoreabbrev tl Tilia list
:cnoreabbrev tp Tilia proTo use directly Tilia from command line, just use a bash functions:
# in ~/.bash_aliases
tl() { nvim -c "Tilia list ${*}"; }
tp() { nvim -c "Tilia pro ${*}"; }
ta() { nvim --headless -c "Tilia add ${*}"; echo; }- (optional) lua-iconv
All configuration options are listed in ./lua/tilia/opts.lua. You must do changes before calling setup() function:
-- in ~/.config/nvim/init.lua or where you want
local tilia = require "tilia"
tilia.opts.map.new_after = '<cr>'
tilia.opts.map.new_before = '<lf>'
tilia.setup()Footnotes
-
Why this name, Tilia? Just because it's the name of a tree, and because its consonants are tl, like todo list or task list. ↩

