From 251360a4da76a821edfdbfc2776b8b12d56cc91e Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Mon, 9 Mar 2026 10:51:19 -0500 Subject: [PATCH 1/2] lua - escape fenced code correctly at line beginning (#14177) --- news/changelog-1.9.md | 3 ++- .../filters/quarto-pre/engine-escape.lua | 6 ++---- .../docs/smoke-all/2026/03/09/issue-14177.qmd | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 tests/docs/smoke-all/2026/03/09/issue-14177.qmd diff --git a/news/changelog-1.9.md b/news/changelog-1.9.md index ecc77efc65..2ddd131438 100644 --- a/news/changelog-1.9.md +++ b/news/changelog-1.9.md @@ -222,4 +222,5 @@ All changes included in 1.9: - ([#13998](https://github.com/quarto-dev/quarto-cli/issues/13998)): Fix YAML validation error with CR-only line terminators (old Mac format). Documents using `\r` line endings no longer fail with "Expected YAML front matter to contain at least 2 lines". - ([#14012](https://github.com/quarto-dev/quarto-cli/issues/14012)): Add `fr-CA` language translation for Quebec French inclusive writing conventions, using parenthetical forms instead of middle dots for author labels. (author: @tdhock) - ([#14032](https://github.com/quarto-dev/quarto-cli/issues/14032)): Add `editor_options` with `chunk_output_type` to YAML schema for autocompletion and validation in RStudio and Positron. -- ([#14156](https://github.com/quarto-dev/quarto-cli/issues/14156)): Avoid O(n^2) performance in handling large code blocks. \ No newline at end of file +- ([#14156](https://github.com/quarto-dev/quarto-cli/issues/14156)): Avoid O(n^2) performance in handling large code blocks. +- ([#14177](https://github.com/quarto-dev/quarto-cli/issues/14177)): Escape fenced code correctly when backticks occur at the beginning of a line. \ No newline at end of file diff --git a/src/resources/filters/quarto-pre/engine-escape.lua b/src/resources/filters/quarto-pre/engine-escape.lua index 87e4a67329..c3bd9e7f99 100644 --- a/src/resources/filters/quarto-pre/engine-escape.lua +++ b/src/resources/filters/quarto-pre/engine-escape.lua @@ -7,10 +7,8 @@ function engine_escape() -- Line-by-line replacement for the pattern (\n?[^`\n]+`+){({+([^<}]+)}+)} -- which suffers from O(n^2) backtracking on long lines without backticks. -- See https://github.com/quarto-dev/quarto-cli/issues/14156 - -- - -- The original pattern cannot cross newlines (due to [^`\n]+), so processing - -- per-line is semantically equivalent and avoids catastrophic backtracking. - local line_pattern = "([^`\n]+`+)" .. patterns.engine_escape + + local line_pattern = "([^`\n]*`+)" .. patterns.engine_escape local function unescape_inline_engine_codes(text) if not text:find("{{", 1, true) then return text diff --git a/tests/docs/smoke-all/2026/03/09/issue-14177.qmd b/tests/docs/smoke-all/2026/03/09/issue-14177.qmd new file mode 100644 index 0000000000..a26e71913f --- /dev/null +++ b/tests/docs/smoke-all/2026/03/09/issue-14177.qmd @@ -0,0 +1,20 @@ +--- +title: "Issue with fenced inline code at beginning of line" +_quarto: + tests: + html: + ensureFileRegexMatches: + - [] + - ["\\{\\{"] +--- + +For teaching materials explaining how to use insert inline code, I would like to display source code demonstrating how to use inline code in the rendered version of a Quarto document. Here's an example below: + +``` markdown +## Inline code demo + +`{{r}} nrow(penguins)` penguins are included in the amazing `penguins` dataset. +On average these penguins weigh `{{r}} mean(penguins$body_mass, na.rm = TRUE)` grams, although the lightest weighs `{{r}} min(penguins$body_mass, na.rm = TRUE)` g and the heaviest `{{r}} max(penguins$body_mass, na.rm = TRUE)` g. +``` + +When the paragraph above is rendered, the inline code snippets are all correctly rendered as fenced code except the first one that retains its double curly brackets (see screenshot). From a38b543ef175de0bd227adfa52c5556a2f7703a1 Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Mon, 9 Mar 2026 12:52:05 -0500 Subject: [PATCH 2/2] better fix to avoid regression --- src/resources/filters/quarto-pre/engine-escape.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/resources/filters/quarto-pre/engine-escape.lua b/src/resources/filters/quarto-pre/engine-escape.lua index c3bd9e7f99..b147e8f3d9 100644 --- a/src/resources/filters/quarto-pre/engine-escape.lua +++ b/src/resources/filters/quarto-pre/engine-escape.lua @@ -7,8 +7,14 @@ function engine_escape() -- Line-by-line replacement for the pattern (\n?[^`\n]+`+){({+([^<}]+)}+)} -- which suffers from O(n^2) backtracking on long lines without backticks. -- See https://github.com/quarto-dev/quarto-cli/issues/14156 - - local line_pattern = "([^`\n]*`+)" .. patterns.engine_escape + -- Start-of-line fix for https://github.com/quarto-dev/quarto-cli/issues/14177 + -- + -- Two patterns for inline engine escape replacement: + -- 1. Start-of-line: match 1-2 backticks only (inline code, not code fences) + -- 2. Mid-line: match any backticks preceded by non-backtick text + -- This avoids over-processing code fence patterns (```{{engine}}) produced by step 2. + local sol_pattern = "^(``?)" .. patterns.engine_escape + local mid_pattern = "([^`\n]+`+)" .. patterns.engine_escape local function unescape_inline_engine_codes(text) if not text:find("{{", 1, true) then return text @@ -27,7 +33,8 @@ function engine_escape() pos = len + 1 end if line:find("`", 1, true) and line:find("{{", 1, true) then - line = line:gsub(line_pattern, "%1%2") + line = line:gsub(sol_pattern, "%1%2") + line = line:gsub(mid_pattern, "%1%2") end result[#result + 1] = line end