diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ad97621 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# contributing + +A Neovim plugin with no dependencies, three files under `lua/shepherd/`: + +| File | What | +|------|------| +| `init.lua` | commands, the picker, quick-add/capture, statusline counts | +| `board.lua` | the board switcher and board CRUD, over `boards --json` | +| `health.lua` | `:checkhealth shepherd` — finds the binary | + +Every read and write goes through the installed `shepherd` binary. The plugin +never parses or writes a board file itself — the binary owns that format, so +keep new features behind a CLI verb rather than touching markdown here. + +```sh +make fmt # stylua +make lint # stylua --check + luacheck +make test # busted +make check # lint + test +``` + +Test deps: [busted](https://lunarmodules.github.io/busted/) and `nlua` (the specs +run under Neovim) — `luarocks install busted nlua`. Formatting and linting need +[stylua](https://github.com/JohnnyMorganz/StyLua) and +[luacheck](https://github.com/lunarmodules/luacheck). + +## tests + +`tests/shepherd_spec.lua` drives the pure helpers — `tally`, `format_status`, +`label`, `flatten`, `edit_seed`, `clean`, and `build_cmd`/filter resolution — +exposed on the `_internal` table. Keep new logic in a pure helper where you can: +anything that only reshapes data is testable without a running UI, and the +command wiring around it stays thin. + +CI (`.github/workflows/pull-request.yml`) runs the lint job plus the suite on +Neovim stable and nightly. Both must be green. + +## conventions + +- stylua: tabs, 120 columns (`.stylua.toml`). Run `make fmt` before committing. +- luacheck: `vim` is the only permitted global (`.luacheckrc`). +- [Conventional Commits](https://www.conventionalcommits.org) — `feat:`, `fix:`, + `docs:`, `chore:`. +- Branch for the change; don't commit to `master`. +- The plugin targets the Neovim version in [requirements](README.md#requirements) + — no compatibility shims for older releases. + +## demo gifs + +The README's `assets/demo.gif` (the commands) and `assets/stats.gif` +(`:ShepherdStats`) are scripted with +[vhs](https://github.com/charmbracelet/vhs), not screen-captured — regenerate +them after any change to the commands, the picker, or the statusline: + +```sh +brew install vhs # pulls in ttyd + ffmpeg +vhs assets/demo.tape +vhs assets/stats.tape +``` + +Both tapes run nvim with `assets/demo-init.lua` — this checkout and nothing else, +so the recording never shows another plugin's UI — against a board seeded by +`assets/demo-seed.sh` under `mktemp -d`, with `$SHEPHERD_TODO_FILE` and +`$SHEPHERD_CONFIG` pointed at it. A real board or config is never touched. + +Two things worth knowing when you edit a tape: + +- The seed writes its dates relative to today, so due dates and the aging numbers + stay meaningful whenever a gif is re-recorded. +- The window has to clear the tallest screen it shows. `shepherd stats` is 56 + rows and the float only gets 85% of the window, which is why `stats.tape` + records at a smaller font in a taller window than `demo.tape`. + +Named boards live under `$XDG_CONFIG_HOME/shepherd/boards/`, *not* wherever +`$SHEPHERD_TODO_FILE` points — that variable only overrides the default board's +file. Set `XDG_CONFIG_HOME` as well when you want a scratch tree for board +commands (`:ShepherdBoards` and friends), or they will read and write your real +boards. diff --git a/README.md b/README.md index 7b1d7cc..1a8c26f 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,13 @@ buffer. Zero dependencies; drives the installed `shepherd` binary. - [requirements](#requirements) - [install](#install) - [usage](#usage) +- [stats](#stats) - [statusline](#statusline) - [configuration](#configuration) - [development](#development) +![nvim-shepherd in action](assets/demo.gif) + ## requirements - Neovim ≥ 0.11 (`vim.fn.jobstart({ term = true })`, `vim.system`). @@ -79,8 +82,8 @@ return { - `:ShepherdList work` — pick within a filtered view. `:ShepherdList!` picks across all boards (read-only — items show their `[board]`; only open-link is offered). -- `:ShepherdStats` — open shepherd's stats charts in the floating terminal. - `:ShepherdStats!` aggregates every board (`shepherd stats --all`). +- `:ShepherdStats` — open shepherd's stats charts in the floating terminal (see + [stats](#stats)). `:ShepherdStats!` aggregates every board. - `:ShepherdBoards` — pick a board (shows `open/total`, `*` marks current, and its working **dir** if set), then **switch** the session to it, **rename**, set its **dir** (working directory; empty clears), **archive**, or **delete** @@ -94,6 +97,19 @@ return { Run `:checkhealth shepherd` to verify the binary is found. +## stats + +`:ShepherdStats` runs `shepherd stats` in the floating terminal — completion, due +and urgency, priority load, aging, status mix, throughput and backlog trend for +the active board. `:ShepherdStats!` aggregates every board and adds a by-board +breakdown. + +![:ShepherdStats](assets/stats.gif) + +The charts are one-shot output, so the float holds until you dismiss it with `q`, +`` or `` — unlike `:Shepherd`, which closes when the board exits. +Done-based counts include the archive. + ## statusline `require("shepherd").status()` returns a short string — the open count, plus an @@ -156,19 +172,12 @@ require("shepherd").setup({ ## development -Tooling: [stylua](https://github.com/JohnnyMorganz/StyLua), -[luacheck](https://github.com/lunarmodules/luacheck), and -[busted](https://lunarmodules.github.io/busted/) + `nlua` (tests run under -Neovim). Install the test deps with `luarocks install busted nlua`. - ```sh -make fmt # format +make fmt # stylua make lint # stylua --check + luacheck make test # busted make check # lint + test ``` -CI (`.github/workflows/pull-request.yml`) runs lint plus the suite on Neovim -stable and nightly. Tests cover the pure helpers (`tally`, `format_status`, `label`, -`flatten`, `edit_seed`, `clean`, `build_cmd`/filter resolution) via the -`_internal` table. +See [CONTRIBUTING.md](CONTRIBUTING.md) for the layout, the test approach, the +conventions, and how to re-record the demo gif. diff --git a/assets/demo-init.lua b/assets/demo-init.lua new file mode 100644 index 0000000..7b2135f --- /dev/null +++ b/assets/demo-init.lua @@ -0,0 +1,33 @@ +-- Minimal init for the vhs recording (assets/demo.tape): loads this checkout, +-- nothing else. No plugin manager, no colourscheme, no other plugins, so what +-- the gif shows is only ever nvim-shepherd. +-- +-- nvim -u assets/demo-init.lua +-- +-- The board comes from $SHEPHERD_TODO_FILE (the tape points it at a scratch +-- file), so `board` stays nil here. + +local root = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":p:h:h") +vim.opt.runtimepath:prepend(root) + +vim.o.swapfile = false +vim.o.shadafile = "NONE" +vim.o.termguicolors = true +vim.o.number = true +vim.o.showmode = false +vim.o.laststatus = 3 +vim.o.statusline = " %t%=%{v:lua.require'shepherd'.status()} " + +require("shepherd").setup({ + status = { icon = "todo" }, + float = { width = 0.85, height = 0.85, border = "rounded" }, +}) + +-- The counts land asynchronously, so redraw when they do — same hook the README +-- suggests for a statusline that doesn't refresh itself. +vim.api.nvim_create_autocmd("User", { + pattern = "ShepherdStatusUpdate", + callback = function() + vim.cmd.redrawstatus() + end, +}) diff --git a/assets/demo-seed.sh b/assets/demo-seed.sh new file mode 100755 index 0000000..74aad34 --- /dev/null +++ b/assets/demo-seed.sh @@ -0,0 +1,80 @@ +#!/bin/sh +# Seeds a throwaway board plus its archive for the vhs recording +# (assets/demo.tape). $SHEPHERD_TODO_FILE must already point at a scratch +# todo.md — the tape sets it under `mktemp -d`. +# +# Dates are written relative to today, so the board and `shepherd stats` still +# look lived-in whenever the gif gets re-recorded. +set -e + +board=$SHEPHERD_TODO_FILE +archive=$(dirname "$board")/archive.md + +ago() { date -v-"$1"d +%d-%m-%Y 2>/dev/null || date -d "$1 days ago" +%d-%m-%Y; } +in_days() { date -v+"$1"d +%Y-%m-%d 2>/dev/null || date -d "$1 days" +%Y-%m-%d; } + +cat >"$board" <"$archive" < $d/board.lua' +Enter +Type 'clear' +Enter +Show + +Type "nvim -u assets/demo-init.lua $d/board.lua" +Enter +Sleep 3s + +# the statusline carries the open count for the board +Sleep 1.5s + +# capture the TODO comment on line 3 as a todo +Type "3G" +Sleep 1s +Type ":ShepherdCapture" +Enter +Sleep 1.5s +Type " @plugin !h" +Sleep 800ms +Enter +Sleep 2.5s + +# pick an item, then edit it with a pre-filled quick-add line +Type ":ShepherdList" +Enter +Sleep 2.5s +Type "4" +Enter +Sleep 2s +Type "2" +Enter +Sleep 2s +Type " due:tomorrow" +Sleep 800ms +Enter +Sleep 2.5s + +# pick again, this time tick it off +Type ":ShepherdList" +Enter +Sleep 2s +Type "4" +Enter +Sleep 1.5s +Type "1" +Enter +Sleep 2.5s + +# the whole board, in a floating terminal +Type ":Shepherd" +Enter +Sleep 3s +Down@450ms 2 +Sleep 1s +Space +Sleep 1.5s +Type "v" +Sleep 2s +Type "q" +Sleep 2s + +Type ":q!" +Enter +Sleep 1.5s diff --git a/assets/stats.gif b/assets/stats.gif new file mode 100644 index 0000000..261e6db Binary files /dev/null and b/assets/stats.gif differ diff --git a/assets/stats.tape b/assets/stats.tape new file mode 100644 index 0000000..0f0e802 --- /dev/null +++ b/assets/stats.tape @@ -0,0 +1,49 @@ +# Regenerate assets/stats.gif: +# +# brew install vhs # pulls in ttyd + ffmpeg +# vhs assets/stats.tape +# +# Same throwaway board as demo.tape (assets/demo-seed.sh), whose archive carries +# a few weeks of completed work so the sparkline and backlog trend are populated. +# Smaller font and a taller window than the board recording: `shepherd stats` is +# 56 rows, and the float only gets 85% of them. + +Require nvim +Require shepherd + +Output assets/stats.gif + +Set Shell zsh +Set FontSize 12 +Set Width 1500 +Set Height 1160 +Set Padding 20 +Set Theme "Catppuccin Mocha" +Set Framerate 24 +Set TypingSpeed 55ms + +Hide +Type 'd=$(mktemp -d) && export SHEPHERD_TODO_FILE=$d/todo.md SHEPHERD_CONFIG=$d/config.toml' +Enter +Type 'sh assets/demo-seed.sh' +Enter +Type 'printf "%s\n" "local M = {}" "" "-- TODO: retry the board fetch on a transient failure" "function M.fetch() end" "" "return M" > $d/board.lua' +Enter +Type 'clear' +Enter +Show + +Type "nvim -u assets/demo-init.lua $d/board.lua" +Enter +Sleep 2.5s + +Type ":ShepherdStats" +Enter +Sleep 6s + +# the charts hold until dismissed +Type "q" +Sleep 1.5s +Type ":q!" +Enter +Sleep 1s