Skip to content

Commit 429409b

Browse files
fix(filter): Fix or matching for todos in tags match filter
1 parent 2a5eeb3 commit 429409b

File tree

2 files changed

+121
-47
lines changed

2 files changed

+121
-47
lines changed

lua/orgmode/files/elements/search.lua

+51-47
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,12 @@ PropertyStringMatch.__index = PropertyStringMatch
5959
local PropertyNumberMatch = {}
6060
PropertyNumberMatch.__index = PropertyNumberMatch
6161

62+
---@class OrgTodoMatchAndItem
63+
---@field operator string
64+
---@field value string
65+
6266
---@class OrgTodoMatch
63-
---@field anyOf string[]
64-
---@field noneOf string[]
67+
---@field orItems OrgTodoMatchAndItem[][]
6568
local TodoMatch = {}
6669
TodoMatch.__index = TodoMatch
6770

@@ -541,10 +544,8 @@ end
541544
---@private
542545
---@return OrgTodoMatch
543546
function TodoMatch:_new()
544-
---@type OrgTodoMatch
545547
local todo_match = {
546-
anyOf = {},
547-
noneOf = {},
548+
orItems = {},
548549
}
549550

550551
setmetatable(todo_match, TodoMatch)
@@ -567,65 +568,68 @@ function TodoMatch:parse(input)
567568

568569
-- Parse a whitelist of keywords
569570
--- @type string[]?
570-
local anyOf
571-
anyOf, input = parse_delimited_sequence(input, function(i)
572-
return parse_pattern(i, '%w+')
573-
end, '%|')
574-
if anyOf and #anyOf > 0 then
575-
-- Successfully parsed the whitelist, return it
576-
local todo_match = TodoMatch:_new()
577-
todo_match.anyOf = anyOf
578-
return todo_match, input
579-
end
571+
local orItems
572+
orItems, input = parse_delimited_sequence(input, function(i)
573+
---@type string?
574+
local operator
575+
operator, i = parse_pattern(i, '[%+%-]?')
576+
577+
if operator == '' then
578+
operator = '+'
579+
end
580580

581-
-- Parse a blacklist of keywords
582-
---@type string?
583-
local negation
584-
negation, input = parse_pattern(input, '-')
585-
if negation then
586-
local negative_items
587-
negative_items, input = parse_delimited_sequence(input, function(i)
588-
return parse_pattern(i, '%w+')
589-
end, '%-')
590-
591-
if negative_items then
592-
if #negation > 0 then
593-
local todo_match = TodoMatch:_new()
594-
todo_match.noneOf = negative_items
595-
return todo_match, input
596-
else
597-
return nil, original_input
581+
local andItems = {}
582+
583+
while operator do
584+
---@type string?
585+
local value
586+
value, i = parse_pattern(i, '%w+')
587+
if not value then
588+
break
598589
end
590+
table.insert(andItems, {
591+
operator = operator,
592+
value = value,
593+
})
594+
595+
operator, i = parse_pattern(i, '[%+%-]')
599596
end
597+
598+
return andItems, i
599+
end, '%|')
600+
601+
if not orItems or #orItems == 0 then
602+
return nil, original_input
600603
end
601604

602-
return nil, original_input
605+
local todo_match = TodoMatch:_new()
606+
todo_match.orItems = orItems
607+
return todo_match, input
603608
end
604609

605610
---@param item OrgSearchable
606611
---@return boolean
607612
function TodoMatch:match(item)
608613
local item_todo = item.todo
609614

610-
if #self.anyOf > 0 then
611-
for _, todo_value in ipairs(self.anyOf) do
612-
if item_todo == todo_value then
613-
return true
615+
for _, orItem in ipairs(self.orItems) do
616+
local validItems = true
617+
for _, andItem in ipairs(orItem) do
618+
if andItem.operator == '-' and item_todo == andItem.value then
619+
validItems = false
620+
break
621+
elseif andItem.operator == '+' and item_todo ~= andItem.value then
622+
validItems = false
623+
break
614624
end
615625
end
616626

617-
return false
618-
elseif #self.noneOf > 0 then
619-
for _, todo_value in ipairs(self.noneOf) do
620-
if item_todo == todo_value then
621-
return false
622-
end
627+
if validItems then
628+
return true
623629
end
624-
625-
return true
626-
else
627-
return true
628630
end
631+
632+
return false
629633
end
630634

631635
return Search

tests/plenary/parser/search_spec.lua

+70
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,76 @@ describe('Search parser', function()
203203
}))
204204
end)
205205

206+
it('should correctly parse todo keywords', function()
207+
local result = Search:new('+WORK/!+TODO|-NEXT')
208+
assert.is.True(result:check({
209+
props = {},
210+
tags = { 'WORK' },
211+
todo = 'TODO',
212+
}))
213+
assert.is.True(result:check({
214+
props = {},
215+
tags = { 'WORK' },
216+
todo = 'DONE',
217+
}))
218+
assert.is.False(result:check({
219+
props = {},
220+
tags = { 'WORK' },
221+
todo = 'NEXT',
222+
}))
223+
224+
result = Search:new('+WORK/!+TODO|+NEXT')
225+
assert.is.True(result:check({
226+
props = {},
227+
tags = { 'WORK' },
228+
todo = 'TODO',
229+
}))
230+
assert.is.False(result:check({
231+
props = {},
232+
tags = { 'WORK' },
233+
todo = 'DONE',
234+
}))
235+
assert.is.True(result:check({
236+
props = {},
237+
tags = { 'WORK' },
238+
todo = 'NEXT',
239+
}))
240+
241+
result = Search:new('+WORK/!+TODO-NEXT')
242+
assert.is.True(result:check({
243+
props = {},
244+
tags = { 'WORK' },
245+
todo = 'TODO',
246+
}))
247+
assert.is.False(result:check({
248+
props = {},
249+
tags = { 'WORK' },
250+
todo = 'DONE',
251+
}))
252+
assert.is.False(result:check({
253+
props = {},
254+
tags = { 'WORK' },
255+
todo = 'NEXT',
256+
}))
257+
258+
result = Search:new('+WORK/!-TODO-NEXT')
259+
assert.is.False(result:check({
260+
props = {},
261+
tags = { 'WORK' },
262+
todo = 'TODO',
263+
}))
264+
assert.is.True(result:check({
265+
props = {},
266+
tags = { 'WORK' },
267+
todo = 'DONE',
268+
}))
269+
assert.is.False(result:check({
270+
props = {},
271+
tags = { 'WORK' },
272+
todo = 'NEXT',
273+
}))
274+
end)
275+
206276
it('should parse allowed punctuation in tags', function()
207277
local result = Search:new('lang_dev|@work|org#mode|a2%')
208278
assert.is.True(result:check({ tags = { 'lang_dev' } }))

0 commit comments

Comments
 (0)