diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 9caa73f3e..5bde1ccf2 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -1565,7 +1565,7 @@ where id in %s""" newRow = max(newRow, 0) self.model.focusedCard = self.model.cards[newRow] self.model.endReset() - self.mw.requireReset(reason=ResetReason.BrowserDeleteNote, context=self) + self.mw.reset() tooltip( ngettext("%d note deleted.", "%d notes deleted.", len(nids)) % len(nids) ) diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 86edfeb45..5f2c5e8de 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -491,7 +491,7 @@ class Editor: self.web.eval("setBackgrounds(%s);" % json.dumps(cols)) def showDupes(self): - contents = stripHTMLMedia(self.note.fields[0]) + contents = html.escape(stripHTMLMedia(self.note.fields[0])) browser = aqt.dialogs.open("Browser", self.mw) browser.form.searchEdit.lineEdit().setText( '"dupe:%s,%s"' % (self.note.model()["id"], contents) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 86dc7b534..58616de3f 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -907,6 +907,7 @@ title="%s" %s>%s""" % ( self.media_syncer.start() def on_collection_sync_finished(): + self.col.clearUndo() self.reset() after_sync() diff --git a/rslib/src/media/database.rs b/rslib/src/media/database.rs index e4fef9027..1ce500567 100644 --- a/rslib/src/media/database.rs +++ b/rslib/src/media/database.rs @@ -19,7 +19,7 @@ pub(super) fn open_or_create>(path: P) -> Result { db.pragma_update(None, "page_size", &4096)?; db.pragma_update(None, "legacy_file_format", &false)?; - db.pragma_update(None, "journal", &"wal")?; + db.pragma_update_and_check(None, "journal_mode", &"wal", |_| Ok(()))?; initial_db_setup(&mut db)?; diff --git a/rslib/src/notes.rs b/rslib/src/notes.rs index 23e9f7019..9a1256c5a 100644 --- a/rslib/src/notes.rs +++ b/rslib/src/notes.rs @@ -274,6 +274,8 @@ impl Collection { // nothing to do return Ok(()); } + } else { + return Err(AnkiError::NotFound); } self.transact(None, |col| { diff --git a/rslib/src/notetype/mod.rs b/rslib/src/notetype/mod.rs index 57ce56d2d..63fd95a39 100644 --- a/rslib/src/notetype/mod.rs +++ b/rslib/src/notetype/mod.rs @@ -163,15 +163,26 @@ impl NoteType { .collect() } + /// Adjust sort index to match repositioned fields. fn reposition_sort_idx(&mut self) { - let adjusted_idx = self.fields.iter().enumerate().find_map(|(idx, f)| { - if f.ord == Some(self.config.sort_field_idx) { - Some(idx) - } else { - None - } - }); - self.config.sort_field_idx = adjusted_idx.unwrap_or(0) as u32; + self.config.sort_field_idx = self + .fields + .iter() + .enumerate() + .find_map(|(idx, f)| { + if f.ord == Some(self.config.sort_field_idx) { + Some(idx as u32) + } else { + None + } + }) + .unwrap_or_else(|| { + // provided ordinal not on any existing field; cap to bounds + self.config + .sort_field_idx + .max(0) + .min((self.fields.len() - 1) as u32) + }); } pub(crate) fn normalize_names(&mut self) { diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index b145c4635..5b24f6453 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -9,11 +9,9 @@ use crate::{ err::Result, notes::field_checksum, notetype::NoteTypeID, - text::matches_wildcard, + text::{matches_wildcard, text_to_re}, text::{normalize_to_nfc, strip_html_preserving_image_filenames, without_combining}, }; -use lazy_static::lazy_static; -use regex::{Captures, Regex}; use std::{borrow::Cow, fmt::Write}; pub(crate) struct SqlWriter<'a> { @@ -458,38 +456,6 @@ fn glob_to_re(glob: &str) -> Option { Some(text_to_re(glob)) } -/// Escape text, converting glob characters to regex syntax, then return. -fn text_to_re(glob: &str) -> String { - lazy_static! { - static ref ESCAPED: Regex = Regex::new(r"(\\\\)?\\\*").unwrap(); - static ref GLOB: Regex = Regex::new(r"(\\\\)?[_%]").unwrap(); - } - - let escaped = regex::escape(glob); - - let text = ESCAPED.replace_all(&escaped, |caps: &Captures| { - if caps.get(0).unwrap().as_str().len() == 2 { - ".*" - } else { - r"\*" - } - }); - - let text2 = GLOB.replace_all(&text, |caps: &Captures| { - match caps.get(0).unwrap().as_str() { - "_" => ".", - "%" => ".*", - other => { - // strip off the escaping char - &other[2..] - } - } - .to_string() - }); - - text2.into() -} - #[derive(Debug, PartialEq, Clone, Copy)] pub enum RequiredTable { Notes, diff --git a/rslib/src/tags.rs b/rslib/src/tags.rs index 93b22e41a..a3cd45106 100644 --- a/rslib/src/tags.rs +++ b/rslib/src/tags.rs @@ -5,6 +5,7 @@ use crate::{ collection::Collection, err::{AnkiError, Result}, notes::{NoteID, TransformNoteOutput}, + text::text_to_re, {text::normalize_to_nfc, types::Usn}, }; use regex::{NoExpand, Regex, Replacer}; @@ -123,11 +124,7 @@ impl Collection { // generate regexps let tags = split_tags(tags) .map(|tag| { - let tag = if regex { - tag.into() - } else { - regex::escape(tag) - }; + let tag = if regex { tag.into() } else { text_to_re(tag) }; Regex::new(&format!("(?i){}", tag)) .map_err(|_| AnkiError::invalid_input("invalid regex")) }) @@ -234,6 +231,10 @@ mod test { let note = col.storage.get_note(note.id)?.unwrap(); assert_eq!(note.tags[0], "bar"); + col.replace_tags_for_notes(&[note.id], "b*r", "baz", false)?; + let note = col.storage.get_note(note.id)?.unwrap(); + assert_eq!(note.tags[0], "baz"); + col.replace_tags_for_notes(&[note.id], "b.r", "baz", true)?; let note = col.storage.get_note(note.id)?.unwrap(); assert_eq!(note.tags[0], "baz"); diff --git a/rslib/src/text.rs b/rslib/src/text.rs index 7272b3e6d..57845615e 100644 --- a/rslib/src/text.rs +++ b/rslib/src/text.rs @@ -261,6 +261,38 @@ pub(crate) fn without_combining(s: &str) -> Cow { .into() } +/// Escape text, converting glob characters to regex syntax, then return. +pub(crate) fn text_to_re(glob: &str) -> String { + lazy_static! { + static ref ESCAPED: Regex = Regex::new(r"(\\\\)?\\\*").unwrap(); + static ref GLOB: Regex = Regex::new(r"(\\\\)?[_%]").unwrap(); + } + + let escaped = regex::escape(glob); + + let text = ESCAPED.replace_all(&escaped, |caps: &Captures| { + if caps.get(0).unwrap().as_str().len() == 2 { + ".*" + } else { + r"\*" + } + }); + + let text2 = GLOB.replace_all(&text, |caps: &Captures| { + match caps.get(0).unwrap().as_str() { + "_" => ".", + "%" => ".*", + other => { + // strip off the escaping char + &other[2..] + } + } + .to_string() + }); + + text2.into() +} + #[cfg(test)] mod test { use super::matches_wildcard;