Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Features

- feat: Add the `code-window-auto-label` attribute, so another filter can ask for a window with its own label on a block that has no `filename`. The label works even when `auto-filename` is off. (#82)

## 1.7.0 (2026-11-04)

### Breaking Changes
Expand Down
31 changes: 25 additions & 6 deletions _extensions/code-window/_modules/language.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

local cell_output = require(
quarto.utils.resolve_path('_modules/cell-output.lua'):gsub('%.lua$', ''))
local str = require(
quarto.utils.resolve_path('_vendor/quarto-lua-modules/string.lua'):gsub('%.lua$', ''))

local M = {}

Expand All @@ -31,6 +33,27 @@ local function is_known_language(lang)
return is_known
end

--- Read the label that names a block's window, set by this module, by the
--- author, or by another filter. An empty value counts as no label.
--- @param block pandoc.CodeBlock
--- @return string|nil label
function M.auto_label(block)
local label = block.attributes['code-window-auto-label']
if str.is_empty(label) then
return nil
end
return label
end

--- Set the label unless the block already has a filename or a label.
--- @param block pandoc.CodeBlock
--- @param label string
local function set_label(block, label)
if str.is_empty(block.attributes['filename']) and not M.auto_label(block) then
block.attributes['code-window-auto-label'] = label
end
end

--- Normalise code blocks with no or unknown language class to "default".
--- Both are framed like a highlighted code cell rather than left as a bare
--- block: a no-language block is labelled "default", and an unknown language
Expand All @@ -47,18 +70,14 @@ function M.CodeBlock(block)

if not block.classes or #block.classes == 0 then
block.classes:insert('default')
if not block.attributes['filename'] or block.attributes['filename'] == '' then
block.attributes['code-window-auto-label'] = 'default'
end
set_label(block, 'default')
return block
end

local lang = block.classes[1]
if not is_known_language(lang) then
block.classes[1] = 'default'
if not block.attributes['filename'] or block.attributes['filename'] == '' then
block.attributes['code-window-auto-label'] = lang
end
set_label(block, lang)
end

return block
Expand Down
3 changes: 3 additions & 0 deletions _extensions/code-window/_schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ attributes:
type: boolean
default: false
description: "Suppress the auto-generated filename for this specific code block without changing the global setting."
code-window-auto-label:
type: string
description: "Frame this code block with this label, styled as an auto-generated filename, even when auto-filename is false."
code-window-style:
type: string
enum: ["default", "macos", "windows"]
Expand Down
15 changes: 9 additions & 6 deletions _extensions/code-window/code-window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ local meta_mod = require(quarto.utils.resolve_path('_vendor/quarto-lua-modules/m
local pdoc = require(quarto.utils.resolve_path('_vendor/quarto-lua-modules/pandoc-helpers.lua'):gsub('%.lua$', ''))
local html_mod = require(quarto.utils.resolve_path('_vendor/quarto-lua-modules/html.lua'):gsub('%.lua$', ''))
local cell_output = require(quarto.utils.resolve_path('_modules/cell-output.lua'):gsub('%.lua$', ''))
local language = require(quarto.utils.resolve_path('_modules/language.lua'):gsub('%.lua$', ''))
local code_annotations = nil
local checker = nil

Expand Down Expand Up @@ -146,7 +147,7 @@ end
--- @param block pandoc.CodeBlock Code block element
--- @return string|nil auto_label Label written by the language module, if any
local function take_block_attributes(block)
local auto_label = block.attributes['code-window-auto-label']
local auto_label = language.auto_label(block)
local owned = {}
for key, _ in pairs(block.attributes) do
if key:sub(1, #ATTRIBUTE_PREFIX) == ATTRIBUTE_PREFIX and not cell_output.is_marker(key) then
Expand Down Expand Up @@ -706,7 +707,7 @@ local function process_html(block, resolved, auto_label)
return block
end

if not CONFIG.auto_filename or overrides.no_auto_filename then
if overrides.no_auto_filename or not (auto_label or CONFIG.auto_filename) then
return block
end

Expand Down Expand Up @@ -1036,10 +1037,12 @@ local function resolve_window_params(block, resolved, auto_label)
local is_auto = false

if (not filename or filename == '') and not overrides.no_auto_filename then
if CONFIG.auto_filename and block.classes and #block.classes > 0 then
-- Default/unknown/no-language blocks carry their label on
-- code-window-auto-label; everything else uses its language class.
filename = auto_label or block.classes[1]
-- A label asks for a window whatever auto-filename says.
if auto_label then
filename = auto_label
is_auto = true
elseif CONFIG.auto_filename and block.classes and #block.classes > 0 then
filename = block.classes[1]
is_auto = true
end
end
Expand Down
17 changes: 16 additions & 1 deletion _extensions/code-window/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,27 @@ end
--- rewritten with no reader either. Answering that per block means relabelling
--- where the name is built, which is a change to the two window paths rather
--- than to this gate.
--- A block that carries "code-window-auto-label" asks for a window by itself,
--- so it takes the pass whatever "auto-filename" says. Without it, a block in
--- a language Pandoc cannot highlight would get no wrapper to frame. Such a
--- block that also refuses the window has no reader for the label, so it is
--- left alone here.
--- @param block pandoc.CodeBlock
--- @return pandoc.CodeBlock|nil Relabelled block, or nil when the pass is skipped
local function normalise_language(block)
if not code_window.draws_chrome() or not code_window.CONFIG().auto_filename then
if not code_window.draws_chrome() then
return nil
end
if not code_window.CONFIG().auto_filename then
-- Lowercased to match the schema's boolean coercion. Resolving through the
-- schema check here would report each of its warnings twice per block.
local enabled = (block.attributes['code-window-enabled'] or ''):lower()
local no_auto_filename = (block.attributes['code-window-no-auto-filename'] or ''):lower()
local refuses_window = enabled == 'false' or no_auto_filename == 'true'
if not language.auto_label(block) or refuses_window then
return nil
end
end
return language.CodeBlock(block)
end

Expand Down
18 changes: 18 additions & 0 deletions docs/reference.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ extensions:
| --- | --- | --- |
| `code-window-enabled` | boolean | Whether this block gets chrome. Annotations still render when it does not. |
| `code-window-no-auto-filename` | boolean | Suppress the derived filename for this block alone. |
| `code-window-auto-label` | string | Frame this block with this label, styled as a derived filename. Works when `auto-filename` is `false`. |
| `code-window-style` | string | Override the style for this block: `macos`, `windows`, `default`. |
| `code-window-collapse` | boolean or string | Fold this block: `open`, `closed`, `true`, or `false`. HTML only. |
| `code-window-lines` | string | The line spec shown in the chip, such as `1,3-5`. Defaults to Quarto's `code-line-numbers`. |
Expand All @@ -62,6 +63,23 @@ The result is the same window; it needs JavaScript, as the rest of the HTML chro
Typst frames derived and author-set filenames alike, without a script.
:::

### Labels from another filter

A filter that runs before Code Window can ask for a window on a block that has no `filename`.
Set `code-window-auto-label` on the block to the label that the title bar must show.

```lua
block.attributes['code-window-auto-label'] = 'Terminal'
```

The label shows in the same style as a derived filename.
The block gets the window also when the document sets `auto-filename` to `false`.
These attributes win over the label:

- A `filename` attribute names the window instead of the label.
- `code-window-enabled="false"` removes the window.
- `code-window-no-auto-filename="true"` removes the window.

## Executed cells

Quarto writes the output of an executed cell as a code block with no language.
Expand Down
50 changes: 50 additions & 0 deletions tests/fixtures/auto-label-forced.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: "A label that asks for its own window"
filters:
- code-window
format:
html: default
typst:
keep-typ: true
extensions:
code-window:
auto-filename: false
---

The document derives no names, yet the first block names its own label, so it
gets a window with that label. Each block after it writes something that wins
over the label, so none of them gets the label in its title bar.

```{.bash code-window-auto-label="Terminal"}
echo forced
```

```{.bash filename="run.sh" code-window-auto-label="Terminal"}
echo explicit
```

```{.bash code-window-enabled="false" code-window-auto-label="Terminal"}
echo disabled
```

```{.bash code-window-no-auto-filename="true" code-window-auto-label="Terminal"}
echo refused
```

A labelled block in a language Pandoc cannot highlight still goes through the
language pass, so it is framed like any other labelled block.

```{.foo code-window-auto-label="Shell"}
x = 1
```

A labelled block that turns the window off keeps the language its author wrote,
because no window reads the label.

```{.foo code-window-no-auto-filename="true" code-window-auto-label="Terminal"}
x = 2
```

```{.foo code-window-enabled="False" code-window-auto-label="Terminal"}
x = 3
```
49 changes: 49 additions & 0 deletions tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,55 @@ else
"pdftotext on PATH (brew install poppler)"
fi

# ============================================================================
# A label set on a block asks for a window of its own
# ============================================================================

# Another filter sets code-window-auto-label to get a window with a derived
# style on a block that names no file. The label has to work when the document
# derives no names, and an explicit filename or a per-block opt-out still wins.
render auto-label-forced html
forced_html="${work_dir}/auto-label-forced.html"
forced_labels="$(block_wrappers "${forced_html}" | grep -c 'data-filename="Terminal"' || true)"
forced_auto="$(block_classes "${forced_html}" | grep -c 'cw-auto' || true)"
if [ "${forced_labels}" -eq 1 ] && [ "${forced_auto}" -eq 2 ] &&
grep -q 'data-filename="run.sh"' "${forced_html}"; then
report pass "auto-label-forced: only the labelled block takes the label in HTML"
else
report fail "auto-label-forced: only the labelled block takes the label in HTML" \
"one data-filename=\"Terminal\", two cw-auto blocks, and data-filename=\"run.sh\", got ${forced_labels} and ${forced_auto}"
fi

render auto-label-forced typst
forced_typst="${work_dir}/auto-label-forced.typ"
if [ "$(count_typst_windows "${forced_typst}")" -eq 3 ] &&
grep -q '^#code-window(filename: "Terminal", is-auto: true' "${forced_typst}" &&
grep -q '^#code-window(filename: "run.sh", is-auto: false' "${forced_typst}"; then
report pass "auto-label-forced: only the labelled block takes the label in Typst"
else
report fail "auto-label-forced: only the labelled block takes the label in Typst" \
"three code-window calls, with one for Terminal with is-auto: true and one for run.sh"
fi

# The language pass runs for a labelled block and keeps its label, or the
# label would read "foo".
if block_wrappers "${forced_html}" | grep -q 'data-filename="Shell"'; then
report pass "auto-label-forced: a labelled block in an unknown language is framed"
else
report fail "auto-label-forced: a labelled block in an unknown language is framed" \
"data-filename=\"Shell\" on a sourceCode wrapper in auto-label-forced.html"
fi

# A labelled block that refuses the window has no reader for the label, so the
# language pass leaves its language alone.
opted_out="$(block_classes "${forced_html}" | grep -c '"foo' || true)"
if [ "${opted_out}" -eq 2 ]; then
report pass "auto-label-forced: a labelled block that refuses the window keeps its language"
else
report fail "auto-label-forced: a labelled block that refuses the window keeps its language" \
"two blocks with a foo class in auto-label-forced.html, got ${opted_out}"
fi

# ============================================================================

printf '\n%s passed, %s failed\n' "${passed}" "${failed}"
Expand Down