Skip to content

Commit cdc581d

Browse files
authored
Merge pull request #660 from multiplex55/codex/update-todoplugin-for-multiple-tags-support
Support multi-tag filtering for `todo list` and update help example
2 parents 48ebfbd + f3371cf commit cdc581d

2 files changed

Lines changed: 63 additions & 16 deletions

File tree

src/help_window.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,11 @@ fn example_queries(name: &str) -> Option<&'static [&'static str]> {
206206
"stopwatch" => Some(&["sw start", "sw list"]),
207207
"task_manager" => Some(&["tm"]),
208208
"text_case" => Some(&["case snake Hello World"]),
209-
"todo" => Some(&["todo add buy milk @home", "todo list"]),
209+
"todo" => Some(&[
210+
"todo add buy milk @home",
211+
"todo list",
212+
"todo list @testing @ui",
213+
]),
210214
"wikipedia" => Some(&["wiki rust"]),
211215
"help" => Some(&["help"]),
212216
_ => None,

src/plugins/todo.rs

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -403,26 +403,69 @@ impl TodoPlugin {
403403
};
404404
let mut entries: Vec<(usize, &TodoEntry)> = guard.iter().enumerate().collect();
405405

406+
let mut requested_tags: Vec<&str> = Vec::new();
407+
let mut excluded_tags: Vec<&str> = Vec::new();
408+
let mut text_tokens: Vec<&str> = Vec::new();
406409
let mut negative = false;
407-
if let Some(stripped) = filter.strip_prefix('!') {
408-
negative = true;
409-
filter = stripped.trim();
410+
for token in filter.split_whitespace() {
411+
let mut token = token;
412+
let mut token_negated = false;
413+
if let Some(stripped) = token.strip_prefix('!') {
414+
token_negated = true;
415+
token = stripped;
416+
}
417+
418+
if let Some(tag) = token.strip_prefix('@').or_else(|| token.strip_prefix('#')) {
419+
if !tag.is_empty() {
420+
if token_negated {
421+
excluded_tags.push(tag);
422+
} else {
423+
requested_tags.push(tag);
424+
}
425+
}
426+
continue;
427+
}
428+
429+
if token_negated
430+
&& !negative
431+
&& !token.is_empty()
432+
&& text_tokens.is_empty()
433+
{
434+
negative = true;
435+
}
436+
437+
if !token.is_empty() {
438+
text_tokens.push(token);
439+
}
410440
}
411441

412-
let tag_filter = filter.starts_with('#');
413-
if tag_filter {
414-
let tag = filter.trim_start_matches('#');
442+
let text_filter = text_tokens.join(" ");
443+
let has_tag_filter = !requested_tags.is_empty() || !excluded_tags.is_empty();
444+
445+
// Tag filters run first, then text filters apply fuzzy matching against remaining text.
446+
if !requested_tags.is_empty() {
415447
entries.retain(|(_, t)| {
416-
let has_tag = t.tags.iter().any(|tg| tg.eq_ignore_ascii_case(tag));
417-
if negative {
418-
!has_tag
419-
} else {
420-
has_tag
421-
}
448+
requested_tags.iter().all(|requested| {
449+
t.tags
450+
.iter()
451+
.any(|tag| tag.eq_ignore_ascii_case(requested))
452+
})
422453
});
423-
} else if !filter.is_empty() {
454+
}
455+
456+
if !excluded_tags.is_empty() {
457+
entries.retain(|(_, t)| {
458+
!excluded_tags.iter().any(|excluded| {
459+
t.tags
460+
.iter()
461+
.any(|tag| tag.eq_ignore_ascii_case(excluded))
462+
})
463+
});
464+
}
465+
466+
if !text_filter.is_empty() {
424467
entries.retain(|(_, t)| {
425-
let text_match = self.matcher.fuzzy_match(&t.text, filter).is_some();
468+
let text_match = self.matcher.fuzzy_match(&t.text, &text_filter).is_some();
426469
if negative {
427470
!text_match
428471
} else {
@@ -431,7 +474,7 @@ impl TodoPlugin {
431474
});
432475
}
433476

434-
if filter.is_empty() || tag_filter {
477+
if text_filter.is_empty() || has_tag_filter {
435478
entries.sort_by(|a, b| b.1.priority.cmp(&a.1.priority));
436479
}
437480

0 commit comments

Comments
 (0)