From 65b5aefd0705463cb12e72e254f798f90c96e815 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 21 Jul 2025 15:00:46 +0700 Subject: [PATCH 1/3] Bump version to 25.07.3 --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index 8dec45b51..3820668b1 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -25.07.3rc1 +25.07.3 From 35a889e1ed8174e7e64c40358d48a8e09b82e8df Mon Sep 17 00:00:00 2001 From: llama Date: Tue, 22 Jul 2025 19:11:33 +0800 Subject: [PATCH 2/3] Prioritise prefix matches in tag autocomplete results (#4212) * prioritise prefix matches in tag autocomplete results * update/add tests * fix lint was fine locally though? --- rslib/src/tags/complete.rs | 58 ++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/rslib/src/tags/complete.rs b/rslib/src/tags/complete.rs index 1093017b0..f995b63a2 100644 --- a/rslib/src/tags/complete.rs +++ b/rslib/src/tags/complete.rs @@ -12,14 +12,20 @@ impl Collection { .map(component_to_regex) .collect::>()?; let mut tags = vec![]; + let mut priority = vec![]; self.storage.get_tags_by_predicate(|tag| { - if tags.len() <= limit && filters_match(&filters, tag) { - tags.push(tag.to_string()); + if priority.len() + tags.len() <= limit { + match filters_match(&filters, tag) { + Some(true) => priority.push(tag.to_string()), + Some(_) => tags.push(tag.to_string()), + _ => {} + } } // we only need the tag name false })?; - Ok(tags) + priority.append(&mut tags); + Ok(priority) } } @@ -27,20 +33,26 @@ fn component_to_regex(component: &str) -> Result { Regex::new(&format!("(?i){}", regex::escape(component))).map_err(Into::into) } -fn filters_match(filters: &[Regex], tag: &str) -> bool { +/// Returns None if tag wasn't a match, otherwise whether it was a consecutive +/// prefix match +fn filters_match(filters: &[Regex], tag: &str) -> Option { let mut remaining_tag_components = tag.split("::"); + let mut is_prefix = true; 'outer: for filter in filters { loop { if let Some(component) = remaining_tag_components.next() { - if filter.is_match(component) { + if let Some(m) = filter.find(component) { + is_prefix &= m.start() == 0; continue 'outer; + } else { + is_prefix = false; } } else { - return false; + return None; } } } - true + Some(is_prefix) } #[cfg(test)] @@ -50,28 +62,32 @@ mod test { #[test] fn matching() -> Result<()> { let filters = &[component_to_regex("b")?]; - assert!(filters_match(filters, "ABC")); - assert!(filters_match(filters, "ABC::def")); - assert!(filters_match(filters, "def::abc")); - assert!(!filters_match(filters, "def")); + assert!(filters_match(filters, "ABC").is_some()); + assert!(filters_match(filters, "ABC::def").is_some()); + assert!(filters_match(filters, "def::abc").is_some()); + assert!(filters_match(filters, "def").is_none()); let filters = &[component_to_regex("b")?, component_to_regex("E")?]; - assert!(!filters_match(filters, "ABC")); - assert!(filters_match(filters, "ABC::def")); - assert!(!filters_match(filters, "def::abc")); - assert!(!filters_match(filters, "def")); + assert!(filters_match(filters, "ABC").is_none()); + assert!(filters_match(filters, "ABC::def").is_some()); + assert!(filters_match(filters, "def::abc").is_none()); + assert!(filters_match(filters, "def").is_none()); let filters = &[ component_to_regex("a")?, component_to_regex("c")?, component_to_regex("e")?, ]; - assert!(!filters_match(filters, "ace")); - assert!(!filters_match(filters, "a::c")); - assert!(!filters_match(filters, "c::e")); - assert!(filters_match(filters, "a::c::e")); - assert!(filters_match(filters, "a::b::c::d::e")); - assert!(filters_match(filters, "1::a::b::c::d::e::f")); + assert!(filters_match(filters, "ace").is_none()); + assert!(filters_match(filters, "a::c").is_none()); + assert!(filters_match(filters, "c::e").is_none()); + assert!(filters_match(filters, "a::c::e").is_some()); + assert!(filters_match(filters, "a::b::c::d::e").is_some()); + assert!(filters_match(filters, "1::a::b::c::d::e::f").is_some()); + + assert_eq!(filters_match(filters, "a1::c2::e3"), Some(true)); + assert_eq!(filters_match(filters, "a1::c2::?::e4"), Some(false)); + assert_eq!(filters_match(filters, "a1::c2::3e"), Some(false)); Ok(()) } From 47c10941959d927c59fa2fe0f556d0f7b3978fce Mon Sep 17 00:00:00 2001 From: user1823 <92206575+user1823@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:56:44 +0530 Subject: [PATCH 3/3] Add last_review_time to _to_backend_card (#4218) Presumably, without this change, add-ons would delete the value of last_review_time from the card when they modify the card. --- pylib/anki/cards.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pylib/anki/cards.py b/pylib/anki/cards.py index 854d4ed18..b8154510e 100644 --- a/pylib/anki/cards.py +++ b/pylib/anki/cards.py @@ -133,6 +133,7 @@ class Card(DeprecatedNamesMixin): memory_state=self.memory_state, desired_retention=self.desired_retention, decay=self.decay, + last_review_time_secs=self.last_review_time, ) @deprecated(info="please use col.update_card()")