Skip to content

Commit 69523d7

Browse files
authored
Merge pull request #793 from multiplex55/codex/update-tag-matching-logic-to-be-case-insensitive
Use case-insensitive substring tag matching for todo and note filters
2 parents 2e7c7b6 + be7b014 commit 69523d7

3 files changed

Lines changed: 94 additions & 12 deletions

File tree

src/plugins/note.rs

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -732,15 +732,14 @@ impl Plugin for NotePlugin {
732732
let tag_ok = if filters.include_tags.is_empty() {
733733
true
734734
} else {
735-
filters
736-
.include_tags
737-
.iter()
738-
.all(|tag| n.tags.iter().any(|t| t == tag))
735+
filters.include_tags.iter().all(|tag| {
736+
n.tags.iter().any(|t| t.contains(tag))
737+
})
739738
};
740739
let exclude_ok = !filters
741740
.exclude_tags
742741
.iter()
743-
.any(|tag| n.tags.iter().any(|t| t == tag));
742+
.any(|tag| n.tags.iter().any(|t| t.contains(tag)));
744743
let text_ok = if text_filter.is_empty() {
745744
true
746745
} else {
@@ -1182,6 +1181,61 @@ mod tests {
11821181
restore_cache(original);
11831182
}
11841183

1184+
#[test]
1185+
fn note_list_supports_partial_tag_filters() {
1186+
let original = set_notes(vec![
1187+
Note {
1188+
title: "Alpha".into(),
1189+
path: PathBuf::new(),
1190+
content: "Review #testing and #ui-kit changes.".into(),
1191+
tags: Vec::new(),
1192+
links: Vec::new(),
1193+
slug: "alpha".into(),
1194+
alias: None,
1195+
},
1196+
Note {
1197+
title: "Beta".into(),
1198+
path: PathBuf::new(),
1199+
content: "Follow up on #testing checklist.".into(),
1200+
tags: Vec::new(),
1201+
links: Vec::new(),
1202+
slug: "beta".into(),
1203+
alias: None,
1204+
},
1205+
Note {
1206+
title: "Gamma".into(),
1207+
path: PathBuf::new(),
1208+
content: "Finalize #ui rollout.".into(),
1209+
tags: Vec::new(),
1210+
links: Vec::new(),
1211+
slug: "gamma".into(),
1212+
alias: None,
1213+
},
1214+
]);
1215+
1216+
let plugin = NotePlugin {
1217+
matcher: SkimMatcherV2::default(),
1218+
data: CACHE.clone(),
1219+
templates: TEMPLATE_CACHE.clone(),
1220+
external_open: NoteExternalOpen::Wezterm,
1221+
watcher: None,
1222+
};
1223+
1224+
let list_test = plugin.search("note list #test");
1225+
let labels_test: Vec<&str> = list_test.iter().map(|a| a.label.as_str()).collect();
1226+
assert_eq!(labels_test, vec!["Alpha", "Beta"]);
1227+
1228+
let list_ui = plugin.search("note list @ui");
1229+
let labels_ui: Vec<&str> = list_ui.iter().map(|a| a.label.as_str()).collect();
1230+
assert_eq!(labels_ui, vec!["Alpha", "Gamma"]);
1231+
1232+
let list_not_ui = plugin.search("note list !#ui");
1233+
let labels_not_ui: Vec<&str> = list_not_ui.iter().map(|a| a.label.as_str()).collect();
1234+
assert_eq!(labels_not_ui, vec!["Beta"]);
1235+
1236+
restore_cache(original);
1237+
}
1238+
11851239
#[test]
11861240
fn note_tag_lists_tags_and_drills_into_list() {
11871241
let original = set_notes(vec![

src/plugins/todo.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,16 @@ impl TodoPlugin {
301301
};
302302
let mut entries: Vec<(usize, &TodoEntry)> = guard.iter().enumerate().collect();
303303

304-
let tag_filter = filter.starts_with('#');
304+
let tag_filter = filter.starts_with('#') || filter.starts_with('@');
305305
if tag_filter {
306-
let tag = filter.trim_start_matches('#');
307-
entries.retain(|(_, t)| t.tags.iter().any(|tg| tg.eq_ignore_ascii_case(tag)));
306+
let tag = filter.trim_start_matches(['#', '@']);
307+
let requested = tag.to_lowercase();
308+
entries.retain(|(_, t)| {
309+
!requested.is_empty()
310+
&& t.tags
311+
.iter()
312+
.any(|tg| tg.to_lowercase().contains(&requested))
313+
});
308314
} else if !filter.is_empty() {
309315
entries.retain(|(_, t)| self.matcher.fuzzy_match(&t.text, filter).is_some());
310316
}
@@ -567,7 +573,10 @@ impl TodoPlugin {
567573
if !filters.include_tags.is_empty() {
568574
entries.retain(|(_, t)| {
569575
filters.include_tags.iter().all(|requested| {
570-
t.tags.iter().any(|tag| tag.eq_ignore_ascii_case(requested))
576+
let requested = requested.to_lowercase();
577+
t.tags
578+
.iter()
579+
.any(|tag| tag.to_lowercase().contains(&requested))
571580
})
572581
});
573582
}
@@ -577,7 +586,12 @@ impl TodoPlugin {
577586
!filters
578587
.exclude_tags
579588
.iter()
580-
.any(|excluded| t.tags.iter().any(|tag| tag.eq_ignore_ascii_case(excluded)))
589+
.any(|excluded| {
590+
let excluded = excluded.to_lowercase();
591+
t.tags
592+
.iter()
593+
.any(|tag| tag.to_lowercase().contains(&excluded))
594+
})
581595
});
582596
}
583597

tests/todo_plugin.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,26 @@ fn list_filters_by_tag() {
256256
std::env::set_current_dir(dir.path()).unwrap();
257257

258258
append_todo(TODO_FILE, "alpha", 1, &["rs3".into()]).unwrap();
259-
append_todo(TODO_FILE, "beta", 1, &["other".into()]).unwrap();
259+
append_todo(TODO_FILE, "beta", 1, &["work".into()]).unwrap();
260+
append_todo(TODO_FILE, "gamma", 1, &["workshop".into()]).unwrap();
261+
append_todo(TODO_FILE, "delta", 1, &["ui-kit".into()]).unwrap();
262+
append_todo(TODO_FILE, "epsilon", 1, &["backend".into()]).unwrap();
260263

261264
let plugin = TodoPlugin::default();
262-
let results = plugin.search("todo list #rs3");
265+
let results = plugin.search("todo list #rs");
263266
assert_eq!(results.len(), 1);
264267
assert!(results[0].label.contains("alpha"));
268+
269+
let results = plugin.search("todo list @wor");
270+
let labels: Vec<&str> = results.iter().map(|action| action.label.as_str()).collect();
271+
assert_eq!(results.len(), 2);
272+
assert!(labels.iter().any(|label| label.contains("beta")));
273+
assert!(labels.iter().any(|label| label.contains("gamma")));
274+
275+
let results = plugin.search("todo list !#ui");
276+
let labels: Vec<&str> = results.iter().map(|action| action.label.as_str()).collect();
277+
assert_eq!(results.len(), 4);
278+
assert!(!labels.iter().any(|label| label.contains("delta")));
265279
}
266280

267281
#[test]

0 commit comments

Comments
 (0)