From bc0cf180b112f637e7dffdb6879f0fc86035356c Mon Sep 17 00:00:00 2001 From: t Date: Thu, 12 Nov 2026 13:21:40 -0600 Subject: [PATCH 1/5] perf-harness: add ast-memory census tool and AST string-storage study (bd-akgst1fi) ast-memory parses a directory of .md/.qmd with pampa's reader, retains every AST, and reports allocator traffic, a census of text-carrying fields, Str shape, and a text-run what-if. The study doc records the measurements on claude-notes/plans (AST heap = 88x source, 89% of it Vec buffers) and the recommendation to fix node layout rather than add a lifetime to the AST. Co-Authored-By: Claude Fable 5.2 --- Cargo.lock | 2 + .../2026-11-12-ast-string-storage-study.md | 454 ++++++++++ crates/perf-harness/Cargo.toml | 6 + crates/perf-harness/src/bin/ast_memory.rs | 848 ++++++++++++++++++ 4 files changed, 1310 insertions(+) create mode 100644 claude-notes/plans/2026-11-12-ast-string-storage-study.md create mode 100644 crates/perf-harness/src/bin/ast_memory.rs diff --git a/Cargo.lock b/Cargo.lock index 56662b2310..da83b2d380 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6327,6 +6327,8 @@ dependencies = [ "pampa", "pollster", "quarto-core", + "quarto-pandoc-types", + "quarto-source-map", "quarto-system-runtime", ] diff --git a/claude-notes/plans/2026-11-12-ast-string-storage-study.md b/claude-notes/plans/2026-11-12-ast-string-storage-study.md new file mode 100644 index 0000000000..677569640a --- /dev/null +++ b/claude-notes/plans/2026-11-12-ast-string-storage-study.md @@ -0,0 +1,454 @@ +--- +title: "Study: arena / borrowed string storage for the Pandoc AST" +strand: bd-akgst1fi +related: [bd-is4q72tt] +status: "Assessment written and discussed (2026-11-12); direction agreed: no lifetimes, node-layout work first, text runs deferred. No implementation yet." +date: 2026-11-12 +--- + +# Arena / borrowed string storage for the Pandoc AST: feasibility study + +## Question + +Pampa stores every `Str` (and all other AST text) as an owned `String`. Could +an arena or borrowing discipline — for example `&str` slices into `.qmd` +buffers that are kept alive for the life of the project — significantly cut +allocation count and memory use, and is the lifetime complexity worth it? + +## Short answer + +1. **Borrowing source slices is feasible only at a very high cost, and it + targets the wrong 2%.** On the benchmark corpus, `Str.text` accounts for + 70% of the AST's heap *blocks* but only 1.9% of its heap *bytes*. +2. **The memory problem is node layout.** The retained AST is **88× the size + of the source**. 89% of that is `Vec` buffers, because `Inline` is + 360 bytes and a `Space` — 44% of all inlines — occupies a full 360-byte + slot. +3. **Lifetime-free changes capture nearly all of the available win.** Boxing + the cold enum variants, slimming `Attr`, a small-string type, and a + compact `SourceInfo` should bring the AST from about 88× to about 12× of + source and remove about 75% of heap blocks (arithmetic estimates, not yet + prototyped). Borrowing would add roughly 4% on top of that. +4. **Recommendation:** do not introduce `Inline<'a>`. Do the layout work + in the order given under "Recommended path". + +## Measurements + +Tool: `crates/perf-harness/src/bin/ast_memory.rs` (new, uncommitted). It +parses every `*.md` / `*.qmd` under a directory with +`pampa::readers::qmd::read`, retains every `(Pandoc, ASTContext)`, and counts +through a wrapping `GlobalAlloc`. + +```bash +cargo build --release -p perf-harness --bin ast-memory +./target/release/ast-memory claude-notes/plans +``` + +Corpus: `claude-notes/plans`, 1,695 files, 26.30 MiB. 1,673 files parsed +(26.27 MiB); 22 small files (0.03 MiB) failed to parse and are excluded. +Platform: macOS arm64, system allocator, release build. + +### Totals + +| | Value | +|---|---| +| Source parsed | 26.27 MiB | +| Retained AST heap (freed by dropping all `Pandoc`s) | 2,304 MiB in 4.06 M blocks | +| AST heap / source bytes | **87.7×** | +| Live heap blocks per KiB of source | 151 | +| Retained `ASTContext`s (a source copy plus line index each) | 33 MiB | +| Peak RSS of the run | 2.75 GiB | +| Allocator calls during parsing | 27.0 M allocs + 3.7 M reallocs, 22.9 M frees | +| Wall time | 7.7 s (about 3.4 MiB/s) | + +Parsing makes 6.6 allocations for every block it retains. + +### Where the bytes are + +| Component | MiB | Share of AST heap | +|---|---|---| +| `Vec` buffers (5,995,451 inlines × 360 B) | 2,058 | 89.3% | +| `Vec` buffers (202 K blocks × 824 B) | 160 | 6.9% | +| All text fields, malloc-rounded | 57 | 2.5% | +| — of which `Str.text` | 44 | 1.9% | +| Everything else (`Concat` source infos, `Arc`s, nested vecs, metadata) | 29 | 1.3% | + +Type sizes that drive this: + +| Type | Bytes | Note | +|---|---|---| +| `Inline` | 360 | sized by `Link` (352) | +| `Block` | 824 | sized by `Table` (824) | +| `Str` | 56 | `String` 24 + `SourceInfo` 32 | +| `Space` | 32 | just a `SourceInfo` | +| `Code` | 240 | `Attr` 104 + `AttrSourceInfo` 80 + text + source info | +| `Attr` | 104 | | +| `SourceInfo` | 32 | three `usize` plus tag | + +Inline census: 2.84 M `Str`, 2.64 M `Space`, 277 K `Code`, 158 K `SoftBreak`, +59 K `Strong`; everything else is under 13 K each. `Link` and `Image` +together are 1,752 nodes — 0.03% of inlines set the size of the other 99.97%. + +Of 365,800 `Attr` sites, 28,735 (7.9%) are non-empty. The other 92% each +carry 184 bytes of empty `Attr` + `AttrSourceInfo`. + +### Where the heap blocks are + +| Field | Count | Heap blocks | Content bytes | Malloc bytes | +|---|---|---|---|---| +| `Str.text` | 2,839,066 | 2,839,066 | 14.5 M | 46.2 M | +| `Code.text` | 276,738 | 276,718 | 4.9 M | 7.0 M | +| `attr.id` | 365,800 | 23,569 | 0.6 M | 0.8 M | +| `meta.key` | 7,017 | 7,017 | 0.05 M | 0.1 M | +| `CodeBlock.text` | 6,550 | 6,547 | 3.2 M | 5.0 M | +| other | 9,075 | 7,321 | 0.1 M | 0.2 M | +| **All text** | 3,504,246 | 3,160,238 | 23.4 M | 59.3 M | + +Text is 78% of the AST's heap blocks. `Str.text` alone is 70%. Each `Str` +string averages 5.1 bytes of content in a 16-byte minimum malloc block. + +### `Str` shape + +| Length | Share | +|---|---| +| ≤ 7 bytes | 80.1% | +| 8–15 | 19.1% | +| 16–23 | 0.7% | +| ≥ 24 | 0.1% | + +- **Borrowable:** 89.7% of `Str` texts are byte-identical to the source range + their `source_info` resolves to. 1.7% differ (smart typography, escapes, + entities). 8.6% have a `Concat` source info that does not resolve to one + range; these are tokens glued by `merge_strs`, such as `(word)`. +- **Repetition:** 130 K distinct texts among 2.39 M `Str` nodes in ordinary + paragraphs, headers and list items (1.27 MB of distinct content). + +### wasm32 type sizes + +The hub client is wasm32, where `usize` and pointers are 4 bytes. Sizes +there (from a `cargo check --target wasm32-unknown-unknown` probe): + +| Type | 64-bit | wasm32 | +|---|---|---| +| `Inline` | 360 | 192 | +| `Block` | 824 | 432 | +| `Str` | 56 | 28 | +| `SourceInfo` | 32 | 16 | +| `Attr` | 104 | 56 | +| `AttrSourceInfo` | 80 | 40 | +| `Code` | 240 | 128 | + +Two consequences: + +- The same structural waste exists in WASM at about half the absolute size: + roughly 47× source instead of 88× (scaled from the native census, not + measured in WASM). Boxing cold variants takes `Inline` from 192 to 32 + bytes there, a 6× cut. +- `SourceInfo` is already 16 bytes on wasm32, so `u32` offsets (step D) do + nothing for the hub. They help native only. + +### Text-run what-if + +Collapsing each maximal run of adjacent `Str`/`Space` inside one +`Vec` into a single node: + +| | Inlines | Runs | Mean run text | +|---|---|---|---| +| Today | 5,995,451 | — | — | +| `Str` + `Space` runs | 1,165,598 (5.1× fewer) | 647,608 | 26.5 bytes | +| also absorbing `SoftBreak` | 895,023 (6.7× fewer) | 535,198 | — | + +Run text totals 17.2 MB. A run of that mean length no longer fits a small +string inline, so each run is one heap block (about 650 K blocks). + +### Caveats + +- This measures the reader's output only. Transforms, Lua filters, the + several whole-AST clones in the pipeline, and writers are not included. +- `q2` uses mimalloc, whose size classes differ from macOS malloc. The + struct-size results are allocator-independent; the per-string rounding + figures are not. +- A project render does not retain every AST at once. The per-document ratio + is what matters there: a 1 MB `.qmd` implies roughly 90 MB of AST per live + copy. This is consistent with, and probably a large part of, bd-is4q72tt + (1.1 MB `api/index.qmd` peaking at 1.1 GB RSS). +- The corpus is prose-heavy Markdown with much inline code. Notebook-style + documents will have a larger `CodeBlock` share. + +## Why borrowing from the source buffer is expensive here + +The codebase survey found these constraints. Each one needs an answer before +`Inline<'a>` could exist. + +### The reader does not produce slices + +- **Per-token rewriting.** The main `Str` path is + `apply_smart_typography(process_backslash_escapes(raw.to_string()))` + (`crates/pampa/src/pandoc/treesitter.rs:902-913`). Entities, numeric + references, task-list boxes and cite suffixes are synthesized. +- **Merging.** `merge_strs` (`postprocess.rs:2012-2067`) concatenates + adjacent tokens into a new `String`; `coalesce_abbreviations` inserts + U+00A0. +- **Metadata.** Every untagged YAML string is re-parsed as Markdown from the + *decoded YAML string*, not from the `.qmd` buffer (`meta.rs:144-176`). +- **Code and math.** `CodeBlock.text` is verbatim only at top level with an + unindented fence; block-quote and list gutters are stripped. `Code.text` + is trimmed and has soft breaks collapsed. + +So an owned or arena path is required for at least 10% of `Str`s and for +most other text. The type would be `Cow<'a, str>` or an arena string, not +`&'a str`. + +### Consumers need `'static` or cross-buffer data + +- **Lua filters.** Nodes live inside mlua userdata as + `Rc>` (`crates/pampa/src/lua/types.rs:196,1116`). + `create_userdata` requires `T: 'static`, and `lua.scope` is not used. Every + crossing already deep-clones the node. +- **Cross-document sharing.** `Arc` holds `DocumentProfile`s + with `Option` and `ConfigValue` fields, read by other documents' + renders on other rayon workers (`project/pass2_renderer.rs:542-571`, + `document_profile.rs:706-717`). Profiles are also deserialized from the + on-disk profile cache, where no source buffer exists. +- **Trees from several buffers.** `reconcile(original, executed)` moves + nodes from two parses of two different buffers into one tree + (`quarto-ast-reconcile/src/apply.rs:24-63`). Includes splice blocks parsed + from other files; book rendering merges chapter ASTs. +- **Trees from no buffer.** The Pandoc JSON reader, JSON filters, + `apply_node_edit`, citeproc output and about 800 synthesized-`Str` sites in + transforms produce text with no source. +- **Async pipeline.** `PipelineData` and `StageContext` are deliberately + lifetime-free so stages can be boxed trait objects + (`claude-notes/plans/2026-01-06-pipeline-stage-design.md:310`). + +### The never-freed pool does not fit the long-running hosts + +- The hub client re-parses the document on every keystroke, in WASM, where + linear memory never shrinks. +- `q2 preview` re-renders on every file change; the LSP re-parses on each + analysis. + +A pool that is never freed is correct only for one-shot `q2 render`. The +other three hosts would need generation-scoped arenas, which brings back the +lifetime (or a refcount). + +### Scale of the change + +- 16 crates depend on `quarto-pandoc-types`. +- About 10,900 `Inline::` / `Block::` sites, 1,264 `Inline::Str(` sites, + 91 `AstTransform` impls, and about 1,900 function-signature lines that + mention `Pandoc`, `Block` or `Inline`. +- A previous, smaller attempt at the same trade — borrowed YAML — was + designed and then abandoned for owned data + (`claude-notes/yaml-with-source-info-lifetime-approach.md`). + +### What it would buy + +After the layout fixes below, borrowing saves the residual text heap: about +12 MiB of about 315 MiB on this corpus, roughly 4%. Against a 16-byte +inline small string, a 16-byte `&str` saves no node space at all. + +## Precedent + +No project found combines an AST borrowed from the source with a +mutation-heavy filter pipeline. (Checked against current docs and source on +2026-11-12; URLs are under "Sources" at the end.) + +| Project | Text storage | Relevance | +|---|---|---| +| pulldown-cmark | `CowStr<'a>`: borrowed, boxed, or inline | Event stream, not a tree; filters emit owned strings | +| jotdown | `Cow<'s, str>` events | Same model | +| comrak | Nodes in a `typed_arena`; text is `Cow<'static, str>`, never borrowed from the source | Closest analogue: arena for structure, owned text, interior mutability. 0.45 reported 1.32× from removing clones | +| markdown-rs | Owned `String` mdast | No lifetimes | +| Typst | `EcoString` leaves (15 bytes inline), `Arc` inner nodes | Lifetime-free, small strings inline | +| rowan / rust-analyzer | Immutable green tree, tokens interned per file behind `Arc` | Lifetime-free; mutation rebuilds spines | +| oxc | Whole AST in a bump arena with `'a` | Reported about 20% from the arena, 10% from boxing enum variants to 16 bytes, up to 5% from `u32` spans. Plain source slices failed for escaped text. Took "five attempts" | +| swc | Owned AST, interned 8-byte `Atom` | Lifetime-free | +| Ruff | `Name(CompactString)`, later a 16-byte inline type | +4–8% from the small-string swap alone; `smol_str` regressed | +| serde | `&'de str` zero-copy | Documents that escaped strings cannot be borrowed | + +Three patterns recur: + +- **Escapes defeat pure borrowing.** oxc and serde both say so explicitly. + Markdown has the same problem. +- **Arena wins come from node structure**, not from borrowed text: fewer + allocations, no `Drop`, smaller enums. +- **Mutating pipelines use lifetime-free cheap text**: inline small strings, + interning, or refcounted buffers. + +## Options + +| Option | AST bytes (est.) | Heap blocks (est.) | Lifetimes | Churn | +|---|---|---|---|---| +| Today | 2,304 MiB (88×) | 4.06 M | none | — | +| A. Box cold variants | ~535 MiB (20×) | ~4.35 M | none | Pattern matches on boxed variants | +| B. A + slim `Attr` | ~490 MiB (19×) | ~4.1 M | none | `Attr` accessors | +| C. B + 24-byte small string | ~440 MiB (17×) | ~1.0 M | none | `String` → new type at text fields | +| D. C + 16-byte `SourceInfo` and 16-byte string | ~315 MiB (12×) | ~1.0 M | none | quarto-source-map API change | +| E. D + borrowed / arena text | ~300 MiB (11×) | ~0.7 M | `'a` on every AST type | Whole workspace | +| F. D + text runs instead of `Str`/`Space` | ~140 MiB (5×) | ~1.0 M | none | Changes the Pandoc data model | + +All estimates are arithmetic on the measured census, not prototypes. + +### A. Box the cold variants + +Make `Inline` 64 bytes and `Block` about 64 bytes by boxing the payloads of +`Link`, `Image`, `Code`, `Cite`, `Custom`, `Shortcode`, the editorial marks +and `Attr` for inlines, and `Table`, `Figure`, `Header`, `CodeBlock`, `Div` +and `Custom` for blocks. `Vec` falls from 2,058 to 366 MiB. Boxed +payloads add back about 72 MiB and about 290 K blocks. + +### B. Slim `Attr` + +Store `Attr` + `AttrSourceInfo` behind one `Option>`, or as a +thin-vec-style handle. 92% of sites are empty and would cost 8 bytes instead +of 184. `Code` drops from 240 to about 64 bytes and may not need boxing. + +### C. Small-string type for text + +Replace `String` in text fields with a type that stores short strings +inline. 99.9% of `Str` texts fit in 23 bytes and 99.2% in 15. This removes +about 2.84 M `Str` blocks and most `Code.text` blocks, with no lifetime. + +Candidates: + +| Crate | Size | Inline | Clone | Note | +|---|---|---|---|---| +| `compact_str` | 24 | 24 | O(n) | Drop-in for `String`; mutable; Ruff's choice | +| `smol_str` | 24 | 23 | O(1) | Immutable; regressed in Ruff | +| `ecow` | 16 | 15 | O(1) | Typst's; already in the lockfile | +| `hipstr` | 24 | 23 | O(1) | O(1) substrings of a shared buffer | + +`hipstr` (or `arcstr::Substr`) is the lifetime-free version of the original +idea: long text such as `CodeBlock.text` can be a refcounted slice of the +source buffer, and the buffer is freed when the last slice goes. It suits +preview and the hub, where a leaked pool does not. + +### D. Compact `SourceInfo` + +See "quarto-source-map changes". With a 16-byte `SourceInfo` and a 16-byte +string, `Str` is 32 bytes and `Inline` 40. + +### E. Borrowed or arena text + +Feasible only by threading `'a` through 16 crates, copying to owned at the +Lua, profile-cache and reconcile boundaries, and scoping arenas per parse +generation in the hub. Not recommended. + +A cheaper variant is `&'static str` from deliberately leaked buffers +(`String::leak`). It needs no lifetime parameter, but it is correct only in +one-shot `q2 render`, and the type must still have an owned arm. + +### F. Text runs + +Represent a run of words and spaces as one inline, splitting into +`Str`/`Space` lazily where Pandoc semantics are needed (Lua, JSON writer). +Measured: inlines fall 5.1× (6.7× if soft breaks are absorbed). On top of +D that is about 315 → 140 MiB on this corpus. Applied to today's layout +alone it would be about 2,300 → 650 MiB, so it is worth less than step A +and stacks with it. + +Costs beyond the data-model change: per-word source locations become +derived (offset arithmetic, exact only when the run is a verbatim source +slice), and the heap-block count does not fall further, because each run +needs a heap string. + +**Decision (2026-11-12): deferred until the node-layout work is done.** It +would need a feature flag, and a reader-level flag cannot live in document +metadata, which is only available after parsing. Where such a flag would +live (project config, CLI, or a lazily split representation that needs no +flag) is an open design question for then. + +## quarto-source-map changes + +These are independent of the string question and useful on their own. + +1. **Compact offsets.** `Original { file_id: u32, start: u32, end: u32 }` + gives a 16-byte `SourceInfo`. Files over 4 GiB are out of scope. Every + AST node carries at least one `SourceInfo`, so this is worth 16 bytes × 6 + M nodes on this corpus. +2. **Hull instead of `Concat` when pieces are adjacent.** `combine()` + (`source_info.rs:370`) always builds a two-piece `Concat`, and + `merge_strs` chains it, so N tokens give N−1 nested `Concat`s. 236,635 + `Str` nodes (8.3%) carry one. Each costs at least one 96-byte heap block + — more than the string it describes — and makes `resolve_byte_range` + return `None`. When both sides are `Original` in the same file and + byte-adjacent, return one `Original`. `contiguous_hull_for_run` in + `postprocess.rs` already does this for abbreviations. +3. **Shared source text.** `SourceFile.content: Option` could be + `Option>`. Today a parsed document holds two full copies of its + source (`DocumentAst.ast_context.source_context` and + `DocumentAst.source_context`), and `SourceContext: Clone` deep-copies + them again. +4. **Text lookup.** A `SourceContext::text_of(&SourceInfo) -> Option<&str>` + helper would let consumers that want the raw slice get it without the + AST storing it. + +## Other findings + +- **Transient churn in the reader.** Each word token costs four allocations + to keep one: `to_string()`, the output of `process_backslash_escapes`, and + a `Vec` plus output in `apply_smart_typography` + (`text_helpers.rs:259-359`). A fast path that returns the input unchanged + when it contains no `\`, `'`, `-` or `.` would remove most of the 22.9 M + frees. +- **Input copies.** The pipeline copies the input three to four times + before parsing (`pipeline.rs:1180`, `stage/data.rs:345`, + `readers/qmd.rs:147-190`). +- **Metadata re-parse.** Each metadata string re-parse copies the string, + usually twice, and builds a line index that is then discarded. +- **Lua boundary.** Every node crossing deep-clones in both directions. + Smaller nodes make this proportionally cheaper. + +## Recommended path + +Priority, as agreed on 2026-11-12: hub/WASM memory first, then peak RSS of +`q2 render`, then wall time; the three are roughly one class. Since `u32` +offsets do nothing on wasm32, D moves behind the small-string step. A +breaking `quarto-source-map` release is acceptable. + +Each step is separately shippable and measurable with `ast-memory`. + +1. **Prototype A on `Inline` only** and re-measure. This is the test of the + central estimate; if `Vec` does not fall to about 370 MiB, stop + and re-examine. +2. **A for `Block`, then B.** +3. **Reader fast paths** (typography/escape no-op path, `Concat` hull). +4. **C**, choosing the string type by benchmark on this corpus and on a + Lua-filter-heavy render. +5. **D**, as a quarto-source-map minor release. +6. Re-measure bd-is4q72tt's 1.1 MB document end to end. Decide then whether + F is worth a design. + +## Decisions (2026-11-12) + +- **Goal order:** hub/WASM memory > `q2 render` peak RSS > wall time. +- **quarto-source-map:** a breaking release with `u32` offsets is acceptable. +- **`ast_memory.rs`:** committed to `perf-harness` as the regression tool. +- **Text runs (F):** deferred to after the layout work; needs a flag design. + +## Open questions + +- The harness measures a native build. A wasm32 measurement (or at least a + size-of table checked in CI) is needed to track the primary goal. +- Which small-string type: `compact_str` is pointer-sized ×3 (12 bytes on + wasm32), `ecow` is 16 bytes on both targets. + +## Sources + +- pulldown-cmark `CowStr`: https://docs.rs/pulldown-cmark/latest/pulldown_cmark/enum.CowStr.html +- comrak `NodeValue` and changelog (0.45.0): https://docs.rs/comrak/latest/comrak/nodes/enum.NodeValue.html, https://github.com/kivikakk/comrak/blob/main/CHANGELOG.md +- jotdown `Event`: https://docs.rs/jotdown/latest/jotdown/enum.Event.html +- markdown-rs mdast `Text`: https://docs.rs/markdown/latest/markdown/mdast/struct.Text.html +- Typst `SyntaxNode`: https://github.com/typst/typst/blob/main/crates/typst-syntax/src/node.rs +- rust-analyzer syntax design: https://github.com/rust-lang/rust-analyzer/blob/master/docs/book/src/contributing/syntax.md +- oxc performance notes and AST docs: https://oxc.rs/docs/learn/performance.html, https://oxc.rs/docs/contribute/parser/ast +- swc string management, `hstr`: https://swc.rs/docs/contributing/commons/string-management, https://docs.rs/hstr +- Ruff `CompactString` names (PR 12101): https://github.com/astral-sh/ruff/pull/12101 +- serde lifetimes: https://serde.rs/lifetimes.html +- Small-string comparison: https://github.com/rosetta-rs/string-rosetta-rs +- `arcstr`, `hipstr`, `self_cell`: https://docs.rs/arcstr, https://docs.rs/hipstr, https://docs.rs/self_cell +- Adrian Sampson, "Flattening ASTs": https://www.cs.cornell.edu/~asampson/blog/flattening.html + +Not verified: the exact inline capacity of pulldown-cmark's `InlineStr`, and +whether oxc's `Atom<'a>` still exists under that name. diff --git a/crates/perf-harness/Cargo.toml b/crates/perf-harness/Cargo.toml index 8551abab95..af31304f83 100644 --- a/crates/perf-harness/Cargo.toml +++ b/crates/perf-harness/Cargo.toml @@ -17,8 +17,14 @@ path = "src/bin/parse_corpus.rs" name = "vfs-flush" path = "src/bin/vfs_flush.rs" +[[bin]] +name = "ast-memory" +path = "src/bin/ast_memory.rs" + [dependencies] pampa = { path = "../pampa" } +quarto-pandoc-types = { workspace = true } +quarto-source-map = { workspace = true } quarto-core = { path = "../quarto-core" } quarto-system-runtime = { path = "../quarto-system-runtime" } pollster = { workspace = true } diff --git a/crates/perf-harness/src/bin/ast_memory.rs b/crates/perf-harness/src/bin/ast_memory.rs new file mode 100644 index 0000000000..d803b0b84e --- /dev/null +++ b/crates/perf-harness/src/bin/ast_memory.rs @@ -0,0 +1,848 @@ +//! Study driver for bd-akgst1fi: how much heap does the Pandoc AST cost +//! relative to the source text it was parsed from, and how much of that is +//! small owned `String`s? +//! +//! It parses every `*.md` / `*.qmd` under a directory (recursively) with +//! pampa's qmd reader, RETAINS every `(Pandoc, ASTContext)`, and reports: +//! +//! - allocator traffic per phase (a counting `GlobalAlloc` wrapper), +//! - a census of every text-carrying field in the retained ASTs, +//! - how many `Str` texts are byte-identical to the source range their +//! `source_info` points at (i.e. could be a borrowed slice), +//! - what is freed when the contexts, then the ASTs, are dropped. +//! +//! Usage: +//! ast-memory [--keep-going] +//! +//! See claude-notes/plans/2026-11-12-ast-string-storage-study.md. + +use std::alloc::{GlobalAlloc, Layout, System}; +use std::collections::{BTreeMap, HashSet}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + +use quarto_pandoc_types::attr::Attr; +use quarto_pandoc_types::block::Block; +use quarto_pandoc_types::config_value::{ConfigValue, ConfigValueKind}; +use quarto_pandoc_types::custom::{CustomNode, Slot}; +use quarto_pandoc_types::inline::{Citation, Inline}; +use quarto_pandoc_types::table::{Cell, Row, Table}; +use quarto_source_map::SourceInfo; + +// ---------------------------------------------------------------- allocator + +struct Counting; + +static ALLOCS: AtomicUsize = AtomicUsize::new(0); +static FREES: AtomicUsize = AtomicUsize::new(0); +static REALLOCS: AtomicUsize = AtomicUsize::new(0); +static LIVE: AtomicUsize = AtomicUsize::new(0); +static PEAK: AtomicUsize = AtomicUsize::new(0); + +fn bump_live(delta: usize) { + let now = LIVE.fetch_add(delta, Relaxed) + delta; + PEAK.fetch_max(now, Relaxed); +} + +// SAFETY: defers to `System` for every operation; only adds counters. +unsafe impl GlobalAlloc for Counting { + unsafe fn alloc(&self, l: Layout) -> *mut u8 { + ALLOCS.fetch_add(1, Relaxed); + bump_live(l.size()); + unsafe { System.alloc(l) } + } + unsafe fn dealloc(&self, p: *mut u8, l: Layout) { + FREES.fetch_add(1, Relaxed); + LIVE.fetch_sub(l.size(), Relaxed); + unsafe { System.dealloc(p, l) } + } + unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 { + REALLOCS.fetch_add(1, Relaxed); + if new >= l.size() { + bump_live(new - l.size()); + } else { + LIVE.fetch_sub(l.size() - new, Relaxed); + } + unsafe { System.realloc(p, l, new) } + } +} + +#[global_allocator] +static GLOBAL: Counting = Counting; + +#[derive(Clone, Copy)] +struct Snap { + allocs: usize, + frees: usize, + reallocs: usize, + live: usize, +} + +fn snap() -> Snap { + Snap { + allocs: ALLOCS.load(Relaxed), + frees: FREES.load(Relaxed), + reallocs: REALLOCS.load(Relaxed), + live: LIVE.load(Relaxed), + } +} + +fn rss_kb() -> usize { + std::process::Command::new("ps") + .args(["-o", "rss=", "-p", &std::process::id().to_string()]) + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .and_then(|s| s.trim().parse().ok()) + .unwrap_or(0) +} + +fn mb(b: usize) -> f64 { + b as f64 / (1024.0 * 1024.0) +} + +fn phase(name: &str, a: Snap, b: Snap) { + println!( + "{name:<34} allocs={:>10} reallocs={:>9} frees={:>10} live_blocks={:>+10} live_bytes={:>+9.1} MiB rss={:.1} MiB", + b.allocs - a.allocs, + b.reallocs - a.reallocs, + b.frees - a.frees, + (b.allocs - a.allocs) as isize - (b.frees - a.frees) as isize, + (b.live as f64 - a.live as f64) / (1024.0 * 1024.0), + rss_kb() as f64 / 1024.0, + ); +} + +// The real heap footprint of a block, including size-class rounding. +#[cfg(target_os = "macos")] +fn heap_size(p: *const u8) -> usize { + unsafe extern "C" { + fn malloc_size(p: *const std::ffi::c_void) -> usize; + } + // SAFETY: `p` is the start of a live allocation made by the system + // allocator (callers only pass pointers of non-zero-capacity buffers). + unsafe { malloc_size(p.cast()) } +} +#[cfg(not(target_os = "macos"))] +fn heap_size(_p: *const u8) -> usize { + 0 +} + +// ------------------------------------------------------------------ census + +#[derive(Default)] +struct Bucket { + count: usize, + heap_blocks: usize, + len: usize, + cap: usize, + heap: usize, +} + +#[derive(Default)] +struct Census { + text: BTreeMap<&'static str, Bucket>, + inline_kinds: BTreeMap<&'static str, usize>, + block_kinds: BTreeMap<&'static str, usize>, + si_kinds: BTreeMap<&'static str, usize>, + inline_vecs: usize, + inline_vec_len: usize, + inline_vec_cap: usize, + block_vecs: usize, + block_vec_len: usize, + block_vec_cap: usize, + str_len_hist: [usize; 6], // <=7, <=15, <=23, <=31, <=63, more + str_verbatim: usize, + str_verbatim_bytes: usize, + str_not_verbatim: usize, + str_unresolvable: usize, + nonempty_attrs: usize, + attr_sites: usize, + // "Text run" what-if: maximal runs of adjacent Str/Space (and, in the + // second counter, SoftBreak too) inside one Vec. + runs: usize, + run_members: usize, + run_text_bytes: usize, + runs_sb: usize, + run_members_sb: usize, +} + +impl Census { + fn s(&mut self, cat: &'static str, s: &str, cap: usize) { + let b = self.text.entry(cat).or_default(); + b.count += 1; + b.len += s.len(); + b.cap += cap; + if cap > 0 { + b.heap_blocks += 1; + b.heap += heap_size(s.as_ptr()); + } + } + fn string(&mut self, cat: &'static str, s: &String) { + self.s(cat, s, s.capacity()); + } + fn si(&mut self, si: &SourceInfo) { + let k = match si { + SourceInfo::Original { .. } => "Original", + SourceInfo::Substring { .. } => "Substring", + SourceInfo::Concat { .. } => "Concat", + SourceInfo::Generated(_) => "Generated", + }; + *self.si_kinds.entry(k).or_default() += 1; + } + fn attr(&mut self, a: &Attr) { + self.attr_sites += 1; + if !(a.0.is_empty() && a.1.is_empty() && a.2.is_empty()) { + self.nonempty_attrs += 1; + } + self.string("attr.id", &a.0); + for c in &a.1 { + self.string("attr.class", c); + } + for (k, v) in &a.2 { + self.string("attr.key", k); + self.string("attr.value", v); + } + } + fn str_node(&mut self, text: &String, si: &SourceInfo, src: &[u8]) { + self.string("Str.text", text); + let i = match text.len() { + 0..=7 => 0, + 8..=15 => 1, + 16..=23 => 2, + 24..=31 => 3, + 32..=63 => 4, + _ => 5, + }; + self.str_len_hist[i] += 1; + match si.resolve_byte_range() { + Some((_, s, e)) => { + if src.get(s..e) == Some(text.as_bytes()) { + self.str_verbatim += 1; + self.str_verbatim_bytes += text.len(); + } else { + self.str_not_verbatim += 1; + } + } + None => self.str_unresolvable += 1, + } + } + + fn inlines(&mut self, v: &Vec, src: &[u8]) { + self.inline_vecs += 1; + self.inline_vec_len += v.len(); + self.inline_vec_cap += v.capacity(); + let (mut in_run, mut in_run_sb) = (false, false); + for i in v { + let word = match i { + Inline::Str(s) => Some(s.text.len()), + Inline::Space(_) => Some(1), + _ => None, + }; + let soft = matches!(i, Inline::SoftBreak(_)); + if let Some(n) = word { + self.runs += usize::from(!in_run); + self.run_members += 1; + self.run_text_bytes += n; + } + in_run = word.is_some(); + if word.is_some() || soft { + self.runs_sb += usize::from(!in_run_sb); + self.run_members_sb += 1; + } + in_run_sb = word.is_some() || soft; + self.inline(i, src); + } + } + fn blocks(&mut self, v: &Vec, src: &[u8]) { + self.block_vecs += 1; + self.block_vec_len += v.len(); + self.block_vec_cap += v.capacity(); + for b in v { + self.block(b, src); + } + } + fn citation(&mut self, c: &Citation, src: &[u8]) { + self.string("Citation.id", &c.id); + self.inlines(&c.prefix, src); + self.inlines(&c.suffix, src); + } + fn custom(&mut self, c: &CustomNode, src: &[u8]) { + self.string("Custom.type_name", &c.type_name); + self.attr(&c.attr); + for (k, slot) in &c.slots { + self.string("Custom.slot_name", k); + match slot { + Slot::Block(b) => self.block(b, src), + Slot::Inline(i) => self.inline(i, src), + Slot::Blocks(b) => self.blocks(b, src), + Slot::Inlines(i) => self.inlines(i, src), + } + } + } + + fn inline(&mut self, i: &Inline, src: &[u8]) { + self.si(i.source_info()); + let kind: &'static str = match i { + Inline::Str(s) => { + self.str_node(&s.text, &s.source_info, src); + "Str" + } + Inline::Emph(x) => { + self.inlines(&x.content, src); + "Emph" + } + Inline::Underline(x) => { + self.inlines(&x.content, src); + "Underline" + } + Inline::Strong(x) => { + self.inlines(&x.content, src); + "Strong" + } + Inline::Strikeout(x) => { + self.inlines(&x.content, src); + "Strikeout" + } + Inline::Superscript(x) => { + self.inlines(&x.content, src); + "Superscript" + } + Inline::Subscript(x) => { + self.inlines(&x.content, src); + "Subscript" + } + Inline::SmallCaps(x) => { + self.inlines(&x.content, src); + "SmallCaps" + } + Inline::Quoted(x) => { + self.inlines(&x.content, src); + "Quoted" + } + Inline::Cite(x) => { + for c in &x.citations { + self.citation(c, src); + } + self.inlines(&x.content, src); + "Cite" + } + Inline::Code(x) => { + self.attr(&x.attr); + self.string("Code.text", &x.text); + "Code" + } + Inline::Space(_) => "Space", + Inline::SoftBreak(_) => "SoftBreak", + Inline::LineBreak(_) => "LineBreak", + Inline::Math(x) => { + self.string("Math.text", &x.text); + "Math" + } + Inline::RawInline(x) => { + self.string("Raw.format", &x.format); + self.string("Raw.text", &x.text); + "RawInline" + } + Inline::Link(x) => { + self.attr(&x.attr); + self.inlines(&x.content, src); + self.string("target.url", &x.target.0); + self.string("target.title", &x.target.1); + "Link" + } + Inline::Image(x) => { + self.attr(&x.attr); + self.inlines(&x.content, src); + self.string("target.url", &x.target.0); + self.string("target.title", &x.target.1); + "Image" + } + Inline::Note(x) => { + self.blocks(&x.content, src); + "Note" + } + Inline::Span(x) => { + self.attr(&x.attr); + self.inlines(&x.content, src); + "Span" + } + Inline::Shortcode(x) => { + self.string("Shortcode.name", &x.name); + "Shortcode" + } + Inline::NoteReference(x) => { + self.string("Note.id", &x.id); + "NoteReference" + } + Inline::Attr(x) => { + self.attr(&x.attr); + "Attr" + } + Inline::Insert(x) => { + self.attr(&x.attr); + self.inlines(&x.content, src); + "Insert" + } + Inline::Delete(x) => { + self.attr(&x.attr); + self.inlines(&x.content, src); + "Delete" + } + Inline::Highlight(x) => { + self.attr(&x.attr); + self.inlines(&x.content, src); + "Highlight" + } + Inline::EditComment(x) => { + self.attr(&x.attr); + self.inlines(&x.content, src); + "EditComment" + } + Inline::Custom(c) => { + self.custom(c, src); + "Custom" + } + }; + *self.inline_kinds.entry(kind).or_default() += 1; + } + + fn rows(&mut self, rows: &[Row], src: &[u8]) { + for r in rows { + self.attr(&r.attr); + for Cell { attr, content, .. } in &r.cells { + self.attr(attr); + self.blocks(content, src); + } + } + } + fn table(&mut self, t: &Table, src: &[u8]) { + self.attr(&t.attr); + if let Some(s) = &t.caption.short { + self.inlines(s, src); + } + if let Some(l) = &t.caption.long { + self.blocks(l, src); + } + self.attr(&t.head.attr); + self.rows(&t.head.rows, src); + for b in &t.bodies { + self.attr(&b.attr); + self.rows(&b.head, src); + self.rows(&b.body, src); + } + self.attr(&t.foot.attr); + self.rows(&t.foot.rows, src); + } + + fn block(&mut self, b: &Block, src: &[u8]) { + self.si(b.source_info()); + let kind: &'static str = match b { + Block::Plain(x) => { + self.inlines(&x.content, src); + "Plain" + } + Block::Paragraph(x) => { + self.inlines(&x.content, src); + "Paragraph" + } + Block::LineBlock(x) => { + for l in &x.content { + self.inlines(l, src); + } + "LineBlock" + } + Block::CodeBlock(x) => { + self.attr(&x.attr); + self.string("CodeBlock.text", &x.text); + "CodeBlock" + } + Block::RawBlock(x) => { + self.string("Raw.format", &x.format); + self.string("Raw.text", &x.text); + "RawBlock" + } + Block::BlockQuote(x) => { + self.blocks(&x.content, src); + "BlockQuote" + } + Block::OrderedList(x) => { + for i in &x.content { + self.blocks(i, src); + } + "OrderedList" + } + Block::BulletList(x) => { + for i in &x.content { + self.blocks(i, src); + } + "BulletList" + } + Block::DefinitionList(x) => { + for (t, ds) in &x.content { + self.inlines(t, src); + for d in ds { + self.blocks(d, src); + } + } + "DefinitionList" + } + Block::Header(x) => { + self.attr(&x.attr); + self.inlines(&x.content, src); + "Header" + } + Block::HorizontalRule(_) => "HorizontalRule", + Block::Table(t) => { + self.table(t, src); + "Table" + } + Block::Figure(x) => { + self.attr(&x.attr); + if let Some(s) = &x.caption.short { + self.inlines(s, src); + } + if let Some(l) = &x.caption.long { + self.blocks(l, src); + } + self.blocks(&x.content, src); + "Figure" + } + Block::Div(x) => { + self.attr(&x.attr); + self.blocks(&x.content, src); + "Div" + } + Block::BlockMetadata(x) => { + self.meta(&x.meta, src); + "BlockMetadata" + } + Block::NoteDefinitionPara(x) => { + self.string("Note.id", &x.id); + self.inlines(&x.content, src); + "NoteDefinitionPara" + } + Block::NoteDefinitionFencedBlock(x) => { + self.string("Note.id", &x.id); + self.blocks(&x.content, src); + "NoteDefinitionFencedBlock" + } + Block::CaptionBlock(x) => { + self.inlines(&x.content, src); + "CaptionBlock" + } + Block::Custom(c) => { + self.custom(c, src); + "Custom" + } + }; + *self.block_kinds.entry(kind).or_default() += 1; + } + + fn meta(&mut self, m: &ConfigValue, src: &[u8]) { + match &m.value { + ConfigValueKind::PandocInlines(i) => self.inlines(i, src), + ConfigValueKind::PandocBlocks(b) => self.blocks(b, src), + ConfigValueKind::Array(a) => { + for v in a { + self.meta(v, src); + } + } + ConfigValueKind::Map(entries) => { + for e in entries { + self.string("meta.key", &e.key); + self.meta(&e.value, src); + } + } + ConfigValueKind::Path(s) | ConfigValueKind::Glob(s) | ConfigValueKind::Expr(s) => { + self.string("meta.scalar", s) + } + ConfigValueKind::Scalar { .. } => { + *self.block_kinds.entry("(meta scalar)").or_default() += 1; + } + } + } +} + +fn collect(dir: &Path, out: &mut Vec) { + let Ok(rd) = std::fs::read_dir(dir) else { + return; + }; + for e in rd.filter_map(Result::ok) { + let p = e.path(); + if p.is_dir() { + collect(&p, out); + } else if p.extension().is_some_and(|x| x == "md" || x == "qmd") { + out.push(p); + } + } +} + +fn main() -> Result<(), Box> { + let dir = std::env::args().nth(1).ok_or("usage: ast-memory ")?; + + println!("== type sizes (bytes) =="); + macro_rules! sz { + ($($t:ty),* $(,)?) => { $( println!(" {:<44} {}", stringify!($t), std::mem::size_of::<$t>()); )* }; + } + sz!( + String, + SourceInfo, + quarto_pandoc_types::inline::Str, + quarto_pandoc_types::inline::Space, + Inline, + quarto_pandoc_types::inline::Link, + quarto_pandoc_types::inline::Code, + Attr, + quarto_pandoc_types::attr::AttrSourceInfo, + CustomNode, + Block, + quarto_pandoc_types::block::Paragraph, + quarto_pandoc_types::block::CodeBlock, + Table, + ConfigValue, + ); + + let s0 = snap(); + let mut paths = Vec::new(); + collect(Path::new(&dir), &mut paths); + paths.sort(); + let sources: Vec> = paths.iter().filter_map(|p| std::fs::read(p).ok()).collect(); + let total_src: usize = sources.iter().map(Vec::len).sum(); + let s1 = snap(); + println!("\n== corpus =="); + println!( + " files={} source_bytes={} ({:.2} MiB)", + sources.len(), + total_src, + mb(total_src) + ); + + println!("\n== allocator traffic =="); + phase("read sources", s0, s1); + + let mut asts = Vec::with_capacity(sources.len()); + let mut ctxs = Vec::with_capacity(sources.len()); + let mut ok_src: Vec<&[u8]> = Vec::with_capacity(sources.len()); + let mut failed = 0usize; + let mut failed_bytes = 0usize; + let s2 = snap(); + PEAK.store(LIVE.load(Relaxed), Relaxed); + for (i, src) in sources.iter().enumerate() { + let name = format!("/doc-{i}.qmd"); + match pampa::readers::qmd::read(src, false, &name, &mut std::io::sink(), true, None) { + Ok((pandoc, ctx, _diags)) => { + asts.push(pandoc); + ctxs.push(ctx); + ok_src.push(src); + } + Err(_) => { + failed += 1; + failed_bytes += src.len(); + } + } + } + let s3 = snap(); + phase("parse all (retain AST+ctx)", s2, s3); + let parsed_bytes = total_src - failed_bytes; + println!( + " parsed files={} ({:.2} MiB); failed files={failed} ({:.2} MiB)", + asts.len(), + mb(parsed_bytes), + mb(failed_bytes) + ); + println!( + " peak live during parse (above pre-parse) = {:.1} MiB", + mb(PEAK.load(Relaxed) - s2.live) + ); + + // Census (allocates only its own small maps). + let mut c = Census::default(); + let mut uniq: HashSet<&str> = HashSet::new(); + for (p, src) in asts.iter().zip(&ok_src) { + c.meta(&p.meta, src); + c.blocks(&p.blocks, src); + } + fn uniq_walk<'a>(v: &'a [Inline], u: &mut HashSet<&'a str>) { + for i in v { + if let Inline::Str(s) = i { + u.insert(&s.text); + } + } + } + // Unique Str texts, top-level paragraphs only is not enough; do a cheap + // full pass via the serialized census instead: count from paragraphs, + // headers and plain blocks at any depth reachable through block lists. + fn uniq_blocks<'a>(bs: &'a [Block], u: &mut HashSet<&'a str>) { + for b in bs { + match b { + Block::Plain(x) => uniq_walk(&x.content, u), + Block::Paragraph(x) => uniq_walk(&x.content, u), + Block::Header(x) => uniq_walk(&x.content, u), + Block::BlockQuote(x) => uniq_blocks(&x.content, u), + Block::Div(x) => uniq_blocks(&x.content, u), + Block::BulletList(x) => x.content.iter().for_each(|i| uniq_blocks(i, u)), + Block::OrderedList(x) => x.content.iter().for_each(|i| uniq_blocks(i, u)), + _ => {} + } + } + } + let mut uniq_seen = 0usize; + for p in &asts { + uniq_blocks(&p.blocks, &mut uniq); + } + let uniq_bytes: usize = uniq.iter().map(|s| s.len()).sum(); + fn count_top<'a>(bs: &'a [Block], n: &mut usize) { + for b in bs { + let f = |v: &'a [Inline], n: &mut usize| { + *n += v.iter().filter(|i| matches!(i, Inline::Str(_))).count() + }; + match b { + Block::Plain(x) => f(&x.content, n), + Block::Paragraph(x) => f(&x.content, n), + Block::Header(x) => f(&x.content, n), + Block::BlockQuote(x) => count_top(&x.content, n), + Block::Div(x) => count_top(&x.content, n), + Block::BulletList(x) => x.content.iter().for_each(|i| count_top(i, n)), + Block::OrderedList(x) => x.content.iter().for_each(|i| count_top(i, n)), + _ => {} + } + } + } + for p in &asts { + count_top(&p.blocks, &mut uniq_seen); + } + + println!("\n== text fields (retained ASTs) =="); + println!( + " {:<18} {:>9} {:>11} {:>11} {:>11} {:>11}", + "field", "count", "heap_blocks", "len_bytes", "cap_bytes", "malloc_bytes" + ); + let mut tot = Bucket::default(); + for (k, b) in &c.text { + println!( + " {:<18} {:>9} {:>11} {:>11} {:>11} {:>11}", + k, b.count, b.heap_blocks, b.len, b.cap, b.heap + ); + tot.count += b.count; + tot.heap_blocks += b.heap_blocks; + tot.len += b.len; + tot.cap += b.cap; + tot.heap += b.heap; + } + println!( + " {:<18} {:>9} {:>11} {:>11} {:>11} {:>11}", + "TOTAL", tot.count, tot.heap_blocks, tot.len, tot.cap, tot.heap + ); + + println!("\n== Str detail =="); + let labels = ["<=7", "8-15", "16-23", "24-31", "32-63", ">=64"]; + let nstr: usize = c.str_len_hist.iter().sum(); + for (l, n) in labels.iter().zip(c.str_len_hist) { + println!( + " len {:<6} {:>9} {:>5.1}%", + l, + n, + 100.0 * n as f64 / nstr.max(1) as f64 + ); + } + println!( + " verbatim-slice-of-source: {} ({:.1}%), {} bytes; differs: {}; unresolvable source_info: {}", + c.str_verbatim, + 100.0 * c.str_verbatim as f64 / nstr.max(1) as f64, + c.str_verbatim_bytes, + c.str_not_verbatim, + c.str_unresolvable + ); + println!( + " distinct Str texts (para/header/plain reachable via lists/quotes/divs): {} distinct of {} nodes, {} distinct bytes", + uniq.len(), + uniq_seen, + uniq_bytes + ); + + println!("\n== node census =="); + let ninl: usize = c.inline_kinds.values().sum(); + let nblk: usize = c.block_kinds.values().sum(); + println!(" inlines={ninl} blocks={nblk}"); + let mut ik: Vec<_> = c.inline_kinds.iter().collect(); + ik.sort_by(|a, b| b.1.cmp(a.1)); + for (k, n) in ik { + println!(" inline {:<16} {:>9}", k, n); + } + let mut bk: Vec<_> = c.block_kinds.iter().collect(); + bk.sort_by(|a, b| b.1.cmp(a.1)); + for (k, n) in bk { + println!(" block {:<26} {:>9}", k, n); + } + for (k, n) in &c.si_kinds { + println!(" node source_info {:<10} {:>9}", k, n); + } + let isz = std::mem::size_of::(); + let bsz = std::mem::size_of::(); + println!( + " Vec: {} vecs, len {} cap {} -> {:.1} MiB in use, {:.1} MiB reserved", + c.inline_vecs, + c.inline_vec_len, + c.inline_vec_cap, + mb(c.inline_vec_len * isz), + mb(c.inline_vec_cap * isz) + ); + println!( + " Vec: {} vecs, len {} cap {} -> {:.1} MiB in use, {:.1} MiB reserved", + c.block_vecs, + c.block_vec_len, + c.block_vec_cap, + mb(c.block_vec_len * bsz), + mb(c.block_vec_cap * bsz) + ); + println!( + " text-run what-if: Str/Space runs={} covering {} inlines ({} text bytes) -> {} inlines total; \ + with SoftBreak: runs={} covering {} -> {} inlines total", + c.runs, + c.run_members, + c.run_text_bytes, + ninl - c.run_members + c.runs, + c.runs_sb, + c.run_members_sb, + ninl - c.run_members_sb + c.runs_sb + ); + println!( + " attr sites: {} of which non-empty: {}", + c.attr_sites, c.nonempty_attrs + ); + + drop(uniq); + println!("\n== what the retained data costs =="); + let s4 = snap(); + drop(ctxs); + let s5 = snap(); + phase("drop ASTContexts", s4, s5); + drop(asts); + let s6 = snap(); + phase("drop Pandoc ASTs", s5, s6); + let ast_bytes = s5.live - s6.live; + let ast_blocks = (s6.frees - s5.frees) - (s6.allocs - s5.allocs); + println!( + "\n AST heap = {:.1} MiB in {} blocks = {:.2}x parsed source bytes; {:.1} heap blocks per KiB of source", + mb(ast_bytes), + ast_blocks, + ast_bytes as f64 / parsed_bytes as f64, + ast_blocks as f64 / (parsed_bytes as f64 / 1024.0) + ); + println!( + " Str.text share: {:.1}% of AST heap blocks, {:.1}% of AST heap bytes (malloc-rounded)", + 100.0 * c.text["Str.text"].heap_blocks as f64 / ast_blocks as f64, + 100.0 * c.text["Str.text"].heap as f64 / ast_bytes as f64 + ); + println!( + " all text fields: {:.1}% of AST heap blocks, {:.1}% of AST heap bytes (malloc-rounded)", + 100.0 * tot.heap_blocks as f64 / ast_blocks as f64, + 100.0 * tot.heap as f64 / ast_bytes as f64 + ); + println!( + " Vec buffers: {:.1}% of AST heap bytes (reserved)", + 100.0 * (c.inline_vec_cap * isz) as f64 / ast_bytes as f64 + ); + Ok(()) +} From f2878b39044acdeca4b0717d52b59f1d46911c41 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 12 Nov 2026 13:22:29 -0600 Subject: [PATCH 2/5] docs(plans): link AST layout follow-up strands in the string-storage study Co-Authored-By: Claude Fable 5.2 --- claude-notes/plans/2026-11-12-ast-string-storage-study.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/claude-notes/plans/2026-11-12-ast-string-storage-study.md b/claude-notes/plans/2026-11-12-ast-string-storage-study.md index 677569640a..012039b098 100644 --- a/claude-notes/plans/2026-11-12-ast-string-storage-study.md +++ b/claude-notes/plans/2026-11-12-ast-string-storage-study.md @@ -408,6 +408,10 @@ offsets do nothing on wasm32, D moves behind the small-string step. A breaking `quarto-source-map` release is acceptable. Each step is separately shippable and measurable with `ast-memory`. +Tracked under epic bd-4x6kih4c: bd-xrfun6mn (step 1), bd-09g8zl30 and +bd-gz80k6c6 (step 2), bd-cwprgmo2 (step 3), bd-v6cug77d (step 4), +bd-9iyh0220 (step 5), bd-r17a0k7v (wasm32 tracking), bd-jmn62md8 (text +runs, deferred). 1. **Prototype A on `Inline` only** and re-measure. This is the test of the central estimate; if `Vec` does not fall to about 370 MiB, stop From 248de5580a3df336f72d34ef8be0235d581ad170 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 12 Nov 2026 13:38:42 -0600 Subject: [PATCH 3/5] docs(plans): record ecow-first, Lua-heavy benchmark, and wasm32 tracking decisions Co-Authored-By: Claude Fable 5.2 --- .../2026-11-12-ast-string-storage-study.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/claude-notes/plans/2026-11-12-ast-string-storage-study.md b/claude-notes/plans/2026-11-12-ast-string-storage-study.md index 012039b098..bf970c7d10 100644 --- a/claude-notes/plans/2026-11-12-ast-string-storage-study.md +++ b/claude-notes/plans/2026-11-12-ast-string-storage-study.md @@ -418,8 +418,10 @@ runs, deferred). and re-examine. 2. **A for `Block`, then B.** 3. **Reader fast paths** (typography/escape no-op path, `Concat` hull). -4. **C**, choosing the string type by benchmark on this corpus and on a - Lua-filter-heavy render. +4. **C**, starting with `ecow`, benchmarked on this corpus and on a + Lua-heavy render. For the Lua-heavy case, convert the listing of the + `claude-notes/plans` website to an lmd-based setup that builds (say) a + bullet list of the entries, instead of the listing we have now. 5. **D**, as a quarto-source-map minor release. 6. Re-measure bd-is4q72tt's 1.1 MB document end to end. Decide then whether F is worth a design. @@ -430,13 +432,16 @@ runs, deferred). - **quarto-source-map:** a breaking release with `u32` offsets is acceptable. - **`ast_memory.rs`:** committed to `perf-harness` as the regression tool. - **Text runs (F):** deferred to after the layout work; needs a flag design. +- **Small-string type:** try `ecow` first (16 bytes on both targets, 15 + inline). Typst uses it and has substantial WASM exposure. Fall back to + comparing `compact_str` / `hipstr` only if it disappoints. +- **wasm32 tracking:** a work item, not a question (bd-r17a0k7v). WASM + targets build here; the tool is the implementer's choice. `ast-memory` + measures a native build only, so the primary goal needs its own number. ## Open questions -- The harness measures a native build. A wasm32 measurement (or at least a - size-of table checked in CI) is needed to track the primary goal. -- Which small-string type: `compact_str` is pointer-sized ×3 (12 bytes on - wasm32), `ecow` is 16 bytes on both targets. +None blocking. The text-run flag design is deferred (see F). ## Sources From 3c8b3d9c057cd7a9523501415060712d5977f9e8 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 12 Nov 2026 14:16:52 -0600 Subject: [PATCH 4/5] Box cold Inline variants: Inline is 64 bytes, down from 360 (bd-xrfun6mn) Every element of a Vec occupies size_of::() bytes, and most inlines are Str and Space. Link (352 bytes) set that size for all of them. Box the payloads of the variants larger than Str: Quoted, Cite, Code, Math, RawInline, Link, Image, Span, Shortcode, Attr, Insert, Delete, Highlight, EditComment and Custom. A const assertion holds Inline at 8 words (64 bytes native, 32 on wasm32). On claude-notes/plans (26.3 MiB) with perf-harness ast-memory: Vec buffers 2058 -> 366 MiB, AST heap 2304 -> 678 MiB (87.7x -> 25.8x of source), peak RSS 2.75 -> 0.87 GiB, parse wall 7.7 -> 6.5 s. Serde is unaffected (Box serializes as T). Call sites change mechanically: constructions gain Box::new, destructuring patterns on boxed payloads become a binding plus field access, and or-patterns mixing boxed and unboxed variants are split. Co-Authored-By: Claude Fable 5.2 --- .../2026-11-12-ast-string-storage-study.md | 37 +++- crates/comrak-to-pandoc/src/compare.rs | 56 ++--- crates/comrak-to-pandoc/src/inline.rs | 16 +- crates/comrak-to-pandoc/src/normalize.rs | 28 +-- .../tests/integration/generators.rs | 16 +- crates/pampa/src/citeproc_filter.rs | 110 +++++----- crates/pampa/src/filters.rs | 159 +++++++------- crates/pampa/src/lua/constructors.rs | 42 ++-- crates/pampa/src/lua/diagnostics.rs | 4 +- crates/pampa/src/lua/filter.rs | 60 +++--- crates/pampa/src/lua/filter_tests.rs | 168 +++++++-------- crates/pampa/src/lua/quarto_api.rs | 8 +- crates/pampa/src/lua/types.rs | 104 ++++----- crates/pampa/src/lua/walk.rs | 54 ++--- crates/pampa/src/pandoc/desugar.rs | 4 +- crates/pampa/src/pandoc/meta.rs | 4 +- crates/pampa/src/pandoc/shortcode.rs | 16 +- crates/pampa/src/pandoc/treesitter.rs | 34 +-- .../src/pandoc/treesitter_utils/caption.rs | 4 +- .../src/pandoc/treesitter_utils/citation.rs | 4 +- .../treesitter_utils/code_span_helpers.rs | 8 +- .../treesitter_utils/editorial_marks.rs | 4 +- .../src/pandoc/treesitter_utils/paragraph.rs | 6 +- .../src/pandoc/treesitter_utils/pipe_table.rs | 6 +- .../pandoc/treesitter_utils/postprocess.rs | 36 ++-- .../pandoc/treesitter_utils/quote_helpers.rs | 4 +- .../src/pandoc/treesitter_utils/shortcode.rs | 6 +- .../treesitter_utils/span_link_helpers.rs | 12 +- .../pandoc/treesitter_utils/uri_autolink.rs | 8 +- crates/pampa/src/readers/json.rs | 68 +++--- crates/pampa/src/toc.rs | 12 +- crates/pampa/src/writers/block_attr.rs | 6 +- crates/pampa/src/writers/html.rs | 18 +- crates/pampa/src/writers/json.rs | 26 +-- crates/pampa/src/writers/native.rs | 2 +- crates/pampa/src/writers/plaintext.rs | 117 ++++++----- crates/pampa/src/writers/qmd.rs | 6 +- .../attribution_html_coalescing_test.rs | 4 +- .../integration/inline_splice_safety_tests.rs | 4 +- .../tests/integration/test_ansi_writer.rs | 72 +++---- .../integration/test_attr_source_structure.rs | 12 +- .../integration/test_lua_attr_mutation.rs | 4 +- .../integration/test_raw_json_roundtrip.rs | 36 ++-- .../pampa/tests/integration/test_shortcode.rs | 2 +- .../src/transforms/shortcode.rs | 4 +- crates/quarto-ast-reconcile/src/apply.rs | 6 +- crates/quarto-ast-reconcile/src/generators.rs | 52 ++--- crates/quarto-ast-reconcile/src/hash.rs | 72 +++---- crates/quarto-ast-reconcile/src/lib.rs | 40 ++-- crates/quarto-citeproc/src/output.rs | 16 +- crates/quarto-core/src/ast_walk.rs | 4 +- crates/quarto-core/src/crossref/target.rs | 2 +- crates/quarto-core/src/dashboard/resolve.rs | 4 +- crates/quarto-core/src/editorial_marks.rs | 14 +- .../src/pandoc_filters/image_alt_encode.rs | 8 +- crates/quarto-core/src/project/book/config.rs | 4 +- crates/quarto-core/src/project/book/merge.rs | 12 +- .../src/project/listing/helpers.rs | 17 +- .../quarto-core/src/revealjs/auto_stretch.rs | 10 +- .../quarto-core/src/revealjs/footer_logo.rs | 4 +- .../src/stage/stages/equation_number.rs | 31 +-- .../src/stage/stages/include_expansion.rs | 12 +- .../src/stage/stages/include_resolve.rs | 16 +- .../src/stage/stages/listing_item_info.rs | 8 +- .../src/stage/stages/lmd_desugar.rs | 2 +- .../src/stage/stages/lmd_execute.rs | 6 +- .../quarto-core/src/stage/stages/math_js.rs | 12 +- .../quarto-core/src/stage/stages/math_ml.rs | 16 +- .../stage/stages/prefetch_remote_images.rs | 4 +- crates/quarto-core/src/template.rs | 24 +-- .../src/transforms/announcement_render.rs | 4 +- crates/quarto-core/src/transforms/appendix.rs | 4 +- .../src/transforms/book_cover_image.rs | 4 +- .../src/transforms/callout_resolve.rs | 12 +- .../src/transforms/components_resolve.rs | 8 +- .../src/transforms/conditional_content.rs | 12 +- .../cross_chapter_crossref_resolve.rs | 2 +- .../src/transforms/crossref_index.rs | 8 +- .../src/transforms/crossref_render.rs | 56 ++--- .../src/transforms/crossref_resolve.rs | 10 +- .../src/transforms/editorial_marks_ooxml.rs | 14 +- .../src/transforms/editorial_marks_typst.rs | 4 +- .../src/transforms/equation_label.rs | 36 ++-- .../src/transforms/example_embed.rs | 8 +- .../src/transforms/float_ref_target.rs | 4 +- .../src/transforms/footer_render.rs | 8 +- .../quarto-core/src/transforms/footnotes.rs | 4 +- .../src/transforms/footnotes_resolve.rs | 12 +- .../src/transforms/image_alt_normalize.rs | 41 ++-- crates/quarto-core/src/transforms/lightbox.rs | 8 +- .../src/transforms/link_external.rs | 8 +- .../src/transforms/link_rewrite.rs | 16 +- crates/quarto-core/src/transforms/llms.rs | 14 +- .../src/transforms/metadata_normalize.rs | 148 +++++++------ .../src/transforms/navbar_render.rs | 12 +- .../src/transforms/navigation_href.rs | 8 +- .../src/transforms/panel_tabset_resolve.rs | 4 +- .../src/transforms/resource_collector.rs | 20 +- .../src/transforms/responsive_image.rs | 16 +- .../src/transforms/shortcode_resolve.rs | 197 +++++++++++------- .../src/transforms/sidebar_render.rs | 8 +- .../quarto-core/src/transforms/title_block.rs | 4 +- .../quarto-core/src/transforms/toc_render.rs | 54 ++--- crates/quarto-core/src/typst_citations.rs | 4 +- .../tests/integration/annotate.rs | 8 +- crates/quarto-lsp-core/src/analysis.rs | 2 +- crates/quarto-navigation/src/render_html.rs | 68 +++--- crates/quarto-pandoc-types/src/inline.rs | 97 +++++---- 108 files changed, 1459 insertions(+), 1295 deletions(-) diff --git a/claude-notes/plans/2026-11-12-ast-string-storage-study.md b/claude-notes/plans/2026-11-12-ast-string-storage-study.md index bf970c7d10..4f2be7e74e 100644 --- a/claude-notes/plans/2026-11-12-ast-string-storage-study.md +++ b/claude-notes/plans/2026-11-12-ast-string-storage-study.md @@ -2,7 +2,7 @@ title: "Study: arena / borrowed string storage for the Pandoc AST" strand: bd-akgst1fi related: [bd-is4q72tt] -status: "Assessment written and discussed (2026-11-12); direction agreed: no lifetimes, node-layout work first, text runs deferred. No implementation yet." +status: "Direction agreed (no lifetimes; node layout first; text runs deferred). Step 1 (box cold Inline variants) done: AST heap 88x -> 26x of source. Steps 2-5 open under epic bd-4x6kih4c." date: 2026-11-12 --- @@ -426,6 +426,41 @@ runs, deferred). 6. Re-measure bd-is4q72tt's 1.1 MB document end to end. Decide then whether F is worth a design. +## Progress + +### Step 1: box cold `Inline` variants (bd-xrfun6mn) — measured + +Boxed the payloads of `Quoted`, `Cite`, `Code`, `Math`, `RawInline`, `Link`, +`Image`, `Span`, `Shortcode`, `Attr`, `Insert`, `Delete`, `Highlight`, +`EditComment` and `Custom`. A compile-time assertion in `inline.rs` holds +`Inline` at 8 words (64 bytes native, 32 on wasm32). + +Same corpus, same tool: + +| | Before | After | Estimate | +|---|---|---|---| +| `size_of::()` | 360 | 64 | 64 | +| `Vec` buffers | 2,058 MiB | 366 MiB | ~366 MiB | +| AST heap | 2,304 MiB (87.7×) | 678 MiB (25.8×) | — | +| AST heap blocks | 4.06 M | 4.36 M | ~4.35 M | +| Peak RSS of the run | 2.75 GiB | 0.87 GiB | — | +| Wall time | 7.7 s | 6.5 s | — | + +The central estimate held, so the remaining steps proceed as planned. The +297 K extra heap blocks are almost all boxed `Code` nodes (277 K, 240 bytes +each); step 2's `Attr` slimming shrinks those. + +Notes for the next steps, from doing this one: + +- Construction sites (`Inline::Link(Link { .. })`) and box/unbox + mismatches are mechanical: a bracket-matching codemod plus rustc's own + `Box::new` / unboxing suggestions handled about 650 sites. +- Patterns that destructure a boxed payload cannot stay as written. Most + code binds the payload and uses field access, which auto-derefs and + needed no change; only about 30 sites destructured. +- Or-patterns that mix boxed and unboxed variants must be split into one + arm per boxed variant. + ## Decisions (2026-11-12) - **Goal order:** hub/WASM memory > `q2 render` peak RSS > wall time. diff --git a/crates/comrak-to-pandoc/src/compare.rs b/crates/comrak-to-pandoc/src/compare.rs index c4b5dee752..c05e8b9317 100644 --- a/crates/comrak-to-pandoc/src/compare.rs +++ b/crates/comrak-to-pandoc/src/compare.rs @@ -347,127 +347,127 @@ mod tests { #[test] fn test_code_inline_eq() { use hashlink::LinkedHashMap; - let a = Inline::Code(quarto_pandoc_types::Code { + let a = Inline::Code(Box::new(quarto_pandoc_types::Code { attr: (String::new(), vec![], LinkedHashMap::new()), text: "code".to_string(), source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); - let b = Inline::Code(quarto_pandoc_types::Code { + })); + let b = Inline::Code(Box::new(quarto_pandoc_types::Code { attr: (String::new(), vec![], LinkedHashMap::new()), text: "code".to_string(), source_info: other_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); + })); assert!(inline_eq(&a, &b)); } #[test] fn test_link_eq() { use hashlink::LinkedHashMap; - let a = Inline::Link(quarto_pandoc_types::Link { + let a = Inline::Link(Box::new(quarto_pandoc_types::Link { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], target: ("url".to_string(), "title".to_string()), source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), target_source: quarto_pandoc_types::TargetSourceInfo::empty(), - }); - let b = Inline::Link(quarto_pandoc_types::Link { + })); + let b = Inline::Link(Box::new(quarto_pandoc_types::Link { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], target: ("url".to_string(), "title".to_string()), source_info: other_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), target_source: quarto_pandoc_types::TargetSourceInfo::empty(), - }); + })); assert!(inline_eq(&a, &b)); } #[test] fn test_image_eq() { use hashlink::LinkedHashMap; - let a = Inline::Image(quarto_pandoc_types::Image { + let a = Inline::Image(Box::new(quarto_pandoc_types::Image { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], target: ("img.png".to_string(), "alt".to_string()), source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), target_source: quarto_pandoc_types::TargetSourceInfo::empty(), - }); - let b = Inline::Image(quarto_pandoc_types::Image { + })); + let b = Inline::Image(Box::new(quarto_pandoc_types::Image { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], target: ("img.png".to_string(), "alt".to_string()), source_info: other_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), target_source: quarto_pandoc_types::TargetSourceInfo::empty(), - }); + })); assert!(inline_eq(&a, &b)); } #[test] fn test_span_eq() { use hashlink::LinkedHashMap; - let a = Inline::Span(quarto_pandoc_types::Span { + let a = Inline::Span(Box::new(quarto_pandoc_types::Span { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); - let b = Inline::Span(quarto_pandoc_types::Span { + })); + let b = Inline::Span(Box::new(quarto_pandoc_types::Span { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: other_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); + })); assert!(inline_eq(&a, &b)); } #[test] fn test_quoted_eq() { - let a = Inline::Quoted(quarto_pandoc_types::Quoted { + let a = Inline::Quoted(Box::new(quarto_pandoc_types::Quoted { quote_type: quarto_pandoc_types::QuoteType::DoubleQuote, content: vec![], source_info: dummy_source(), - }); - let b = Inline::Quoted(quarto_pandoc_types::Quoted { + })); + let b = Inline::Quoted(Box::new(quarto_pandoc_types::Quoted { quote_type: quarto_pandoc_types::QuoteType::DoubleQuote, content: vec![], source_info: other_source(), - }); + })); assert!(inline_eq(&a, &b)); } #[test] fn test_math_eq() { - let a = Inline::Math(quarto_pandoc_types::Math { + let a = Inline::Math(Box::new(quarto_pandoc_types::Math { math_type: quarto_pandoc_types::MathType::InlineMath, text: "x^2".to_string(), source_info: dummy_source(), text_source: None, - }); - let b = Inline::Math(quarto_pandoc_types::Math { + })); + let b = Inline::Math(Box::new(quarto_pandoc_types::Math { math_type: quarto_pandoc_types::MathType::InlineMath, text: "x^2".to_string(), source_info: other_source(), text_source: None, - }); + })); assert!(inline_eq(&a, &b)); } #[test] fn test_rawinline_eq() { - let a = Inline::RawInline(quarto_pandoc_types::RawInline { + let a = Inline::RawInline(Box::new(quarto_pandoc_types::RawInline { format: "html".to_string(), text: "".to_string(), source_info: dummy_source(), - }); - let b = Inline::RawInline(quarto_pandoc_types::RawInline { + })); + let b = Inline::RawInline(Box::new(quarto_pandoc_types::RawInline { format: "html".to_string(), text: "".to_string(), source_info: other_source(), - }); + })); assert!(inline_eq(&a, &b)); } diff --git a/crates/comrak-to-pandoc/src/inline.rs b/crates/comrak-to-pandoc/src/inline.rs index 0271e63889..5da04005db 100644 --- a/crates/comrak-to-pandoc/src/inline.rs +++ b/crates/comrak-to-pandoc/src/inline.rs @@ -105,11 +105,11 @@ fn convert_inline<'a>( // Raw HTML, as pandoc's commonmark reader (raw_html on) produces it. NodeValue::HtmlInline(html) => { - vec![Inline::RawInline(RawInline { + vec![Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: html.clone(), source_info, - })] + }))] } // Unsupported inline types - panic as they're outside CommonMark; @@ -159,12 +159,12 @@ fn convert_inline<'a>( } fn convert_code(code: &NodeCode, source_info: SourceInfo) -> Inline { - Inline::Code(Code { + Inline::Code(Box::new(Code { attr: empty_attr(), text: code.literal.clone(), source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } fn convert_link(link: &NodeLink, children: Inlines, source_info: SourceInfo) -> Inline { @@ -181,26 +181,26 @@ fn convert_link(link: &NodeLink, children: Inlines, source_info: SourceInfo) -> empty_attr() }; - Inline::Link(Link { + Inline::Link(Box::new(Link { attr, content: children, target: (link.url.clone(), link.title.clone()), source_info, attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn convert_image(link: &NodeLink, children: Inlines, source_info: SourceInfo) -> Inline { // For images, children become alt text - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: empty_attr(), content: children, target: (link.url.clone(), link.title.clone()), source_info, attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } #[cfg(test)] diff --git a/crates/comrak-to-pandoc/src/normalize.rs b/crates/comrak-to-pandoc/src/normalize.rs index 7008b18a3e..ecb7225903 100644 --- a/crates/comrak-to-pandoc/src/normalize.rs +++ b/crates/comrak-to-pandoc/src/normalize.rs @@ -241,14 +241,14 @@ mod tests { let ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Link(Link { + content: vec![Inline::Link(Box::new(Link { content: vec![], target: ("http://example.com".to_string(), String::new()), attr: (String::new(), vec!["uri".to_string()], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: empty_source_info(), })], }; @@ -457,14 +457,14 @@ mod tests { let ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Image(Image { + content: vec![Inline::Image(Box::new(Image { content: vec![], target: ("img.png".to_string(), String::new()), attr: (String::new(), vec![], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: empty_source_info(), })], }; @@ -483,7 +483,7 @@ mod tests { let ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { content: vec![Inline::Str(Str { text: "hello".to_string(), source_info: empty_source_info(), @@ -491,7 +491,7 @@ mod tests { attr: (String::new(), vec![], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: empty_source_info(), })], }; @@ -555,14 +555,14 @@ mod tests { meta: ConfigValue::default(), blocks: vec![Block::Figure(Figure { content: vec![Block::Plain(Plain { - content: vec![Inline::Image(Image { + content: vec![Inline::Image(Box::new(Image { content: vec![], target: ("img.png".to_string(), String::new()), attr: (String::new(), vec![], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: empty_source_info(), })], caption: Caption { @@ -589,14 +589,14 @@ mod tests { meta: ConfigValue::default(), blocks: vec![Block::Figure(Figure { content: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Image(Image { + content: vec![Inline::Image(Box::new(Image { content: vec![], target: ("img.png".to_string(), String::new()), attr: (String::new(), vec![], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: empty_source_info(), })], caption: Caption { @@ -673,14 +673,14 @@ mod tests { use quarto_pandoc_types::{Image, Str}; // Single image - let single_image = vec![Inline::Image(Image { + let single_image = vec![Inline::Image(Box::new(Image { content: vec![], target: ("img.png".to_string(), String::new()), attr: (String::new(), vec![], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })]; + }))]; assert!(is_single_image(&single_image)); // Multiple inlines @@ -689,14 +689,14 @@ mod tests { text: "hello".to_string(), source_info: empty_source_info(), }), - Inline::Image(Image { + Inline::Image(Box::new(Image { content: vec![], target: ("img.png".to_string(), String::new()), attr: (String::new(), vec![], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }), + })), ]; assert!(!is_single_image(&multiple)); diff --git a/crates/comrak-to-pandoc/tests/integration/generators.rs b/crates/comrak-to-pandoc/tests/integration/generators.rs index b2192fe6f0..21bdd06ed2 100644 --- a/crates/comrak-to-pandoc/tests/integration/generators.rs +++ b/crates/comrak-to-pandoc/tests/integration/generators.rs @@ -352,12 +352,12 @@ fn gen_single_inline(features: InlineFeatures) -> impl Strategy 1, safe_word() .prop_map(|text| { - Inline::Code(Code { + Inline::Code(Box::new(Code { text, attr: empty_attr(), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) }) .boxed(), )); @@ -403,14 +403,14 @@ fn gen_single_inline(features: InlineFeatures) -> impl Strategy gen_title(), ) .prop_map(|(content, url, title)| { - Inline::Link(Link { + Inline::Link(Box::new(Link { content, target: (url, title), attr: empty_attr(), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) }) .boxed(), )); @@ -426,14 +426,14 @@ fn gen_single_inline(features: InlineFeatures) -> impl Strategy gen_title(), ) .prop_map(|(alt, url, title)| { - Inline::Image(Image { + Inline::Image(Box::new(Image { content: alt, target: (url, title), attr: empty_attr(), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) }) .boxed(), )); @@ -444,7 +444,7 @@ fn gen_single_inline(features: InlineFeatures) -> impl Strategy 1, gen_url() .prop_map(|url| { - Inline::Link(Link { + Inline::Link(Box::new(Link { content: vec![Inline::Str(Str { text: url.clone(), source_info: empty_source_info(), @@ -454,7 +454,7 @@ fn gen_single_inline(features: InlineFeatures) -> impl Strategy source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) }) .boxed(), )); diff --git a/crates/pampa/src/citeproc_filter.rs b/crates/pampa/src/citeproc_filter.rs index e5972da96a..b5d683a9eb 100644 --- a/crates/pampa/src/citeproc_filter.rs +++ b/crates/pampa/src/citeproc_filter.rs @@ -351,7 +351,7 @@ pub fn check_citation_keys( cite.citations.retain(|c| known.contains(&c.id)); let mut out: Inlines = Vec::new(); if !cite.citations.is_empty() { - out.push(Inline::Cite(cite)); + out.push(Inline::Cite(Box::new(cite))); } for marker in markers { match out.last() { @@ -1214,12 +1214,12 @@ fn localize_quotes(inlines: &mut Inlines, quotes: &QuoteConfig, depth: usize) { wrapped.push(quote_str(open)); wrapped.extend(content); wrapped.push(quote_str(close)); - *inline = Inline::Span(crate::pandoc::Span { + *inline = Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: wrapped, source_info: q.source_info.clone(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - }); + })); } _ => {} } @@ -1475,12 +1475,12 @@ fn transform_inline_slot( *inline = if one.len() == 1 { one.pop().unwrap() } else { - Inline::Span(crate::pandoc::Span { + Inline::Span(Box::new(crate::pandoc::Span { attr: quarto_pandoc_types::empty_attr(), content: one, source_info: quarto_source_map::SourceInfo::generated(quarto_source_map::By::citeproc()), attr_source: crate::pandoc::AttrSourceInfo::empty(), - }) + })) }; } @@ -2164,67 +2164,67 @@ mod tests { #[test] fn test_inlines_to_text_quoted() { - let inlines = vec![Inline::Quoted(Quoted { + let inlines = vec![Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::DoubleQuote, content: vec![str_inline("quoted")], source_info: si(), - })]; + }))]; assert_eq!(inlines_to_text(&inlines), "quoted"); } #[test] fn test_inlines_to_text_code() { - let inlines = vec![Inline::Code(Code { + let inlines = vec![Inline::Code(Box::new(Code { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), text: "println!".to_string(), source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - })]; + }))]; assert_eq!(inlines_to_text(&inlines), "println!"); } #[test] fn test_inlines_to_text_math() { - let inlines = vec![Inline::Math(Math { + let inlines = vec![Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text: "x^2".to_string(), source_info: si(), text_source: None, - })]; + }))]; assert_eq!(inlines_to_text(&inlines), "x^2"); } #[test] fn test_inlines_to_text_raw_inline() { - let inlines = vec![Inline::RawInline(RawInline { + let inlines = vec![Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: "raw".to_string(), source_info: si(), - })]; + }))]; assert_eq!(inlines_to_text(&inlines), "raw"); } #[test] fn test_inlines_to_text_link() { - let inlines = vec![Inline::Link(crate::pandoc::Link { + let inlines = vec![Inline::Link(Box::new(crate::pandoc::Link { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![str_inline("link text")], target: ("https://example.com".to_string(), String::new()), source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), target_source: crate::pandoc::TargetSourceInfo::empty(), - })]; + }))]; assert_eq!(inlines_to_text(&inlines), "link text"); } #[test] fn test_inlines_to_text_span() { - let inlines = vec![Inline::Span(crate::pandoc::Span { + let inlines = vec![Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![str_inline("span content")], source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - })]; + }))]; assert_eq!(inlines_to_text(&inlines), "span content"); } @@ -2256,12 +2256,12 @@ mod tests { }), str_inline("!"), Inline::Space(Space { source_info: si() }), - Inline::Code(Code { + Inline::Code(Box::new(Code { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), text: "code".to_string(), source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - }), + })), ]; assert_eq!(inlines_to_text(&inlines), "Hello world! code"); } @@ -2734,7 +2734,7 @@ mod tests { #[test] fn test_collect_citations_single_citation() { - let cite = Inline::Cite(crate::pandoc::Cite { + let cite = Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: "smith2020".to_string(), prefix: vec![], @@ -2746,7 +2746,7 @@ mod tests { }], content: vec![], source_info: si(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![]), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { @@ -2762,7 +2762,7 @@ mod tests { #[test] fn test_collect_citations_in_emphasis() { - let cite = Inline::Cite(crate::pandoc::Cite { + let cite = Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: "jones2021".to_string(), prefix: vec![], @@ -2774,7 +2774,7 @@ mod tests { }], content: vec![], source_info: si(), - }); + })); let emph = Inline::Emph(Emph { content: vec![cite], source_info: si(), @@ -2793,7 +2793,7 @@ mod tests { #[test] fn test_collect_citations_in_block_quote() { - let cite = Inline::Cite(crate::pandoc::Cite { + let cite = Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: "quoted2020".to_string(), prefix: vec![], @@ -2805,7 +2805,7 @@ mod tests { }], content: vec![], source_info: si(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![]), blocks: vec![Block::BlockQuote(crate::pandoc::BlockQuote { @@ -2823,7 +2823,7 @@ mod tests { #[test] fn test_collect_citations_in_div() { - let cite = Inline::Cite(crate::pandoc::Cite { + let cite = Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: "div2020".to_string(), prefix: vec![], @@ -2835,7 +2835,7 @@ mod tests { }], content: vec![], source_info: si(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![]), blocks: vec![Block::Div(crate::pandoc::Div { @@ -2965,7 +2965,7 @@ mod tests { // Helper to create a citation inline fn make_cite(id: &str) -> Inline { - Inline::Cite(crate::pandoc::Cite { + Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: id.to_string(), prefix: vec![], @@ -2977,7 +2977,7 @@ mod tests { }], content: vec![], source_info: si(), - }) + })) } // Tests for collect_citations in various block types @@ -3334,11 +3334,11 @@ mod tests { #[test] fn test_collect_citations_in_quoted() { let cite = make_cite("quoted2020"); - let quoted = Inline::Quoted(Quoted { + let quoted = Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::DoubleQuote, content: vec![cite], source_info: si(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![]), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { @@ -3354,14 +3354,14 @@ mod tests { #[test] fn test_collect_citations_in_link() { let cite = make_cite("link2020"); - let link = Inline::Link(crate::pandoc::Link { + let link = Inline::Link(Box::new(crate::pandoc::Link { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![cite], target: ("https://example.com".to_string(), String::new()), source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), target_source: crate::pandoc::TargetSourceInfo::empty(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![]), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { @@ -3377,12 +3377,12 @@ mod tests { #[test] fn test_collect_citations_in_span() { let cite = make_cite("span2020"); - let span = Inline::Span(crate::pandoc::Span { + let span = Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![cite], source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![]), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { @@ -3420,7 +3420,7 @@ mod tests { // Test citation with prefix and suffix #[test] fn test_collect_citations_with_prefix_suffix() { - let cite = Inline::Cite(crate::pandoc::Cite { + let cite = Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: "prefixsuffix2020".to_string(), prefix: vec![str_inline("see ")], @@ -3432,7 +3432,7 @@ mod tests { }], content: vec![], source_info: si(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![]), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { @@ -3449,7 +3449,7 @@ mod tests { // Test citation modes #[test] fn test_collect_citations_suppress_author_mode() { - let cite = Inline::Cite(crate::pandoc::Cite { + let cite = Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: "suppress2020".to_string(), prefix: vec![], @@ -3461,7 +3461,7 @@ mod tests { }], content: vec![], source_info: si(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![]), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { @@ -3477,7 +3477,7 @@ mod tests { #[test] fn test_collect_citations_author_in_text_mode() { - let cite = Inline::Cite(crate::pandoc::Cite { + let cite = Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: "authoronly2020".to_string(), prefix: vec![], @@ -3489,7 +3489,7 @@ mod tests { }], content: vec![], source_info: si(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![]), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { @@ -3685,27 +3685,27 @@ mod tests { source_info: si(), }), // Quoted - Inline::Quoted(Quoted { + Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::DoubleQuote, content: vec![make_cite("quoted2020")], source_info: si(), - }), + })), // Link - Inline::Link(crate::pandoc::Link { + Inline::Link(Box::new(crate::pandoc::Link { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![make_cite("link2020")], target: ("https://example.com".to_string(), String::new()), source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), target_source: crate::pandoc::TargetSourceInfo::empty(), - }), + })), // Span - Inline::Span(crate::pandoc::Span { + Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![make_cite("span2020")], source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - }), + })), ]; let ids = [ @@ -3906,7 +3906,7 @@ mod tests { ]); // Create a citation - let cite = Inline::Cite(crate::pandoc::Cite { + let cite = Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: "test2020".to_string(), prefix: vec![], @@ -3918,7 +3918,7 @@ mod tests { }], content: vec![], source_info: si(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![("references", meta_array(vec![full_ref]))]), @@ -3992,7 +3992,7 @@ mod tests { } fn cite_of(ids: &[&str]) -> Inline { - Inline::Cite(crate::pandoc::Cite { + Inline::Cite(Box::new(crate::pandoc::Cite { citations: ids .iter() .map(|id| crate::pandoc::Citation { @@ -4007,7 +4007,7 @@ mod tests { .collect(), content: vec![], source_info: si(), - }) + })) } /// Run the filter over one paragraph holding `cite`, with the two @@ -4646,11 +4646,11 @@ mod tests { inner_close: "‘".to_string(), }; let quoted = |content: Inlines| { - Inline::Quoted(Quoted { + Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::DoubleQuote, content, source_info: si(), - }) + })) }; let mut inlines = vec![quoted(vec![ str_inline("a "), @@ -4983,7 +4983,7 @@ mod tests { vec![str_inline(text)] } }; - Inline::Cite(crate::pandoc::Cite { + Inline::Cite(Box::new(crate::pandoc::Cite { citations: items .iter() .map(|(id, prefix, suffix, id_source)| crate::pandoc::Citation { @@ -4998,7 +4998,7 @@ mod tests { .collect(), content: vec![], source_info: si(), - }) + })) } /// Run the filter on one paragraph of `content` against `SCOPE_BIB`. diff --git a/crates/pampa/src/filters.rs b/crates/pampa/src/filters.rs index efff1b51f6..da03a42e62 100644 --- a/crates/pampa/src/filters.rs +++ b/crates/pampa/src/filters.rs @@ -191,6 +191,22 @@ define_filter_with_methods!( // Macro to generate repetitive match arms // Macro to reduce repetition in filter logic macro_rules! handle_inline_filter { + // For variants whose payload is boxed in `Inline` (see the layout note on + // `Inline`). `$value` is the `Box`; it is only unboxed on the path that + // hands the bare node to a type-specific filter function. + (boxed $variant:ident, $value:ident, $filter_field:ident, $filter:expr, $ctx:expr, $out:expr) => { + if let Some(f) = &mut $filter.$filter_field { + inlines_apply_and_maybe_recurse!(*$value, f, $filter, $ctx, $out) + } else if let Some(f) = &mut $filter.inline { + inlines_apply_and_maybe_recurse!(Inline::$variant($value), f, $filter, $ctx, $out) + } else { + $out.push(traverse_inline_structure( + Inline::$variant($value), + $filter, + $ctx, + )) + } + }; ($variant:ident, $value:ident, $filter_field:ident, $filter:expr, $ctx:expr, $out:expr) => { if let Some(f) = &mut $filter.$filter_field { inlines_apply_and_maybe_recurse!($value, f, $filter, $ctx, $out) @@ -231,7 +247,7 @@ macro_rules! impl_inline_filterable_terminal { $( impl InlineFilterableStructure for pandoc::$variant { fn filter_structure(self, _: &mut Filter, _ctx: &mut FilterContext) -> Inline { - Inline::$variant(self) + Inline::$variant(self.into()) } } )* @@ -256,7 +272,7 @@ impl InlineFilterableStructure for InlineAttr { fn filter_structure(self, _: &mut Filter, _ctx: &mut FilterContext) -> Inline { // Note: This should not be called in practice because Attr inlines // are stripped during postprocessing before filters run - Inline::Attr(self) + Inline::Attr(Box::new(self)) } } @@ -265,10 +281,13 @@ macro_rules! impl_inline_filterable_simple { $( impl InlineFilterableStructure for pandoc::$variant { fn filter_structure(self, filter: &mut Filter, ctx: &mut FilterContext) -> Inline { - Inline::$variant(pandoc::$variant { - content: topdown_traverse_inlines(self.content, filter, ctx), - ..self - }) + Inline::$variant( + pandoc::$variant { + content: topdown_traverse_inlines(self.content, filter, ctx), + ..self + } + .into(), + ) } } )* @@ -304,7 +323,7 @@ impl InlineFilterableStructure for pandoc::Note { impl InlineFilterableStructure for pandoc::Cite { fn filter_structure(self, filter: &mut Filter, ctx: &mut FilterContext) -> Inline { - Inline::Cite(pandoc::Cite { + Inline::Cite(Box::new(pandoc::Cite { citations: self .citations .into_iter() @@ -320,7 +339,7 @@ impl InlineFilterableStructure for pandoc::Cite { .collect(), content: topdown_traverse_inlines(self.content, filter, ctx), source_info: self.source_info, - }) + })) } } @@ -589,13 +608,13 @@ fn topdown_traverse_inline_into( handle_inline_filter!(SmallCaps, sc, small_caps, filter, ctx, out) } Inline::Quoted(q) => { - handle_inline_filter!(Quoted, q, quoted, filter, ctx, out) + handle_inline_filter!(boxed Quoted, q, quoted, filter, ctx, out) } Inline::Cite(c) => { - handle_inline_filter!(Cite, c, cite, filter, ctx, out) + handle_inline_filter!(boxed Cite, c, cite, filter, ctx, out) } Inline::Code(co) => { - handle_inline_filter!(Code, co, code, filter, ctx, out) + handle_inline_filter!(boxed Code, co, code, filter, ctx, out) } Inline::Space(sp) => { handle_inline_filter!(Space, sp, space, filter, ctx, out) @@ -607,26 +626,26 @@ fn topdown_traverse_inline_into( handle_inline_filter!(LineBreak, lb, line_break, filter, ctx, out) } Inline::Math(m) => { - handle_inline_filter!(Math, m, math, filter, ctx, out) + handle_inline_filter!(boxed Math, m, math, filter, ctx, out) } Inline::RawInline(ri) => { - handle_inline_filter!(RawInline, ri, raw_inline, filter, ctx, out) + handle_inline_filter!(boxed RawInline, ri, raw_inline, filter, ctx, out) } Inline::Link(l) => { - handle_inline_filter!(Link, l, link, filter, ctx, out) + handle_inline_filter!(boxed Link, l, link, filter, ctx, out) } Inline::Image(i) => { - handle_inline_filter!(Image, i, image, filter, ctx, out) + handle_inline_filter!(boxed Image, i, image, filter, ctx, out) } Inline::Note(note) => { handle_inline_filter!(Note, note, note, filter, ctx, out) } Inline::Span(span) => { - handle_inline_filter!(Span, span, span, filter, ctx, out) + handle_inline_filter!(boxed Span, span, span, filter, ctx, out) } // quarto extensions Inline::Shortcode(shortcode) => { - handle_inline_filter!(Shortcode, shortcode, shortcode, filter, ctx, out) + handle_inline_filter!(boxed Shortcode, shortcode, shortcode, filter, ctx, out) } Inline::NoteReference(note_ref) => { handle_inline_filter!(NoteReference, note_ref, note_reference, filter, ctx, out) @@ -649,16 +668,16 @@ fn topdown_traverse_inline_into( } } Inline::Insert(ins) => { - handle_inline_filter!(Insert, ins, insert, filter, ctx, out) + handle_inline_filter!(boxed Insert, ins, insert, filter, ctx, out) } Inline::Delete(del) => { - handle_inline_filter!(Delete, del, delete, filter, ctx, out) + handle_inline_filter!(boxed Delete, del, delete, filter, ctx, out) } Inline::Highlight(hl) => { - handle_inline_filter!(Highlight, hl, highlight, filter, ctx, out) + handle_inline_filter!(boxed Highlight, hl, highlight, filter, ctx, out) } Inline::EditComment(ec) => { - handle_inline_filter!(EditComment, ec, edit_comment, filter, ctx, out) + handle_inline_filter!(boxed EditComment, ec, edit_comment, filter, ctx, out) } Inline::Custom(custom) => { // Custom nodes: recursively filter the content in slots, then pass through @@ -852,12 +871,12 @@ fn traverse_inline_nonterminal( content: topdown_traverse_inlines(sc.content, filter, ctx), source_info: sc.source_info, }), - Inline::Quoted(q) => Inline::Quoted(crate::pandoc::Quoted { + Inline::Quoted(q) => Inline::Quoted(Box::new(crate::pandoc::Quoted { quote_type: q.quote_type, content: topdown_traverse_inlines(q.content, filter, ctx), source_info: q.source_info, - }), - Inline::Cite(c) => Inline::Cite(crate::pandoc::Cite { + })), + Inline::Cite(c) => Inline::Cite(Box::new(crate::pandoc::Cite { citations: c .citations .into_iter() @@ -873,49 +892,49 @@ fn traverse_inline_nonterminal( .collect(), content: topdown_traverse_inlines(c.content, filter, ctx), source_info: c.source_info, - }), - Inline::Link(l) => Inline::Link(crate::pandoc::Link { + })), + Inline::Link(l) => Inline::Link(Box::new(crate::pandoc::Link { attr: l.attr, target: l.target, content: topdown_traverse_inlines(l.content, filter, ctx), source_info: l.source_info, attr_source: l.attr_source, target_source: l.target_source, - }), - Inline::Image(i) => Inline::Image(crate::pandoc::Image { + })), + Inline::Image(i) => Inline::Image(Box::new(crate::pandoc::Image { attr: i.attr, target: i.target, content: topdown_traverse_inlines(i.content, filter, ctx), source_info: i.source_info, attr_source: i.attr_source, target_source: i.target_source, - }), + })), Inline::Note(note) => Inline::Note(crate::pandoc::Note { content: topdown_traverse_blocks(note.content, filter, ctx), source_info: note.source_info, }), - Inline::Span(span) => Inline::Span(crate::pandoc::Span { + Inline::Span(span) => Inline::Span(Box::new(crate::pandoc::Span { attr: span.attr, content: topdown_traverse_inlines(span.content, filter, ctx), source_info: span.source_info, attr_source: span.attr_source, - }), - Inline::Insert(ins) => Inline::Insert(crate::pandoc::Insert { + })), + Inline::Insert(ins) => Inline::Insert(Box::new(crate::pandoc::Insert { content: topdown_traverse_inlines(ins.content, filter, ctx), - ..ins - }), - Inline::Delete(del) => Inline::Delete(crate::pandoc::Delete { + ..*ins + })), + Inline::Delete(del) => Inline::Delete(Box::new(crate::pandoc::Delete { content: topdown_traverse_inlines(del.content, filter, ctx), - ..del - }), - Inline::Highlight(hl) => Inline::Highlight(crate::pandoc::Highlight { + ..*del + })), + Inline::Highlight(hl) => Inline::Highlight(Box::new(crate::pandoc::Highlight { content: topdown_traverse_inlines(hl.content, filter, ctx), - ..hl - }), - Inline::EditComment(ec) => Inline::EditComment(crate::pandoc::EditComment { + ..*hl + })), + Inline::EditComment(ec) => Inline::EditComment(Box::new(crate::pandoc::EditComment { content: topdown_traverse_inlines(ec.content, filter, ctx), - ..ec - }), + ..*ec + })), _ => panic!("Unsupported inline type: {:?}", inline), } } @@ -1398,12 +1417,12 @@ mod tests { fn test_traverse_code() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let inline = Inline::Code(Code { + let inline = Inline::Code(Box::new(Code { attr: empty_attr(), text: "code".to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let result = topdown_traverse_inline(inline, &mut filter, &mut ctx); assert_eq!(result.len(), 1); match &result[0] { @@ -1416,12 +1435,12 @@ mod tests { fn test_traverse_math() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let inline = Inline::Math(Math { + let inline = Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text: "x^2".to_string(), source_info: si(), text_source: None, - }); + })); let result = topdown_traverse_inline(inline, &mut filter, &mut ctx); assert_eq!(result.len(), 1); match &result[0] { @@ -1434,11 +1453,11 @@ mod tests { fn test_traverse_raw_inline() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let inline = Inline::RawInline(RawInline { + let inline = Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: "bold".to_string(), source_info: si(), - }); + })); let result = topdown_traverse_inline(inline, &mut filter, &mut ctx); assert_eq!(result.len(), 1); match &result[0] { @@ -1451,13 +1470,13 @@ mod tests { fn test_traverse_shortcode() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let inline = Inline::Shortcode(Shortcode { + let inline = Inline::Shortcode(Box::new(Shortcode { is_escaped: false, name: "test".to_string(), positional_args: vec![], keyword_args: hashlink::LinkedHashMap::new(), source_info: si(), - }); + })); let result = topdown_traverse_inline(inline, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Inline::Shortcode(_))); @@ -1499,14 +1518,14 @@ mod tests { fn test_traverse_link() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let inline = Inline::Link(Link { + let inline = Inline::Link(Box::new(Link { attr: empty_attr(), content: vec![str_inline("link text")], target: ("https://example.com".to_string(), String::new()), source_info: si(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); let result = topdown_traverse_inline(inline, &mut filter, &mut ctx); assert_eq!(result.len(), 1); match &result[0] { @@ -1519,14 +1538,14 @@ mod tests { fn test_traverse_image() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let inline = Inline::Image(Image { + let inline = Inline::Image(Box::new(Image { attr: empty_attr(), content: vec![str_inline("alt")], target: ("image.png".to_string(), String::new()), source_info: si(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); let result = topdown_traverse_inline(inline, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Inline::Image(_))); @@ -1536,12 +1555,12 @@ mod tests { fn test_traverse_span() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let inline = Inline::Span(Span { + let inline = Inline::Span(Box::new(Span { attr: empty_attr(), content: vec![str_inline("span")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let result = topdown_traverse_inline(inline, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Inline::Span(_))); @@ -1563,30 +1582,30 @@ mod tests { ) }; let marks = vec![ - Inline::Insert(Insert { + Inline::Insert(Box::new(Insert { attr: empty_attr(), content: vec![str_inline("ins")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), - Inline::Delete(Delete { + })), + Inline::Delete(Box::new(Delete { attr: empty_attr(), content: vec![str_inline("del")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), - Inline::Highlight(Highlight { + })), + Inline::Highlight(Box::new(Highlight { attr: empty_attr(), content: vec![str_inline("hl")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), - Inline::EditComment(EditComment { + })), + Inline::EditComment(Box::new(EditComment { attr: empty_attr(), content: vec![str_inline("com")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), ]; for mark in marks { let mut filter = Filter::new().with_str(upper); @@ -1611,7 +1630,7 @@ mod tests { fn test_traverse_custom_inline() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let inline = Inline::Custom(CustomNode::new("test", empty_attr(), si())); + let inline = Inline::Custom(Box::new(CustomNode::new("test", empty_attr(), si()))); let result = topdown_traverse_inline(inline, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Inline::Custom(_))); @@ -1623,11 +1642,11 @@ mod tests { let mut filter = Filter::new().with_inlines(|inlines, _ctx| FilterReturn::Unchanged(inlines)); let mut ctx = FilterContext::new(); - let inline = Inline::Attr(InlineAttr::new( + let inline = Inline::Attr(Box::new(InlineAttr::new( empty_attr(), AttrSourceInfo::empty(), quarto_source_map::SourceInfo::for_test(), - )); + ))); let result = topdown_traverse_inline(inline, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Inline::Attr(_))); @@ -1637,11 +1656,11 @@ mod tests { fn test_traverse_attr_without_filter() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let inline = Inline::Attr(InlineAttr::new( + let inline = Inline::Attr(Box::new(InlineAttr::new( empty_attr(), AttrSourceInfo::empty(), quarto_source_map::SourceInfo::for_test(), - )); + ))); let result = topdown_traverse_inline(inline, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Inline::Attr(_))); diff --git a/crates/pampa/src/lua/constructors.rs b/crates/pampa/src/lua/constructors.rs index da8e3bb3cc..73bd583825 100644 --- a/crates/pampa/src/lua/constructors.rs +++ b/crates/pampa/src/lua/constructors.rs @@ -1160,11 +1160,11 @@ fn register_inline_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { } }; let inlines = peek_inlines_fuzzy(lua, content)?; - lua.create_userdata(LuaInline::new(Inline::Quoted(Quoted { + lua.create_userdata(LuaInline::new(Inline::Quoted(Box::new(Quoted { quote_type: qt, content: inlines, source_info: filter_source_info(lua), - }))) + })))) })?, )?; @@ -1173,12 +1173,14 @@ fn register_inline_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { "Code", lua.create_function(|lua, (text, attr): (String, Option)| { let attr = parse_attr(lua, attr)?; - lua.create_userdata(LuaInline::new(Inline::Code(crate::pandoc::Code { - text, - attr, - source_info: filter_source_info(lua), - attr_source: AttrSourceInfo::empty(), - }))) + lua.create_userdata(LuaInline::new(Inline::Code(Box::new( + crate::pandoc::Code { + text, + attr, + source_info: filter_source_info(lua), + attr_source: AttrSourceInfo::empty(), + }, + )))) })?, )?; @@ -1197,12 +1199,12 @@ fn register_inline_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { )); } }; - lua.create_userdata(LuaInline::new(Inline::Math(Math { + lua.create_userdata(LuaInline::new(Inline::Math(Box::new(Math { math_type: mt, text, source_info: filter_source_info(lua), text_source: None, - }))) + })))) })?, )?; @@ -1210,11 +1212,11 @@ fn register_inline_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { pandoc.set( "RawInline", lua.create_function(|lua, (format, text): (String, String)| { - lua.create_userdata(LuaInline::new(Inline::RawInline(RawInline { + lua.create_userdata(LuaInline::new(Inline::RawInline(Box::new(RawInline { format, text, source_info: filter_source_info(lua), - }))) + })))) })?, )?; @@ -1226,14 +1228,14 @@ fn register_inline_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { let inlines = peek_inlines_fuzzy(lua, content)?; let title = title.unwrap_or_default(); let attr = parse_attr(lua, attr)?; - lua.create_userdata(LuaInline::new(Inline::Link(Link { + lua.create_userdata(LuaInline::new(Inline::Link(Box::new(Link { content: inlines, target: (target, title), attr, source_info: filter_source_info(lua), attr_source: AttrSourceInfo::empty(), target_source: crate::pandoc::attr::TargetSourceInfo::empty(), - }))) + })))) }, )?, )?; @@ -1246,14 +1248,14 @@ fn register_inline_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { let inlines = peek_inlines_fuzzy(lua, content)?; let title = title.unwrap_or_default(); let attr = parse_attr(lua, attr)?; - lua.create_userdata(LuaInline::new(Inline::Image(Image { + lua.create_userdata(LuaInline::new(Inline::Image(Box::new(Image { content: inlines, target: (src, title), attr, source_info: filter_source_info(lua), attr_source: AttrSourceInfo::empty(), target_source: crate::pandoc::attr::TargetSourceInfo::empty(), - }))) + })))) }, )?, )?; @@ -1264,12 +1266,12 @@ fn register_inline_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { lua.create_function(|lua, (content, attr): (Value, Option)| { let inlines = peek_inlines_fuzzy(lua, content)?; let attr = parse_attr(lua, attr)?; - lua.create_userdata(LuaInline::new(Inline::Span(Span { + lua.create_userdata(LuaInline::new(Inline::Span(Box::new(Span { content: inlines, attr, source_info: filter_source_info(lua), attr_source: AttrSourceInfo::empty(), - }))) + })))) })?, )?; @@ -1294,11 +1296,11 @@ fn register_inline_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { lua.create_function(|lua, (content, citations): (Value, Value)| { let inlines = peek_inlines_fuzzy(lua, content)?; let citations = super::types::lua_table_to_citations(lua, citations)?; - lua.create_userdata(LuaInline::new(Inline::Cite(Cite { + lua.create_userdata(LuaInline::new(Inline::Cite(Box::new(Cite { citations, content: inlines, source_info: filter_source_info(lua), - }))) + })))) })?, )?; diff --git a/crates/pampa/src/lua/diagnostics.rs b/crates/pampa/src/lua/diagnostics.rs index 25593c2a6a..e266c57a3f 100644 --- a/crates/pampa/src/lua/diagnostics.rs +++ b/crates/pampa/src/lua/diagnostics.rs @@ -970,7 +970,7 @@ mod tests { register_quarto_namespace(&lua).unwrap(); // Shortcode has source_info — Inline::source_info() returns it directly - let shortcode = Inline::Shortcode(Shortcode { + let shortcode = Inline::Shortcode(Box::new(Shortcode { is_escaped: false, name: "test".to_string(), positional_args: vec![], @@ -980,7 +980,7 @@ mod tests { 10, 20, ), - }); + })); let lua_inline = LuaInline::new(shortcode); lua.globals() .set("test_shortcode", lua.create_userdata(lua_inline).unwrap()) diff --git a/crates/pampa/src/lua/filter.rs b/crates/pampa/src/lua/filter.rs index 80bbd566be..69b8d0ea11 100644 --- a/crates/pampa/src/lua/filter.rs +++ b/crates/pampa/src/lua/filter.rs @@ -1574,28 +1574,28 @@ mod unit_tests { "SmallCaps" ); assert_eq!( - inline_tag(&Inline::Quoted(Quoted { + inline_tag(&Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::DoubleQuote, content: vec![], source_info: source_info.clone() - })), + }))), "Quoted" ); assert_eq!( - inline_tag(&Inline::Cite(Cite { + inline_tag(&Inline::Cite(Box::new(Cite { citations: vec![], content: vec![], source_info: source_info.clone() - })), + }))), "Cite" ); assert_eq!( - inline_tag(&Inline::Code(Code { + inline_tag(&Inline::Code(Box::new(Code { attr: (String::new(), vec![], LinkedHashMap::new()), text: String::new(), source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "Code" ); assert_eq!( @@ -1617,42 +1617,42 @@ mod unit_tests { "LineBreak" ); assert_eq!( - inline_tag(&Inline::Math(Math { + inline_tag(&Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text: String::new(), source_info: source_info.clone(), text_source: None, - })), + }))), "Math" ); assert_eq!( - inline_tag(&Inline::RawInline(RawInline { + inline_tag(&Inline::RawInline(Box::new(RawInline { format: String::new(), text: String::new(), source_info: source_info.clone() - })), + }))), "RawInline" ); assert_eq!( - inline_tag(&Inline::Link(Link { + inline_tag(&Inline::Link(Box::new(Link { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], target: (String::new(), String::new()), source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty() - })), + }))), "Link" ); assert_eq!( - inline_tag(&Inline::Image(Image { + inline_tag(&Inline::Image(Box::new(Image { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], target: (String::new(), String::new()), source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty() - })), + }))), "Image" ); assert_eq!( @@ -1663,16 +1663,16 @@ mod unit_tests { "Note" ); assert_eq!( - inline_tag(&Inline::Span(Span { + inline_tag(&Inline::Span(Box::new(Span { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "Span" ); assert_eq!( - inline_tag(&Inline::Shortcode( + inline_tag(&Inline::Shortcode(Box::new( quarto_pandoc_types::shortcode::Shortcode { is_escaped: false, name: String::new(), @@ -1680,7 +1680,7 @@ mod unit_tests { keyword_args: LinkedHashMap::new(), source_info: source_info.clone() } - )), + ))), "Shortcode" ); assert_eq!( @@ -1691,57 +1691,57 @@ mod unit_tests { "NoteReference" ); assert_eq!( - inline_tag(&Inline::Attr(InlineAttr::new( + inline_tag(&Inline::Attr(Box::new(InlineAttr::new( (String::new(), vec![], LinkedHashMap::new()), AttrSourceInfo::empty(), quarto_source_map::SourceInfo::for_test(), - ))), + )))), "Attr" ); assert_eq!( - inline_tag(&Inline::Insert(Insert { + inline_tag(&Inline::Insert(Box::new(Insert { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "Insert" ); assert_eq!( - inline_tag(&Inline::Delete(Delete { + inline_tag(&Inline::Delete(Box::new(Delete { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "Delete" ); assert_eq!( - inline_tag(&Inline::Highlight(Highlight { + inline_tag(&Inline::Highlight(Box::new(Highlight { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "Highlight" ); assert_eq!( - inline_tag(&Inline::EditComment(EditComment { + inline_tag(&Inline::EditComment(Box::new(EditComment { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "EditComment" ); assert_eq!( - inline_tag(&Inline::Custom(CustomNode { + inline_tag(&Inline::Custom(Box::new(CustomNode { type_name: String::new(), slots: LinkedHashMap::new(), plain_data: serde_json::Value::Null, attr: (String::new(), vec![], LinkedHashMap::new()), source_info: source_info.clone() - })), + }))), "Custom" ); } diff --git a/crates/pampa/src/lua/filter_tests.rs b/crates/pampa/src/lua/filter_tests.rs index a1f8b0d912..c4d98d46cf 100644 --- a/crates/pampa/src/lua/filter_tests.rs +++ b/crates/pampa/src/lua/filter_tests.rs @@ -53,7 +53,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { attr: ( "old-id".to_string(), vec!["old-class".to_string()], @@ -65,7 +65,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -120,7 +120,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![Inline::Str(crate::pandoc::Str { @@ -128,7 +128,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2259,14 +2259,14 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Quoted(crate::pandoc::Quoted { + content: vec![Inline::Quoted(Box::new(crate::pandoc::Quoted { quote_type: crate::pandoc::QuoteType::SingleQuote, content: vec![Inline::Str(crate::pandoc::Str { text: "quoted".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2294,14 +2294,14 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Quoted(crate::pandoc::Quoted { + content: vec![Inline::Quoted(Box::new(crate::pandoc::Quoted { quote_type: crate::pandoc::QuoteType::DoubleQuote, content: vec![Inline::Str(crate::pandoc::Str { text: "double".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2349,12 +2349,12 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Code(crate::pandoc::Code { + content: vec![Inline::Code(Box::new(crate::pandoc::Code { attr: ("my-code".to_string(), vec!["python".to_string()], attrs), attr_source: crate::pandoc::AttrSourceInfo::empty(), text: "print(1)".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2402,12 +2402,12 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Math(crate::pandoc::Math { + content: vec![Inline::Math(Box::new(crate::pandoc::Math { math_type: crate::pandoc::MathType::InlineMath, text: "x^2".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), text_source: None, - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2435,12 +2435,12 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Math(crate::pandoc::Math { + content: vec![Inline::Math(Box::new(crate::pandoc::Math { math_type: crate::pandoc::MathType::DisplayMath, text: "E=mc^2".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), text_source: None, - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2484,11 +2484,11 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::RawInline(crate::pandoc::RawInline { + content: vec![Inline::RawInline(Box::new(crate::pandoc::RawInline { format: "html".to_string(), text: "bold".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2535,7 +2535,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Link(crate::pandoc::Link { + content: vec![Inline::Link(Box::new(crate::pandoc::Link { attr: ( "link-id".to_string(), vec![], @@ -2549,7 +2549,7 @@ end target: ("https://example.com".to_string(), "Example".to_string()), target_source: crate::pandoc::TargetSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2601,7 +2601,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Image(crate::pandoc::Image { + content: vec![Inline::Image(Box::new(crate::pandoc::Image { attr: ("img-id".to_string(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![Inline::Str(crate::pandoc::Str { @@ -2611,7 +2611,7 @@ end target: ("image.png".to_string(), "Title".to_string()), target_source: crate::pandoc::TargetSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2717,7 +2717,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Cite(crate::pandoc::Cite { + content: vec![Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: "smith2020".to_string(), prefix: vec![], @@ -2732,7 +2732,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2794,7 +2794,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Insert(crate::pandoc::Insert { + content: vec![Inline::Insert(Box::new(crate::pandoc::Insert { attr: ("ins-id".to_string(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![Inline::Str(crate::pandoc::Str { @@ -2802,7 +2802,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2829,7 +2829,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Delete(crate::pandoc::Delete { + content: vec![Inline::Delete(Box::new(crate::pandoc::Delete { attr: ("del-id".to_string(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![Inline::Str(crate::pandoc::Str { @@ -2837,7 +2837,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -2864,7 +2864,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Highlight(crate::pandoc::Highlight { + content: vec![Inline::Highlight(Box::new(crate::pandoc::Highlight { attr: ("hl-id".to_string(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![Inline::Str(crate::pandoc::Str { @@ -2872,7 +2872,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3201,7 +3201,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { content: vec![Inline::Str(crate::pandoc::Str { text: "original".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -3209,7 +3209,7 @@ end attr: ("old-id".to_string(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3262,7 +3262,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Link(crate::pandoc::Link { + content: vec![Inline::Link(Box::new(crate::pandoc::Link { content: vec![Inline::Str(crate::pandoc::Str { text: "old text".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -3272,7 +3272,7 @@ end attr_source: crate::pandoc::AttrSourceInfo::empty(), target_source: crate::pandoc::TargetSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3327,7 +3327,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Image(crate::pandoc::Image { + content: vec![Inline::Image(Box::new(crate::pandoc::Image { content: vec![Inline::Str(crate::pandoc::Str { text: "old_alt".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -3337,7 +3337,7 @@ end attr_source: crate::pandoc::AttrSourceInfo::empty(), target_source: crate::pandoc::TargetSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3390,12 +3390,12 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Code(crate::pandoc::Code { + content: vec![Inline::Code(Box::new(crate::pandoc::Code { text: "old_code()".to_string(), attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3443,11 +3443,11 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::RawInline(crate::pandoc::RawInline { + content: vec![Inline::RawInline(Box::new(crate::pandoc::RawInline { text: "old".to_string(), format: "html".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3494,12 +3494,12 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Math(crate::pandoc::Math { + content: vec![Inline::Math(Box::new(crate::pandoc::Math { text: "x = a + b".to_string(), math_type: crate::pandoc::MathType::InlineMath, source_info: quarto_source_map::SourceInfo::for_test(), text_source: None, - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3545,14 +3545,14 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Quoted(crate::pandoc::Quoted { + content: vec![Inline::Quoted(Box::new(crate::pandoc::Quoted { content: vec![Inline::Str(crate::pandoc::Str { text: "old_quote".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), })], quote_type: crate::pandoc::QuoteType::DoubleQuote, source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3669,7 +3669,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Insert(crate::pandoc::Insert { + content: vec![Inline::Insert(Box::new(crate::pandoc::Insert { content: vec![Inline::Str(crate::pandoc::Str { text: "original".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -3677,7 +3677,7 @@ end attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3707,7 +3707,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Delete(crate::pandoc::Delete { + content: vec![Inline::Delete(Box::new(crate::pandoc::Delete { content: vec![Inline::Str(crate::pandoc::Str { text: "original".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -3715,7 +3715,7 @@ end attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3745,7 +3745,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Highlight(crate::pandoc::Highlight { + content: vec![Inline::Highlight(Box::new(crate::pandoc::Highlight { content: vec![Inline::Str(crate::pandoc::Str { text: "original".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -3753,7 +3753,7 @@ end attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -3849,7 +3849,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { content: vec![Inline::Str(crate::pandoc::Str { text: "test".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -3861,7 +3861,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -4746,7 +4746,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { content: vec![Inline::Str(crate::pandoc::Str { text: "test".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -4758,7 +4758,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -4807,7 +4807,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { content: vec![Inline::Str(crate::pandoc::Str { text: "test".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -4819,7 +4819,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -4866,7 +4866,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { content: vec![Inline::Str(crate::pandoc::Str { text: "test".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -4878,7 +4878,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -4930,7 +4930,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { content: vec![Inline::Str(crate::pandoc::Str { text: "test".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -4942,7 +4942,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -4993,7 +4993,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { content: vec![Inline::Str(crate::pandoc::Str { text: "test".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -5005,7 +5005,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -5055,7 +5055,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Cite(crate::pandoc::Cite { + content: vec![Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![crate::pandoc::Citation { id: "smith2021".to_string(), prefix: vec![], @@ -5070,7 +5070,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -5190,7 +5190,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { content: vec![Inline::Str(crate::pandoc::Str { text: "test".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -5198,7 +5198,7 @@ end attr: ("old-id".to_string(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -5507,7 +5507,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { content: vec![Inline::Str(crate::pandoc::Str { text: "test".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), @@ -5519,7 +5519,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -5633,7 +5633,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { content: vec![ Inline::Str(crate::pandoc::Str { text: "one".to_string(), @@ -5650,7 +5650,7 @@ end attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -6717,7 +6717,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![Inline::Str(crate::pandoc::Str { @@ -6733,7 +6733,7 @@ end 5, 7, ), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -6834,14 +6834,14 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![], source_info: quarto_source_map::SourceInfo::generated( quarto_source_map::By::filter("test.lua", 1), ), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -6956,12 +6956,12 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -7064,12 +7064,12 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -7122,7 +7122,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![], @@ -7132,7 +7132,7 @@ end 0, 5, ), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -7188,7 +7188,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![], @@ -7199,7 +7199,7 @@ end 0, 5, ), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -7776,7 +7776,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![], @@ -7785,7 +7785,7 @@ end 5, 7, ), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; @@ -7840,12 +7840,12 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(crate::pandoc::Paragraph { - content: vec![Inline::Span(crate::pandoc::Span { + content: vec![Inline::Span(Box::new(crate::pandoc::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], source_info: quarto_source_map::SourceInfo::for_test(), })], }; diff --git a/crates/pampa/src/lua/quarto_api.rs b/crates/pampa/src/lua/quarto_api.rs index 1cd57d4e79..37eacc9381 100644 --- a/crates/pampa/src/lua/quarto_api.rs +++ b/crates/pampa/src/lua/quarto_api.rs @@ -183,18 +183,18 @@ fn register_quarto_component(lua: &Lua, quarto: &Table) -> Result<()> { _ => false, }; if inline { - let mut inlines = vec![Inline::RawInline(RawInline { + let mut inlines = vec![Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: script, source_info: source_info.clone(), - })]; + }))]; inlines.extend(peek_inlines_fuzzy(lua, content)?); - return lua.create_userdata(LuaInline::new(Inline::Span(Span { + return lua.create_userdata(LuaInline::new(Inline::Span(Box::new(Span { attr, content: inlines, source_info, attr_source: AttrSourceInfo::empty(), - }))); + })))); } let mut blocks = vec![Block::RawBlock(RawBlock { format: "html".to_string(), diff --git a/crates/pampa/src/lua/types.rs b/crates/pampa/src/lua/types.rs index c788bab7b1..07efa8ce87 100644 --- a/crates/pampa/src/lua/types.rs +++ b/crates/pampa/src/lua/types.rs @@ -3476,11 +3476,11 @@ impl LuaCitation { /// the comparison). fn structurally_eq(&self, other: &LuaCitation) -> bool { let wrap = |c: &LuaCitation| { - Inline::Cite(crate::pandoc::Cite { + Inline::Cite(Box::new(crate::pandoc::Cite { citations: vec![c.cell.borrow().clone()], content: vec![], source_info: SourceInfo::generated(By::unknown()), - }) + })) }; inline_structurally_eq(&wrap(self), &wrap(other)) } @@ -3786,32 +3786,32 @@ mod tests { #[test] fn test_lua_inline_tag_name_quoted() { - let inline = Inline::Quoted(Quoted { + let inline = Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::SingleQuote, content: vec![], source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Quoted"); } #[test] fn test_lua_inline_tag_name_cite() { - let inline = Inline::Cite(Cite { + let inline = Inline::Cite(Box::new(Cite { citations: vec![], content: vec![], source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Cite"); } #[test] fn test_lua_inline_tag_name_code() { - let inline = Inline::Code(Code { + let inline = Inline::Code(Box::new(Code { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), text: "code".into(), source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Code"); } @@ -3835,48 +3835,48 @@ mod tests { #[test] fn test_lua_inline_tag_name_math() { - let inline = Inline::Math(Math { + let inline = Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text: "x^2".into(), source_info: si(), text_source: None, - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Math"); } #[test] fn test_lua_inline_tag_name_raw_inline() { - let inline = Inline::RawInline(RawInline { + let inline = Inline::RawInline(Box::new(RawInline { format: "html".into(), text: "".into(), source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "RawInline"); } #[test] fn test_lua_inline_tag_name_link() { - let inline = Inline::Link(Link { + let inline = Inline::Link(Box::new(Link { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], target: ("url".into(), "title".into()), target_source: target_si(), source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Link"); } #[test] fn test_lua_inline_tag_name_image() { - let inline = Inline::Image(Image { + let inline = Inline::Image(Box::new(Image { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], target: ("src".into(), "alt".into()), target_source: target_si(), source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Image"); } @@ -3891,24 +3891,24 @@ mod tests { #[test] fn test_lua_inline_tag_name_span() { - let inline = Inline::Span(Span { + let inline = Inline::Span(Box::new(Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Span"); } #[test] fn test_lua_inline_tag_name_shortcode() { - let inline = Inline::Shortcode(Shortcode { + let inline = Inline::Shortcode(Box::new(Shortcode { is_escaped: false, name: "test".into(), positional_args: vec![], keyword_args: hashlink::LinkedHashMap::new(), source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Shortcode"); } @@ -3923,65 +3923,65 @@ mod tests { #[test] fn test_lua_inline_tag_name_attr() { - let inline = Inline::Attr(crate::pandoc::inline::InlineAttr::new( + let inline = Inline::Attr(Box::new(crate::pandoc::inline::InlineAttr::new( (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_si(), quarto_source_map::SourceInfo::for_test(), - )); + ))); assert_eq!(LuaInline::new(inline).tag_name(), "Attr"); } #[test] fn test_lua_inline_tag_name_insert() { - let inline = Inline::Insert(Insert { + let inline = Inline::Insert(Box::new(Insert { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Insert"); } #[test] fn test_lua_inline_tag_name_delete() { - let inline = Inline::Delete(Delete { + let inline = Inline::Delete(Box::new(Delete { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Delete"); } #[test] fn test_lua_inline_tag_name_highlight() { - let inline = Inline::Highlight(Highlight { + let inline = Inline::Highlight(Box::new(Highlight { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "Highlight"); } #[test] fn test_lua_inline_tag_name_edit_comment() { - let inline = Inline::EditComment(EditComment { + let inline = Inline::EditComment(Box::new(EditComment { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaInline::new(inline).tag_name(), "EditComment"); } #[test] fn test_lua_inline_tag_name_custom() { - let inline = Inline::Custom(crate::pandoc::custom::CustomNode::new( + let inline = Inline::Custom(Box::new(crate::pandoc::custom::CustomNode::new( "test-type", (String::new(), vec![], hashlink::LinkedHashMap::new()), si(), - )); + ))); assert_eq!(LuaInline::new(inline).tag_name(), "Custom"); } @@ -4013,11 +4013,11 @@ mod tests { #[test] fn test_lua_inline_field_names_quoted() { - let inline = Inline::Quoted(Quoted { + let inline = Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::DoubleQuote, content: vec![], source_info: si(), - }); + })); assert_eq!( LuaInline::new(inline).field_names(), &["tag", "quotetype", "content", "clone", "walk"] @@ -4026,11 +4026,11 @@ mod tests { #[test] fn test_lua_inline_field_names_cite() { - let inline = Inline::Cite(Cite { + let inline = Inline::Cite(Box::new(Cite { citations: vec![], content: vec![], source_info: si(), - }); + })); assert_eq!( LuaInline::new(inline).field_names(), &["tag", "content", "citations", "clone", "walk"] @@ -4039,12 +4039,12 @@ mod tests { #[test] fn test_lua_inline_field_names_code() { - let inline = Inline::Code(Code { + let inline = Inline::Code(Box::new(Code { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), text: "code".into(), source_info: si(), - }); + })); assert_eq!( LuaInline::new(inline).field_names(), &[ @@ -4071,12 +4071,12 @@ mod tests { #[test] fn test_lua_inline_field_names_math() { - let inline = Inline::Math(Math { + let inline = Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: "E=mc^2".into(), source_info: si(), text_source: None, - }); + })); assert_eq!( LuaInline::new(inline).field_names(), &["tag", "mathtype", "text", "clone", "walk"] @@ -4085,11 +4085,11 @@ mod tests { #[test] fn test_lua_inline_field_names_raw_inline() { - let inline = Inline::RawInline(RawInline { + let inline = Inline::RawInline(Box::new(RawInline { format: "latex".into(), text: "\\alpha".into(), source_info: si(), - }); + })); assert_eq!( LuaInline::new(inline).field_names(), &["tag", "format", "text", "clone", "walk"] @@ -4098,14 +4098,14 @@ mod tests { #[test] fn test_lua_inline_field_names_link() { - let inline = Inline::Link(Link { + let inline = Inline::Link(Box::new(Link { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], target: ("url".into(), "title".into()), target_source: target_si(), source_info: si(), - }); + })); assert_eq!( LuaInline::new(inline).field_names(), &[ @@ -4125,14 +4125,14 @@ mod tests { #[test] fn test_lua_inline_field_names_image() { - let inline = Inline::Image(Image { + let inline = Inline::Image(Box::new(Image { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], target: ("src".into(), "alt".into()), target_source: target_si(), source_info: si(), - }); + })); assert_eq!( LuaInline::new(inline).field_names(), &[ @@ -4165,12 +4165,12 @@ mod tests { #[test] fn test_lua_inline_field_names_span() { - let inline = Inline::Span(Span { + let inline = Inline::Span(Box::new(Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!( LuaInline::new(inline).field_names(), &[ @@ -4200,12 +4200,12 @@ mod tests { #[test] fn test_lua_inline_field_names_insert() { - let inline = Inline::Insert(Insert { + let inline = Inline::Insert(Box::new(Insert { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!( LuaInline::new(inline).field_names(), &[ @@ -4223,11 +4223,11 @@ mod tests { #[test] fn test_lua_inline_field_names_custom() { - let inline = Inline::Custom(crate::pandoc::custom::CustomNode::new( + let inline = Inline::Custom(Box::new(crate::pandoc::custom::CustomNode::new( "test-type", (String::new(), vec![], hashlink::LinkedHashMap::new()), si(), - )); + ))); assert_eq!(LuaInline::new(inline).field_names(), &["tag", "clone"]); } diff --git a/crates/pampa/src/lua/walk.rs b/crates/pampa/src/lua/walk.rs index 08721e5e56..be1a4013f2 100644 --- a/crates/pampa/src/lua/walk.rs +++ b/crates/pampa/src/lua/walk.rs @@ -151,53 +151,53 @@ pub(crate) async fn walk_inline_children(w: &W, inline: Inline) -> content: w.walk_inlines(s.content).await?, ..s }), - Inline::Quoted(q) => Inline::Quoted(pandoc::Quoted { + Inline::Quoted(q) => Inline::Quoted(Box::new(pandoc::Quoted { content: w.walk_inlines(q.content).await?, - ..q - }), + ..*q + })), Inline::Cite(c) => { let mut citations = Vec::with_capacity(c.citations.len()); for citation in c.citations { citations.push(walk_citation(w, citation).await?); } - Inline::Cite(pandoc::Cite { + Inline::Cite(Box::new(pandoc::Cite { citations, content: w.walk_inlines(c.content).await?, - ..c - }) + ..*c + })) } - Inline::Link(l) => Inline::Link(pandoc::Link { + Inline::Link(l) => Inline::Link(Box::new(pandoc::Link { content: w.walk_inlines(l.content).await?, - ..l - }), - Inline::Image(i) => Inline::Image(pandoc::Image { + ..*l + })), + Inline::Image(i) => Inline::Image(Box::new(pandoc::Image { content: w.walk_inlines(i.content).await?, - ..i - }), + ..*i + })), Inline::Note(n) => Inline::Note(pandoc::Note { content: w.walk_blocks(n.content).await?, ..n }), - Inline::Span(s) => Inline::Span(pandoc::Span { + Inline::Span(s) => Inline::Span(Box::new(pandoc::Span { content: w.walk_inlines(s.content).await?, - ..s - }), - Inline::Insert(i) => Inline::Insert(pandoc::Insert { + ..*s + })), + Inline::Insert(i) => Inline::Insert(Box::new(pandoc::Insert { content: w.walk_inlines(i.content).await?, - ..i - }), - Inline::Delete(d) => Inline::Delete(pandoc::Delete { + ..*i + })), + Inline::Delete(d) => Inline::Delete(Box::new(pandoc::Delete { content: w.walk_inlines(d.content).await?, - ..d - }), - Inline::Highlight(h) => Inline::Highlight(pandoc::Highlight { + ..*d + })), + Inline::Highlight(h) => Inline::Highlight(Box::new(pandoc::Highlight { content: w.walk_inlines(h.content).await?, - ..h - }), - Inline::EditComment(e) => Inline::EditComment(pandoc::EditComment { + ..*h + })), + Inline::EditComment(e) => Inline::EditComment(Box::new(pandoc::EditComment { content: w.walk_inlines(e.content).await?, - ..e - }), + ..*e + })), // Terminal inlines (no walkable children) Inline::Str(_) | Inline::Code(_) diff --git a/crates/pampa/src/pandoc/desugar.rs b/crates/pampa/src/pandoc/desugar.rs index 575a94a6fa..500a8af4b7 100644 --- a/crates/pampa/src/pandoc/desugar.rs +++ b/crates/pampa/src/pandoc/desugar.rs @@ -239,7 +239,7 @@ mod tests { fn image_para(alt: &str, id: &str) -> Block { Block::Paragraph(Paragraph { - content: vec![Inline::Image(Image { + content: vec![Inline::Image(Box::new(Image { attr: ( id.to_string(), vec!["cls".to_string()], @@ -257,7 +257,7 @@ mod tests { source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: empty_source_info(), }) } diff --git a/crates/pampa/src/pandoc/meta.rs b/crates/pampa/src/pandoc/meta.rs index fd85dd207f..5034db007f 100644 --- a/crates/pampa/src/pandoc/meta.rs +++ b/crates/pampa/src/pandoc/meta.rs @@ -221,7 +221,7 @@ fn parse_yaml_string_as_markdown_to_config( source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), }; - ConfigValueKind::PandocInlines(vec![Inline::Span(span)]) + ConfigValueKind::PandocInlines(vec![Inline::Span(Box::new(span))]) } } } @@ -403,7 +403,7 @@ fn yaml_to_config_value_at( source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), }; - ConfigValueKind::PandocInlines(vec![Inline::Span(span)]) + ConfigValueKind::PandocInlines(vec![Inline::Span(Box::new(span))]) } else if let Some(annotated) = super::meta_annotations::annotated_interpretation(path) { diff --git a/crates/pampa/src/pandoc/shortcode.rs b/crates/pampa/src/pandoc/shortcode.rs index 8f380044e9..8a43f086fb 100644 --- a/crates/pampa/src/pandoc/shortcode.rs +++ b/crates/pampa/src/pandoc/shortcode.rs @@ -17,7 +17,7 @@ fn shortcode_value_span(str: String) -> Inline { attr_hash.insert("data-value".to_string(), str); attr_hash.insert("data-is-shortcode".to_string(), "1".to_string()); - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: ( String::new(), vec!["quarto-shortcode__-param".to_string()], @@ -26,7 +26,7 @@ fn shortcode_value_span(str: String) -> Inline { content: vec![], source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Key-value param whose value is itself a shortcode. Q1 convention @@ -39,16 +39,16 @@ fn shortcode_key_recursive_value_span(key: String, value: Shortcode) -> Inline { attr_hash.insert("data-key".to_string(), key); attr_hash.insert("data-is-shortcode".to_string(), "1".to_string()); - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: ( String::new(), vec!["quarto-shortcode__-param".to_string()], attr_hash, ), - content: vec![Inline::Span(shortcode_to_span(value))], + content: vec![Inline::Span(Box::new(shortcode_to_span(value)))], source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn shortcode_key_value_span(key: String, value: String) -> Inline { @@ -63,7 +63,7 @@ fn shortcode_key_value_span(key: String, value: String) -> Inline { attr_hash.insert("data-value".to_string(), value); attr_hash.insert("data-is-shortcode".to_string(), "1".to_string()); - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: ( String::new(), vec!["quarto-shortcode__-param".to_string()], @@ -72,7 +72,7 @@ fn shortcode_key_value_span(key: String, value: String) -> Inline { content: vec![], source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } pub fn shortcode_to_span(shortcode: Shortcode) -> Span { @@ -94,7 +94,7 @@ pub fn shortcode_to_span(shortcode: Shortcode) -> Span { })); } ShortcodeArg::Shortcode(inner_shortcode) => { - content.push(Inline::Span(shortcode_to_span(inner_shortcode))); + content.push(Inline::Span(Box::new(shortcode_to_span(inner_shortcode)))); } ShortcodeArg::KeyValue(spec) => { for (key, value) in spec { diff --git a/crates/pampa/src/pandoc/treesitter.rs b/crates/pampa/src/pandoc/treesitter.rs index 19a07fd5fd..344a4b9ff6 100644 --- a/crates/pampa/src/pandoc/treesitter.rs +++ b/crates/pampa/src/pandoc/treesitter.rs @@ -670,7 +670,7 @@ fn process_native_inline( // Multiple inlines need to be wrapped in a Span // This shouldn't normally happen in practice, but handle it gracefully use crate::pandoc::attr::{AttrSourceInfo, empty_attr}; - Inline::Span(crate::pandoc::inline::Span { + Inline::Span(Box::new(crate::pandoc::inline::Span { attr: empty_attr(), attr_source: AttrSourceInfo { id: None, @@ -679,7 +679,7 @@ fn process_native_inline( }, content: inlines, source_info: node_source_info_fn(), - }) + })) } } PandocNativeIntermediate::IntermediateBaseText(text, range) => { @@ -705,7 +705,7 @@ fn process_native_inline( // see tests/cursed/002.qmd for why this cannot be parsed directly in // the block grammar. PandocNativeIntermediate::IntermediateAttr(attr, attr_source, source_info) => { - Inline::Attr(InlineAttr::new(attr, attr_source, source_info)) + Inline::Attr(Box::new(InlineAttr::new(attr, attr_source, source_info))) } PandocNativeIntermediate::IntermediateUnknown(range) => { writeln!( @@ -716,11 +716,11 @@ fn process_native_inline( node_name ) .unwrap(); - Inline::RawInline(RawInline { + Inline::RawInline(Box::new(RawInline { format: "quarto-internal-leftover".to_string(), text: node_text_fn(), source_info: node_source_info_fn(), - }) + })) } other => { writeln!( @@ -729,11 +729,11 @@ fn process_native_inline( other ) .unwrap(); - Inline::RawInline(RawInline { + Inline::RawInline(Box::new(RawInline { format: "quarto-internal-leftover".to_string(), text: node_text_fn(), source_info: node_source_info_fn(), - }) + })) } } } @@ -837,12 +837,12 @@ fn native_visitor( // collapse each pandoc_soft_break range to a single `\n`. let (text, text_source) = extract_inline_math_text(node, input_bytes, context); - PandocNativeIntermediate::IntermediateInline(Inline::Math(Math { + PandocNativeIntermediate::IntermediateInline(Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text, source_info: node_source_info_with_context(node, context), text_source: Some(text_source), - })) + }))) } "pandoc_display_math" => { // Extract display math content (text between $$ delimiters) @@ -866,12 +866,12 @@ fn native_visitor( let mut builder = provenance_builder_with_context(context, content_start); let text = strip_continuation_prefix(content, content_start, start_col, &mut builder); - PandocNativeIntermediate::IntermediateInline(Inline::Math(Math { + PandocNativeIntermediate::IntermediateInline(Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text, source_info: node_source_info_with_context(node, context), text_source: Some(builder.finish()), - })) + }))) } "pandoc_str" => { // Tree-sitter may include leading ASCII whitespace in the @@ -1688,11 +1688,11 @@ fn native_visitor( // maintain correct spacing (e.g., "Hello world" // — the space before ) are preserved as RawInline(html) diff --git a/crates/pampa/src/pandoc/treesitter_utils/atx_heading.rs b/crates/pampa/src/pandoc/treesitter_utils/atx_heading.rs index 98b2fd49a4..50bfe61e7e 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/atx_heading.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/atx_heading.rs @@ -80,11 +80,11 @@ pub fn process_atx_heading( .count(); content.drain(..leading); - PandocNativeIntermediate::IntermediateBlock(Block::Header(Header { + PandocNativeIntermediate::IntermediateBlock(Block::Header(Box::new(Header { level, attr, content, source_info: node_source_info_with_context(node, context), attr_source, - })) + }))) } diff --git a/crates/pampa/src/pandoc/treesitter_utils/document.rs b/crates/pampa/src/pandoc/treesitter_utils/document.rs index e22b2894d1..aa710a01e5 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/document.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/document.rs @@ -27,7 +27,7 @@ pub fn process_document( } PandocNativeIntermediate::IntermediateMetadataString(text, range) => { // for now we assume it's metadata and emit it as a rawblock - blocks.push(Block::RawBlock(RawBlock { + blocks.push(Block::RawBlock(Box::new(RawBlock { format: "quarto_minus_metadata".to_string(), text, // Reroot through `parent_source_info` when this parse is @@ -37,7 +37,7 @@ pub fn process_document( // the converted buffer instead of the true original // file. source_info: range_to_source_info_with_context(&range, context), - })); + }))); } PandocNativeIntermediate::IntermediateUnknown(_) => { // Skip unknown nodes - these occur when tree-sitter encounters parse errors diff --git a/crates/pampa/src/pandoc/treesitter_utils/editorial_div.rs b/crates/pampa/src/pandoc/treesitter_utils/editorial_div.rs index a1f917042f..714d72b5ca 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/editorial_div.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/editorial_div.rs @@ -90,10 +90,10 @@ pub fn process_editorial_div( attr.1 = classes; attr_source.classes = class_sources; - PandocNativeIntermediate::IntermediateBlock(Block::Div(Div { + PandocNativeIntermediate::IntermediateBlock(Block::Div(Box::new(Div { attr, content, source_info: node_source_info_with_context(node, context), attr_source, - })) + }))) } diff --git a/crates/pampa/src/pandoc/treesitter_utils/fenced_code_block.rs b/crates/pampa/src/pandoc/treesitter_utils/fenced_code_block.rs index 8f27c9c40b..f3f6d41f9d 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/fenced_code_block.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/fenced_code_block.rs @@ -72,17 +72,17 @@ pub fn process_fenced_code_block( } if let Some(format) = raw_format { - PandocNativeIntermediate::IntermediateBlock(Block::RawBlock(RawBlock { + PandocNativeIntermediate::IntermediateBlock(Block::RawBlock(Box::new(RawBlock { format, text: content, source_info: location, - })) + }))) } else { - PandocNativeIntermediate::IntermediateBlock(Block::CodeBlock(CodeBlock { + PandocNativeIntermediate::IntermediateBlock(Block::CodeBlock(Box::new(CodeBlock { attr, text: content, source_info: location, attr_source, - })) + }))) } } diff --git a/crates/pampa/src/pandoc/treesitter_utils/fenced_div_block.rs b/crates/pampa/src/pandoc/treesitter_utils/fenced_div_block.rs index bcb5f73c48..60cf0d21cc 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/fenced_div_block.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/fenced_div_block.rs @@ -39,7 +39,7 @@ pub fn process_fenced_div_block( } PandocNativeIntermediate::IntermediateMetadataString(text, range) => { // for now we assume it's metadata and emit it as a rawblock - content.push(Block::RawBlock(RawBlock { + content.push(Block::RawBlock(Box::new(RawBlock { format: "quarto_minus_metadata".to_string(), text, // Was hardcoded to `FileId(0)` — wrong even outside a @@ -49,17 +49,17 @@ pub fn process_fenced_div_block( // `document.rs`'s identical fix for the top-level // metadata block. source_info: range_to_source_info_with_context(&range, context), - })); + }))); } _ => { // Skip unexpected intermediates (shouldn't happen in practice) } } } - PandocNativeIntermediate::IntermediateBlock(Block::Div(Div { + PandocNativeIntermediate::IntermediateBlock(Block::Div(Box::new(Div { attr, content, source_info: node_source_info_with_context(node, context), attr_source, - })) + }))) } diff --git a/crates/pampa/src/pandoc/treesitter_utils/note_definition_fenced_block.rs b/crates/pampa/src/pandoc/treesitter_utils/note_definition_fenced_block.rs index f3653011d6..73380dde30 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/note_definition_fenced_block.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/note_definition_fenced_block.rs @@ -45,11 +45,11 @@ pub fn process_note_definition_fenced_block( } } - PandocNativeIntermediate::IntermediateBlock(Block::NoteDefinitionFencedBlock( + PandocNativeIntermediate::IntermediateBlock(Block::NoteDefinitionFencedBlock(Box::new( NoteDefinitionFencedBlock { id, content, source_info: node_source_info_with_context(node, context), }, - )) + ))) } diff --git a/crates/pampa/src/pandoc/treesitter_utils/note_definition_para.rs b/crates/pampa/src/pandoc/treesitter_utils/note_definition_para.rs index 04fd214727..1ccf50e547 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/note_definition_para.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/note_definition_para.rs @@ -42,9 +42,11 @@ pub fn process_note_definition_para( } } - PandocNativeIntermediate::IntermediateBlock(Block::NoteDefinitionPara(NoteDefinitionPara { - id, - content, - source_info: node_source_info_with_context(node, context), - })) + PandocNativeIntermediate::IntermediateBlock(Block::NoteDefinitionPara(Box::new( + NoteDefinitionPara { + id, + content, + source_info: node_source_info_with_context(node, context), + }, + ))) } diff --git a/crates/pampa/src/pandoc/treesitter_utils/paragraph.rs b/crates/pampa/src/pandoc/treesitter_utils/paragraph.rs index 255228201d..e9bca0d8bc 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/paragraph.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/paragraph.rs @@ -61,11 +61,13 @@ pub fn process_paragraph( } } - return PandocNativeIntermediate::IntermediateBlock(Block::RawBlock(RawBlock { - format: "html".to_string(), - text, - source_info, - })); + return PandocNativeIntermediate::IntermediateBlock(Block::RawBlock(Box::new( + RawBlock { + format: "html".to_string(), + text, + source_info, + }, + ))); } } @@ -262,11 +264,11 @@ fn split_html_block_runs(inlines: &[Inline], paragraph_si: &SourceInfo) -> Vec>() .join("\n"); let source_info = span_of(run.iter().map(|i| i.source_info()), fallback); - out.push(Block::RawBlock(RawBlock { + out.push(Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text, source_info, - })); + }))); run.clear(); } diff --git a/crates/pampa/src/pandoc/treesitter_utils/pipe_table.rs b/crates/pampa/src/pandoc/treesitter_utils/pipe_table.rs index 8aaf698b5a..1100e9b2a4 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/pipe_table.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/pipe_table.rs @@ -302,7 +302,7 @@ pub fn process_pipe_table( node_source_info_with_context(node, context) }; - PandocNativeIntermediate::IntermediateBlock(Block::Table(Table { + PandocNativeIntermediate::IntermediateBlock(Block::Table(Box::new(Table { attr, caption, colspec, @@ -328,5 +328,5 @@ pub fn process_pipe_table( }, source_info: table_source_info, attr_source, - })) + }))) } diff --git a/crates/pampa/src/pandoc/treesitter_utils/postprocess.rs b/crates/pampa/src/pandoc/treesitter_utils/postprocess.rs index 30e71e87e7..d8ef40152c 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/postprocess.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/postprocess.rs @@ -605,7 +605,7 @@ pub(crate) fn transform_list_table_div(div: Div) -> Block { attr_source: empty_attr_source(), }; - Block::Table(Table { + Block::Table(Box::new(Table { attr: table_attr, caption, colspec, @@ -614,7 +614,7 @@ pub(crate) fn transform_list_table_div(div: Div) -> Block { foot, source_info, attr_source: table_attr_source, - }) + })) } /// Pop a trailing `Inline::Attr` off a table caption's inlines and merge it @@ -1163,12 +1163,12 @@ pub fn postprocess( attr.0 = final_id; if !is_empty_attr(&attr) || trailing_lb_converted { header.attr = attr; - FilterResult(vec![Block::Header(header)], true) + FilterResult(vec![Block::Header(Box::new(header))], true) } else { Unchanged(header) } } else if trailing_lb_converted { - FilterResult(vec![Block::Header(header)], true) + FilterResult(vec![Block::Header(Box::new(header))], true) } else { Unchanged(header) } @@ -1179,7 +1179,7 @@ pub fn postprocess( header.attr = inline_attr.attr; header.attr_source = inline_attr.attr_source; header.content = trim_inlines(header.content).0; - FilterResult(vec![Block::Header(header)], true) + FilterResult(vec![Block::Header(Box::new(header))], true) } }) // attempt to desugar single-image paragraphs into figures @@ -1192,7 +1192,7 @@ pub fn postprocess( // Item 3 of the structural desugaring set (`pandoc::desugar`): // a single-image paragraph with alt text is a figure. if let Some(figure) = implicit_figure(¶) { - return FilterResult(vec![Block::Figure(figure)], true); + return FilterResult(vec![Block::Figure(Box::new(figure))], true); } // Not a figure conversion case, but may have converted trailing LineBreak @@ -1220,7 +1220,7 @@ pub fn postprocess( // itself (see `desugar_div`). .with_block(|block, _ctx| match block { Block::Div(div) => { - Unchanged(desugar_div(div, &mut error_collector_ref.borrow_mut())) + Unchanged(desugar_div(*div, &mut error_collector_ref.borrow_mut())) } other => Unchanged(other), }) @@ -1490,7 +1490,7 @@ pub fn postprocess( }; // Return the transformed table - FilterResult(vec![Block::Table(table)], false) + FilterResult(vec![Block::Table(Box::new(table))], false) }) // NOTE: We intentionally do NOT convert shortcodes to spans here. // Shortcodes remain as Inline::Shortcode so that ShortcodeResolveTransform diff --git a/crates/pampa/src/pandoc/treesitter_utils/section.rs b/crates/pampa/src/pandoc/treesitter_utils/section.rs index 14624959fd..955a3a2255 100644 --- a/crates/pampa/src/pandoc/treesitter_utils/section.rs +++ b/crates/pampa/src/pandoc/treesitter_utils/section.rs @@ -30,7 +30,7 @@ pub fn process_section( } PandocNativeIntermediate::IntermediateMetadataString(text, range) => { // for now we assume it's metadata and emit it as a rawblock - blocks.push(Block::RawBlock(RawBlock { + blocks.push(Block::RawBlock(Box::new(RawBlock { format: "quarto_minus_metadata".to_string(), text, // Reroot through `parent_source_info` when this parse is @@ -38,7 +38,7 @@ pub fn process_section( // converted buffer, Plan 7b) — see `document.rs`'s // identical fix for the top-level metadata block. source_info: range_to_source_info_with_context(&range, context), - })); + }))); } _ => panic!("Expected Block or Section, got {:?}", child), } diff --git a/crates/pampa/src/readers/json.rs b/crates/pampa/src/readers/json.rs index 4a5e4eda15..5c4b602028 100644 --- a/crates/pampa/src/readers/json.rs +++ b/crates/pampa/src/readers/json.rs @@ -2291,12 +2291,12 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result { let c = obj @@ -2322,11 +2322,11 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result { let c = obj @@ -2353,11 +2353,11 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result { let c = obj @@ -2416,13 +2416,13 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result Ok(Block::HorizontalRule(HorizontalRule { source_info })), "Figure" => { @@ -2444,13 +2444,13 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result { let c = obj @@ -2494,7 +2494,7 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result>>()?; let foot = read_table_foot(&arr[5], deserializer, foot_source)?; let attr_source = read_attr_source(obj.get("a"), deserializer)?; - Ok(Block::Table(Table { + Ok(Block::Table(Box::new(Table { attr, caption, colspec, @@ -2503,7 +2503,7 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result { let c = obj @@ -2526,12 +2526,12 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result { let c = obj @@ -2539,7 +2539,10 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result { let c = obj @@ -2560,13 +2563,13 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result { let c = obj @@ -2591,13 +2594,13 @@ fn read_block(value: &Value, deserializer: &SourceInfoDeserializer) -> Result( // Lexical metadata - return as BlockMetadata // ConfigValue is already fully processed (strings parsed as markdown) FilterReturn::FilterResult( - vec![Block::BlockMetadata(MetaBlock { + vec![Block::BlockMetadata(Box::new(MetaBlock { meta: config_value, source_info: rb.source_info.clone(), - })], + }))], false, ) } else { diff --git a/crates/pampa/src/toc.rs b/crates/pampa/src/toc.rs index 6141177075..dac28513d5 100644 --- a/crates/pampa/src/toc.rs +++ b/crates/pampa/src/toc.rs @@ -568,7 +568,7 @@ mod tests { } fn make_header(level: usize, id: &str, classes: Vec<&str>, text: &str) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: ( id.to_string(), @@ -581,7 +581,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn make_section( @@ -594,7 +594,7 @@ mod tests { let mut section_classes = vec!["section".to_string(), format!("level{}", level)]; section_classes.extend(classes.iter().map(|s| s.to_string())); - let header = Block::Header(Header { + let header = Block::Header(Box::new(Header { level, attr: ( String::new(), @@ -607,17 +607,17 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }); + })); let mut section_content = vec![header]; section_content.extend(content); - Block::Div(Div { + Block::Div(Box::new(Div { attr: (id.to_string(), section_classes, LinkedHashMap::new()), content: section_content, source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn make_para(text: &str) -> Block { @@ -755,7 +755,7 @@ mod tests { } fn make_plain_div(id: &str, classes: Vec<&str>, content: Vec) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( id.to_string(), classes.iter().map(|s| s.to_string()).collect(), @@ -764,7 +764,7 @@ mod tests { content, source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } #[test] @@ -1139,7 +1139,7 @@ mod tests { })), ]; - let blocks = vec![Block::Header(Header { + let blocks = vec![Block::Header(Box::new(Header { level: 2, attr: ( "using-a-raw-volume".to_string(), @@ -1149,7 +1149,7 @@ mod tests { content: header_content.clone(), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; let toc = generate_toc(&blocks, &TocConfig::default()); diff --git a/crates/pampa/src/transforms/fragment.rs b/crates/pampa/src/transforms/fragment.rs index 0e7bcc6fe5..2b7a24c762 100644 --- a/crates/pampa/src/transforms/fragment.rs +++ b/crates/pampa/src/transforms/fragment.rs @@ -172,7 +172,7 @@ pub fn strip_derived_heading_ids(blocks: Blocks) -> Blocks { if header.attr_source.id.is_none() && !header.attr.0.is_empty() { header.attr.0.clear(); // A heading holds no headings; nothing below needs this pass. - return FilterReturn::FilterResult(vec![Block::Header(header)], false); + return FilterReturn::FilterResult(vec![Block::Header(Box::new(header))], false); } FilterReturn::Unchanged(header) }), diff --git a/crates/pampa/src/transforms/sectionize.rs b/crates/pampa/src/transforms/sectionize.rs index 3eb5ae1d38..7e42e1f5d2 100644 --- a/crates/pampa/src/transforms/sectionize.rs +++ b/crates/pampa/src/transforms/sectionize.rs @@ -94,12 +94,12 @@ pub fn sectionize_blocks(blocks: Vec) -> Vec { while let Some((stack_level, _, _)) = section_stack.last() { if *stack_level >= level { let (_, section_attr, section_content) = section_stack.pop().unwrap(); - let section_div = Block::Div(Div { + let section_div = Block::Div(Box::new(Div { attr: section_attr, content: section_content, source_info: SourceInfo::generated(By::sectionize()), attr_source: AttrSourceInfo::empty(), - }); + })); // Add closed section to parent, or output if no parent if let Some((_, _, parent_content)) = section_stack.last_mut() { parent_content.push(section_div); @@ -124,13 +124,13 @@ pub fn sectionize_blocks(blocks: Vec) -> Vec { ); // Create header with ID removed but classes and attributes preserved - let header_without_id = Block::Header(Header { + let header_without_id = Block::Header(Box::new(Header { level, attr: (String::new(), classes.clone(), attrs.clone()), // Empty ID, keep classes and attrs content: header.content.clone(), source_info: header.source_info.clone(), attr_source: header.attr_source.clone(), - }); + })); // Push new section onto stack (with ID-less header as first content) section_stack.push((level, section_attr, vec![header_without_id])); @@ -146,12 +146,12 @@ pub fn sectionize_blocks(blocks: Vec) -> Vec { // Close all remaining open sections (innermost first) while let Some((_, section_attr, section_content)) = section_stack.pop() { - let section_div = Block::Div(Div { + let section_div = Block::Div(Box::new(Div { attr: section_attr, content: section_content, source_info: SourceInfo::generated(By::sectionize()), attr_source: AttrSourceInfo::empty(), - }); + })); if let Some((_, _, parent_content)) = section_stack.last_mut() { parent_content.push(section_div); } else { @@ -196,7 +196,7 @@ fn sectionize_div(block: Block) -> Block { return Block::Div(div); } div.content = sectionize_blocks(std::mem::take(&mut div.content)); - absorb_section_wrapper(div) + absorb_section_wrapper(*div) } /// Pandoc's absorb rule for a wrapper `Div` whose content is already @@ -228,12 +228,12 @@ pub fn absorb_section_wrapper(div: Div) -> Block { && content.len() == 1 && matches!(&content[0], Block::Div(inner) if is_section(&inner.attr.1)); if !absorbable { - return Block::Div(Div { + return Block::Div(Box::new(Div { attr, content, source_info, attr_source, - }); + })); } let Some(Block::Div(mut section)) = content.pop() else { @@ -273,7 +273,7 @@ mod tests { } fn make_header(level: usize, id: &str, classes: Vec<&str>, text: &str) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: ( id.to_string(), @@ -286,7 +286,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn make_header_with_attrs( @@ -300,7 +300,7 @@ mod tests { for (k, v) in attrs { attr_map.insert(k.to_string(), v.to_string()); } - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: ( id.to_string(), @@ -313,7 +313,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn make_para(text: &str) -> Block { @@ -693,7 +693,7 @@ mod tests { for (k, v) in attrs { attr_map.insert(k.to_string(), v.to_string()); } - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( id.to_string(), classes.iter().map(|s| s.to_string()).collect(), @@ -702,7 +702,7 @@ mod tests { content, source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Unwrap a `Block::Div`, or panic with a useful message. @@ -925,7 +925,7 @@ mod tests { make_para("Body."), ]), ); - let result = sectionize_blocks(vec![Block::Custom(node)]); + let result = sectionize_blocks(vec![Block::Custom(Box::new(node))]); let Block::Custom(node) = &result[0] else { panic!("custom node survives"); }; diff --git a/crates/pampa/src/writers/html.rs b/crates/pampa/src/writers/html.rs index d51abafc7a..10be843d98 100644 --- a/crates/pampa/src/writers/html.rs +++ b/crates/pampa/src/writers/html.rs @@ -2541,7 +2541,7 @@ mod tests { use quarto_pandoc_types::attr::AttrSourceInfo; let ctx = ASTContext::anonymous(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: ( "my-section".to_string(), vec!["section".to_string(), "level2".to_string()], @@ -2556,7 +2556,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }); + })); let pandoc = Pandoc { meta: ConfigValue::default(), blocks: vec![div], @@ -2605,7 +2605,7 @@ mod tests { use quarto_pandoc_types::attr::AttrSourceInfo; let ctx = ASTContext::anonymous(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: ( "my-div".to_string(), vec!["callout".to_string(), "warning".to_string()], @@ -2620,7 +2620,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }); + })); let pandoc = Pandoc { meta: ConfigValue::default(), blocks: vec![div], @@ -2657,12 +2657,12 @@ mod tests { use hashlink::LinkedHashMap; let mut kvs = LinkedHashMap::new(); kvs.insert("data-hl-spans".to_string(), spans_json.to_string()); - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![language.to_string()], kvs), text: text.to_string(), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn render_block_to_html(block: Block) -> String { @@ -2708,7 +2708,7 @@ mod tests { fn codeblock_without_hl_spans_falls_back_to_plain() { // No data-hl-spans attr: output looks like before this feature. use hashlink::LinkedHashMap; - let block = Block::CodeBlock(CodeBlock { + let block = Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), vec!["python".to_string()], @@ -2717,7 +2717,7 @@ mod tests { text: "def foo():".to_string(), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }); + })); let html = render_block_to_html(block); assert!(!html.contains("item"), @@ -3039,7 +3039,7 @@ mod tests { use hashlink::LinkedHashMap; let mut kvs = LinkedHashMap::new(); kvs.insert("data-hl-spans".to_string(), spans_json.to_string()); - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: ( id.to_string(), classes.into_iter().map(|s| s.to_string()).collect(), @@ -3048,7 +3048,7 @@ mod tests { text: text.to_string(), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } #[test] @@ -3170,7 +3170,7 @@ mod tests { fn unhighlighted_codeblock_has_no_div_wrapper() { // No data-hl-spans => no highlighting => no div.sourceCode wrapper. use hashlink::LinkedHashMap; - let block = Block::CodeBlock(CodeBlock { + let block = Block::CodeBlock(Box::new(CodeBlock { attr: ( "cb1".to_string(), vec!["python".to_string()], @@ -3179,7 +3179,7 @@ mod tests { text: "def foo()".to_string(), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }); + })); let html = render_block_to_html(block); assert!( !html.contains("
`. diff --git a/crates/pampa/src/writers/json.rs b/crates/pampa/src/writers/json.rs index 5d3cd7d4e2..e12eab1fc3 100644 --- a/crates/pampa/src/writers/json.rs +++ b/crates/pampa/src/writers/json.rs @@ -4838,7 +4838,7 @@ mod tests { source_info: SourceInfo::for_test(), }; - let block = Block::Custom(custom); + let block = Block::Custom(Box::new(custom)); // Create a minimal Pandoc document with this block let pandoc = crate::pandoc::Pandoc { @@ -5047,7 +5047,7 @@ mod tests { source_info: SourceInfo::for_test(), }; - let block = Block::Custom(custom); + let block = Block::Custom(Box::new(custom)); // Create a minimal Pandoc document let pandoc = crate::pandoc::Pandoc { @@ -5282,14 +5282,14 @@ mod tests { fn test_ordered_list_item_attr_emits_itemattr_key() { use crate::pandoc::{Block, ListNumberDelim, ListNumberStyle, OrderedList}; let foo: Attr = (String::new(), vec!["foo".to_string()], LinkedHashMap::new()); - let list = Block::OrderedList(OrderedList { + let list = Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![ vec![li_attr_plain("first", None)], vec![li_attr_plain("second", Some(foo))], ], source_info: SourceInfo::for_test(), - }); + })); let v = write_blocks_to_json(vec![list]); let node = &v["blocks"][0]; assert_eq!(node["t"], "OrderedList"); diff --git a/crates/pampa/src/writers/native.rs b/crates/pampa/src/writers/native.rs index b3f74dd025..d2d5271527 100644 --- a/crates/pampa/src/writers/native.rs +++ b/crates/pampa/src/writers/native.rs @@ -577,18 +577,20 @@ fn write_block( write!(buf, "Para ")?; write_inlines(content, context, buf, errors)?; } - Block::CodeBlock(crate::pandoc::CodeBlock { - attr, - text, - source_info: _, - attr_source: _, - }) => { + Block::CodeBlock(code_block) => { + let crate::pandoc::CodeBlock { + attr, + text, + source_info: _, + attr_source: _, + } = &**code_block; write!(buf, "CodeBlock ")?; write_native_attr(attr, buf)?; write!(buf, " ")?; write_safe_string(text, buf)?; } - Block::RawBlock(crate::pandoc::RawBlock { format, text, .. }) => { + Block::RawBlock(raw) => { + let crate::pandoc::RawBlock { format, text, .. } = &**raw; write!(buf, "RawBlock (Format ")?; write_safe_string(format, buf)?; write!(buf, ") ")?; @@ -611,7 +613,8 @@ fn write_block( } write!(buf, "]")?; } - Block::OrderedList(crate::pandoc::OrderedList { content, attr, .. }) => { + Block::OrderedList(ordered_list) => { + let crate::pandoc::OrderedList { content, attr, .. } = &**ordered_list; let (number, style, delim) = attr; write!(buf, "OrderedList ({}, ", number)?; write_list_number_style(style, buf)?; @@ -643,7 +646,8 @@ fn write_block( } write!(buf, "]")?; } - Block::Div(crate::pandoc::Div { attr, content, .. }) => { + Block::Div(div) => { + let crate::pandoc::Div { attr, content, .. } = &**div; write!(buf, "Div ")?; write_native_attr(attr, buf)?; write!(buf, " [")?; @@ -655,12 +659,13 @@ fn write_block( } write!(buf, "]")?; } - Block::Figure(crate::pandoc::Figure { - attr, - caption, - content, - .. - }) => { + Block::Figure(figure) => { + let crate::pandoc::Figure { + attr, + caption, + content, + .. + } = &**figure; write!(buf, "Figure ")?; write_native_attr(attr, buf)?; write!(buf, " ")?; @@ -674,12 +679,13 @@ fn write_block( } write!(buf, "]")?; } - Block::Header(crate::pandoc::Header { - level, - attr, - content, - .. - }) => { + Block::Header(header) => { + let crate::pandoc::Header { + level, + attr, + content, + .. + } = &**header; write!(buf, "Header {} ", level)?; write_native_attr(attr, buf)?; write!(buf, " ")?; @@ -688,15 +694,16 @@ fn write_block( Block::HorizontalRule(crate::pandoc::HorizontalRule { .. }) => { write!(buf, "HorizontalRule")? } - Block::Table(crate::pandoc::Table { - attr, - caption, - colspec, - head, - bodies, - foot, - .. - }) => { + Block::Table(table) => { + let crate::pandoc::Table { + attr, + caption, + colspec, + head, + bodies, + foot, + .. + } = &**table; write!(buf, "Table ")?; write_native_attr(attr, buf)?; write!(buf, " ")?; diff --git a/crates/pampa/src/writers/plaintext.rs b/crates/pampa/src/writers/plaintext.rs index cb513d5112..db5c620c76 100644 --- a/crates/pampa/src/writers/plaintext.rs +++ b/crates/pampa/src/writers/plaintext.rs @@ -219,12 +219,14 @@ fn write_block( } // Header: write inlines (no # prefix in plain text) - Block::Header(Header { content, .. }) => { + Block::Header(header) => { + let Header { content, .. } = &**header; write_inlines(content, buf, ctx)?; } // CodeBlock: mimic markdown (fenced with backticks) - Block::CodeBlock(CodeBlock { text, .. }) => { + Block::CodeBlock(code_block) => { + let CodeBlock { text, .. } = &**code_block; writeln!(buf, "```")?; write!(buf, "{}", text)?; if !text.ends_with('\n') { @@ -250,7 +252,8 @@ fn write_block( } // OrderedList: mimic markdown (1. 2. 3.) - Block::OrderedList(OrderedList { content, attr, .. }) => { + Block::OrderedList(ordered_list) => { + let OrderedList { content, attr, .. } = &**ordered_list; let start = attr.0; for (i, item) in content.iter().enumerate() { if i > 0 { @@ -278,21 +281,24 @@ fn write_block( } // Div: write contents only (no ::: scaffold) - Block::Div(Div { content, .. }) => { + Block::Div(div) => { + let Div { content, .. } = &**div; write_blocks(content, buf, ctx)?; } // Figure: write contents only - Block::Figure(Figure { content, .. }) => { + Block::Figure(figure) => { + let Figure { content, .. } = &**figure; write_blocks(content, buf, ctx)?; } // RawBlock: echo if 'plaintext', else drop with warning - Block::RawBlock(RawBlock { - format, - text, - source_info, - }) => { + Block::RawBlock(raw) => { + let RawBlock { + format, + text, + source_info, + } = &**raw; if format == "plaintext" { write!(buf, "{}", text)?; } else { @@ -306,18 +312,22 @@ fn write_block( } // Table: complex structure, drop with warning - Block::Table(Table { source_info, .. }) => { + Block::Table(table) => { + let Table { source_info, .. } = &**table; ctx.warn_dropped_node("Table", source_info); } // Quarto extensions: drop with warning - Block::BlockMetadata(MetaBlock { source_info, .. }) => { + Block::BlockMetadata(meta_block) => { + let MetaBlock { source_info, .. } = &**meta_block; ctx.warn_dropped_node("BlockMetadata", source_info); } - Block::NoteDefinitionPara(NoteDefinitionPara { source_info, .. }) => { + Block::NoteDefinitionPara(note_definition_para) => { + let NoteDefinitionPara { source_info, .. } = &**note_definition_para; ctx.warn_dropped_node("NoteDefinitionPara", source_info); } - Block::NoteDefinitionFencedBlock(NoteDefinitionFencedBlock { source_info, .. }) => { + Block::NoteDefinitionFencedBlock(note_def) => { + let NoteDefinitionFencedBlock { source_info, .. } = &**note_def; ctx.warn_dropped_node("NoteDefinitionFencedBlock", source_info); } Block::CaptionBlock(CaptionBlock { source_info, .. }) => { @@ -538,12 +548,12 @@ mod tests { #[test] fn test_code_block_fenced() { - let blocks = vec![Block::CodeBlock(CodeBlock { + let blocks = vec![Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), text: "let x = 1;".to_string(), source_info: dummy_source_info(), attr_source: crate::pandoc::attr::AttrSourceInfo::empty(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, "```\nlet x = 1;\n```"); assert!(diags.is_empty()); @@ -571,7 +581,7 @@ mod tests { #[test] fn test_table_dropped_with_warning() { - let blocks = vec![Block::Table(Table { + let blocks = vec![Block::Table(Box::new(Table { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), caption: crate::pandoc::caption::Caption { short: None, @@ -594,7 +604,7 @@ mod tests { }, source_info: dummy_source_info(), attr_source: crate::pandoc::attr::AttrSourceInfo::empty(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, ""); assert_eq!(diags.len(), 1); @@ -939,13 +949,13 @@ mod tests { #[test] fn test_header_block() { - let blocks = vec![Block::Header(Header { + let blocks = vec![Block::Header(Box::new(Header { level: 2, attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![make_str("Heading")], source_info: dummy_source_info(), attr_source: crate::pandoc::attr::AttrSourceInfo::empty(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); // No # prefix in plain text assert_eq!(result, "Heading"); @@ -1006,7 +1016,7 @@ mod tests { fn test_ordered_list() { use crate::pandoc::list::{ListNumberDelim, ListNumberStyle}; - let blocks = vec![Block::OrderedList(OrderedList { + let blocks = vec![Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![ vec![Block::Plain(Plain { @@ -1019,7 +1029,7 @@ mod tests { })], ], source_info: dummy_source_info(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, "1. First\n2. Second"); assert!(diags.is_empty()); @@ -1029,7 +1039,7 @@ mod tests { fn test_ordered_list_start_number() { use crate::pandoc::list::{ListNumberDelim, ListNumberStyle}; - let blocks = vec![Block::OrderedList(OrderedList { + let blocks = vec![Block::OrderedList(Box::new(OrderedList { attr: (5, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![ vec![Block::Plain(Plain { @@ -1042,7 +1052,7 @@ mod tests { })], ], source_info: dummy_source_info(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, "5. Fifth\n6. Sixth"); assert!(diags.is_empty()); @@ -1060,7 +1070,7 @@ mod tests { #[test] fn test_div_contents_only() { - let blocks = vec![Block::Div(Div { + let blocks = vec![Block::Div(Box::new(Div { attr: ( "myid".to_string(), vec!["myclass".to_string()], @@ -1072,7 +1082,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: crate::pandoc::attr::AttrSourceInfo::empty(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); // No ::: scaffold assert_eq!(result, "Div content"); @@ -1081,7 +1091,7 @@ mod tests { #[test] fn test_figure_contents_only() { - let blocks = vec![Block::Figure(Figure { + let blocks = vec![Block::Figure(Box::new(Figure { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), caption: crate::pandoc::caption::Caption { short: None, @@ -1094,7 +1104,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: crate::pandoc::attr::AttrSourceInfo::empty(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, "Figure content"); assert!(diags.is_empty()); @@ -1102,11 +1112,11 @@ mod tests { #[test] fn test_raw_block_plaintext() { - let blocks = vec![Block::RawBlock(RawBlock { + let blocks = vec![Block::RawBlock(Box::new(RawBlock { format: "plaintext".to_string(), text: "Raw plaintext content".to_string(), source_info: dummy_source_info(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, "Raw plaintext content"); assert!(diags.is_empty()); @@ -1114,11 +1124,11 @@ mod tests { #[test] fn test_raw_block_html_dropped_with_warning() { - let blocks = vec![Block::RawBlock(RawBlock { + let blocks = vec![Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: "
HTML content
".to_string(), source_info: dummy_source_info(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, ""); assert_eq!(diags.len(), 1); @@ -1148,10 +1158,10 @@ mod tests { fn test_block_metadata_dropped_with_warning() { use quarto_pandoc_types::config_value::ConfigValue; - let blocks = vec![Block::BlockMetadata(MetaBlock { + let blocks = vec![Block::BlockMetadata(Box::new(MetaBlock { meta: ConfigValue::null(dummy_source_info()), source_info: dummy_source_info(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, ""); assert_eq!(diags.len(), 1); @@ -1160,11 +1170,11 @@ mod tests { #[test] fn test_note_definition_para_dropped_with_warning() { - let blocks = vec![Block::NoteDefinitionPara(NoteDefinitionPara { + let blocks = vec![Block::NoteDefinitionPara(Box::new(NoteDefinitionPara { id: "fn1".to_string(), content: vec![make_str("Note content")], source_info: dummy_source_info(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, ""); assert_eq!(diags.len(), 1); @@ -1173,7 +1183,7 @@ mod tests { #[test] fn test_note_definition_fenced_block_dropped_with_warning() { - let blocks = vec![Block::NoteDefinitionFencedBlock( + let blocks = vec![Block::NoteDefinitionFencedBlock(Box::new( NoteDefinitionFencedBlock { id: "fn2".to_string(), content: vec![Block::Paragraph(Paragraph { @@ -1182,7 +1192,7 @@ mod tests { })], source_info: dummy_source_info(), }, - )]; + ))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, ""); assert_eq!(diags.len(), 1); @@ -1205,13 +1215,13 @@ mod tests { fn test_custom_block_with_warning() { use crate::pandoc::custom::CustomNode; - let blocks = vec![Block::Custom(CustomNode { + let blocks = vec![Block::Custom(Box::new(CustomNode { type_name: "TestCallout".to_string(), slots: hashlink::LinkedHashMap::new(), plain_data: serde_json::Value::Null, attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), source_info: dummy_source_info(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); assert_eq!(result, ""); assert_eq!(diags.len(), 1); @@ -1250,12 +1260,12 @@ mod tests { #[test] fn test_code_block_with_trailing_newline() { - let blocks = vec![Block::CodeBlock(CodeBlock { + let blocks = vec![Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), text: "let x = 1;\n".to_string(), source_info: dummy_source_info(), attr_source: crate::pandoc::attr::AttrSourceInfo::empty(), - })]; + }))]; let (result, diags) = blocks_to_string(&blocks); // Text already has trailing newline, so no extra added assert_eq!(result, "```\nlet x = 1;\n```"); diff --git a/crates/pampa/src/writers/qmd_prepass.rs b/crates/pampa/src/writers/qmd_prepass.rs index 773d5a178a..2197e235dd 100644 --- a/crates/pampa/src/writers/qmd_prepass.rs +++ b/crates/pampa/src/writers/qmd_prepass.rs @@ -477,11 +477,11 @@ fn separate_ordered_lists(blocks: &mut Vec) { && needs_list_separator(prev, &block) { let source_info = prev_list.source_info.clone(); - out.push(Block::RawBlock(RawBlock { + out.push(Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: LIST_SEPARATOR.to_string(), source_info, - })); + }))); } out.push(block); } @@ -507,7 +507,7 @@ fn rewrite(pandoc: &Pandoc) -> Pandoc { out.push(block); for mut definition in rewriter.definitions.drain(..) { definition.source_info = source_info.clone(); - out.push(Block::NoteDefinitionFencedBlock(definition)); + out.push(Block::NoteDefinitionFencedBlock(Box::new(definition))); } } separate_ordered_lists(&mut out); diff --git a/crates/pampa/tests/integration/list_looseness.rs b/crates/pampa/tests/integration/list_looseness.rs index 589fa5f3fd..95f7daa219 100644 --- a/crates/pampa/tests/integration/list_looseness.rs +++ b/crates/pampa/tests/integration/list_looseness.rs @@ -1418,11 +1418,11 @@ fn imported_same_delimiter_lists_stay_apart() { let mut expected = imported.clone(); container(&mut expected).insert( second, - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: "".to_string(), source_info, - }), + })), ); let (written, reread) = roundtrip_pandoc(&imported).expect("writes"); diff --git a/crates/pampa/tests/integration/qmd_writer_source_info.rs b/crates/pampa/tests/integration/qmd_writer_source_info.rs index 2afcd5b945..fd96ae21e8 100644 --- a/crates/pampa/tests/integration/qmd_writer_source_info.rs +++ b/crates/pampa/tests/integration/qmd_writer_source_info.rs @@ -29,22 +29,22 @@ fn paragraph(text: &str, source_info: SourceInfo) -> Block { } fn code_block(code: &str, source_info: SourceInfo) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: quarto_pandoc_types::attr::empty_attr(), text: code.to_string(), source_info, attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - }) + })) } fn header(text: &str, level: usize, source_info: SourceInfo) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: quarto_pandoc_types::attr::empty_attr(), content: vec![str_inline(text, source_info.clone())], source_info, attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - }) + })) } #[test] diff --git a/crates/pampa/tests/integration/test_ansi_writer.rs b/crates/pampa/tests/integration/test_ansi_writer.rs index 408ef6a2cb..0146af46a4 100644 --- a/crates/pampa/tests/integration/test_ansi_writer.rs +++ b/crates/pampa/tests/integration/test_ansi_writer.rs @@ -133,7 +133,7 @@ fn test_paragraph_spacing() { fn test_header_level_1() { let pandoc = Pandoc { meta: Default::default(), - blocks: vec![Block::Header(Header { + blocks: vec![Block::Header(Box::new(Header { level: 1, attr: empty_attr(), content: vec![Inline::Str(Str { @@ -142,7 +142,7 @@ fn test_header_level_1() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], }; let output = write_ansi(&pandoc); @@ -153,7 +153,7 @@ fn test_header_level_1() { fn test_header_level_2() { let pandoc = Pandoc { meta: Default::default(), - blocks: vec![Block::Header(Header { + blocks: vec![Block::Header(Box::new(Header { level: 2, attr: empty_attr(), content: vec![Inline::Str(Str { @@ -162,7 +162,7 @@ fn test_header_level_2() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], }; let output = write_ansi(&pandoc); @@ -174,7 +174,7 @@ fn test_header_level_3_to_6() { for level in 3..=6 { let pandoc = Pandoc { meta: Default::default(), - blocks: vec![Block::Header(Header { + blocks: vec![Block::Header(Box::new(Header { level, attr: empty_attr(), content: vec![Inline::Str(Str { @@ -183,7 +183,7 @@ fn test_header_level_3_to_6() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], }; let output = write_ansi(&pandoc); @@ -247,7 +247,7 @@ fn test_bullet_list() { fn test_ordered_list() { let pandoc = Pandoc { meta: Default::default(), - blocks: vec![Block::OrderedList(OrderedList { + blocks: vec![Block::OrderedList(Box::new(OrderedList { attr: ( 1, pampa::pandoc::ListNumberStyle::Decimal, @@ -270,7 +270,7 @@ fn test_ordered_list() { })], ], source_info: empty_source(), - })], + }))], }; let output = write_ansi(&pandoc); @@ -339,7 +339,7 @@ fn test_blockquote() { fn test_div() { let pandoc = Pandoc { meta: Default::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: empty_attr(), content: vec![Block::Paragraph(Paragraph { content: vec![Inline::Str(Str { @@ -350,7 +350,7 @@ fn test_div() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], }; let output = write_ansi(&pandoc); @@ -365,7 +365,7 @@ fn test_div_with_color() { let pandoc = Pandoc { meta: Default::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr, content: vec![Block::Paragraph(Paragraph { content: vec![Inline::Str(Str { @@ -376,7 +376,7 @@ fn test_div_with_color() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], }; let output = write_ansi(&pandoc); @@ -391,11 +391,11 @@ fn test_div_with_color() { fn test_rawblock_ansi_format() { let pandoc = Pandoc { meta: Default::default(), - blocks: vec![Block::RawBlock(RawBlock { + blocks: vec![Block::RawBlock(Box::new(RawBlock { format: "ansi".to_string(), text: "Raw ANSI content".to_string(), source_info: empty_source(), - })], + }))], }; let output = write_ansi(&pandoc); @@ -406,11 +406,11 @@ fn test_rawblock_ansi_format() { fn test_rawblock_other_format_skipped() { let pandoc = Pandoc { meta: Default::default(), - blocks: vec![Block::RawBlock(RawBlock { + blocks: vec![Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: "

HTML content

".to_string(), source_info: empty_source(), - })], + }))], }; let output = write_ansi(&pandoc); @@ -1149,12 +1149,12 @@ fn test_unsupported_codeblock() { let pandoc = Pandoc { meta: Default::default(), - blocks: vec![Block::CodeBlock(CodeBlock { + blocks: vec![Block::CodeBlock(Box::new(CodeBlock { attr: empty_attr(), text: "code".to_string(), source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], }; let mut buf = Vec::new(); @@ -1175,7 +1175,7 @@ fn test_mixed_blocks_and_inlines() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![ - Block::Header(Header { + Block::Header(Box::new(Header { level: 1, attr: empty_attr(), content: vec![Inline::Str(Str { @@ -1184,7 +1184,7 @@ fn test_mixed_blocks_and_inlines() { })], source_info: empty_source(), attr_source: empty_attr_source(), - }), + })), Block::Paragraph(Paragraph { content: vec![ Inline::Str(Str { diff --git a/crates/pampa/tests/integration/test_attr_source_structure.rs b/crates/pampa/tests/integration/test_attr_source_structure.rs index 87b8b549af..2c14f623aa 100644 --- a/crates/pampa/tests/integration/test_attr_source_structure.rs +++ b/crates/pampa/tests/integration/test_attr_source_structure.rs @@ -554,13 +554,13 @@ fn test_block_enum_variants_with_source_fields() { // Verify that we can pattern match on Block variants and access // their source fields where applicable - let header_block = Block::Header(Header { + let header_block = Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }); + })); match header_block { Block::Header(h) => { @@ -569,12 +569,12 @@ fn test_block_enum_variants_with_source_fields() { _ => panic!("Expected Header"), } - let div_block = Block::Div(Div { + let div_block = Block::Div(Box::new(Div { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }); + })); match div_block { Block::Div(d) => { diff --git a/crates/pampa/tests/integration/test_figure_figcaption_synthesis.rs b/crates/pampa/tests/integration/test_figure_figcaption_synthesis.rs index 8e25eb1478..88159aca02 100644 --- a/crates/pampa/tests/integration/test_figure_figcaption_synthesis.rs +++ b/crates/pampa/tests/integration/test_figure_figcaption_synthesis.rs @@ -41,7 +41,7 @@ fn figure_with_kvs(kvs: &[(&str, &str)], caption: Option<&str>) -> Block { for (k, v) in kvs { map.insert(k.to_string(), v.to_string()); } - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr: ( String::new(), vec!["quarto-float".to_string(), "quarto-float-fig".to_string()], @@ -58,7 +58,7 @@ fn figure_with_kvs(kvs: &[(&str, &str)], caption: Option<&str>) -> Block { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn to_html(block: Block) -> String { @@ -141,7 +141,7 @@ fn uncaptioned_kv_adds_uncaptioned_class() { #[test] fn plain_figure_without_kvs_is_unchanged() { - let html = to_html(Block::Figure(Figure { + let html = to_html(Block::Figure(Box::new(Figure { attr: ("fig-x".to_string(), Vec::new(), LinkedHashMap::new()), caption: Caption { short: None, @@ -154,7 +154,7 @@ fn plain_figure_without_kvs_is_unchanged() { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - })); + }))); assert!( html.contains("
"), "plain figure: {html}" diff --git a/crates/pampa/tests/integration/test_html_block_lift.rs b/crates/pampa/tests/integration/test_html_block_lift.rs index 2688b082ea..404dafaeea 100644 --- a/crates/pampa/tests/integration/test_html_block_lift.rs +++ b/crates/pampa/tests/integration/test_html_block_lift.rs @@ -929,11 +929,11 @@ fn unrelated_blocks_are_not_merged_by_the_writer() { }) }; let raw = |text: &str| { - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: text.to_string(), source_info: span(), - }) + })) }; let pandoc = pampa::pandoc::Pandoc { diff --git a/crates/pampa/tests/integration/test_json_roundtrip.rs b/crates/pampa/tests/integration/test_json_roundtrip.rs index 347beabe49..f4e2411b8d 100644 --- a/crates/pampa/tests/integration/test_json_roundtrip.rs +++ b/crates/pampa/tests/integration/test_json_roundtrip.rs @@ -195,7 +195,7 @@ fn test_json_roundtrip_complex_document() { }, ), }), - Block::CodeBlock(pampa::pandoc::CodeBlock { + Block::CodeBlock(Box::new(pampa::pandoc::CodeBlock { attr: (String::new(), vec![], LinkedHashMap::new()), text: "print('Hello, world!')".to_string(), source_info: SourceInfo::from_range( @@ -214,7 +214,7 @@ fn test_json_roundtrip_complex_document() { }, ), attr_source: pampa::pandoc::attr::AttrSourceInfo::empty(), - }), + })), ], }; @@ -311,7 +311,7 @@ fn test_json_write_then_read_matches_original_structure() { }, ), }), - Block::RawBlock(pampa::pandoc::RawBlock { + Block::RawBlock(Box::new(pampa::pandoc::RawBlock { format: "html".to_string(), text: "
Raw HTML
".to_string(), source_info: SourceInfo::from_range( @@ -329,7 +329,7 @@ fn test_json_write_then_read_matches_original_structure() { }, }, ), - }), + })), ], }; diff --git a/crates/pampa/tests/integration/test_lua_attr_mutation.rs b/crates/pampa/tests/integration/test_lua_attr_mutation.rs index 24827a0029..f4c3e3b1a7 100644 --- a/crates/pampa/tests/integration/test_lua_attr_mutation.rs +++ b/crates/pampa/tests/integration/test_lua_attr_mutation.rs @@ -37,7 +37,7 @@ fn si() -> quarto_source_map::SourceInfo { } fn code_block_with(class: &str, text: &str) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), vec![class.to_string()], @@ -46,7 +46,7 @@ fn code_block_with(class: &str, text: &str) -> Block { text: text.to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn para_with_inline_code(class: &str, text: &str) -> Block { diff --git a/crates/pampa/tests/integration/test_lua_content_mutation.rs b/crates/pampa/tests/integration/test_lua_content_mutation.rs index 9674f2849d..b9daf495f7 100644 --- a/crates/pampa/tests/integration/test_lua_content_mutation.rs +++ b/crates/pampa/tests/integration/test_lua_content_mutation.rs @@ -29,7 +29,7 @@ fn str_inline(text: &str) -> Inline { fn div_doc() -> Pandoc { Pandoc { meta: Default::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { content: vec![Block::Paragraph(Paragraph { content: vec![str_inline("asdf")], source_info: quarto_source_map::SourceInfo::for_test(), @@ -37,7 +37,7 @@ fn div_doc() -> Pandoc { attr: (String::new(), vec!["a-div".to_string()], Default::default()), source_info: quarto_source_map::SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - })], + }))], } } diff --git a/crates/pampa/tests/integration/test_lua_list.rs b/crates/pampa/tests/integration/test_lua_list.rs index f830ac90d9..30370b054c 100644 --- a/crates/pampa/tests/integration/test_lua_list.rs +++ b/crates/pampa/tests/integration/test_lua_list.rs @@ -370,7 +370,7 @@ fn empty_source() -> quarto_source_map::SourceInfo { fn create_div_doc(classes: Vec<&str>, content: Vec) -> Pandoc { Pandoc { meta: Default::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: ( String::new(), classes.into_iter().map(|s| s.to_string()).collect(), @@ -379,7 +379,7 @@ fn create_div_doc(classes: Vec<&str>, content: Vec) -> Pandoc { content, source_info: empty_source(), attr_source: AttrSourceInfo::empty(), - })], + }))], } } @@ -532,7 +532,7 @@ end let doc = Pandoc { meta: Default::default(), - blocks: vec![Block::OrderedList(OrderedList { + blocks: vec![Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![vec![Block::Plain(Plain { content: vec![Inline::Str(Str { @@ -542,7 +542,7 @@ end source_info: empty_source(), })]], source_info: empty_source(), - })], + }))], }; run_filter(filter_code, doc).await; diff --git a/crates/pampa/tests/integration/test_raw_json_roundtrip.rs b/crates/pampa/tests/integration/test_raw_json_roundtrip.rs index 415bb85827..e6b70449b1 100644 --- a/crates/pampa/tests/integration/test_raw_json_roundtrip.rs +++ b/crates/pampa/tests/integration/test_raw_json_roundtrip.rs @@ -277,17 +277,17 @@ fn test_raw_json_roundtrip_note_definitions_and_block_metadata() { let doc = Pandoc { meta: ConfigValue::default(), blocks: vec![ - Block::NoteDefinitionPara(NoteDefinitionPara { + Block::NoteDefinitionPara(Box::new(NoteDefinitionPara { id: "fn-1".to_string(), content: vec![str_inline("a footnote", 0, 10)], source_info: si(0, 10), - }), - Block::NoteDefinitionFencedBlock(NoteDefinitionFencedBlock { + })), + Block::NoteDefinitionFencedBlock(Box::new(NoteDefinitionFencedBlock { id: "fn-2".to_string(), content: vec![para(vec![str_inline("fenced note", 11, 22)], 11, 22)], source_info: si(11, 22), - }), - Block::BlockMetadata(MetaBlock { + })), + Block::BlockMetadata(Box::new(MetaBlock { meta: ConfigValue { value: ConfigValueKind::Map(vec![ConfigMapEntry { key: "layout".to_string(), @@ -304,7 +304,7 @@ fn test_raw_json_roundtrip_note_definitions_and_block_metadata() { merge_op: MergeOp::default(), }, source_info: si(23, 35), - }), + })), ], }; assert_roundtrip_identity(&doc, &ASTContext::new()); @@ -326,7 +326,7 @@ fn test_raw_json_roundtrip_custom_nodes() { let doc = Pandoc { meta: ConfigValue::default(), blocks: vec![ - Block::Custom(block_custom), + Block::Custom(Box::new(block_custom)), para(vec![Inline::Custom(Box::new(inline_custom))], 31, 40), ], }; @@ -832,13 +832,13 @@ fn test_raw_json_roundtrip_standard_constructs() { 0, 11, ), - Block::Div(Div { + Block::Div(Box::new(Div { attr: ("d1".to_string(), vec!["note".to_string()], kvs), content: vec![para(vec![str_inline("inner", 12, 17)], 12, 17)], source_info: si(11, 18), attr_source: AttrSourceInfo::empty(), - }), - Block::CodeBlock(pampa::pandoc::CodeBlock { + })), + Block::CodeBlock(Box::new(pampa::pandoc::CodeBlock { attr: ( "cb".to_string(), vec!["rust".to_string()], @@ -847,7 +847,7 @@ fn test_raw_json_roundtrip_standard_constructs() { text: "fn main() {}".to_string(), source_info: si(19, 31), attr_source: AttrSourceInfo::empty(), - }), + })), ], }; assert_roundtrip_identity(&doc, &ASTContext::new()); diff --git a/crates/quarto-analysis/src/transforms/shortcode.rs b/crates/quarto-analysis/src/transforms/shortcode.rs index f5e2221fb8..047484abed 100644 --- a/crates/quarto-analysis/src/transforms/shortcode.rs +++ b/crates/quarto-analysis/src/transforms/shortcode.rs @@ -421,13 +421,13 @@ mod tests { let mut pandoc = Pandoc { meta: metadata, - blocks: vec![Block::Header(Header { + blocks: vec![Block::Header(Box::new(Header { level: 1, attr: Default::default(), content: vec![make_meta_shortcode("title")], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let transform = MetaShortcodeTransform; @@ -493,7 +493,7 @@ mod tests { let mut pandoc = Pandoc { meta: metadata, - blocks: vec![Block::Header(Header { + blocks: vec![Block::Header(Box::new(Header { level: 2, attr: Default::default(), content: vec![ @@ -505,7 +505,7 @@ mod tests { ], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let transform = MetaShortcodeTransform; diff --git a/crates/quarto-ast-reconcile/src/apply.rs b/crates/quarto-ast-reconcile/src/apply.rs index 346b81bc1e..2f182669d6 100644 --- a/crates/quarto-ast-reconcile/src/apply.rs +++ b/crates/quarto-ast-reconcile/src/apply.rs @@ -406,9 +406,9 @@ fn apply_custom_node_block_reconciliation( slot_plan: &CustomNodeSlotPlan, ) -> Block { match (orig_block, exec_block) { - (Block::Custom(orig), Block::Custom(exec)) => { - Block::Custom(apply_custom_node_reconciliation(orig, exec, slot_plan)) - } + (Block::Custom(orig), Block::Custom(exec)) => Block::Custom(Box::new( + apply_custom_node_reconciliation(*orig, *exec, slot_plan), + )), // Fallback: shouldn't happen, return original (orig, _) => orig, } @@ -537,9 +537,9 @@ fn apply_table_block_reconciliation( table_plan: &TableReconciliationPlan, ) -> Block { match (orig_block, exec_block) { - (Block::Table(orig), Block::Table(exec)) => { - Block::Table(apply_table_reconciliation(orig, exec, table_plan)) - } + (Block::Table(orig), Block::Table(exec)) => Block::Table(Box::new( + apply_table_reconciliation(*orig, *exec, table_plan), + )), // Fallback: shouldn't happen, return original (orig, _) => orig, } @@ -765,12 +765,12 @@ mod tests { } fn make_div_with_source(blocks: Vec, source: SourceInfo) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: (String::new(), vec![], LinkedHashMap::new()), content: blocks, source_info: source, attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) } // ==================== Basic Apply Tests ==================== @@ -923,19 +923,19 @@ mod tests { fn test_ordered_list_reconciliation() { let original = Pandoc { meta: Default::default(), - blocks: vec![Block::OrderedList(OrderedList { + blocks: vec![Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![vec![make_para_with_source("item1", source_a())]], source_info: source_a(), - })], + }))], }; let executed = Pandoc { meta: Default::default(), - blocks: vec![Block::OrderedList(OrderedList { + blocks: vec![Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![vec![make_para_with_source("item1", source_b())]], source_info: source_b(), - })], + }))], }; let plan = compute_reconciliation(&original, &executed); @@ -954,23 +954,23 @@ mod tests { fn test_header_inline_reconciliation() { let original = Pandoc { meta: Default::default(), - blocks: vec![Block::Header(Header { + blocks: vec![Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![make_str("Title", source_a())], source_info: source_a(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let executed = Pandoc { meta: Default::default(), - blocks: vec![Block::Header(Header { + blocks: vec![Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![make_str("Title", source_b())], source_info: source_b(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let plan = compute_reconciliation(&original, &executed); diff --git a/crates/quarto-ast-reconcile/src/compute.rs b/crates/quarto-ast-reconcile/src/compute.rs index 98f00c8799..b12fb33202 100644 --- a/crates/quarto-ast-reconcile/src/compute.rs +++ b/crates/quarto-ast-reconcile/src/compute.rs @@ -872,22 +872,22 @@ mod tests { } fn make_header(level: usize, text: &str) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![make_str(text)], source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) } fn make_div(blocks: Vec) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: (String::new(), vec![], LinkedHashMap::new()), content: blocks, source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) } fn make_blockquote(blocks: Vec) -> Block { @@ -905,15 +905,15 @@ mod tests { } fn make_ordered_list(items: Vec>) -> Block { - Block::OrderedList(OrderedList { + Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Default, ListNumberDelim::Default), content: items, source_info: dummy_source(), - }) + })) } fn make_figure(blocks: Vec) -> Block { - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr: (String::new(), vec![], LinkedHashMap::new()), caption: Caption { short: None, @@ -923,7 +923,7 @@ mod tests { content: blocks, source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) } fn make_emph(text: &str) -> Inline { diff --git a/crates/quarto-ast-reconcile/src/generators.rs b/crates/quarto-ast-reconcile/src/generators.rs index f1a09721d9..acf399afdd 100644 --- a/crates/quarto-ast-reconcile/src/generators.rs +++ b/crates/quarto-ast-reconcile/src/generators.rs @@ -998,23 +998,23 @@ fn gen_plain(config: GenConfig) -> impl Strategy { /// Generate a CodeBlock. fn gen_code_block() -> impl Strategy { (gen_attr(), gen_code_text()).prop_map(|(attr, text)| { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr, text, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } /// Generate a RawBlock. fn gen_raw_block() -> impl Strategy { (gen_format(), gen_code_text()).prop_map(|(format, text)| { - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format, text, source_info: dummy_source(), - }) + })) }) } @@ -1028,13 +1028,13 @@ fn gen_horizontal_rule() -> impl Strategy { /// Generate a Header block. fn gen_header(config: GenConfig) -> impl Strategy { (1..=6usize, gen_attr(), gen_inlines_inner(config)).prop_map(|(level, attr, content)| { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr, content, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } @@ -1096,23 +1096,23 @@ fn gen_ordered_list(config: GenConfig) -> impl Strategy { proptest::collection::vec(gen_blocks_inner(config), 1..=max_children), ) .prop_map(|(attr, content)| { - Block::OrderedList(OrderedList { + Block::OrderedList(Box::new(OrderedList { attr, content, source_info: dummy_source(), - }) + })) }) } /// Generate a Div block. fn gen_div(config: GenConfig) -> impl Strategy { (gen_attr(), gen_blocks_inner(config)).prop_map(|(attr, content)| { - Block::Div(Div { + Block::Div(Box::new(Div { attr, content, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } @@ -1141,13 +1141,13 @@ fn gen_figure(config: GenConfig) -> impl Strategy { gen_blocks_inner(config), ) .prop_map(|(attr, caption, content)| { - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr, caption, content, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } @@ -1168,22 +1168,22 @@ fn gen_caption_block(config: GenConfig) -> impl Strategy { /// Generate a NoteDefinitionPara (leaf block with ID and inline content). fn gen_note_definition_para(config: GenConfig) -> impl Strategy { (gen_note_id(), gen_inlines_inner(config)).prop_map(|(id, content)| { - Block::NoteDefinitionPara(NoteDefinitionPara { + Block::NoteDefinitionPara(Box::new(NoteDefinitionPara { id, content, source_info: dummy_source(), - }) + })) }) } /// Generate a NoteDefinitionFencedBlock (container block with ID and block content). fn gen_note_definition_fenced(config: GenConfig) -> impl Strategy { (gen_note_id(), gen_blocks_inner(config)).prop_map(|(id, content)| { - Block::NoteDefinitionFencedBlock(NoteDefinitionFencedBlock { + Block::NoteDefinitionFencedBlock(Box::new(NoteDefinitionFencedBlock { id, content, source_info: dummy_source(), - }) + })) }) } @@ -1270,7 +1270,7 @@ fn gen_custom_node(config: GenConfig) -> impl Strategy { /// Generate a Block::Custom. fn gen_custom_block(config: GenConfig) -> impl Strategy { - gen_custom_node(config).prop_map(Block::Custom) + gen_custom_node(config).prop_map(|c| Block::Custom(Box::new(c))) } /// Generate an Inline::Custom. @@ -1408,7 +1408,7 @@ fn gen_table(config: GenConfig) -> impl Strategy { gen_table_foot(child_config, num_cols), ) .prop_map(|(attr, caption, colspec, head, bodies, foot)| { - Block::Table(Table { + Block::Table(Box::new(Table { attr, caption, colspec, @@ -1417,7 +1417,7 @@ fn gen_table(config: GenConfig) -> impl Strategy { foot, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) }) } diff --git a/crates/quarto-ast-reconcile/src/hash.rs b/crates/quarto-ast-reconcile/src/hash.rs index f6cbfefce4..0a8696817e 100644 --- a/crates/quarto-ast-reconcile/src/hash.rs +++ b/crates/quarto-ast-reconcile/src/hash.rs @@ -1180,7 +1180,7 @@ mod tests { #[test] fn test_hash_code_block() { - let cb1 = Block::CodeBlock(CodeBlock { + let cb1 = Block::CodeBlock(Box::new(CodeBlock { attr: ( "id".to_string(), vec!["python".to_string()], @@ -1189,9 +1189,9 @@ mod tests { text: "print('hello')".to_string(), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let cb2 = Block::CodeBlock(CodeBlock { + let cb2 = Block::CodeBlock(Box::new(CodeBlock { attr: ( "id".to_string(), vec!["python".to_string()], @@ -1200,9 +1200,9 @@ mod tests { text: "print('hello')".to_string(), source_info: other_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let cb3 = Block::CodeBlock(CodeBlock { + let cb3 = Block::CodeBlock(Box::new(CodeBlock { attr: ( "id".to_string(), vec!["python".to_string()], @@ -1211,7 +1211,7 @@ mod tests { text: "print('world')".to_string(), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); // Same content, different source -> same hash assert_eq!( @@ -1228,17 +1228,17 @@ mod tests { #[test] fn test_hash_raw_block() { - let rb1 = Block::RawBlock(RawBlock { + let rb1 = Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: "
hello
".to_string(), source_info: dummy_source(), - }); + })); - let rb2 = Block::RawBlock(RawBlock { + let rb2 = Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: "
world
".to_string(), source_info: dummy_source(), - }); + })); assert_ne!( compute_block_hash_fresh(&rb1), @@ -1273,23 +1273,23 @@ mod tests { #[test] fn test_hash_ordered_list() { - let ol1 = Block::OrderedList(OrderedList { + let ol1 = Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![vec![Block::Paragraph(Paragraph { content: vec![make_str("item 1")], source_info: dummy_source(), })]], source_info: dummy_source(), - }); + })); - let ol2 = Block::OrderedList(OrderedList { + let ol2 = Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![vec![Block::Paragraph(Paragraph { content: vec![make_str("item 1")], source_info: other_source(), })]], source_info: other_source(), - }); + })); assert_eq!( compute_block_hash_fresh(&ol1), @@ -1348,21 +1348,21 @@ mod tests { #[test] fn test_hash_header() { - let h1 = Block::Header(Header { + let h1 = Block::Header(Box::new(Header { level: 1, attr: empty_attr(), content: vec![make_str("Title")], source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let h2 = Block::Header(Header { + let h2 = Block::Header(Box::new(Header { level: 2, attr: empty_attr(), content: vec![make_str("Title")], source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); // Different level -> different hash assert_ne!(compute_block_hash_fresh(&h1), compute_block_hash_fresh(&h2)); @@ -1387,7 +1387,7 @@ mod tests { #[test] fn test_hash_div() { - let div1 = Block::Div(Div { + let div1 = Block::Div(Box::new(Div { attr: ( "my-div".to_string(), vec!["note".to_string()], @@ -1399,9 +1399,9 @@ mod tests { })], source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let div2 = Block::Div(Div { + let div2 = Block::Div(Box::new(Div { attr: ( "my-div".to_string(), vec!["note".to_string()], @@ -1413,7 +1413,7 @@ mod tests { })], source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); // Different content -> different hash assert_ne!( @@ -1775,7 +1775,7 @@ mod tests { #[test] fn test_structural_eq_code_block() { - let cb1 = Block::CodeBlock(CodeBlock { + let cb1 = Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), vec!["python".to_string()], @@ -1784,9 +1784,9 @@ mod tests { text: "code".to_string(), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let cb2 = Block::CodeBlock(CodeBlock { + let cb2 = Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), vec!["python".to_string()], @@ -1795,14 +1795,14 @@ mod tests { text: "code".to_string(), source_info: other_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let cb3 = Block::CodeBlock(CodeBlock { + let cb3 = Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec!["r".to_string()], LinkedHashMap::new()), text: "code".to_string(), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); assert!(structural_eq_block(&cb1, &cb2)); assert!(!structural_eq_block(&cb1, &cb3)); // Different class @@ -1810,17 +1810,17 @@ mod tests { #[test] fn test_structural_eq_raw_block() { - let rb1 = Block::RawBlock(RawBlock { + let rb1 = Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: "
".to_string(), source_info: dummy_source(), - }); + })); - let rb2 = Block::RawBlock(RawBlock { + let rb2 = Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: "
".to_string(), source_info: other_source(), - }); + })); assert!(structural_eq_block(&rb1, &rb2)); } @@ -1848,32 +1848,32 @@ mod tests { #[test] fn test_structural_eq_ordered_list() { - let ol1 = Block::OrderedList(OrderedList { + let ol1 = Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![vec![Block::Paragraph(Paragraph { content: vec![make_str("item")], source_info: dummy_source(), })]], source_info: dummy_source(), - }); + })); - let ol2 = Block::OrderedList(OrderedList { + let ol2 = Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![vec![Block::Paragraph(Paragraph { content: vec![make_str("item")], source_info: other_source(), })]], source_info: other_source(), - }); + })); - let ol3 = Block::OrderedList(OrderedList { + let ol3 = Block::OrderedList(Box::new(OrderedList { attr: (5, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![vec![Block::Paragraph(Paragraph { content: vec![make_str("item")], source_info: dummy_source(), })]], source_info: dummy_source(), - }); + })); assert!(structural_eq_block(&ol1, &ol2)); assert!(!structural_eq_block(&ol1, &ol3)); // Different start number @@ -1929,29 +1929,29 @@ mod tests { #[test] fn test_structural_eq_header() { - let h1 = Block::Header(Header { + let h1 = Block::Header(Box::new(Header { level: 1, attr: empty_attr(), content: vec![make_str("Title")], source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let h2 = Block::Header(Header { + let h2 = Block::Header(Box::new(Header { level: 1, attr: empty_attr(), content: vec![make_str("Title")], source_info: other_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let h3 = Block::Header(Header { + let h3 = Block::Header(Box::new(Header { level: 2, attr: empty_attr(), content: vec![make_str("Title")], source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); assert!(structural_eq_block(&h1, &h2)); assert!(!structural_eq_block(&h1, &h3)); // Different level @@ -1972,7 +1972,7 @@ mod tests { #[test] fn test_structural_eq_div() { - let div1 = Block::Div(Div { + let div1 = Block::Div(Box::new(Div { attr: ("id".to_string(), vec![], LinkedHashMap::new()), content: vec![Block::Paragraph(Paragraph { content: vec![make_str("text")], @@ -1980,9 +1980,9 @@ mod tests { })], source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let div2 = Block::Div(Div { + let div2 = Block::Div(Box::new(Div { attr: ("id".to_string(), vec![], LinkedHashMap::new()), content: vec![Block::Paragraph(Paragraph { content: vec![make_str("text")], @@ -1990,7 +1990,7 @@ mod tests { })], source_info: other_source(), attr_source: AttrSourceInfo::empty(), - }); + })); assert!(structural_eq_block(&div1, &div2)); } @@ -2225,7 +2225,7 @@ mod tests { source_info: dummy_source(), }; - let block = Block::Custom(cn); + let block = Block::Custom(Box::new(cn)); let hash = compute_block_hash_fresh(&block); let _ = hash; } @@ -2249,7 +2249,7 @@ mod tests { source_info: dummy_source(), }; - let block = Block::Custom(cn); + let block = Block::Custom(Box::new(cn)); let hash = compute_block_hash_fresh(&block); let _ = hash; } diff --git a/crates/quarto-ast-reconcile/src/lib.rs b/crates/quarto-ast-reconcile/src/lib.rs index e55573a1b4..386b4b528a 100644 --- a/crates/quarto-ast-reconcile/src/lib.rs +++ b/crates/quarto-ast-reconcile/src/lib.rs @@ -100,7 +100,7 @@ mod tests { } fn make_header(level: usize, text: &str, source: SourceInfo) -> quarto_pandoc_types::Block { - quarto_pandoc_types::Block::Header(Header { + quarto_pandoc_types::Block::Header(Box::new(Header { level, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![quarto_pandoc_types::Inline::Str(Str { @@ -109,23 +109,23 @@ mod tests { })], source_info: source, attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) } fn make_code_block(code: &str, source: SourceInfo) -> quarto_pandoc_types::Block { - quarto_pandoc_types::Block::CodeBlock(CodeBlock { + quarto_pandoc_types::Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec!["{r}".to_string()], LinkedHashMap::new()), text: code.to_string(), source_info: source, attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) } fn make_div( blocks: Vec, source: SourceInfo, ) -> quarto_pandoc_types::Block { - quarto_pandoc_types::Block::Div(Div { + quarto_pandoc_types::Block::Div(Box::new(Div { attr: ( String::new(), vec!["cell".to_string()], @@ -134,7 +134,7 @@ mod tests { content: blocks, source_info: source, attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) } /// bd-205v6: two Cites whose `citations` differ (id/mode/prefix/suffix) @@ -774,20 +774,14 @@ mod tests { fn test_custom_node_identical_content_kept() { let original = Pandoc { meta: Default::default(), - blocks: vec![quarto_pandoc_types::Block::Custom(make_custom_node( - "Callout", - "Note", - "This is important.", - source_original(), + blocks: vec![quarto_pandoc_types::Block::Custom(Box::new( + make_custom_node("Callout", "Note", "This is important.", source_original()), ))], }; let executed = Pandoc { meta: Default::default(), - blocks: vec![quarto_pandoc_types::Block::Custom(make_custom_node( - "Callout", - "Note", - "This is important.", - source_executed(), + blocks: vec![quarto_pandoc_types::Block::Custom(Box::new( + make_custom_node("Callout", "Note", "This is important.", source_executed()), ))], }; @@ -811,20 +805,19 @@ mod tests { fn test_custom_node_slot_content_changed() { let original = Pandoc { meta: Default::default(), - blocks: vec![quarto_pandoc_types::Block::Custom(make_custom_node( - "Callout", - "Note", - "Original content.", - source_original(), + blocks: vec![quarto_pandoc_types::Block::Custom(Box::new( + make_custom_node("Callout", "Note", "Original content.", source_original()), ))], }; let executed = Pandoc { meta: Default::default(), - blocks: vec![quarto_pandoc_types::Block::Custom(make_custom_node( - "Callout", - "Note", // Title unchanged - "Changed content.", // Content changed - source_executed(), + blocks: vec![quarto_pandoc_types::Block::Custom(Box::new( + make_custom_node( + "Callout", + "Note", // Title unchanged + "Changed content.", // Content changed + source_executed(), + ), ))], }; @@ -868,20 +861,19 @@ mod tests { fn test_custom_node_different_type_not_reconciled() { let original = Pandoc { meta: Default::default(), - blocks: vec![quarto_pandoc_types::Block::Custom(make_custom_node( - "Callout", - "Note", - "Content", - source_original(), + blocks: vec![quarto_pandoc_types::Block::Custom(Box::new( + make_custom_node("Callout", "Note", "Content", source_original()), ))], }; let executed = Pandoc { meta: Default::default(), - blocks: vec![quarto_pandoc_types::Block::Custom(make_custom_node( - "PanelTabset", // Different type! - "Note", - "Content", - source_executed(), + blocks: vec![quarto_pandoc_types::Block::Custom(Box::new( + make_custom_node( + "PanelTabset", // Different type! + "Note", + "Content", + source_executed(), + ), ))], }; @@ -911,11 +903,11 @@ mod tests { let original = Pandoc { meta: Default::default(), - blocks: vec![quarto_pandoc_types::Block::Custom(orig_cn)], + blocks: vec![quarto_pandoc_types::Block::Custom(Box::new(orig_cn))], }; let executed = Pandoc { meta: Default::default(), - blocks: vec![quarto_pandoc_types::Block::Custom(exec_cn)], + blocks: vec![quarto_pandoc_types::Block::Custom(Box::new(exec_cn))], }; let (result, _plan) = reconcile(original, executed); @@ -937,12 +929,12 @@ mod tests { meta: Default::default(), blocks: vec![ make_para("Before callout", source_original()), - quarto_pandoc_types::Block::Custom(make_custom_node( + quarto_pandoc_types::Block::Custom(Box::new(make_custom_node( "Callout", "Warning", "Be careful!", source_original(), - )), + ))), make_para("After callout", source_original()), ], }; @@ -950,12 +942,12 @@ mod tests { meta: Default::default(), blocks: vec![ make_para("Before callout", source_executed()), - quarto_pandoc_types::Block::Custom(make_custom_node( + quarto_pandoc_types::Block::Custom(Box::new(make_custom_node( "Callout", "Warning", "Be careful!", source_executed(), - )), + ))), make_para("After callout", source_executed()), ], }; @@ -1033,20 +1025,24 @@ mod tests { let original = Pandoc { meta: Default::default(), - blocks: vec![quarto_pandoc_types::Block::Custom(make_complex_callout( - "Important", - "First paragraph unchanged.", - "Second paragraph will change.", - source_original(), + blocks: vec![quarto_pandoc_types::Block::Custom(Box::new( + make_complex_callout( + "Important", + "First paragraph unchanged.", + "Second paragraph will change.", + source_original(), + ), ))], }; let executed = Pandoc { meta: Default::default(), - blocks: vec![quarto_pandoc_types::Block::Custom(make_complex_callout( - "Important", // Title unchanged - "First paragraph unchanged.", // First para unchanged - "Second paragraph CHANGED!", // Second para changed - source_executed(), + blocks: vec![quarto_pandoc_types::Block::Custom(Box::new( + make_complex_callout( + "Important", // Title unchanged + "First paragraph unchanged.", // First para unchanged + "Second paragraph CHANGED!", // Second para changed + source_executed(), + ), ))], }; @@ -1181,7 +1177,7 @@ mod tests { Alignment, Cell, ColWidth, Row, Table, TableBody, TableFoot, TableHead, }; let attr_source = quarto_pandoc_types::AttrSourceInfo::empty; - quarto_pandoc_types::Block::Table(Table { + quarto_pandoc_types::Block::Table(Box::new(Table { attr: empty_attr(), caption: quarto_pandoc_types::Caption { short: None, @@ -1224,7 +1220,7 @@ mod tests { }, source_info: source, attr_source: attr_source(), - }) + })) } fn table_caption_long(block: &quarto_pandoc_types::Block) -> &[quarto_pandoc_types::Block] { @@ -1252,13 +1248,13 @@ mod tests { ) -> quarto_pandoc_types::Block { let mut slots = LinkedHashMap::new(); slots.insert("body".to_string(), Slot::Blocks(blocks)); - quarto_pandoc_types::Block::Custom(CustomNode { + quarto_pandoc_types::Block::Custom(Box::new(CustomNode { type_name: "TestPanel".to_string(), slots, plain_data: serde_json::json!({}), attr: empty_attr(), source_info: source, - }) + })) } /// Custom block node with a single Slot::Inlines slot named "title". @@ -1268,13 +1264,13 @@ mod tests { ) -> quarto_pandoc_types::Block { let mut slots = LinkedHashMap::new(); slots.insert("title".to_string(), Slot::Inlines(inlines)); - quarto_pandoc_types::Block::Custom(CustomNode { + quarto_pandoc_types::Block::Custom(Box::new(CustomNode { type_name: "TestPanel".to_string(), slots, plain_data: serde_json::json!({}), attr: empty_attr(), source_info: source, - }) + })) } fn blocks_slot( diff --git a/crates/quarto-ast-reconcile/src/remap.rs b/crates/quarto-ast-reconcile/src/remap.rs index 8671e0dca6..f24e85ef83 100644 --- a/crates/quarto-ast-reconcile/src/remap.rs +++ b/crates/quarto-ast-reconcile/src/remap.rs @@ -595,12 +595,12 @@ mod tests { content: vec![make_str(0)], source_info: src(0), }); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![inner_para], source_info: src(0), attr_source: AttrSourceInfo::empty(), - }); + })); let mut pandoc = Pandoc { meta: ConfigValue::null(src(0)), blocks: vec![div], @@ -629,13 +629,13 @@ mod tests { classes: vec![Some(src(0))], attributes: vec![(Some(src(0)), Some(src(0)))], }; - let header = Block::Header(Header { + let header = Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: src(0), attr_source: std::mem::replace(&mut attr_source, AttrSourceInfo::empty()), - }); + })); let mut pandoc = Pandoc { meta: ConfigValue::null(src(0)), blocks: vec![header], @@ -679,13 +679,13 @@ mod tests { let mut pandoc = Pandoc { meta: ConfigValue::null(src(0)), blocks: vec![ - Block::Header(Header { + Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![make_str(0)], source_info: src(0), attr_source, - }), + })), Block::Paragraph(Paragraph { content: vec![make_str(0)], source_info: src(0), diff --git a/crates/quarto-citeproc/src/output.rs b/crates/quarto-citeproc/src/output.rs index 77a9010ec1..7978661035 100644 --- a/crates/quarto-citeproc/src/output.rs +++ b/crates/quarto-citeproc/src/output.rs @@ -1651,7 +1651,7 @@ fn to_blocks_inner(output: &Output) -> Vec { let mut attr = quarto_pandoc_types::empty_attr(); attr.1.push(class.to_string()); - blocks.push(Block::Div(Div { + blocks.push(Block::Div(Box::new(Div { attr, content: vec![Block::Plain(Plain { content: inlines, @@ -1659,7 +1659,7 @@ fn to_blocks_inner(output: &Output) -> Vec { })], source_info: empty_source_info(), attr_source: empty_attr_source(), - })); + }))); } None => { // No display attribute - wrap in Plain diff --git a/crates/quarto-core/src/ast_walk.rs b/crates/quarto-core/src/ast_walk.rs index a23b7d9e62..f6e40ebead 100644 --- a/crates/quarto-core/src/ast_walk.rs +++ b/crates/quarto-core/src/ast_walk.rs @@ -311,12 +311,12 @@ fn slot_children_mut(slot: &mut Slot, v: &mut dyn ListVisitor) { **b = if list.len() == 1 { list.pop().expect("one block") } else { - Block::Div(quarto_pandoc_types::block::Div { + Block::Div(Box::new(quarto_pandoc_types::block::Div { attr: Default::default(), content: list, source_info: b.source_info().clone(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - }) + })) }; } Slot::Inline(i) => { diff --git a/crates/quarto-core/src/crossref/codeblock_shorthand.rs b/crates/quarto-core/src/crossref/codeblock_shorthand.rs index 8d600ff2e3..94f441b093 100644 --- a/crates/quarto-core/src/crossref/codeblock_shorthand.rs +++ b/crates/quarto-core/src/crossref/codeblock_shorthand.rs @@ -243,12 +243,12 @@ fn try_desugar_code_block( if let Some(align) = fig_align { attr.2.insert("fig-align".to_string(), align); } - let new_code_block = Block::CodeBlock(quarto_pandoc_types::block::CodeBlock { + let new_code_block = Block::CodeBlock(Box::new(quarto_pandoc_types::block::CodeBlock { attr, text, source_info: cb.source_info.clone(), attr_source: cb.attr_source.clone(), - }); + })); Some(match wrapper { Wrapper::Float { @@ -263,12 +263,12 @@ fn try_desugar_code_block( } let attr_map: hashlink::LinkedHashMap = attributes.into_iter().collect(); - Block::Div(Div { + Block::Div(Box::new(Div { attr: (label, classes, attr_map), content, source_info: cb.source_info.clone(), attr_source: AttrSourceInfo::empty(), - }) + })) } Wrapper::Figure { caption, short } => { let long = match caption_paragraph(caption, diagnostics) { @@ -278,7 +278,7 @@ fn try_desugar_code_block( })], other => vec![other], }; - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr: (String::new(), Vec::new(), hashlink::LinkedHashMap::new()), caption: Caption { short: short.map(|s| caption_inlines(s, diagnostics)), @@ -288,7 +288,7 @@ fn try_desugar_code_block( content: vec![new_code_block], source_info: cb.source_info.clone(), attr_source: AttrSourceInfo::empty(), - }) + })) } // Nothing to wrap — the cell was rewritten in place (by the // accessibility injection and/or the `fig-align` attribute). @@ -990,12 +990,12 @@ mod tests { } fn code_in(language: &str, text: &str) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![language.into()], LinkedHashMap::new()), text: text.to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Tests don't need real provenance — the desugar only uses it to @@ -1726,12 +1726,12 @@ mod tests { fn desugar_recurses_into_div_content() { let reg = RefTypeRegistry::builtin(); let inner = code("#| label: fig-inner\n#| fig-cap: inner cap\nprint(1)\n"); - let outer = Block::Div(Div { + let outer = Block::Div(Box::new(Div { attr: (String::new(), vec!["wrapper".into()], LinkedHashMap::new()), content: vec![inner], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let mut blocks = vec![outer]; desugar(&mut blocks, ®); let Block::Div(wrapper) = &blocks[0] else { diff --git a/crates/quarto-core/src/crossref/label_reinject.rs b/crates/quarto-core/src/crossref/label_reinject.rs index e2021f543f..b18107c5f8 100644 --- a/crates/quarto-core/src/crossref/label_reinject.rs +++ b/crates/quarto-core/src/crossref/label_reinject.rs @@ -222,12 +222,12 @@ mod tests { fn wrapped(id: &str, cb: CodeBlock) -> Pandoc { Pandoc { meta: Default::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: (id.to_string(), Vec::new(), LinkedHashMap::new()), - content: vec![Block::CodeBlock(cb)], + content: vec![Block::CodeBlock(Box::new(cb))], source_info: si(), attr_source: AttrSourceInfo::empty(), - })], + }))], } } @@ -330,12 +330,12 @@ mod tests { #[test] fn collect_returns_wrapper_div_ids_in_document_order() { let mut doc = wrapped("fig-a", code_block("plot(a)\n")); - let Block::Div(nested) = Block::Div(Div { + let Block::Div(nested) = Block::Div(Box::new(Div { attr: (String::new(), Vec::new(), LinkedHashMap::new()), content: wrapped("tbl-b", code_block("head(b)\n")).blocks, source_info: si(), attr_source: AttrSourceInfo::empty(), - }) else { + })) else { unreachable!() }; doc.blocks.push(Block::Div(nested)); diff --git a/crates/quarto-core/src/crossref/roundtrip_tests.rs b/crates/quarto-core/src/crossref/roundtrip_tests.rs index bf02a32718..df79b7712d 100644 --- a/crates/quarto-core/src/crossref/roundtrip_tests.rs +++ b/crates/quarto-core/src/crossref/roundtrip_tests.rs @@ -47,18 +47,18 @@ fn attr_with_class(class: &str) -> Attr { /// Build a synthetic `Div(#) > CodeBlock(class=, text=)`. fn synthetic_div_over_code_block(ident: &str, lang: &str, body: &str) -> Pandoc { - let code_block = Block::CodeBlock(CodeBlock { + let code_block = Block::CodeBlock(Box::new(CodeBlock { attr: attr_with_class(lang), text: body.to_string(), source_info: si(), attr_source: empty_attr_source(), - }); - let div = Block::Div(Div { + })); + let div = Block::Div(Box::new(Div { attr: attr_with_id(ident), content: vec![code_block], source_info: si(), attr_source: empty_attr_source(), - }); + })); Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![div], diff --git a/crates/quarto-core/src/crossref/target.rs b/crates/quarto-core/src/crossref/target.rs index 1b2bd7d670..bef9a7c101 100644 --- a/crates/quarto-core/src/crossref/target.rs +++ b/crates/quarto-core/src/crossref/target.rs @@ -142,7 +142,7 @@ mod tests { "identifier": ident, }); node.slots.insert("content".into(), Slot::Blocks(vec![])); - Block::Custom(node) + Block::Custom(Box::new(node)) } #[test] @@ -167,7 +167,7 @@ mod tests { fn view_none_for_unrelated_custom_node() { // Callout without crossref plain_data returns None. let node = CustomNode::new("Callout", empty_attr(), si()); - let block = Block::Custom(node); + let block = Block::Custom(Box::new(node)); assert!(crossref_target_view(&block).is_none()); } @@ -181,7 +181,7 @@ mod tests { "kind": "Theorem", "identifier": "thm-one", }); - let block = Block::Custom(node); + let block = Block::Custom(Box::new(node)); let view = crossref_target_view(&block).expect("theorem recognized"); assert_eq!(view.ref_type, "thm"); assert_eq!(view.kind, "Theorem"); @@ -200,7 +200,7 @@ mod tests { "kind": "Note", "identifier": "nte-one", }); - let block = Block::Custom(node); + let block = Block::Custom(Box::new(node)); let view = crossref_target_view(&block).expect("callout recognized"); assert_eq!(view.ref_type, "nte"); } @@ -210,7 +210,7 @@ mod tests { let attr = (String::new(), vec![], LinkedHashMap::new()); let mut node = CustomNode::new(FLOAT_REF_TARGET, attr, si()); node.plain_data = json!({"ref_type": "fig", "kind": "Figure"}); - let block = Block::Custom(node); + let block = Block::Custom(Box::new(node)); assert!(crossref_target_view(&block).is_none()); } @@ -219,7 +219,7 @@ mod tests { let attr = ("fig-one".to_string(), vec![], LinkedHashMap::new()); let mut node = CustomNode::new(FLOAT_REF_TARGET, attr, si()); node.plain_data = json!({"kind": "Figure"}); - let block = Block::Custom(node); + let block = Block::Custom(Box::new(node)); assert!(crossref_target_view(&block).is_none()); } diff --git a/crates/quarto-core/src/dashboard/model.rs b/crates/quarto-core/src/dashboard/model.rs index 2732b463e5..77c5911f02 100644 --- a/crates/quarto-core/src/dashboard/model.rs +++ b/crates/quarto-core/src/dashboard/model.rs @@ -240,11 +240,11 @@ pub struct Panel { impl Panel { pub fn to_block(self) -> Block { - Block::Custom( + Block::Custom(Box::new( custom(PANEL_TYPE, &self.data, generated()) .with_slot("panel", Slot::Blocks(self.panel)) .with_slot("content", Slot::Blocks(self.content)), - ) + )) } pub fn from_custom(node: &CustomNode) -> Option { @@ -312,10 +312,10 @@ fn custom(type_name: &str, data: impl Serialize, source_info: SourceInfo) -> Cus impl Container { pub fn to_block(self) -> Block { - Block::Custom( + Block::Custom(Box::new( custom(CONTAINER_TYPE, &self.data, generated()) .with_slot("children", Slot::Blocks(self.children)), - ) + )) } pub fn from_custom(node: &CustomNode) -> Option { @@ -329,10 +329,10 @@ impl Container { impl Page { pub fn to_block(self) -> Block { - Block::Custom( + Block::Custom(Box::new( custom(PAGE_TYPE, &self.data, generated()) .with_slot("content", Slot::Blocks(self.content)), - ) + )) } pub fn from_custom(node: &CustomNode) -> Option { @@ -366,7 +366,7 @@ impl Card { if !self.sidebar.is_empty() { node.set_slot("sidebar", Slot::Blocks(self.sidebar)); } - Block::Custom(node) + Block::Custom(Box::new(node)) } pub fn from_custom(node: &CustomNode) -> Option { diff --git a/crates/quarto-core/src/dashboard/resolve.rs b/crates/quarto-core/src/dashboard/resolve.rs index d4b2248deb..c83d504c18 100644 --- a/crates/quarto-core/src/dashboard/resolve.rs +++ b/crates/quarto-core/src/dashboard/resolve.rs @@ -87,20 +87,20 @@ fn generated() -> SourceInfo { } fn raw_html(text: &str) -> Block { - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: text.to_string(), source_info: generated(), - }) + })) } fn div(attr: Attr, content: Blocks) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr, content, source_info: generated(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn add_class(attr: &mut Attr, class: &str) { @@ -445,7 +445,7 @@ fn lower_children(blocks: Blocks, cx: &mut Lower, parent: &mut Attr) -> Blocks { fn lower(block: Block, cx: &mut Lower) -> Block { let Block::Custom(node) = &block else { return match block { - Block::Div(d) if explicit_grid(&d.attr).is_some() => lower_explicit_grid(d, cx), + Block::Div(d) if explicit_grid(&d.attr).is_some() => lower_explicit_grid(*d, cx), Block::Div(mut d) => { let parent = cx.parent.take(); d.content = lower_all(d.content, cx); diff --git a/crates/quarto-core/src/dashboard/sizing.rs b/crates/quarto-core/src/dashboard/sizing.rs index c87166f2e2..1db0dfaa0b 100644 --- a/crates/quarto-core/src/dashboard/sizing.rs +++ b/crates/quarto-core/src/dashboard/sizing.rs @@ -366,11 +366,11 @@ mod tests { #[test] fn raw_bslib_value_boxes_flow() { - let raw = Block::RawBlock(quarto_pandoc_types::block::RawBlock { + let raw = Block::RawBlock(Box::new(quarto_pandoc_types::block::RawBlock { format: "html".into(), text: r#"
1
"#.into(), source_info: quarto_source_map::SourceInfo::for_test(), - }); + })); let p = parent(Orientation::Rows, vec![raw]); assert_eq!(grid_template(&p), "minmax(3em, max-content)"); } diff --git a/crates/quarto-core/src/dashboard/structure.rs b/crates/quarto-core/src/dashboard/structure.rs index 24310913e3..0a5b0d057f 100644 --- a/crates/quarto-core/src/dashboard/structure.rs +++ b/crates/quarto-core/src/dashboard/structure.rs @@ -63,12 +63,12 @@ fn has_class(attr: &Attr, class: &str) -> bool { } fn div(attr: Attr, content: Blocks) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr, content, source_info: generated(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn class_attr(classes: &[&str]) -> Attr { @@ -214,7 +214,7 @@ fn is_preserve_placeholder(inlines: &[Inline]) -> bool { /// heading becomes a `Div.h` with its level offset. fn process_body_content(block: Block, heading_offset: usize) -> Block { match block { - Block::Header(h) => header_div(h, heading_offset), + Block::Header(h) => header_div(*h, heading_offset), other => unwrap_single_inline_paras(other), } } @@ -505,17 +505,17 @@ pub fn build_cards( d.content = build_cards(d.content, doc, diagnostics); out.push(Block::Div(d)); } else if is_input_kind(&d.attr, "card-sidebar") { - out.push(card_sidebar_marker(d)); + out.push(card_sidebar_marker(*d)); } else if is_input_kind(&d.attr, "card-toolbar") { - out.push(card_toolbar_marker(d)); + out.push(card_toolbar_marker(*d)); } else if is_content_value(&d.attr, "sidebar") || is_content_value(&d.attr, "toolbar") { // D5-Q9: `content: sidebar` / `content: toolbar` (in Q1's schema // but never implemented) make a sidebar or toolbar like a // `.sidebar` / `.toolbar` heading does. - out.push(content_panel_div(d)); + out.push(content_panel_div(*d)); } else if has_class(&d.attr, "card") { if is_literal_card(&d) { - out.push(literal_card(d, doc)); + out.push(literal_card(*d, doc)); continue; } let (options, classes) = read_card_options(&d.attr, &d.source_info); @@ -525,7 +525,7 @@ pub fn build_cards( None => out.push(Block::Div(d)), } } else if is_value_box(&d.attr) { - out.push(make_value_box(d, doc, diagnostics)); + out.push(make_value_box(*d, doc, diagnostics)); } else if has_class(&d.attr, "quarto-float") && let Some(cell) = cell_around_float(&mut d) { @@ -538,7 +538,7 @@ pub fn build_cards( } else if has_class(&d.attr, "cell") { let id = d.attr.0.clone(); out.extend( - cell_to_card(d, doc) + cell_to_card(*d, doc) .into_iter() .map(|b| with_source_id(b, &id)), ); @@ -575,7 +575,7 @@ fn cell_around_float(float: &mut Div) -> Option
{ }); blocks.splice(i..i, unwrapped); cell.content = rest; - return Some(cell); + return Some(*cell); } Block::Div(d) => { if let Some(c) = take_cell(&mut d.content) { @@ -602,16 +602,16 @@ fn cell_around_float(float: &mut Div) -> Option
{ attr_source: AttrSourceInfo::empty(), }, ); - cell.content.push(Block::Div(Div { + cell.content.push(Block::Div(Box::new(Div { attr: ( String::new(), vec!["cell-output-display".to_string()], LinkedHashMap::new(), ), - content: vec![Block::Div(float)], + content: vec![Block::Div(Box::new(float))], source_info: generated(), attr_source: AttrSourceInfo::empty(), - })); + }))); Some(cell) } @@ -686,7 +686,7 @@ fn card_toolbar_marker(d: Div) -> Block { } else { marker_contents(d) }; - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( String::new(), vec![ @@ -698,7 +698,7 @@ fn card_toolbar_marker(d: Div) -> Block { content, source_info: source, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Q1 `card_sidebar.readOptions` + `makeCardSidebar`. @@ -728,7 +728,7 @@ fn card_sidebar_marker(d: Div) -> Block { } else { marker_contents(d) }; - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( String::new(), vec![ @@ -740,7 +740,7 @@ fn card_sidebar_marker(d: Div) -> Block { content, source_info: source, attr_source: AttrSourceInfo::empty(), - }) + })) } /// D5-Q9: a `content: sidebar` / `content: toolbar` cell as a sidebar or @@ -776,7 +776,7 @@ fn escape_with(blocks: Blocks, offset: usize) -> Blocks { let mut out = Vec::new(); for b in blocks { match b { - Block::Header(h) => out.push(header_div(h, offset)), + Block::Header(h) => out.push(header_div(*h, offset)), Block::Custom(n) if n.type_name == CARD_TYPE => { if let Some(card) = Card::from_custom(&n) { out.extend(escape_with( @@ -939,12 +939,12 @@ fn make_value_box( el.source_info, ) .unwrap_or_else(|| { - Block::Div(Div { + Block::Div(Box::new(Div { attr: class_attr(&["valuebox"]), content: Vec::new(), source_info: generated(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } @@ -1068,7 +1068,7 @@ fn cell_to_card(mut cell: Div, doc: &DashboardOptions) -> Vec { if !has_class(&cell.attr, "hidden") { cell.attr.1.push("hidden".to_string()); } - return vec![Block::Div(cell)]; + return vec![Block::Div(Box::new(cell))]; } let (mut options, classes) = read_card_options(&cell.attr, &cell.source_info); @@ -1262,7 +1262,7 @@ fn convert_section( return vec![block]; }; if let Some(orientation) = explicit_orientation(§ion.attr) { - return vec![explicit_container(section, orientation, doc, info)]; + return vec![explicit_container(*section, orientation, doc, info)]; } if !has_class(§ion.attr, "section") { return vec![Block::Div(section)]; @@ -1324,7 +1324,7 @@ fn convert_section( } if has_class(&header.attr, "tabset") { - return make_tabset(header, content, parent, doc, info); + return make_tabset(*header, content, parent, doc, info); } let orientation = suggested_orientation(&content).unwrap_or(parent.flip()); diff --git a/crates/quarto-core/src/dashboard/view.rs b/crates/quarto-core/src/dashboard/view.rs index 649bbe9e6b..81c2cf47df 100644 --- a/crates/quarto-core/src/dashboard/view.rs +++ b/crates/quarto-core/src/dashboard/view.rs @@ -68,14 +68,14 @@ pub fn tab_nav(tabset_id: &str, titles: &[String]) -> Block { .enumerate() .map(|(i, t)| json!({ "id": format!("{tabset_id}-{}", i + 1), "title": t })) .collect(); - Block::Custom( + Block::Custom(Box::new( node( TAB_NAV, (String::new(), Vec::new(), LinkedHashMap::new()), SourceInfo::generated(quarto_source_map::By::unknown()), ) .with_data(json!({ "tabset_id": tabset_id, "tabs": tabs })), - ) + )) } /// Rewrite the lowered document for the React preview (see the module @@ -110,22 +110,22 @@ fn to_view(block: Block) -> Block { content, source_info, .. - } = d; + } = *d; if has_class(&attr, "dashboard-page") { let index = attr.0.clone(); - return Block::Custom( + return Block::Custom(Box::new( node(PAGE_VIEW, attr, source_info) .with_data(json!({ "id": index })) .with_slot("content", Slot::Blocks(content)), - ); + )); } if has_class(&attr, "tab-pane") && attr.2.get("role").is_some_and(|r| r == "tabpanel") { let id = attr.0.clone(); - return Block::Custom( + return Block::Custom(Box::new( node(TAB_PANE, attr, source_info) .with_data(json!({ "id": id })) .with_slot("content", Slot::Blocks(content)), - ); + )); } if has_class(&attr, "bslib-card") { let expandable = attr.2.contains_key("data-full-screen"); @@ -133,18 +133,18 @@ fn to_view(block: Block) -> Block { .into_iter() .filter(|b| !raw_text(b).is_some_and(is_card_chrome)) .collect(); - return Block::Custom( + return Block::Custom(Box::new( node(CARD_VIEW, attr, source_info) .with_data(json!({ "expandable": expandable })) .with_slot("content", Slot::Blocks(content)), - ); + )); } - Block::Div(Div { + Block::Div(Box::new(Div { attr, content, source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// The card\'s bslib expand button and init script (React renders its own @@ -183,12 +183,12 @@ fn sidebar_layout(d: &mut Div) -> Option { &mut d.attr, (String::new(), Vec::new(), LinkedHashMap::new()), ); - Some(Block::Custom( + Some(Block::Custom(Box::new( node(SIDEBAR_LAYOUT, attr, d.source_info.clone()) .with_data(json!({ "sidebar_id": sidebar_id })) .with_slot("main", Slot::Blocks(to_views(vec![main]))) .with_slot("sidebar", Slot::Blocks(to_views(vec![sidebar]))), - )) + ))) } /// `

`, `Plain`, `

` (a value-box title or value, @@ -208,14 +208,14 @@ fn join_value_box_paragraphs(blocks: Blocks) -> Blocks { let plain = it.next().expect("peeked"); if it.peek().and_then(raw_text) == Some("

") { it.next(); - out.push(Block::Custom( + out.push(Block::Custom(Box::new( node( PARAGRAPH, (String::new(), vec![class], LinkedHashMap::new()), SourceInfo::generated(quarto_source_map::By::unknown()), ) .with_slot("content", Slot::Blocks(vec![plain])), - )); + ))); continue; } out.push(b); diff --git a/crates/quarto-core/src/editorial_marks.rs b/crates/quarto-core/src/editorial_marks.rs index 71c0b90230..52d9f65502 100644 --- a/crates/quarto-core/src/editorial_marks.rs +++ b/crates/quarto-core/src/editorial_marks.rs @@ -396,12 +396,12 @@ mod tests { #[test] fn blocks_to_inlines_matches_pandoc_utils() { // The verified case: Para, Para, CodeBlock, BulletList -> one LB two LB Code LB li. - let code = Block::CodeBlock(CodeBlock { + let code = Block::CodeBlock(Box::new(CodeBlock { attr: Default::default(), text: "x=1".into(), source_info: generated(), attr_source: AttrSourceInfo::empty(), - }); + })); let list = Block::BulletList(BulletList { content: vec![vec![para(vec![s("li")])]], source_info: generated(), @@ -419,12 +419,12 @@ mod tests { content: vec![vec![para(vec![s("a")])], vec![para(vec![s("b")])]], source_info: generated(), }); - let div = Block::Div(quarto_pandoc_types::block::Div { + let div = Block::Div(Box::new(quarto_pandoc_types::block::Div { attr: Default::default(), content: vec![para(vec![s("x")]), para(vec![s("y")])], source_info: generated(), attr_source: AttrSourceInfo::empty(), - }); + })); assert_eq!(describe(&blocks_to_inlines(&[list])), ["a", "b"]); assert_eq!(describe(&blocks_to_inlines(&[div])), ["x", "LB", "y"]); assert!(blocks_to_inlines(&[]).is_empty()); diff --git a/crates/quarto-core/src/engine/capture_splice.rs b/crates/quarto-core/src/engine/capture_splice.rs index 65a52b3caa..f3c64663af 100644 --- a/crates/quarto-core/src/engine/capture_splice.rs +++ b/crates/quarto-core/src/engine/capture_splice.rs @@ -68,7 +68,7 @@ use std::collections::HashMap; use quarto_ast_reconcile::compute_block_hash_fresh; -use quarto_pandoc_types::{Block, Blocks, Div, Pandoc}; +use quarto_pandoc_types::{Block, Blocks, Pandoc}; use super::cell_lang::engine_cell_lang; @@ -105,10 +105,10 @@ fn is_echo_of(candidate: &Block, cell: &Block) -> bool { /// `"cell"`. This is the shape the engine emits to wrap a single /// code cell's source + output blocks in its post-engine markdown. fn is_cell_wrapper(block: &Block) -> bool { - let Block::Div(Div { attr, .. }) = block else { + let Block::Div(div) = block else { return false; }; - attr.1.iter().any(|c| c == "cell") + div.attr.1.iter().any(|c| c == "cell") } /// A B1 block that is an engine's per-cell OUTPUT (to be spliced onto the @@ -472,12 +472,12 @@ mod tests { fn code_cell(lang: &str, body: &str) -> Block { let mut classes = Vec::new(); classes.push(format!("{{{lang}}}")); - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), classes, LinkedHashMap::new()), text: body.to_string(), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn prose(text: &str) -> Block { @@ -495,7 +495,7 @@ mod tests { /// (a marker paragraph) so we can assert the splice produced /// the right one when there are multiple cells. fn cell_wrapper(marker: &str) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( String::new(), vec!["cell".to_string()], @@ -504,7 +504,7 @@ mod tests { content: vec![prose(marker)], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn first_div_marker(block: &Block) -> Option<&str> { @@ -713,11 +713,11 @@ mod tests { /// executed island. `marker` is embedded so we can assert which island /// landed where. fn raw_island(marker: &str) -> Block { - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: format!("{marker}"), source_info: SourceInfo::for_test(), - }) + })) } /// A plain highlighted code block (classes = `[lang]`, no `{lang}` braces) — @@ -726,12 +726,12 @@ mod tests { /// `None` for it (no braces), which is how the derive walk tells it apart /// from a braced engine cell. fn echo_source(lang: &str, body: &str) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![lang.to_string()], LinkedHashMap::new()), text: body.to_string(), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Extract the marker text from a `raw_island(...)` block, else `None`. @@ -957,12 +957,12 @@ mod tests { /// blocks — the shape pre-engine sugaring produces for a cell /// carrying `#| label: fig-*`. fn fig_div(id: &str, content: Vec) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: (id.to_string(), Vec::new(), LinkedHashMap::new()), content, source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } #[test] @@ -1030,12 +1030,12 @@ mod tests { // must NOT be treated as an engine cell. Confirm // engine_cell_lang returns None. let attr = (String::new(), vec!["r".to_string()], LinkedHashMap::new()); - let block = Block::CodeBlock(CodeBlock { + let block = Block::CodeBlock(Box::new(CodeBlock { attr, text: "x <- 1".to_string(), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }); + })); assert_eq!(engine_cell_lang(&block), None); } @@ -1050,12 +1050,12 @@ mod tests { vec!["{{python}}".to_string()], LinkedHashMap::new(), ); - let block = Block::CodeBlock(CodeBlock { + let block = Block::CodeBlock(Box::new(CodeBlock { attr, text: "import matplotlib.pyplot as plt".to_string(), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }); + })); assert_eq!(engine_cell_lang(&block), None); } } diff --git a/crates/quarto-core/src/engine/cell_lang.rs b/crates/quarto-core/src/engine/cell_lang.rs index f768d04da5..a821454c96 100644 --- a/crates/quarto-core/src/engine/cell_lang.rs +++ b/crates/quarto-core/src/engine/cell_lang.rs @@ -5,7 +5,7 @@ //! desugar and emit stages and the unexecuted-cell counter all need it, and //! none of them is legacy. -use quarto_pandoc_types::{Block, CodeBlock}; +use quarto_pandoc_types::Block; /// Identify an "engine cell" — a Quarto-syntax fenced code block of /// the form ` ```{}` (e.g. `{r}`, `{python}`). The literal @@ -21,9 +21,10 @@ use quarto_pandoc_types::{Block, CodeBlock}; /// /// Returns the language token (without braces) when matched. pub fn engine_cell_lang(block: &Block) -> Option<&str> { - let Block::CodeBlock(CodeBlock { attr, .. }) = block else { + let Block::CodeBlock(code_block) = block else { return None; }; + let attr = &code_block.attr; for class in attr.1.iter() { if let Some(stripped) = class.strip_prefix('{').and_then(|s| s.strip_suffix('}')) { // `{{lang}}` strips to `{lang}` — still braced, so not a cell. diff --git a/crates/quarto-core/src/engine/jupyter/text_execute.rs b/crates/quarto-core/src/engine/jupyter/text_execute.rs index e52fd7a023..bb13fb1c66 100644 --- a/crates/quarto-core/src/engine/jupyter/text_execute.rs +++ b/crates/quarto-core/src/engine/jupyter/text_execute.rs @@ -1291,7 +1291,7 @@ mod tests { ); let make_cell = |lang: &str| { - Block::CodeBlock(AstCodeBlock { + Block::CodeBlock(Box::new(AstCodeBlock { attr: ( String::new(), vec![format!("{{{lang}}}")], @@ -1300,7 +1300,7 @@ mod tests { text: format!("-- {lang} code"), source_info: si.clone(), attr_source: AttrSourceInfo::empty(), - }) + })) }; let ast = Pandoc { meta: meta.clone(), diff --git a/crates/quarto-core/src/engine/knitr/label_reinsert.rs b/crates/quarto-core/src/engine/knitr/label_reinsert.rs index 6a92999d6d..5a01b18e25 100644 --- a/crates/quarto-core/src/engine/knitr/label_reinsert.rs +++ b/crates/quarto-core/src/engine/knitr/label_reinsert.rs @@ -272,17 +272,17 @@ mod tests { } fn cell(id: &str, body: &str) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: (id.to_string(), Vec::new(), LinkedHashMap::new()), - content: vec![Block::CodeBlock(CodeBlock { + content: vec![Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec!["r".to_string()], LinkedHashMap::new()), text: format!("{body}\n"), source_info: si(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn para(text: &str) -> Block { @@ -298,7 +298,7 @@ mod tests { } fn div(content: Vec) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( String::new(), vec!["callout".to_string()], @@ -307,7 +307,7 @@ mod tests { content, source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn bullet(items: Vec>) -> Block { @@ -318,7 +318,7 @@ mod tests { } fn ordered(items: Vec>) -> Block { - Block::OrderedList(OrderedList { + Block::OrderedList(Box::new(OrderedList { attr: ( 1, quarto_pandoc_types::ListNumberStyle::Default, @@ -326,7 +326,7 @@ mod tests { ), content: items, source_info: si(), - }) + })) } fn quote(content: Vec) -> Block { diff --git a/crates/quarto-core/src/engine/nested_cell_mask.rs b/crates/quarto-core/src/engine/nested_cell_mask.rs index 66021264d4..91176f678b 100644 --- a/crates/quarto-core/src/engine/nested_cell_mask.rs +++ b/crates/quarto-core/src/engine/nested_cell_mask.rs @@ -1162,14 +1162,14 @@ mod tests { let attr = || quarto_pandoc_types::empty_attr(); let attr_source = quarto_pandoc_types::AttrSourceInfo::empty; - let display_block = Block::CodeBlock(CodeBlock { + let display_block = Block::CodeBlock(Box::new(CodeBlock { attr: attr(), text: "```{r}\ncat(\"x\")\n```".to_string(), source_info: source.clone(), attr_source: attr_source(), - }); + })); - let table = Block::Table(quarto_pandoc_types::Table { + let table = Block::Table(Box::new(quarto_pandoc_types::Table { attr: attr(), caption: quarto_pandoc_types::Caption { short: None, @@ -1195,7 +1195,7 @@ mod tests { }, source_info: source.clone(), attr_source: attr_source(), - }); + })); let mut doc = Pandoc { blocks: vec![table], diff --git a/crates/quarto-core/src/engine/resolution.rs b/crates/quarto-core/src/engine/resolution.rs index ddbcf18d2a..911caaa168 100644 --- a/crates/quarto-core/src/engine/resolution.rs +++ b/crates/quarto-core/src/engine/resolution.rs @@ -1277,7 +1277,7 @@ mod tests { /// Create a CodeBlock with a braced language class `{lang}`. fn engine_cell(lang: &str) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), vec![format!("{{{}}}", lang)], @@ -1286,13 +1286,13 @@ mod tests { text: format!("# {lang} code"), source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Create a CodeBlock with a braced language class `{lang}` and an /// additional plain class (first_class). fn engine_cell_with_class(lang: &str, class: &str) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), vec![format!("{{{}}}", lang), class.to_string()], @@ -1301,7 +1301,7 @@ mod tests { text: format!("# {lang} {class} code"), source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Build an AST with the given top-level blocks. @@ -1640,12 +1640,12 @@ mod tests { source_info: si(), }), // A plain (non-braced) highlight fence: not an engine cell. - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec!["r".to_string()], LinkedHashMap::new()), text: "x <- 1".to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), ]); let meta = empty_meta(); @@ -1809,12 +1809,12 @@ mod tests { #[test] fn test_container_recursion() { let registry = mock_registry(vec![mock_knitr(), mock_jupyter()]); - let nested_div = Block::Div(Div { + let nested_div = Block::Div(Box::new(Div { attr: empty_attr(), content: vec![engine_cell("python")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let nested_list = Block::BulletList(BulletList { content: vec![vec![engine_cell("sql")]], source_info: si(), @@ -3932,7 +3932,7 @@ mod tests { #[test] fn test_doubled_brace_display_fence_is_not_computational() { let registry = mock_registry(vec![mock_knitr(), mock_jupyter()]); - let display_cell = Block::CodeBlock(CodeBlock { + let display_cell = Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), vec!["{{python}}".to_string()], @@ -3941,7 +3941,7 @@ mod tests { text: "#| label: fig-line-plot\nimport matplotlib.pyplot as plt".to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let ast = ast_with_blocks(vec![display_cell]); let meta = empty_meta(); diff --git a/crates/quarto-core/src/pandoc_filters/image_alt_encode.rs b/crates/quarto-core/src/pandoc_filters/image_alt_encode.rs index 3f2fa5bcfb..c54c09f545 100644 --- a/crates/quarto-core/src/pandoc_filters/image_alt_encode.rs +++ b/crates/quarto-core/src/pandoc_filters/image_alt_encode.rs @@ -147,7 +147,7 @@ mod tests { #[test] fn inline_and_figure_images_keep_their_alt_inlines() { - let figure = Block::Figure(Figure { + let figure = Block::Figure(Box::new(Figure { attr: (String::new(), vec![], LinkedHashMap::new()), caption: Caption { short: None, @@ -163,7 +163,7 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); assert_eq!( summary(vec![ para(vec![word("Text"), image("Alt D", &[])]), diff --git a/crates/quarto-core/src/project/book/merge.rs b/crates/quarto-core/src/project/book/merge.rs index 65f1a11fac..4d4b447529 100644 --- a/crates/quarto-core/src/project/book/merge.rs +++ b/crates/quarto-core/src/project/book/merge.rs @@ -127,11 +127,11 @@ fn marker_blocks(item: &BookRenderItem) -> [Block; 2] { ))], source_info: generated_source_info(), }); - let block_marker = Block::RawBlock(RawBlock { + let block_marker = Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: block_text, source_info: generated_source_info(), - }); + })); [inline_marker, block_marker] } @@ -205,7 +205,7 @@ fn build_divider_heading(title: &str) -> Block { }) { return Block::Header(header); } - Block::Header(Header { + Block::Header(Box::new(Header { level: 1, attr: quarto_pandoc_types::empty_attr(), content: vec![quarto_pandoc_types::Inline::Str(Str { @@ -214,7 +214,7 @@ fn build_divider_heading(title: &str) -> Block { })], source_info: generated_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// A chapter's front-matter `title:` as a level-1 heading, or `None` when @@ -225,13 +225,13 @@ fn front_matter_title_heading(meta: &ConfigValue) -> Option { return None; } if let quarto_pandoc_types::ConfigValueKind::PandocInlines(inlines) = &title.value { - return Some(Block::Header(Header { + return Some(Block::Header(Box::new(Header { level: 1, attr: quarto_pandoc_types::empty_attr(), content: inlines.clone(), source_info: generated_source_info(), attr_source: AttrSourceInfo::empty(), - })); + }))); } title.as_plain_text().map(|t| build_divider_heading(&t)) } @@ -258,13 +258,13 @@ fn add_chapter_heading(body: &mut Blocks, meta: &ConfigValue) { } _ => unreachable!("position() matched a Header"), }, - None => Block::Header(Header { + None => Block::Header(Box::new(Header { level: 1, attr: quarto_pandoc_types::empty_attr(), content: Vec::new(), source_info: generated_source_info(), attr_source: AttrSourceInfo::empty(), - }), + })), } }); body.insert(0, heading); @@ -370,7 +370,7 @@ pub fn merge_book_chapters( } if should_wrap_in_part_div(&item) { - blocks.push(Block::Div(Div { + blocks.push(Block::Div(Box::new(Div { attr: ( String::new(), vec!["quarto-book-part".to_string()], @@ -379,7 +379,7 @@ pub fn merge_book_chapters( content, source_info: generated_source_info(), attr_source: AttrSourceInfo::empty(), - })); + }))); } else { blocks.extend(content); } diff --git a/crates/quarto-core/src/revealjs/auto_stretch.rs b/crates/quarto-core/src/revealjs/auto_stretch.rs index 6e99b52d8f..6c04a7192a 100644 --- a/crates/quarto-core/src/revealjs/auto_stretch.rs +++ b/crates/quarto-core/src/revealjs/auto_stretch.rs @@ -541,7 +541,7 @@ mod tests { } fn header() -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level: 2, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![Inline::Str(Str { @@ -550,22 +550,22 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn section(classes: &[&str], content: Vec) -> Block { let mut cls = vec!["section".to_string()]; cls.extend(classes.iter().map(|s| s.to_string())); - Block::Div(Div { + Block::Div(Box::new(Div { attr: (String::new(), cls, LinkedHashMap::new()), content, source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn figure(blocks: Vec) -> Block { - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr: (String::new(), vec![], LinkedHashMap::new()), caption: Caption { short: None, @@ -575,13 +575,13 @@ mod tests { content: blocks, source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// A `Figure` with an `id` and a `caption.long` of `[Plain[caption]]`, /// mirroring what `![caption](x)` parses to. fn figure_with_caption(blocks: Vec, caption: Vec, id: &str) -> Block { - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr: (id.to_string(), vec![], LinkedHashMap::new()), caption: Caption { short: None, @@ -594,7 +594,7 @@ mod tests { content: blocks, source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn caption_inlines() -> Vec { @@ -857,7 +857,7 @@ mod tests { fn slide_with_aside_skipped() { // A peripheral aside (e.g. a coalesced footnote) suppresses auto-stretch, // matching Q1 (`aside:not(.notes)`). - let aside = Block::Div(Div { + let aside = Block::Div(Box::new(Div { attr: ( String::new(), vec!["aside".to_string()], @@ -869,7 +869,7 @@ mod tests { })])], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let blocks = vec![section( &[], vec![header(), para(vec![image(&[], &[])]), aside], @@ -881,7 +881,7 @@ mod tests { fn image_nested_in_column_div_skipped() { // A single image inside a `.column` is not a top-level standalone block, // so it is left alone (Q1 screens out layout/column/fragment parents). - let column = Block::Div(Div { + let column = Block::Div(Box::new(Div { attr: ( String::new(), vec!["column".to_string()], @@ -890,7 +890,7 @@ mod tests { content: vec![para(vec![image(&[], &[])])], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let blocks = vec![section(&[], vec![header(), column])]; assert!(!stretch_classes(blocks, true).contains(&"r-stretch".to_string())); } diff --git a/crates/quarto-core/src/revealjs/columns.rs b/crates/quarto-core/src/revealjs/columns.rs index b3f6a19ea2..d3f94416a4 100644 --- a/crates/quarto-core/src/revealjs/columns.rs +++ b/crates/quarto-core/src/revealjs/columns.rs @@ -179,9 +179,9 @@ mod tests { ); callout.set_slot( "content", - Slot::Blocks(vec![Block::Div(column_div(Some("40%"), None))]), + Slot::Blocks(vec![Block::Div(Box::new(column_div(Some("40%"), None)))]), ); - let mut blocks = vec![Block::Custom(callout)]; + let mut blocks = vec![Block::Custom(Box::new(callout))]; walk_blocks(&mut blocks); let Block::Custom(node) = &blocks[0] else { panic!("custom node survives"); diff --git a/crates/quarto-core/src/revealjs/footnotes.rs b/crates/quarto-core/src/revealjs/footnotes.rs index 169be72928..301759b7fb 100644 --- a/crates/quarto-core/src/revealjs/footnotes.rs +++ b/crates/quarto-core/src/revealjs/footnotes.rs @@ -218,11 +218,11 @@ fn coalesce_one_slide(slide: &mut Div, defs: &HashMap) { .iter() .map(|fn_id| defs.get(fn_id).cloned().unwrap_or_default()) .collect(); - let ol = Block::OrderedList(OrderedList { + let ol = Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: items, source_info: SourceInfo::generated(By::revealjs()), - }); + })); aside_content.push(classed_div("aside-footnotes", vec![ol])); } slide.content.push(classed_div("aside", aside_content)); @@ -230,22 +230,22 @@ fn coalesce_one_slide(slide: &mut Div, defs: &HashMap) { /// A `
` with no attributes wrapping `content`. fn plain_div(content: Blocks) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: (String::new(), Vec::new(), LinkedHashMap::new()), content, source_info: SourceInfo::generated(By::revealjs()), attr_source: AttrSourceInfo::empty(), - }) + })) } /// A `
` wrapping `content`. fn classed_div(class: &str, content: Blocks) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: (String::new(), vec![class.to_string()], LinkedHashMap::new()), content, source_info: SourceInfo::generated(By::revealjs()), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Remove footnote backlinks (``) from a definition's diff --git a/crates/quarto-core/src/revealjs/slides.rs b/crates/quarto-core/src/revealjs/slides.rs index 5f7d0c5579..04312c0595 100644 --- a/crates/quarto-core/src/revealjs/slides.rs +++ b/crates/quarto-core/src/revealjs/slides.rs @@ -54,12 +54,12 @@ fn slide_attr() -> Attr { } fn make_section(attr: Attr, content: Vec) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr, content, source_info: SourceInfo::generated(By::revealjs()), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Split a heading into (section attr, id-stripped header). The section gets @@ -144,13 +144,13 @@ pub fn build_reveal_slides(blocks: Vec, slide_level: usize) -> Vec b.flush_stack(); let (stack_attr, header) = section_attr_and_header(h); b.stack = Some((stack_attr, Vec::new())); - b.slide = Some((slide_attr(), vec![Block::Header(header)])); + b.slide = Some((slide_attr(), vec![Block::Header(Box::new(header))])); } Block::Header(h) if h.level == slide_level => { // A slide: vertical child of the open stack, else top-level. b.flush_slide(); let (slide_attr, header) = section_attr_and_header(h); - b.slide = Some((slide_attr, vec![Block::Header(header)])); + b.slide = Some((slide_attr, vec![Block::Header(Box::new(header))])); } Block::Header(_) => { // Level > slide_level → ordinary heading within the slide. @@ -177,7 +177,7 @@ mod tests { use quarto_pandoc_types::inline::{Inline, Str}; fn header(level: usize, id: &str, text: &str) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: (id.to_string(), vec![], LinkedHashMap::new()), content: vec![Inline::Str(Str { @@ -186,11 +186,11 @@ mod tests { })], source_info: SourceInfo::generated(By::revealjs()), attr_source: AttrSourceInfo::empty(), - }) + })) } fn header_with(level: usize, id: &str, classes: &[&str], text: &str) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: ( id.to_string(), @@ -203,7 +203,7 @@ mod tests { })], source_info: SourceInfo::generated(By::revealjs()), attr_source: AttrSourceInfo::empty(), - }) + })) } fn para(text: &str) -> Block { diff --git a/crates/quarto-core/src/revealjs/transform.rs b/crates/quarto-core/src/revealjs/transform.rs index 04e0801594..74da4160dd 100644 --- a/crates/quarto-core/src/revealjs/transform.rs +++ b/crates/quarto-core/src/revealjs/transform.rs @@ -96,12 +96,12 @@ fn classed_para(class: &str, text: String) -> Block { content: vec![str_inline(text)], source_info: SourceInfo::generated(By::revealjs()), }); - Block::Div(Div { + Block::Div(Box::new(Div { attr: (String::new(), vec![class.to_string()], LinkedHashMap::new()), content: vec![para], source_info: SourceInfo::generated(By::revealjs()), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Read a metadata field as plain text (joining array items with ", "). @@ -133,7 +133,7 @@ fn build_title_slide(meta: &quarto_pandoc_types::ConfigValue) -> Option { let mut content: Vec = Vec::new(); if let Some(title) = title { - content.push(Block::Header(Header { + content.push(Block::Header(Box::new(Header { level: 1, attr: ( String::new(), @@ -143,7 +143,7 @@ fn build_title_slide(meta: &quarto_pandoc_types::ConfigValue) -> Option { content: vec![str_inline(title)], source_info: SourceInfo::generated(By::revealjs()), attr_source: AttrSourceInfo::empty(), - })); + }))); } if let Some(subtitle) = subtitle { content.push(classed_para("subtitle", subtitle)); @@ -155,7 +155,7 @@ fn build_title_slide(meta: &quarto_pandoc_types::ConfigValue) -> Option { content.push(classed_para("date", date)); } - Some(Block::Div(Div { + Some(Block::Div(Box::new(Div { attr: ( "title-slide".to_string(), // `center` keeps the title slide vertically centered even though @@ -173,7 +173,7 @@ fn build_title_slide(meta: &quarto_pandoc_types::ConfigValue) -> Option { content, source_info: SourceInfo::generated(By::revealjs()), attr_source: AttrSourceInfo::empty(), - })) + }))) } #[cfg(test)] diff --git a/crates/quarto-core/src/stage/stages/equation_number.rs b/crates/quarto-core/src/stage/stages/equation_number.rs index 9e19eb0fa5..420a10a649 100644 --- a/crates/quarto-core/src/stage/stages/equation_number.rs +++ b/crates/quarto-core/src/stage/stages/equation_number.rs @@ -537,7 +537,7 @@ mod tests { .insert("content".to_string(), Slot::Inlines(vec![eq()])); let mut blocks = vec![ para(vec![eq()]), - Block::Div(Div { + Block::Div(Box::new(Div { attr: quarto_pandoc_types::attr::empty_attr(), content: vec![para(vec![Inline::Emph(Emph { content: vec![eq()], @@ -545,12 +545,12 @@ mod tests { })])], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), Block::BulletList(BulletList { content: vec![vec![para(vec![eq()])]], source_info: si(), }), - Block::Custom(custom), + Block::Custom(Box::new(custom)), ]; let out = walk(&mut blocks, NumberEncoding::TexTag); assert_eq!(out.encoded, 4); diff --git a/crates/quarto-core/src/stage/stages/include_expansion.rs b/crates/quarto-core/src/stage/stages/include_expansion.rs index 5e5c198559..6c6d4e78e2 100644 --- a/crates/quarto-core/src/stage/stages/include_expansion.rs +++ b/crates/quarto-core/src/stage/stages/include_expansion.rs @@ -1058,7 +1058,7 @@ mod tests { for (k, v) in kvs { attrs.insert(k.to_string(), v.to_string()); } - Block::CodeBlock(quarto_pandoc_types::block::CodeBlock { + Block::CodeBlock(Box::new(quarto_pandoc_types::block::CodeBlock { attr: ( String::new(), classes.iter().map(|c| c.to_string()).collect(), @@ -1067,7 +1067,7 @@ mod tests { text: text.to_string(), source_info: SourceInfo::for_test(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - }) + })) } /// Recognize on one line, for the strictness tests. diff --git a/crates/quarto-core/src/stage/stages/include_resolve.rs b/crates/quarto-core/src/stage/stages/include_resolve.rs index 518a0a2368..6feee8a60e 100644 --- a/crates/quarto-core/src/stage/stages/include_resolve.rs +++ b/crates/quarto-core/src/stage/stages/include_resolve.rs @@ -996,11 +996,11 @@ mod tests { // branch. fn raw_html_block(text: &str) -> Block { - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: text.to_string(), source_info: SourceInfo::for_test(), - }) + })) } fn para_of_raw_html(text: &str) -> Block { @@ -1059,12 +1059,12 @@ mod tests { #[test] fn text_holding_blocks_inside_div_recurses_into_container() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: quarto_pandoc_types::empty_attr(), content: vec![raw_html_block("")], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }); + })); let (header, diags) = resolve_text_blocks(blocks(vec![div])); assert!(diags.is_empty(), "unexpected diagnostics: {:?}", diags); assert_eq!(header.len(), 1); diff --git a/crates/quarto-core/src/stage/stages/inline_table_css.rs b/crates/quarto-core/src/stage/stages/inline_table_css.rs index ac3e425876..cf8056a2ab 100644 --- a/crates/quarto-core/src/stage/stages/inline_table_css.rs +++ b/crates/quarto-core/src/stage/stages/inline_table_css.rs @@ -181,11 +181,11 @@ mod tests {
x
"; fn raw(format: &str, text: &str) -> Block { - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: format.to_string(), text: text.to_string(), source_info: SourceInfo::for_test(), - }) + })) } fn div(attrs: &[(&str, &str)], content: Vec) -> Block { @@ -193,12 +193,12 @@ mod tests { for (k, v) in attrs { attr.2.insert(k.to_string(), v.to_string()); } - Block::Div(Div { + Block::Div(Box::new(Div { attr, content, source_info: SourceInfo::for_test(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - }) + })) } fn text(block: &Block) -> &str { diff --git a/crates/quarto-core/src/stage/stages/listing_item_info.rs b/crates/quarto-core/src/stage/stages/listing_item_info.rs index 25d8dbd9dc..fc23370301 100644 --- a/crates/quarto-core/src/stage/stages/listing_item_info.rs +++ b/crates/quarto-core/src/stage/stages/listing_item_info.rs @@ -591,13 +591,13 @@ mod tests { }) } fn heading(level: usize, content: Vec) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: empty_attr(), content, source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn img(src: &str) -> Inline { Inline::Image(Box::new(Image { diff --git a/crates/quarto-core/src/stage/stages/lmd_execute.rs b/crates/quarto-core/src/stage/stages/lmd_execute.rs index a46029355f..a8dd75446a 100644 --- a/crates/quarto-core/src/stage/stages/lmd_execute.rs +++ b/crates/quarto-core/src/stage/stages/lmd_execute.rs @@ -176,7 +176,7 @@ impl ListVisitor for Numberer<'_> { /// its (option-stripped) desugared Lua. Used for `echo: true` and for /// failures. fn displayed_block_cell(code: &str, source_info: &SourceInfo) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), vec![LMD_LANG.to_string()], @@ -185,7 +185,7 @@ fn displayed_block_cell(code: &str, source_info: &SourceInfo) -> Block { text: code.to_string(), source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// The displayed form of a failed inline cell: a plain `lmd` code span diff --git a/crates/quarto-core/src/stage/stages/math_js.rs b/crates/quarto-core/src/stage/stages/math_js.rs index 398bb51977..72865988d0 100644 --- a/crates/quarto-core/src/stage/stages/math_js.rs +++ b/crates/quarto-core/src/stage/stages/math_js.rs @@ -955,7 +955,7 @@ mod tests { .slots .insert("content".to_string(), Slot::Blocks(vec![inner_para])); - let math = run_and_get_math(vec![Block::Custom(callout)], empty_meta()).await; + let math = run_and_get_math(vec![Block::Custom(Box::new(callout))], empty_meta()).await; assert!( math.is_some(), "math inside CustomNode block-slot must trigger meta.math" @@ -968,12 +968,12 @@ mod tests { async fn math_inside_codeblock_is_not_math() { // Code block whose text happens to contain dollar-signs; this // is not an Inline::Math node, just a CodeBlock with a string. - let cb = Block::CodeBlock(quarto_pandoc_types::block::CodeBlock { + let cb = Block::CodeBlock(Box::new(quarto_pandoc_types::block::CodeBlock { attr: empty_attr(), text: "$x = y$".to_string(), source_info: SourceInfo::for_test(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); + })); let math = run_and_get_math(vec![cb], empty_meta()).await; assert!( diff --git a/crates/quarto-core/src/stage/stages/math_ml.rs b/crates/quarto-core/src/stage/stages/math_ml.rs index 0f01a6f937..34ad217b97 100644 --- a/crates/quarto-core/src/stage/stages/math_ml.rs +++ b/crates/quarto-core/src/stage/stages/math_ml.rs @@ -358,18 +358,18 @@ mod tests { Slot::Inlines(vec![math(MathType::InlineMath, "c^2")]), ); let mut blocks = vec![ - Block::Header(Header { + Block::Header(Box::new(Header { level: 1, attr: quarto_pandoc_types::attr::empty_attr(), content: vec![math(MathType::InlineMath, "h^2")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), para(vec![Inline::Note(quarto_pandoc_types::inline::Note { content: vec![para(vec![math(MathType::InlineMath, "n^2")])], source_info: si(), })]), - Block::Custom(custom), + Block::Custom(Box::new(custom)), ]; let out = convert_document(&mut blocks); assert_eq!((out.converted, out.left), (3, 0)); diff --git a/crates/quarto-core/src/stage/stages/pandoc_write.rs b/crates/quarto-core/src/stage/stages/pandoc_write.rs index 05329b1704..d42a1abd5f 100644 --- a/crates/quarto-core/src/stage/stages/pandoc_write.rs +++ b/crates/quarto-core/src/stage/stages/pandoc_write.rs @@ -1859,22 +1859,22 @@ mod tests { use yaml_rust2::Yaml; fn header(level: usize) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: quarto_pandoc_types::empty_attr(), content: vec![], source_info: SourceInfo::for_test(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) } fn div(content: Vec) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: quarto_pandoc_types::empty_attr(), content, source_info: SourceInfo::for_test(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) } /// pandoc-hybrid-typst Phase 1: a top-level level-1 heading means no diff --git a/crates/quarto-core/src/template.rs b/crates/quarto-core/src/template.rs index 98613694dd..98ad1d8e2b 100644 --- a/crates/quarto-core/src/template.rs +++ b/crates/quarto-core/src/template.rs @@ -2466,7 +2466,7 @@ mod tests { #[test] fn test_blocks_to_text_header() { - let blocks = vec![Block::Header(Header { + let blocks = vec![Block::Header(Box::new(Header { level: 1, attr: quarto_pandoc_types::attr::Attr::default(), content: vec![Inline::Str(Str { @@ -2475,19 +2475,19 @@ mod tests { })], source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = blocks_to_text(&blocks); assert_eq!(text, "Heading"); } #[test] fn test_blocks_to_text_code_block() { - let blocks = vec![Block::CodeBlock(CodeBlock { + let blocks = vec![Block::CodeBlock(Box::new(CodeBlock { attr: quarto_pandoc_types::attr::Attr::default(), text: "fn main() {}".to_string(), source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = blocks_to_text(&blocks); assert_eq!(text, "fn main() {}"); } @@ -2510,7 +2510,7 @@ mod tests { #[test] fn test_blocks_to_text_div() { - let blocks = vec![Block::Div(Div { + let blocks = vec![Block::Div(Box::new(Div { attr: quarto_pandoc_types::attr::Attr::default(), content: vec![Block::Paragraph(Paragraph { content: vec![Inline::Str(Str { @@ -2521,7 +2521,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = blocks_to_text(&blocks); assert_eq!(text, "div content"); } @@ -2548,7 +2548,7 @@ mod tests { #[test] fn test_blocks_to_text_ordered_list() { - let blocks = vec![Block::OrderedList(OrderedList { + let blocks = vec![Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Default, ListNumberDelim::Default), content: vec![vec![Block::Plain(Plain { content: vec![Inline::Str(Str { @@ -2558,7 +2558,7 @@ mod tests { source_info: dummy_source_info(), })]], source_info: dummy_source_info(), - })]; + }))]; let text = blocks_to_text(&blocks); assert!(text.contains("Item 1")); } diff --git a/crates/quarto-core/src/transforms/anchor_sections.rs b/crates/quarto-core/src/transforms/anchor_sections.rs index 3c4d4d9a83..1bfb8e3c79 100644 --- a/crates/quarto-core/src/transforms/anchor_sections.rs +++ b/crates/quarto-core/src/transforms/anchor_sections.rs @@ -284,7 +284,7 @@ mod tests { use quarto_source_map::SourceInfo; fn header(level: usize, id: &str, classes: &[&str]) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: ( id.to_string(), @@ -294,11 +294,11 @@ mod tests { content: vec![], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn div(id: &str, classes: &[&str], content: Blocks) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( id.to_string(), classes.iter().map(|c| c.to_string()).collect(), @@ -307,7 +307,7 @@ mod tests { content, source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn classes(b: &Block) -> Vec { diff --git a/crates/quarto-core/src/transforms/appendix.rs b/crates/quarto-core/src/transforms/appendix.rs index 6a8de51c64..4ca33abed5 100644 --- a/crates/quarto-core/src/transforms/appendix.rs +++ b/crates/quarto-core/src/transforms/appendix.rs @@ -295,7 +295,7 @@ fn title_inlines(title: &str, source_info: &SourceInfo) -> Vec { /// a `Vec` is not idempotent the way Quarto 1's `classList.add` is). fn appendix_heading(meta: &ConfigValue, term_key: &str, english: &str) -> Block { let source_info = SourceInfo::generated(By::appendix()); - Block::Header(Header { + Block::Header(Box::new(Header { level: 2, attr: ( String::new(), @@ -308,7 +308,7 @@ fn appendix_heading(meta: &ConfigValue, term_key: &str, english: &str) -> Block content: title_inlines(&appendix_title(meta, term_key, english), &source_info), source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Title the footnotes section in place. @@ -351,7 +351,7 @@ fn wrap_bibliography(bibliography: Block, meta: &ConfigValue) -> Block { // Create header for the bibliography section let header = appendix_heading(meta, "section-title-references", "References"); - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( "quarto-bibliography".to_string(), vec!["section".to_string()], @@ -360,12 +360,12 @@ fn wrap_bibliography(bibliography: Block, meta: &ConfigValue) -> Block { content: vec![header, bibliography], source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Create the appendix container div. fn create_appendix_container(sections: Blocks, style_class: &str) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( "quarto-appendix".to_string(), vec![style_class.to_string()], @@ -374,7 +374,7 @@ fn create_appendix_container(sections: Blocks, style_class: &str) -> Block { content: sections, source_info: SourceInfo::generated(By::appendix()), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Create license section from metadata. @@ -407,7 +407,7 @@ fn create_license_section(meta: &ConfigValue) -> Option { source_info: source_info.clone(), }); - Some(Block::Div(Div { + Some(Block::Div(Box::new(Div { attr: ( "quarto-reuse".to_string(), vec!["section".to_string()], @@ -416,7 +416,7 @@ fn create_license_section(meta: &ConfigValue) -> Option { content: vec![header, content], source_info, attr_source: AttrSourceInfo::empty(), - })) + }))) } /// Create copyright section from metadata. @@ -449,7 +449,7 @@ fn create_copyright_section(meta: &ConfigValue) -> Option { source_info: source_info.clone(), }); - Some(Block::Div(Div { + Some(Block::Div(Box::new(Div { attr: ( "quarto-copyright".to_string(), vec!["section".to_string()], @@ -458,7 +458,7 @@ fn create_copyright_section(meta: &ConfigValue) -> Option { content: vec![header, content], source_info, attr_source: AttrSourceInfo::empty(), - })) + }))) } /// Create citation section from metadata. @@ -508,7 +508,7 @@ fn create_citation_section(meta: &ConfigValue) -> Option { source_info: source_info.clone(), }); - Some(Block::Div(Div { + Some(Block::Div(Box::new(Div { attr: ( "quarto-citation".to_string(), vec!["section".to_string()], @@ -517,7 +517,7 @@ fn create_citation_section(meta: &ConfigValue) -> Option { content: vec![header, content], source_info, attr_source: AttrSourceInfo::empty(), - })) + }))) } #[cfg(test)] @@ -569,7 +569,7 @@ mod tests { } fn make_appendix_div(id: &str, content: &str) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( id.to_string(), vec!["appendix".to_string()], @@ -581,7 +581,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Metadata carrying a resolved `quarto.language` table, shaped exactly as @@ -606,7 +606,7 @@ mod tests { /// `make_footnotes_section` helper omits the rule, which is precisely the /// element the appendix transform has to strip (bd-v9zs83zj). fn make_footnotes_section_with_rule() -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( "footnotes".to_string(), vec!["footnotes".to_string(), "section".to_string()], @@ -623,7 +623,7 @@ mod tests { ], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// The `Header` an appendix section leads with. @@ -676,7 +676,7 @@ mod tests { } fn make_footnotes_section() -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( "footnotes".to_string(), vec!["footnotes".to_string(), "section".to_string()], @@ -688,7 +688,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn meta_entry(key: &str, value: ConfigValue) -> ConfigMapEntry { @@ -704,7 +704,7 @@ mod tests { } fn make_bibliography() -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( "refs".to_string(), vec!["references".to_string()], @@ -716,7 +716,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } #[tokio::test] diff --git a/crates/quarto-core/src/transforms/brace_diagram_cell_diagnostics.rs b/crates/quarto-core/src/transforms/brace_diagram_cell_diagnostics.rs index bfa50e8a41..ce51791276 100644 --- a/crates/quarto-core/src/transforms/brace_diagram_cell_diagnostics.rs +++ b/crates/quarto-core/src/transforms/brace_diagram_cell_diagnostics.rs @@ -129,12 +129,12 @@ mod tests { use quarto_source_map::SourceInfo; fn cell(class: &str) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![class.to_string()], Default::default()), text: "A --> B".to_string(), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn codes(blocks: &Blocks) -> Vec> { @@ -159,12 +159,12 @@ mod tests { #[test] fn nested_cells_warn() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: Default::default(), content: vec![cell("{mermaid}")], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }); + })); assert_eq!(codes(&vec![div]).len(), 1); } } diff --git a/crates/quarto-core/src/transforms/callout.rs b/crates/quarto-core/src/transforms/callout.rs index 673e76e0af..4d7f7850c0 100644 --- a/crates/quarto-core/src/transforms/callout.rs +++ b/crates/quarto-core/src/transforms/callout.rs @@ -141,7 +141,7 @@ fn transform_block( // Then check if this div is a callout and convert it if let Some(callout_type) = extract_callout_type(&div.attr) { let custom = convert_div_to_callout(div, &callout_type, registry, diagnostics); - *block = Block::Custom(custom); + *block = Block::Custom(Box::new(custom)); } } Block::Table(table) => { @@ -836,7 +836,7 @@ mod tests { async fn test_convert_simple_callout() { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: callout_attr("warning"), content: vec![Block::Paragraph(Paragraph { content: vec![quarto_pandoc_types::inline::Inline::Str(Str { @@ -847,7 +847,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let project = make_test_project(); @@ -875,10 +875,10 @@ mod tests { async fn test_convert_callout_with_title() { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: callout_attr("tip"), content: vec![ - Block::Header(Header { + Block::Header(Box::new(Header { level: 2, attr: empty_attr(), content: vec![quarto_pandoc_types::inline::Inline::Str(Str { @@ -887,7 +887,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }), + })), Block::Paragraph(Paragraph { content: vec![quarto_pandoc_types::inline::Inline::Str(Str { text: "Tip content".to_string(), @@ -898,7 +898,7 @@ mod tests { ], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let project = make_test_project(); @@ -941,7 +941,7 @@ mod tests { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::BlockQuote(quarto_pandoc_types::block::BlockQuote { - content: vec![Block::Div(Div { + content: vec![Block::Div(Box::new(Div { attr: callout_attr("note"), content: vec![Block::Paragraph(Paragraph { content: vec![quarto_pandoc_types::inline::Inline::Str(Str { @@ -952,7 +952,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: dummy_source_info(), })], }; @@ -996,7 +996,7 @@ mod tests { let mut ast = quarto_pandoc_types::pandoc::Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: ( "nte-important".into(), vec!["callout-note".into()], @@ -1011,7 +1011,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; CalloutTransform::new() @@ -1042,7 +1042,7 @@ mod tests { let mut ast = quarto_pandoc_types::pandoc::Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: callout_attr("note"), content: vec![Block::Paragraph(Paragraph { content: vec![quarto_pandoc_types::inline::Inline::Str(Str { @@ -1053,7 +1053,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; CalloutTransform::new() @@ -1080,7 +1080,7 @@ mod tests { let mut ast = quarto_pandoc_types::pandoc::Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: ( "my-callout".into(), vec!["callout-warning".into()], @@ -1095,7 +1095,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; CalloutTransform::new() diff --git a/crates/quarto-core/src/transforms/callout_resolve.rs b/crates/quarto-core/src/transforms/callout_resolve.rs index 575cc39552..de567aed82 100644 --- a/crates/quarto-core/src/transforms/callout_resolve.rs +++ b/crates/quarto-core/src/transforms/callout_resolve.rs @@ -202,7 +202,7 @@ fn resolve_block(block: &mut Block, counter: &mut u32, env: &ResolveEnv) { // Then check if this is a Callout that should be resolved if custom.type_name == "Callout" { let resolved_div = resolve_callout(custom, counter, env.terms, env.chapter_seed); - *block = Block::Div(resolved_div); + *block = Block::Div(Box::new(resolved_div)); } } // Other block types don't contain nested blocks @@ -437,10 +437,10 @@ fn build_titled_content( // Always emit the icon container (Q1-parity, callouts.lua:254-262); // when icon=false the inner `` carries `no-icon` so CSS can hide it. - header_content.push(Block::Div(icon_container_div(source_info, icon))); + header_content.push(Block::Div(Box::new(icon_container_div(source_info, icon)))); // Title container. - header_content.push(Block::Div(Div { + header_content.push(Block::Div(Box::new(Div { attr: make_attr(&["callout-title-container", "flex-fill"]), content: vec![Block::Plain(Plain { content: title_inlines, @@ -448,7 +448,7 @@ fn build_titled_content( })], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - })); + }))); // Header: classes + (when collapsible) Bootstrap toggle attrs. let mut header_classes: Vec = vec![ @@ -490,12 +490,12 @@ fn build_titled_content( source_info: source_info.clone(), })); } - let header_div = Block::Div(Div { + let header_div = Block::Div(Box::new(Div { attr: (String::new(), header_classes, header_attrs), content: header_content, source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); // Body. With collapse, wrap the body-container in a // `.callout-collapse.collapse[.show]` div; without collapse, the @@ -516,7 +516,7 @@ fn build_titled_content( // titled+collapse output). let mut body_with_class = body_inner_div; body_with_class.attr.1.push("callout-body".to_string()); - Block::Div(Div { + Block::Div(Box::new(Div { // Wrapper id is the clean `callout-N` (no -contents // suffix); aria-controls on the header points at this id. attr: ( @@ -527,7 +527,7 @@ fn build_titled_content( content: vec![absorb_section_wrapper(body_with_class)], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }) + })) } else { let mut body_with_class = body_inner_div; body_with_class.attr.1.push("callout-body".to_string()); @@ -545,7 +545,7 @@ fn build_untitled_content(source_info: &SourceInfo, icon: bool, body_inner_div: // Always emit the icon container (Q1-parity); icon=false adds // the `no-icon` co-class to the inner ``. let body_content = vec![ - Block::Div(icon_container_div(source_info, icon)), + Block::Div(Box::new(icon_container_div(source_info, icon))), absorb_section_wrapper(body_inner_div), ]; @@ -555,7 +555,7 @@ fn build_untitled_content(source_info: &SourceInfo, icon: bool, body_inner_div: source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), }; - vec![Block::Div(body_outer)] + vec![Block::Div(Box::new(body_outer))] } /// Build the icon container div. The container is ALWAYS emitted @@ -742,7 +742,7 @@ mod tests { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Custom(custom)], + blocks: vec![Block::Custom(Box::new(custom))], }; let project = make_test_project(); @@ -812,7 +812,7 @@ mod tests { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Custom(custom)], + blocks: vec![Block::Custom(Box::new(custom))], }; let project = make_test_project(); @@ -915,7 +915,7 @@ mod tests { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::BlockQuote(quarto_pandoc_types::block::BlockQuote { - content: vec![Block::Custom(custom)], + content: vec![Block::Custom(Box::new(custom))], source_info: dummy_source_info(), })], }; diff --git a/crates/quarto-core/src/transforms/code_block_generate.rs b/crates/quarto-core/src/transforms/code_block_generate.rs index 4824473809..c2edd22851 100644 --- a/crates/quarto-core/src/transforms/code_block_generate.rs +++ b/crates/quarto-core/src/transforms/code_block_generate.rs @@ -364,7 +364,7 @@ mod tests { for (k, v) in kvs { kv_map.insert(k.to_string(), v.to_string()); } - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), classes.into_iter().map(String::from).collect(), @@ -373,7 +373,7 @@ mod tests { text: text.to_string(), source_info: source_info_at(0, 0, text.len()), attr_source: AttrSourceInfo::empty(), - }) + })) } fn make_test_project() -> ProjectContext { @@ -851,12 +851,12 @@ mod tests { blocks: vec![Block::BlockQuote(BlockQuote { content: vec![ cb_inside_blockquote, - Block::Div(Div { + Block::Div(Box::new(Div { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![cb_inside_div], source_info: source_info_at(0, 100, 200), attr_source: AttrSourceInfo::empty(), - }), + })), ], source_info: source_info_at(0, 0, 300), })], @@ -964,7 +964,7 @@ mod tests { ); let mut ast = Pandoc { meta: ConfigValue::default(), - blocks: vec![Block::Custom(callout)], + blocks: vec![Block::Custom(Box::new(callout))], }; let project = make_test_project(); diff --git a/crates/quarto-core/src/transforms/code_block_render.rs b/crates/quarto-core/src/transforms/code_block_render.rs index f2eb6d5b0d..15fdbbfd88 100644 --- a/crates/quarto-core/src/transforms/code_block_render.rs +++ b/crates/quarto-core/src/transforms/code_block_render.rs @@ -157,11 +157,11 @@ fn wrap_in_place(block: &mut Block, decoration: &CodeBlockDecoration) { // info has zero rendered output and zero cost; it's the cheapest // sentinel we can construct. let source_info = block.source_info().clone(); - let placeholder = Block::RawBlock(RawBlock { + let placeholder = Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: String::new(), source_info: source_info.clone(), - }); + })); let mut inner: Block = std::mem::replace(block, placeholder); // Layer 1 (Phase 1): filename header. When present, wrap `inner` @@ -191,7 +191,7 @@ fn wrap_with_filename( filename: &str, source_info: quarto_source_map::SourceInfo, ) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( String::new(), vec!["code-with-filename".to_string()], @@ -200,7 +200,7 @@ fn wrap_with_filename( content: vec![make_filename_header(filename, source_info.clone()), inner], source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Build a `
` wrapper around @@ -208,7 +208,7 @@ fn wrap_with_filename( /// Hover-vs-always visibility is controlled by the ported SCSS, not /// by the markup. fn wrap_with_copy_scaffold(inner: Block, source_info: quarto_source_map::SourceInfo) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( String::new(), vec!["code-copy-outer-scaffold".to_string()], @@ -217,7 +217,7 @@ fn wrap_with_copy_scaffold(inner: Block, source_info: quarto_source_map::SourceI content: vec![inner, make_copy_button(source_info.clone())], source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Build the copy-button RawBlock. The `title=` attribute drives the @@ -237,11 +237,11 @@ fn make_copy_button(source_info: quarto_source_map::SourceInfo) -> Block { \ " .to_string(); - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text, source_info, - }) + })) } /// Build the filename-header sub-block. Emitted as a `RawBlock("html", …)` @@ -255,11 +255,11 @@ fn make_filename_header(filename: &str, source_info: quarto_source_map::SourceIn "
{}
", escaped ); - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text, source_info, - }) + })) } /// Minimal HTML escape — enough for an attribute / element text @@ -305,12 +305,12 @@ mod tests { for (k, v) in kvs { kv_map.insert(k.to_string(), v.to_string()); } - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec!["python".to_string()], kv_map), text: text.to_string(), source_info: source_info_at(0, 0, text.len()), attr_source: AttrSourceInfo::empty(), - }) + })) } fn make_test_project() -> ProjectContext { diff --git a/crates/quarto-core/src/transforms/components_resolve.rs b/crates/quarto-core/src/transforms/components_resolve.rs index c1965e11aa..9eda54f850 100644 --- a/crates/quarto-core/src/transforms/components_resolve.rs +++ b/crates/quarto-core/src/transforms/components_resolve.rs @@ -523,11 +523,11 @@ fn attach_ast_payloads(ast: &mut Pandoc, decls: &[ComponentDecl]) { let text = ast_script(&json[0]); div.content.insert( 0, - Block::RawBlock(quarto_pandoc_types::block::RawBlock { + Block::RawBlock(Box::new(quarto_pandoc_types::block::RawBlock { format: "html".to_string(), text, source_info: div.source_info.clone(), - }), + })), ); } }); @@ -725,12 +725,12 @@ mod tests { } fn div(id: &str, classes: &[&str], content: Vec) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: attr(id, classes), content, source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn para(text: &str) -> Block { @@ -1148,12 +1148,12 @@ mod tests { #[tokio::test] async fn content_ast_prepends_the_node_json_without_the_props_script() { let yaml = "components:\n - name: gallery\n src: gallery.js\n content: ast\n"; - let props = Block::RawBlock(quarto_pandoc_types::block::RawBlock { + let props = Block::RawBlock(Box::new(quarto_pandoc_types::block::RawBlock { format: "html".into(), text: "" .into(), source_info: SourceInfo::for_test(), - }); + })); let ast = Pandoc { meta: meta(yaml), blocks: vec![ diff --git a/crates/quarto-core/src/transforms/conditional_content.rs b/crates/quarto-core/src/transforms/conditional_content.rs index 36249f1da1..747c503b9a 100644 --- a/crates/quarto-core/src/transforms/conditional_content.rs +++ b/crates/quarto-core/src/transforms/conditional_content.rs @@ -684,7 +684,7 @@ mod tests { } fn div(classes: &[&str], kvs: &[(&str, &str)], text: &str) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: attr(classes, kvs), content: vec![Block::Plain(quarto_pandoc_types::block::Plain { content: vec![Inline::Str(quarto_pandoc_types::inline::Str { @@ -695,7 +695,7 @@ mod tests { })], source_info: SourceInfo::generated(By::unknown()), attr_source: AttrSourceInfo::empty(), - }) + })) } fn run( @@ -910,12 +910,12 @@ mod tests { // `adv` is not active, so the inner block is removed; the outer // condition holds, so the outer's *other* content survives. let inner = div(&["content-visible"], &[("when-profile", "adv")], "INNER"); - let outer = Block::Div(Div { + let outer = Block::Div(Box::new(Div { attr: attr(&["content-visible"], &[("when-format", "html")]), content: vec![inner, plain("OUTER")], source_info: SourceInfo::generated(By::unknown()), attr_source: AttrSourceInfo::empty(), - }); + })); let mut blocks = vec![outer]; run(&mut blocks, "html", &[], &meta); assert!(!texts(&blocks).contains("INNER"), "inner removed"); @@ -1271,13 +1271,13 @@ mod tests { }))], source_info: quarto_source_map::SourceInfo::generated(quarto_source_map::By::unknown()), }); - let hidden_codeblock = Block::CodeBlock(CodeBlock { + let hidden_codeblock = Block::CodeBlock(Box::new(CodeBlock { attr: attr(&["content-hidden"], &[("when-format", "docx")]), text: "HIDDEN-CODEBLOCK".to_string(), source_info: quarto_source_map::SourceInfo::generated(quarto_source_map::By::unknown()), attr_source: AttrSourceInfo::empty(), - }); - let nested_in_custom = Block::Custom( + })); + let nested_in_custom = Block::Custom(Box::new( CustomNode::new( "test-custom", attr(&[], &[]), @@ -1291,7 +1291,7 @@ mod tests { "VISIBLE-IN-CUSTOM", ))), ), - ); + )); let mut blocks = vec![ hidden_div, @@ -1632,7 +1632,7 @@ mod tests { "SECRET", )])]), ); - let mut blocks = vec![Block::Custom(outer)]; + let mut blocks = vec![Block::Custom(Box::new(outer))]; let meta = empty_meta(); run(&mut blocks, "html", &[], &meta); diff --git a/crates/quarto-core/src/transforms/crossref_index.rs b/crates/quarto-core/src/transforms/crossref_index.rs index 989c4636f2..ab14728a3d 100644 --- a/crates/quarto-core/src/transforms/crossref_index.rs +++ b/crates/quarto-core/src/transforms/crossref_index.rs @@ -626,7 +626,7 @@ mod tests { } fn header(level: usize, id: &str, text: &str) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: attr_id(id), content: vec![Inline::Str(Str { @@ -635,11 +635,11 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn header_with_class(level: usize, id: &str, class: &str, text: &str) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: ( id.to_string(), @@ -652,19 +652,19 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn fig_div(id: &str, caption: &str) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: attr_id(id), content: vec![ - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec!["python".into()], LinkedHashMap::new()), text: "x=1".into(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), Block::Paragraph(Paragraph { content: vec![Inline::Str(Str { text: caption.into(), @@ -675,7 +675,7 @@ mod tests { ], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn para(text: &str) -> Block { @@ -692,7 +692,7 @@ mod tests { /// whose first child is the header with its id moved onto the div (the /// header's own id is empty). fn section_div(id: &str, level: usize, children: Vec) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( id.to_string(), vec!["section".to_string(), format!("level{level}")], @@ -701,7 +701,7 @@ mod tests { content: children, source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Run sugaring + indexing end-to-end on a block list and return the @@ -1190,7 +1190,7 @@ mod tests { fig_div("fig-one", "f1"), fig_div("fig-two", "f2"), // tbl has its own counter — wrap in a tbl-prefix div. - Block::Div(Div { + Block::Div(Box::new(Div { attr: attr_id("tbl-one"), content: vec![Block::Paragraph(Paragraph { content: vec![], @@ -1198,7 +1198,7 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), ]) .await; assert_eq!(idx.get("fig-one").unwrap().order.order, 1); diff --git a/crates/quarto-core/src/transforms/crossref_render.rs b/crates/quarto-core/src/transforms/crossref_render.rs index 7eade37cbb..731552c419 100644 --- a/crates/quarto-core/src/transforms/crossref_render.rs +++ b/crates/quarto-core/src/transforms/crossref_render.rs @@ -196,16 +196,16 @@ fn render_block(block: &mut Block, terms: Option<&LanguageTerms>, fs: &mut Float if needs_wrap { let Block::Figure(f) = std::mem::replace( block, - Block::Div(Div { + Block::Div(Box::new(Div { attr: (String::new(), Vec::new(), hashlink::LinkedHashMap::new()), content: Vec::new(), source_info: SourceInfo::generated(quarto_source_map::By::unknown()), attr_source: AttrSourceInfo::empty(), - }), + })), ) else { unreachable!("guarded by needs_wrap"); }; - *block = wrap_standalone_figure(f); + *block = wrap_standalone_figure(*f); } } } @@ -290,12 +290,12 @@ fn wrap_standalone_figure(mut f: Figure) -> Block { kvs.insert("style".to_string(), style); } let source_info = f.source_info.clone(); - Block::Div(Div { + Block::Div(Box::new(Div { attr: (id, classes, kvs), - content: vec![Block::Figure(f)], + content: vec![Block::Figure(Box::new(f))], source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Swap out a `CustomNode` in place with a placeholder, returning the @@ -779,12 +779,12 @@ fn render_float_ref_target(node: CustomNode, fs: &mut FloatState) -> Block { let mut wrapper_kvs: hashlink::LinkedHashMap = hashlink::LinkedHashMap::new(); wrapper_kvs.insert("aria-describedby".to_string(), caption_id.clone()); - let content_wrapper = Block::Div(Div { + let content_wrapper = Block::Div(Box::new(Div { attr: (String::new(), Vec::new(), wrapper_kvs), content, source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); // Inner
: quarto-float + quarto-float-, and the // data-qf-* kvs both writers use to synthesize the
. @@ -798,7 +798,7 @@ fn render_float_ref_target(node: CustomNode, fs: &mut FloatState) -> Block { if is_uncaptioned { fig_kvs.insert("data-qf-uncaptioned".to_string(), "1".to_string()); } - let figure = Block::Figure(Figure { + let figure = Block::Figure(Box::new(Figure { attr: ( String::new(), vec![ @@ -815,7 +815,7 @@ fn render_float_ref_target(node: CustomNode, fs: &mut FloatState) -> Block { content: vec![content_wrapper], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); // Outer div: user classes + the Q1 taxonomy + forwarded classes. user_classes.extend([ @@ -831,18 +831,18 @@ fn render_float_ref_target(node: CustomNode, fs: &mut FloatState) -> Block { if let Some(style) = style { user_kvs.insert("style".to_string(), style); } - return Block::Div(Div { + return Block::Div(Box::new(Div { attr: (identifier, user_classes, user_kvs), content: vec![figure], source_info, attr_source: AttrSourceInfo::empty(), - }); + })); } if ref_type == "fig" { // Prefer Pandoc's native Figure so the HTML writer emits // `
...
` with the id. - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr: node.attr, caption: Caption { short: caption_short, @@ -852,7 +852,7 @@ fn render_float_ref_target(node: CustomNode, fs: &mut FloatState) -> Block { content, source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } else { // Div wrapper: content + numbered-caption paragraph. let mut body = content; @@ -860,12 +860,12 @@ fn render_float_ref_target(node: CustomNode, fs: &mut FloatState) -> Block { body.extend(numbered_caption); } let _ = identifier; // id is on node.attr already - Block::Div(Div { + Block::Div(Box::new(Div { attr: node.attr, content: body, source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } } @@ -953,12 +953,12 @@ fn render_theorem(node: CustomNode, fs: &FloatState) -> Block { let content = ensure_leading_paragraph_nbsp(content, source_info.clone()); let body = prepend_theorem_label(content, label, source_info.clone()); - Block::Div(Div { + Block::Div(Box::new(Div { attr, content: body, source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Map a theorem ref-type prefix to its Q1 env name (which doubles as @@ -1168,12 +1168,12 @@ fn render_proof(node: CustomNode, terms: Option<&LanguageTerms>) -> Block { let body = prepend_theorem_label(content, label, source_info.clone()); - Block::Div(Div { + Block::Div(Box::new(Div { attr, content: body, source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Convert an Equation custom node into a `Span(id=...)` containing the @@ -1538,20 +1538,20 @@ mod tests { } fn fig_div(id: &str, cap: &str) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: attr_id(id), content: vec![ - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec!["python".into()], LinkedHashMap::new()), text: "x=1".into(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), para(cap), ], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } async fn run_full(blocks: Vec) -> Pandoc { @@ -1626,13 +1626,13 @@ mod tests { } fn header(level: usize, id: &str, text: &str) -> Block { - Block::Header(quarto_pandoc_types::block::Header { + Block::Header(Box::new(quarto_pandoc_types::block::Header { level, attr: attr_id(id), content: vec![str_inline(text)], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// `crossref: {chapters: }` document metadata. @@ -1752,7 +1752,7 @@ mod tests { #[tokio::test] async fn table_target_renders_to_div_with_prefixed_caption() { use quarto_pandoc_types::table::{Table, TableBody, TableFoot, TableHead}; - let table = Block::Table(Table { + let table = Block::Table(Box::new(Table { attr: (String::new(), Vec::new(), LinkedHashMap::new()), caption: Caption { short: None, @@ -1782,13 +1782,13 @@ mod tests { }, source_info: si(), attr_source: AttrSourceInfo::empty(), - }); - let blocks = vec![Block::Div(Div { + })); + let blocks = vec![Block::Div(Box::new(Div { attr: attr_id("tbl-one"), content: vec![table, para("Table caption")], source_info: si(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; let ast = run_full(blocks).await; // bd-hcp8m3ve: table floats are figure-wrapped now; the table lives // in the aria wrapper and the caption is a real Figure caption with @@ -1899,7 +1899,7 @@ mod tests { attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), })); - let blocks = vec![Block::Div(Div { + let blocks = vec![Block::Div(Box::new(Div { attr: attr_id("fig-a"), content: vec![ Block::Paragraph(Paragraph { @@ -1910,7 +1910,7 @@ mod tests { ], source_info: si(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; let ast = run_full(blocks).await; let (outer, fig) = float_shape(&ast.blocks[0]); assert_classes(&outer.attr, &["quarto-figure-left"], "outer div"); @@ -1951,20 +1951,20 @@ mod tests { // image's. let mut cb_attr: LinkedHashMap = LinkedHashMap::new(); cb_attr.insert("fig-align".to_string(), "right".to_string()); - let blocks = vec![Block::Div(Div { + let blocks = vec![Block::Div(Box::new(Div { attr: attr_id("fig-d"), content: vec![ - Block::CodeBlock(quarto_pandoc_types::block::CodeBlock { + Block::CodeBlock(Box::new(quarto_pandoc_types::block::CodeBlock { attr: (String::new(), vec!["mermaid".to_string()], cb_attr), text: "flowchart LR".to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), para("Cap"), ], source_info: si(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; let ast = run_full(blocks).await; let (outer, fig) = float_shape(&ast.blocks[0]); assert_classes(&outer.attr, &["quarto-figure-right"], "outer div"); @@ -1989,7 +1989,7 @@ mod tests { #[tokio::test] async fn table_target_renders_q1_float_shape() { use quarto_pandoc_types::table::{Table, TableBody, TableFoot, TableHead}; - let table = Block::Table(Table { + let table = Block::Table(Box::new(Table { attr: (String::new(), Vec::new(), LinkedHashMap::new()), caption: Caption { short: None, @@ -2019,13 +2019,13 @@ mod tests { }, source_info: si(), attr_source: AttrSourceInfo::empty(), - }); - let blocks = vec![Block::Div(Div { + })); + let blocks = vec![Block::Div(Box::new(Div { attr: attr_id("tbl-one"), content: vec![table, para("Table caption")], source_info: si(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; let ast = run_full(blocks).await; let (outer, fig) = float_shape(&ast.blocks[0]); assert_eq!(outer.attr.0, "tbl-one"); @@ -2085,7 +2085,7 @@ mod tests { #[tokio::test] async fn table_float_clears_the_tables_own_caption() { use quarto_pandoc_types::table::{Table, TableBody, TableFoot, TableHead}; - let table = Block::Table(Table { + let table = Block::Table(Box::new(Table { attr: (String::new(), Vec::new(), LinkedHashMap::new()), caption: Caption { short: None, @@ -2115,13 +2115,13 @@ mod tests { }, source_info: si(), attr_source: AttrSourceInfo::empty(), - }); - let blocks = vec![Block::Div(Div { + })); + let blocks = vec![Block::Div(Box::new(Div { attr: attr_id("tbl-one"), content: vec![table], source_info: si(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; let ast = run_full(blocks).await; let (_outer, fig) = float_shape(&ast.blocks[0]); @@ -2161,7 +2161,7 @@ mod tests { attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), })); - let figure = Block::Figure(Figure { + let figure = Block::Figure(Box::new(Figure { attr: (String::new(), Vec::new(), LinkedHashMap::new()), caption: Caption { short: None, @@ -2174,7 +2174,7 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let ast = run_full(vec![figure]).await; let Block::Div(outer) = &ast.blocks[0] else { panic!("expected wrapper Div, got {:?}", ast.blocks[0]); @@ -2214,7 +2214,7 @@ mod tests { // pass-through, after the kitchen-sink fixture caught sections being // swallowed into `quarto-float-sec` figures. They now hold for the // stronger reason that the misclassification never happens.) - let blocks = vec![Block::Div(Div { + let blocks = vec![Block::Div(Box::new(Div { attr: ( "sec-x".to_string(), vec!["section".to_string()], @@ -2223,7 +2223,7 @@ mod tests { content: vec![para("Section body text")], source_info: si(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; let ast = run_full(blocks).await; let Block::Div(d) = &ast.blocks[0] else { panic!("expected section Div, got {:?}", ast.blocks[0]); @@ -2249,12 +2249,12 @@ mod tests { async fn caption_id_collides_with_user_id_and_disambiguates() { // A user-authored element already owns "fig-1-caption": the generated // figcaption id must disambiguate (this replaces Q1's uuid suffix). - let user_div = Block::Div(Div { + let user_div = Block::Div(Box::new(Div { attr: attr_id("fig-1-caption"), content: vec![para("mine")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let ast = run_full(vec![fig_div("fig-1", "Cap"), user_div]).await; let (_outer, fig) = float_shape(&ast.blocks[0]); assert_eq!( @@ -2278,17 +2278,17 @@ mod tests { async fn uncaptioned_float_gets_uncaptioned_kv_and_label_caption() { // Q1: an uncaptioned float's figcaption holds just the label and the // figcaption gains `quarto-uncaptioned` (via the kv here). - let blocks = vec![Block::Div(Div { + let blocks = vec![Block::Div(Box::new(Div { attr: attr_id("fig-bare"), - content: vec![Block::CodeBlock(CodeBlock { + content: vec![Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec!["python".into()], LinkedHashMap::new()), text: "x=1".into(), source_info: si(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: si(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; let ast = run_full(blocks).await; let (_outer, fig) = float_shape(&ast.blocks[0]); assert_eq!( @@ -2961,17 +2961,17 @@ mod tests { #[tokio::test] async fn float_ref_target_with_no_caption_renders_figure_with_empty_caption() { - let blocks = vec![Block::Div(Div { + let blocks = vec![Block::Div(Box::new(Div { attr: attr_id("fig-bare"), - content: vec![Block::CodeBlock(CodeBlock { + content: vec![Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec!["python".into()], LinkedHashMap::new()), text: "x=1".into(), source_info: si(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: si(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; let ast = run_full(blocks).await; // HTML float shape (bd-hcp8m3ve): outer div carries the id; the // uncaptioned float still gets a label-only caption + the @@ -3043,12 +3043,12 @@ mod tests { // bd-n3sark9b: a caption whose first block carries no inlines // (e.g. a CodeBlock) must not silently lose its prefix. Insert a // label-only Plain in front, mirroring `prepend_theorem_label`. - let cap = vec![Block::CodeBlock(CodeBlock { + let cap = vec![Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), Vec::new(), LinkedHashMap::new()), text: "x".into(), source_info: si(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; let out = prefix_caption(cap, "Figure", Some("3".to_string())); assert_eq!(out.len(), 2); let Block::Plain(p) = &out[0] else { @@ -3073,7 +3073,7 @@ mod tests { attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), })); - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr: attr_id(id), caption: Caption { short: None, @@ -3089,7 +3089,7 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// First caption block's inlines, whichever of Plain / Paragraph it is. @@ -3130,7 +3130,7 @@ mod tests { // bd-n3sark9b: `Div(#tbl-x) > Table` where the Table's own caption // is `[Plain[...]]` (Pandoc's convention, and what the // `: cap {#tbl-x}` form desugars to). - let table = Block::Table(quarto_pandoc_types::table::Table { + let table = Block::Table(Box::new(quarto_pandoc_types::table::Table { attr: (String::new(), Vec::new(), LinkedHashMap::new()), caption: Caption { short: None, @@ -3156,13 +3156,13 @@ mod tests { }, source_info: si(), attr_source: AttrSourceInfo::empty(), - }); - let div = Block::Div(Div { + })); + let div = Block::Div(Box::new(Div { attr: attr_id("tbl-nums"), content: vec![table], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let ast = run_full(vec![div]).await; let (outer, f) = float_shape(&ast.blocks[0]); assert_eq!(outer.attr.0, "tbl-nums"); @@ -3190,7 +3190,7 @@ mod tests { callout .slots .insert("content".into(), Slot::Blocks(vec![para("inside")])); - let mut block = Block::Custom(callout); + let mut block = Block::Custom(Box::new(callout)); let mut fs = float_state_for_tests(); render_block(&mut block, None, &mut fs); match block { @@ -3205,12 +3205,12 @@ mod tests { if let Some(t) = title { kvs.insert("name".into(), t.to_string()); } - Block::Div(Div { + Block::Div(Box::new(Div { attr: (id.into(), vec!["theorem".into()], kvs), content: vec![para(body)], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Pull the `Strong` out of the theorem-title `Span` at @@ -3309,12 +3309,12 @@ mod tests { async fn theorem_counters_independent_from_lemmas() { let t1 = theorem_div("thm-a", None, "a"); let t2 = theorem_div("thm-b", None, "b"); - let lemma_div = Block::Div(Div { + let lemma_div = Block::Div(Box::new(Div { attr: ("lem-c".into(), vec!["lemma".into()], LinkedHashMap::new()), content: vec![para("c")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let ast = run_full(vec![t1, t2, lemma_div]).await; let strong_text = |block: &Block| -> String { let strong = theorem_label_strong(block); @@ -3336,12 +3336,12 @@ mod tests { #[tokio::test] async fn unnumbered_theorem_renders_without_number() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: (String::new(), vec!["theorem".into()], LinkedHashMap::new()), content: vec![para("unnumbered")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let ast = run_full(vec![div]).await; let strong = theorem_label_strong(&ast.blocks[0]); let Inline::Str(label) = &strong.content[0] else { @@ -3380,12 +3380,12 @@ mod tests { if let Some(t) = title { kvs.insert("name".into(), t.to_string()); } - Block::Div(Div { + Block::Div(Box::new(Div { attr: (String::new(), vec!["proof".into()], kvs), content: vec![para(body)], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } #[tokio::test] diff --git a/crates/quarto-core/src/transforms/editorial_marks_ooxml.rs b/crates/quarto-core/src/transforms/editorial_marks_ooxml.rs index 2dc219dc75..d8c8e1074d 100644 --- a/crates/quarto-core/src/transforms/editorial_marks_ooxml.rs +++ b/crates/quarto-core/src/transforms/editorial_marks_ooxml.rs @@ -324,7 +324,7 @@ impl Converter { let mut out: Vec = Vec::with_capacity(list.len()); for block in std::mem::take(list) { match block { - Block::Div(div) if self.is_live_mark(&div.attr.1) => self.mark_div(div, &mut out), + Block::Div(div) if self.is_live_mark(&div.attr.1) => self.mark_div(*div, &mut out), Block::Paragraph(mut p) if wrap.is_some() => { self.inline_list(&mut p.content); p.content = wrap.expect("checked").wrap(p.content); @@ -831,7 +831,7 @@ mod tests { .with_slot("title", Slot::Inlines(title)) .with_slot("content", Slot::Blocks(vec![Block::Paragraph(inline_para)])) .with_slot("one", Slot::Block(Box::new(div))); - ast.blocks = vec![Block::Custom(node)]; + ast.blocks = vec![Block::Custom(Box::new(node))]; convert_document(&mut ast, Target::Docx, None); let shown = show_blocks(&ast.blocks); @@ -855,7 +855,7 @@ mod tests { let div = ast.blocks[0].clone(); let node = CustomNode::new("Callout", Default::default(), SourceInfo::for_test()) .with_slot("one", Slot::Block(Box::new(div))); - ast.blocks = vec![Block::Custom(node)]; + ast.blocks = vec![Block::Custom(Box::new(node))]; convert_document(&mut ast, Target::Docx, None); assert_eq!( show_blocks(&ast.blocks), diff --git a/crates/quarto-core/src/transforms/editorial_marks_typst.rs b/crates/quarto-core/src/transforms/editorial_marks_typst.rs index fb8d63b91b..716f3f20d9 100644 --- a/crates/quarto-core/src/transforms/editorial_marks_typst.rs +++ b/crates/quarto-core/src/transforms/editorial_marks_typst.rs @@ -158,11 +158,11 @@ fn raw_inline(text: String) -> Inline { } fn raw_block(text: String) -> Block { - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: "typst".to_string(), text, source_info: generated(), - }) + })) } struct Converter { @@ -445,7 +445,7 @@ mod tests { .with_slot("title", Slot::Inlines(title)) .with_slot("content", Slot::Blocks(vec![inline_para])) .with_slot("one", Slot::Block(Box::new(div))); - ast.blocks = vec![Block::Custom(node)]; + ast.blocks = vec![Block::Custom(Box::new(node))]; convert_document(&mut ast, None); assert_eq!( diff --git a/crates/quarto-core/src/transforms/example_embed.rs b/crates/quarto-core/src/transforms/example_embed.rs index 596f33aaba..ba3c2e75fb 100644 --- a/crates/quarto-core/src/transforms/example_embed.rs +++ b/crates/quarto-core/src/transforms/example_embed.rs @@ -151,7 +151,7 @@ fn sugar_block(block: &mut Block, diags: &mut Vec) { if let Block::Div(div) = block && div.attr.1.iter().any(|c| c == MATCH_CLASS) { - *block = Block::Custom(sugar_embed(div, diags)); + *block = Block::Custom(Box::new(sugar_embed(div, diags))); } } @@ -304,7 +304,7 @@ fn render_block(block: &mut Block, resolve: Resolve) { if let Block::Custom(node) = block && node.type_name == NODE_TYPE { - *block = Block::Div(render_embed(node, resolve)); + *block = Block::Div(Box::new(render_embed(node, resolve))); } } @@ -361,7 +361,7 @@ fn render_embed(node: &mut CustomNode, resolve: Resolve) -> Div { Some(n) => with_number_label(body, kind, n, &source_info), None => body, }; - content.push(Block::Div(Div { + content.push(Block::Div(Box::new(Div { attr: ( String::new(), vec![SOURCE_CLASS.to_string()], @@ -370,7 +370,7 @@ fn render_embed(node: &mut CustomNode, resolve: Resolve) -> Div { content: caption_blocks, source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - })); + }))); // The container carries the id so `@demo-…` references jump to it. let mut classes = vec![CONTAINER_CLASS.to_string()]; @@ -416,11 +416,11 @@ fn iframe_block( title = title, style = attr_escape(&style), ); - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: html, source_info: source_info.clone(), - }) + })) } /// Prepend a `Demo N: ` label (a styled span) to the caption body. If the @@ -650,17 +650,17 @@ mod tests { for (k, v) in kvs { map.insert((*k).to_string(), (*v).to_string()); } - Block::Div(Div { + Block::Div(Box::new(Div { attr: (id.to_string(), vec![MATCH_CLASS.to_string()], map), content: vec![source_link("View source", "https://github.com/q/x")], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// A markdown code-block, as the first child of a placeholder would parse. fn code_block(text: &str) -> Block { - Block::CodeBlock(quarto_pandoc_types::block::CodeBlock { + Block::CodeBlock(Box::new(quarto_pandoc_types::block::CodeBlock { attr: ( String::new(), vec!["markdown".to_string()], @@ -669,7 +669,7 @@ mod tests { text: text.to_string(), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// A placeholder Div whose body is a leading code block followed by the @@ -679,12 +679,12 @@ mod tests { for (k, v) in kvs { map.insert((*k).to_string(), (*v).to_string()); } - Block::Div(Div { + Block::Div(Box::new(Div { attr: (id.to_string(), vec![MATCH_CLASS.to_string()], map), content: body, source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// The ordered top-level block kinds inside the rendered container Div. diff --git a/crates/quarto-core/src/transforms/float_ref_target.rs b/crates/quarto-core/src/transforms/float_ref_target.rs index be055564a2..2fc53d23cc 100644 --- a/crates/quarto-core/src/transforms/float_ref_target.rs +++ b/crates/quarto-core/src/transforms/float_ref_target.rs @@ -249,7 +249,7 @@ fn transform_block( }; if let Some(custom) = converted { - *block = Block::Custom(custom); + *block = Block::Custom(Box::new(custom)); } } @@ -349,7 +349,7 @@ fn convert_codeblock( }); node.slots.insert( "content".into(), - Slot::Blocks(vec![Block::CodeBlock(inner)]), + Slot::Blocks(vec![Block::CodeBlock(Box::new(inner))]), ); node.slots.insert( "caption_long".into(), @@ -408,12 +408,12 @@ fn maybe_wrap_bare_table_into_div(block: &mut Block, reg: &RefTypeRegistry) { // move the Table into the Div's content. let table_block = std::mem::replace( block, - Block::Div(Div { + Block::Div(Box::new(Div { attr: div_attr, content: Vec::new(), source_info, attr_source: div_attr_source, - }), + })), ); if let Block::Div(div) = block { div.content.push(table_block); @@ -854,12 +854,12 @@ mod tests { } fn code(lang: &str, body: &str) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![lang.into()], LinkedHashMap::new()), text: body.to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn run_transform(blocks: Vec, reg: &RefTypeRegistry) -> Vec { @@ -872,12 +872,12 @@ mod tests { #[test] fn div_with_trailing_paragraph_becomes_float_ref_target() { let reg = RefTypeRegistry::builtin(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id("fig-hello"), content: vec![code("python", "pyplot.show()"), para("Hello, world.")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div], ®); assert_eq!(out.len(), 1); @@ -919,7 +919,7 @@ mod tests { // produced bottom-up, caption-stealing registration; section headers // are registered by CrossrefIndexTransform directly instead. let reg = RefTypeRegistry::builtin(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: ( "sec-x".to_string(), vec!["section".to_string(), "level1".to_string()], @@ -928,7 +928,7 @@ mod tests { content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div], ®); let Block::Div(d) = &out[0] else { panic!("section div must stay a Div, got {:?}", out[0]); @@ -942,21 +942,21 @@ mod tests { // Excluding `.section` divs must not shield their contents: a figure // div nested inside a section is still a crossref target. let reg = RefTypeRegistry::builtin(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: ( "sec-x".to_string(), vec!["section".to_string()], LinkedHashMap::new(), ), - content: vec![Block::Div(Div { + content: vec![Block::Div(Box::new(Div { attr: attr_id("fig-inner"), content: vec![code("python", "1+1"), para("Inner cap")], source_info: si(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div], ®); let Block::Div(section) = &out[0] else { panic!("section div must stay a Div, got {:?}", out[0]); @@ -973,12 +973,12 @@ mod tests { // `section` class keeps today's float-path behavior. Q1's treatment // of that shape is its own question, not P0's. let reg = RefTypeRegistry::builtin(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id("sec-x"), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div], ®); let view = crossref_target_view(&out[0]).expect("bare sec div is a target"); assert_eq!(view.ref_type, "sec"); @@ -1008,7 +1008,7 @@ mod tests { // Lua filters or the JSON reader; Pandoc itself emits Plain). use quarto_pandoc_types::table::{Table, TableFoot, TableHead}; let reg = RefTypeRegistry::builtin(); - let table = Block::Table(Table { + let table = Block::Table(Box::new(Table { attr: (String::new(), Vec::new(), LinkedHashMap::new()), caption: Caption { short: None, @@ -1031,13 +1031,13 @@ mod tests { }, source_info: si(), attr_source: AttrSourceInfo::empty(), - }); - let div = Block::Div(Div { + })); + let div = Block::Div(Box::new(Div { attr: attr_id("tbl-nums"), content: vec![table], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div], ®); let Block::Custom(node) = &out[0] else { panic!("expected custom node"); @@ -1048,12 +1048,12 @@ mod tests { #[test] fn div_without_trailing_paragraph_has_no_caption_slot() { let reg = RefTypeRegistry::builtin(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id("fig-no-cap"), content: vec![code("python", "pyplot.show()")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div], ®); let Block::Custom(node) = &out[0] else { panic!("expected custom node"); @@ -1065,7 +1065,7 @@ mod tests { #[test] fn figure_with_id_becomes_float_ref_target() { let reg = RefTypeRegistry::builtin(); - let fig = Block::Figure(Figure { + let fig = Block::Figure(Box::new(Figure { attr: attr_id("fig-plain"), caption: Caption { short: None, @@ -1075,7 +1075,7 @@ mod tests { content: vec![para("image placeholder")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![fig], ®); let view = crossref_target_view(&out[0]).expect("is a target"); assert_eq!(view.identifier, "fig-plain"); @@ -1098,7 +1098,7 @@ mod tests { #[test] fn figure_image_classes_and_attributes_merge_onto_float() { let reg = RefTypeRegistry::builtin(); - let fig = Block::Figure(Figure { + let fig = Block::Figure(Box::new(Figure { attr: attr_id("fig-margin"), caption: Caption { short: None, @@ -1111,7 +1111,7 @@ mod tests { )], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![fig], ®); let Block::Custom(node) = &out[0] else { panic!("expected FloatRefTarget"); @@ -1144,7 +1144,7 @@ mod tests { #[test] fn div_over_table_clears_the_tables_own_caption() { use quarto_pandoc_types::table::{Table, TableBody, TableFoot, TableHead}; - let table = Block::Table(Table { + let table = Block::Table(Box::new(Table { attr: (String::new(), Vec::new(), LinkedHashMap::new()), caption: Caption { short: None, @@ -1174,14 +1174,14 @@ mod tests { }, source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let reg = RefTypeRegistry::builtin(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id("tbl-one"), content: vec![table], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div], ®); let Block::Custom(node) = &out[0] else { panic!("expected custom node"); @@ -1215,7 +1215,7 @@ mod tests { #[test] fn div_over_figure_flattens_to_single_target() { let reg = RefTypeRegistry::builtin(); - let inner_fig = Block::Figure(Figure { + let inner_fig = Block::Figure(Box::new(Figure { attr: (String::new(), Vec::new(), LinkedHashMap::new()), caption: Caption { short: None, @@ -1225,13 +1225,13 @@ mod tests { content: vec![para("image")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); - let div = Block::Div(Div { + })); + let div = Block::Div(Box::new(Div { attr: attr_id("fig-outer"), content: vec![inner_fig], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div], ®); assert_eq!(out.len(), 1); let view = crossref_target_view(&out[0]).expect("is a target"); @@ -1251,12 +1251,12 @@ mod tests { #[test] fn div_without_crossref_id_left_alone() { let reg = RefTypeRegistry::builtin(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id("just-a-div"), content: vec![para("content")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div.clone()], ®); assert_eq!(out, vec![div]); } @@ -1264,12 +1264,12 @@ mod tests { #[test] fn citation_shaped_id_not_mistaken_for_crossref() { let reg = RefTypeRegistry::builtin(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id("smithfoo-2020"), content: vec![para("biblio-looking div")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div.clone()], ®); assert_eq!(out, vec![div]); } @@ -1277,13 +1277,13 @@ mod tests { #[test] fn nested_crossref_target_sugared() { let reg = RefTypeRegistry::builtin(); - let inner = Block::Div(Div { + let inner = Block::Div(Box::new(Div { attr: attr_id("fig-nested"), content: vec![code("python", "x=1"), para("cap")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); - let outer = Block::Div(Div { + })); + let outer = Block::Div(Box::new(Div { attr: ( String::new(), vec!["callout-note".into()], @@ -1292,7 +1292,7 @@ mod tests { content: vec![inner], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![outer], ®); let Block::Div(outer_div) = &out[0] else { panic!("outer remains a Div"); @@ -1305,12 +1305,12 @@ mod tests { fn custom_category_honored_if_registered() { let mut reg = RefTypeRegistry::builtin(); reg.register_custom("dia", "Diagram", None).unwrap(); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id("dia-one"), content: vec![para("diagram caption")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![div], ®); let view = crossref_target_view(&out[0]).expect("is a target"); assert_eq!(view.ref_type, "dia"); @@ -1329,12 +1329,12 @@ mod tests { let reg = RefTypeRegistry::builtin(); let mut kvs = LinkedHashMap::new(); kvs.insert("lst-cap".to_string(), "Hello World in Python".to_string()); - let cb = Block::CodeBlock(CodeBlock { + let cb = Block::CodeBlock(Box::new(CodeBlock { attr: ("lst-hello".to_string(), vec!["python".to_string()], kvs), text: "def hello():\n print(\"Hello, World!\")\n".to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![cb], ®); let Block::Custom(node) = &out[0] else { panic!( @@ -1397,12 +1397,12 @@ mod tests { #[test] fn code_block_without_lst_cap_is_not_sugared() { let reg = RefTypeRegistry::builtin(); - let cb = Block::CodeBlock(CodeBlock { + let cb = Block::CodeBlock(Box::new(CodeBlock { attr: attr_id("lst-nocap"), text: "1 + 1\n".to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![cb], ®); assert!( matches!(&out[0], Block::CodeBlock(_)), @@ -1418,12 +1418,12 @@ mod tests { let reg = RefTypeRegistry::builtin(); let mut kvs = LinkedHashMap::new(); kvs.insert("fig-cap".to_string(), "Not a float".to_string()); - let cb = Block::CodeBlock(CodeBlock { + let cb = Block::CodeBlock(Box::new(CodeBlock { attr: ("fig-cb".to_string(), vec!["python".to_string()], kvs), text: "1\n".to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run_transform(vec![cb], ®); assert!( matches!(&out[0], Block::CodeBlock(_)), diff --git a/crates/quarto-core/src/transforms/footnotes.rs b/crates/quarto-core/src/transforms/footnotes.rs index de4e19c986..9c117e9393 100644 --- a/crates/quarto-core/src/transforms/footnotes.rs +++ b/crates/quarto-core/src/transforms/footnotes.rs @@ -188,11 +188,11 @@ impl NoteContent { fn mark_with_ref_id(mut blocks: Blocks, ref_id: &str, source_info: &SourceInfo) -> Blocks { blocks.insert( 0, - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: FOOTNOTE_REF_ID_MARKER_FORMAT.to_string(), text: ref_id.to_string(), source_info: source_info.clone(), - }), + })), ); blocks } @@ -800,11 +800,11 @@ mod tests { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![ // Note definition - Block::NoteDefinitionPara(NoteDefinitionPara { + Block::NoteDefinitionPara(Box::new(NoteDefinitionPara { id: "myfoot".to_string(), content: vec![make_str("Defined footnote content")], source_info: dummy_source_info(), - }), + })), // Paragraph with reference Block::Paragraph(Paragraph { content: vec![ @@ -844,11 +844,11 @@ mod tests { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![ - Block::NoteDefinitionPara(NoteDefinitionPara { + Block::NoteDefinitionPara(Box::new(NoteDefinitionPara { id: "myfoot".to_string(), content: vec![make_str("Defined footnote content")], source_info: dummy_source_info(), - }), + })), Block::Paragraph(Paragraph { content: vec![ make_str("See note"), @@ -919,14 +919,16 @@ mod tests { source_info: dummy_source_info(), }), // Block definition `::: ^bk … :::`. - Block::NoteDefinitionFencedBlock(quarto_pandoc_types::NoteDefinitionFencedBlock { - id: "bk".to_string(), - content: vec![Block::Paragraph(Paragraph { - content: vec![make_str("Note.")], + Block::NoteDefinitionFencedBlock(Box::new( + quarto_pandoc_types::NoteDefinitionFencedBlock { + id: "bk".to_string(), + content: vec![Block::Paragraph(Paragraph { + content: vec![make_str("Note.")], + source_info: dummy_source_info(), + })], source_info: dummy_source_info(), - })], - source_info: dummy_source_info(), - }), + }, + )), ], }; @@ -1178,11 +1180,11 @@ mod tests { ConfigValue::new_string(mode, dummy_source_info()), )]), blocks: vec![ - Block::NoteDefinitionPara(NoteDefinitionPara { + Block::NoteDefinitionPara(Box::new(NoteDefinitionPara { id: "1".to_string(), content: vec![make_str("note content")], source_info: dummy_source_info(), - }), + })), Block::Paragraph(Paragraph { content: vec![ make_str("Text"), @@ -1321,22 +1323,22 @@ mod tests { source_info: dummy_source_info(), }), // A definition inside the callout body is collected too. - Block::NoteDefinitionPara(NoteDefinitionPara { + Block::NoteDefinitionPara(Box::new(NoteDefinitionPara { id: "b".to_string(), content: vec![make_str("Note B")], source_info: dummy_source_info(), - }), + })), ]), ); let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![ - Block::Custom(callout), - Block::NoteDefinitionPara(NoteDefinitionPara { + Block::Custom(Box::new(callout)), + Block::NoteDefinitionPara(Box::new(NoteDefinitionPara { id: "t".to_string(), content: vec![make_str("Note T")], source_info: dummy_source_info(), - }), + })), ], }; diff --git a/crates/quarto-core/src/transforms/footnotes_resolve.rs b/crates/quarto-core/src/transforms/footnotes_resolve.rs index da3ab10977..3dfe8d6edf 100644 --- a/crates/quarto-core/src/transforms/footnotes_resolve.rs +++ b/crates/quarto-core/src/transforms/footnotes_resolve.rs @@ -469,15 +469,15 @@ fn create_footnotes_section(footnotes: &[CollectedFootnote]) -> Block { let list_items: Vec = footnotes.iter().map(create_footnote_item).collect(); // Create the ordered list - let ordered_list = Block::OrderedList(OrderedList { + let ordered_list = Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: list_items, source_info: source_info.clone(), - }); + })); // Wrap in a section Div with appropriate attributes // Note: We use a Div with class "section" so the HTML writer emits
- Block::Div(Div { + Block::Div(Box::new(Div { attr: ( "footnotes".to_string(), vec!["footnotes".to_string(), "section".to_string()], @@ -491,7 +491,7 @@ fn create_footnotes_section(footnotes: &[CollectedFootnote]) -> Block { ], source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Create a single footnote list item. @@ -551,12 +551,12 @@ fn create_footnote_item(footnote: &CollectedFootnote) -> Blocks { // with OrderedList. So we wrap content in a Div with ID. // Actually, looking at Pandoc output more carefully, the ID is on the
  • . // Our OrderedList doesn't support per-item IDs, so we'll wrap in a Div. - vec![Block::Div(Div { + vec![Block::Div(Box::new(Div { attr: (fn_id, Vec::new(), LinkedHashMap::new()), content: content_blocks, source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - })] + }))] } #[cfg(test)] diff --git a/crates/quarto-core/src/transforms/heading_id.rs b/crates/quarto-core/src/transforms/heading_id.rs index bc5d0336e2..65e69c5082 100644 --- a/crates/quarto-core/src/transforms/heading_id.rs +++ b/crates/quarto-core/src/transforms/heading_id.rs @@ -149,7 +149,7 @@ mod tests { #[test] fn an_empty_id_is_filled_whatever_the_headings_origin() { let mut doc = parse("## Intro\n"); - doc.blocks.push(Block::Header(Header { + doc.blocks.push(Block::Header(Box::new(Header { level: 2, attr: (String::new(), vec![], Default::default()), content: vec![Inline::Str(Str { @@ -158,7 +158,7 @@ mod tests { })], source_info: SourceInfo::generated(By::unknown()), attr_source: AttrSourceInfo::empty(), - })); + }))); let after = rederive_stale_heading_ids(doc); assert_eq!(header_ids(&after), vec!["intro", "built"]); } @@ -176,7 +176,7 @@ mod tests { // pandoc.Header(2, "x", pandoc.Attr("intro")) from Lua: no source // location on the id, but a generated source_info. let mut doc = parse("## Alpha\n"); - doc.blocks.push(Block::Header(Header { + doc.blocks.push(Block::Header(Box::new(Header { level: 2, attr: ("intro".to_string(), vec![], Default::default()), content: vec![Inline::Str(Str { @@ -185,7 +185,7 @@ mod tests { })], source_info: SourceInfo::generated(By::unknown()), attr_source: AttrSourceInfo::empty(), - })); + }))); let after = rederive_stale_heading_ids(doc); assert_eq!(header_ids(&after), vec!["alpha", "intro"]); } diff --git a/crates/quarto-core/src/transforms/image_alt_normalize.rs b/crates/quarto-core/src/transforms/image_alt_normalize.rs index 56f09c55af..f6c349754f 100644 --- a/crates/quarto-core/src/transforms/image_alt_normalize.rs +++ b/crates/quarto-core/src/transforms/image_alt_normalize.rs @@ -206,7 +206,7 @@ mod tests { /// The implicit figure the reader builds for `![caption](x.png){…}`. fn implicit_figure(caption: &str, kvs: &[(&str, &str)]) -> Block { - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr: (String::new(), vec![], LinkedHashMap::new()), caption: Caption { short: None, @@ -216,7 +216,7 @@ mod tests { content: vec![plain(vec![Inline::Image(Box::new(image(caption, kvs)))])], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn float(ref_type: &str, kvs: &[(&str, &str)], content: Vec, caption: &str) -> Block { @@ -232,7 +232,7 @@ mod tests { .with_data(json!({ "ref_type": ref_type, "kind": "Figure", "identifier": format!("{ref_type}-x") })) .with_slot("content", Slot::Blocks(content)) .with_slot("caption_long", Slot::Blocks(vec![plain(text(caption))])); - Block::Custom(node) + Block::Custom(Box::new(node)) } /// Every image in document order: (alt text, remaining key-values). @@ -375,7 +375,7 @@ mod tests { source_info: si(), attr_source: AttrSourceInfo::empty(), }; - let table = Block::Table(Table { + let table = Block::Table(Box::new(Table { attr: (String::new(), vec![], LinkedHashMap::new()), caption: Caption { short: None, @@ -410,7 +410,7 @@ mod tests { }, source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let note = para(vec![Inline::Note(Note { content: vec![para(vec![Inline::Image(Box::new(image( "", diff --git a/crates/quarto-core/src/transforms/image_walk.rs b/crates/quarto-core/src/transforms/image_walk.rs index 8a78fef685..7932b21487 100644 --- a/crates/quarto-core/src/transforms/image_walk.rs +++ b/crates/quarto-core/src/transforms/image_walk.rs @@ -179,7 +179,7 @@ impl Walker<'_> { attr, plain_data, .. - } = &mut *c; + } = &mut **c; let own = FloatScope { attr: &*attr, plain_data: &*plain_data, diff --git a/crates/quarto-core/src/transforms/lightbox.rs b/crates/quarto-core/src/transforms/lightbox.rs index 5e913302a6..091179faf2 100644 --- a/crates/quarto-core/src/transforms/lightbox.rs +++ b/crates/quarto-core/src/transforms/lightbox.rs @@ -201,7 +201,7 @@ impl AstTransform for LightboxTransform { if !descriptions.is_empty() { let mut kvs = LinkedHashMap::new(); kvs.insert("aria-hidden".to_string(), "true".to_string()); - ast.blocks.push(Block::Div(Div { + ast.blocks.push(Block::Div(Box::new(Div { attr: (String::new(), vec!["hidden".to_string()], kvs), content: descriptions, // The container is lightbox chrome; the descriptions inside @@ -211,7 +211,7 @@ impl AstTransform for LightboxTransform { data: serde_json::Value::Null, }), attr_source: AttrSourceInfo::empty(), - })); + }))); } // The runtime is render-only: the preview has no @@ -724,7 +724,7 @@ impl Walker<'_> { if let Some(description) = img.attr.2.remove(DESCRIPTION) { let desc_class = format!("lightbox-desc-{n}"); let content = self.parse_description(&description, &img.source_info); - self.descriptions.push(Block::Div(Div { + self.descriptions.push(Block::Div(Box::new(Div { attr: ( String::new(), vec!["glightbox-desc".to_string(), desc_class.clone()], @@ -733,7 +733,7 @@ impl Walker<'_> { content, source_info: img.source_info.clone(), attr_source: AttrSourceInfo::empty(), - })); + }))); let mut config = format!("description: .{desc_class};"); if let Some(title) = &title { config.push_str(&format!("title:{title};")); @@ -1214,7 +1214,7 @@ mod tests { })]), ); } - Block::Custom(node) + Block::Custom(Box::new(node)) } fn auto_meta() -> ConfigValue { @@ -1281,7 +1281,7 @@ mod tests { let caption_img = image_para("cap.png", "in caption"); let mut ast = Pandoc { meta: auto_meta(), - blocks: vec![Block::Figure(Figure { + blocks: vec![Block::Figure(Box::new(Figure { attr: (String::new(), vec![], LinkedHashMap::new()), caption: Caption { short: None, @@ -1291,7 +1291,7 @@ mod tests { content: vec![image_para("a.png", "a")], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; run(&mut ast).await; let found = links(&mut ast.blocks); diff --git a/crates/quarto-core/src/transforms/link_rewrite.rs b/crates/quarto-core/src/transforms/link_rewrite.rs index 813d1e34b7..9e997f125c 100644 --- a/crates/quarto-core/src/transforms/link_rewrite.rs +++ b/crates/quarto-core/src/transforms/link_rewrite.rs @@ -783,12 +783,12 @@ mod tests { /// Plan test 32: link inside a `Div`. #[tokio::test] async fn link_rewrite_walks_div_blocks() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: Attr::default(), content: vec![para(vec![link_inline("about.qmd", "About")])], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }); + })); let (out, _) = run( vec![div], "index.qmd", @@ -837,7 +837,7 @@ mod tests { custom .slots .insert("content".to_string(), Slot::Inlines(vec![link])); - let blocks = vec![Block::Custom(custom)]; + let blocks = vec![Block::Custom(Box::new(custom))]; let (out, _) = run( blocks, "index.qmd", @@ -1047,7 +1047,7 @@ mod tests { } fn figure_with_caption_image() -> Block { - Block::Figure(quarto_pandoc_types::Figure { + Block::Figure(Box::new(quarto_pandoc_types::Figure { attr: Attr::default(), caption: quarto_pandoc_types::Caption { short: None, @@ -1057,7 +1057,7 @@ mod tests { content: vec![para(vec![image_inline("/images/main.svg", "main")])], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// R9 task 5, intended behaviour change: an image inside a figure diff --git a/crates/quarto-core/src/transforms/listing_render.rs b/crates/quarto-core/src/transforms/listing_render.rs index 84cdb5944c..1557877a8b 100644 --- a/crates/quarto-core/src/transforms/listing_render.rs +++ b/crates/quarto-core/src/transforms/listing_render.rs @@ -317,11 +317,11 @@ fn render_one( if emit_listjs && listjs::supports_listjs(r.listing.kind) { let fields = effective_fields(&r.listing, &r.items); let raw_html = |text: String| { - Block::RawBlock(RawBlock { + Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text, source_info: SourceInfo::generated(By::programmatic_config()), - }) + })) }; if let Some(nav) = listjs::pagination_nav(&r.listing, r.items.len()) { parsed_blocks.push(raw_html(nav)); @@ -348,7 +348,7 @@ fn render_one( attr_source: AttrSourceInfo::empty(), }; mark_as_container(&mut div, &r.listing); - ast.blocks.push(Block::Div(div)); + ast.blocks.push(Block::Div(Box::new(div))); } } @@ -856,12 +856,12 @@ mod tests { fn pandoc_with_slot(id: &str) -> Pandoc { Pandoc { meta: ConfigValue::default(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: (id.to_string(), vec![], LinkedHashMap::new()), content: vec![], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - })], + }))], } } @@ -2208,7 +2208,7 @@ mod tests { // produces ahead of us. fn header(level: usize, text: &str) -> Block { - Block::Header(quarto_pandoc_types::block::Header { + Block::Header(Box::new(quarto_pandoc_types::block::Header { level, attr: (slug(text), vec![], LinkedHashMap::new()), content: vec![quarto_pandoc_types::inline::Inline::Str( @@ -2219,7 +2219,7 @@ mod tests { )], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn slug(text: &str) -> String { @@ -2239,12 +2239,12 @@ mod tests { } fn slot(id: &str) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: (id.to_string(), vec![], LinkedHashMap::new()), content: vec![], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn sectionized(blocks: Vec) -> Pandoc { @@ -2511,7 +2511,7 @@ mod tests { .content .iter() .filter_map(|b| match b { - Block::Div(d) => Some(d), + Block::Div(d) => Some(&**d), _ => None, }) .collect(); diff --git a/crates/quarto-core/src/transforms/llms.rs b/crates/quarto-core/src/transforms/llms.rs index d894127032..80bdc70d8f 100644 --- a/crates/quarto-core/src/transforms/llms.rs +++ b/crates/quarto-core/src/transforms/llms.rs @@ -420,16 +420,16 @@ fn clean_block_into(mut block: Block, cx: &ViewContext, out: &mut Vec) { .iter() .find(|l| l.listing.container_id() == div.attr.0) { - out.push(Block::Div(quarto_pandoc_types::block::Div { + out.push(Block::Div(Box::new(quarto_pandoc_types::block::Div { attr: (div.attr.0.clone(), vec![], hashlink::LinkedHashMap::new()), content: vec![synthesize_listing_list(listing, cx)], source_info: div.source_info, attr_source: AttrSourceInfo::empty(), - })); + }))); return; } if has_class(&div.attr, "callout") { - out.extend(rebuild_callout(div, cx)); + out.extend(rebuild_callout(*div, cx)); return; } if is_float_div(&div.attr) { @@ -442,12 +442,12 @@ fn clean_block_into(mut block: Block, cx: &ViewContext, out: &mut Vec) { if id.is_empty() { out.extend(children); } else { - out.push(Block::Div(quarto_pandoc_types::block::Div { + out.push(Block::Div(Box::new(quarto_pandoc_types::block::Div { attr: (id, vec![], hashlink::LinkedHashMap::new()), content: children, source_info: div.source_info, attr_source: AttrSourceInfo::empty(), - })); + }))); } return; } @@ -683,12 +683,12 @@ fn rebuild_callout(div: quarto_pandoc_types::block::Div, cx: &ViewContext) -> Ve content.extend(build_llms_view(body, cx)); let class = callout_type.map_or_else(|| "callout".to_string(), |t| format!("callout-{t}")); - vec![Block::Div(quarto_pandoc_types::block::Div { + vec![Block::Div(Box::new(quarto_pandoc_types::block::Div { attr: (String::new(), vec![class], hashlink::LinkedHashMap::new()), content, source_info: div.source_info, attr_source: AttrSourceInfo::empty(), - })] + }))] } /// Flatten the plain text of a block list (for callout titles). diff --git a/crates/quarto-core/src/transforms/mermaid.rs b/crates/quarto-core/src/transforms/mermaid.rs index a5ce94eda8..67207bb198 100644 --- a/crates/quarto-core/src/transforms/mermaid.rs +++ b/crates/quarto-core/src/transforms/mermaid.rs @@ -264,11 +264,11 @@ fn convert_mermaid_blocks(blocks: &mut Blocks, found: &mut bool) { }; let text = format!("
    \n{}\n
    ", html_escape(&cb.text)); let source_info = cb.source_info.clone(); - *block = Block::RawBlock(RawBlock { + *block = Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text, source_info, - }); + })); } Block::BlockQuote(bq) => convert_mermaid_blocks(&mut bq.content, found), Block::Div(div) => convert_mermaid_blocks(&mut div.content, found), @@ -351,7 +351,7 @@ mod tests { use quarto_source_map::SourceInfo; fn make_codeblock(classes: &[&str], text: &str) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), classes.iter().map(|c| c.to_string()).collect(), @@ -360,7 +360,7 @@ mod tests { text: text.to_string(), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn make_test_project() -> ProjectContext { @@ -829,7 +829,7 @@ mod tests { let inner = make_codeblock(&["mermaid"], "flowchart LR\n a --> b"); let mut ast = Pandoc { meta: default_meta(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: ( String::new(), vec!["section".to_string()], @@ -838,7 +838,7 @@ mod tests { content: vec![inner], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; run_transform(&mut ast, Format::html()).await; diff --git a/crates/quarto-core/src/transforms/metadata_normalize.rs b/crates/quarto-core/src/transforms/metadata_normalize.rs index 89865fab82..044a09c1ed 100644 --- a/crates/quarto-core/src/transforms/metadata_normalize.rs +++ b/crates/quarto-core/src/transforms/metadata_normalize.rs @@ -950,12 +950,12 @@ mod tests { #[tokio::test] async fn test_blocks_code_block() { use quarto_pandoc_types::block::CodeBlock; - let blocks = vec![Block::CodeBlock(CodeBlock { + let blocks = vec![Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), text: "fn main() {}".to_string(), source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = blocks_to_plain_text(&blocks); assert_eq!(text, "fn main() {}"); } @@ -963,11 +963,11 @@ mod tests { #[tokio::test] async fn test_blocks_raw_block() { use quarto_pandoc_types::block::RawBlock; - let blocks = vec![Block::RawBlock(RawBlock { + let blocks = vec![Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: "
    raw content
    ".to_string(), source_info: dummy_source_info(), - })]; + }))]; let text = blocks_to_plain_text(&blocks); assert_eq!(text, "
    raw content
    "); } @@ -990,7 +990,7 @@ mod tests { async fn test_blocks_ordered_list() { use quarto_pandoc_types::block::{OrderedList, Plain}; use quarto_pandoc_types::list::{ListNumberDelim, ListNumberStyle}; - let blocks = vec![Block::OrderedList(OrderedList { + let blocks = vec![Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![ vec![Block::Plain(Plain { @@ -1003,7 +1003,7 @@ mod tests { })], ], source_info: dummy_source_info(), - })]; + }))]; let text = blocks_to_plain_text(&blocks); assert_eq!(text, "item1\nitem2\n"); } @@ -1048,13 +1048,13 @@ mod tests { #[tokio::test] async fn test_blocks_header() { use quarto_pandoc_types::block::Header; - let blocks = vec![Block::Header(Header { + let blocks = vec![Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![make_str_inline("heading")], source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = blocks_to_plain_text(&blocks); assert_eq!(text, "heading"); } @@ -1062,7 +1062,7 @@ mod tests { #[tokio::test] async fn test_blocks_div() { use quarto_pandoc_types::block::{Div, Plain}; - let blocks = vec![Block::Div(Div { + let blocks = vec![Block::Div(Box::new(Div { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![Block::Plain(Plain { content: vec![make_str_inline("div content")], @@ -1070,7 +1070,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = blocks_to_plain_text(&blocks); assert_eq!(text, "div content"); } @@ -1081,7 +1081,7 @@ mod tests { use quarto_pandoc_types::table::{ Alignment, ColWidth, Table, TableBody, TableFoot, TableHead, }; - let blocks = vec![Block::Table(Table { + let blocks = vec![Block::Table(Box::new(Table { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), caption: Caption { short: Some(vec![make_str_inline("table caption")]), @@ -1111,7 +1111,7 @@ mod tests { }, source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = blocks_to_plain_text(&blocks); assert_eq!(text, "table caption"); } @@ -1120,7 +1120,7 @@ mod tests { async fn test_blocks_figure_with_caption() { use quarto_pandoc_types::block::Figure; use quarto_pandoc_types::caption::Caption; - let blocks = vec![Block::Figure(Figure { + let blocks = vec![Block::Figure(Box::new(Figure { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), caption: Caption { short: Some(vec![make_str_inline("figure caption")]), @@ -1130,7 +1130,7 @@ mod tests { content: vec![], source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = blocks_to_plain_text(&blocks); assert_eq!(text, "figure caption"); } diff --git a/crates/quarto-core/src/transforms/navigation_href.rs b/crates/quarto-core/src/transforms/navigation_href.rs index 8a7dcaefa2..a30eafcfd1 100644 --- a/crates/quarto-core/src/transforms/navigation_href.rs +++ b/crates/quarto-core/src/transforms/navigation_href.rs @@ -999,7 +999,7 @@ mod tests { use quarto_pandoc_types::block::{Block, Figure, Paragraph}; let mut blocks = vec![ - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr: Default::default(), caption: Caption { short: None, @@ -1009,7 +1009,7 @@ mod tests { content: vec![plain(vec![test_image("/images/x.svg")])], source_info: SourceInfo::for_test(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }), + })), Block::Paragraph(Paragraph { content: vec![test_image("images/y.svg")], source_info: SourceInfo::for_test(), diff --git a/crates/quarto-core/src/transforms/panel_tabset.rs b/crates/quarto-core/src/transforms/panel_tabset.rs index fde38821c0..f9cd5509d2 100644 --- a/crates/quarto-core/src/transforms/panel_tabset.rs +++ b/crates/quarto-core/src/transforms/panel_tabset.rs @@ -169,7 +169,7 @@ fn transform_block(block: &mut Block, diagnostics: &mut Vec) if div.attr.1.iter().any(|c| c == TABSET_CLASS) && let Some(custom) = convert_div_to_tabset(div, diagnostics) { - *block = Block::Custom(custom); + *block = Block::Custom(Box::new(custom)); } } Block::Table(table) => { @@ -330,7 +330,7 @@ mod tests { } fn header(level: usize, text: &str, classes: &[&str]) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: ( String::new(), @@ -343,7 +343,7 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn para(text: &str) -> Block { @@ -357,7 +357,7 @@ mod tests { } fn tabset_div(content: Vec) -> Block { - Block::Div(Div { + Block::Div(Box::new(Div { attr: ( String::new(), vec![TABSET_CLASS.to_string()], @@ -366,7 +366,7 @@ mod tests { content, source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } async fn run_transform(blocks: Vec, format: Format) -> (Pandoc, Vec) { @@ -519,12 +519,12 @@ mod tests { async fn group_attribute_lands_in_plain_data() { let mut attrs = hashlink::LinkedHashMap::new(); attrs.insert("group".to_string(), "language".to_string()); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: (String::new(), vec![TABSET_CLASS.to_string()], attrs), content: vec![header(2, "Python", &[]), para("py")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let (ast, _) = run_transform(vec![div], Format::html()).await; let Block::Custom(custom) = &ast.blocks[0] else { diff --git a/crates/quarto-core/src/transforms/panel_tabset_resolve.rs b/crates/quarto-core/src/transforms/panel_tabset_resolve.rs index 5e9e11d6c0..f67c4a91b6 100644 --- a/crates/quarto-core/src/transforms/panel_tabset_resolve.rs +++ b/crates/quarto-core/src/transforms/panel_tabset_resolve.rs @@ -156,7 +156,7 @@ fn resolve_block(block: &mut Block, counter: &mut u32) { // Nested tabsets inside the panes resolve after the // parent claimed its id (document-order numbering). resolve_blocks(&mut resolved_div.content, counter); - *block = Block::Div(resolved_div); + *block = Block::Div(Box::new(resolved_div)); } else { for (_name, slot) in &mut custom.slots { match slot { @@ -246,14 +246,14 @@ fn resolve_tabset(custom: &mut CustomNode, counter: &mut u32) -> Div { Some(Slot::Blocks(blocks)) => blocks, _ => Vec::new(), }; - panes.push(Block::Div(Div { + panes.push(Block::Div(Box::new(Div { attr: pane_attr, content, source_info: tabset_source(), attr_source: AttrSourceInfo::empty(), - })); + }))); } - let tab_content = Block::Div(Div { + let tab_content = Block::Div(Box::new(Div { attr: ( String::new(), vec!["tab-content".to_string()], @@ -262,7 +262,7 @@ fn resolve_tabset(custom: &mut CustomNode, counter: &mut u32) -> Div { content: panes, source_info: tabset_source(), attr_source: AttrSourceInfo::empty(), - }); + })); // ── Outer div: the original attr (panel-tabset class and any // group= attribute, which the HTML writer emits as data-group=). ── @@ -344,7 +344,7 @@ mod tests { #[test] fn resolves_to_nav_and_panes() { - let mut blocks = vec![Block::Custom(make_tabset(&["Alpha", "Beta"], 0))]; + let mut blocks = vec![Block::Custom(Box::new(make_tabset(&["Alpha", "Beta"], 0)))]; let mut counter = 1u32; resolve_blocks(&mut blocks, &mut counter); @@ -391,7 +391,7 @@ mod tests { #[test] fn active_index_places_active_on_selected_tab_only() { - let mut blocks = vec![Block::Custom(make_tabset(&["A", "B"], 1))]; + let mut blocks = vec![Block::Custom(Box::new(make_tabset(&["A", "B"], 1)))]; let mut counter = 1u32; resolve_blocks(&mut blocks, &mut counter); @@ -413,8 +413,8 @@ mod tests { #[test] fn counter_increments_in_document_order() { let mut blocks = vec![ - Block::Custom(make_tabset(&["A"], 0)), - Block::Custom(make_tabset(&["B"], 0)), + Block::Custom(Box::new(make_tabset(&["A"], 0))), + Block::Custom(Box::new(make_tabset(&["B"], 0))), ]; let mut counter = 1u32; resolve_blocks(&mut blocks, &mut counter); @@ -441,9 +441,12 @@ mod tests { let mut inner = make_tabset(&["Inner"], 0); inner.plain_data["level"] = json!(3); let mut outer = make_tabset(&["Outer"], 0); - outer.set_slot("content-0", Slot::Blocks(vec![Block::Custom(inner)])); + outer.set_slot( + "content-0", + Slot::Blocks(vec![Block::Custom(Box::new(inner))]), + ); - let mut blocks = vec![Block::Custom(outer)]; + let mut blocks = vec![Block::Custom(Box::new(outer))]; let mut counter = 1u32; resolve_blocks(&mut blocks, &mut counter); diff --git a/crates/quarto-core/src/transforms/proof.rs b/crates/quarto-core/src/transforms/proof.rs index de971bc1aa..403b46416f 100644 --- a/crates/quarto-core/src/transforms/proof.rs +++ b/crates/quarto-core/src/transforms/proof.rs @@ -23,7 +23,7 @@ //! treatment. See the Phase 2 plan follow-up. use quarto_pandoc_types::attr::{Attr, AttrSourceInfo}; -use quarto_pandoc_types::block::{Block, Blocks, Div, Header}; +use quarto_pandoc_types::block::{Block, Blocks, Div}; use quarto_pandoc_types::custom::{CustomNode, Slot}; use quarto_pandoc_types::inline::Inlines; use quarto_pandoc_types::pandoc::Pandoc; @@ -121,7 +121,7 @@ fn transform_block(block: &mut Block) { attr_source: AttrSourceInfo::empty(), }, )); - *block = Block::Custom(converted); + *block = Block::Custom(Box::new(converted)); } } @@ -205,8 +205,8 @@ fn extract_name_attr(attr: &mut Attr, attr_source: &AttrSourceInfo) -> Option Option { if let Some(Block::Header(_)) = content.first() { let first = content.remove(0); - if let Block::Header(Header { content: title, .. }) = first { - return Some(title); + if let Block::Header(header) = first { + return Some(header.content); } } None @@ -250,12 +250,12 @@ mod tests { #[test] fn div_proof_becomes_proof_custom_node() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr("", &["proof"]), content: vec![para("Proof body.")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div]); let Block::Custom(node) = &out[0] else { panic!("expected custom node"); @@ -269,12 +269,12 @@ mod tests { #[test] fn proof_not_recognized_as_crossref_target() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr("my-proof", &["proof"]), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div]); // Even with an id, a Proof doesn't carry `plain_data.ref_type`, // so `crossref_target_view` returns None — the indexer and @@ -286,12 +286,12 @@ mod tests { fn proof_with_name_attribute_captures_title() { let mut kvs = LinkedHashMap::new(); kvs.insert("name".into(), "Custom proof title".into()); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: (String::new(), vec!["proof".into()], kvs), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div]); let Block::Custom(node) = &out[0] else { panic!() @@ -307,12 +307,12 @@ mod tests { #[test] fn div_without_proof_class_untouched() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr("", &["not-a-proof"]), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div.clone()]); assert_eq!(out, vec![div]); } diff --git a/crates/quarto-core/src/transforms/responsive_image.rs b/crates/quarto-core/src/transforms/responsive_image.rs index 5851f2976d..3a05f0e38a 100644 --- a/crates/quarto-core/src/transforms/responsive_image.rs +++ b/crates/quarto-core/src/transforms/responsive_image.rs @@ -426,11 +426,11 @@ mod tests { content: vec![para(vec![img()])], source_info: si(), }), - Block::OrderedList(OrderedList { + Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![vec![para(vec![img()])]], source_info: si(), - }), + })), Block::BulletList(BulletList { content: vec![vec![para(vec![img()])]], source_info: si(), @@ -440,21 +440,21 @@ mod tests { content: vec![(vec![img()], vec![vec![para(vec![img()])]])], source_info: si(), }), - Block::Header(Header { + Block::Header(Box::new(Header { level: 1, attr: attr(&[], &[]), content: vec![img()], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), - Block::Div(Div { + })), + Block::Div(Box::new(Div { attr: attr(&[], &[]), content: vec![para(vec![img()])], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), // Figure body plus both caption slots. - Block::Figure(Figure { + Block::Figure(Box::new(Figure { attr: attr(&[], &[]), caption: Caption { short: Some(vec![img()]), @@ -464,7 +464,7 @@ mod tests { content: vec![para(vec![img()])], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), Block::CaptionBlock(CaptionBlock { content: vec![img()], source_info: si(), @@ -486,7 +486,7 @@ mod tests { source_info: si(), attr_source: AttrSourceInfo::empty(), }; - Block::Table(Table { + Block::Table(Box::new(Table { attr: attr(&[], &[]), caption: Caption { short: None, @@ -516,7 +516,7 @@ mod tests { }, source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) }, // Inline containers, all in one paragraph. para(vec![ @@ -574,12 +574,12 @@ mod tests { content: vec![vec![para(vec![image(&[], &[])])]], source_info: si(), }); - let mut blocks = vec![Block::Div(Div { + let mut blocks = vec![Block::Div(Box::new(Div { attr: attr(&[], &[]), content: vec![inner], source_info: si(), attr_source: AttrSourceInfo::empty(), - })]; + }))]; visit_blocks(&mut blocks); assert_eq!( first_image_classes(&blocks), diff --git a/crates/quarto-core/src/transforms/sectionize.rs b/crates/quarto-core/src/transforms/sectionize.rs index f5c42bfe68..ca5932595e 100644 --- a/crates/quarto-core/src/transforms/sectionize.rs +++ b/crates/quarto-core/src/transforms/sectionize.rs @@ -96,7 +96,7 @@ mod tests { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![ - Block::Header(Header { + Block::Header(Box::new(Header { level: 2, attr: ("sec-a".to_string(), vec![], hashlink::LinkedHashMap::new()), content: vec![Inline::Str(Str { @@ -105,7 +105,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - }), + })), Block::Paragraph(Paragraph { content: vec![Inline::Str(Str { text: "Content.".to_string(), diff --git a/crates/quarto-core/src/transforms/shortcode_resolve.rs b/crates/quarto-core/src/transforms/shortcode_resolve.rs index 795687fd3c..6ad9a4e5ce 100644 --- a/crates/quarto-core/src/transforms/shortcode_resolve.rs +++ b/crates/quarto-core/src/transforms/shortcode_resolve.rs @@ -1012,7 +1012,8 @@ fn stamp_block(block: &mut Block, name: &str, token_arc: &Arc) { } } } - Block::Header(Header { content, .. }) => { + Block::Header(header) => { + let Header { content, .. } = &mut **header; for child in content.iter_mut() { stamp_inline(child, name, token_arc); } @@ -1022,8 +1023,14 @@ fn stamp_block(block: &mut Block, name: &str, token_arc: &Arc) { stamp_block(child, name, token_arc); } } - Block::OrderedList(OrderedList { content, .. }) - | Block::BulletList(BulletList { content, .. }) => { + Block::OrderedList(_) | Block::BulletList(_) => { + // `OrderedList` is boxed in `Block` and `BulletList` is not, so + // one or-pattern cannot bind `content` for both. + let content = match block { + Block::OrderedList(list) => &mut list.content, + Block::BulletList(list) => &mut list.content, + _ => unreachable!("outer arm matched a list"), + }; for item in content.iter_mut() { for child in item.iter_mut() { stamp_block(child, name, token_arc); @@ -1042,9 +1049,10 @@ fn stamp_block(block: &mut Block, name: &str, token_arc: &Arc) { } } } - Block::Figure(Figure { - content, caption, .. - }) => { + Block::Figure(figure) => { + let Figure { + content, caption, .. + } = &mut **figure; for child in content.iter_mut() { stamp_block(child, name, token_arc); } @@ -1059,18 +1067,20 @@ fn stamp_block(block: &mut Block, name: &str, token_arc: &Arc) { } } } - Block::Div(Div { content, .. }) => { + Block::Div(div) => { + let Div { content, .. } = &mut **div; for child in content.iter_mut() { stamp_block(child, name, token_arc); } } - Block::Table(Table { - caption, - head, - bodies, - foot, - .. - }) => { + Block::Table(table) => { + let Table { + caption, + head, + bodies, + foot, + .. + } = &mut **table; if let Some(short) = caption.short.as_mut() { for child in short.iter_mut() { stamp_inline(child, name, token_arc); @@ -1787,12 +1797,13 @@ fn resolve_block<'a>( resolve_inlines(line, transform, metadata, diagnostics, lua_engine).await; } } - Block::Header(Header { - attr, - content, - source_info, - .. - }) => { + Block::Header(header) => { + let Header { + attr, + content, + source_info, + .. + } = &mut **header; expand_attr_value_shortcodes( attr, transform, @@ -1807,7 +1818,8 @@ fn resolve_block<'a>( Block::BlockQuote(BlockQuote { content, .. }) => { resolve_blocks(content, transform, metadata, diagnostics, lua_engine).await; } - Block::OrderedList(OrderedList { content, .. }) => { + Block::OrderedList(ordered_list) => { + let OrderedList { content, .. } = &mut **ordered_list; for item in content { resolve_blocks(item, transform, metadata, diagnostics, lua_engine).await; } @@ -1825,9 +1837,10 @@ fn resolve_block<'a>( } } } - Block::Figure(Figure { - content, caption, .. - }) => { + Block::Figure(figure) => { + let Figure { + content, caption, .. + } = &mut **figure; resolve_blocks(content, transform, metadata, diagnostics, lua_engine).await; if let Some(short) = &mut caption.short { resolve_inlines(short, transform, metadata, diagnostics, lua_engine).await; @@ -1836,12 +1849,13 @@ fn resolve_block<'a>( resolve_blocks(long, transform, metadata, diagnostics, lua_engine).await; } } - Block::Div(Div { - attr, - content, - source_info, - .. - }) => { + Block::Div(div) => { + let Div { + attr, + content, + source_info, + .. + } = &mut **div; expand_attr_value_shortcodes( attr, transform, @@ -1853,13 +1867,14 @@ fn resolve_block<'a>( .await; resolve_blocks(content, transform, metadata, diagnostics, lua_engine).await; } - Block::Table(Table { - caption, - head, - bodies, - foot, - .. - }) => { + Block::Table(table) => { + let Table { + caption, + head, + bodies, + foot, + .. + } = &mut **table; // Table caption if let Some(short) = &mut caption.short { resolve_inlines(short, transform, metadata, diagnostics, lua_engine).await; @@ -3350,7 +3365,7 @@ mod tests { for (k, v) in attributes { kvs.insert(k.to_string(), v.to_string()); } - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), classes.into_iter().map(String::from).collect(), @@ -3359,7 +3374,7 @@ mod tests { text: text.to_string(), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn code_text(block: &Block) -> &str { @@ -3442,7 +3457,7 @@ mod tests { ); let mut ast = Pandoc { meta: meta_with_version(), - blocks: vec![Block::Div(Div { + blocks: vec![Block::Div(Box::new(Div { attr: ( "{{< meta version >}}".to_string(), vec!["{{< meta version >}}".to_string()], @@ -3451,7 +3466,7 @@ mod tests { content: vec![], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let diags = run_transform(&mut ast).await; let Block::Div(div) = &ast.blocks[0] else { diff --git a/crates/quarto-core/src/transforms/table_bootstrap_class.rs b/crates/quarto-core/src/transforms/table_bootstrap_class.rs index 2e3bd9bdb6..8a360dc941 100644 --- a/crates/quarto-core/src/transforms/table_bootstrap_class.rs +++ b/crates/quarto-core/src/transforms/table_bootstrap_class.rs @@ -204,12 +204,12 @@ fn wrap_in_div(block: &mut Block, class: String) { let source_info = block.source_info().clone(); let inner = std::mem::replace( block, - Block::Div(Div { + Block::Div(Box::new(Div { attr: (String::new(), vec![class], hashlink::LinkedHashMap::new()), content: Vec::new(), source_info, attr_source: AttrSourceInfo::empty(), - }), + })), ); if let Block::Div(div) = block { div.content.push(inner); @@ -320,7 +320,7 @@ mod tests { classes.into_iter().map(|s| s.to_string()).collect(), LinkedHashMap::new(), ); - Block::Table(Table { + Block::Table(Box::new(Table { attr, caption: Caption { short: None, @@ -369,7 +369,7 @@ mod tests { }, source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn ast_with(blocks: Vec) -> Pandoc { @@ -442,12 +442,12 @@ mod tests { #[test] fn table_nested_in_div_is_reached() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: empty_attr(), content: vec![table_with_classes(vec![])], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }); + })); let mut ast = ast_with(vec![div]); apply_to_blocks(&mut ast.blocks); let Block::Div(d) = &ast.blocks[0] else { @@ -469,11 +469,11 @@ mod tests { strings(&classes), LinkedHashMap::new(), ); - Block::Custom( + Block::Custom(Box::new( CustomNode::new(FLOAT_REF_TARGET, attr, dummy_source_info()) .with_data(serde_json::json!({ "ref_type": ref_type })) .with_slot("content", Slot::Blocks(content)), - ) + )) } fn float_content(block: &Block) -> &[Block] { @@ -617,12 +617,12 @@ mod tests { #[test] fn every_table_in_a_multi_table_float_gets_the_classes() { - let layout = Block::Div(Div { + let layout = Block::Div(Box::new(Div { attr: empty_attr(), content: vec![table_with_classes(vec![])], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }); + })); let mut ast = ast_with(vec![float_node( "tbl", vec!["hover"], diff --git a/crates/quarto-core/src/transforms/theorem.rs b/crates/quarto-core/src/transforms/theorem.rs index e0fd1d18c9..00729f256d 100644 --- a/crates/quarto-core/src/transforms/theorem.rs +++ b/crates/quarto-core/src/transforms/theorem.rs @@ -42,7 +42,7 @@ use quarto_error_reporting::DiagnosticMessage; use quarto_pandoc_types::attr::{Attr, AttrSourceInfo}; -use quarto_pandoc_types::block::{Block, Blocks, Div, Header}; +use quarto_pandoc_types::block::{Block, Blocks, Div}; use quarto_pandoc_types::custom::{CustomNode, Slot}; use quarto_pandoc_types::inline::Inlines; use quarto_pandoc_types::pandoc::Pandoc; @@ -209,7 +209,7 @@ fn transform_block( ref_type, &kind, ); - *block = Block::Custom(converted); + *block = Block::Custom(Box::new(converted)); } } } @@ -366,8 +366,8 @@ fn extract_name_attr(attr: &mut Attr, attr_source: &AttrSourceInfo) -> Option Option { if let Some(Block::Header(_)) = content.first() { let first = content.remove(0); - if let Block::Header(Header { content: title, .. }) = first { - return Some(title); + if let Block::Header(header) = first { + return Some(header.content); } } None @@ -456,12 +456,12 @@ mod tests { #[test] fn plain_theorem_div_becomes_theorem_custom_node() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes("thm-pyth", &["theorem"]), content: vec![para("For a right triangle...")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div]); let Block::Custom(node) = &out[0] else { @@ -494,12 +494,12 @@ mod tests { ("example", "exm", "Example"), ("exercise", "exr", "Exercise"), ] { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes(&format!("{ref_type}-x"), &[class]), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div]); let Block::Custom(node) = &out[0] else { panic!("{} did not sugar", class); @@ -511,12 +511,12 @@ mod tests { #[test] fn div_without_theorem_class_untouched() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes("thm-fake", &["callout-note"]), content: vec![para("not a theorem")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div.clone()]); assert_eq!(out, vec![div]); } @@ -525,12 +525,12 @@ mod tests { fn name_attribute_becomes_title_slot() { let mut kvs = LinkedHashMap::new(); kvs.insert("name".into(), "Pythagorean Theorem".into()); - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: ("thm-pyth".into(), vec!["theorem".into()], kvs), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div]); let Block::Custom(node) = &out[0] else { panic!() @@ -550,10 +550,10 @@ mod tests { #[test] fn first_header_becomes_title_if_no_name_attribute() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes("thm-x", &["theorem"]), content: vec![ - Block::Header(Header { + Block::Header(Box::new(Header { level: 3, attr: (String::new(), Vec::new(), LinkedHashMap::new()), content: vec![Inline::Str(Str { @@ -562,12 +562,12 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), + })), para("body"), ], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div]); let Block::Custom(node) = &out[0] else { panic!(); @@ -589,12 +589,12 @@ mod tests { #[test] fn crossref_target_view_recognizes_sugared_theorem() { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes("thm-x", &["theorem"]), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div]); let view = crossref_target_view(&out[0]).expect("theorem visible as crossref target"); assert_eq!(view.identifier, "thm-x"); @@ -606,12 +606,12 @@ mod tests { fn theorem_without_id_still_sugars() { // Unnumbered theorem: no id. Still becomes a Theorem custom // node so renderers can style it consistently. - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes("", &["theorem"]), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let out = run(vec![div]); let Block::Custom(node) = &out[0] else { panic!() @@ -625,12 +625,12 @@ mod tests { // (see plan bd-gvhe §D1). Without this, FloatRefTargetSugarTransform // would claim it later and render as a generic Div with a // "Theorem 1: " caption on the last paragraph. - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes("thm-x", &[]), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let (out, diags) = run_with_registry(vec![div]); assert!(diags.is_empty(), "unexpected diags: {diags:?}"); let Block::Custom(node) = &out[0] else { @@ -644,12 +644,12 @@ mod tests { #[test] fn id_prefix_detects_all_theorem_flavors() { for (_class, ref_type, kind) in THEOREM_CLASSES { - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes(&format!("{ref_type}-x"), &[]), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let (out, _) = run_with_registry(vec![div]); let Block::Custom(node) = &out[0] else { panic!("{ref_type} by id alone did not sugar"); @@ -662,12 +662,12 @@ mod tests { #[test] fn id_only_non_theorem_prefix_leaves_div_alone() { // `::: {#fig-foo}` must NOT be theorem-sugared — it's a float. - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes("fig-foo", &[]), content: vec![para("caption")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let (out, diags) = run_with_registry(vec![div.clone()]); assert!(diags.is_empty()); // Still a Div, not a Custom node. @@ -679,12 +679,12 @@ mod tests { // Plan §D1 edge case: `.theorem #fig-x` → warn. The div still // sugars as a theorem (class wins for display), but the id // prefix (`fig`) disagrees with the theorem flavor. - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes("fig-x", &["theorem"]), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let (out, diags) = run_with_registry(vec![div]); assert!(matches!(out[0], Block::Custom(_))); assert_eq!( @@ -702,12 +702,12 @@ mod tests { #[test] fn class_id_match_emits_no_diagnostic() { // `::: {.theorem #thm-x}` — consistent. No warning. - let div = Block::Div(Div { + let div = Block::Div(Box::new(Div { attr: attr_id_classes("thm-x", &["theorem"]), content: vec![para("body")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let (_out, diags) = run_with_registry(vec![div]); assert!(diags.is_empty(), "unexpected diags: {diags:?}"); } diff --git a/crates/quarto-core/src/transforms/title_block.rs b/crates/quarto-core/src/transforms/title_block.rs index 6d4d1556e0..3f93ea01a3 100644 --- a/crates/quarto-core/src/transforms/title_block.rs +++ b/crates/quarto-core/src/transforms/title_block.rs @@ -186,13 +186,13 @@ pub(crate) fn create_title_header(content: Vec) -> Block { for inline in &mut content { stamp_generated(inline, &source_info); } - Block::Header(Header { + Block::Header(Box::new(Header { level: 1, attr: empty_attr(), content, source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Recursively stamp `provenance` over an inline subtree. @@ -420,7 +420,7 @@ mod tests { dummy_source_info(), ), blocks: vec![ - Block::Header(Header { + Block::Header(Box::new(Header { level: 1, attr: empty_attr(), content: vec![Inline::Str(Str { @@ -429,7 +429,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }), + })), Block::Paragraph(Paragraph { content: vec![Inline::Str(Str { text: "Content".to_string(), diff --git a/crates/quarto-core/src/transforms/toc_generate.rs b/crates/quarto-core/src/transforms/toc_generate.rs index 9ce88014ab..7401597381 100644 --- a/crates/quarto-core/src/transforms/toc_generate.rs +++ b/crates/quarto-core/src/transforms/toc_generate.rs @@ -280,7 +280,7 @@ mod tests { } fn make_header(level: usize, id: &str, text: &str) -> Block { - Block::Header(Header { + Block::Header(Box::new(Header { level, attr: (id.to_string(), vec![], hashlink::LinkedHashMap::new()), content: vec![Inline::Str(Str { @@ -289,7 +289,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - }) + })) } fn make_para(text: &str) -> Block { diff --git a/crates/quarto-core/tests/integration/capture_splice_engines.rs b/crates/quarto-core/tests/integration/capture_splice_engines.rs index 94b9b6b97f..b78208d761 100644 --- a/crates/quarto-core/tests/integration/capture_splice_engines.rs +++ b/crates/quarto-core/tests/integration/capture_splice_engines.rs @@ -28,7 +28,7 @@ use quarto_core::engine::cell_lang::engine_cell_lang; use quarto_core::engine::jupyter::JupyterError; use quarto_core::engine::preview_record::record_capture; use quarto_core::project::ProjectContext; -use quarto_pandoc_types::{Block, Div, Pandoc}; +use quarto_pandoc_types::{Block, Pandoc}; use quarto_system_runtime::{NativeRuntime, SystemRuntime}; fn engine_available(name: &str) -> bool { @@ -96,10 +96,10 @@ fn parse(qmd: &str, name: &str) -> Pandoc { } fn is_cell_div(block: &Block) -> bool { - let Block::Div(Div { attr, .. }) = block else { + let Block::Div(div) = block else { return false; }; - attr.1.iter().any(|c| c == "cell") + div.attr.1.iter().any(|c| c == "cell") } /// Run `content` through the real engine via `record_capture`, then diff --git a/crates/quarto-highlight/tests/integration/annotate.rs b/crates/quarto-highlight/tests/integration/annotate.rs index 3d9063c783..4c6fa663b7 100644 --- a/crates/quarto-highlight/tests/integration/annotate.rs +++ b/crates/quarto-highlight/tests/integration/annotate.rs @@ -25,12 +25,12 @@ fn empty_attr_source() -> AttrSourceInfo { } fn make_code_block(class: &str, text: &str) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: attr_with_class(class), text: text.to_string(), source_info: empty_source_info(), attr_source: empty_attr_source(), - }) + })) } fn empty_pandoc() -> Pandoc { @@ -144,12 +144,12 @@ fn annotates_inline_code() { fn code_block_with_no_class_is_skipped() { use hashlink::LinkedHashMap; let mut doc = empty_pandoc(); - doc.blocks.push(Block::CodeBlock(CodeBlock { + doc.blocks.push(Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![], LinkedHashMap::new()), text: "foo bar".to_string(), source_info: empty_source_info(), attr_source: empty_attr_source(), - })); + }))); annotate_pandoc(&mut doc, None).expect("annotate must not error"); @@ -195,7 +195,7 @@ fn builtin_lookup_api_is_case_insensitive() { fn annotate_block_with_classes(classes: &[&str], text: &str) -> Attr { use hashlink::LinkedHashMap; let mut doc = empty_pandoc(); - doc.blocks.push(Block::CodeBlock(CodeBlock { + doc.blocks.push(Block::CodeBlock(Box::new(CodeBlock { attr: ( String::new(), classes.iter().map(|c| c.to_string()).collect(), @@ -204,7 +204,7 @@ fn annotate_block_with_classes(classes: &[&str], text: &str) -> Attr { text: text.to_string(), source_info: empty_source_info(), attr_source: empty_attr_source(), - })); + }))); annotate_pandoc(&mut doc, None).expect("annotate must not error"); let Block::CodeBlock(cb) = doc.blocks.remove(0) else { unreachable!() diff --git a/crates/quarto-highlight/tests/integration/trait_provider.rs b/crates/quarto-highlight/tests/integration/trait_provider.rs index cb06650bfe..8906471152 100644 --- a/crates/quarto-highlight/tests/integration/trait_provider.rs +++ b/crates/quarto-highlight/tests/integration/trait_provider.rs @@ -64,12 +64,12 @@ fn empty_attr_source() -> AttrSourceInfo { } fn make_code_block(class: &str, text: &str) -> Block { - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: attr_with_class(class), text: text.to_string(), source_info: empty_source_info(), attr_source: empty_attr_source(), - }) + })) } fn empty_pandoc() -> Pandoc { diff --git a/crates/quarto-navigation/src/render_html.rs b/crates/quarto-navigation/src/render_html.rs index c479c9823a..a2c5b7a70b 100644 --- a/crates/quarto-navigation/src/render_html.rs +++ b/crates/quarto-navigation/src/render_html.rs @@ -1732,7 +1732,7 @@ mod tests { target_source: TargetSourceInfo::empty(), })); let cv = ConfigValue { - value: ConfigValueKind::PandocBlocks(vec![Block::Figure(Figure { + value: ConfigValueKind::PandocBlocks(vec![Block::Figure(Box::new(Figure { attr: Default::default(), caption: Caption { short: None, @@ -1748,7 +1748,7 @@ mod tests { })], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - })]), + }))]), source_info: SourceInfo::for_test(), merge_op: Default::default(), }; diff --git a/crates/quarto-pandoc-types/src/block.rs b/crates/quarto-pandoc-types/src/block.rs index 5ec384a46e..e9cb455a34 100644 --- a/crates/quarto-pandoc-types/src/block.rs +++ b/crates/quarto-pandoc-types/src/block.rs @@ -12,34 +12,46 @@ use crate::list::ListAttributes; use crate::table::Table; use serde::{Deserialize, Serialize}; +/// A block element. +/// +/// **Layout.** As with [`Inline`](crate::inline::Inline), every element of a +/// `Vec` occupies `size_of::()` bytes whichever variant it +/// holds, so the variants whose payload is larger than `Paragraph`'s are +/// boxed (`Table` alone made this enum 824 bytes). The assertion below keeps +/// it that way; see +/// `claude-notes/plans/2026-11-12-ast-string-storage-study.md`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Block { Plain(Plain), Paragraph(Paragraph), LineBlock(LineBlock), - CodeBlock(CodeBlock), - RawBlock(RawBlock), + CodeBlock(Box), + RawBlock(Box), BlockQuote(BlockQuote), - OrderedList(OrderedList), + OrderedList(Box), BulletList(BulletList), DefinitionList(DefinitionList), - Header(Header), + Header(Box
    ), HorizontalRule(HorizontalRule), - Table(Table), - Figure(Figure), - Div(Div), + Table(Box), + Figure(Box
    ), + Div(Box
    ), // quarto extensions - BlockMetadata(MetaBlock), - NoteDefinitionPara(NoteDefinitionPara), - NoteDefinitionFencedBlock(NoteDefinitionFencedBlock), + BlockMetadata(Box), + NoteDefinitionPara(Box), + NoteDefinitionFencedBlock(Box), CaptionBlock(CaptionBlock), /// Custom node for Quarto extensions (callouts, tabsets, etc.) /// /// Parsed from Divs with special class names. When serialized to Pandoc JSON, /// these are converted to wrapper Divs with `__quarto_custom_node` class. - Custom(CustomNode), + Custom(Box), } +// 64 bytes on 64-bit targets, 32 on wasm32. If this fails, a variant's +// payload has outgrown `Paragraph`'s: box it rather than raise the bound. +const _: () = assert!(std::mem::size_of::() <= 8 * std::mem::size_of::()); + impl Block { pub fn source_info(&self) -> &quarto_source_map::SourceInfo { match self { @@ -242,37 +254,37 @@ mod tests { #[test] fn source_info_codeblock() { let si = test_si(2, 20, 30); - let block = Block::CodeBlock(CodeBlock { + let block = Block::CodeBlock(Box::new(CodeBlock { attr: crate::attr::empty_attr(), text: String::new(), source_info: si.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); assert_eq!(block.source_info(), &si); } #[test] fn source_info_header() { let si = test_si(3, 30, 40); - let block = Block::Header(Header { + let block = Block::Header(Box::new(Header { level: 1, attr: crate::attr::empty_attr(), content: vec![], source_info: si.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); assert_eq!(block.source_info(), &si); } #[test] fn source_info_div() { let si = test_si(4, 40, 50); - let block = Block::Div(Div { + let block = Block::Div(Box::new(Div { attr: crate::attr::empty_attr(), content: vec![], source_info: si.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); assert_eq!(block.source_info(), &si); }