Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

moor.nvim

Notes and todos, moored to your code.

To moor a boat is to tie it to a fixed point so it stays where you left it. moor.nvim does that with your thoughts: capture them without leaving the buffer, tie them to a file:line, and pull yourself back to that exact spot later.

moor is not an Obsidian or ZenNotes clone — it's meant to be used with those apps, not instead of them. moor is the quick-capture lane while you code; your notes app stays the place for reading, organizing, and everything else. It shares exactly one concept with them — [[backlinks]] — and writes plain, portable markdown. Point notes_dir at a vault that syncs (iCloud, git, Syncthing) and your captures and todos show up on your phone; edits made there flow back the next time moor scans.

capture float over code

More screenshots

The dashboard — every open todo in the vault, wikilinks bare, due dates highlighted:

dashboard

s flips it into a flat soonest-first deadline list:

dashboard sorted by due date

The project todo view keeps done and cancelled tasks visible:

project todo view

Moored todos anchor a sign to their line in the code:

mooring signs in the code

At a glance

  • Capture float — jot a note or todo over your code; :w saves and closes, <C-p> promotes to a split, <C-c> abandons
  • Per-project todos — todo/<project>.md, keyed by your git root
  • Moorings — todos tied to a file:line; the code line shows a ⚓ sign, and jumps work in both directions
  • Due dates — due:tomorrow, due:fri, due:3d expand to ZenNotes-parseable dates; the dashboard reads them back as due in 3d / overdue 3d
  • Dashboard — every open todo in the vault, toggle in place, sort by deadline
  • Backlinks — follow [[links]], list backlinks, complete titles as you type [[, insert links via your picker
  • Synced-vault safe — no caching, tolerant of iCloud eviction, never rewrites a line that changed underneath it
  • Zero dependencies — list UIs go through vim.ui.select, so your picker (telescope ui-select, snacks, fzf-lua, mini.pick) is used automatically

Requirements

  • Neovim 0.11+
  • Nothing else.

Installation

With lazy.nvim:

{
  "54l1m/moor.nvim",
  opts = {
    notes_dir = "~/notes", -- your vault, e.g. an iCloud-synced ZenNotes folder
  },
}

Configuration

require("moor").setup({
  notes_dir = "~/notes",
  ignore = { ".git", ".obsidian", "trash", "archive" }, -- dir names skipped by every scan

  capture = {
    -- Destination for note captures, relative to notes_dir. os.date() tokens
    -- are expanded: "inbox/%Y-%m-%d.md" turns captures into daily notes.
    note_file = "Captures.md",
    -- Heading above each capture, as an os.date() format. false = raw append.
    timestamp = "## %Y-%m-%d %H:%M",
    window = { width = 0.5, height = 0.3, border = "rounded", title = " moor " },
    maps = { promote = "<C-p>", abort = "<C-c>" }, -- inside the float only
  },

  todo = {
    dir = "todo",                 -- todos live in <notes_dir>/todo/<project>.md
    toggle_states = { " ", "x" }, -- add "-" for a cancelled state in the cycle
  },

  dashboard = {
    window = { width = 0.7, height = 0.7, border = "rounded", title = " open todos " },
    -- View-only; files keep "- [ ]" markdown. icons = false shows raw brackets.
    -- cancelled styles the "-" state (add it to todo.toggle_states to use it).
    icons = { open = "○", done = "✓", cancelled = "✗" },
    -- Show "due tomorrow" / "due in 3d" / "overdue 3d" instead of the raw
    -- due:YYYY-MM-DD token. View-only; files keep the absolute date.
    friendly_dates = true,
    maps = { toggle = "t", jump = "<CR>", jump_context = "gd", sort = "s", refresh = "r", close = "q" },
  },

  links = {
    new_note_dir = "", -- where notes created from [[missing links]] land
    completion = true, -- complete note titles when typing [[ (false disables)
  },

  -- Signs on code lines that have an open todo moored to them.
  -- Set moorings = false to disable.
  moorings = {
    sign = "⚓",
  },

  -- Global keymaps, applied by setup(). Set keymaps = false to define none
  -- (and bind the API yourself), set one entry to false to skip just it, or
  -- give an entry a different lhs to rebind.
  keymaps = {
    capture_note = "<leader>nn",
    capture_todo = "<leader>nt",
    capture_todo_context = "<leader>nT",
    add_todo = "<leader>na",        -- prompt, moored to the cursor position
    add_todo_plain = "<leader>nA",  -- prompt, no file:line reference
    dashboard = "<leader>nd",
    toggle = "<leader>nx",
    follow_link = "<leader>nf",
    backlinks = "<leader>nb",
    open_todo = "<leader>no",
    mooring = "<leader>nm",         -- jump from a moored code line to its todo
    find_note = "<leader>ns",       -- pick any note and open it
    insert_link = "<leader>ni",     -- pick a note, insert [[link]] at the cursor
  },
})

Features

Capture

A thought hits while you're mid-function. Press your capture key: a small float opens over the code, you type, :w — the float closes and you're back where you were. That's the whole gesture.

  • note mode appends to capture.note_file under a dated heading (## 2026-08-21 12:32 by default — capture.timestamp takes any os.date() format, or false), creating the file with a # Title header so it renders properly in ZenNotes/Obsidian.

  • todo mode turns each line into - [ ] … in your project's todo file.

  • context (capture({ mode = "todo", context = true })) moors the todo to where your cursor was:

    - [ ] handle EOF case · `internal/server/conn.go:142`

    On your phone that's just readable monospace. In nvim, it's a jump target. Works whether you type plain prose or a full - [ ] … line yourself — a hand-typed task without a mooring still gets one.

  • promote (<C-p> inside the float) reopens the same buffer in a split when the thought turns out to be bigger than the float. :w still saves to the vault. <C-c> abandons.

:Moor capture, :Moor capture todo, :Moor capture todo context, or the one-liner prompt :Moor todo (<leader>na) — moored by default; :Moor todo plain skips the file:line.

Todos

  • One file per project: <notes_dir>/todo/<project>.md, where the project is your git root's name (cwd basename outside a repo). :Moor open shows the project todo view — the dashboard scoped to this project's file, with done tasks kept visible, struck through. <CR> opens the underlying file when you want to edit freely.

  • :Moor toggle flips the checkbox on the current line — in any buffer, including inside the dashboard.

  • Due dates are inline due:YYYY-MM-DD tokens — the syntax ZenNotes already parses, so they work on your phone too. When capturing you can type relative shortcuts and moor expands them to the absolute date on save:

    due:today  due:tomorrow  due:fri  due:monday  due:3d  due:2w
    

    The dashboard shows the date as friendly relative text — due tomorrow, due in 3d, overdue 3d — with overdue and due-today accented in the warning color (friendly_dates = false keeps the raw ISO token). The files always store the absolute date.

  • :Moor dashboard opens a float aggregating every open - [ ] across the vault, grouped by note:

     todo/pf4.md (2)
       ○ fix race in loader · `lua/moor/init.lua:33`
       ○ ship the release due tomorrow
     inbox/Ideas.md (1)
       ○ try the capture float on the phone
    

    <CR> jumps to the note, gd jumps to the moored code location, t toggles (the item disappears — that's the feedback), s switches to a flat soonest-first deadline list (undated items last, source note dimmed at the end of each row) and back, r rescans, q closes. The active keys are pinned in the float's bottom border, and your global toggle binding works here too.

    The rendering is view-only sugar — wikilinks show without their [[brackets]], checkboxes use the configured dashboard.icons — while the files on disk always keep plain markdown.

Highlight groups

Group Styles Default
MoorLink wikilink text in the views Underlined
MoorDone done task text strikethrough, Comment fg
MoorDoneMark done checkbox icon Comment
MoorTodoMark open checkbox icon unstyled
MoorDue future due dates Special
MoorOverdue overdue / due-today dates DiagnosticError
MoorAnchor mooring sign in the code Special

Change any of them in your config or colorscheme:

vim.api.nvim_set_hl(0, "MoorLink", { fg = "#7aa2f7" })

Moorings in the code

The anchor works in both directions. Lines of your code that have an open todo moored to them show a ⚓ sign in the sign column, read fresh from the project todo file whenever you enter the buffer (and after every capture or toggle). Complete the todo and the sign disappears.

<leader>nm / :Moor mooring jumps from the code to the todo: on a moored line it goes straight there; elsewhere in a file with moorings it offers a pick. moorings = false turns the whole thing off.

Backlinks

  • :Moor follow opens the [[Note Title]] under the cursor, offering to create the note when it doesn't exist yet.
  • :Moor backlinks lists every note linking to the current one via vim.ui.select — [[Title]], [[Title|alias]], and [[Title#heading]] all count.
  • Typing [[ in a vault note (or a capture float) pops note-title completion, matched case-insensitively anywhere in the title; accepting a title closes the ]] for you. It's a plain completefunc, so <C-x><C-u> also summons it and <C-n>/<C-p> navigate. links.completion = false turns it off.
  • :Moor find (<leader>ns) opens any note via your picker; :Moor link (<leader>ni) picks a note and inserts a [[link]] to it at the cursor.

Health

:checkhealth moor verifies the nvim version, that notes_dir exists and is writable, times a vault scan, and shows where the current project's todos go.

Built for synced vaults

moor never caches your notes. Every scan re-reads the directory, unreadable files (like evicted iCloud placeholders) are skipped silently, and toggling a todo from the dashboard re-checks the source line first — if your phone edited the note in the meantime, moor rescans instead of rewriting the wrong line.


Plugin structure

moor.nvim/
├── lua/moor/
│   ├── init.lua        -- public API: defaults, options, setup()
│   ├── cmd.lua         -- :Moor subcommand dispatcher
│   ├── project.lua     -- project identity (git root / cwd)
│   ├── vault.lua       -- all notes_dir filesystem access
│   ├── tasks.lua       -- pure checkbox-line parse/toggle/format
│   ├── due.lua         -- due-date expansion, parsing, friendly display
│   ├── capture.lua     -- the capture float
│   ├── todo.lua        -- per-project todo operations
│   ├── dashboard.lua   -- the todo dashboard (vault + project scopes)
│   ├── moorings.lua    -- code-side anchor signs + jump to todo
│   ├── links.lua       -- wikilinks: follow, backlinks, find, insert
│   ├── completion.lua  -- [[title]] completefunc
│   ├── context.lua     -- file:line references
│   ├── health.lua      -- :checkhealth moor
│   └── picker.lua      -- vim.ui.select seam (native adapters land here)
├── plugin/moor.lua     -- :Moor command (lazy, zero startup cost)
├── scripts/demo.lua    -- stages a fake vault for screenshots
└── tests/              -- plenary busted specs, one per module

Contributing

Contributions are welcome and encouraged — especially picker adapters: every list UI goes through lua/moor/picker.lua (a thin vim.ui.select wrapper), so a native telescope/fzf-lua/snacks/mini.pick adapter with previews only needs to touch that one file.

make test          # run the suite (fetches plenary on first run)
make format        # stylua
make format-check

Conventional commits, tests for behavior changes, and make format before pushing.

License

MIT

About

Notes and todos, moored to your code.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages