From e8894d7d8bc12283b2c9d283cf0eed10982bc6b8 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 5 Mar 2026 00:59:08 -0500 Subject: [PATCH] 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 ` command form. Solution: add `:Pending-gtasks` and `:Pending-gcal` command sections with per-action docs, update sync backend interface, and add gtasks config example. --- doc/pending.txt | 136 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 115 insertions(+), 21 deletions(-) diff --git a/doc/pending.txt b/doc/pending.txt index 01728a3..08c6315 100644 --- a/doc/pending.txt +++ b/doc/pending.txt @@ -41,6 +41,7 @@ Features: ~ - Foldable category sections (`zc`/`zo`) in category view - Omnifunc completion for `cat:`, `due:`, and `rec:` tokens (``) - Google Calendar one-way push via OAuth PKCE +- Google Tasks bidirectional sync via OAuth PKCE ============================================================================== CONTENTS *pending-contents* @@ -63,15 +64,16 @@ CONTENTS *pending-contents* 16. Recipes ............................................... |pending-recipes| 17. Sync Backends ................................... |pending-sync-backend| 18. Google Calendar .......................................... |pending-gcal| - 19. Data Format .............................................. |pending-data| - 20. Health Check ........................................... |pending-health| + 19. Google Tasks ............................................ |pending-gtasks| + 20. Data Format .............................................. |pending-data| + 21. Health Check ........................................... |pending-health| ============================================================================== REQUIREMENTS *pending-requirements* - Neovim 0.10+ - No external dependencies for local use -- `curl` and `openssl` are required for the `gcal` sync backend +- `curl` and `openssl` are required for the `gcal` and `gtasks` sync backends ============================================================================== INSTALL *pending-install* @@ -146,24 +148,42 @@ COMMANDS *pending-commands* Populate the quickfix list with all tasks that are overdue or due today. Open the list with |:copen| to navigate to each task's category. - *:Pending-sync* -:Pending sync {backend} [{action}] - Run a sync action against a named backend. {backend} is required — bare - `:Pending sync` prints a usage message. {action} defaults to `sync` - when omitted. Each backend lives at `lua/pending/sync/.lua`. + *:Pending-gtasks* +:Pending gtasks [{action}] + Run a Google Tasks sync action. {action} defaults to `sync` when omitted. + + Actions: ~ + `sync` Push local changes then pull remote changes (default). + `push` Push local changes to Google Tasks only. + `pull` Pull remote changes from Google Tasks only. + `auth` Run the OAuth authorization flow. Examples: >vim - :Pending sync gcal " runs gcal.sync() - :Pending sync gcal auth " runs gcal.auth() - :Pending sync gcal sync " explicit sync (same as bare) + :Pending gtasks " push then pull (default) + :Pending gtasks push " push local → Google Tasks + :Pending gtasks pull " pull Google Tasks → local + :Pending gtasks auth " authorize < - Tab completion after `:Pending sync ` lists discovered backends. - Tab completion after `:Pending sync gcal ` lists available actions. + Tab completion after `:Pending gtasks ` lists available actions. + See |pending-gtasks| for full details. - Built-in backends: ~ + *:Pending-gcal* +:Pending gcal [{action}] + Run a Google Calendar sync action. {action} defaults to `sync` when + omitted. - `gcal` Google Calendar one-way push. See |pending-gcal|. + Actions: ~ + `sync` Push tasks with due dates to Google Calendar (default). + `auth` Run the OAuth authorization flow. + + Examples: >vim + :Pending gcal " push to Google Calendar (default) + :Pending gcal auth " authorize +< + + Tab completion after `:Pending gcal ` lists available actions. + See |pending-gcal| for full details. *:Pending-filter* :Pending filter {predicates} @@ -590,6 +610,9 @@ loads: >lua calendar = 'Pendings', credentials_path = '/path/to/client_secret.json', }, + gtasks = { + credentials_path = '/path/to/client_secret.json', + }, }, } < @@ -870,21 +893,30 @@ Open tasks in a new tab on startup: >lua SYNC BACKENDS *pending-sync-backend* Sync backends are Lua modules under `lua/pending/sync/.lua`. Each -module returns a table conforming to the backend interface: >lua +backend is exposed as a top-level `:Pending` subcommand: >vim + :Pending gtasks [action] + :Pending gcal [action] +< + +Each module returns a table conforming to the backend interface: >lua ---@class pending.SyncBackend ---@field name string ---@field auth fun(): nil ---@field sync fun(): nil + ---@field push? fun(): nil + ---@field pull? fun(): nil ---@field health? fun(): nil < Required fields: ~ {name} Backend identifier (matches the filename). - {sync} Main sync action. Called by `:Pending sync `. - {auth} Authorization flow. Called by `:Pending sync auth`. + {sync} Main sync action. Called by `:Pending `. + {auth} Authorization flow. Called by `:Pending auth`. Optional fields: ~ + {push} Push-only action. Called by `:Pending push`. + {pull} Pull-only action. Called by `:Pending pull`. {health} Called by `:checkhealth pending` to report backend-specific diagnostics (e.g. checking for external tools). @@ -923,7 +955,7 @@ Fields: ~ that Google provides or as a bare credentials object. OAuth flow: ~ -On the first `:Pending sync gcal` call the plugin detects that no refresh token +On the first `:Pending gcal` call the plugin detects that no refresh token exists and opens the Google authorization URL in the browser using |vim.ui.open()|. A temporary local HTTP server listens on port 18392 for the OAuth redirect. The PKCE (Proof Key for Code Exchange) flow is used — @@ -933,7 +965,7 @@ authorization code is exchanged for tokens and the refresh token is stored at use the stored refresh token and refresh the access token automatically when it is about to expire. -`:Pending sync gcal` behavior: ~ +`:Pending gcal` behavior: ~ For each task in the store: - A pending task with a due date and no existing event: a new all-day event is created and the event ID is stored in the task's `_extra` table. @@ -946,6 +978,67 @@ For each task in the store: A summary notification is shown after sync: `created: N, updated: N, deleted: N`. +============================================================================== +GOOGLE TASKS *pending-gtasks* + +pending.nvim can sync tasks bidirectionally with Google Tasks. Each +pending.nvim category maps to a Google Tasks list of the same name. Lists are +created automatically on first sync. + +Configuration: >lua + vim.g.pending = { + sync = { + gtasks = { + credentials_path = '/path/to/client_secret.json', + }, + }, + } +< + + *pending.GtasksConfig* +Fields: ~ + {credentials_path} (string) + Path to the OAuth client secret JSON file downloaded + from the Google Cloud Console. Default: + `stdpath('data')..'/pending/gtasks_credentials.json'`. + Accepts the `installed` wrapper format or a bare + credentials object. + +OAuth flow: ~ +Same PKCE flow as the gcal backend; listens on port 18393. Tokens are stored +at `stdpath('data')/pending/gtasks_tokens.json`. Run `:Pending gtasks auth` +to authorize; subsequent syncs refresh the token automatically. + +`:Pending gtasks` actions: ~ + +`:Pending gtasks` (or `:Pending gtasks sync`) runs push then pull. Use +`:Pending gtasks push` or `:Pending gtasks pull` to run only one direction. + +Push (local → Google Tasks, `:Pending gtasks push`): +- Pending task with no `_gtasks_task_id`: created in the matching list. +- Pending task with an existing ID: updated in Google Tasks. +- Done task with an existing ID: marked `completed` in Google Tasks. +- Deleted task with an existing ID: deleted from Google Tasks. + +Pull (Google Tasks → local, `:Pending gtasks pull`): +- GTasks task already known (matched by `_gtasks_task_id`): updated locally + if `gtasks.updated` timestamp is newer than `task.modified`. +- GTasks task not known locally: created as a new pending.nvim task in the + category matching the list name. + +Field mapping: ~ + {title} ↔ task description + {status} `needsAction` ↔ `pending`, `completed` ↔ `done` + {due} date-only; time component ignored (GTasks limitation) + {notes} serializes extra fields: `pri:1 rec:weekly` + +The `notes` field is used exclusively for pending.nvim metadata. Any existing +notes on tasks created outside pending.nvim are parsed for known tokens and +the remainder is ignored. + +Recurrence (`rec:`) is stored in notes for round-tripping but is not +expanded by Google Tasks (GTasks has no recurrence API). + ============================================================================== DATA FORMAT *pending-data* @@ -979,7 +1072,8 @@ Task fields: ~ Any field not in the list above is preserved in `_extra` and written back on save. This is used internally to store the Google Calendar event ID -(`_gcal_event_id`) and allows third-party tooling to annotate tasks without +(`_gcal_event_id`) and Google Tasks IDs (`_gtasks_task_id`, +`_gtasks_list_id`), and allows third-party tooling to annotate tasks without data loss. The `version` field is checked on load. If the file version is newer than the