mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 17:26: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
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
mod bulkadd;
|
||||||
mod dragdrop;
|
mod dragdrop;
|
||||||
mod prefix_replacer;
|
mod prefix_replacer;
|
||||||
mod register;
|
mod register;
|
||||||
|
|
|
@ -38,6 +38,23 @@ impl Collection {
|
||||||
Ok((tags, added))
|
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
|
/// 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 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
|
/// 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)
|
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)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in a new issue