From 4f0876481ab4016ccdd2b545bac875fb7a50d449 Mon Sep 17 00:00:00 2001 From: llama <100429699+iamllama@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:33:11 +0800 Subject: [PATCH 1/6] modify render_card to return whether card was empty --- rslib/src/template.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rslib/src/template.rs b/rslib/src/template.rs index a24ffec5f92..85ebec423b5 100644 --- a/rslib/src/template.rs +++ b/rslib/src/template.rs @@ -592,6 +592,7 @@ pub struct RenderCardRequest<'a> { pub partial_render: bool, } +/// Returns `(qnodes, anodes, is_empty)` pub fn render_card( RenderCardRequest { qfmt, @@ -603,7 +604,7 @@ pub fn render_card( tr, partial_render: partial_for_python, }: RenderCardRequest<'_>, -) -> Result<(Vec, Vec)> { +) -> Result<(Vec, Vec, bool)> { // prepare context let mut context = RenderContext { fields: field_map, @@ -638,7 +639,7 @@ pub fn render_card( }; if let Some(text) = empty_message { qnodes.push(RenderedNode::Text { text: text.clone() }); - return Ok((qnodes, vec![RenderedNode::Text { text }])); + return Ok((qnodes, vec![RenderedNode::Text { text }], true)); } // answer side @@ -654,7 +655,7 @@ pub fn render_card( .and_then(|tmpl| tmpl.render(&context, tr)) .map_err(|e| template_error_to_anki_error(e, false, browser, tr))?; - Ok((qnodes, anodes)) + Ok((qnodes, anodes, false)) } fn cloze_is_empty(field_map: &HashMap<&str, Cow>, card_ord: u16) -> bool { From 9eb87b1d94a24ab701dd974e2920e7db99fc9a86 Mon Sep 17 00:00:00 2001 From: llama <100429699+iamllama@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:34:51 +0800 Subject: [PATCH 2/6] plumbing --- rslib/src/notetype/render.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rslib/src/notetype/render.rs b/rslib/src/notetype/render.rs index ffbd5eb5caf..c8da3abdfeb 100644 --- a/rslib/src/notetype/render.rs +++ b/rslib/src/notetype/render.rs @@ -20,6 +20,7 @@ pub struct RenderCardOutput { pub anodes: Vec, pub css: String, pub latex_svg: bool, + pub is_empty: bool, } impl RenderCardOutput { @@ -136,7 +137,7 @@ impl Collection { ) }; - let (qnodes, anodes) = render_card(RenderCardRequest { + let (qnodes, anodes, is_empty) = render_card(RenderCardRequest { qfmt, afmt, field_map: &field_map, @@ -151,6 +152,7 @@ impl Collection { anodes, css: nt.config.css.clone(), latex_svg: nt.config.latex_svg, + is_empty, }) } From f0461aca58a29712d3c80644c6cf5db07b32ac13 Mon Sep 17 00:00:00 2001 From: llama <100429699+iamllama@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:35:44 +0800 Subject: [PATCH 3/6] add flag to proto message --- proto/anki/card_rendering.proto | 1 + rslib/src/card_rendering/service.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/proto/anki/card_rendering.proto b/proto/anki/card_rendering.proto index 4035ae68b05..0ca8e5fdec2 100644 --- a/proto/anki/card_rendering.proto +++ b/proto/anki/card_rendering.proto @@ -127,6 +127,7 @@ message RenderCardResponse { repeated RenderedTemplateNode answer_nodes = 2; string css = 3; bool latex_svg = 4; + bool is_empty = 5; } message RenderedTemplateNode { diff --git a/rslib/src/card_rendering/service.rs b/rslib/src/card_rendering/service.rs index 8d15857258c..73f8302cacf 100644 --- a/rslib/src/card_rendering/service.rs +++ b/rslib/src/card_rendering/service.rs @@ -219,6 +219,7 @@ impl From for anki_proto::card_rendering::RenderCardResponse { answer_nodes: rendered_nodes_to_proto(o.anodes), css: o.css, latex_svg: o.latex_svg, + is_empty: o.is_empty, } } } From c731c77c12cd8f268091effde472555544252a3f Mon Sep 17 00:00:00 2001 From: llama <100429699+iamllama@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:38:25 +0800 Subject: [PATCH 4/6] plumbing: pass flag along to PartiallyRenderedCard --- pylib/anki/template.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pylib/anki/template.py b/pylib/anki/template.py index 3e993b9ea1d..3cc8afaf829 100644 --- a/pylib/anki/template.py +++ b/pylib/anki/template.py @@ -60,6 +60,7 @@ class PartiallyRenderedCard: anodes: TemplateReplacementList css: str latex_svg: bool + is_empty: bool @classmethod def from_proto( @@ -68,7 +69,9 @@ def from_proto( qnodes = cls.nodes_from_proto(out.question_nodes) anodes = cls.nodes_from_proto(out.answer_nodes) - return PartiallyRenderedCard(qnodes, anodes, out.css, out.latex_svg) + return PartiallyRenderedCard( + qnodes, anodes, out.css, out.latex_svg, out.is_empty + ) @staticmethod def nodes_from_proto( From 274c976be6188b78817032e5e1e7f599b1cca005 Mon Sep 17 00:00:00 2001 From: llama <100429699+iamllama@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:38:58 +0800 Subject: [PATCH 5/6] add tests --- rslib/src/template.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rslib/src/template.rs b/rslib/src/template.rs index 85ebec423b5..69abd8ded9e 100644 --- a/rslib/src/template.rs +++ b/rslib/src/template.rs @@ -1339,13 +1339,14 @@ mod test { tr: &tr, partial_render: true, }; - let qnodes = super::render_card(req.clone()).unwrap().0; + let (qnodes, _, is_empty) = super::render_card(req.clone()).unwrap(); assert_eq!( qnodes[0], FN::Text { text: "test".into() } ); + assert!(is_empty); if let FN::Text { ref text } = qnodes[1] { assert!(text.contains("card is blank")); } else { @@ -1355,7 +1356,7 @@ mod test { // a popular card template expects {{FrontSide}} to resolve to an empty // string on the front side :-( req.qfmt = "{{FrontSide}}{{N}}"; - let qnodes = super::render_card(req.clone()).unwrap().0; + let (qnodes, _, is_empty) = super::render_card(req.clone()).unwrap(); assert_eq!( &qnodes, &[ @@ -1367,8 +1368,10 @@ mod test { FN::Text { text: "N".into() } ] ); + assert!(!is_empty); req.partial_render = false; - let qnodes = super::render_card(req.clone()).unwrap().0; + let (qnodes, _, is_empty) = super::render_card(req.clone()).unwrap(); assert_eq!(&qnodes, &[FN::Text { text: "N".into() }]); + assert!(!is_empty); } } From aa1f9cd42b93a0b5f7a1689569359d57953647f9 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 31 Mar 2025 17:47:10 +0700 Subject: [PATCH 6/6] Use a custom return type for clarity --- rslib/src/notetype/render.rs | 8 ++++---- rslib/src/template.rs | 40 ++++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/rslib/src/notetype/render.rs b/rslib/src/notetype/render.rs index c8da3abdfeb..8f686b0a540 100644 --- a/rslib/src/notetype/render.rs +++ b/rslib/src/notetype/render.rs @@ -137,7 +137,7 @@ impl Collection { ) }; - let (qnodes, anodes, is_empty) = render_card(RenderCardRequest { + let response = render_card(RenderCardRequest { qfmt, afmt, field_map: &field_map, @@ -148,11 +148,11 @@ impl Collection { partial_render, })?; Ok(RenderCardOutput { - qnodes, - anodes, + qnodes: response.qnodes, + anodes: response.anodes, css: nt.config.css.clone(), latex_svg: nt.config.latex_svg, - is_empty, + is_empty: response.is_empty, }) } diff --git a/rslib/src/template.rs b/rslib/src/template.rs index 69abd8ded9e..8d353070e99 100644 --- a/rslib/src/template.rs +++ b/rslib/src/template.rs @@ -592,6 +592,12 @@ pub struct RenderCardRequest<'a> { pub partial_render: bool, } +pub struct RenderCardResponse { + pub qnodes: Vec, + pub anodes: Vec, + pub is_empty: bool, +} + /// Returns `(qnodes, anodes, is_empty)` pub fn render_card( RenderCardRequest { @@ -604,7 +610,7 @@ pub fn render_card( tr, partial_render: partial_for_python, }: RenderCardRequest<'_>, -) -> Result<(Vec, Vec, bool)> { +) -> Result { // prepare context let mut context = RenderContext { fields: field_map, @@ -639,7 +645,11 @@ pub fn render_card( }; if let Some(text) = empty_message { qnodes.push(RenderedNode::Text { text: text.clone() }); - return Ok((qnodes, vec![RenderedNode::Text { text }], true)); + return Ok(RenderCardResponse { + qnodes, + anodes: vec![RenderedNode::Text { text }], + is_empty: true, + }); } // answer side @@ -655,7 +665,11 @@ pub fn render_card( .and_then(|tmpl| tmpl.render(&context, tr)) .map_err(|e| template_error_to_anki_error(e, false, browser, tr))?; - Ok((qnodes, anodes, false)) + Ok(RenderCardResponse { + qnodes, + anodes, + is_empty: false, + }) } fn cloze_is_empty(field_map: &HashMap<&str, Cow>, card_ord: u16) -> bool { @@ -1339,15 +1353,15 @@ mod test { tr: &tr, partial_render: true, }; - let (qnodes, _, is_empty) = super::render_card(req.clone()).unwrap(); + let response = super::render_card(req.clone()).unwrap(); assert_eq!( - qnodes[0], + response.qnodes[0], FN::Text { text: "test".into() } ); - assert!(is_empty); - if let FN::Text { ref text } = qnodes[1] { + assert!(response.is_empty); + if let FN::Text { ref text } = response.qnodes[1] { assert!(text.contains("card is blank")); } else { unreachable!(); @@ -1356,9 +1370,9 @@ mod test { // a popular card template expects {{FrontSide}} to resolve to an empty // string on the front side :-( req.qfmt = "{{FrontSide}}{{N}}"; - let (qnodes, _, is_empty) = super::render_card(req.clone()).unwrap(); + let response = super::render_card(req.clone()).unwrap(); assert_eq!( - &qnodes, + &response.qnodes, &[ FN::Replacement { field_name: "FrontSide".into(), @@ -1368,10 +1382,10 @@ mod test { FN::Text { text: "N".into() } ] ); - assert!(!is_empty); + assert!(!response.is_empty); req.partial_render = false; - let (qnodes, _, is_empty) = super::render_card(req.clone()).unwrap(); - assert_eq!(&qnodes, &[FN::Text { text: "N".into() }]); - assert!(!is_empty); + let response = super::render_card(req.clone()).unwrap(); + assert_eq!(&response.qnodes, &[FN::Text { text: "N".into() }]); + assert!(!response.is_empty); } }