org.nvim

TODO items and priorities

Keywords are defined with todo_keywords or per file with #+TODO: lines. Each string is a sequence, and keywords after | are done states:

todo_keywords = {
  "TODO(t) NEXT(n) | DONE(d!)",
  "WAITING(w@/!) | CANCELLED(c@)",
}

A sequence given as { type = "Fred Sara Lucy | DONE" } (Emacs (type ...), or per file #+TYP_TODO:) holds types, e.g. people, rather than states: C-c C-t goes from no keyword to the first type, from any type straight to the sequence's DONE keyword, and from its last keyword back to no keyword; repeated C-c C-t (nothing edited and the cursor not moved in between, like Emacs' repeated command) walks the types in order instead. Like Emacs, #+TYP_TODO: sequences come first, then #+TODO:, then #+SEQ_TODO: lines.

In (x!/@), x is the fast-selection key, the first flag applies when entering the state and the flag after / applies when leaving it: ! logs a timestamp and @ asks for a note. Log entries go into the log_into_drawer drawer (default none: under the headline, like Emacs; true = LOGBOOK), or the drawer named by the inherited LOG_INTO_DRAWER property (nil logs under the headline); #+STARTUP: logdrawer / nologdrawer also switch it. The headings of the log entries come from log_note_headings (org-log-note-headings). Notes are typed in a small *Org Note* split, like Emacs: <C-c><C-c> stores the note (lines starting with "# " are dropped), <C-c><C-k> cancels it (note_buffer = false asks with a one-line prompt instead). This holds for every note: state changes, rescheduling, add_note (<C-c><C-z>, and z in the agenda), clock-out notes (log_note_clock_out, stored right below the CLOCK line with the "clock-out" heading) and refile notes (refile.log = "note"); a cancelled note stores nothing. The *Org Note* buffer fires the User autocmd OrgLogBufferSetup (data { bufnr, purpose }, org-log-buffer-setup-hook) when it opens, and every stored log entry fires OrgNoteStored (data { bufnr, lnum, headline }, org-after-note-stored-hook). Without a log drawer, entries go right after the planning line and property drawer, past blank lines (with log_states_order_reversed = false, after the existing state notes); log_state_notes_insert_after_drawers also skips the clock lines and drawers that follow (org-log-state-notes-insert-after-drawers).

cit / ciT       Next / previous TODO state.
<S-Right/Left>  (ctx) Same, on a headline. Like Emacs, this walks every
                keyword of every sequence in order, with no keyword before
                the first and after the last one. With
                treat_S_cursor_todo_selection_as_state_change = false
                the change is neither logged nor blocked.
<prefix>S       Select a state (fast keys when defined).
<C-c><C-t>      Fast selection when the keywords have keys (turn it off
                with use_fast_todo_selection = false), otherwise cycle
                within the sequence: after its last keyword the headline
                has no keyword, and the next C-c C-t goes back to the
                first keyword of that same sequence. The count is the
                Emacs prefix argument:
                  4 (C-u)          take a note for this change
                  16 (C-u C-u)     switch to the next keyword set
                  64 (C-u C-u C-u) ignore blocking (dependencies)
                  N                switch to the Nth keyword
                In Visual mode every headline of the selection changes.
<C-S-Right/Left>  Next / previous keyword set (not logged, like Emacs).
:Org todo_without_note   Change the state, recording a time instead of
                asking for a note (C-0 C-c C-t).
:Org todo_yesterday   Like C-c C-t (same counts), but CLOSED, log notes
                and LAST_REPEAT record 23:59 of yesterday and .+/++
                repeaters count from yesterday (org-todo-yesterday). Lua:
                require("org.todo").todo_yesterday(target, arg).
:Org todo_cancel_repeaters   Set the repeaters of the entry to 0 and change
                the state, so a repeating task is done for good
                (C-- 1 C-c C-t, org-cancel-repeaters).

In Visual mode, <C-c><C-t>, <C-c><C-s>, <C-c><C-d> and the archiving commands (<C-c>$, <C-c><C-x>a, <C-c><C-x>A) act on every headline of the selection, skipping headlines hidden in closed folds (loop_over_headlines_in_active_region: true, false, or "start-level" for only the headlines at the level of the first one; a match string acts like true, as in Emacs 9.8, whose commands pass nil as the match). Archiving a subtree takes its children with it, and archiving to the sibling skips entries already archived.

Entering a done state adds CLOSED: [timestamp] when log_done is set, and going back to a TODO state removes it. Like Emacs, CLOSED is only touched when some logging is set up (log_done or a keyword with !/@); closed_keep_when_no_todo keeps it when the keyword is removed. With use_effective_time, times recorded before extend_today_until o'clock are 23:59 of the previous day; with use_last_clock_out_time_as_effective_time, CLOSED and the notes record the last clock-out time of the subtree instead. log_done_with_time = false makes CLOSED a date without time. M-S-RET / C-S-RET log the new heading's keyword like a state change with treat_insert_todo_heading_as_state_change.

The inherited LOGGING property overrides the logging of a subtree, like Emacs: :LOGGING: nil turns it off, :LOGGING: lognotedone logrepeat selects done/repeat logging, and keyword specs such as WAIT(@) DONE(!) replace the flags of the keyword definitions.

todo_state_tags_triggers adds or removes tags on state changes. Keys are keywords, todo (any not-done state), done or "" (no keyword):

todo_state_tags_triggers = {
  WAITING = { WAITING = true },
  done = { WAITING = false },
  [""] = { WAITING = false },
}

Every state change fires the User autocmd OrgTodoStateChange (Emacs org-after-todo-state-change-hook and org-trigger-hook), and a repeating task marked done also fires OrgTodoRepeat (org-todo-repeat-hook). data holds bufnr, lnum, from, to (the final keyword), state (the keyword chosen), done and repeated:

vim.api.nvim_create_autocmd("User", {
  pattern = "OrgTodoStateChange",
  callback = function(ev) print(ev.data.from, "->", ev.data.to) end,
})

todo_blockers is a list of functions (org-blocker-hook); each receives { type = "todo-state-change", from, to, bufnr, lnum } and blocks the change by returning false.

todo_get_default_hooks (org-todo-get-default-hook) are functions (new, old): the first one returning a string picks the state the change really goes to ("" removes the keyword); M-S-RET asks them for the new heading's keyword with old nil. After a state change that updates TODO statistics, after_todo_statistics_hooks (org-after-todo-statistics-hook) are called as fn(n_done, n_not_done, target) for each ancestor whose statistics cookie is updated (target = { bufnr, lnum } of the ancestor), then todo_statistics_hooks (org-todo-statistics-hook) as fn(target) with the changed entry, even without a cookie. Emacs's org-summary-todo:

after_todo_statistics_hooks = { function(n_done, n_not_done, target)
  require("org.todo").change_state(target, n_not_done == 0 and "DONE" or "TODO",
    { no_log = true })
end },
Repeated tasks
A SCHEDULED or DEADLINE timestamp with a repeater (+1w, ++1d, .+2d)
is shifted when the task is marked done, and the task returns to the first
keyword of its sequence, like Emacs. The REPEAT_TO_STATE property or
todo_repeat_to_state (a keyword, or true for the previous state) choose
another state. A SCHEDULED date without a repeater is removed. The state
change is logged and LAST_REPEAT set when log_repeat is enabled
(LAST_REPEAT is also set when the entry has clock lines).
  +N   shift by N units once
  ++N  shift until the date is in the future, keeping the weekday
  .+N  shift to N units after today
Months keep the day of the month and roll over like Emacs: <2026-01-31
+1m> becomes <2026-03-03>.

Dependencies With enforce_todo_dependencies, an entry cannot be marked done while its children are unfinished, or while earlier siblings under an :ORDERED: t parent are unfinished, or while an ancestor TODO is blocked that way. A NOBLOCKING property disables blocking for the entry. enforce_todo_checkbox_dependencies also blocks entries that have unchecked or partial checkboxes (not the ones inside blocks). <C-c><C-x>o (toggle_ordered) toggles the ORDERED property; with track_ordered_property_with_tag (true or a tag name) it also toggles an ORDERED (or that) tag to make it visible.

Statistics A [/] or [%] cookie on a headline counts its children with a TODO keyword. provide_todo_statistics chooses what counts (org-provide-todo- statistics): true, "all-headlines", a list of keywords, or { todo_keywords, done_keywords }; false stops updating cookies when a state changes. hierarchical_todo_statistics = false counts the whole subtree instead of the direct children (:COOKIE_DATA: recursive does it for one entry).

Priorities
<S-Up> / <S-Down>   (ctx) Raise / lower priority on a headline. Past the
                    highest or lowest priority the cookie is removed; the
                    next shift in the same direction wraps around to the
                    other end, like Emacs.
<C-a> / <C-x>       (ctx) Same, with the cursor on the [#A] cookie.
<prefix>,           Set priority (press a letter, <Space> removes it). With
                    a count of 4 (C-u C-c ,), show the priority value.
:Org priority_show  Show the priority as Emacs computes it for sorting,
                    1000 × (lowest - priority) (org-priority-show).
Range and default come from priority_highest/lowest/default or
#+PRIORITIES: A C B. Numbers from 0 to 64 work too, like Emacs:
#+PRIORITIES: 1 10 5 uses [#1] .. [#10] (typed as a number).
ui.priority_faces gives priorities their own highlight, like
ui.todo_keyword_faces. priority_enable_commands = false disables the
priority commands (<S-Up>/<S-Down> then fall back to Vim);
priority_start_cycle_with_default = false makes the first shift of a
headline without cookie go one step past the default; a
priority_get_priority_function (called with the headline line, returning
a number) replaces the value used for sorting and priority_show.