mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
speed up "add tags" and avoid usage of regex
This commit is contained in:
parent
08895c58d9
commit
b287cd5238
4 changed files with 106 additions and 36 deletions
88
rslib/src/tags/bulkadd.rs
Normal file
88
rslib/src/tags/bulkadd.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
//! Adding tags to selected notes in the browse screen.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use unicase::UniCase;
|
||||
|
||||
use super::{join_tags, split_tags};
|
||||
use crate::{notes::NoteTags, prelude::*};
|
||||
|
||||
impl Collection {
|
||||
pub fn add_tags_to_notes(&mut self, nids: &[NoteID], tags: &str) -> Result<OpOutput<usize>> {
|
||||
self.transact(Op::UpdateTag, |col| col.add_tags_to_notes_inner(nids, tags))
|
||||
}
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
fn add_tags_to_notes_inner(&mut self, nids: &[NoteID], tags: &str) -> Result<usize> {
|
||||
let usn = self.usn()?;
|
||||
|
||||
// will update tag list for any new tags, and match case
|
||||
let tags_to_add = self.canonified_tags_as_vec(tags, usn)?;
|
||||
|
||||
// modify notes
|
||||
let mut match_count = 0;
|
||||
let notes = self.storage.get_note_tags_by_id_list(nids)?;
|
||||
for original in notes {
|
||||
if let Some(updated_tags) = add_missing_tags(&original.tags, &tags_to_add) {
|
||||
match_count += 1;
|
||||
let mut note = NoteTags {
|
||||
tags: updated_tags,
|
||||
..original
|
||||
};
|
||||
note.set_modified(usn);
|
||||
self.update_note_tags_undoable(¬e, original)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(match_count)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the sorted new tag string if any tags were added.
|
||||
fn add_missing_tags(note_tags: &str, desired: &[UniCase<String>]) -> Option<String> {
|
||||
let mut note_tags: HashSet<_> = split_tags(note_tags)
|
||||
.map(ToOwned::to_owned)
|
||||
.map(UniCase::new)
|
||||
.collect();
|
||||
|
||||
let mut modified = false;
|
||||
for tag in desired {
|
||||
if !note_tags.contains(tag) {
|
||||
note_tags.insert(tag.clone());
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
if !modified {
|
||||
return None;
|
||||
}
|
||||
|
||||
// sort
|
||||
let mut tags: Vec<_> = note_tags.into_iter().collect::<Vec<_>>();
|
||||
tags.sort_unstable();
|
||||
|
||||
// turn back into a string
|
||||
let tags: Vec<_> = tags.into_iter().map(|s| s.into_inner()).collect();
|
||||
Some(join_tags(&tags))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_missing() {
|
||||
let desired: Vec<_> = ["xyz", "abc", "DEF"]
|
||||
.iter()
|
||||
.map(|s| UniCase::new(s.to_string()))
|
||||
.collect();
|
||||
|
||||
let add_to = |text| add_missing_tags(text, &desired).unwrap();
|
||||
|
||||
assert_eq!(&add_to(""), " abc DEF xyz ");
|
||||
assert_eq!(&add_to("XYZ deF aaa"), " aaa abc deF XYZ ");
|
||||
assert_eq!(add_missing_tags("def xyz abc", &desired).is_none(), true);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
mod bulkadd;
|
||||
mod dragdrop;
|
||||
mod prefix_replacer;
|
||||
mod register;
|
||||
|
|
|
@ -38,6 +38,23 @@ impl Collection {
|
|||
Ok((tags, added))
|
||||
}
|
||||
|
||||
/// Returns true if any cards were added to the tag list.
|
||||
pub(crate) fn canonified_tags_as_vec(
|
||||
&mut self,
|
||||
tags: &str,
|
||||
usn: Usn,
|
||||
) -> Result<Vec<UniCase<String>>> {
|
||||
let mut out_tags = vec![];
|
||||
|
||||
for tag in split_tags(tags) {
|
||||
let mut tag = Tag::new(tag.to_string(), usn);
|
||||
self.register_tag(&mut tag)?;
|
||||
out_tags.push(UniCase::new(tag.name));
|
||||
}
|
||||
|
||||
Ok(out_tags)
|
||||
}
|
||||
|
||||
/// Adjust tag casing to match any existing parents, and register it if it's not already
|
||||
/// in the tags list. True if the tag was added and not already in tag list.
|
||||
/// In the case the tag is already registered, tag will be mutated to match the existing
|
||||
|
|
|
@ -57,42 +57,6 @@ impl Collection {
|
|||
self.replace_tags_for_notes_inner(nids, &tags, repl)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_tags_to_notes(&mut self, nids: &[NoteID], tags: &str) -> Result<OpOutput<usize>> {
|
||||
let tags: Vec<_> = split_tags(tags).collect();
|
||||
let matcher = regex::RegexSet::new(
|
||||
tags.iter()
|
||||
.map(|s| regex::escape(s))
|
||||
.map(|s| format!("(?i)^{}$", s)),
|
||||
)
|
||||
.map_err(|_| AnkiError::invalid_input("invalid regex"))?;
|
||||
|
||||
self.transact(Op::UpdateTag, |col| {
|
||||
col.transform_notes(nids, |note, _nt| {
|
||||
let mut need_to_add = true;
|
||||
let mut match_count = 0;
|
||||
for tag in ¬e.tags {
|
||||
if matcher.is_match(tag) {
|
||||
match_count += 1;
|
||||
}
|
||||
if match_count == tags.len() {
|
||||
need_to_add = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if need_to_add {
|
||||
note.tags.extend(tags.iter().map(|&s| s.to_string()))
|
||||
}
|
||||
|
||||
Ok(TransformNoteOutput {
|
||||
changed: need_to_add,
|
||||
generate_cards: false,
|
||||
mark_modified: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Reference in a new issue