diff --git a/lua/pending/textobj.lua b/lua/pending/textobj.lua index 3f33197..77a6539 100644 --- a/lua/pending/textobj.lua +++ b/lua/pending/textobj.lua @@ -21,50 +21,40 @@ end ---@return integer start_col ---@return integer end_col function M.inner_task_range(line) - local prefix_end = line:find('/') and select(2, line:find('^/%d+/- %[.%] ')) + local prefix_end = line:find('/') and select(2, line:find('^/%d+/%- %[.%] ')) if not prefix_end then - prefix_end = select(2, line:find('^- %[.%] ')) or 0 + prefix_end = select(2, line:find('^%- %[.%] ')) or 0 end local start_col = prefix_end + 1 local dk = config.get().date_syntax or 'due' local rk = config.get().recur_syntax or 'rec' - local dk_esc = vim.pesc(dk) - local rk_esc = vim.pesc(rk) + local dk_pat = '^' .. vim.pesc(dk) .. ':%S+$' + local rk_pat = '^' .. vim.pesc(rk) .. ':%S+$' - local desc_end = #line - local tokens = {} - for token in line:gmatch('%S+') do - table.insert(tokens, token) + local rest = line:sub(start_col) + local words = {} + for word in rest:gmatch('%S+') do + table.insert(words, word) end - local i = #tokens + local i = #words while i >= 1 do - local token = tokens[i] - if token:match('^' .. dk_esc .. ':%S+$') - or token:match('^cat:%S+$') - or token:match('^' .. rk_esc .. ':%S+$') - then + local word = words[i] + if word:match(dk_pat) or word:match('^cat:%S+$') or word:match(rk_pat) then i = i - 1 else break end end - if i < #tokens then - local rebuilt = {} - for j = 1, i do - table.insert(rebuilt, tokens[j]) - end - local desc_text = table.concat(rebuilt, ' ') - local search_start = prefix_end - local found = line:find(desc_text, search_start + 1, true) - if found then - desc_end = found + #desc_text - 1 - end + if i < 1 then + return start_col, start_col end - return start_col, desc_end + local desc = table.concat(words, ' ', 1, i) + local end_col = start_col + #desc - 1 + return start_col, end_col end ---@param row integer diff --git a/spec/textobj_spec.lua b/spec/textobj_spec.lua index 0d05c47..fa7ac65 100644 --- a/spec/textobj_spec.lua +++ b/spec/textobj_spec.lua @@ -18,8 +18,8 @@ describe('textobj', function() describe('inner_task_range', function() it('returns description range for task with id prefix', function() local s, e = textobj.inner_task_range('/1/- [ ] Buy groceries') - assert.are.equal(11, s) - assert.are.equal(23, e) + assert.are.equal(10, s) + assert.are.equal(22, e) end) it('returns description range for task without id prefix', function() @@ -30,78 +30,78 @@ describe('textobj', function() it('excludes trailing due: token', function() local s, e = textobj.inner_task_range('/1/- [ ] Buy groceries due:2026-03-15') - assert.are.equal(11, s) - assert.are.equal(23, e) + assert.are.equal(10, s) + assert.are.equal(22, e) end) it('excludes trailing cat: token', function() local s, e = textobj.inner_task_range('/1/- [ ] Buy groceries cat:Errands') - assert.are.equal(11, s) - assert.are.equal(23, e) + assert.are.equal(10, s) + assert.are.equal(22, e) end) it('excludes trailing rec: token', function() local s, e = textobj.inner_task_range('/1/- [ ] Take out trash rec:weekly') - assert.are.equal(11, s) - assert.are.equal(25, e) + assert.are.equal(10, s) + assert.are.equal(23, e) end) it('excludes multiple trailing metadata tokens', function() local s, e = textobj.inner_task_range('/1/- [ ] Buy milk due:2026-03-15 cat:Errands rec:weekly') - assert.are.equal(11, s) - assert.are.equal(18, e) + assert.are.equal(10, s) + assert.are.equal(17, e) end) it('handles priority checkbox', function() local s, e = textobj.inner_task_range('/1/- [!] Important task') - assert.are.equal(11, s) - assert.are.equal(24, e) + assert.are.equal(10, s) + assert.are.equal(23, e) end) it('handles done checkbox', function() local s, e = textobj.inner_task_range('/1/- [x] Finished task') - assert.are.equal(11, s) - assert.are.equal(23, e) + assert.are.equal(10, s) + assert.are.equal(22, e) end) it('handles multi-digit task ids', function() local s, e = textobj.inner_task_range('/123/- [ ] Some task') - assert.are.equal(13, s) - assert.are.equal(21, e) + assert.are.equal(12, s) + assert.are.equal(20, e) end) it('does not strip non-metadata tokens', function() local s, e = textobj.inner_task_range('/1/- [ ] Buy groceries for dinner') - assert.are.equal(11, s) - assert.are.equal(34, e) + assert.are.equal(10, s) + assert.are.equal(33, e) end) it('stops stripping at first non-metadata token from right', function() local s, e = textobj.inner_task_range('/1/- [ ] Buy groceries for dinner due:2026-03-15') - assert.are.equal(11, s) - assert.are.equal(34, e) + assert.are.equal(10, s) + assert.are.equal(33, e) end) it('respects custom date_syntax', function() vim.g.pending = { date_syntax = 'by' } config.reset() local s, e = textobj.inner_task_range('/1/- [ ] Buy groceries by:2026-03-15') - assert.are.equal(11, s) - assert.are.equal(23, e) + assert.are.equal(10, s) + assert.are.equal(22, e) end) it('respects custom recur_syntax', function() vim.g.pending = { recur_syntax = 'repeat' } config.reset() local s, e = textobj.inner_task_range('/1/- [ ] Take trash repeat:weekly') - assert.are.equal(11, s) - assert.are.equal(20, e) + assert.are.equal(10, s) + assert.are.equal(19, e) end) it('handles task with only metadata after description', function() local s, e = textobj.inner_task_range('/1/- [ ] X due:tomorrow') - assert.are.equal(11, s) - assert.are.equal(11, e) + assert.are.equal(10, s) + assert.are.equal(10, e) end) end) @@ -129,7 +129,7 @@ describe('textobj', function() } local h, l = textobj.category_bounds(2, meta) assert.are.equal(1, h) - assert.are.equal(2, l) + assert.are.equal(3, l) end) it('returns bounds for second category', function()