33 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dce409b2cc |
Revert "feat(diff): disallow editing done tasks by default (#132)" (#133)
This reverts commit
|
||
|
|
44541ce753 |
feat(forge): support custom shorthand prefixes (#131)
* docs: document S3 backend, auto-auth, and `:Pending done` command Problem: The S3 backend had no `:Pending s3` entry in the COMMANDS section, `:Pending auth` only mentioned Google, the `sync` config field omitted `s3`, `_s3_sync_id` was missing from the data format section, `:Pending done` was implemented but undocumented, and the README lacked a features overview. Solution: Add `:Pending s3` and `:Pending done` command docs, rewrite `:Pending auth` to cover all backends and sub-actions, update config and data format references, add `aws` CLI to requirements, and add a Features section to `README.md`. * feat(forge): add forge link parser and metadata fetcher Problem: no way to associate tasks with GitHub, GitLab, or Codeberg issues/PRs, or to track their remote state. Solution: add `forge.lua` with shorthand (`gh:user/repo#42`) and full URL parsing, async metadata fetching via `curl`, label formatting, conceal pattern generation, token resolution, and `refresh()` for state pull (closed/merged -> done). * feat(config): add forge config defaults and `%l` eol specifier Problem: no configuration surface for forge link rendering, icons, issue format, or self-hosted instances. Solution: add `pending.ForgeConfig` class with per-forge `token`, `icon`, `issue_format`, and `instances` fields. Add `%l` to the default `eol_format` so forge labels render in virtual text. * feat(parse): extract forge refs from task body Problem: `parse.body()` had no awareness of forge link tokens, so `gh:user/repo#42` stayed in the description instead of metadata. Solution: add `forge_ref` field to `pending.Metadata` and extend the right-to-left token loop in `body()` to call `forge.parse_ref()` as the final fallback before breaking. * feat(diff): persist forge refs in store on write Problem: forge refs parsed from buffer lines were discarded during diff reconciliation and never stored in the JSON. Solution: thread `forge_ref` through `parse_buffer` entries into `diff.apply`, storing it in `task._extra._forge_ref` for both new and existing tasks. * feat(views): pass forge ref and cache to line metadata Problem: `LineMeta` had no forge fields, so `buffer.lua` could not render forge labels or apply forge-specific highlights. Solution: add `forge_ref` and `forge_cache` fields to `LineMeta`, populated from `task._extra` in both `category_view` and `priority_view`. * feat(buffer): render forge links as concealed text with eol virt text Problem: forge tokens were visible as raw text with no virtual text labels, and the eol separator logic collapsed all gaps when non-adjacent specifiers were absent. Solution: add forge conceal syntax patterns in `setup_syntax()`, add `PendingForge`/`PendingForgeClosed` highlight groups, handle the `%l` specifier in `build_eol_virt()`, fix separator collapsing to buffer one separator between present segments, and change `concealcursor` to `nc` (reveal in visual and insert mode). * feat(complete): add forge shorthand omnifunc completions Problem: no completion support for `gh:`, `gl:`, or `cb:` tokens, requiring users to type owner/repo from memory. Solution: extend `omnifunc` to detect `gh:`/`gl:`/`cb:` prefixes and complete with `owner/repo#` candidates from existing forge refs in the store. * feat: trigger forge refresh on buffer open Problem: forge metadata was never fetched, so virt text highlights could not reflect remote issue/PR state. Solution: call `forge.refresh()` in `M.open()` so metadata is fetched once per `:Pending` invocation rather than on every render. * test(forge): add forge parsing spec Problem: no test coverage for forge link shorthand parsing, URL parsing, label formatting, or API URL generation. Solution: add `spec/forge_spec.lua` covering `_parse_shorthand`, `parse_ref` for all three forges, full URL parsing including nested GitLab groups, `format_label`, and `_api_url`. * docs: document forge links feature Problem: no user-facing documentation for forge link syntax, configuration, or behavior. Solution: add forge links section to `README.md` and `pending.txt` covering shorthand/URL syntax, config options, virtual text rendering, state pull, and auth resolution. * feat(forge): add `find_refs()` inline token scanner Problem: forge tokens were extracted by `parse.body()` which stripped them from the description, making editing awkward and multi-ref lines impossible. Solution: add `find_refs(text)` that scans a string for all forge tokens by whitespace tokenization, returning byte offsets and parsed refs without modifying the input. Remove unused `conceal_patterns()`. * refactor: move forge ref detection from `parse.body()` to `diff` Problem: `parse.body()` stripped forge tokens from the description, losing the raw text. This made inline overlay rendering impossible since the token no longer existed in the buffer. Solution: remove the `forge.parse_ref()` branch from `parse.body()` and call `forge.find_refs()` in `diff.parse_buffer()` instead. The description now retains forge tokens verbatim; `_extra._forge_ref` is still populated from the first matched ref. * feat(buffer): render forge links as inline conceal overlays Problem: forge tokens were stripped from the buffer and shown as EOL virtual text via `%l`. The token disappeared from the editable line, and multi-ref tasks broke. Solution: compute `forge_spans` in `views.lua` with byte offsets for each forge token in the rendered line. In `apply_inline_row()`, place extmarks with `conceal=''` and `virt_text_pos='inline'` to visually replace each raw token with its formatted label. Clear stale `forge_spans` on dirty rows to prevent `end_col` out-of-range errors after edits like `dd`. * fix(config): remove `%l` from default `eol_format` Problem: forge links are now rendered inline, making the `%l` EOL specifier redundant in the default format. Solution: change default `eol_format` from `'%l %c %r %d'` to `'%c %r %d'`. The `%l` specifier remains functional for users who explicitly set it. * test(forge): update specs for inline forge refs Problem: existing tests asserted that `parse.body()` stripped forge tokens from the description and populated `meta.forge_ref`. The `conceal_patterns` test referenced a removed function. Solution: update `parse.body` integration tests to assert tokens stay in the description. Add `find_refs()` tests covering single/multiple refs, URLs, byte offsets, and empty cases. Remove `conceal_patterns` test. Update diff tests to assert description includes the token. * docs: update forge links for inline overlay rendering Problem: documentation described forge tokens as stripped from the description and rendered via EOL `%l` specifier by default. Solution: update forge links section to describe inline conceal overlay rendering. Update default `eol_format` reference. Change `issue_format` field description from "EOL label" to "inline overlay label". * ci: format * refactor(forge): remove `%l` eol specifier, add `auto_close` config, fix icons Problem: `%l` was dead code after inline overlays replaced EOL rendering. Auto-close was always on with no opt-out. Forge icon defaults were empty strings. Solution: remove `%l` from the eol format parser and renderer. Add `forge.auto_close` (default `false`) to gate state-pull. Set nerd font icons: `` (GitHub), `` (GitLab), `` (Codeberg). Keep conceal active in insert mode via `concealcursor = 'nic'`. * fix(config): set correct nerd font icons for forge defaults * refactor(forge): replace curl/token auth with CLI-native API calls Problem: Forge metadata fetching required manual token management — config fields, CLI token extraction, and curl with auth headers. Each forge had a different auth path, and Codeberg had no CLI support at all. Solution: Delete `get_token()` and `_api_url()`, replace with `_api_args()` that builds `gh api`, `glab api`, or `tea api` arg arrays. The CLIs handle auth internally. Add `warn_missing_cli` config (default true) that warns once per forge per session on failure. Add forge CLI checks to `:checkhealth`. Remove `token` from config/docs. * refactor(forge): extract ForgeBackend class and registry Problem: adding a new forge required touching 5 lookup tables (`FORGE_HOSTS`, `FORGE_CLI`, `FORGE_AUTH_CMD`, `SHORTHAND_PREFIX`, `_warned_forges`) and every branching site in `_api_args`, `fetch_metadata`, and `parse_ref`. Solution: introduce a `ForgeBackend` class with `parse_url`, `api_args`, and `parse_state` methods, plus a `register()` / `backends()` registry. New forges (Gitea, Forgejo) are a single `register()` call via the `gitea_backend()` convenience constructor. * ci: format * feat(forge): support custom shorthand prefixes Problem: forge shorthand parsing hardcoded `%l%l` (exactly 2 lowercase letters), preventing custom prefixes like `github:`. Completions also hardcoded `gh:`, `gl:`, `cb:` patterns. Solution: iterate `_by_shorthand` keys dynamically in `_parse_shorthand` instead of matching a fixed pattern. Build completion patterns from `forge.backends()`. Add `shorthand` field to `ForgeInstanceConfig` so users can override prefixes via config, applied in `_ensure_instances()`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
c34adbeadd |
feat(diff): disallow editing done tasks by default (#132)
* docs: document S3 backend, auto-auth, and `:Pending done` command Problem: The S3 backend had no `:Pending s3` entry in the COMMANDS section, `:Pending auth` only mentioned Google, the `sync` config field omitted `s3`, `_s3_sync_id` was missing from the data format section, `:Pending done` was implemented but undocumented, and the README lacked a features overview. Solution: Add `:Pending s3` and `:Pending done` command docs, rewrite `:Pending auth` to cover all backends and sub-actions, update config and data format references, add `aws` CLI to requirements, and add a Features section to `README.md`. * feat(forge): add forge link parser and metadata fetcher Problem: no way to associate tasks with GitHub, GitLab, or Codeberg issues/PRs, or to track their remote state. Solution: add `forge.lua` with shorthand (`gh:user/repo#42`) and full URL parsing, async metadata fetching via `curl`, label formatting, conceal pattern generation, token resolution, and `refresh()` for state pull (closed/merged -> done). * feat(config): add forge config defaults and `%l` eol specifier Problem: no configuration surface for forge link rendering, icons, issue format, or self-hosted instances. Solution: add `pending.ForgeConfig` class with per-forge `token`, `icon`, `issue_format`, and `instances` fields. Add `%l` to the default `eol_format` so forge labels render in virtual text. * feat(parse): extract forge refs from task body Problem: `parse.body()` had no awareness of forge link tokens, so `gh:user/repo#42` stayed in the description instead of metadata. Solution: add `forge_ref` field to `pending.Metadata` and extend the right-to-left token loop in `body()` to call `forge.parse_ref()` as the final fallback before breaking. * feat(diff): persist forge refs in store on write Problem: forge refs parsed from buffer lines were discarded during diff reconciliation and never stored in the JSON. Solution: thread `forge_ref` through `parse_buffer` entries into `diff.apply`, storing it in `task._extra._forge_ref` for both new and existing tasks. * feat(views): pass forge ref and cache to line metadata Problem: `LineMeta` had no forge fields, so `buffer.lua` could not render forge labels or apply forge-specific highlights. Solution: add `forge_ref` and `forge_cache` fields to `LineMeta`, populated from `task._extra` in both `category_view` and `priority_view`. * feat(buffer): render forge links as concealed text with eol virt text Problem: forge tokens were visible as raw text with no virtual text labels, and the eol separator logic collapsed all gaps when non-adjacent specifiers were absent. Solution: add forge conceal syntax patterns in `setup_syntax()`, add `PendingForge`/`PendingForgeClosed` highlight groups, handle the `%l` specifier in `build_eol_virt()`, fix separator collapsing to buffer one separator between present segments, and change `concealcursor` to `nc` (reveal in visual and insert mode). * feat(complete): add forge shorthand omnifunc completions Problem: no completion support for `gh:`, `gl:`, or `cb:` tokens, requiring users to type owner/repo from memory. Solution: extend `omnifunc` to detect `gh:`/`gl:`/`cb:` prefixes and complete with `owner/repo#` candidates from existing forge refs in the store. * feat: trigger forge refresh on buffer open Problem: forge metadata was never fetched, so virt text highlights could not reflect remote issue/PR state. Solution: call `forge.refresh()` in `M.open()` so metadata is fetched once per `:Pending` invocation rather than on every render. * test(forge): add forge parsing spec Problem: no test coverage for forge link shorthand parsing, URL parsing, label formatting, or API URL generation. Solution: add `spec/forge_spec.lua` covering `_parse_shorthand`, `parse_ref` for all three forges, full URL parsing including nested GitLab groups, `format_label`, and `_api_url`. * docs: document forge links feature Problem: no user-facing documentation for forge link syntax, configuration, or behavior. Solution: add forge links section to `README.md` and `pending.txt` covering shorthand/URL syntax, config options, virtual text rendering, state pull, and auth resolution. * feat(forge): add `find_refs()` inline token scanner Problem: forge tokens were extracted by `parse.body()` which stripped them from the description, making editing awkward and multi-ref lines impossible. Solution: add `find_refs(text)` that scans a string for all forge tokens by whitespace tokenization, returning byte offsets and parsed refs without modifying the input. Remove unused `conceal_patterns()`. * refactor: move forge ref detection from `parse.body()` to `diff` Problem: `parse.body()` stripped forge tokens from the description, losing the raw text. This made inline overlay rendering impossible since the token no longer existed in the buffer. Solution: remove the `forge.parse_ref()` branch from `parse.body()` and call `forge.find_refs()` in `diff.parse_buffer()` instead. The description now retains forge tokens verbatim; `_extra._forge_ref` is still populated from the first matched ref. * feat(buffer): render forge links as inline conceal overlays Problem: forge tokens were stripped from the buffer and shown as EOL virtual text via `%l`. The token disappeared from the editable line, and multi-ref tasks broke. Solution: compute `forge_spans` in `views.lua` with byte offsets for each forge token in the rendered line. In `apply_inline_row()`, place extmarks with `conceal=''` and `virt_text_pos='inline'` to visually replace each raw token with its formatted label. Clear stale `forge_spans` on dirty rows to prevent `end_col` out-of-range errors after edits like `dd`. * fix(config): remove `%l` from default `eol_format` Problem: forge links are now rendered inline, making the `%l` EOL specifier redundant in the default format. Solution: change default `eol_format` from `'%l %c %r %d'` to `'%c %r %d'`. The `%l` specifier remains functional for users who explicitly set it. * test(forge): update specs for inline forge refs Problem: existing tests asserted that `parse.body()` stripped forge tokens from the description and populated `meta.forge_ref`. The `conceal_patterns` test referenced a removed function. Solution: update `parse.body` integration tests to assert tokens stay in the description. Add `find_refs()` tests covering single/multiple refs, URLs, byte offsets, and empty cases. Remove `conceal_patterns` test. Update diff tests to assert description includes the token. * docs: update forge links for inline overlay rendering Problem: documentation described forge tokens as stripped from the description and rendered via EOL `%l` specifier by default. Solution: update forge links section to describe inline conceal overlay rendering. Update default `eol_format` reference. Change `issue_format` field description from "EOL label" to "inline overlay label". * ci: format * refactor(forge): remove `%l` eol specifier, add `auto_close` config, fix icons Problem: `%l` was dead code after inline overlays replaced EOL rendering. Auto-close was always on with no opt-out. Forge icon defaults were empty strings. Solution: remove `%l` from the eol format parser and renderer. Add `forge.auto_close` (default `false`) to gate state-pull. Set nerd font icons: `` (GitHub), `` (GitLab), `` (Codeberg). Keep conceal active in insert mode via `concealcursor = 'nic'`. * fix(config): set correct nerd font icons for forge defaults * refactor(forge): replace curl/token auth with CLI-native API calls Problem: Forge metadata fetching required manual token management — config fields, CLI token extraction, and curl with auth headers. Each forge had a different auth path, and Codeberg had no CLI support at all. Solution: Delete `get_token()` and `_api_url()`, replace with `_api_args()` that builds `gh api`, `glab api`, or `tea api` arg arrays. The CLIs handle auth internally. Add `warn_missing_cli` config (default true) that warns once per forge per session on failure. Add forge CLI checks to `:checkhealth`. Remove `token` from config/docs. * refactor(forge): extract ForgeBackend class and registry Problem: adding a new forge required touching 5 lookup tables (`FORGE_HOSTS`, `FORGE_CLI`, `FORGE_AUTH_CMD`, `SHORTHAND_PREFIX`, `_warned_forges`) and every branching site in `_api_args`, `fetch_metadata`, and `parse_ref`. Solution: introduce a `ForgeBackend` class with `parse_url`, `api_args`, and `parse_state` methods, plus a `register()` / `backends()` registry. New forges (Gitea, Forgejo) are a single `register()` call via the `gitea_backend()` convenience constructor. * ci: format * feat(diff): disallow editing done tasks by default Problem: Done tasks could be freely edited in the buffer, leading to accidental modifications of completed work. Solution: Add a `lock_done` config option (default `true`) and a guard in `diff.apply()` that rejects field changes to done tasks unless the user toggles the checkbox back to pending first. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
5f50f45b55 |
refactor(forge): extract ForgeBackend class and registry (#129)
* docs: document S3 backend, auto-auth, and `:Pending done` command Problem: The S3 backend had no `:Pending s3` entry in the COMMANDS section, `:Pending auth` only mentioned Google, the `sync` config field omitted `s3`, `_s3_sync_id` was missing from the data format section, `:Pending done` was implemented but undocumented, and the README lacked a features overview. Solution: Add `:Pending s3` and `:Pending done` command docs, rewrite `:Pending auth` to cover all backends and sub-actions, update config and data format references, add `aws` CLI to requirements, and add a Features section to `README.md`. * feat(forge): add forge link parser and metadata fetcher Problem: no way to associate tasks with GitHub, GitLab, or Codeberg issues/PRs, or to track their remote state. Solution: add `forge.lua` with shorthand (`gh:user/repo#42`) and full URL parsing, async metadata fetching via `curl`, label formatting, conceal pattern generation, token resolution, and `refresh()` for state pull (closed/merged -> done). * feat(config): add forge config defaults and `%l` eol specifier Problem: no configuration surface for forge link rendering, icons, issue format, or self-hosted instances. Solution: add `pending.ForgeConfig` class with per-forge `token`, `icon`, `issue_format`, and `instances` fields. Add `%l` to the default `eol_format` so forge labels render in virtual text. * feat(parse): extract forge refs from task body Problem: `parse.body()` had no awareness of forge link tokens, so `gh:user/repo#42` stayed in the description instead of metadata. Solution: add `forge_ref` field to `pending.Metadata` and extend the right-to-left token loop in `body()` to call `forge.parse_ref()` as the final fallback before breaking. * feat(diff): persist forge refs in store on write Problem: forge refs parsed from buffer lines were discarded during diff reconciliation and never stored in the JSON. Solution: thread `forge_ref` through `parse_buffer` entries into `diff.apply`, storing it in `task._extra._forge_ref` for both new and existing tasks. * feat(views): pass forge ref and cache to line metadata Problem: `LineMeta` had no forge fields, so `buffer.lua` could not render forge labels or apply forge-specific highlights. Solution: add `forge_ref` and `forge_cache` fields to `LineMeta`, populated from `task._extra` in both `category_view` and `priority_view`. * feat(buffer): render forge links as concealed text with eol virt text Problem: forge tokens were visible as raw text with no virtual text labels, and the eol separator logic collapsed all gaps when non-adjacent specifiers were absent. Solution: add forge conceal syntax patterns in `setup_syntax()`, add `PendingForge`/`PendingForgeClosed` highlight groups, handle the `%l` specifier in `build_eol_virt()`, fix separator collapsing to buffer one separator between present segments, and change `concealcursor` to `nc` (reveal in visual and insert mode). * feat(complete): add forge shorthand omnifunc completions Problem: no completion support for `gh:`, `gl:`, or `cb:` tokens, requiring users to type owner/repo from memory. Solution: extend `omnifunc` to detect `gh:`/`gl:`/`cb:` prefixes and complete with `owner/repo#` candidates from existing forge refs in the store. * feat: trigger forge refresh on buffer open Problem: forge metadata was never fetched, so virt text highlights could not reflect remote issue/PR state. Solution: call `forge.refresh()` in `M.open()` so metadata is fetched once per `:Pending` invocation rather than on every render. * test(forge): add forge parsing spec Problem: no test coverage for forge link shorthand parsing, URL parsing, label formatting, or API URL generation. Solution: add `spec/forge_spec.lua` covering `_parse_shorthand`, `parse_ref` for all three forges, full URL parsing including nested GitLab groups, `format_label`, and `_api_url`. * docs: document forge links feature Problem: no user-facing documentation for forge link syntax, configuration, or behavior. Solution: add forge links section to `README.md` and `pending.txt` covering shorthand/URL syntax, config options, virtual text rendering, state pull, and auth resolution. * feat(forge): add `find_refs()` inline token scanner Problem: forge tokens were extracted by `parse.body()` which stripped them from the description, making editing awkward and multi-ref lines impossible. Solution: add `find_refs(text)` that scans a string for all forge tokens by whitespace tokenization, returning byte offsets and parsed refs without modifying the input. Remove unused `conceal_patterns()`. * refactor: move forge ref detection from `parse.body()` to `diff` Problem: `parse.body()` stripped forge tokens from the description, losing the raw text. This made inline overlay rendering impossible since the token no longer existed in the buffer. Solution: remove the `forge.parse_ref()` branch from `parse.body()` and call `forge.find_refs()` in `diff.parse_buffer()` instead. The description now retains forge tokens verbatim; `_extra._forge_ref` is still populated from the first matched ref. * feat(buffer): render forge links as inline conceal overlays Problem: forge tokens were stripped from the buffer and shown as EOL virtual text via `%l`. The token disappeared from the editable line, and multi-ref tasks broke. Solution: compute `forge_spans` in `views.lua` with byte offsets for each forge token in the rendered line. In `apply_inline_row()`, place extmarks with `conceal=''` and `virt_text_pos='inline'` to visually replace each raw token with its formatted label. Clear stale `forge_spans` on dirty rows to prevent `end_col` out-of-range errors after edits like `dd`. * fix(config): remove `%l` from default `eol_format` Problem: forge links are now rendered inline, making the `%l` EOL specifier redundant in the default format. Solution: change default `eol_format` from `'%l %c %r %d'` to `'%c %r %d'`. The `%l` specifier remains functional for users who explicitly set it. * test(forge): update specs for inline forge refs Problem: existing tests asserted that `parse.body()` stripped forge tokens from the description and populated `meta.forge_ref`. The `conceal_patterns` test referenced a removed function. Solution: update `parse.body` integration tests to assert tokens stay in the description. Add `find_refs()` tests covering single/multiple refs, URLs, byte offsets, and empty cases. Remove `conceal_patterns` test. Update diff tests to assert description includes the token. * docs: update forge links for inline overlay rendering Problem: documentation described forge tokens as stripped from the description and rendered via EOL `%l` specifier by default. Solution: update forge links section to describe inline conceal overlay rendering. Update default `eol_format` reference. Change `issue_format` field description from "EOL label" to "inline overlay label". * ci: format * refactor(forge): remove `%l` eol specifier, add `auto_close` config, fix icons Problem: `%l` was dead code after inline overlays replaced EOL rendering. Auto-close was always on with no opt-out. Forge icon defaults were empty strings. Solution: remove `%l` from the eol format parser and renderer. Add `forge.auto_close` (default `false`) to gate state-pull. Set nerd font icons: `` (GitHub), `` (GitLab), `` (Codeberg). Keep conceal active in insert mode via `concealcursor = 'nic'`. * fix(config): set correct nerd font icons for forge defaults * refactor(forge): replace curl/token auth with CLI-native API calls Problem: Forge metadata fetching required manual token management — config fields, CLI token extraction, and curl with auth headers. Each forge had a different auth path, and Codeberg had no CLI support at all. Solution: Delete `get_token()` and `_api_url()`, replace with `_api_args()` that builds `gh api`, `glab api`, or `tea api` arg arrays. The CLIs handle auth internally. Add `warn_missing_cli` config (default true) that warns once per forge per session on failure. Add forge CLI checks to `:checkhealth`. Remove `token` from config/docs. * refactor(forge): extract ForgeBackend class and registry Problem: adding a new forge required touching 5 lookup tables (`FORGE_HOSTS`, `FORGE_CLI`, `FORGE_AUTH_CMD`, `SHORTHAND_PREFIX`, `_warned_forges`) and every branching site in `_api_args`, `fetch_metadata`, and `parse_ref`. Solution: introduce a `ForgeBackend` class with `parse_url`, `api_args`, and `parse_state` methods, plus a `register()` / `backends()` registry. New forges (Gitea, Forgejo) are a single `register()` call via the `gitea_backend()` convenience constructor. * ci: format |
||
|
|
b60e58336e |
feat(forge): inline overlay rendering for forge links (#127)
* docs: document S3 backend, auto-auth, and `:Pending done` command Problem: The S3 backend had no `:Pending s3` entry in the COMMANDS section, `:Pending auth` only mentioned Google, the `sync` config field omitted `s3`, `_s3_sync_id` was missing from the data format section, `:Pending done` was implemented but undocumented, and the README lacked a features overview. Solution: Add `:Pending s3` and `:Pending done` command docs, rewrite `:Pending auth` to cover all backends and sub-actions, update config and data format references, add `aws` CLI to requirements, and add a Features section to `README.md`. * feat(forge): add forge link parser and metadata fetcher Problem: no way to associate tasks with GitHub, GitLab, or Codeberg issues/PRs, or to track their remote state. Solution: add `forge.lua` with shorthand (`gh:user/repo#42`) and full URL parsing, async metadata fetching via `curl`, label formatting, conceal pattern generation, token resolution, and `refresh()` for state pull (closed/merged -> done). * feat(config): add forge config defaults and `%l` eol specifier Problem: no configuration surface for forge link rendering, icons, issue format, or self-hosted instances. Solution: add `pending.ForgeConfig` class with per-forge `token`, `icon`, `issue_format`, and `instances` fields. Add `%l` to the default `eol_format` so forge labels render in virtual text. * feat(parse): extract forge refs from task body Problem: `parse.body()` had no awareness of forge link tokens, so `gh:user/repo#42` stayed in the description instead of metadata. Solution: add `forge_ref` field to `pending.Metadata` and extend the right-to-left token loop in `body()` to call `forge.parse_ref()` as the final fallback before breaking. * feat(diff): persist forge refs in store on write Problem: forge refs parsed from buffer lines were discarded during diff reconciliation and never stored in the JSON. Solution: thread `forge_ref` through `parse_buffer` entries into `diff.apply`, storing it in `task._extra._forge_ref` for both new and existing tasks. * feat(views): pass forge ref and cache to line metadata Problem: `LineMeta` had no forge fields, so `buffer.lua` could not render forge labels or apply forge-specific highlights. Solution: add `forge_ref` and `forge_cache` fields to `LineMeta`, populated from `task._extra` in both `category_view` and `priority_view`. * feat(buffer): render forge links as concealed text with eol virt text Problem: forge tokens were visible as raw text with no virtual text labels, and the eol separator logic collapsed all gaps when non-adjacent specifiers were absent. Solution: add forge conceal syntax patterns in `setup_syntax()`, add `PendingForge`/`PendingForgeClosed` highlight groups, handle the `%l` specifier in `build_eol_virt()`, fix separator collapsing to buffer one separator between present segments, and change `concealcursor` to `nc` (reveal in visual and insert mode). * feat(complete): add forge shorthand omnifunc completions Problem: no completion support for `gh:`, `gl:`, or `cb:` tokens, requiring users to type owner/repo from memory. Solution: extend `omnifunc` to detect `gh:`/`gl:`/`cb:` prefixes and complete with `owner/repo#` candidates from existing forge refs in the store. * feat: trigger forge refresh on buffer open Problem: forge metadata was never fetched, so virt text highlights could not reflect remote issue/PR state. Solution: call `forge.refresh()` in `M.open()` so metadata is fetched once per `:Pending` invocation rather than on every render. * test(forge): add forge parsing spec Problem: no test coverage for forge link shorthand parsing, URL parsing, label formatting, or API URL generation. Solution: add `spec/forge_spec.lua` covering `_parse_shorthand`, `parse_ref` for all three forges, full URL parsing including nested GitLab groups, `format_label`, and `_api_url`. * docs: document forge links feature Problem: no user-facing documentation for forge link syntax, configuration, or behavior. Solution: add forge links section to `README.md` and `pending.txt` covering shorthand/URL syntax, config options, virtual text rendering, state pull, and auth resolution. * feat(forge): add `find_refs()` inline token scanner Problem: forge tokens were extracted by `parse.body()` which stripped them from the description, making editing awkward and multi-ref lines impossible. Solution: add `find_refs(text)` that scans a string for all forge tokens by whitespace tokenization, returning byte offsets and parsed refs without modifying the input. Remove unused `conceal_patterns()`. * refactor: move forge ref detection from `parse.body()` to `diff` Problem: `parse.body()` stripped forge tokens from the description, losing the raw text. This made inline overlay rendering impossible since the token no longer existed in the buffer. Solution: remove the `forge.parse_ref()` branch from `parse.body()` and call `forge.find_refs()` in `diff.parse_buffer()` instead. The description now retains forge tokens verbatim; `_extra._forge_ref` is still populated from the first matched ref. * feat(buffer): render forge links as inline conceal overlays Problem: forge tokens were stripped from the buffer and shown as EOL virtual text via `%l`. The token disappeared from the editable line, and multi-ref tasks broke. Solution: compute `forge_spans` in `views.lua` with byte offsets for each forge token in the rendered line. In `apply_inline_row()`, place extmarks with `conceal=''` and `virt_text_pos='inline'` to visually replace each raw token with its formatted label. Clear stale `forge_spans` on dirty rows to prevent `end_col` out-of-range errors after edits like `dd`. * fix(config): remove `%l` from default `eol_format` Problem: forge links are now rendered inline, making the `%l` EOL specifier redundant in the default format. Solution: change default `eol_format` from `'%l %c %r %d'` to `'%c %r %d'`. The `%l` specifier remains functional for users who explicitly set it. * test(forge): update specs for inline forge refs Problem: existing tests asserted that `parse.body()` stripped forge tokens from the description and populated `meta.forge_ref`. The `conceal_patterns` test referenced a removed function. Solution: update `parse.body` integration tests to assert tokens stay in the description. Add `find_refs()` tests covering single/multiple refs, URLs, byte offsets, and empty cases. Remove `conceal_patterns` test. Update diff tests to assert description includes the token. * docs: update forge links for inline overlay rendering Problem: documentation described forge tokens as stripped from the description and rendered via EOL `%l` specifier by default. Solution: update forge links section to describe inline conceal overlay rendering. Update default `eol_format` reference. Change `issue_format` field description from "EOL label" to "inline overlay label". * ci: format * refactor(forge): remove `%l` eol specifier, add `auto_close` config, fix icons Problem: `%l` was dead code after inline overlays replaced EOL rendering. Auto-close was always on with no opt-out. Forge icon defaults were empty strings. Solution: remove `%l` from the eol format parser and renderer. Add `forge.auto_close` (default `false`) to gate state-pull. Set nerd font icons: `` (GitHub), `` (GitLab), `` (Codeberg). Keep conceal active in insert mode via `concealcursor = 'nic'`. * fix(config): set correct nerd font icons for forge defaults |
||
|
|
4c0ddad39c |
feat(forge): inline overlay rendering for forge links (#126)
* docs: document S3 backend, auto-auth, and `:Pending done` command Problem: The S3 backend had no `:Pending s3` entry in the COMMANDS section, `:Pending auth` only mentioned Google, the `sync` config field omitted `s3`, `_s3_sync_id` was missing from the data format section, `:Pending done` was implemented but undocumented, and the README lacked a features overview. Solution: Add `:Pending s3` and `:Pending done` command docs, rewrite `:Pending auth` to cover all backends and sub-actions, update config and data format references, add `aws` CLI to requirements, and add a Features section to `README.md`. * feat(forge): add forge link parser and metadata fetcher Problem: no way to associate tasks with GitHub, GitLab, or Codeberg issues/PRs, or to track their remote state. Solution: add `forge.lua` with shorthand (`gh:user/repo#42`) and full URL parsing, async metadata fetching via `curl`, label formatting, conceal pattern generation, token resolution, and `refresh()` for state pull (closed/merged -> done). * feat(config): add forge config defaults and `%l` eol specifier Problem: no configuration surface for forge link rendering, icons, issue format, or self-hosted instances. Solution: add `pending.ForgeConfig` class with per-forge `token`, `icon`, `issue_format`, and `instances` fields. Add `%l` to the default `eol_format` so forge labels render in virtual text. * feat(parse): extract forge refs from task body Problem: `parse.body()` had no awareness of forge link tokens, so `gh:user/repo#42` stayed in the description instead of metadata. Solution: add `forge_ref` field to `pending.Metadata` and extend the right-to-left token loop in `body()` to call `forge.parse_ref()` as the final fallback before breaking. * feat(diff): persist forge refs in store on write Problem: forge refs parsed from buffer lines were discarded during diff reconciliation and never stored in the JSON. Solution: thread `forge_ref` through `parse_buffer` entries into `diff.apply`, storing it in `task._extra._forge_ref` for both new and existing tasks. * feat(views): pass forge ref and cache to line metadata Problem: `LineMeta` had no forge fields, so `buffer.lua` could not render forge labels or apply forge-specific highlights. Solution: add `forge_ref` and `forge_cache` fields to `LineMeta`, populated from `task._extra` in both `category_view` and `priority_view`. * feat(buffer): render forge links as concealed text with eol virt text Problem: forge tokens were visible as raw text with no virtual text labels, and the eol separator logic collapsed all gaps when non-adjacent specifiers were absent. Solution: add forge conceal syntax patterns in `setup_syntax()`, add `PendingForge`/`PendingForgeClosed` highlight groups, handle the `%l` specifier in `build_eol_virt()`, fix separator collapsing to buffer one separator between present segments, and change `concealcursor` to `nc` (reveal in visual and insert mode). * feat(complete): add forge shorthand omnifunc completions Problem: no completion support for `gh:`, `gl:`, or `cb:` tokens, requiring users to type owner/repo from memory. Solution: extend `omnifunc` to detect `gh:`/`gl:`/`cb:` prefixes and complete with `owner/repo#` candidates from existing forge refs in the store. * feat: trigger forge refresh on buffer open Problem: forge metadata was never fetched, so virt text highlights could not reflect remote issue/PR state. Solution: call `forge.refresh()` in `M.open()` so metadata is fetched once per `:Pending` invocation rather than on every render. * test(forge): add forge parsing spec Problem: no test coverage for forge link shorthand parsing, URL parsing, label formatting, or API URL generation. Solution: add `spec/forge_spec.lua` covering `_parse_shorthand`, `parse_ref` for all three forges, full URL parsing including nested GitLab groups, `format_label`, and `_api_url`. * docs: document forge links feature Problem: no user-facing documentation for forge link syntax, configuration, or behavior. Solution: add forge links section to `README.md` and `pending.txt` covering shorthand/URL syntax, config options, virtual text rendering, state pull, and auth resolution. * feat(forge): add `find_refs()` inline token scanner Problem: forge tokens were extracted by `parse.body()` which stripped them from the description, making editing awkward and multi-ref lines impossible. Solution: add `find_refs(text)` that scans a string for all forge tokens by whitespace tokenization, returning byte offsets and parsed refs without modifying the input. Remove unused `conceal_patterns()`. * refactor: move forge ref detection from `parse.body()` to `diff` Problem: `parse.body()` stripped forge tokens from the description, losing the raw text. This made inline overlay rendering impossible since the token no longer existed in the buffer. Solution: remove the `forge.parse_ref()` branch from `parse.body()` and call `forge.find_refs()` in `diff.parse_buffer()` instead. The description now retains forge tokens verbatim; `_extra._forge_ref` is still populated from the first matched ref. * feat(buffer): render forge links as inline conceal overlays Problem: forge tokens were stripped from the buffer and shown as EOL virtual text via `%l`. The token disappeared from the editable line, and multi-ref tasks broke. Solution: compute `forge_spans` in `views.lua` with byte offsets for each forge token in the rendered line. In `apply_inline_row()`, place extmarks with `conceal=''` and `virt_text_pos='inline'` to visually replace each raw token with its formatted label. Clear stale `forge_spans` on dirty rows to prevent `end_col` out-of-range errors after edits like `dd`. * fix(config): remove `%l` from default `eol_format` Problem: forge links are now rendered inline, making the `%l` EOL specifier redundant in the default format. Solution: change default `eol_format` from `'%l %c %r %d'` to `'%c %r %d'`. The `%l` specifier remains functional for users who explicitly set it. * test(forge): update specs for inline forge refs Problem: existing tests asserted that `parse.body()` stripped forge tokens from the description and populated `meta.forge_ref`. The `conceal_patterns` test referenced a removed function. Solution: update `parse.body` integration tests to assert tokens stay in the description. Add `find_refs()` tests covering single/multiple refs, URLs, byte offsets, and empty cases. Remove `conceal_patterns` test. Update diff tests to assert description includes the token. * docs: update forge links for inline overlay rendering Problem: documentation described forge tokens as stripped from the description and rendered via EOL `%l` specifier by default. Solution: update forge links section to describe inline conceal overlay rendering. Update default `eol_format` reference. Change `issue_format` field description from "EOL label" to "inline overlay label". * ci: format |
||
|
|
0c1f509129 |
fix(config): update default keymaps to match vimdoc (#116)
Problem: four keymap defaults in `config.lua` still used the old deprecated keys (`!`, `D`, `U`, `F`) while `doc/pending.txt` documents the `g`-prefixed replacements (`g!`, `gd`, `gz`, `gf`). Solution: update `priority`, `date`, `undo`, and `filter` defaults to `g!`, `gd`, `gz`, and `gf` respectively. |
||
|
|
6b23e6810e |
feat: add <C-a> / <C-x> keymaps for priority increment/decrement (#114)
* feat(s3): create bucket interactively during auth when unconfigured Problem: when a user runs `:Pending s3 auth` with no bucket configured, auth succeeds but offers no way to create the bucket. The user must manually run `aws s3api create-bucket` and update their config. Solution: add `util.input()` coroutine-aware prompt wrapper and a `create_bucket()` flow in `s3.lua` that prompts for bucket name and region, handles the `us-east-1` LocationConstraint quirk, and logs a config snippet on success. Called automatically from `auth()` when `sync.s3.bucket` is absent. * ci: typing * feat(parse): add `parse_duration_to_days` for duration string conversion Problem: The archive command accepted only a bare integer for days, inconsistent with the `+Nd`/`+Nw`/`+Nm` duration syntax used elsewhere. Solution: Add `parse_duration_to_days()` supporting `Nd`, `Nw`, `Nm`, and bare integers. Returns nil on invalid input for caller error handling. * feat(archive): duration syntax and confirmation prompt Problem: `:Pending archive` accepted only a bare integer for days and silently deleted tasks with no confirmation, risking accidental data loss. Solution: Accept duration strings (`7d`, `3w`, `2m`) via `parse.parse_duration_to_days()`, show a `vim.ui.input` confirmation prompt before removing tasks, and skip the prompt when zero tasks match. * feat: add `<C-a>` / `<C-x>` keymaps for priority increment/decrement Problem: Priority could only be cycled with `g!` (0→1→2→3→0), with no way to directly increment or decrement. Solution: Add `adjust_priority()` with clamping at 0 and `max_priority`, exposed as `increment_priority()` / `decrement_priority()` on `<C-a>` / `<C-x>`. Includes `<Plug>` mappings and vimdoc. * fix(s3): use parenthetical defaults in bucket creation prompts Problem: `util.input` with `default` pre-filled the input field, and the success message said "Add to your config" ambiguously. Solution: Show defaults in prompt text as `(default)` instead of pre-filling, and clarify the message to "Add to your pending.nvim config". * ci: format |
||
|
|
d12838abbf |
feat: auth backend (#111)
* refactor(types): extract inline anonymous types into named classes
Problem: several functions used inline `{...}` table types in their
`@param` and `@return` annotations, making them hard to read and
impossible to reference from other modules.
Solution: extract each into a named `---@class`: `pending.Metadata`,
`pending.TaskFields`, `pending.CompletionItem`, `pending.SystemResult`,
and `pending.OAuthClientOpts`.
* refactor(sync): extract shared utilities into `sync/util.lua`
Problem: sync epilogue code (`s:save()`, `_recompute_counts()`,
`buffer.render()`) and `fmt_counts` were duplicated across `gcal.lua`
and `gtasks.lua`. The concurrency guard lived in `oauth.lua`, coupling
non-OAuth backends to the OAuth module.
Solution: create `sync/util.lua` with `async`, `system`, `with_guard`,
`finish`, and `fmt_counts`. Delegate from `oauth.lua` and replace
duplicated code in both backends. Add per-backend `auth()` and
`auth_complete()` methods to `gcal.lua` and `gtasks.lua`.
* feat(sync): auto-discover backends, per-backend auth, S3 backend
Problem: sync backends were hardcoded in `SYNC_BACKENDS` list in
`init.lua`, auth routed directly through `oauth.google_client`, and
adding a non-OAuth backend required editing multiple files.
Solution: replace hardcoded list with `discover_backends()` that globs
`lua/pending/sync/*.lua` at runtime. Rewrite `M.auth()` to dispatch
to per-backend `auth()` methods with `vim.ui.select` fallback. Add
`lua/pending/sync/s3.lua` with push/pull/sync via AWS CLI, per-task
merge by `_s3_sync_id` (UUID), and `pending.S3Config` type.
|
||
|
|
24bc1e395b |
feat: complete task editing coverage (#109)
Problem: the task editing surface had gaps — category and recurrence had no keymaps, `:Pending edit` required knowing the task ID, tasks couldn't be reordered with a keymap, priority was binary (0/1), and `wip`/`blocked` states were documented but unimplemented. Solution: fill every cell so every property is editable in every way. - `gc`/`gr` keymaps for category select and recurrence prompt - cursor-aware `:Pending edit` (omit ID to use task under cursor) - `J`/`K` keymaps to reorder tasks within a category - multi-level priorities (`max_priority` config, `g!` cycles 0→1→2→3→0) - `+!!`/`+!!!` tokens in `:Pending edit`, `:Pending add`, `parse.body()` - `PendingPriority2`/`PendingPriority3` highlight groups - `gw`/`gb` keymaps toggle `wip`/`blocked` status - `>`/`=` state chars in buffer rendering and diff parsing - `PendingWip`/`PendingBlocked` highlight groups - sort order: wip → pending → blocked → done - `wip`/`blocked` filter predicates and icons |
||
|
|
77e95b5772 |
refactor(config): nest view settings under view key (#103)
Problem: View-related config fields (`default_view`, `eol_format`, `category_order`, `folding`) are scattered as top-level siblings alongside unrelated fields like `data_path` and `date_syntax`. Solution: Group them under a `view` table with per-view sub-tables: `view.default`, `view.eol_format`, `view.category.order`, `view.category.folding`, and `view.queue` (empty, ready for #100). Update all call sites, tests, and vimdoc. |
||
|
|
0bbef5d010 |
feat: persistent inline extmarks and configurable EOL format (#97)
* refactor(buffer): split extmark namespace into `ns_eol` and `ns_inline` Problem: all extmarks shared a single `pending` namespace, making it impossible to selectively clear position-sensitive extmarks (overlays, highlights) while preserving stable EOL virtual text (due dates, recurrence). Solution: introduce `ns_eol` for end-of-line virtual text and `ns_inline` for overlays and highlights. `clear_marks()` and `apply_extmarks()` operate on both namespaces independently. * feat(buffer): track line changes via `on_bytes` to keep `_meta` aligned Problem: `_meta` is a positional array keyed by line number. Line insertions and deletions during editing desync it from actual buffer content, breaking `get_fold()`, cursor-based task lookups, and extmark re-application. Solution: attach an `on_bytes` callback that adjusts `_meta` on line insertions/deletions and tracks dirty rows. Remove the manual `_meta` insert from `open_line()` since `on_bytes` now handles it. Reset dirty rows on each full render. * feat(buffer): clear only inline extmarks on dirty rows during edits Problem: `TextChanged` cleared all extmarks (both namespaces) on every edit, causing EOL virtual text (due dates, recurrence) to vanish while the user types. Solution: replace blanket `clear_marks()` with per-row `clear_inline_row()` that only removes `ns_inline` extmarks on rows flagged dirty by `on_bytes`. EOL virtual text is preserved untouched. * feat(buffer): re-apply inline extmarks after edits Problem: inline extmarks (checkbox overlays, strikethrough, header highlights) were cleared during edits and only restored on `:w`, leaving the buffer visually bare while editing. Solution: extract `apply_inline_row()` from `apply_extmarks()` and call it via `reapply_dirty_inline()` on `InsertLeave` and normal-mode `TextChanged`. Insert-mode `TextChangedI` still only clears inline marks on dirty rows to avoid overlay flicker while typing. * fix(buffer): suppress `on_bytes` during render and fix definition order Problem: `on_bytes` fired during `render()`'s `nvim_buf_set_lines`, corrupting `_meta` with duplicate entries and causing out-of-range extmark errors. Also, `apply_inline_row` was defined after its first caller `reapply_dirty_inline`. Solution: add `_rendering` guard flag around `nvim_buf_set_lines` in `render()` so `on_bytes` is a no-op during authoritative renders. Move `apply_inline_row` above `reapply_dirty_inline` to satisfy Lua local scoping rules. * feat(buffer): add configurable `eol_format` for EOL virtual text Problem: EOL virtual text order (category → recurrence → due) and the double-space separator are hardcoded in `apply_extmarks()`. Users cannot reorder, omit, or restyle metadata fields. Solution: Add `eol_format` config field (default `'%c %r %d'`) with `%c`, `%r`, `%d` specifiers. `parse_eol_format()` tokenizes the format string; `build_eol_virt()` resolves specifiers against `LineMeta` and collapses literals around absent fields. * ci: format |
||
|
|
d8324e6a6d |
feat(buffer): add configurable category-level folds (#91)
Problem: category folds were hardcoded with no config option, no custom foldtext, and no vimdoc coverage. Solution: add `folding` config field (boolean or table with `foldtext` format string). Default foldtext is `%c (%n tasks)` with automatic singular/plural. Gate all fold logic on the config so `folding = false` disables folds entirely. Document the new option in vimdoc. |
||
|
|
9de53f2bb3 |
feat(sync): add opt-in remote deletion for gcal and gtasks (#85)
Problem: push/sync permanently deleted remote Google Calendar events and Google Tasks entries whenever a local task was marked deleted, done, or de-due'd. There was no opt-out, so a misfire could silently cause irreversible data loss on the remote side. Solution: add a `remote_delete` boolean to the config (default `false`). A unified flag at `sync.remote_delete` sets the base; per-backend overrides at `sync.gcal.remote_delete` / `sync.gtasks.remote_delete` take precedence when non-nil. When disabled, `_extra` remote IDs are cleared silently (unlinking) so stale IDs don't accumulate. |
||
|
|
710cf562c9 |
fix(diff): preserve due/rec when absent from buffer line (#68)
* fix(diff): preserve due/rec when absent from buffer line Problem: `diff.apply` overwrites `task.due` and `task.recur` with `nil` whenever those fields aren't present as inline tokens in the buffer line. Because metadata is rendered as virtual text (never in the line text), every description edit silently clears due dates and recurrence rules. Solution: Only update `due`, `recur`, and `recur_mode` in the existing- task branch when the parsed entry actually contains them (non-nil). Users can still set/change these inline by typing `due:<date>` or `rec:<rule>`; clearing them requires `:Pending edit <id> -due`. * refactor: remove project-local store discovery Problem: `store.resolve_path()` searched upward for `.pending.json`, silently splitting task data across multiple files depending on CWD. Solution: `resolve_path()` now always returns `config.get().data_path`. Remove `M.init()` and the `:Pending init` command and tab-completion entry. Remove the project-local health message. * refactor: extract log.lua, standardise [pending.nvim]: prefix Problem: Notifications were scattered across files using bare `vim.notify` with inconsistent `pending.nvim: ` prefixes, and the `debug` guard in `textobj.lua` and `init.lua` was duplicated inline. Solution: Add `lua/pending/log.lua` with `info`, `warn`, `error`, and `debug` functions (prefix `[pending.nvim]: `). `log.debug` only fires when `config.debug = true` or the optional `override` param is `true`. Replace all `vim.notify` callsites and remove inline debug guards. * feat(parse): configurable input date formats Problem: `due:` only accepted ISO `YYYY-MM-DD` and built-in keywords; users expecting locale-style dates like `03/15/2026` or `15-Mar-2026` had no way to configure alternative input formats. Solution: Add `input_date_formats` config field (string[]). Each entry is a strftime-like format string supporting `%Y`, `%y`, `%m`, `%d`, `%e`, `%b`, `%B`. Formats are tried in order after built-in keywords fail. When no year specifier is present the current or next year is inferred. Update vimdoc and add 8 parse_spec tests. |
||
|
|
ee362f7785 |
fix: harden sync backends and fix edit recompute (#66)
* refactor(oauth): async coroutine support, pure-Lua PKCE, server hardening Problem: OAuth module shelled out to openssl for PKCE, used blocking `vim.system():wait()`, had a weak `os.time()` PRNG seed, and the TCP callback server leaked on read errors with no timeout. Solution: Add `M.system()` coroutine wrapper and `M.async()` helper, replace openssl with `vim.fn.sha256` + `vim.base64.encode`, seed from `vim.uv.hrtime()`, add `close_server()` guard with 120s timeout, and close the server on read errors. * fix(gtasks): async operations, error notifications, buffer refresh Problem: Sync operations blocked the editor, `push_pass` silently dropped delete/update/create API errors, and the buffer was not re-rendered after push/pull/sync. Solution: Wrap `push`, `pull`, `sync` in `oauth.async()`, add `vim.notify` for all `push_pass` failure paths, and re-render the pending buffer after each operation. * fix(init): edit recompute, filter predicates, sync action listing Problem: `M.edit()` skipped `_recompute_counts()` after saving, `compute_hidden_ids` lacked `done`/`pending` predicates, and `run_sync` defaulted to `sync` instead of listing available actions. Solution: Replace `s:save()` with `_save_and_notify()` in `M.edit()`, add `done` and `pending` filter predicates, and list backend actions when no action is specified. * refactor(gcal): per-category calendars, async push, error notifications Problem: gcal used a single hardcoded calendar name, ran synchronously blocking the editor, and silently dropped some API errors. Solution: Fetch all calendars and map categories to calendars (creating on demand), wrap push in `oauth.async()`, notify on individual API failures, track `_gcal_calendar_id` in `_extra`, and remove the `$` anchor from `next_day` pattern. * refactor: formatting fixes, config cleanup, health simplification Problem: Formatter disagreements in `init.lua` and `gtasks.lua`, stale `calendar` field in gcal config, and redundant health checks for data directory existence. Solution: Apply stylua formatting, remove `calendar` field from `pending.GcalConfig`, drop data-dir and no-file health messages, add `done`/`pending` to filter tab-completion candidates. * docs: update vimdoc for sync refactor, remove demo scripts Problem: Docs still referenced openssl dependency, defaulting to `sync` action, and the `calendar` config field. Demo scripts used the old singleton `store` API. Solution: Update vimdoc and README to reflect explicit actions, per- category calendars, and pure-Lua PKCE. Remove stale demo scripts and update sync specs to match new behavior. * fix(types): correct LuaLS annotations in oauth and gcal |
||
|
|
6910bdb1be |
Google Tasks sync + shared OAuth module (#60)
* feat(gtasks): add Google Tasks bidirectional sync
Problem: pending.nvim only supported one-way push to Google Calendar.
Users who use Google Tasks had no way to sync tasks bidirectionally.
Solution: add `lua/pending/sync/gtasks.lua` backend with OAuth PKCE
auth, push/pull/sync actions, and field mapping between pending tasks
and Google Tasks (category↔tasklist, `priority`/`recur` via notes).
* refactor(cli): promote sync backends to top-level subcommands
Problem: `:Pending sync gtasks auth` required an extra `sync` keyword
that added no value and made the command unnecessarily verbose.
Solution: route `gtasks` and `gcal` as top-level `:Pending` subcommands
via `SYNC_BACKEND_SET` lookup. Tab completion introspects backend
modules for available actions instead of hardcoding `{ 'auth', 'sync' }`.
* docs(gtasks): document Google Tasks backend and CLI changes
Problem: vimdoc had no coverage for the gtasks backend and still
referenced the old `:Pending sync <backend>` command form.
Solution: add `:Pending-gtasks` and `:Pending-gcal` command sections
with per-action docs, update sync backend interface, and add gtasks
config example.
* ci: format
* refactor(sync): extract shared OAuth into `oauth.lua`
Problem: `gcal.lua` and `gtasks.lua` duplicated ~250 lines of identical
OAuth code (token management, PKCE flow, credential loading, curl
helpers, url encoding).
Solution: Extract a shared `OAuthClient` metatable in `oauth.lua` with
module-level utilities and instance methods. Both backends now delegate
all OAuth to `oauth.new()`. Skip `oauth` in `health.lua` backend
discovery by checking for a `name` field.
* feat(sync): ship bundled OAuth credentials
Problem: Users must manually create a Google Cloud project and place a
credentials JSON file before sync works — terrible onboarding.
Solution: Add `client_id`/`client_secret` fields to `GcalConfig` and
`GtasksConfig`. `oauth.lua` resolves credentials in three tiers: config
fields, credentials file, then bundled defaults (placeholders for now).
* docs(sync): document bundled credentials and config fields
* ci: format
|
||
|
|
a6be248cbf |
feat: Google Tasks bidirectional sync and CLI refactor (#59)
* feat(gtasks): add Google Tasks bidirectional sync
Problem: pending.nvim only supported one-way push to Google Calendar.
Users who use Google Tasks had no way to sync tasks bidirectionally.
Solution: add `lua/pending/sync/gtasks.lua` backend with OAuth PKCE
auth, push/pull/sync actions, and field mapping between pending tasks
and Google Tasks (category↔tasklist, `priority`/`recur` via notes).
* refactor(cli): promote sync backends to top-level subcommands
Problem: `:Pending sync gtasks auth` required an extra `sync` keyword
that added no value and made the command unnecessarily verbose.
Solution: route `gtasks` and `gcal` as top-level `:Pending` subcommands
via `SYNC_BACKEND_SET` lookup. Tab completion introspects backend
modules for available actions instead of hardcoding `{ 'auth', 'sync' }`.
* docs(gtasks): document Google Tasks backend and CLI changes
Problem: vimdoc had no coverage for the gtasks backend and still
referenced the old `:Pending sync <backend>` command form.
Solution: add `:Pending-gtasks` and `:Pending-gcal` command sections
with per-action docs, update sync backend interface, and add gtasks
config example.
* ci: format
|
||
|
|
910c8d2d69 |
refactor(icons): ascii defaults, checkbox overlays, and cleanup (#57)
* docs: remove unnecessary mini.ai recipe from vimdoc Problem: the `*pending-mini-ai*` section assumed mini.ai intercepts buffer-local `at`/`it`/`aC`/`iC` mappings, requiring a manual `vim.b.miniai_config` workaround. Solution: remove the section. Neovim's keymap resolver already prioritizes longer buffer-local mappings over mini.ai's global `a`/`i` handlers — no recipe needed. * refactor(icons): unify category/header icon and use checkbox overlays Problem: `header` and `category` were separate icons for the same concept. The icon overlay replaced `[ ]` with a bare character, hiding the markdown checkbox syntax. Header format `## ` produced a double-space with single-char icons. Solution: merge `header` into `category` (one icon for both header lines and EOL labels). Overlay renders `[icon]` preserving bracket syntax. Change header line format from `## ` to `# ` so the 2-char overlay (`# `) maps cleanly. * ci: remove empty `assets/` placeholder |
||
|
|
76aa22472d |
refactor(config): default icons to ascii (#55)
* refactor(config): default icons to ascii Problem: default icons used unicode characters (○, ✓, ●, ▸, ·, ↺) which render poorly in some terminals and font configurations. Solution: replace defaults with ascii equivalents (-, x, !, >, ., ~). Users can still override to unicode or nerd font icons via config. * ci: ignore library type checking |
||
|
|
5adf2341f3 |
feat(filter): wire F key and <Plug>(pending-filter) mapping (#53)
* refactor(config): remove legacy gcal top-level config key
Problem: the gcal migration shim silently accepted vim.g.pending = { gcal
= {...} } and copied it to sync.gcal, adding complexity and a deprecated
API surface.
Solution: remove the migration block in config.get(), drop the cfg.gcal
fallback in gcal_config(), delete the two migration tests, and clean up
the vimdoc references. Callers must now use sync.gcal directly.
* ci: fix
* fix(spec): remove duplicate buffer require in complete_spec
* docs(pending): reorganize vimdoc and fix incorrect defaults
Problem: sections were out of logical order — inline metadata appeared
before commands, GCal before its own backend framework, store resolution
duplicated and buried after health check. Two defaults were wrong:
default_category documented as 'Inbox' (should be 'Todo') and the gcal
calendar example used 'Tasks' (should be 'Pendings').
Solution: reorder all 21 sections into onboarding-first flow, add a
CONTENTS table with hyperlinks, fix both incorrect defaults in every
location they appeared, and remove the duplicate STORE RESOLUTION
section.
* feat(filter): wire F key and <Plug>(pending-filter) mapping
Problem: the filter predicate logic, diff guard, _on_write handling,
:Pending filter command, and filter_spec were already implemented, but
there was no buffer-local key to invoke filtering interactively.
Solution: add filter = 'F' to keymaps config and defaults, wire the
filter action in _setup_buf_mappings via vim.ui.input, add
<Plug>(pending-filter), and update the vimdoc (mappings table, Plug
section, config example, and FILTERS section).
|
||
|
|
0cdb4e6dd6 |
refactor(config): remove legacy gcal top-level config key (#51)
* refactor(config): remove legacy gcal top-level config key
Problem: the gcal migration shim silently accepted vim.g.pending = { gcal
= {...} } and copied it to sync.gcal, adding complexity and a deprecated
API surface.
Solution: remove the migration block in config.get(), drop the cfg.gcal
fallback in gcal_config(), delete the two migration tests, and clean up
the vimdoc references. Callers must now use sync.gcal directly.
* ci: fix
* fix(spec): remove duplicate buffer require in complete_spec
|
||
|
|
103e2036c8 |
refactor: remove file token feature (#50)
* refactor: remove file token feature Problem: The file metadata token (file:<path>:<line>) was implemented but is no longer wanted. Solution: Remove all traces — parse.lua token parsing, diff.lua reconciliation, views.lua LineMeta field, buffer.lua virtual text and PendingFile highlight, complete.lua omnifunc trigger, init.lua goto_file/add_here functions and -file edit token, plugin keymaps <Plug>(pending-goto-file) and <Plug>(pending-add-here), config.lua goto_file keymap field, vimdoc FILE TOKEN section, and spec/file_spec.lua. * ci: format |
||
|
|
dbd76d6759 |
feat(customization): icons config, PendingTab, and demo infrastructure (#46)
* feat(config): add icons table with unicode defaults * feat(buffer): render icon overlays from config.icons Problem: status characters ([ ], [x], [!]) and metadata prefixes are hardcoded literals with no user customization. Solution: read config.icons in apply_extmarks and apply overlay extmarks for checkboxes/headers, replace hardcoded recur ↺ with icons.recur, and prefix due/category virt_text with configurable icon characters. * feat(plugin): add PendingTab command and <Plug>(pending-tab) * docs: add icons config, PendingTab recipes, and demo infrastructure Problem: icon customization and auto-start workflow are undocumented; no demo asset exists for the README. Solution: document pending.Icons in vimdoc with nerd font and ASCII recipes, add PendingTab to commands and mappings, add open-on-startup recipe, add demo-init.lua and demo.tape for VHS screenshot generation, add assets/ directory, add README icons section and demo placeholder. * ci: format |
||
|
|
a2e0e296ac |
feat(file-token): file: inline metadata token with gf navigation (#45)
* feat(file-token): add file: inline metadata token with gf navigation Problem: there was no way to link a task to a specific location in a source file, or to quickly jump from a task to the relevant code. Solution: add a file:<path>:<line> inline token that stores a relative file reference in task._extra.file. Virtual text renders basename:line in a new PendingFile highlight group. A buffer-local gf mapping (configurable via keymaps.goto_file) opens the file at the given line. M.add_here() lets users attach the current cursor position to any task via vim.ui.select(). M.edit() gains -file support to clear the reference. <Plug>(pending-goto-file) and <Plug>(pending-add-here) are exposed for custom mappings. * test(file-token): add parse, diff, views, edit, and navigation tests Problem: the file: token implementation had no test coverage. Solution: add spec/file_spec.lua covering parse.body extraction, malformed token handling, duplicate token stop-parsing, diff reconciliation (store/update/clear/round-trip), LineMeta population in both views, :Pending edit -file, and goto_file notify paths for no-file and unreadable-file cases. All 292 tests pass. * style: apply stylua formatting * fix(types): remove empty elseif block, fix file? annotation nullability |
||
|
|
306e11aee6 |
feat(sync): backend interface + CLI refactor (#42)
* refactor(sync): extract backend interface, adapt gcal module
Problem: :Pending sync hardcodes Google Calendar — M.sync() does
pcall(require, 'pending.sync.gcal') and calls gcal.sync() directly.
The config has a flat gcal field. This prevents adding new sync backends
without modifying init.lua.
Solution: Define a backend interface contract (name, auth, sync, health
fields), refactor :Pending sync to dispatch via require('pending.sync.'
.. backend_name), add sync table to config with legacy gcal migration,
rename gcal.authorize to gcal.auth, add gcal.health for checkhealth,
and add tab completion for backend names and actions.
* docs(sync): update vimdoc for backend interface
Problem: Vimdoc documents :Pending sync as a bare command that pushes
to Google Calendar, with no mention of backends or the sync table config.
Solution: Update :Pending sync section to show {backend} [{action}]
syntax with examples, add SYNC BACKENDS section documenting the interface
contract, update config example to use sync.gcal, document legacy gcal
migration, and update health check description.
* test(sync): add backend dispatch tests
Problem: No test coverage for sync dispatch logic, config migration,
or gcal module interface conformance.
Solution: Add spec/sync_spec.lua with tests for: bare sync errors,
empty backend errors, unknown backend errors, unknown action errors,
default-to-sync routing, explicit sync/auth routing, legacy gcal config
migration, explicit sync.gcal precedence, and gcal module interface
fields (name, auth, sync, health).
|
||
|
|
92c2c670c5 |
feat: text objects and motions for the pending buffer (#39)
* feat: text objects and motions for the pending buffer
Problem: the pending buffer has action-button mappings but no Vim
grammar. You cannot dat to delete a task, cit to change a description,
or ]] to jump to the next category header.
Solution: add textobj.lua with at/it (a task / inner task), aC/iC
(a category / inner category), ]]/[[ (next/prev header), and ]t/[t
(next/prev task). All text objects work in operator-pending and visual
modes; motions work in normal, visual, and operator-pending. Mappings
are configurable via the keymaps table and exposed as <Plug> mappings.
* fix(textobj): escape Lua pattern hyphen, fix test expectations
Problem: inner_task_range used unescaped '-' in Lua patterns, which
acts as a lazy quantifier instead of matching a literal hyphen. The
metadata-stripping logic also tokenized the full line including the
prefix, so the rebuilt string could never be found after the prefix.
All test column expectations were off by one.
Solution: escape hyphens with %-, rewrite metadata stripping to
tokenize only the description portion after the prefix, and correct
all test assertions to match actual rendered column positions.
* feat(textobj): add debug mode, rename priority view buffer
Problem: the ]] motion reportedly lands one line past the header in
some environments, and ]t/[t may not override Neovim defaults. No
way to diagnose these at runtime. Also, pending://priority is a poor
buffer name for the flat ranked view.
Solution: add a debug config option (vim.g.pending = { debug = true })
that logs meta state, cursor positions, and mapping registration to
:messages at DEBUG level. Rename the buffer from pending://priority to
pending://queue. Internal view identifier stays 'priority'.
* docs: text objects, motions, debug mode, queue view rename
Problem: vimdoc had no documentation for the new text objects, motions,
debug config, or the pending://queue buffer rename.
Solution: add text object and motion tables to the mappings section,
document all eight <Plug> mappings, add debug field to the config
reference, update config example with new keymap defaults, rename
priority view references to queue throughout the vimdoc.
* fix(textobj): use correct config variable, raise log level
Problem: motion keymaps (]], [[, ]t, [t) were never set because
`config.get().debug` referenced an undefined `config` variable,
crashing _setup_buf_mappings before the motion loop. Debug logging
also used vim.log.levels.DEBUG which is filtered by default.
Solution: replace `config` with `cfg` (already in scope) and raise
both debug notify calls from DEBUG to INFO.
* ci: formt
|
||
|
|
f689cac70b |
feat: time-aware due dates, persistent undo, @return audit (#33)
* fix(plugin): allow command chaining with bar separator
Problem: :Pending|only failed because the command definition lacked the
bar attribute, causing | to be consumed as an argument.
Solution: Add bar = true to nvim_create_user_command so | is treated as
a command separator, matching fugitive's :Git behavior.
* refactor(buffer): remove opinionated window options
Problem: The plugin hardcoded number, relativenumber, wrap, spell,
signcolumn, foldcolumn, and cursorline in set_win_options, overriding
user preferences with no way to opt out.
Solution: Remove all cosmetic window options. Users who want them can
set them in after/ftplugin/pending.lua. Only conceallevel,
concealcursor, and winfixheight remain as functionally required.
* feat: time-aware due dates, persistent undo, @return audit
Problem: Due dates had no time component, the undo stack was lost on
restart and stored in a separate file, and many public functions lacked
required @return annotations.
Solution: Add YYYY-MM-DDThh:mm support across parse, views, recur,
complete, and init with time-aware overdue checks. Merge the undo stack
into the task store JSON so a single file holds all state. Add @return
nil annotations to all 27 void public functions across every module.
* feat(parse): flexible time parsing for @ suffix
Problem: the @HH:MM time suffix required zero-padded 24-hour format,
forcing users to write due:tomorrow@14:00 instead of due:tomorrow@2pm.
Solution: add normalize_time() that accepts bare hours (9, 14),
H:MM (9:30), am/pm (2pm, 9:30am, 12am), and existing HH:MM format,
normalizing all to canonical HH:MM on save.
* feat(complete): add info descriptions to omnifunc items
Problem: completion menu items had no description, making it hard to
distinguish between similar entries like date shorthands and recurrence
patterns.
Solution: return { word, info } tables from date_completions() and
recur_completions(), surfacing human-readable descriptions in the
completion popup.
* ci: format
|
||
|
|
5935124668 |
feat: omnifunc completion, recurring tasks, expanded date syntax (#27)
* feat(config): add recur_syntax and someday_date fields Problem: the plugin needs configuration for the recurrence token name and the sentinel date used by the `later`/`someday` named dates. Solution: add `recur_syntax` (default 'rec') and `someday_date` (default '9999-12-30') to pending.Config and the defaults table. * feat(parse): expand date vocabulary with named dates Problem: the date input only supports today, tomorrow, +Nd, and weekday names, lacking relative offsets like weeks/months, period boundaries, ordinals, month names, and backdating. Solution: add yesterday, eod, sow/eow, som/eom, soq/eoq, soy/eoy, +Nw, +Nm, -Nd, -Nw, ordinals (1st-31st), month names (jan-dec), and later/someday to resolve_date(). Add tests for all new tokens. * feat(recur): add recurrence parsing and next-date computation Problem: the plugin has no concept of recurring tasks, which is needed for habits and repeating deadlines. Solution: add recur.lua with parse(), validate(), next_due(), to_rrule(), and shorthand_list(). Supports named shorthands (daily, weekdays, weekly, etc.), interval notation (Nd, Nw, Nm, Ny), raw RRULE passthrough, and ! prefix for completion-based mode. Includes day-clamping for month/year advancement. * feat(store): add recur and recur_mode task fields Problem: the task schema has no fields for storing recurrence rules. Solution: add recur and recur_mode to the Task class, known_fields, task_to_table, table_to_task, and the add() signature. * feat(parse): add rec: inline token parsing Problem: the buffer parser does not recognize recurrence tokens, so users cannot set recurrence rules inline. Solution: add recur_key() helper and rec: token parsing in body() and command_add(), with ! prefix handling for completion-based mode and validation via recur.validate(). * feat(diff): propagate recurrence through buffer reconciliation Problem: the diff layer does not extract or apply recurrence fields, so rec: tokens written in the buffer are silently ignored on :w. Solution: add rec and rec_mode to ParsedEntry, extract them in parse_buffer(), and pass them through create and update paths in apply(). * feat(init): spawn next task on recurring task completion Problem: completing a recurring task does not create the next occurrence, and :Pending add does not pass recurrence fields. Solution: in toggle_complete(), detect recurrence and spawn a new pending task with the next due date. Wire rec/rec_mode through the add() command path. * feat(views): add recurrence to LineMeta Problem: LineMeta does not carry recurrence info, so the buffer layer cannot display recurrence indicators. Solution: add recur field to LineMeta and populate it in both category_view() and priority_view(). * feat(buffer): add PendingRecur highlight and recurrence virtual text Problem: recurring tasks have no visual indicator in the buffer, and the extmark logic uses a rigid if/elseif chain that does not compose well with additional virtual text fields. Solution: add PendingRecur highlight group linking to DiagnosticInfo. Refactor apply_extmarks() to build virtual text parts dynamically, appending category, recurrence indicator, and due date as separate composable segments. Set omnifunc on the pending buffer. * feat(complete): add omnifunc for cat:, due:, and rec: tokens Problem: the pending buffer has no completion source, requiring users to type metadata tokens from memory. Solution: add complete.lua with an omnifunc that completes cat: tokens from existing categories, due: tokens from the named date vocabulary, and rec: tokens from recurrence shorthands. * docs: document recurrence, expanded dates, omnifunc, new config Problem: the vimdoc does not cover recurrence, expanded date syntax, omnifunc completion, or the new config fields. Solution: add DATE INPUT and RECURRENCE sections, update INLINE METADATA, COMMANDS, CONFIGURATION, HIGHLIGHT GROUPS, HEALTH CHECK, and DATA FORMAT. Expand the help popup with recurrence patterns and new date tokens. Add recurrence validation to healthcheck. * ci: fix * fix(recur): resolve LuaLS type errors Problem: LuaLS reported undefined-field for `_raw` on RecurSpec and param-type-mismatch for `last_day.day` in `advance_date` because `osdate.day` infers as `string|integer`. Solution: Add `_raw` to the RecurSpec class annotation and cast `last_day.day` to integer in both `math.min` call sites. * refactor(init): remove help popup, use config-driven keymaps Problem: Buffer-local keymaps were hardcoded with no way for users to customize them. The g? help popup duplicated information already in the vimdoc. Solution: Remove show_help() and the g? mapping. Refactor _setup_buf_mappings to read from cfg.keymaps, letting users override or disable any buffer-local binding via vim.g.pending. * feat(config): add keymaps table for buffer-local bindings Problem: Users had no way to customize or disable buffer-local key bindings in the pending buffer. Solution: Add a pending.Keymaps class and keymaps field to pending.Config with defaults for all eight buffer actions. Setting any key to false disables that binding. * feat(plugin): add Plug mappings for all buffer actions Problem: Only five of nine buffer actions had <Plug> mappings, so users could not bind close, undo, open-line, or open-line-above globally. Solution: Add <Plug>(pending-close), <Plug>(pending-undo), <Plug>(pending-open-line), and <Plug>(pending-open-line-above). * docs: update mappings and config for keymaps and new Plug entries Problem: Vimdoc still listed g? help popup, lacked documentation for the four new <Plug> mappings, and had no keymaps config section. Solution: Remove g? from mappings table, document all nine <Plug> mappings, add keymaps table to the config example and field reference, and note that buffer-local keys are configurable. |
||
|
|
d1be0985b6 |
feat(buffer): open as bottom-drawer split like fugitive (#23)
* feat(buffer): open as bottom-drawer split like fugitive Problem: :Pending replaced the current buffer, making it impossible to view tasks alongside the file being edited. No way to close the drawer without :q or switching buffers manually. Solution: open the task buffer in a botright horizontal split instead of replacing the current buffer. Track the drawer window ID so re-opening focuses it rather than creating a second split. Set winfixheight so the drawer keeps its height when other windows open or close. Add q/<Esc> mappings to close the drawer, and a WinClosed autocmd to clear the tracked window ID when the user closes it manually. Add drawer_height config option (default 15). * fix(buffer): default to natural split height like fugitive Problem: hardcoded drawer_height=15 was too small and diverged from fugitive's model. Fugitive issues a plain botright split and lets Vim's own split rules (equalalways, winheight) divide the available space. Solution: remove the default height so the split sizes naturally. Only call nvim_win_set_height when the user sets drawer_height to a positive value, preserving the opt-in customization path. |
||
|
|
e04440bb3d |
refactor: adopt markdown-style checkbox buffer format (#20)
* refactor(config): change default category from Inbox to Todo * refactor(views): adopt markdown checkbox line format Problem: task lines used an opaque /ID/ [N] prefix format that was hard to read and inconsistent between category and priority views. Header lines had no visual marker distinguishing them from tasks. Solution: render headers as '## Cat', task lines as '/ID/- [x|!| ] description'. State encoding: [x]=done, [!]=urgent, [ ]=pending. Both views use the same construction. * refactor(diff): parse and reconcile markdown checkbox format Problem: parse_buffer matched the old ' text' indent pattern and detected headers via '^%S'. Priority was read from a '[N] ' prefix. apply() never reconciled status changes written into the buffer. Solution: match '- [.] text' for tasks and '^## ' for headers. Extract state char to derive priority (! -> 1) and status (x -> done). apply() now reconciles status from the buffer, setting/clearing 'end' timestamps — enabling the oil-style edit-checkbox-then-:w workflow. * refactor(buffer): update syntax, extmarks, and render for checkbox format Problem: syntax patterns matched the old indent/[N] format; right_align virtual text produced a broken layout in narrow windows; the done strikethrough skipped past the ' ' indent leaving '- [x] ' unstyled; render() added undo history entries so 'u' could undo a re-render. Solution: update taskHeader/taskLine patterns for '## '/'- [.]'; rename taskPriority -> taskCheckbox matching '[!]'; switch virt_text_pos to 'eol'; drop the +2 col_start offset so strikethrough covers '- [x] '; guard nvim_buf_set_lines with undolevels=-1 so renders are not undoable. Also fix open_line to insert '- [ ] ' and position cursor at col 6. * refactor(init): replace multi-level priority with binary toggle Problem: <C-a>/<C-x> overrode Vim's native number increment and the visual g<C-a>/g<C-x> variants added complexity for marginal value. toggle_complete() left the cursor on the wrong line after re-render. Solution: remove change_priority/change_priority_visual; add toggle_priority() (0<->1) mapped to '!', with cursor-follow after render matching the pattern already used in priority toggle. Add cursor-follow to toggle_complete() for the same reason. Update plugin plugs (priority-up/down -> priority) and add 'due'/'undo' to the :Pending completion list. Update help text accordingly. * feat(buffer): reflect current view in buffer name Problem: no way to tell at a glance which view (category vs priority) is active — the buffer was always named 'pending://'. Solution: update the buffer name to 'pending://category' or 'pending://priority' on every render, so the view is visible in the statusline/tabline without any extra UI. |
||
|
|
3a35fab6cf |
feat: overdue highlighting, relative dates, undo write, buffer mappings (#1)
* feat(config): add category_order field Problem: category display order was always insertion order with no way to configure it. Solution: add category_order to config defaults so users can declare a preferred category ordering; unspecified categories append after. * feat(parse): add relative date resolution Problem: due dates required full YYYY-MM-DD input, adding friction for common cases like "today" or "next monday". Solution: add resolve_date() supporting today, tomorrow, +Nd, and weekday abbreviations; extend inline token parsing to resolve relative values before falling back to strict date validation. * feat(views): overdue flag, category in priority view, category ordering Problem: overdue tasks were visually indistinct from upcoming ones; priority view had no category context; category display order was not configurable. Solution: compute overdue meta flag for pending tasks past their due date; set show_category on priority view task meta; reorder categories according to config.category_order when present. * feat(buffer): overdue highlight, category virt text in priority view Problem: overdue tasks had no visual distinction; priority view showed no category context alongside due dates. Solution: add PendingOverdue highlight group; render category name as right-aligned virtual text in priority view, composited with the due date when both are present. * feat(init): undo write and buffer-local default mappings Problem: _undo_state was captured on every save but never consumed; toggle_priority and prompt_date had no buffer-local defaults, requiring manual <Plug> configuration. Solution: implement undo_write() to restore pre-save task state; add !, d, and U as buffer-local defaults following fugitive's philosophy of owning the buffer; expose :Pending undo as a command alias. * test(views): add views spec Problem: views.lua had no test coverage. Solution: add 26 tests covering category_view and priority_view including sort order, line format, overdue detection, show_category meta, and category_order config behavior. * test(archive): add archive spec Problem: archive had no test coverage. Solution: add 9 tests covering cutoff logic, custom day counts, pending task preservation, deleted task cleanup, and notify output. * docs: add vimdoc Problem: no :help documentation existed. Solution: add doc/pending.txt covering all features — commands, mappings, views, configuration, Google Calendar sync, highlight groups, data format, and health check — following standard vimdoc conventions. * ci: format * fix: resolve lint and type check errors Problem: selene flagged unused variables in new spec files; LuaLS flagged os.date/os.time return type mismatches, integer? assignments, and stale task.Task/task.GcalConfig type references. Solution: prefix unused spec variables with _ or drop unnecessary assignments; add --[[@as string/integer]] casts for os.date and os.time calls; add category_order field to pending.Config annotation; fix task.GcalConfig -> pending.GcalConfig and task.Task[] -> pending.Task[]; add nil guards on meta[row].id before store calls; cast store.data() return to non-optional. * ci: format * fix: sync * ci: format |
||
| 53ab1cc000 | feat: rename |
Renamed from lua/todo/config.lua (Browse further)