@@ -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