diff --git a/lua/pending/config.lua b/lua/pending/config.lua index ac98b64..a1767db 100644 --- a/lua/pending/config.lua +++ b/lua/pending/config.lua @@ -2,6 +2,9 @@ ---@field calendar? string ---@field credentials_path? string +---@class pending.SyncConfig +---@field gcal? pending.GcalConfig + ---@class pending.Keymaps ---@field close? string|false ---@field toggle? string|false @@ -32,6 +35,7 @@ ---@field drawer_height? integer ---@field debug? boolean ---@field keymaps pending.Keymaps +---@field sync? pending.SyncConfig ---@field gcal? pending.GcalConfig ---@class pending.config @@ -65,6 +69,7 @@ local defaults = { next_task = ']t', prev_task = '[t', }, + sync = {}, } ---@type pending.Config? @@ -77,6 +82,10 @@ function M.get() end local user = vim.g.pending or {} _resolved = vim.tbl_deep_extend('force', defaults, user) + if _resolved.gcal and not (_resolved.sync and _resolved.sync.gcal) then + _resolved.sync = _resolved.sync or {} + _resolved.sync.gcal = _resolved.gcal + end return _resolved end diff --git a/lua/pending/health.lua b/lua/pending/health.lua index cc285e0..93f7c72 100644 --- a/lua/pending/health.lua +++ b/lua/pending/health.lua @@ -47,16 +47,18 @@ function M.check() vim.health.info('No data file yet (will be created on first save)') end - if vim.fn.executable('curl') == 1 then - vim.health.ok('curl found (required for Google Calendar sync)') + local sync_paths = vim.fn.globpath(vim.o.runtimepath, 'lua/pending/sync/*.lua', false, true) + if #sync_paths == 0 then + vim.health.info('No sync backends found') else - vim.health.warn('curl not found (needed for Google Calendar sync)') - end - - if vim.fn.executable('openssl') == 1 then - vim.health.ok('openssl found (required for OAuth PKCE)') - else - vim.health.warn('openssl not found (needed for Google Calendar OAuth)') + for _, path in ipairs(sync_paths) do + local name = vim.fn.fnamemodify(path, ':t:r') + local bok, backend = pcall(require, 'pending.sync.' .. name) + if bok and type(backend.health) == 'function' then + vim.health.start('pending.nvim: sync/' .. name) + backend.health() + end + end end end diff --git a/lua/pending/init.lua b/lua/pending/init.lua index 0fcd564..cae13a9 100644 --- a/lua/pending/init.lua +++ b/lua/pending/init.lua @@ -414,14 +414,25 @@ function M.add(text) vim.notify('Pending added: ' .. description) end +---@param backend_name string +---@param action? string ---@return nil -function M.sync() - local ok, gcal = pcall(require, 'pending.sync.gcal') - if not ok then - vim.notify('Google Calendar sync module not available.', vim.log.levels.ERROR) +function M.sync(backend_name, action) + if not backend_name or backend_name == '' then + vim.notify('Usage: :Pending sync [action]', vim.log.levels.ERROR) return end - gcal.sync() + action = (action and action ~= '') and action or 'sync' + local ok, backend = pcall(require, 'pending.sync.' .. backend_name) + if not ok then + vim.notify('Unknown sync backend: ' .. backend_name, vim.log.levels.ERROR) + return + end + if type(backend[action]) ~= 'function' then + vim.notify(backend_name .. " backend has no '" .. action .. "' action", vim.log.levels.ERROR) + return + end + backend[action]() end ---@param days? integer @@ -700,7 +711,8 @@ function M.command(args) local id_str, edit_rest = rest:match('^(%S+)%s*(.*)') M.edit(id_str, edit_rest) elseif cmd == 'sync' then - M.sync() + local backend, action = rest:match('^(%S+)%s*(.*)') + M.sync(backend, action) elseif cmd == 'archive' then local d = rest ~= '' and tonumber(rest) or nil M.archive(d) diff --git a/lua/pending/sync/gcal.lua b/lua/pending/sync/gcal.lua index 3b29b33..843f310 100644 --- a/lua/pending/sync/gcal.lua +++ b/lua/pending/sync/gcal.lua @@ -3,6 +3,8 @@ local store = require('pending.store') local M = {} +M.name = 'gcal' + local BASE_URL = 'https://www.googleapis.com/calendar/v3' local TOKEN_URL = 'https://oauth2.googleapis.com/token' local AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth' @@ -22,7 +24,7 @@ local SCOPE = 'https://www.googleapis.com/auth/calendar' ---@return table local function gcal_config() local cfg = config.get() - return cfg.gcal or {} + return (cfg.sync and cfg.sync.gcal) or cfg.gcal or {} end ---@return string @@ -199,7 +201,7 @@ local function get_access_token() end local tokens = load_tokens() if not tokens or not tokens.refresh_token then - M.authorize() + M.auth() tokens = load_tokens() if not tokens then return nil @@ -218,7 +220,7 @@ local function get_access_token() return tokens.access_token end -function M.authorize() +function M.auth() local creds = load_credentials() if not creds then vim.notify( @@ -514,4 +516,18 @@ function M.sync() ) end +---@return nil +function M.health() + if vim.fn.executable('curl') == 1 then + vim.health.ok('curl found (required for gcal sync)') + else + vim.health.warn('curl not found (needed for gcal sync)') + end + if vim.fn.executable('openssl') == 1 then + vim.health.ok('openssl found (required for gcal OAuth PKCE)') + else + vim.health.warn('openssl not found (needed for gcal OAuth)') + end +end + return M diff --git a/plugin/pending.lua b/plugin/pending.lua index f9a8df1..839b351 100644 --- a/plugin/pending.lua +++ b/plugin/pending.lua @@ -171,6 +171,34 @@ end, { if cmd_line:match('^Pending%s+edit') then return complete_edit(arg_lead, cmd_line) end + if cmd_line:match('^Pending%s+sync') then + local after_sync = cmd_line:match('^Pending%s+sync%s+(.*)') + if not after_sync then + return {} + end + local parts = {} + for part in after_sync:gmatch('%S+') do + table.insert(parts, part) + end + local trailing_space = after_sync:match('%s$') + if #parts == 0 or (#parts == 1 and not trailing_space) then + local backends = {} + local pattern = vim.fn.globpath(vim.o.runtimepath, 'lua/pending/sync/*.lua', false, true) + for _, path in ipairs(pattern) do + local name = vim.fn.fnamemodify(path, ':t:r') + table.insert(backends, name) + end + table.sort(backends) + return filter_candidates(arg_lead, backends) + end + if #parts == 1 and trailing_space then + return filter_candidates(arg_lead, { 'auth', 'sync' }) + end + if #parts >= 2 and not trailing_space then + return filter_candidates(arg_lead, { 'auth', 'sync' }) + end + return {} + end return {} end, })