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..b147e8f3d9 100644 --- a/src/resources/filters/quarto-pre/engine-escape.lua +++ b/src/resources/filters/quarto-pre/engine-escape.lua @@ -7,10 +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 + -- Start-of-line fix for https://github.com/quarto-dev/quarto-cli/issues/14177 -- - -- 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 + -- 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 @@ -29,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 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).