From 19bbcb4cc0002daf4ed6cc65c61c2a2a35cecc21 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 18 Jan 2023 23:05:28 +1000 Subject: [PATCH] Use backend for extracting cloze text to type Closes #2311 --- proto/anki/card_rendering.proto | 7 +++++++ pylib/anki/collection.py | 3 +++ qt/aqt/reviewer.py | 19 ++----------------- rslib/src/backend/cardrendering.rs | 11 +++++++++++ rslib/src/cloze.rs | 28 ++++++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/proto/anki/card_rendering.proto b/proto/anki/card_rendering.proto index ab9dc6a44..aaa8132ac 100644 --- a/proto/anki/card_rendering.proto +++ b/proto/anki/card_rendering.proto @@ -27,6 +27,8 @@ service CardRenderingService { rpc DecodeIriPaths(generic.String) returns (generic.String); rpc StripHtml(StripHtmlRequest) returns (generic.String); rpc CompareAnswer(CompareAnswerRequest) returns (generic.String); + rpc ExtractClozeForTyping(ExtractClozeForTypingRequest) + returns (generic.String); } message ExtractAVTagsRequest { @@ -138,3 +140,8 @@ message CompareAnswerRequest { string expected = 1; string provided = 2; } + +message ExtractClozeForTypingRequest { + string text = 1; + uint32 ordinal = 2; +} diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 7f9239d78..0992cabf1 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -1219,6 +1219,9 @@ class Collection(DeprecatedNamesMixin): def compare_answer(self, expected: str, provided: str) -> str: return self._backend.compare_answer(expected=expected, provided=provided) + def extract_cloze_for_typing(self, text: str, ordinal: int) -> str: + return self._backend.extract_cloze_for_typing(text=text, ordinal=ordinal) + # Timeboxing ########################################################################## # fixme: there doesn't seem to be a good reason why this code is in main.py diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index eb7e1232a..3583734e8 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -630,23 +630,8 @@ class Reviewer: return re.sub(self.typeAnsPat, repl, buf) - def _contentForCloze(self, txt: str, idx: int) -> str: - matches = re.findall(r"\{\{c%s::(.+?)\}\}" % idx, txt, re.DOTALL) - if not matches: - return None - - def noHint(txt: str) -> str: - if "::" in txt: - return txt.split("::")[0] - return txt - - matches = [noHint(txt) for txt in matches] - uniqMatches = set(matches) - if len(uniqMatches) == 1: - txt = matches[0] - else: - txt = ", ".join(matches) - return txt + def _contentForCloze(self, txt: str, idx: int) -> str | None: + return self.mw.col.extract_cloze_for_typing(txt, idx) or None def _getTypedAnswer(self) -> None: self.web.evalWithCallback("getTypedAnswer();", self._onTypedAnswer) diff --git a/rslib/src/backend/cardrendering.rs b/rslib/src/backend/cardrendering.rs index 363c80793..e09a691a2 100644 --- a/rslib/src/backend/cardrendering.rs +++ b/rslib/src/backend/cardrendering.rs @@ -4,6 +4,7 @@ use super::Backend; use crate::card_rendering::extract_av_tags; use crate::card_rendering::strip_av_tags; +use crate::cloze::extract_cloze_for_typing; use crate::latex::extract_latex; use crate::latex::extract_latex_expanding_clozes; use crate::latex::ExtractedLatex; @@ -12,6 +13,7 @@ use crate::notetype::CardTemplateSchema11; use crate::notetype::RenderCardOutput; use crate::pb; pub(super) use crate::pb::card_rendering::cardrendering_service::Service as CardRenderingService; +use crate::pb::card_rendering::ExtractClozeForTypingRequest; use crate::prelude::*; use crate::template::RenderedNode; use crate::text::decode_iri_paths; @@ -164,6 +166,15 @@ impl CardRenderingService for Backend { ) -> Result { Ok(compare_answer(&input.expected, &input.provided).into()) } + + fn extract_cloze_for_typing( + &self, + input: ExtractClozeForTypingRequest, + ) -> Result { + Ok(extract_cloze_for_typing(&input.text, input.ordinal as u16) + .to_string() + .into()) + } } fn rendered_nodes_to_proto( diff --git a/rslib/src/cloze.rs b/rslib/src/cloze.rs index ae63640b1..1e091e533 100644 --- a/rslib/src/cloze.rs +++ b/rslib/src/cloze.rs @@ -306,6 +306,21 @@ pub fn reveal_cloze_text_only(text: &str, cloze_ord: u16, question: bool) -> Cow output.join(", ").into() } +pub fn extract_cloze_for_typing(text: &str, cloze_ord: u16) -> Cow { + let mut output = Vec::new(); + for node in &parse_text_with_clozes(text) { + reveal_cloze_text_in_nodes(node, cloze_ord, false, &mut output); + } + if output.is_empty() { + "".into() + } else if output.iter().min() == output.iter().max() { + // If all matches are identical text, they get collapsed into a single entry + output.pop().unwrap().into() + } else { + output.join(", ").into() + } +} + /// If text contains any LaTeX tags, render the front and back /// of each cloze deletion so that LaTeX can be generated. If /// no LaTeX is found, returns an empty string. @@ -419,6 +434,19 @@ mod test { ); } + #[test] + fn clozes_for_typing() { + assert_eq!(extract_cloze_for_typing("{{c2::foo}}", 1), ""); + assert_eq!( + extract_cloze_for_typing("{{c1::foo}} {{c1::bar}} {{c1::foo}}", 1), + "foo, bar, foo" + ); + assert_eq!( + extract_cloze_for_typing("{{c1::foo}} {{c1::foo}} {{c1::foo}}", 1), + "foo" + ); + } + #[test] fn nested_cloze_plain_text() { assert_eq!(