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
6 changes: 5 additions & 1 deletion src/help_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,11 @@ fn example_queries(name: &str) -> Option<&'static [&'static str]> {
"stopwatch" => Some(&["sw start", "sw list"]),
"task_manager" => Some(&["tm"]),
"text_case" => Some(&["case snake Hello World"]),
"todo" => Some(&["todo add buy milk @home", "todo list"]),
"todo" => Some(&[
"todo add buy milk @home",
"todo list",
"todo list @testing @ui",
]),
"wikipedia" => Some(&["wiki rust"]),
"help" => Some(&["help"]),
_ => None,
Expand Down
73 changes: 58 additions & 15 deletions src/plugins/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,26 +403,69 @@ impl TodoPlugin {
};
let mut entries: Vec<(usize, &TodoEntry)> = guard.iter().enumerate().collect();

let mut requested_tags: Vec<&str> = Vec::new();
let mut excluded_tags: Vec<&str> = Vec::new();
let mut text_tokens: Vec<&str> = Vec::new();
let mut negative = false;
if let Some(stripped) = filter.strip_prefix('!') {
negative = true;
filter = stripped.trim();
for token in filter.split_whitespace() {
let mut token = token;
let mut token_negated = false;
if let Some(stripped) = token.strip_prefix('!') {
token_negated = true;
token = stripped;
}

if let Some(tag) = token.strip_prefix('@').or_else(|| token.strip_prefix('#')) {
if !tag.is_empty() {
if token_negated {
excluded_tags.push(tag);
} else {
requested_tags.push(tag);
}
}
continue;
}

if token_negated
&& !negative
&& !token.is_empty()
&& text_tokens.is_empty()
{
negative = true;
}

if !token.is_empty() {
text_tokens.push(token);
}
}

let tag_filter = filter.starts_with('#');
if tag_filter {
let tag = filter.trim_start_matches('#');
let text_filter = text_tokens.join(" ");
let has_tag_filter = !requested_tags.is_empty() || !excluded_tags.is_empty();

// Tag filters run first, then text filters apply fuzzy matching against remaining text.
if !requested_tags.is_empty() {
entries.retain(|(_, t)| {
let has_tag = t.tags.iter().any(|tg| tg.eq_ignore_ascii_case(tag));
if negative {
!has_tag
} else {
has_tag
}
requested_tags.iter().all(|requested| {
t.tags
.iter()
.any(|tag| tag.eq_ignore_ascii_case(requested))
})
});
} else if !filter.is_empty() {
}

if !excluded_tags.is_empty() {
entries.retain(|(_, t)| {
!excluded_tags.iter().any(|excluded| {
t.tags
.iter()
.any(|tag| tag.eq_ignore_ascii_case(excluded))
})
});
}

if !text_filter.is_empty() {
entries.retain(|(_, t)| {
let text_match = self.matcher.fuzzy_match(&t.text, filter).is_some();
let text_match = self.matcher.fuzzy_match(&t.text, &text_filter).is_some();
if negative {
!text_match
} else {
Expand All @@ -431,7 +474,7 @@ impl TodoPlugin {
});
}

if filter.is_empty() || tag_filter {
if text_filter.is_empty() || has_tag_filter {
entries.sort_by(|a, b| b.1.priority.cmp(&a.1.priority));
}

Expand Down