From 904be4e9105e6a98f29f4124f3c123ebbeec8965 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 26 Feb 2026 22:38:35 -0500 Subject: [PATCH] refactor: remove file token feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: The file metadata token (file::) 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 (pending-goto-file) and (pending-add-here), config.lua goto_file keymap field, vimdoc FILE TOKEN section, and spec/file_spec.lua. --- doc/pending.txt | 78 +-------- lua/pending/buffer.lua | 5 - lua/pending/complete.lua | 1 - lua/pending/config.lua | 1 - lua/pending/diff.lua | 15 -- lua/pending/init.lua | 115 +------------ lua/pending/parse.lua | 17 +- lua/pending/views.lua | 3 - plugin/pending.lua | 10 -- spec/file_spec.lua | 358 --------------------------------------- 10 files changed, 6 insertions(+), 597 deletions(-) delete mode 100644 spec/file_spec.lua diff --git a/doc/pending.txt b/doc/pending.txt index fc04dc4..f8c4b50 100644 --- a/doc/pending.txt +++ b/doc/pending.txt @@ -30,7 +30,7 @@ concealed tokens and are never visible during editing. Features: ~ - Oil-style buffer editing: standard Vim motions for all task operations -- Inline metadata syntax: `due:`, `cat:`, `rec:`, and `file:` tokens parsed on `:w` +- Inline metadata syntax: `due:`, `cat:`, and `rec:` tokens parsed on `:w` - Relative date input: `today`, `tomorrow`, `+Nd`, `+Nw`, `+Nm`, weekday names, month names, ordinals, and more - Recurring tasks with automatic next-date spawning on completion @@ -101,7 +101,6 @@ Supported tokens: ~ `due:` Resolve a named date (see |pending-dates| below). `cat:Name` Move the task to the named category on save. `rec:` Set a recurrence rule (see |pending-recurrence|). - `file::` Attach a file reference (see |pending-file-token|). The token name for due dates defaults to `due` and is configurable via `date_syntax` in |pending-config|. The token name for recurrence defaults to @@ -119,45 +118,12 @@ placed under the `Errands` category header. Parsing stops at the first token that is not a recognised metadata token. Repeated tokens of the same type also stop parsing — only one `due:`, one -`cat:`, one `rec:`, and one `file:` per task line are consumed. +`cat:`, and one `rec:` per task line are consumed. Omnifunc completion is available for `due:`, `cat:`, and `rec:` token types. In insert mode, type the token prefix and press `` to see suggestions. -============================================================================== -FILE TOKEN *pending-file-token* - -The `file:` inline token attaches a source file reference to a task. The -syntax is: > - - file:: -< - -The path is stored relative to the directory containing the data file. The -token is rendered as virtual text at the end of the task line, showing only -the basename and line number (e.g. `auth.lua:42`) using the |PendingFile| -highlight group. - -Example: > - - Fix null pointer file:src/auth.lua:42 - Update tests file:spec/parse_spec.lua:100 -< - -`gf` in normal mode in the task buffer follows the file reference, opening -the file and jumping to the specified line. The default key is `gf` and can -be changed via the `goto_file` keymap in |pending-config|. Set it to `false` -to disable. - -To attach the current file and cursor position to an existing task, invoke -|(pending-add-here)| from any source file. A `vim.ui.select()` picker -lists all active tasks; selecting one records the current file and line. - -To clear a file reference with `:Pending edit`: >vim - :Pending edit 5 -file -< - ============================================================================== DATE INPUT *pending-dates* @@ -334,7 +300,6 @@ COMMANDS *pending-commands* :Pending edit 5 due:tomorrow cat:Work +! :Pending edit 5 -due -cat -rec :Pending edit 5 rec:!weekly due:fri - :Pending edit 5 -file < Operations: ~ `due:` Set due date (accepts all |pending-dates| vocabulary). @@ -345,7 +310,6 @@ COMMANDS *pending-commands* `-due` Clear due date. `-cat` Clear category. `-rec` Clear recurrence. - `-file` Clear the attached file reference (see |pending-file-token|). Tab completion is available for IDs, field names, date values, categories, and recurrence patterns. @@ -388,7 +352,6 @@ Default buffer-local keys: ~ `U` Undo the last `:w` save (`undo`) `o` Insert a new task line below (`open_line`) `O` Insert a new task line above (`open_line_above`) - `gf` Open the file attached to the task under the cursor (`goto_file`) `zc` Fold the current category section (category view only) `zo` Unfold the current category section (category view only) @@ -490,21 +453,6 @@ All motions support count: `3]]` jumps three headers forward. `]]` and (pending-prev-task) Jump to the previous task line, skipping headers and blanks. - *(pending-goto-file)* -(pending-goto-file) - Open the file attached to the task under the cursor. If the cursor is not - on a task line, or the task has no file reference, a warning is shown. If - the referenced file cannot be read, an error is shown. - See |pending-file-token|. - - *(pending-add-here)* -(pending-add-here) - Attach the current file and cursor line to an existing task. Invoke from - any source file (not the pending buffer itself) to open a picker listing - all active tasks. The selected task receives a `file:` reference pointing - to the current buffer's file and the cursor's line number. - See |pending-file-token|. - (pending-tab) *(pending-tab)* Open the task buffer in a new tab. See |:PendingTab|. @@ -605,7 +553,6 @@ loads: >lua prev_header = '[[', next_task = ']t', prev_task = '[t', - goto_file = 'gf', }, sync = { gcal = { @@ -668,11 +615,6 @@ Fields: ~ See |pending-mappings| for the full list of actions and their default keys. - {goto_file} (string|false, default: 'gf') - Open the file attached to the task under the - cursor. Set to `false` to disable. See - |pending-file-token|. - {debug} (boolean, default: false) Enable diagnostic logging. When `true`, textobj motions, mapping registration, and cursor jumps @@ -1042,12 +984,6 @@ PendingFilter Applied to the `FILTER:` header line shown at the top of the buffer when a filter is active. Default: links to `DiagnosticWarn`. - *PendingFile* -PendingFile Applied to the file reference virtual text shown for tasks - that have a `file:` token attached (see |pending-file-token|). - Displays the basename and line number (e.g. `auth.lua:42`). - Default: links to `Directory`. - To override a group in your colorscheme or config: >lua vim.api.nvim_set_hl(0, 'PendingDue', { fg = '#aaaaaa', italic = true }) < @@ -1059,16 +995,6 @@ Run |:checkhealth| pending to verify your setup: >vim :checkhealth pending < -Checks performed: ~ -- Config loads without error -- Reports active configuration values (data path, default view, default - category, date format, date syntax) -- Whether the data directory exists (warning if not yet created) -- Whether the data file exists and can be parsed; reports total task count -- Validates recurrence specs on stored tasks -- Discovers sync backends under `lua/pending/sync/` and runs each backend's - `health()` function if it exists (e.g. gcal checks for `curl` and `openssl`) - ============================================================================== STORE RESOLUTION *pending-store-resolution* diff --git a/lua/pending/buffer.lua b/lua/pending/buffer.lua index e9d7318..8b661a0 100644 --- a/lua/pending/buffer.lua +++ b/lua/pending/buffer.lua @@ -178,10 +178,6 @@ local function apply_extmarks(bufnr, line_meta) if m.due then table.insert(virt_parts, { icons.due .. ' ' .. m.due, due_hl }) end - if m.file then - local display = m.file:match('([^/]+:%d+)$') or m.file - table.insert(virt_parts, { display, 'PendingFile' }) - end if #virt_parts > 0 then for p = 1, #virt_parts - 1 do virt_parts[p][1] = virt_parts[p][1] .. ' ' @@ -238,7 +234,6 @@ local function setup_highlights() vim.api.nvim_set_hl(0, 'PendingPriority', { link = 'DiagnosticWarn', default = true }) vim.api.nvim_set_hl(0, 'PendingRecur', { link = 'DiagnosticInfo', default = true }) vim.api.nvim_set_hl(0, 'PendingFilter', { link = 'DiagnosticWarn', default = true }) - vim.api.nvim_set_hl(0, 'PendingFile', { link = 'Directory', default = true }) end local function snapshot_folds(bufnr) diff --git a/lua/pending/complete.lua b/lua/pending/complete.lua index ceeecc9..9ed4971 100644 --- a/lua/pending/complete.lua +++ b/lua/pending/complete.lua @@ -124,7 +124,6 @@ function M.omnifunc(findstart, base) { vim.pesc(dk) .. ':([%S]*)$', dk }, { 'cat:([%S]*)$', 'cat' }, { vim.pesc(rk) .. ':([%S]*)$', rk }, - { 'file:([%S]*)$', 'file' }, } for _, check in ipairs(checks) do diff --git a/lua/pending/config.lua b/lua/pending/config.lua index 6adf1c3..f1749e2 100644 --- a/lua/pending/config.lua +++ b/lua/pending/config.lua @@ -31,7 +31,6 @@ ---@field prev_header? string|false ---@field next_task? string|false ---@field prev_task? string|false ----@field goto_file? string|false ---@class pending.Config ---@field data_path string diff --git a/lua/pending/diff.lua b/lua/pending/diff.lua index b507179..e5a93e5 100644 --- a/lua/pending/diff.lua +++ b/lua/pending/diff.lua @@ -11,7 +11,6 @@ local parse = require('pending.parse') ---@field due? string ---@field rec? string ---@field rec_mode? string ----@field file? string ---@field lnum integer ---@class pending.diff @@ -57,7 +56,6 @@ function M.parse_buffer(lines) due = metadata.due, rec = metadata.rec, rec_mode = metadata.rec_mode, - file = metadata.file, lnum = i, }) end @@ -135,19 +133,6 @@ function M.apply(lines, s, hidden_ids) task.recur_mode = entry.rec_mode changed = true end - local old_file = (task._extra and task._extra.file) or nil - if entry.file ~= old_file then - task._extra = task._extra or {} - if entry.file then - task._extra.file = entry.file - else - task._extra.file = nil - if next(task._extra) == nil then - task._extra = nil - end - end - changed = true - end if entry.status and task.status ~= entry.status then task.status = entry.status if entry.status == 'done' then diff --git a/lua/pending/init.lua b/lua/pending/init.lua index 5205182..077fecf 100644 --- a/lua/pending/init.lua +++ b/lua/pending/init.lua @@ -324,15 +324,6 @@ function M._setup_buf_mappings(bufnr) end end - local goto_key = km.goto_file - if goto_key == nil then - goto_key = 'gf' - end - if goto_key and goto_key ~= false then - vim.keymap.set('n', goto_key --[[@as string]], function() - M.goto_file() - end, opts) - end end ---@param bufnr integer @@ -664,10 +655,6 @@ local function parse_edit_token(token) if token == '-rec' or token == '-' .. rk then return 'recur', vim.NIL, nil end - if token == '-file' then - return 'file_clear', true, nil - end - local due_val = token:match('^' .. vim.pesc(dk) .. ':(.+)$') if due_val then local resolved = parse.resolve_date(due_val) @@ -711,11 +698,10 @@ local function parse_edit_token(token) .. dk .. ':, cat:, ' .. rk - .. ':, file::, +!, -!, -' + .. ':, +!, -!, -' .. dk .. ', -cat, -' .. rk - .. ', -file' end ---@param id_str string @@ -795,9 +781,6 @@ function M.edit(id_str, rest) elseif field == 'priority' then updates.priority = value table.insert(feedback, value == 1 and 'priority added' or 'priority removed') - elseif field == 'file_clear' then - updates.file_clear = true - table.insert(feedback, 'file reference removed') end end @@ -810,17 +793,6 @@ function M.edit(id_str, rest) s:update(id, updates) - if updates.file_clear then - local t = s:get(id) - if t and t._extra then - t._extra.file = nil - if next(t._extra) == nil then - t._extra = nil - end - t.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]] - end - end - s:save() local bufnr = buffer.bufnr() @@ -831,91 +803,6 @@ function M.edit(id_str, rest) vim.notify('Task #' .. id .. ' updated: ' .. table.concat(feedback, ', ')) end ----@return nil -function M.goto_file() - local bufnr = vim.api.nvim_get_current_buf() - if vim.bo[bufnr].filetype ~= 'pending' then - return - end - local lnum = vim.api.nvim_win_get_cursor(0)[1] - local meta = buffer.meta() - local m = meta and meta[lnum] - if not m or m.type ~= 'task' then - vim.notify('No task on this line', vim.log.levels.WARN) - return - end - local task = get_store():get(m.id) - if not task or not task._extra or not task._extra.file then - vim.notify('No file attached to this task', vim.log.levels.WARN) - return - end - local file_spec = task._extra.file - local rel_path, line_str = file_spec:match('^(.+):(%d+)$') - if not rel_path then - vim.notify('Invalid file spec: ' .. file_spec, vim.log.levels.ERROR) - return - end - local data_dir = vim.fn.fnamemodify(get_store().path, ':h') - local abs_path = data_dir .. '/' .. rel_path - if vim.fn.filereadable(abs_path) == 0 then - vim.notify('File not found: ' .. abs_path, vim.log.levels.ERROR) - return - end - vim.cmd.edit(abs_path) - local lnum_target = tonumber(line_str) or 1 - vim.api.nvim_win_set_cursor(0, { lnum_target, 0 }) -end - ----@return nil -function M.add_here() - local cur_bufnr = vim.api.nvim_get_current_buf() - if vim.bo[cur_bufnr].filetype == 'pending' then - vim.notify('Already in pending buffer', vim.log.levels.WARN) - return - end - local cur_file = vim.api.nvim_buf_get_name(cur_bufnr) - if cur_file == '' or vim.fn.filereadable(cur_file) == 0 then - vim.notify('Not editing a readable file', vim.log.levels.ERROR) - return - end - local cur_lnum = vim.api.nvim_win_get_cursor(0)[1] - local s = get_store() - local data_dir = vim.fn.fnamemodify(s.path, ':h') - local abs_file = vim.fn.fnamemodify(cur_file, ':p') - local rel_file - if abs_file:sub(1, #data_dir + 1) == data_dir .. '/' then - rel_file = abs_file:sub(#data_dir + 2) - else - rel_file = abs_file - end - local file_spec = rel_file .. ':' .. cur_lnum - s:load() - local tasks = s:active_tasks() - if #tasks == 0 then - vim.notify('No active tasks', vim.log.levels.INFO) - return - end - local items = {} - for _, task in ipairs(tasks) do - table.insert(items, task) - end - vim.ui.select(items, { - prompt = 'Attach file to task:', - format_item = function(task) - return '[' .. task.id .. '] ' .. task.description - end, - }, function(task) - if not task then - return - end - task._extra = task._extra or {} - task._extra.file = file_spec - task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]] - s:save() - vim.notify('Attached ' .. file_spec .. ' to task ' .. task.id) - end) -end - ---@return nil function M.init() local path = vim.fn.getcwd() .. '/.pending.json' diff --git a/lua/pending/parse.lua b/lua/pending/parse.lua index 6d43be4..9ce4c0d 100644 --- a/lua/pending/parse.lua +++ b/lua/pending/parse.lua @@ -416,7 +416,7 @@ end ---@param text string ---@return string description ----@return { due?: string, cat?: string, rec?: string, rec_mode?: 'scheduled'|'completion', file?: string? } metadata +---@return { due?: string, cat?: string, rec?: string, rec_mode?: 'scheduled'|'completion' } metadata function M.body(text) local tokens = {} for token in text:gmatch('%S+') do @@ -481,18 +481,7 @@ function M.body(text) metadata.rec = raw_spec i = i - 1 else - local file_path_val, file_line_val = token:match('^file:(.+):(%d+)$') - if file_path_val and file_line_val then - if metadata.file then - break - end - metadata.file = file_path_val .. ':' .. file_line_val - i = i - 1 - elseif token:match('^file:') then - break - else - break - end + break end end end @@ -510,7 +499,7 @@ end ---@param text string ---@return string description ----@return { due?: string, cat?: string, rec?: string, rec_mode?: 'scheduled'|'completion', file?: string? } metadata +---@return { due?: string, cat?: string, rec?: string, rec_mode?: 'scheduled'|'completion' } metadata function M.command_add(text) local cat_prefix = text:match('^(%S.-):%s') if cat_prefix then diff --git a/lua/pending/views.lua b/lua/pending/views.lua index 5447a90..286db9a 100644 --- a/lua/pending/views.lua +++ b/lua/pending/views.lua @@ -12,7 +12,6 @@ local parse = require('pending.parse') ---@field show_category? boolean ---@field priority? integer ---@field recur? string ----@field file? string ---@class pending.views local M = {} @@ -160,7 +159,6 @@ function M.category_view(tasks) overdue = task.status == 'pending' and task.due ~= nil and parse.is_overdue(task.due) or nil, recur = task.recur, - file = task._extra and task._extra.file or nil, }) end end @@ -212,7 +210,6 @@ function M.priority_view(tasks) overdue = task.status == 'pending' and task.due ~= nil and parse.is_overdue(task.due) or nil, show_category = true, recur = task.recur, - file = task._extra and task._extra.file or nil, }) end diff --git a/plugin/pending.lua b/plugin/pending.lua index 4814f50..0350b73 100644 --- a/plugin/pending.lua +++ b/plugin/pending.lua @@ -12,13 +12,11 @@ local function edit_field_candidates() dk .. ':', 'cat:', rk .. ':', - 'file:', '+!', '-!', '-' .. dk, '-cat', '-' .. rk, - '-file', } end @@ -300,14 +298,6 @@ vim.keymap.set({ 'n', 'x', 'o' }, '(pending-prev-task)', function() require('pending.textobj').prev_task(vim.v.count1) end) -vim.keymap.set('n', '(pending-goto-file)', function() - require('pending').goto_file() -end) - -vim.keymap.set('n', '(pending-add-here)', function() - require('pending').add_here() -end) - vim.keymap.set('n', '(pending-tab)', function() vim.cmd.tabnew() require('pending').open() diff --git a/spec/file_spec.lua b/spec/file_spec.lua deleted file mode 100644 index c7e3151..0000000 --- a/spec/file_spec.lua +++ /dev/null @@ -1,358 +0,0 @@ -require('spec.helpers') - -local config = require('pending.config') -local diff = require('pending.diff') -local parse = require('pending.parse') -local store = require('pending.store') -local views = require('pending.views') - -describe('file token', function() - local tmpdir - local s - - before_each(function() - tmpdir = vim.fn.tempname() - vim.fn.mkdir(tmpdir, 'p') - vim.g.pending = { data_path = tmpdir .. '/tasks.json' } - config.reset() - package.loaded['pending'] = nil - package.loaded['pending.buffer'] = nil - s = store.new(tmpdir .. '/tasks.json') - s:load() - end) - - after_each(function() - vim.fn.delete(tmpdir, 'rf') - vim.g.pending = nil - config.reset() - package.loaded['pending'] = nil - package.loaded['pending.buffer'] = nil - end) - - describe('parse.body', function() - it('extracts file token with path and line number', function() - local desc, meta = parse.body('Fix the bug file:src/auth.lua:42') - assert.are.equal('Fix the bug', desc) - assert.are.equal('src/auth.lua:42', meta.file) - end) - - it('extracts file token with nested path', function() - local desc, meta = parse.body('Do something file:lua/pending/init.lua:100') - assert.are.equal('Do something', desc) - assert.are.equal('lua/pending/init.lua:100', meta.file) - end) - - it('strips file token from description', function() - local desc, meta = parse.body('Task description file:foo.lua:1') - assert.are.equal('Task description', desc) - assert.are.equal('foo.lua:1', meta.file) - end) - - it('stops parsing on duplicate file token', function() - local desc, meta = parse.body('Task file:b.lua:2 file:a.lua:1') - assert.are.equal('Task file:b.lua:2', desc) - assert.are.equal('a.lua:1', meta.file) - end) - - it('treats malformed file token (no line number) as non-metadata', function() - local desc, meta = parse.body('Task file:nolineno') - assert.are.equal('Task file:nolineno', desc) - assert.is_nil(meta.file) - end) - - it('treats file: prefix with no path as non-metadata', function() - local desc, meta = parse.body('Task file:') - assert.are.equal('Task file:', desc) - assert.is_nil(meta.file) - end) - - it('handles file token alongside other metadata tokens', function() - local desc, meta = parse.body('Task cat:Work file:src/main.lua:10') - assert.are.equal('Task', desc) - assert.are.equal('Work', meta.cat) - assert.are.equal('src/main.lua:10', meta.file) - end) - - it('does not extract file token when line number is not numeric', function() - local desc, meta = parse.body('Task file:src/foo.lua:abc') - assert.are.equal('Task file:src/foo.lua:abc', desc) - assert.is_nil(meta.file) - end) - end) - - describe('diff reconciliation', function() - it('stores file field in _extra on write', function() - local t = s:add({ description = 'Task one' }) - s:save() - local lines = { - '/' .. t.id .. '/- [ ] Task one file:src/auth.lua:42', - } - diff.apply(lines, s) - local updated = s:get(t.id) - assert.is_not_nil(updated._extra) - assert.are.equal('src/auth.lua:42', updated._extra.file) - end) - - it('updates file field when token changes', function() - local t = s:add({ description = 'Task one', _extra = { file = 'old.lua:1' } }) - s:save() - local lines = { - '/' .. t.id .. '/- [ ] Task one file:new.lua:99', - } - diff.apply(lines, s) - local updated = s:get(t.id) - assert.are.equal('new.lua:99', updated._extra.file) - end) - - it('clears file field when token is removed from line', function() - local t = s:add({ description = 'Task one', _extra = { file = 'src/auth.lua:42' } }) - s:save() - local lines = { - '/' .. t.id .. '/- [ ] Task one', - } - diff.apply(lines, s) - local updated = s:get(t.id) - assert.is_nil(updated._extra) - end) - - it('preserves other _extra fields when file is cleared', function() - local t = s:add({ - description = 'Task one', - _extra = { file = 'src/auth.lua:42', _gcal_event_id = 'abc123' }, - }) - s:save() - local lines = { - '/' .. t.id .. '/- [ ] Task one', - } - diff.apply(lines, s) - local updated = s:get(t.id) - assert.is_not_nil(updated._extra) - assert.is_nil(updated._extra.file) - assert.are.equal('abc123', updated._extra._gcal_event_id) - end) - - it('round-trips file field through JSON', function() - local t = s:add({ description = 'Task one' }) - s:save() - local lines = { - '/' .. t.id .. '/- [ ] Task one file:src/auth.lua:42', - } - diff.apply(lines, s) - s:load() - local loaded = s:get(t.id) - assert.is_not_nil(loaded._extra) - assert.are.equal('src/auth.lua:42', loaded._extra.file) - end) - - it('accepts optional hidden_ids parameter without error', function() - local t = s:add({ description = 'Task one' }) - s:save() - local lines = { - '/' .. t.id .. '/- [ ] Task one', - } - assert.has_no_error(function() - diff.apply(lines, s, {}) - end) - end) - end) - - describe('LineMeta', function() - it('category_view populates file field in LineMeta', function() - local t = s:add({ - description = 'Task one', - _extra = { file = 'src/auth.lua:42' }, - }) - s:save() - local tasks = s:active_tasks() - local _, meta = views.category_view(tasks) - local task_meta = nil - for _, m in ipairs(meta) do - if m.type == 'task' and m.id == t.id then - task_meta = m - break - end - end - assert.is_not_nil(task_meta) - assert.are.equal('src/auth.lua:42', task_meta.file) - end) - - it('priority_view populates file field in LineMeta', function() - local t = s:add({ - description = 'Task one', - _extra = { file = 'src/auth.lua:42' }, - }) - s:save() - local tasks = s:active_tasks() - local _, meta = views.priority_view(tasks) - local task_meta = nil - for _, m in ipairs(meta) do - if m.type == 'task' and m.id == t.id then - task_meta = m - break - end - end - assert.is_not_nil(task_meta) - assert.are.equal('src/auth.lua:42', task_meta.file) - end) - - it('file field is nil in LineMeta when task has no file', function() - local t = s:add({ description = 'Task one' }) - s:save() - local tasks = s:active_tasks() - local _, meta = views.category_view(tasks) - local task_meta = nil - for _, m in ipairs(meta) do - if m.type == 'task' and m.id == t.id then - task_meta = m - break - end - end - assert.is_not_nil(task_meta) - assert.is_nil(task_meta.file) - end) - end) - - describe(':Pending edit -file', function() - it('clears file reference from task', function() - local pending = require('pending') - local t = s:add({ description = 'Task one', _extra = { file = 'src/auth.lua:42' } }) - s:save() - pending.edit(tostring(t.id), '-file') - s:load() - local updated = s:get(t.id) - assert.is_nil(updated._extra) - end) - - it('shows feedback when file reference is removed', function() - local pending = require('pending') - local t = s:add({ description = 'Task one', _extra = { file = 'src/auth.lua:42' } }) - s:save() - local messages = {} - local orig_notify = vim.notify - vim.notify = function(msg, level) - table.insert(messages, { msg = msg, level = level }) - end - pending.edit(tostring(t.id), '-file') - vim.notify = orig_notify - assert.are.equal(1, #messages) - assert.truthy(messages[1].msg:find('file reference removed')) - end) - - it('does not error when task has no file', function() - local pending = require('pending') - local t = s:add({ description = 'Task one' }) - s:save() - assert.has_no_error(function() - pending.edit(tostring(t.id), '-file') - end) - end) - - it('preserves other _extra fields when -file is used', function() - local pending = require('pending') - local t = s:add({ - description = 'Task one', - _extra = { file = 'src/auth.lua:42', _gcal_event_id = 'abc' }, - }) - s:save() - pending.edit(tostring(t.id), '-file') - s:load() - local updated = s:get(t.id) - assert.is_not_nil(updated._extra) - assert.is_nil(updated._extra.file) - assert.are.equal('abc', updated._extra._gcal_event_id) - end) - end) - - describe('goto_file', function() - it('notifies warn when task has no file attached', function() - local pending = require('pending') - local buffer = require('pending.buffer') - - local t = s:add({ description = 'Task one' }) - s:save() - - buffer.set_store(s) - local bufnr = buffer.open() - vim.bo[bufnr].filetype = 'pending' - vim.api.nvim_set_current_buf(bufnr) - - local meta = buffer.meta() - local task_lnum = nil - for lnum, m in ipairs(meta) do - if m.type == 'task' and m.id == t.id then - task_lnum = lnum - break - end - end - assert.is_not_nil(task_lnum) - vim.api.nvim_win_set_cursor(0, { task_lnum, 0 }) - - local messages = {} - local orig_notify = vim.notify - vim.notify = function(msg, level) - table.insert(messages, { msg = msg, level = level }) - end - - pending.goto_file() - - vim.notify = orig_notify - - local warned = false - for _, m in ipairs(messages) do - if m.level == vim.log.levels.WARN and m.msg:find('No file attached') then - warned = true - end - end - assert.is_true(warned) - - vim.api.nvim_buf_delete(bufnr, { force = true }) - end) - - it('notifies error when file spec is unreadable', function() - local pending = require('pending') - local buffer = require('pending.buffer') - - local t = s:add({ - description = 'Task one', - _extra = { file = 'nonexistent/path.lua:1' }, - }) - s:save() - - buffer.set_store(s) - local bufnr = buffer.open() - vim.bo[bufnr].filetype = 'pending' - vim.api.nvim_set_current_buf(bufnr) - - local meta = buffer.meta() - local task_lnum = nil - for lnum, m in ipairs(meta) do - if m.type == 'task' and m.id == t.id then - task_lnum = lnum - break - end - end - assert.is_not_nil(task_lnum) - vim.api.nvim_win_set_cursor(0, { task_lnum, 0 }) - - local messages = {} - local orig_notify = vim.notify - vim.notify = function(msg, level) - table.insert(messages, { msg = msg, level = level }) - end - - pending.goto_file() - - vim.notify = orig_notify - - local errored = false - for _, m in ipairs(messages) do - if m.level == vim.log.levels.ERROR and m.msg:find('File not found') then - errored = true - end - end - assert.is_true(errored) - - vim.api.nvim_buf_delete(bufnr, { force = true }) - end) - end) -end)