Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 59 additions & 5 deletions src/plugins/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,15 +732,14 @@ impl Plugin for NotePlugin {
let tag_ok = if filters.include_tags.is_empty() {
true
} else {
filters
.include_tags
.iter()
.all(|tag| n.tags.iter().any(|t| t == tag))
filters.include_tags.iter().all(|tag| {
n.tags.iter().any(|t| t.contains(tag))
})
};
let exclude_ok = !filters
.exclude_tags
.iter()
.any(|tag| n.tags.iter().any(|t| t == tag));
.any(|tag| n.tags.iter().any(|t| t.contains(tag)));
let text_ok = if text_filter.is_empty() {
true
} else {
Expand Down Expand Up @@ -1182,6 +1181,61 @@ mod tests {
restore_cache(original);
}

#[test]
fn note_list_supports_partial_tag_filters() {
let original = set_notes(vec![
Note {
title: "Alpha".into(),
path: PathBuf::new(),
content: "Review #testing and #ui-kit changes.".into(),
tags: Vec::new(),
links: Vec::new(),
slug: "alpha".into(),
alias: None,
},
Note {
title: "Beta".into(),
path: PathBuf::new(),
content: "Follow up on #testing checklist.".into(),
tags: Vec::new(),
links: Vec::new(),
slug: "beta".into(),
alias: None,
},
Note {
title: "Gamma".into(),
path: PathBuf::new(),
content: "Finalize #ui rollout.".into(),
tags: Vec::new(),
links: Vec::new(),
slug: "gamma".into(),
alias: None,
},
]);

let plugin = NotePlugin {
matcher: SkimMatcherV2::default(),
data: CACHE.clone(),
templates: TEMPLATE_CACHE.clone(),
external_open: NoteExternalOpen::Wezterm,
watcher: None,
};

let list_test = plugin.search("note list #test");
let labels_test: Vec<&str> = list_test.iter().map(|a| a.label.as_str()).collect();
assert_eq!(labels_test, vec!["Alpha", "Beta"]);

let list_ui = plugin.search("note list @ui");
let labels_ui: Vec<&str> = list_ui.iter().map(|a| a.label.as_str()).collect();
assert_eq!(labels_ui, vec!["Alpha", "Gamma"]);

let list_not_ui = plugin.search("note list !#ui");
let labels_not_ui: Vec<&str> = list_not_ui.iter().map(|a| a.label.as_str()).collect();
assert_eq!(labels_not_ui, vec!["Beta"]);

restore_cache(original);
}

#[test]
fn note_tag_lists_tags_and_drills_into_list() {
let original = set_notes(vec![
Expand Down
24 changes: 19 additions & 5 deletions src/plugins/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,16 @@ impl TodoPlugin {
};
let mut entries: Vec<(usize, &TodoEntry)> = guard.iter().enumerate().collect();

let tag_filter = filter.starts_with('#');
let tag_filter = filter.starts_with('#') || filter.starts_with('@');
if tag_filter {
let tag = filter.trim_start_matches('#');
entries.retain(|(_, t)| t.tags.iter().any(|tg| tg.eq_ignore_ascii_case(tag)));
let tag = filter.trim_start_matches(['#', '@']);
let requested = tag.to_lowercase();
entries.retain(|(_, t)| {
!requested.is_empty()
&& t.tags
.iter()
.any(|tg| tg.to_lowercase().contains(&requested))
});
} else if !filter.is_empty() {
entries.retain(|(_, t)| self.matcher.fuzzy_match(&t.text, filter).is_some());
}
Expand Down Expand Up @@ -567,7 +573,10 @@ impl TodoPlugin {
if !filters.include_tags.is_empty() {
entries.retain(|(_, t)| {
filters.include_tags.iter().all(|requested| {
t.tags.iter().any(|tag| tag.eq_ignore_ascii_case(requested))
let requested = requested.to_lowercase();
t.tags
.iter()
.any(|tag| tag.to_lowercase().contains(&requested))
})
});
}
Expand All @@ -577,7 +586,12 @@ impl TodoPlugin {
!filters
.exclude_tags
.iter()
.any(|excluded| t.tags.iter().any(|tag| tag.eq_ignore_ascii_case(excluded)))
.any(|excluded| {
let excluded = excluded.to_lowercase();
t.tags
.iter()
.any(|tag| tag.to_lowercase().contains(&excluded))
})
});
}

Expand Down
18 changes: 16 additions & 2 deletions tests/todo_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,26 @@ fn list_filters_by_tag() {
std::env::set_current_dir(dir.path()).unwrap();

append_todo(TODO_FILE, "alpha", 1, &["rs3".into()]).unwrap();
append_todo(TODO_FILE, "beta", 1, &["other".into()]).unwrap();
append_todo(TODO_FILE, "beta", 1, &["work".into()]).unwrap();
append_todo(TODO_FILE, "gamma", 1, &["workshop".into()]).unwrap();
append_todo(TODO_FILE, "delta", 1, &["ui-kit".into()]).unwrap();
append_todo(TODO_FILE, "epsilon", 1, &["backend".into()]).unwrap();

let plugin = TodoPlugin::default();
let results = plugin.search("todo list #rs3");
let results = plugin.search("todo list #rs");
assert_eq!(results.len(), 1);
assert!(results[0].label.contains("alpha"));

let results = plugin.search("todo list @wor");
let labels: Vec<&str> = results.iter().map(|action| action.label.as_str()).collect();
assert_eq!(results.len(), 2);
assert!(labels.iter().any(|label| label.contains("beta")));
assert!(labels.iter().any(|label| label.contains("gamma")));

let results = plugin.search("todo list !#ui");
let labels: Vec<&str> = results.iter().map(|action| action.label.as_str()).collect();
assert_eq!(results.len(), 4);
assert!(!labels.iter().any(|label| label.contains("delta")));
}

#[test]
Expand Down