Merge pull request #923 from RumovZ/fix-write-dupe

Fix writing dupe node and escape issues
This commit is contained in:
Damien Elmes 2021-01-14 10:49:48 +10:00 committed by GitHub
commit 23bc7ac892
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 47 additions and 12 deletions

View file

@ -48,6 +48,8 @@ DeckTreeNode = pb.DeckTreeNode
StockNoteType = pb.StockNoteType
FilterToSearchIn = pb.FilterToSearchIn
NamedFilter = pb.FilterToSearchIn.NamedFilter
DupeIn = pb.FilterToSearchIn.DupeIn
BackendNoteTypeID = pb.NoteTypeID
ConcatSeparator = pb.ConcatenateSearchesIn.Separator
SyncAuth = pb.SyncAuth
SyncOutput = pb.SyncCollectionOut

View file

@ -21,8 +21,10 @@ from anki.lang import without_unicode_isolation
from anki.models import NoteType
from anki.notes import Note
from anki.rsbackend import (
BackendNoteTypeID,
ConcatSeparator,
DeckTreeNode,
DupeIn,
FilterToSearchIn,
InvalidInput,
NamedFilter,
@ -2016,6 +2018,17 @@ where id in %s"""
# Edit: finding dupes
######################################################################
# filter called by the editor
def search_dupe(self, mid: int, text: str):
self.form.searchEdit.lineEdit().setText(
self.col.backend.filter_to_search(
FilterToSearchIn(
dupe=DupeIn(mid=BackendNoteTypeID(ntid=mid), text=text)
)
)
)
self.onSearchActivated()
def onFindDupes(self):
self.editor.saveNow(self._onFindDupes)

View file

@ -537,12 +537,8 @@ class Editor:
self.web.eval("setBackgrounds(%s);" % json.dumps(cols))
def showDupes(self):
contents = self.note.fields[0].replace('"', r"\"")
browser = aqt.dialogs.open("Browser", self.mw)
browser.form.searchEdit.lineEdit().setText(
'"dupe:%s,%s"' % (self.note.model()["id"], contents)
)
browser.onSearchActivated()
browser.search_dupe(self.note.model()["id"], self.note.fields[0])
def fieldsAreBlank(self, previousNote=None):
if not self.note:

View file

@ -775,12 +775,17 @@ message FilterToSearchIn {
NO_FLAG = 15;
ANY_FLAG = 16;
}
message DupeIn {
NoteTypeID mid = 1;
string text = 2;
}
oneof filter {
NamedFilter name = 1;
string tag = 2;
string deck = 3;
string note = 4;
uint32 template = 5;
DupeIn dupe = 6;
}
}

View file

@ -327,6 +327,10 @@ impl From<pb::FilterToSearchIn> for Node<'_> {
Filter::Template(u) => {
Node::Search(SearchNode::CardTemplate(TemplateKind::Ordinal(u as u16)))
}
Filter::Dupe(dupe) => Node::Search(SearchNode::Duplicates {
note_type_id: dupe.mid.unwrap_or(pb::NoteTypeId { ntid: 0 }).into(),
text: dupe.text.into(),
}),
}
}
}

View file

@ -293,8 +293,8 @@ fn search_node_for_text_with_argument<'a>(
"is" => parse_state(val)?,
"flag" => parse_flag(val)?,
"rated" => parse_rated(val)?,
"dupe" => parse_dupe(val)?,
"resched" => parse_resched(val)?,
"dupe" => parse_dupes(val)?,
"prop" => parse_prop(val)?,
"re" => SearchNode::Regex(unescape_quotes(val)),
"nc" => SearchNode::NoCombining(unescape(val)?),
@ -392,14 +392,14 @@ fn parse_resched(val: &str) -> ParseResult<SearchNode<'static>> {
Ok(SearchNode::Rated { days, ease })
}
/// eg dupes:1231,hello
fn parse_dupes(val: &str) -> ParseResult<SearchNode> {
/// eg dupe:1231,hello
fn parse_dupe(val: &str) -> ParseResult<SearchNode> {
let mut it = val.splitn(2, ',');
let mid: NoteTypeID = it.next().unwrap().parse()?;
let text = it.next().ok_or(ParseError {})?;
Ok(SearchNode::Duplicates {
note_type_id: mid,
text: unescape_quotes(text),
text: unescape_quotes_and_backslashes(text),
})
}
@ -478,6 +478,15 @@ fn unescape_quotes(s: &str) -> Cow<str> {
}
}
/// For non-globs like dupe text without any assumption about the content
fn unescape_quotes_and_backslashes(s: &str) -> Cow<str> {
if s.contains('"') || s.contains('\\') {
s.replace(r#"\""#, "\"").replace(r"\\", r"\").into()
} else {
s.into()
}
}
/// Unescape chars with special meaning to the parser.
fn unescape(txt: &str) -> ParseResult<Cow<str>> {
if is_invalid_escape(txt) {

View file

@ -123,7 +123,7 @@ impl SqlWriter<'_> {
self.write_single_field(&norm(field), &self.norm_note(text), *is_re)?
}
SearchNode::Duplicates { note_type_id, text } => {
self.write_dupes(*note_type_id, &self.norm_note(text))?
self.write_dupe(*note_type_id, &self.norm_note(text))?
}
SearchNode::Regex(re) => self.write_regex(&self.norm_note(re)),
SearchNode::NoCombining(text) => self.write_no_combining(&self.norm_note(text)),
@ -443,7 +443,7 @@ impl SqlWriter<'_> {
Ok(())
}
fn write_dupes(&mut self, ntid: NoteTypeID, text: &str) -> Result<()> {
fn write_dupe(&mut self, ntid: NoteTypeID, text: &str) -> Result<()> {
let text_nohtml = strip_html_preserving_media_filenames(text);
let csum = field_checksum(text_nohtml.as_ref());

View file

@ -119,7 +119,7 @@ fn write_search_node(node: &SearchNode) -> String {
NoteType(s) => quote(&format!("note:{}", s)),
Rated { days, ease } => write_rated(days, ease),
Tag(s) => quote(&format!("tag:{}", s)),
Duplicates { note_type_id, text } => quote(&format!("dupes:{},{}", note_type_id, text)),
Duplicates { note_type_id, text } => write_dupe(note_type_id, text),
State(k) => write_state(k),
Flag(u) => format!("\"flag:{}\"", u),
NoteIDs(s) => format!("\"nid:{}\"", s),
@ -163,6 +163,12 @@ fn write_rated(days: &u32, ease: &EaseKind) -> String {
}
}
/// Escape double quotes and backslashes: \"
fn write_dupe(note_type_id: &NoteTypeIDType, text: &str) -> String {
let esc = text.replace(r"\", r"\\").replace('"', r#"\""#);
format!("\"dupe:{},{}\"", note_type_id, esc)
}
fn write_state(kind: &StateKind) -> String {
use StateKind::*;
format!(