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..128ed17aa9 --- /dev/null +++ b/claude-notes/plans/2026-11-12-ast-string-storage-study.md @@ -0,0 +1,517 @@ +--- +title: "Study: arena / borrowed string storage for the Pandoc AST" +strand: bd-akgst1fi +related: [bd-is4q72tt] +status: "Direction agreed (no lifetimes; node layout first; text runs deferred). Inline and Block cold variants boxed: AST heap 88x -> 20.5x of source. Attr slimming and steps 3-5 open under epic bd-4x6kih4c." +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`. +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 + and re-examine. +2. **A for `Block`, then B.** +3. **Reader fast paths** (typography/escape no-op path, `Concat` hull). +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. + +## 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. + +### Step 2a: box cold `Block` variants (bd-09g8zl30) — measured + +Boxed the payloads of `CodeBlock`, `RawBlock`, `OrderedList`, `Header`, +`Table`, `Figure`, `Div`, `BlockMetadata`, `NoteDefinitionPara`, +`NoteDefinitionFencedBlock` and `Custom`. The same 8-word assertion now +guards `Block` (64 bytes native, 32 on wasm32). + +| | Start | After step 1 | After step 2a | +|---|---|---|---| +| `size_of::()` | 824 | 824 | 64 | +| `Vec` buffers | 160 MiB | 160 MiB | 12.5 MiB | +| AST heap | 2,304 MiB (87.7×) | 678 MiB (25.8×) | 538 MiB (20.5×) | +| AST heap blocks | 4.06 M | 4.36 M | 4.39 M | +| Peak RSS of the run | 2.75 GiB | 0.87 GiB | 0.72 GiB | +| Wall time | 7.7 s | 6.5 s | 6.2 s | + +This matches the option-A estimate (~535 MiB). `Vec` buffers are +now 68% of the AST heap, and text fields 10.5%. + +## 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. +- **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 + +None blocking. The text-run flag design is deferred (see F). + +## 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/comrak-to-pandoc/src/block.rs b/crates/comrak-to-pandoc/src/block.rs index 93aa8e143c..3bbb3fcc55 100644 --- a/crates/comrak-to-pandoc/src/block.rs +++ b/crates/comrak-to-pandoc/src/block.rs @@ -133,11 +133,11 @@ fn convert_block<'a>( // Raw HTML, as pandoc's commonmark reader (raw_html on) produces it. NodeValue::HtmlBlock(html) => { - vec![Block::RawBlock(RawBlock { + vec![Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: html.literal.clone(), source_info, - })] + }))] } // Unsupported block types - panic as they're outside CommonMark. @@ -197,13 +197,13 @@ fn convert_heading<'a>( let ast = node.data.borrow(); let source_info = get_source_info(&ast, source_ctx); let inlines = convert_children_to_inlines_with_source(node, source_ctx); - Block::Header(Header { + Block::Header(Box::new(Header { level: heading.level as usize, attr: empty_attr(), content: inlines, source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } fn convert_code_block(code_block: &NodeCodeBlock, source_info: SourceInfo) -> Block { @@ -223,12 +223,12 @@ fn convert_code_block(code_block: &NodeCodeBlock, source_info: SourceInfo) -> Bl ] }; - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), classes, LinkedHashMap::new()), text: code_block.literal.clone(), source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } fn convert_list<'a>( @@ -258,11 +258,11 @@ fn convert_list<'a>( ListDelimType::Paren => ListNumberDelim::OneParen, }, ); - Block::OrderedList(OrderedList { + Block::OrderedList(Box::new(OrderedList { attr, content: items, source_info, - }) + })) } } } diff --git a/crates/comrak-to-pandoc/src/compare.rs b/crates/comrak-to-pandoc/src/compare.rs index c4b5dee752..b210e2e9a0 100644 --- a/crates/comrak-to-pandoc/src/compare.rs +++ b/crates/comrak-to-pandoc/src/compare.rs @@ -215,20 +215,20 @@ mod tests { fn test_header_eq() { use hashlink::LinkedHashMap; - let a = Block::Header(Header { + let a = Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); - let b = Block::Header(Header { + })); + let b = Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: other_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); + })); assert!(block_eq(&a, &b)); } @@ -236,20 +236,20 @@ mod tests { fn test_header_neq_different_level() { use hashlink::LinkedHashMap; - let a = Block::Header(Header { + let a = Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); - let b = Block::Header(Header { + })); + let b = Block::Header(Box::new(Header { level: 2, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); + })); assert!(!block_eq(&a, &b)); } @@ -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)); } @@ -487,7 +487,7 @@ mod tests { #[test] fn test_codeblock_eq() { use hashlink::LinkedHashMap; - let a = Block::CodeBlock(quarto_pandoc_types::CodeBlock { + let a = Block::CodeBlock(Box::new(quarto_pandoc_types::CodeBlock { attr: ( String::new(), vec!["python".to_string()], @@ -496,8 +496,8 @@ mod tests { text: "print(1)".to_string(), source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); - let b = Block::CodeBlock(quarto_pandoc_types::CodeBlock { + })); + let b = Block::CodeBlock(Box::new(quarto_pandoc_types::CodeBlock { attr: ( String::new(), vec!["python".to_string()], @@ -506,7 +506,7 @@ mod tests { text: "print(1)".to_string(), source_info: other_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); + })); assert!(block_eq(&a, &b)); } @@ -538,7 +538,7 @@ mod tests { #[test] fn test_orderedlist_eq() { - let a = Block::OrderedList(quarto_pandoc_types::OrderedList { + let a = Block::OrderedList(Box::new(quarto_pandoc_types::OrderedList { attr: ( 1, quarto_pandoc_types::ListNumberStyle::Decimal, @@ -546,8 +546,8 @@ mod tests { ), content: vec![vec![]], source_info: dummy_source(), - }); - let b = Block::OrderedList(quarto_pandoc_types::OrderedList { + })); + let b = Block::OrderedList(Box::new(quarto_pandoc_types::OrderedList { attr: ( 1, quarto_pandoc_types::ListNumberStyle::Decimal, @@ -555,7 +555,7 @@ mod tests { ), content: vec![vec![]], source_info: other_source(), - }); + })); assert!(block_eq(&a, &b)); } @@ -573,18 +573,18 @@ mod tests { #[test] fn test_div_eq() { use hashlink::LinkedHashMap; - let a = Block::Div(quarto_pandoc_types::Div { + let a = Block::Div(Box::new(quarto_pandoc_types::Div { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: dummy_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); - let b = Block::Div(quarto_pandoc_types::Div { + })); + let b = Block::Div(Box::new(quarto_pandoc_types::Div { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: other_source(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }); + })); assert!(block_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..434a0385b6 100644 --- a/crates/comrak-to-pandoc/src/normalize.rs +++ b/crates/comrak-to-pandoc/src/normalize.rs @@ -39,7 +39,7 @@ fn normalize_block(block: Block) -> Block { } // Figure → Paragraph(Image) for standalone images - Block::Figure(fig) => normalize_figure(fig), + Block::Figure(fig) => normalize_figure(*fig), // Normalize code block attributes (keep only language class) and text Block::CodeBlock(mut cb) => { @@ -124,13 +124,13 @@ fn normalize_figure(fig: Figure) -> Block { } // Not a simple standalone image figure, recurse normally - Block::Figure(Figure { + Block::Figure(Box::new(Figure { content: fig.content.into_iter().map(normalize_block).collect(), caption: fig.caption, // TODO: normalize caption if needed attr: fig.attr, source_info: fig.source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Check if inlines contain a single Image. @@ -220,13 +220,13 @@ mod tests { fn test_normalize_strips_heading_id() { let ast = Pandoc { meta: ConfigValue::default(), - blocks: vec![Block::Header(Header { + blocks: vec![Block::Header(Box::new(Header { level: 1, content: vec![], attr: ("my-id".to_string(), vec![], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let normalized = normalize(ast); @@ -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(), })], }; @@ -268,7 +268,7 @@ mod tests { use quarto_pandoc_types::CodeBlock; let ast = Pandoc { meta: ConfigValue::default(), - blocks: vec![Block::CodeBlock(CodeBlock { + blocks: vec![Block::CodeBlock(Box::new(CodeBlock { attr: ( "id".to_string(), vec!["python".to_string(), "extra".to_string()], @@ -277,7 +277,7 @@ mod tests { text: "code\n".to_string(), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let normalized = normalize(ast); @@ -299,12 +299,12 @@ mod tests { use quarto_pandoc_types::CodeBlock; let ast = Pandoc { meta: ConfigValue::default(), - blocks: vec![Block::CodeBlock(CodeBlock { + blocks: vec![Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![], LinkedHashMap::new()), text: "code".to_string(), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let normalized = normalize(ast); @@ -375,14 +375,14 @@ mod tests { use quarto_pandoc_types::{ListNumberDelim, ListNumberStyle, OrderedList}; let ast = Pandoc { meta: ConfigValue::default(), - blocks: vec![Block::OrderedList(OrderedList { + blocks: vec![Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![vec![Block::Paragraph(Paragraph { content: vec![], source_info: empty_source_info(), })]], source_info: empty_source_info(), - })], + }))], }; let normalized = normalize(ast); @@ -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(), })], }; @@ -553,16 +553,16 @@ mod tests { // Figure containing Plain(Image) should become Paragraph(Image) let ast = Pandoc { meta: ConfigValue::default(), - blocks: vec![Block::Figure(Figure { + blocks: vec![Block::Figure(Box::new(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 { @@ -573,7 +573,7 @@ mod tests { attr: (String::new(), vec![], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let normalized = normalize(ast); @@ -587,16 +587,16 @@ mod tests { // Figure containing Paragraph(Image) should stay Paragraph(Image) let ast = Pandoc { meta: ConfigValue::default(), - blocks: vec![Block::Figure(Figure { + blocks: vec![Block::Figure(Box::new(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 { @@ -607,7 +607,7 @@ mod tests { attr: (String::new(), vec![], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let normalized = normalize(ast); @@ -620,7 +620,7 @@ mod tests { // Figure with multiple blocks should stay as Figure let ast = Pandoc { meta: ConfigValue::default(), - blocks: vec![Block::Figure(Figure { + blocks: vec![Block::Figure(Box::new(Figure { content: vec![ Block::Paragraph(Paragraph { content: vec![Inline::Str(Str { @@ -642,7 +642,7 @@ mod tests { attr: (String::new(), vec![], LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - })], + }))], }; let normalized = normalize(ast); @@ -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..48554cc1b7 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(), )); @@ -516,13 +516,13 @@ fn gen_header(inline_features: InlineFeatures) -> impl Strategy { ..inline_features }; (1..=6usize, gen_inlines(header_features)).prop_map(|(level, content)| { - Block::Header(Header { + Block::Header(Box::new(Header { level, content, attr: empty_attr(), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } @@ -530,12 +530,12 @@ fn gen_header(inline_features: InlineFeatures) -> impl Strategy { fn gen_code_block() -> impl Strategy { (safe_text(), gen_lang()).prop_map(|(text, lang)| { let classes = lang.map(|l| vec![l]).unwrap_or_default(); - Block::CodeBlock(CodeBlock { + Block::CodeBlock(Box::new(CodeBlock { text, attr: (String::new(), classes, LinkedHashMap::new()), source_info: empty_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } @@ -673,7 +673,7 @@ fn gen_single_block( 2..=4, ) .prop_map(|content| { - Block::OrderedList(OrderedList { + Block::OrderedList(Box::new(OrderedList { content, attr: ( 1, // start number @@ -681,7 +681,7 @@ fn gen_single_block( quarto_pandoc_types::ListNumberDelim::Period, ), source_info: empty_source_info(), - }) + })) }) .boxed(), )); diff --git a/crates/pampa/src/citeproc_filter.rs b/crates/pampa/src/citeproc_filter.rs index e5972da96a..59fcb770b7 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(), - }) + })) }; } @@ -1594,7 +1594,7 @@ pub fn generate_bibliography(processor: &mut Processor) -> Result, Ci if !blocks.is_empty() { // Wrap each entry in a Div with the reference ID // Attr is a tuple: (id, classes, attributes) - let entry_div = Block::Div(Div { + let entry_div = Block::Div(Box::new(Div { attr: ( format!("ref-{}", id), vec!["csl-entry".to_string()], @@ -1605,7 +1605,7 @@ pub fn generate_bibliography(processor: &mut Processor) -> Result, Ci quarto_source_map::By::citeproc(), ), attr_source: crate::pandoc::AttrSourceInfo::empty(), - }); + })); bib_blocks.push(entry_div); } } @@ -1657,7 +1657,7 @@ pub fn insert_bibliography(blocks: &mut Vec, bib_blocks: Vec) { } // No #refs div found, create one at the end - let refs_div = Block::Div(Div { + let refs_div = Block::Div(Box::new(Div { attr: ( "refs".to_string(), vec!["references".to_string(), "csl-bib-body".to_string()], @@ -1666,7 +1666,7 @@ pub fn insert_bibliography(blocks: &mut Vec, bib_blocks: Vec) { content: bib_blocks, source_info: quarto_source_map::SourceInfo::generated(quarto_source_map::By::citeproc()), attr_source: crate::pandoc::AttrSourceInfo::empty(), - }); + })); blocks.push(refs_div); } @@ -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,10 +2835,10 @@ mod tests { }], content: vec![], source_info: si(), - }); + })); let pandoc = Pandoc { meta: meta_map(vec![]), - blocks: vec![Block::Div(crate::pandoc::Div { + blocks: vec![Block::Div(Box::new(crate::pandoc::Div { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![Block::Paragraph(crate::pandoc::Paragraph { content: vec![cite], @@ -2846,7 +2846,7 @@ mod tests { })], source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - })], + }))], }; let citations = collect_citations(&pandoc); assert_eq!(citations.len(), 1); @@ -2877,7 +2877,7 @@ mod tests { ); let pandoc = Pandoc { meta: meta_map(vec![]), - blocks: vec![Block::Custom(node)], + blocks: vec![Block::Custom(Box::new(node))], }; let citations = collect_citations(&pandoc); assert_eq!(citations.len(), 1); @@ -2898,7 +2898,7 @@ mod tests { source_info: si(), })]), ); - let mut block = Block::Custom(node); + let mut block = Block::Custom(Box::new(node)); let rendered = str_renders(&["(Callout, 2020)"]); let mut citation_index = 0; transform_block(&mut block, &rendered, &mut citation_index); @@ -2941,12 +2941,12 @@ mod tests { #[test] fn test_insert_bibliography_replaces_existing_refs_div() { - let mut blocks = vec![Block::Div(crate::pandoc::Div { + let mut blocks = vec![Block::Div(Box::new(crate::pandoc::Div { attr: ("refs".to_string(), vec![], hashlink::LinkedHashMap::new()), content: vec![], // Empty initially source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - })]; + }))]; let bib_blocks = vec![Block::Paragraph(crate::pandoc::Paragraph { content: vec![str_inline("New bibliography")], source_info: si(), @@ -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 @@ -2999,13 +2999,13 @@ mod tests { fn test_collect_citations_in_header_block() { let pandoc = Pandoc { meta: meta_map(vec![]), - blocks: vec![Block::Header(crate::pandoc::Header { + blocks: vec![Block::Header(Box::new(crate::pandoc::Header { level: 1, attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![str_inline("Title "), make_cite("header2020")], source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - })], + }))], }; let citations = collect_citations(&pandoc); assert_eq!(citations.len(), 1); @@ -3016,7 +3016,7 @@ mod tests { fn test_collect_citations_in_ordered_list() { let pandoc = Pandoc { meta: meta_map(vec![]), - blocks: vec![Block::OrderedList(crate::pandoc::OrderedList { + blocks: vec![Block::OrderedList(Box::new(crate::pandoc::OrderedList { attr: ( 1, crate::pandoc::ListNumberStyle::Decimal, @@ -3027,7 +3027,7 @@ mod tests { source_info: si(), })]], source_info: si(), - })], + }))], }; let citations = collect_citations(&pandoc); assert_eq!(citations.len(), 1); @@ -3076,7 +3076,7 @@ mod tests { fn test_collect_citations_in_figure() { let pandoc = Pandoc { meta: meta_map(vec![]), - blocks: vec![Block::Figure(crate::pandoc::Figure { + blocks: vec![Block::Figure(Box::new(crate::pandoc::Figure { attr: ("fig1".to_string(), vec![], hashlink::LinkedHashMap::new()), caption: crate::pandoc::Caption { short: Some(vec![make_cite("figshort2020")]), @@ -3092,7 +3092,7 @@ mod tests { })], source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - })], + }))], }; let citations = collect_citations(&pandoc); assert_eq!(citations.len(), 3); @@ -3110,7 +3110,7 @@ mod tests { // Create a table with citations in caption and cells let pandoc = Pandoc { meta: meta_map(vec![]), - blocks: vec![Block::Table(Table { + blocks: vec![Block::Table(Box::new(Table { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), caption: Caption { short: Some(vec![make_cite("tableshort2020")]), @@ -3191,7 +3191,7 @@ mod tests { }, source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - })], + }))], }; let citations = collect_citations(&pandoc); assert_eq!(citations.len(), 5); @@ -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 { @@ -3570,7 +3570,7 @@ mod tests { source_info: si(), }), // Test in Div - Block::Div(crate::pandoc::Div { + Block::Div(Box::new(crate::pandoc::Div { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![Block::Paragraph(crate::pandoc::Paragraph { content: vec![make_cite("div2020")], @@ -3578,7 +3578,7 @@ mod tests { })], source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - }), + })), ]; let rendered = str_renders(&["(Container 2020)", "(Div 2020)"]); @@ -3607,7 +3607,7 @@ mod tests { fn test_transform_blocks_in_lists() { let mut blocks = vec![ // Test in OrderedList - Block::OrderedList(crate::pandoc::OrderedList { + Block::OrderedList(Box::new(crate::pandoc::OrderedList { attr: ( 1, crate::pandoc::ListNumberStyle::Decimal, @@ -3618,7 +3618,7 @@ mod tests { source_info: si(), })]], source_info: si(), - }), + })), // Test in BulletList Block::BulletList(crate::pandoc::BulletList { content: vec![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 = [ @@ -3767,13 +3767,13 @@ mod tests { content: vec![make_cite("plain2020")], source_info: si(), }), - Block::Header(crate::pandoc::Header { + Block::Header(Box::new(crate::pandoc::Header { level: 1, attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![make_cite("header2020")], source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - }), + })), ]; let rendered = str_renders(&["(Plain 2020)", "(Header 2020)"]); @@ -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 @@ -4455,7 +4455,7 @@ mod tests { #[test] fn test_insert_bibliography_adds_classes_to_existing_refs_div() { - let mut blocks = vec![Block::Div(crate::pandoc::Div { + let mut blocks = vec![Block::Div(Box::new(crate::pandoc::Div { attr: ( "refs".to_string(), vec![], // No classes initially @@ -4464,7 +4464,7 @@ mod tests { content: vec![], source_info: si(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - })]; + }))]; let bib_blocks = vec![Block::Paragraph(crate::pandoc::Paragraph { content: vec![str_inline("Ref 1")], source_info: si(), @@ -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..76711cbb15 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) @@ -207,6 +223,21 @@ macro_rules! handle_inline_filter { } macro_rules! handle_block_filter { + // For variants whose payload is boxed in `Block`; see the `boxed` arm of + // `handle_inline_filter!`. + (boxed $variant:ident, $value:ident, $filter_field:ident, $filter:expr, $ctx:expr, $out:expr) => { + if let Some(f) = &mut $filter.$filter_field { + blocks_apply_and_maybe_recurse!(*$value, f, $filter, $ctx, $out) + } else if let Some(f) = &mut $filter.block { + blocks_apply_and_maybe_recurse!(Block::$variant($value), f, $filter, $ctx, $out) + } else { + $out.push(traverse_block_structure( + Block::$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 { blocks_apply_and_maybe_recurse!($value, f, $filter, $ctx, $out) @@ -231,7 +262,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 +287,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 +296,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 +338,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 +354,7 @@ impl InlineFilterableStructure for pandoc::Cite { .collect(), content: topdown_traverse_inlines(self.content, filter, ctx), source_info: self.source_info, - }) + })) } } @@ -338,7 +372,7 @@ macro_rules! impl_block_filterable_terminal { $( impl BlockFilterableStructure for pandoc::$variant { fn filter_structure(self, _: &mut Filter, _ctx: &mut FilterContext) -> Block { - Block::$variant(self) + Block::$variant(self.into()) } } )* @@ -351,10 +385,13 @@ macro_rules! impl_block_filterable_simple { $( impl BlockFilterableStructure for pandoc::$variant { fn filter_structure(self, filter: &mut Filter, ctx: &mut FilterContext) -> Block { - Block::$variant(pandoc::$variant { - content: topdown_traverse_blocks(self.content, filter, ctx), - ..self - }) + Block::$variant( + pandoc::$variant { + content: topdown_traverse_blocks(self.content, filter, ctx), + ..self + } + .into(), + ) } } )* @@ -395,14 +432,14 @@ impl BlockFilterableStructure for pandoc::LineBlock { impl BlockFilterableStructure for pandoc::OrderedList { fn filter_structure(self, filter: &mut Filter, ctx: &mut FilterContext) -> Block { - Block::OrderedList(pandoc::OrderedList { + Block::OrderedList(Box::new(pandoc::OrderedList { content: self .content .into_iter() .map(|blocks| topdown_traverse_blocks(blocks, filter, ctx)) .collect(), ..self - }) + })) } } @@ -441,16 +478,16 @@ impl BlockFilterableStructure for pandoc::DefinitionList { impl BlockFilterableStructure for pandoc::Header { fn filter_structure(self, filter: &mut Filter, ctx: &mut FilterContext) -> Block { - Block::Header(pandoc::Header { + Block::Header(Box::new(pandoc::Header { content: topdown_traverse_inlines(self.content, filter, ctx), ..self - }) + })) } } impl BlockFilterableStructure for pandoc::Table { fn filter_structure(self, filter: &mut Filter, ctx: &mut FilterContext) -> Block { - Block::Table(pandoc::Table { + Block::Table(Box::new(pandoc::Table { caption: traverse_caption(self.caption, filter, ctx), head: pandoc::TableHead { rows: self @@ -488,17 +525,17 @@ impl BlockFilterableStructure for pandoc::Table { ..self.foot }, ..self - }) + })) } } impl BlockFilterableStructure for pandoc::Figure { fn filter_structure(self, filter: &mut Filter, ctx: &mut FilterContext) -> Block { - Block::Figure(pandoc::Figure { + Block::Figure(Box::new(pandoc::Figure { caption: traverse_caption(self.caption, filter, ctx), content: topdown_traverse_blocks(self.content, filter, ctx), ..self - }) + })) } } @@ -589,13 +626,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 +644,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 +686,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 @@ -691,25 +728,25 @@ fn topdown_traverse_block_into( handle_block_filter!(Paragraph, para, paragraph, filter, ctx, out) } Block::CodeBlock(code) => { - handle_block_filter!(CodeBlock, code, code_block, filter, ctx, out) + handle_block_filter!(boxed CodeBlock, code, code_block, filter, ctx, out) } Block::RawBlock(raw) => { - handle_block_filter!(RawBlock, raw, raw_block, filter, ctx, out) + handle_block_filter!(boxed RawBlock, raw, raw_block, filter, ctx, out) } Block::BulletList(list) => { handle_block_filter!(BulletList, list, bullet_list, filter, ctx, out) } Block::OrderedList(list) => { - handle_block_filter!(OrderedList, list, ordered_list, filter, ctx, out) + handle_block_filter!(boxed OrderedList, list, ordered_list, filter, ctx, out) } Block::BlockQuote(quote) => { handle_block_filter!(BlockQuote, quote, block_quote, filter, ctx, out) } Block::Div(div) => { - handle_block_filter!(Div, div, div, filter, ctx, out) + handle_block_filter!(boxed Div, div, div, filter, ctx, out) } Block::Figure(figure) => { - handle_block_filter!(Figure, figure, figure, filter, ctx, out) + handle_block_filter!(boxed Figure, figure, figure, filter, ctx, out) } Block::Plain(plain) => { handle_block_filter!(Plain, plain, plain, filter, ctx, out) @@ -721,10 +758,10 @@ fn topdown_traverse_block_into( handle_block_filter!(DefinitionList, def_list, definition_list, filter, ctx, out) } Block::Header(header) => { - handle_block_filter!(Header, header, header, filter, ctx, out) + handle_block_filter!(boxed Header, header, header, filter, ctx, out) } Block::Table(table) => { - handle_block_filter!(Table, table, table, filter, ctx, out) + handle_block_filter!(boxed Table, table, table, filter, ctx, out) } Block::HorizontalRule(hr) => { handle_block_filter!(HorizontalRule, hr, horizontal_rule, filter, ctx, out) @@ -733,22 +770,24 @@ fn topdown_traverse_block_into( Block::BlockMetadata(meta) => { if let Some(f) = &mut filter.meta { match f(meta.meta, ctx) { - FilterReturn::Unchanged(m) => out.push(Block::BlockMetadata(MetaBlock { - meta: m, - source_info: meta.source_info, - })), + FilterReturn::Unchanged(m) => { + out.push(Block::BlockMetadata(Box::new(MetaBlock { + meta: m, + source_info: meta.source_info, + }))) + } FilterReturn::FilterResult(new_meta, recurse) => { if !recurse { - out.push(Block::BlockMetadata(MetaBlock { + out.push(Block::BlockMetadata(Box::new(MetaBlock { meta: new_meta, source_info: meta.source_info, - })) + }))) } else { let traversed = topdown_traverse_config_value(new_meta, filter, ctx); - out.push(Block::BlockMetadata(MetaBlock { + out.push(Block::BlockMetadata(Box::new(MetaBlock { meta: traversed, source_info: meta.source_info, - })) + }))) } } }; @@ -759,24 +798,24 @@ fn topdown_traverse_block_into( Block::NoteDefinitionPara(refdef) => { // Process the inline content of the reference definition let content = topdown_traverse_inlines(refdef.content, filter, ctx); - out.push(Block::NoteDefinitionPara( + out.push(Block::NoteDefinitionPara(Box::new( crate::pandoc::block::NoteDefinitionPara { id: refdef.id, content, source_info: refdef.source_info, }, - )) + ))) } Block::NoteDefinitionFencedBlock(refdef) => { // Process the block content of the fenced reference definition let content = topdown_traverse_blocks(refdef.content, filter, ctx); - out.push(Block::NoteDefinitionFencedBlock( + out.push(Block::NoteDefinitionFencedBlock(Box::new( crate::pandoc::block::NoteDefinitionFencedBlock { id: refdef.id, content, source_info: refdef.source_info, }, - )) + ))) } Block::CaptionBlock(_) => { // CaptionBlock should have been removed by postprocessing @@ -852,12 +891,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 +912,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), } } @@ -1019,10 +1058,10 @@ fn traverse_block_nonterminal(block: Block, filter: &mut Filter, ctx: &mut Filte content: topdown_traverse_blocks(quote.content, filter, ctx), ..quote }), - Block::OrderedList(list) => Block::OrderedList(crate::pandoc::OrderedList { + Block::OrderedList(list) => Block::OrderedList(Box::new(crate::pandoc::OrderedList { content: traverse_blocks_vec_nonterminal(list.content, filter, ctx), - ..list - }), + ..*list + })), Block::BulletList(list) => Block::BulletList(crate::pandoc::BulletList { content: traverse_blocks_vec_nonterminal(list.content, filter, ctx), ..list @@ -1040,11 +1079,11 @@ fn traverse_block_nonterminal(block: Block, filter: &mut Filter, ctx: &mut Filte .collect(), ..list }), - Block::Header(header) => Block::Header(crate::pandoc::Header { + Block::Header(header) => Block::Header(Box::new(crate::pandoc::Header { content: topdown_traverse_inlines(header.content, filter, ctx), - ..header - }), - Block::Table(table) => Block::Table(crate::pandoc::Table { + ..*header + })), + Block::Table(table) => Block::Table(Box::new(crate::pandoc::Table { caption: traverse_caption(table.caption, filter, ctx), head: crate::pandoc::TableHead { rows: traverse_rows(table.head.rows, filter, ctx), @@ -1063,17 +1102,17 @@ fn traverse_block_nonterminal(block: Block, filter: &mut Filter, ctx: &mut Filte rows: traverse_rows(table.foot.rows, filter, ctx), ..table.foot }, - ..table - }), - Block::Figure(figure) => Block::Figure(crate::pandoc::Figure { + ..*table + })), + Block::Figure(figure) => Block::Figure(Box::new(crate::pandoc::Figure { caption: traverse_caption(figure.caption, filter, ctx), content: topdown_traverse_blocks(figure.content, filter, ctx), - ..figure - }), - Block::Div(div) => Block::Div(crate::pandoc::Div { + ..*figure + })), + Block::Div(div) => Block::Div(Box::new(crate::pandoc::Div { content: topdown_traverse_blocks(div.content, filter, ctx), - ..div - }), + ..*div + })), _ => { panic!("Unsupported block type: {:?}", block); } @@ -1398,12 +1437,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 +1455,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 +1473,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 +1490,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 +1538,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 +1558,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 +1575,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 +1602,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 +1650,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 +1662,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 +1676,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(_))); @@ -1679,12 +1718,12 @@ mod tests { fn test_traverse_code_block() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let block = Block::CodeBlock(CodeBlock { + let block = Block::CodeBlock(Box::new(CodeBlock { attr: empty_attr(), text: "code".to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Block::CodeBlock(_))); @@ -1694,11 +1733,11 @@ mod tests { fn test_traverse_raw_block() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let block = Block::RawBlock(RawBlock { + let block = Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: "
".to_string(), source_info: si(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Block::RawBlock(_))); @@ -1734,14 +1773,14 @@ mod tests { fn test_traverse_ordered_list() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let block = Block::OrderedList(OrderedList { + let block = Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Default, ListNumberDelim::Default), content: vec![vec![Block::Plain(Plain { content: vec![str_inline("item 1")], source_info: si(), })]], source_info: si(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); match &result[0] { @@ -1811,7 +1850,7 @@ mod tests { fn test_traverse_div() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let block = Block::Div(Div { + let block = Block::Div(Box::new(Div { attr: empty_attr(), content: vec![Block::Paragraph(Paragraph { content: vec![str_inline("div content")], @@ -1819,7 +1858,7 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Block::Div(_))); @@ -1829,13 +1868,13 @@ mod tests { fn test_traverse_header() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let block = Block::Header(Header { + let block = Block::Header(Box::new(Header { level: 1, attr: empty_attr(), content: vec![str_inline("heading")], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); match &result[0] { @@ -1848,7 +1887,7 @@ mod tests { fn test_traverse_figure() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let block = Block::Figure(Figure { + let block = Block::Figure(Box::new(Figure { attr: empty_attr(), caption: Caption { short: None, @@ -1864,7 +1903,7 @@ mod tests { })], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Block::Figure(_))); @@ -1874,7 +1913,7 @@ mod tests { fn test_traverse_table() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let block = Block::Table(Table { + let block = Block::Table(Box::new(Table { attr: empty_attr(), caption: Caption { short: None, @@ -1920,7 +1959,7 @@ mod tests { }, source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Block::Table(_))); @@ -1930,10 +1969,10 @@ mod tests { fn test_traverse_block_metadata() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let block = Block::BlockMetadata(MetaBlock { + let block = Block::BlockMetadata(Box::new(MetaBlock { meta: config_str("value"), source_info: si(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Block::BlockMetadata(_))); @@ -1943,10 +1982,10 @@ mod tests { fn test_traverse_block_metadata_with_meta_filter() { let mut filter = Filter::new().with_meta(|meta, _ctx| FilterReturn::Unchanged(meta)); let mut ctx = FilterContext::new(); - let block = Block::BlockMetadata(MetaBlock { + let block = Block::BlockMetadata(Box::new(MetaBlock { meta: config_str("value"), source_info: si(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Block::BlockMetadata(_))); @@ -1957,10 +1996,10 @@ mod tests { let mut filter = Filter::new() .with_meta(|_meta, _ctx| FilterReturn::FilterResult(config_str("transformed"), false)); let mut ctx = FilterContext::new(); - let block = Block::BlockMetadata(MetaBlock { + let block = Block::BlockMetadata(Box::new(MetaBlock { meta: config_str("value"), source_info: si(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); match &result[0] { @@ -1980,10 +2019,10 @@ mod tests { let mut filter = Filter::new().with_meta(|meta, _ctx| FilterReturn::FilterResult(meta, true)); let mut ctx = FilterContext::new(); - let block = Block::BlockMetadata(MetaBlock { + let block = Block::BlockMetadata(Box::new(MetaBlock { meta: config_map(vec![("key", config_str("value"))]), source_info: si(), - }); + })); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Block::BlockMetadata(_))); @@ -1993,7 +2032,7 @@ mod tests { fn test_traverse_custom_block() { let mut filter = Filter::new(); let mut ctx = FilterContext::new(); - let block = Block::Custom(CustomNode::new("test", empty_attr(), si())); + let block = Block::Custom(Box::new(CustomNode::new("test", empty_attr(), si()))); let result = topdown_traverse_block(block, &mut filter, &mut ctx); assert_eq!(result.len(), 1); assert!(matches!(result[0], Block::Custom(_))); diff --git a/crates/pampa/src/lua/constructors.rs b/crates/pampa/src/lua/constructors.rs index da8e3bb3cc..1d7f0ad5e1 100644 --- a/crates/pampa/src/lua/constructors.rs +++ b/crates/pampa/src/lua/constructors.rs @@ -273,7 +273,7 @@ fn is_attr_alias(key: &str) -> bool { /// both sides in a synthetic Table block and reuse the JSON writer's /// source-free comparison (same approach as elements and Citation). fn synthetic_table_eq(a: PandocTable, b: PandocTable) -> bool { - super::types::block_structurally_eq(&Block::Table(a), &Block::Table(b)) + super::types::block_structurally_eq(&Block::Table(Box::new(a)), &Block::Table(Box::new(b))) } fn empty_synthetic_table() -> PandocTable { @@ -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), - }))) + })))) })?, )?; @@ -1336,13 +1338,13 @@ fn register_block_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { lua.create_function(|lua, (level, content, attr): (i64, Value, Option)| { let inlines = peek_inlines_fuzzy(lua, content)?; let attr = parse_attr(lua, attr)?; - lua.create_userdata(LuaBlock::new(Block::Header(Header { + lua.create_userdata(LuaBlock::new(Block::Header(Box::new(Header { level: level as usize, content: inlines, attr, source_info: filter_source_info(lua), attr_source: AttrSourceInfo::empty(), - }))) + })))) })?, )?; @@ -1351,12 +1353,12 @@ fn register_block_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { "CodeBlock", lua.create_function(|lua, (text, attr): (String, Option)| { let attr = parse_attr(lua, attr)?; - lua.create_userdata(LuaBlock::new(Block::CodeBlock(CodeBlock { + lua.create_userdata(LuaBlock::new(Block::CodeBlock(Box::new(CodeBlock { text, attr, source_info: filter_source_info(lua), attr_source: AttrSourceInfo::empty(), - }))) + })))) })?, )?; @@ -1364,11 +1366,11 @@ fn register_block_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { pandoc.set( "RawBlock", lua.create_function(|lua, (format, text): (String, String)| { - lua.create_userdata(LuaBlock::new(Block::RawBlock(RawBlock { + lua.create_userdata(LuaBlock::new(Block::RawBlock(Box::new(RawBlock { format, text, source_info: filter_source_info(lua), - }))) + })))) })?, )?; @@ -1409,11 +1411,11 @@ fn register_block_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { crate::pandoc::ListNumberDelim::Default, ), }; - lua.create_userdata(LuaBlock::new(Block::OrderedList(OrderedList { + lua.create_userdata(LuaBlock::new(Block::OrderedList(Box::new(OrderedList { content, attr, source_info: filter_source_info(lua), - }))) + })))) })?, )?; @@ -1423,12 +1425,12 @@ fn register_block_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { lua.create_function(|lua, (content, attr): (Value, Option)| { let blocks = peek_blocks_fuzzy(lua, content)?; let attr = parse_attr(lua, attr)?; - lua.create_userdata(LuaBlock::new(Block::Div(Div { + lua.create_userdata(LuaBlock::new(Block::Div(Box::new(Div { content: blocks, attr, source_info: filter_source_info(lua), attr_source: AttrSourceInfo::empty(), - }))) + })))) })?, )?; @@ -1477,13 +1479,13 @@ fn register_block_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { let blocks = peek_blocks_fuzzy(lua, content)?; let caption = parse_caption(lua, caption)?; let attr = parse_attr(lua, attr)?; - lua.create_userdata(LuaBlock::new(Block::Figure(Figure { + lua.create_userdata(LuaBlock::new(Block::Figure(Box::new(Figure { content: blocks, caption, attr, source_info: filter_source_info(lua), attr_source: AttrSourceInfo::empty(), - }))) + })))) }, )?, )?; @@ -1507,7 +1509,7 @@ fn register_block_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { let bodies = parse_table_bodies(lua, bodies)?; let foot = parse_table_foot(lua, foot)?; let attr = parse_attr(lua, attr)?; - lua.create_userdata(LuaBlock::new(Block::Table(PandocTable { + lua.create_userdata(LuaBlock::new(Block::Table(Box::new(PandocTable { caption, colspec: colspecs, head, @@ -1516,7 +1518,7 @@ fn register_block_constructors(lua: &Lua, pandoc: &LuaTable) -> Result<()> { attr, source_info: filter_source_info(lua), attr_source: AttrSourceInfo::empty(), - }))) + })))) }, )?, )?; diff --git a/crates/pampa/src/lua/diagnostics.rs b/crates/pampa/src/lua/diagnostics.rs index 25593c2a6a..bb8abcf7c9 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()) @@ -1066,12 +1066,12 @@ mod tests { start_offset: 200, end_offset: 300, }; - let codeblock = Block::CodeBlock(CodeBlock { + let codeblock = Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![], LinkedHashMap::new()), text: "print('hello')".to_string(), source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); let lua_block = LuaBlock::new(codeblock); lua.globals() .set("test_codeblock", lua.create_userdata(lua_block).unwrap()) diff --git a/crates/pampa/src/lua/filter.rs b/crates/pampa/src/lua/filter.rs index 80bbd566be..3b5510b555 100644 --- a/crates/pampa/src/lua/filter.rs +++ b/crates/pampa/src/lua/filter.rs @@ -1344,20 +1344,20 @@ mod unit_tests { "LineBlock" ); assert_eq!( - block_tag(&Block::CodeBlock(CodeBlock { + block_tag(&Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![], LinkedHashMap::new()), text: String::new(), source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "CodeBlock" ); assert_eq!( - block_tag(&Block::RawBlock(RawBlock { + block_tag(&Block::RawBlock(Box::new(RawBlock { format: String::new(), text: String::new(), source_info: source_info.clone() - })), + }))), "RawBlock" ); assert_eq!( @@ -1368,7 +1368,7 @@ mod unit_tests { "BlockQuote" ); assert_eq!( - block_tag(&Block::OrderedList(OrderedList { + block_tag(&Block::OrderedList(Box::new(OrderedList { attr: ( 1, crate::pandoc::list::ListNumberStyle::Default, @@ -1376,7 +1376,7 @@ mod unit_tests { ), content: vec![], source_info: source_info.clone() - })), + }))), "OrderedList" ); assert_eq!( @@ -1394,13 +1394,13 @@ mod unit_tests { "DefinitionList" ); assert_eq!( - block_tag(&Block::Header(Header { + block_tag(&Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "Header" ); assert_eq!( @@ -1410,7 +1410,7 @@ mod unit_tests { "HorizontalRule" ); assert_eq!( - block_tag(&Block::Table(Table { + block_tag(&Block::Table(Box::new(Table { attr: (String::new(), vec![], LinkedHashMap::new()), caption: Caption { short: None, @@ -1433,11 +1433,11 @@ mod unit_tests { }, source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "Table" ); assert_eq!( - block_tag(&Block::Figure(Figure { + block_tag(&Block::Figure(Box::new(Figure { attr: (String::new(), vec![], LinkedHashMap::new()), caption: Caption { short: None, @@ -1447,41 +1447,41 @@ mod unit_tests { content: vec![], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "Figure" ); assert_eq!( - block_tag(&Block::Div(Div { + block_tag(&Block::Div(Box::new(Div { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty() - })), + }))), "Div" ); assert_eq!( - block_tag(&Block::BlockMetadata(MetaBlock { + block_tag(&Block::BlockMetadata(Box::new(MetaBlock { meta: crate::pandoc::config_value::ConfigValue::null(source_info.clone()), source_info: source_info.clone() - })), + }))), "BlockMetadata" ); assert_eq!( - block_tag(&Block::NoteDefinitionPara(NoteDefinitionPara { + block_tag(&Block::NoteDefinitionPara(Box::new(NoteDefinitionPara { id: String::new(), content: vec![], source_info: source_info.clone() - })), + }))), "NoteDefinitionPara" ); assert_eq!( - block_tag(&Block::NoteDefinitionFencedBlock( + block_tag(&Block::NoteDefinitionFencedBlock(Box::new( NoteDefinitionFencedBlock { id: String::new(), content: vec![], source_info: source_info.clone() } - )), + ))), "NoteDefinitionFencedBlock" ); assert_eq!( @@ -1492,13 +1492,13 @@ mod unit_tests { "CaptionBlock" ); assert_eq!( - block_tag(&Block::Custom(CustomNode { + block_tag(&Block::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" ); } @@ -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..c5adc4354b 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(), })], }; @@ -474,7 +474,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Header(crate::pandoc::Header { + blocks: vec![Block::Header(Box::new(crate::pandoc::Header { level: 1, content: vec![ Inline::Str(crate::pandoc::Str { @@ -492,7 +492,7 @@ end attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), source_info: quarto_source_map::SourceInfo::for_test(), attr_source: crate::pandoc::AttrSourceInfo::empty(), - })], + }))], }; let context = ASTContext::new(); @@ -1136,12 +1136,12 @@ end })], source_info: quarto_source_map::SourceInfo::for_test(), }), - Block::CodeBlock(crate::pandoc::CodeBlock { + Block::CodeBlock(Box::new(crate::pandoc::CodeBlock { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), text: "code".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), - }), + })), ], }; let context = ASTContext::new(); @@ -1353,7 +1353,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![ - Block::Div(crate::pandoc::Div { + Block::Div(Box::new(crate::pandoc::Div { content: vec![Block::Paragraph(crate::pandoc::Paragraph { content: vec![Inline::Str(crate::pandoc::Str { text: "inside".to_string(), @@ -1364,7 +1364,7 @@ end attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - }), + })), Block::Paragraph(crate::pandoc::Paragraph { content: vec![Inline::Str(crate::pandoc::Str { text: "outside".to_string(), @@ -1524,7 +1524,7 @@ end // Document: Div containing two paragraphs, each with one Str let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(crate::pandoc::Div { + blocks: vec![Block::Div(Box::new(crate::pandoc::Div { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![ @@ -1544,7 +1544,7 @@ end }), ], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); @@ -1612,7 +1612,7 @@ end // Document: Div containing Para with Str "hello" let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(crate::pandoc::Div { + blocks: vec![Block::Div(Box::new(crate::pandoc::Div { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![Block::Paragraph(crate::pandoc::Paragraph { @@ -1623,7 +1623,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); @@ -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(), })], }; @@ -3999,7 +3999,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Header(crate::pandoc::Header { + blocks: vec![Block::Header(Box::new(crate::pandoc::Header { level: 2, content: vec![Inline::Str(crate::pandoc::Str { text: "Title".to_string(), @@ -4012,7 +4012,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -4055,7 +4055,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::CodeBlock(crate::pandoc::CodeBlock { + blocks: vec![Block::CodeBlock(Box::new(crate::pandoc::CodeBlock { text: "print('hello')".to_string(), attr: ( "code-id".to_string(), @@ -4064,7 +4064,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -4106,11 +4106,11 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::RawBlock(crate::pandoc::RawBlock { + blocks: vec![Block::RawBlock(Box::new(crate::pandoc::RawBlock { text: "
html
".to_string(), format: "html".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -4206,7 +4206,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(crate::pandoc::Div { + blocks: vec![Block::Div(Box::new(crate::pandoc::Div { content: vec![Block::Paragraph(crate::pandoc::Paragraph { content: vec![Inline::Str(crate::pandoc::Str { text: "inside".to_string(), @@ -4221,7 +4221,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -4322,7 +4322,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::OrderedList(crate::pandoc::OrderedList { + blocks: vec![Block::OrderedList(Box::new(crate::pandoc::OrderedList { content: vec![vec![Block::Paragraph(crate::pandoc::Paragraph { content: vec![Inline::Str(crate::pandoc::Str { text: "first".to_string(), @@ -4336,7 +4336,7 @@ end crate::pandoc::ListNumberDelim::Period, ), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -4466,7 +4466,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Header(crate::pandoc::Header { + blocks: vec![Block::Header(Box::new(crate::pandoc::Header { level: 1, content: vec![Inline::Str(crate::pandoc::Str { text: "Old Title".to_string(), @@ -4475,7 +4475,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(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -4521,7 +4521,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::CodeBlock(crate::pandoc::CodeBlock { + blocks: vec![Block::CodeBlock(Box::new(crate::pandoc::CodeBlock { text: "old_code()".to_string(), attr: ( "old-id".to_string(), @@ -4530,7 +4530,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -4574,11 +4574,11 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::RawBlock(crate::pandoc::RawBlock { + blocks: vec![Block::RawBlock(Box::new(crate::pandoc::RawBlock { text: "
old
".to_string(), format: "html".to_string(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -4673,7 +4673,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(crate::pandoc::Div { + blocks: vec![Block::Div(Box::new(crate::pandoc::Div { content: vec![Block::Paragraph(crate::pandoc::Paragraph { content: vec![Inline::Str(crate::pandoc::Str { text: "old_content".to_string(), @@ -4688,7 +4688,7 @@ end ), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -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(), })], }; @@ -5123,7 +5123,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Figure(crate::pandoc::Figure { + blocks: vec![Block::Figure(Box::new(crate::pandoc::Figure { content: vec![Block::Paragraph(crate::pandoc::Paragraph { content: vec![Inline::Str(crate::pandoc::Str { text: "figure content".to_string(), @@ -5145,7 +5145,7 @@ end attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -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(), })], }; @@ -5248,7 +5248,7 @@ end use crate::pandoc::table::*; let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Table(Table { + blocks: vec![Block::Table(Box::new(Table { attr: ( "my-table".to_string(), vec![], @@ -5275,7 +5275,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), }, source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -5441,7 +5441,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::OrderedList(crate::pandoc::OrderedList { + blocks: vec![Block::OrderedList(Box::new(crate::pandoc::OrderedList { content: vec![vec![Block::Paragraph(crate::pandoc::Paragraph { content: vec![Inline::Str(crate::pandoc::Str { text: "item".to_string(), @@ -5455,7 +5455,7 @@ end crate::pandoc::ListNumberDelim::OneParen, ), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -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(), })], }; @@ -5571,7 +5571,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Header(crate::pandoc::Header { + blocks: vec![Block::Header(Box::new(crate::pandoc::Header { level: 1, content: vec![Inline::Str(crate::pandoc::Str { text: "Heading".to_string(), @@ -5580,7 +5580,7 @@ end attr: ("hdr-id".to_string(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -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(), })], }; @@ -5701,7 +5701,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(crate::pandoc::Div { + blocks: vec![Block::Div(Box::new(crate::pandoc::Div { content: vec![ Block::Paragraph(crate::pandoc::Paragraph { content: vec![Inline::Str(crate::pandoc::Str { @@ -5721,7 +5721,7 @@ end attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -5947,7 +5947,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Header(crate::pandoc::Header { + blocks: vec![Block::Header(Box::new(crate::pandoc::Header { level: 1, attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), @@ -5956,7 +5956,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -6124,7 +6124,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::OrderedList(crate::pandoc::OrderedList { + blocks: vec![Block::OrderedList(Box::new(crate::pandoc::OrderedList { attr: ( 1, quarto_pandoc_types::ListNumberStyle::Default, @@ -6138,7 +6138,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })]], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -6189,7 +6189,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Figure(crate::pandoc::Figure { + blocks: vec![Block::Figure(Box::new(crate::pandoc::Figure { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), caption: crate::pandoc::Caption { @@ -6211,7 +6211,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -6316,7 +6316,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(crate::pandoc::Div { + blocks: vec![Block::Div(Box::new(crate::pandoc::Div { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![Block::Paragraph(crate::pandoc::Paragraph { @@ -6327,7 +6327,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -6553,7 +6553,7 @@ end let pandoc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::Div(crate::pandoc::Div { + blocks: vec![Block::Div(Box::new(crate::pandoc::Div { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: crate::pandoc::AttrSourceInfo::empty(), content: vec![Block::Paragraph(crate::pandoc::Paragraph { @@ -6564,7 +6564,7 @@ end source_info: quarto_source_map::SourceInfo::for_test(), })], source_info: quarto_source_map::SourceInfo::for_test(), - })], + }))], }; let context = ASTContext::new(); let filtered = apply_lua_filter( @@ -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(), })], }; @@ -8031,7 +8031,7 @@ async fn test_returned_table_dispatches_name_outside_globals_whitelist() { // (bd-18a2r2lp). A returned table must not be filtered through that list. let doc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::NoteDefinitionPara( + blocks: vec![Block::NoteDefinitionPara(Box::new( quarto_pandoc_types::NoteDefinitionPara { id: "1".to_string(), content: vec![Inline::Str(crate::pandoc::Str { @@ -8040,7 +8040,7 @@ async fn test_returned_table_dispatches_name_outside_globals_whitelist() { })], source_info: quarto_source_map::SourceInfo::for_test(), }, - )], + ))], }; let out = run_filter_source( r#" @@ -8238,7 +8238,7 @@ async fn test_globals_dispatch_name_outside_old_whitelist() { // now generated from the same tag names the walker dispatches on. let doc = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), - blocks: vec![Block::NoteDefinitionPara( + blocks: vec![Block::NoteDefinitionPara(Box::new( quarto_pandoc_types::NoteDefinitionPara { id: "1".to_string(), content: vec![Inline::Str(crate::pandoc::Str { @@ -8247,7 +8247,7 @@ async fn test_globals_dispatch_name_outside_old_whitelist() { })], source_info: quarto_source_map::SourceInfo::for_test(), }, - )], + ))], }; let out = run_filter_source( r#" diff --git a/crates/pampa/src/lua/quarto_api.rs b/crates/pampa/src/lua/quarto_api.rs index 1cd57d4e79..4c7f0af802 100644 --- a/crates/pampa/src/lua/quarto_api.rs +++ b/crates/pampa/src/lua/quarto_api.rs @@ -183,33 +183,33 @@ 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 { + let mut blocks = vec![Block::RawBlock(Box::new(RawBlock { format: "html".to_string(), text: script, source_info: source_info.clone(), - })]; + }))]; if !matches!(content, Value::Nil) { blocks.extend(peek_blocks_fuzzy(lua, content)?); } - lua.create_userdata(LuaBlock::new(Block::Div(Div { + lua.create_userdata(LuaBlock::new(Block::Div(Box::new(Div { attr, content: blocks, source_info, attr_source: AttrSourceInfo::empty(), - }))) + })))) })?, )?; Ok(()) diff --git a/crates/pampa/src/lua/types.rs b/crates/pampa/src/lua/types.rs index c788bab7b1..a787c22b8d 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"]); } @@ -4262,22 +4262,22 @@ mod tests { #[test] fn test_lua_block_tag_name_code_block() { - let block = Block::CodeBlock(CodeBlock { + let block = Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), text: "code".into(), source_info: si(), - }); + })); assert_eq!(LuaBlock::new(block).tag_name(), "CodeBlock"); } #[test] fn test_lua_block_tag_name_raw_block() { - let block = Block::RawBlock(RawBlock { + let block = Block::RawBlock(Box::new(RawBlock { format: "html".into(), text: "
".into(), source_info: si(), - }); + })); assert_eq!(LuaBlock::new(block).tag_name(), "RawBlock"); } @@ -4292,11 +4292,11 @@ mod tests { #[test] fn test_lua_block_tag_name_ordered_list() { - let block = Block::OrderedList(OrderedList { + let block = Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaBlock::new(block).tag_name(), "OrderedList"); } @@ -4320,13 +4320,13 @@ mod tests { #[test] fn test_lua_block_tag_name_header() { - let block = Block::Header(Header { + let block = Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaBlock::new(block).tag_name(), "Header"); } @@ -4338,7 +4338,7 @@ mod tests { #[test] fn test_lua_block_tag_name_table() { - let block = Block::Table(PandocTable { + let block = Block::Table(Box::new(PandocTable { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), caption: empty_caption(), @@ -4347,59 +4347,59 @@ mod tests { bodies: vec![], foot: empty_table_foot(), source_info: si(), - }); + })); assert_eq!(LuaBlock::new(block).tag_name(), "Table"); } #[test] fn test_lua_block_tag_name_figure() { - let block = Block::Figure(Figure { + let block = Block::Figure(Box::new(Figure { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), caption: empty_caption(), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaBlock::new(block).tag_name(), "Figure"); } #[test] fn test_lua_block_tag_name_div() { - let block = Block::Div(Div { + let block = Block::Div(Box::new(Div { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaBlock::new(block).tag_name(), "Div"); } #[test] fn test_lua_block_tag_name_block_metadata() { - let block = Block::BlockMetadata(MetaBlock { + let block = Block::BlockMetadata(Box::new(MetaBlock { meta: quarto_pandoc_types::ConfigValue::default(), source_info: si(), - }); + })); assert_eq!(LuaBlock::new(block).tag_name(), "BlockMetadata"); } #[test] fn test_lua_block_tag_name_note_definition_para() { - let block = Block::NoteDefinitionPara(NoteDefinitionPara { + let block = Block::NoteDefinitionPara(Box::new(NoteDefinitionPara { id: "1".into(), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaBlock::new(block).tag_name(), "NoteDefinitionPara"); } #[test] fn test_lua_block_tag_name_note_definition_fenced_block() { - let block = Block::NoteDefinitionFencedBlock(NoteDefinitionFencedBlock { + let block = Block::NoteDefinitionFencedBlock(Box::new(NoteDefinitionFencedBlock { id: "1".into(), content: vec![], source_info: si(), - }); + })); assert_eq!(LuaBlock::new(block).tag_name(), "NoteDefinitionFencedBlock"); } @@ -4414,11 +4414,11 @@ mod tests { #[test] fn test_lua_block_tag_name_custom() { - let block = Block::Custom(crate::pandoc::custom::CustomNode::new( + let block = Block::Custom(Box::new(crate::pandoc::custom::CustomNode::new( "test-type", (String::new(), vec![], hashlink::LinkedHashMap::new()), si(), - )); + ))); assert_eq!(LuaBlock::new(block).tag_name(), "Custom"); } @@ -4450,12 +4450,12 @@ mod tests { #[test] fn test_lua_block_field_names_code_block() { - let block = Block::CodeBlock(CodeBlock { + let block = Block::CodeBlock(Box::new(CodeBlock { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), text: "code".into(), source_info: si(), - }); + })); assert_eq!( LuaBlock::new(block).field_names(), &[ @@ -4473,11 +4473,11 @@ mod tests { #[test] fn test_lua_block_field_names_raw_block() { - let block = Block::RawBlock(RawBlock { + let block = Block::RawBlock(Box::new(RawBlock { format: "html".into(), text: "
".into(), source_info: si(), - }); + })); assert_eq!( LuaBlock::new(block).field_names(), &["tag", "format", "text", "clone", "walk"] @@ -4486,13 +4486,13 @@ mod tests { #[test] fn test_lua_block_field_names_header() { - let block = Block::Header(Header { + let block = Block::Header(Box::new(Header { level: 1, attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!( LuaBlock::new(block).field_names(), &[ @@ -4511,11 +4511,11 @@ mod tests { #[test] fn test_lua_block_field_names_ordered_list() { - let block = Block::OrderedList(OrderedList { + let block = Block::OrderedList(Box::new(OrderedList { attr: (1, ListNumberStyle::Decimal, ListNumberDelim::Period), content: vec![], source_info: si(), - }); + })); assert_eq!( LuaBlock::new(block).field_names(), &[ @@ -4545,7 +4545,7 @@ mod tests { #[test] fn test_lua_block_field_names_table() { - let block = Block::Table(PandocTable { + let block = Block::Table(Box::new(PandocTable { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), caption: empty_caption(), @@ -4554,7 +4554,7 @@ mod tests { bodies: vec![], foot: empty_table_foot(), source_info: si(), - }); + })); assert_eq!( LuaBlock::new(block).field_names(), &[ @@ -4576,13 +4576,13 @@ mod tests { #[test] fn test_lua_block_field_names_figure() { - let block = Block::Figure(Figure { + let block = Block::Figure(Box::new(Figure { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), caption: empty_caption(), content: vec![], source_info: si(), - }); + })); assert_eq!( LuaBlock::new(block).field_names(), &[ @@ -4601,12 +4601,12 @@ mod tests { #[test] fn test_lua_block_field_names_div() { - let block = Block::Div(Div { + let block = Block::Div(Box::new(Div { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), attr_source: attr_si(), content: vec![], source_info: si(), - }); + })); assert_eq!( LuaBlock::new(block).field_names(), &[ @@ -4633,11 +4633,11 @@ mod tests { #[test] fn test_lua_block_field_names_custom() { - let block = Block::Custom(crate::pandoc::custom::CustomNode::new( + let block = Block::Custom(Box::new(crate::pandoc::custom::CustomNode::new( "test-type", (String::new(), vec![], hashlink::LinkedHashMap::new()), si(), - )); + ))); assert_eq!(LuaBlock::new(block).field_names(), &["tag", "clone"]); } diff --git a/crates/pampa/src/lua/walk.rs b/crates/pampa/src/lua/walk.rs index 08721e5e56..90c4db606c 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(_) @@ -240,10 +240,10 @@ pub(crate) async fn walk_block_children(w: &W, block: Block) -> Re content: w.walk_blocks(b.content).await?, ..b }), - Block::OrderedList(l) => Block::OrderedList(pandoc::OrderedList { + Block::OrderedList(l) => Block::OrderedList(Box::new(pandoc::OrderedList { content: walk_blocks_vec(w, l.content).await?, - ..l - }), + ..*l + })), Block::BulletList(l) => Block::BulletList(pandoc::BulletList { content: walk_blocks_vec(w, l.content).await?, ..l @@ -258,10 +258,10 @@ pub(crate) async fn walk_block_children(w: &W, block: Block) -> Re } Block::DefinitionList(pandoc::DefinitionList { content, ..l }) } - Block::Header(h) => Block::Header(pandoc::Header { + Block::Header(h) => Block::Header(Box::new(pandoc::Header { content: w.walk_inlines(h.content).await?, - ..h - }), + ..*h + })), Block::Table(t) => { let caption = walk_caption(w, t.caption).await?; let head = pandoc::TableHead { @@ -280,23 +280,23 @@ pub(crate) async fn walk_block_children(w: &W, block: Block) -> Re rows: walk_rows(w, t.foot.rows).await?, ..t.foot }; - Block::Table(pandoc::Table { + Block::Table(Box::new(pandoc::Table { caption, head, bodies, foot, - ..t - }) + ..*t + })) } - Block::Figure(f) => Block::Figure(pandoc::Figure { + Block::Figure(f) => Block::Figure(Box::new(pandoc::Figure { caption: walk_caption(w, f.caption).await?, content: w.walk_blocks(f.content).await?, - ..f - }), - Block::Div(d) => Block::Div(pandoc::Div { + ..*f + })), + Block::Div(d) => Block::Div(Box::new(pandoc::Div { content: w.walk_blocks(d.content).await?, - ..d - }), + ..*d + })), // Terminal blocks (no walkable children) Block::CodeBlock(_) | Block::RawBlock(_) diff --git a/crates/pampa/src/pandoc/desugar.rs b/crates/pampa/src/pandoc/desugar.rs index 575a94a6fa..ebcd047c01 100644 --- a/crates/pampa/src/pandoc/desugar.rs +++ b/crates/pampa/src/pandoc/desugar.rs @@ -93,13 +93,13 @@ pub(crate) fn desugar_div(div: Div, collector: &mut DiagnosticCollector) -> Bloc .add_hint("Check the list-table documentation for the correct structure?") .build(), ); - Block::Div(div) + Block::Div(Box::new(div)) } ListTableValidation::NotListTable => { if is_valid_definition_list_div(&div) { transform_definition_list_div(div) } else { - Block::Div(div) + Block::Div(Box::new(div)) } } } @@ -162,11 +162,11 @@ fn desugar_filter<'a>(collector: &'a RefCell<&'a mut DiagnosticCollector>) -> Fi Filter::new() // See `desugar_div` for why this is `Unchanged`, not a re-visit. .with_block(move |block, _ctx| match block { - Block::Div(div) => Unchanged(desugar_div(div, &mut collector.borrow_mut())), + Block::Div(div) => Unchanged(desugar_div(*div, &mut collector.borrow_mut())), other => Unchanged(other), }) .with_paragraph(|para, _ctx| match implicit_figure(¶) { - Some(figure) => FilterResult(vec![Block::Figure(figure)], true), + Some(figure) => FilterResult(vec![Block::Figure(Box::new(figure))], true), None => Unchanged(para), }) } @@ -219,12 +219,12 @@ mod tests { } fn 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: empty_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn list_table() -> Block { @@ -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..fb06b71dcb 100644 --- a/crates/pampa/src/pandoc/treesitter.rs +++ b/crates/pampa/src/pandoc/treesitter.rs @@ -203,11 +203,11 @@ fn process_list( // Increment counter by the number of items in this list context.example_list_counter.set(start_num + content.len()); } - PandocNativeIntermediate::IntermediateBlock(Block::OrderedList(OrderedList { + PandocNativeIntermediate::IntermediateBlock(Block::OrderedList(Box::new(OrderedList { attr, content, source_info: node_source_info_with_context(node, context), - })) + }))) } None => PandocNativeIntermediate::IntermediateBlock(Block::BulletList(BulletList { content, @@ -275,13 +275,13 @@ fn process_list_item( PandocNativeIntermediate::IntermediateSection(section) => blocks.extend(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, source_info: crate::pandoc::location::range_to_source_info_with_context( &range, context, ), - })); + }))); } // Tokens with no AST-level meaning: tree-sitter ERROR nodes from // error recovery, fence delimiters, etc. all dispatch to @@ -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 @@ -1672,11 +1672,11 @@ fn native_visitor( let msg = builder.build(); error_collector.add(msg); - PandocNativeIntermediate::IntermediateBlock(Block::RawBlock(RawBlock { + PandocNativeIntermediate::IntermediateBlock(Block::RawBlock(Box::new(RawBlock { format: "qmd-grid-table".to_string(), text: raw_text.to_string(), source_info: node_source_info_with_context(node, context), - })) + }))) } "comment" => { // HTML comments () are preserved as RawInline(html) @@ -1688,11 +1688,11 @@ fn native_visitor( // maintain correct spacing (e.g., "Hello world" // — the space before ".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 00c082cf97..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); @@ -666,14 +666,14 @@ fn test_inline_quoted_double() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Quoted(Quoted { + content: vec![Inline::Quoted(Box::new(Quoted { quote_type: pampa::pandoc::QuoteType::DoubleQuote, content: vec![Inline::Str(Str { text: "quoted".to_string(), source_info: empty_source(), })], source_info: empty_source(), - })], + }))], source_info: empty_source(), })], }; @@ -687,14 +687,14 @@ fn test_inline_quoted_single() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Quoted(Quoted { + content: vec![Inline::Quoted(Box::new(Quoted { quote_type: pampa::pandoc::QuoteType::SingleQuote, content: vec![Inline::Str(Str { text: "quoted".to_string(), source_info: empty_source(), })], source_info: empty_source(), - })], + }))], source_info: empty_source(), })], }; @@ -708,12 +708,12 @@ fn test_inline_code() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Code(Code { + content: vec![Inline::Code(Box::new(Code { attr: empty_attr(), text: "let x = 1".to_string(), source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -727,12 +727,12 @@ fn test_inline_math() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Math(Math { + content: vec![Inline::Math(Box::new(Math { math_type: pampa::pandoc::MathType::InlineMath, text: "E=mc^2".to_string(), source_info: empty_source(), text_source: None, - })], + }))], source_info: empty_source(), })], }; @@ -746,11 +746,11 @@ fn test_inline_rawinline_ansi() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::RawInline(RawInline { + content: vec![Inline::RawInline(Box::new(RawInline { format: "ansi".to_string(), text: "\x1b[31mRed\x1b[0m".to_string(), source_info: empty_source(), - })], + }))], source_info: empty_source(), })], }; @@ -764,11 +764,11 @@ fn test_inline_rawinline_other_format() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::RawInline(RawInline { + content: vec![Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: "bold".to_string(), source_info: empty_source(), - })], + }))], source_info: empty_source(), })], }; @@ -783,7 +783,7 @@ fn test_inline_link() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Link(Link { + content: vec![Inline::Link(Box::new(Link { attr: empty_attr(), content: vec![Inline::Str(Str { text: "Click me".to_string(), @@ -793,7 +793,7 @@ fn test_inline_link() { target_source: TargetSourceInfo::empty(), source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -807,7 +807,7 @@ fn test_inline_image() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Image(Image { + content: vec![Inline::Image(Box::new(Image { attr: empty_attr(), content: vec![Inline::Str(Str { text: "Alt text".to_string(), @@ -817,7 +817,7 @@ fn test_inline_image() { target_source: TargetSourceInfo::empty(), source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -855,7 +855,7 @@ fn test_inline_span() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr: empty_attr(), content: vec![Inline::Str(Str { text: "Span text".to_string(), @@ -863,7 +863,7 @@ fn test_inline_span() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -881,7 +881,7 @@ fn test_inline_span_with_color() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr, content: vec![Inline::Str(Str { text: "Blue text".to_string(), @@ -889,7 +889,7 @@ fn test_inline_span_with_color() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -909,7 +909,7 @@ fn test_inline_span_with_background_color() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr, content: vec![Inline::Str(Str { text: "Highlighted".to_string(), @@ -917,7 +917,7 @@ fn test_inline_span_with_background_color() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -944,7 +944,7 @@ fn test_color_parsing_basic_colors() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr, content: vec![Inline::Str(Str { text: format!("Color {}", color), @@ -952,7 +952,7 @@ fn test_color_parsing_basic_colors() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -989,7 +989,7 @@ fn test_color_parsing_dark_colors() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr, content: vec![Inline::Str(Str { text: "Text".to_string(), @@ -997,7 +997,7 @@ fn test_color_parsing_dark_colors() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -1017,7 +1017,7 @@ fn test_color_parsing_hex() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr, content: vec![Inline::Str(Str { text: "Hex color".to_string(), @@ -1025,7 +1025,7 @@ fn test_color_parsing_hex() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -1044,7 +1044,7 @@ fn test_color_parsing_hex_short() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr, content: vec![Inline::Str(Str { text: "Short hex".to_string(), @@ -1052,7 +1052,7 @@ fn test_color_parsing_hex_short() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -1070,7 +1070,7 @@ fn test_color_parsing_rgb_function() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr, content: vec![Inline::Str(Str { text: "RGB func".to_string(), @@ -1078,7 +1078,7 @@ fn test_color_parsing_rgb_function() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -1096,7 +1096,7 @@ fn test_color_parsing_ansi_palette() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr, content: vec![Inline::Str(Str { text: "ANSI palette".to_string(), @@ -1104,7 +1104,7 @@ fn test_color_parsing_ansi_palette() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -1122,7 +1122,7 @@ fn test_color_parsing_ansi_dash() { let pandoc = Pandoc { meta: Default::default(), blocks: vec![Block::Plain(Plain { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr, content: vec![Inline::Str(Str { text: "ANSI dash".to_string(), @@ -1130,7 +1130,7 @@ fn test_color_parsing_ansi_dash() { })], source_info: empty_source(), attr_source: empty_attr_source(), - })], + }))], source_info: empty_source(), })], }; @@ -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 79b686ccd4..2c14f623aa 100644 --- a/crates/pampa/tests/integration/test_attr_source_structure.rs +++ b/crates/pampa/tests/integration/test_attr_source_structure.rs @@ -503,12 +503,12 @@ fn test_inline_enum_variants_with_source_fields() { // Verify that we can pattern match on Inline variants and access // their source fields where applicable - let span_inline = Inline::Span(Span { + let span_inline = Inline::Span(Box::new(Span { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }); + })); match span_inline { Inline::Span(s) => { @@ -517,12 +517,12 @@ fn test_inline_enum_variants_with_source_fields() { _ => panic!("Expected Span"), } - let code_inline = Inline::Code(Code { + let code_inline = Inline::Code(Box::new(Code { attr: (String::new(), vec![], LinkedHashMap::new()), text: "code".to_string(), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }); + })); match code_inline { Inline::Code(c) => { @@ -531,14 +531,14 @@ fn test_inline_enum_variants_with_source_fields() { _ => panic!("Expected Code"), } - let link_inline = Inline::Link(Link { + let link_inline = Inline::Link(Box::new(Link { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], target: (String::new(), String::new()), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); match link_inline { Inline::Link(l) => { @@ -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 9b36615320..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,12 +46,12 @@ 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 { Block::Paragraph(Paragraph { - content: vec![Inline::Code(Code { + content: vec![Inline::Code(Box::new(Code { attr: ( String::new(), vec![class.to_string()], @@ -60,7 +60,7 @@ fn para_with_inline_code(class: &str, text: &str) -> Block { text: text.to_string(), source_info: si(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: si(), }) } 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 eb5e582eb1..e6b70449b1 100644 --- a/crates/pampa/tests/integration/test_raw_json_roundtrip.rs +++ b/crates/pampa/tests/integration/test_raw_json_roundtrip.rs @@ -122,7 +122,11 @@ fn test_raw_json_roundtrip_standalone_attr() { blocks: vec![para( vec![ str_inline("Hello.", 0, 6), - Inline::Attr(InlineAttr::new(attr, AttrSourceInfo::empty(), si(8, 32))), + Inline::Attr(Box::new(InlineAttr::new( + attr, + AttrSourceInfo::empty(), + si(8, 32), + ))), str_inline("Here?", 34, 39), ], 0, @@ -174,30 +178,30 @@ fn test_raw_json_roundtrip_critic_markup() { meta: ConfigValue::default(), blocks: vec![para( vec![ - Inline::Insert(Insert { + Inline::Insert(Box::new(Insert { attr: attr.clone(), content: vec![str_inline("added", 3, 8)], source_info: si(0, 11), attr_source: AttrSourceInfo::empty(), - }), - Inline::Delete(Delete { + })), + Inline::Delete(Box::new(Delete { attr: empty_attr(), content: vec![str_inline("removed", 14, 21)], source_info: si(11, 24), attr_source: AttrSourceInfo::empty(), - }), - Inline::Highlight(Highlight { + })), + Inline::Highlight(Box::new(Highlight { attr: empty_attr(), content: vec![str_inline("marked", 27, 33)], source_info: si(24, 36), attr_source: AttrSourceInfo::empty(), - }), - Inline::EditComment(EditComment { + })), + Inline::EditComment(Box::new(EditComment { attr: empty_attr(), content: vec![str_inline("why?", 39, 43)], source_info: si(36, 46), attr_source: AttrSourceInfo::empty(), - }), + })), ], 0, 46, @@ -230,7 +234,7 @@ fn test_raw_json_roundtrip_shortcode() { let doc = Pandoc { meta: ConfigValue::default(), blocks: vec![para( - vec![Inline::Shortcode(Shortcode { + vec![Inline::Shortcode(Box::new(Shortcode { is_escaped: true, name: "video".to_string(), positional_args: vec![ @@ -241,7 +245,7 @@ fn test_raw_json_roundtrip_shortcode() { ], keyword_args, source_info: si(0, 40), - })], + }))], 0, 40, )], @@ -273,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(), @@ -300,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()); @@ -322,8 +326,8 @@ fn test_raw_json_roundtrip_custom_nodes() { let doc = Pandoc { meta: ConfigValue::default(), blocks: vec![ - Block::Custom(block_custom), - para(vec![Inline::Custom(inline_custom)], 31, 40), + Block::Custom(Box::new(block_custom)), + para(vec![Inline::Custom(Box::new(inline_custom))], 31, 40), ], }; assert_roundtrip_identity(&doc, &ASTContext::new()); @@ -778,7 +782,7 @@ fn test_raw_json_roundtrip_sidecar_source_infos() { meta: ConfigValue::default(), blocks: vec![para( vec![ - Inline::Link(pampa::pandoc::Link { + Inline::Link(Box::new(pampa::pandoc::Link { attr: empty_attr(), content: vec![str_inline("a link", 1, 7)], target: ("./hello".to_string(), "out".to_string()), @@ -788,8 +792,8 @@ fn test_raw_json_roundtrip_sidecar_source_infos() { url: Some(si(9, 16)), title: Some(si(17, 22)), }, - }), - Inline::Cite(pampa::pandoc::Cite { + })), + Inline::Cite(Box::new(pampa::pandoc::Cite { citations: vec![pampa::pandoc::Citation { id: "knuth1984".to_string(), prefix: vec![str_inline("see", 48, 51)], @@ -801,7 +805,7 @@ fn test_raw_json_roundtrip_sidecar_source_infos() { }], content: vec![str_inline("[@knuth1984]", 47, 68)], source_info: si(47, 68), - }), + })), ], 0, 68, @@ -828,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()], @@ -843,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/pampa/tests/integration/test_shortcode.rs b/crates/pampa/tests/integration/test_shortcode.rs index 11f64759e0..8c0584a3ee 100644 --- a/crates/pampa/tests/integration/test_shortcode.rs +++ b/crates/pampa/tests/integration/test_shortcode.rs @@ -47,7 +47,7 @@ fn get_all_shortcodes(pandoc: &pampa::pandoc::Pandoc) -> Vec<&Shortcode> { .iter() .filter_map(|i| { if let Inline::Shortcode(shortcode) = i { - Some(shortcode) + Some(&**shortcode) } else { None } 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(()) +} diff --git a/crates/quarto-analysis/src/transforms/shortcode.rs b/crates/quarto-analysis/src/transforms/shortcode.rs index ef57bb05e3..047484abed 100644 --- a/crates/quarto-analysis/src/transforms/shortcode.rs +++ b/crates/quarto-analysis/src/transforms/shortcode.rs @@ -384,13 +384,13 @@ mod tests { use yaml_rust2::Yaml; fn make_meta_shortcode(key: &str) -> Inline { - Inline::Shortcode(Shortcode { + Inline::Shortcode(Box::new(Shortcode { is_escaped: false, name: "meta".to_string(), positional_args: vec![ShortcodeArg::String(key.to_string())], keyword_args: Default::default(), source_info: SourceInfo::for_test(), - }) + })) } fn make_metadata(entries: Vec<(&str, &str)>) -> ConfigValue { @@ -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 3caf5789e2..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, } @@ -421,9 +421,9 @@ fn apply_custom_node_inline_reconciliation( slot_plan: &CustomNodeSlotPlan, ) -> Inline { match (orig_inline, exec_inline) { - (Inline::Custom(orig), Inline::Custom(exec)) => { - Inline::Custom(apply_custom_node_reconciliation(orig, exec, slot_plan)) - } + (Inline::Custom(orig), Inline::Custom(exec)) => Inline::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 02e414a312..acf399afdd 100644 --- a/crates/quarto-ast-reconcile/src/generators.rs +++ b/crates/quarto-ast-reconcile/src/generators.rs @@ -543,35 +543,35 @@ fn gen_line_break() -> impl Strategy { /// Generate a Code inline. fn gen_code_inline() -> impl Strategy { (gen_attr(), gen_code_text()).prop_map(|(attr, text)| { - Inline::Code(Code { + Inline::Code(Box::new(Code { attr, text, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } /// Generate a Math inline. fn gen_math() -> impl Strategy { (gen_math_type(), gen_code_text()).prop_map(|(math_type, text)| { - Inline::Math(Math { + Inline::Math(Box::new(Math { math_type, text, source_info: dummy_source(), text_source: None, - }) + })) }) } /// Generate a RawInline. fn gen_raw_inline() -> impl Strategy { (gen_format(), gen_code_text()).prop_map(|(format, text)| { - Inline::RawInline(RawInline { + Inline::RawInline(Box::new(RawInline { format, text, source_info: dummy_source(), - }) + })) }) } @@ -640,7 +640,7 @@ fn gen_shortcode_inner(depth: usize) -> impl Strategy { /// recursive args (ShortcodeArg can contain nested Shortcodes). fn gen_shortcode() -> impl Strategy { // Use depth 2 to allow some nesting in args - gen_shortcode_inner(2).prop_map(Inline::Shortcode) + gen_shortcode_inner(2).prop_map(|s| Inline::Shortcode(Box::new(s))) } // ============================================================================= @@ -720,51 +720,51 @@ fn gen_smallcaps(config: GenConfig) -> impl Strategy { /// Generate a Quoted inline containing other inlines. fn gen_quoted(config: GenConfig) -> impl Strategy { (gen_quote_type(), gen_inlines_inner(config)).prop_map(|(quote_type, content)| { - Inline::Quoted(Quoted { + Inline::Quoted(Box::new(Quoted { quote_type, content, source_info: dummy_source(), - }) + })) }) } /// Generate a Span inline containing other inlines. fn gen_span(config: GenConfig) -> impl Strategy { (gen_attr(), gen_inlines_inner(config)).prop_map(|(attr, content)| { - Inline::Span(Span { + Inline::Span(Box::new(Span { attr, content, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } /// Generate a Link inline containing other inlines. fn gen_link(config: GenConfig) -> impl Strategy { (gen_attr(), gen_inlines_inner(config), gen_target()).prop_map(|(attr, content, target)| { - Inline::Link(Link { + Inline::Link(Box::new(Link { attr, content, target, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) }) } /// Generate an Image inline containing other inlines (for alt text). fn gen_image(config: GenConfig) -> impl Strategy { (gen_attr(), gen_inlines_inner(config), gen_target()).prop_map(|(attr, content, target)| { - Inline::Image(Image { + Inline::Image(Box::new(Image { attr, content, target, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) }) } @@ -776,11 +776,11 @@ fn gen_cite(config: GenConfig) -> impl Strategy { gen_inlines_inner(config), ) .prop_map(|(citations, content)| { - Inline::Cite(Cite { + Inline::Cite(Box::new(Cite { citations, content, source_info: dummy_source(), - }) + })) }) } @@ -802,48 +802,48 @@ fn gen_note(config: GenConfig) -> impl Strategy { /// Generate an Insert inline (CriticMarkup addition). fn gen_insert(config: GenConfig) -> impl Strategy { (gen_attr(), gen_inlines_inner(config)).prop_map(|(attr, content)| { - Inline::Insert(Insert { + Inline::Insert(Box::new(Insert { attr, content, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } /// Generate a Delete inline (CriticMarkup deletion). fn gen_delete(config: GenConfig) -> impl Strategy { (gen_attr(), gen_inlines_inner(config)).prop_map(|(attr, content)| { - Inline::Delete(Delete { + Inline::Delete(Box::new(Delete { attr, content, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } /// Generate a Highlight inline (CriticMarkup highlight). fn gen_highlight(config: GenConfig) -> impl Strategy { (gen_attr(), gen_inlines_inner(config)).prop_map(|(attr, content)| { - Inline::Highlight(Highlight { + Inline::Highlight(Box::new(Highlight { attr, content, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } /// Generate an EditComment inline (CriticMarkup comment). fn gen_edit_comment(config: GenConfig) -> impl Strategy { (gen_attr(), gen_inlines_inner(config)).prop_map(|(attr, content)| { - Inline::EditComment(EditComment { + Inline::EditComment(Box::new(EditComment { attr, content, source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }) + })) }) } @@ -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,12 +1270,12 @@ 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. fn gen_custom_inline(config: GenConfig) -> impl Strategy { - gen_custom_node(config).prop_map(Inline::Custom) + gen_custom_node(config).prop_map(|c| Inline::Custom(Box::new(c))) } // ============================================================================= @@ -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 9629afee5b..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!( @@ -1521,17 +1521,17 @@ mod tests { #[test] fn test_hash_inline_quoted() { - let q1 = Inline::Quoted(Quoted { + let q1 = Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::DoubleQuote, content: vec![make_str("quoted")], source_info: dummy_source(), - }); + })); - let q2 = Inline::Quoted(Quoted { + let q2 = Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::SingleQuote, content: vec![make_str("quoted")], source_info: dummy_source(), - }); + })); // Different quote type -> different hash assert_ne!( @@ -1542,19 +1542,19 @@ mod tests { #[test] fn test_hash_inline_code() { - let code1 = Inline::Code(Code { + let code1 = Inline::Code(Box::new(Code { attr: empty_attr(), text: "x = 1".to_string(), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let code2 = Inline::Code(Code { + let code2 = Inline::Code(Box::new(Code { attr: empty_attr(), text: "x = 2".to_string(), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); assert_ne!( compute_inline_hash_fresh(&code1), @@ -1586,19 +1586,19 @@ mod tests { #[test] fn test_hash_inline_math() { - let math1 = Inline::Math(Math { + let math1 = Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text: "x^2".to_string(), source_info: dummy_source(), text_source: None, - }); + })); - let math2 = Inline::Math(Math { + let math2 = Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: "x^2".to_string(), source_info: dummy_source(), text_source: None, - }); + })); // Different math type -> different hash assert_ne!( @@ -1609,11 +1609,11 @@ mod tests { #[test] fn test_hash_inline_raw() { - let raw = Inline::RawInline(RawInline { + let raw = Inline::RawInline(Box::new(RawInline { format: "tex".to_string(), text: "\\alpha".to_string(), source_info: dummy_source(), - }); + })); let hash = compute_inline_hash_fresh(&raw); let _ = hash; @@ -1621,23 +1621,23 @@ mod tests { #[test] fn test_hash_inline_link() { - let link1 = Inline::Link(Link { + let link1 = Inline::Link(Box::new(Link { attr: empty_attr(), content: vec![make_str("click")], target: ("https://example.com".to_string(), "title".to_string()), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); - let link2 = Inline::Link(Link { + let link2 = Inline::Link(Box::new(Link { attr: empty_attr(), content: vec![make_str("click")], target: ("https://other.com".to_string(), "title".to_string()), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); // Different URL -> different hash assert_ne!( @@ -1648,14 +1648,14 @@ mod tests { #[test] fn test_hash_inline_image() { - let img = Inline::Image(Image { + let img = Inline::Image(Box::new(Image { attr: empty_attr(), content: vec![make_str("alt text")], target: ("image.png".to_string(), String::new()), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); let hash = compute_inline_hash_fresh(&img); let _ = hash; @@ -1677,7 +1677,7 @@ mod tests { #[test] fn test_hash_inline_span() { - let span = Inline::Span(Span { + let span = Inline::Span(Box::new(Span { attr: ( "id".to_string(), vec!["class".to_string()], @@ -1686,7 +1686,7 @@ mod tests { content: vec![make_str("spanned")], source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); let hash = compute_inline_hash_fresh(&span); let _ = hash; @@ -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)); } @@ -2044,19 +2044,19 @@ mod tests { #[test] fn test_structural_eq_inline_code() { - let c1 = Inline::Code(Code { + let c1 = Inline::Code(Box::new(Code { attr: empty_attr(), text: "code".to_string(), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), - }); + })); - let c2 = Inline::Code(Code { + let c2 = Inline::Code(Box::new(Code { attr: empty_attr(), text: "code".to_string(), source_info: other_source(), attr_source: AttrSourceInfo::empty(), - }); + })); assert!(structural_eq_inline(&c1, &c2)); } @@ -2076,26 +2076,26 @@ mod tests { #[test] fn test_structural_eq_inline_math() { - let m1 = Inline::Math(Math { + let m1 = Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text: "x".to_string(), source_info: dummy_source(), text_source: None, - }); + })); - let m2 = Inline::Math(Math { + let m2 = Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text: "x".to_string(), source_info: other_source(), text_source: None, - }); + })); - let m3 = Inline::Math(Math { + let m3 = Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: "x".to_string(), source_info: dummy_source(), text_source: None, - }); + })); assert!(structural_eq_inline(&m1, &m2)); assert!(!structural_eq_inline(&m1, &m3)); // Different math type @@ -2103,23 +2103,23 @@ mod tests { #[test] fn test_structural_eq_inline_link() { - let l1 = Inline::Link(Link { + let l1 = Inline::Link(Box::new(Link { attr: empty_attr(), content: vec![make_str("text")], target: ("url".to_string(), "title".to_string()), source_info: dummy_source(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); - let l2 = Inline::Link(Link { + let l2 = Inline::Link(Box::new(Link { attr: empty_attr(), content: vec![make_str("text")], target: ("url".to_string(), "title".to_string()), source_info: other_source(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); assert!(structural_eq_inline(&l1, &l2)); } @@ -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 00beed6d84..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) @@ -158,7 +158,7 @@ mod tests { text: "anchor".to_string(), source_info: source.clone(), }), - quarto_pandoc_types::Inline::Cite(Cite { + quarto_pandoc_types::Inline::Cite(Box::new(Cite { citations: vec![Citation { id: id.to_string(), prefix: vec![quarto_pandoc_types::Inline::Str(Str { @@ -176,7 +176,7 @@ mod tests { source_info: source.clone(), })], source_info: source.clone(), - }), + })), ], source_info: source, }) @@ -213,7 +213,7 @@ mod tests { text: "anchor".to_string(), source_info: source.clone(), }), - quarto_pandoc_types::Inline::Cite(Cite { + quarto_pandoc_types::Inline::Cite(Box::new(Cite { citations: vec![Citation { id: "key".to_string(), prefix: vec![], @@ -228,7 +228,7 @@ mod tests { source_info: source.clone(), })], source_info: source.clone(), - }), + })), ], source_info: source, }) @@ -332,7 +332,7 @@ mod tests { } assert_identity_changed_container_not_paired( |changed, source| { - quarto_pandoc_types::Inline::Quoted(Quoted { + quarto_pandoc_types::Inline::Quoted(Box::new(Quoted { quote_type: if changed { QuoteType::SingleQuote } else { @@ -340,51 +340,51 @@ mod tests { }, content: content(&source), source_info: source, - }) + })) }, "Quoted(quote_type)", ); assert_identity_changed_container_not_paired( |changed, source| { - quarto_pandoc_types::Inline::Insert(Insert { + quarto_pandoc_types::Inline::Insert(Box::new(Insert { attr: attr_for(changed), content: content(&source), source_info: source, attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) }, "Insert(attr)", ); assert_identity_changed_container_not_paired( |changed, source| { - quarto_pandoc_types::Inline::Delete(Delete { + quarto_pandoc_types::Inline::Delete(Box::new(Delete { attr: attr_for(changed), content: content(&source), source_info: source, attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) }, "Delete(attr)", ); assert_identity_changed_container_not_paired( |changed, source| { - quarto_pandoc_types::Inline::Highlight(Highlight { + quarto_pandoc_types::Inline::Highlight(Box::new(Highlight { attr: attr_for(changed), content: content(&source), source_info: source, attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) }, "Highlight(attr)", ); assert_identity_changed_container_not_paired( |changed, source| { - quarto_pandoc_types::Inline::EditComment(EditComment { + quarto_pandoc_types::Inline::EditComment(Box::new(EditComment { attr: attr_for(changed), content: content(&source), source_info: source, attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }) + })) }, "EditComment(attr)", ); @@ -572,12 +572,12 @@ mod tests { text: "Value: ".to_string(), source_info: source_original(), }), - quarto_pandoc_types::Inline::Code(quarto_pandoc_types::Code { + quarto_pandoc_types::Inline::Code(Box::new(quarto_pandoc_types::Code { attr: (String::new(), vec![], LinkedHashMap::new()), text: "r 23 * 37".to_string(), source_info: source_original(), attr_source: quarto_pandoc_types::AttrSourceInfo::empty(), - }), + })), ], source_info: source_original(), }); @@ -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(), + ), ))], }; @@ -1097,13 +1093,13 @@ mod tests { })]), ); - quarto_pandoc_types::Inline::Custom(CustomNode { + quarto_pandoc_types::Inline::Custom(Box::new(CustomNode { type_name: "Shortcode".to_string(), slots, plain_data: serde_json::json!({"name": "video"}), attr: (String::new(), vec![], LinkedHashMap::new()), source_info: source, - }) + })) } let original = Pandoc { @@ -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( @@ -1935,7 +1931,7 @@ mod list_length_tests { source: SourceInfo, ) -> quarto_pandoc_types::Block { quarto_pandoc_types::Block::Paragraph(Paragraph { - content: vec![quarto_pandoc_types::Inline::Insert(Insert { + content: vec![quarto_pandoc_types::Inline::Insert(Box::new(Insert { attr: ( String::new(), vec![attr_class.to_string()], @@ -1947,7 +1943,7 @@ mod list_length_tests { })], source_info: source.clone(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: source, }) } 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 f02c9964dc..7978661035 100644 --- a/crates/quarto-citeproc/src/output.rs +++ b/crates/quarto-citeproc/src/output.rs @@ -1398,12 +1398,12 @@ fn to_inlines_inner(output: &Output) -> quarto_pandoc_types::Inlines { // Create attr with nocase class: (id, classes, attrs) let mut attr = empty_attr(); attr.1.push("nocase".to_string()); - vec![Inline::Span(Span { + vec![Inline::Span(Box::new(Span { attr, content: inner, source_info: empty_source_info(), attr_source: empty_attr_source(), - })] + }))] } } Tag::NoDecoration => { @@ -1413,12 +1413,12 @@ fn to_inlines_inner(output: &Output) -> quarto_pandoc_types::Inlines { } else { let mut attr = empty_attr(); attr.1.push("nodecoration".to_string()); - vec![Inline::Span(Span { + vec![Inline::Span(Box::new(Span { attr, content: inner, source_info: empty_source_info(), attr_source: empty_attr_source(), - })] + }))] } } _ => inner, @@ -1429,14 +1429,14 @@ fn to_inlines_inner(output: &Output) -> quarto_pandoc_types::Inlines { if content.is_empty() { vec![] } else { - vec![Inline::Link(Link { + vec![Inline::Link(Box::new(Link { attr: empty_attr(), content, target: (url.clone(), String::new()), source_info: empty_source_info(), attr_source: empty_attr_source(), target_source: empty_target_source(), - })] + }))] } } Output::InNote(child) => { @@ -1580,11 +1580,11 @@ fn to_inlines_inner(output: &Output) -> quarto_pandoc_types::Inlines { // 8. Apply quotes if formatting.quotes { - inner = vec![Inline::Quoted(Quoted { + inner = vec![Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::DoubleQuote, content: inner, source_info: empty_source_info(), - })]; + }))]; } // 9. Apply prefix/suffix OUTSIDE formatting (for regular elements) @@ -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 674e6b4e86..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) => { @@ -328,12 +328,12 @@ fn slot_children_mut(slot: &mut Slot, v: &mut dyn ListVisitor) { **i = if list.len() == 1 { list.pop().expect("one inline") } else { - Inline::Span(quarto_pandoc_types::inline::Span { + Inline::Span(Box::new(quarto_pandoc_types::inline::Span { attr: Default::default(), content: list, source_info: i.source_info().clone(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - }) + })) }; } } 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 67559bfee0..bef9a7c101 100644 --- a/crates/quarto-core/src/crossref/target.rs +++ b/crates/quarto-core/src/crossref/target.rs @@ -77,7 +77,7 @@ pub fn crossref_target_view(block: &Block) -> Option> { /// Inline-level counterpart to [`crossref_target_view`]. /// /// Inline crossref targets exist too — labelled display equations are -/// `Inline::Custom("Equation")` with the standard plain_data triple. A +/// `Inline::Custom(Box::new("Equation"))` with the standard plain_data triple. A /// walker that wants a uniform outline of all crossref targets (as the /// LSP outline does) must recognize both shapes. pub fn crossref_target_view_inline(inline: &Inline) -> Option> { @@ -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 f7eb6b9425..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); @@ -959,7 +959,7 @@ fn lower_card(card: Card, cx: &mut Lower) -> Block { let mut title_div = None; match title { Some(title) if !title.is_empty() => { - let span = Inline::Span(Span { + let span = Inline::Span(Box::new(Span { attr: ( String::new(), Vec::new(), @@ -971,7 +971,7 @@ fn lower_card(card: Card, cx: &mut Lower) -> Block { content: title, source_info: generated(), attr_source: AttrSourceInfo::empty(), - }); + })); title_div = Some(div( ( String::new(), 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 f164077aeb..52d9f65502 100644 --- a/crates/quarto-core/src/editorial_marks.rs +++ b/crates/quarto-core/src/editorial_marks.rs @@ -80,12 +80,12 @@ pub fn new_span( attrs: LinkedHashMap, content: Inlines, ) -> Inline { - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: (String::new(), classes, attrs), content, source_info: generated(), attr_source: AttrSourceInfo::empty(), - }) + })) } // --------------------------------------------------------------------------- @@ -255,17 +255,17 @@ fn block_to_inlines(block: &Block) -> Inlines { } out } - Block::CodeBlock(cb) => vec![Inline::Code(Code { + Block::CodeBlock(cb) => vec![Inline::Code(Box::new(Code { attr: cb.attr.clone(), text: cb.text.clone(), source_info: generated(), attr_source: AttrSourceInfo::empty(), - })], - Block::RawBlock(rb) => vec![Inline::RawInline(RawInline { + }))], + Block::RawBlock(rb) => vec![Inline::RawInline(Box::new(RawInline { format: rb.format.clone(), text: rb.text.clone(), source_info: generated(), - })], + }))], Block::Div(d) => blocks_to_inlines(&d.content), Block::BlockQuote(bq) => blocks_to_inlines(&bq.content), Block::Figure(f) => blocks_to_inlines(&f.content), @@ -352,7 +352,7 @@ mod tests { fn plain_span(content: Inlines) -> Span { match new_span(Vec::new(), LinkedHashMap::new(), content) { - Inline::Span(s) => s, + Inline::Span(s) => *s, _ => unreachable!(), } } @@ -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 22cd9fe0d7..c54c09f545 100644 --- a/crates/quarto-core/src/pandoc_filters/image_alt_encode.rs +++ b/crates/quarto-core/src/pandoc_filters/image_alt_encode.rs @@ -70,7 +70,7 @@ mod tests { } fn image(alt: &str, kvs: &[(&str, &str)]) -> Inline { - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: ( String::new(), vec![], @@ -83,7 +83,7 @@ mod tests { source_info: si(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn para(content: Vec) -> Block { @@ -123,14 +123,14 @@ mod tests { #[test] fn standalone_linked_image_alt_moves_to_fig_alt() { - let link = Inline::Link(Link { + let link = Inline::Link(Box::new(Link { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![image("Alt L", &[])], target: ("https://example.com".into(), String::new()), source_info: si(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); assert_eq!( summary(vec![para(vec![link])]), vec![(String::new(), Some("Alt L".into()))] @@ -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/config.rs b/crates/quarto-core/src/project/book/config.rs index 4427b0eef1..a8a79dff01 100644 --- a/crates/quarto-core/src/project/book/config.rs +++ b/crates/quarto-core/src/project/book/config.rs @@ -140,7 +140,7 @@ pub fn chapter_label_prefix(item: &BookRenderItem) -> Option { /// verbatim (design doc §4). pub fn decorated_chapter_text(prefix: &str, title: &str) -> ConfigValue { let span = |class: &str, text: &str| { - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: (String::new(), vec![class.to_string()], LinkedHashMap::new()), content: vec![Inline::Str(Str { text: text.to_string(), @@ -148,7 +148,7 @@ pub fn decorated_chapter_text(prefix: &str, title: &str) -> ConfigValue { })], source_info: gen_si(), attr_source: AttrSourceInfo::empty(), - }) + })) }; let inlines: Inlines = vec![ span("chapter-number", prefix), diff --git a/crates/quarto-core/src/project/book/merge.rs b/crates/quarto-core/src/project/book/merge.rs index 4c9cb65fbf..4d4b447529 100644 --- a/crates/quarto-core/src/project/book/merge.rs +++ b/crates/quarto-core/src/project/book/merge.rs @@ -118,18 +118,20 @@ fn marker_blocks(item: &BookRenderItem) -> [Block; 2] { let block_text = file_metadata_marker_text(&file_metadata_json(item, &dir)); let inline_marker = Block::Paragraph(Paragraph { - content: vec![quarto_pandoc_types::Inline::RawInline(RawInline { - format: "html".to_string(), - text: inline_text, - source_info: generated_source_info(), - })], + content: vec![quarto_pandoc_types::Inline::RawInline(Box::new( + RawInline { + format: "html".to_string(), + text: inline_text, + source_info: generated_source_info(), + }, + ))], 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] } @@ -203,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 { @@ -212,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 @@ -223,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)) } @@ -256,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); @@ -368,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()], @@ -377,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/project/listing/helpers.rs b/crates/quarto-core/src/project/listing/helpers.rs index 396f7c4735..041044c0c9 100644 --- a/crates/quarto-core/src/project/listing/helpers.rs +++ b/crates/quarto-core/src/project/listing/helpers.rs @@ -468,10 +468,13 @@ fn take_plain_prefix(inlines: &[Inline], budget: &mut usize) -> Inlines { } macro_rules! container { ($out:ident, $variant:ident, $node:ident, $budget:ident) => { - $out.push(Inline::$variant($variant { - content: take_plain_prefix(&$node.content, $budget), - ..$node.clone() - })) + $out.push(Inline::$variant( + $variant { + content: take_plain_prefix(&$node.content, $budget), + ..$variant::clone($node) + } + .into(), + )) }; } let mut out = Inlines::new(); @@ -484,10 +487,10 @@ fn take_plain_prefix(inlines: &[Inline], budget: &mut usize) -> Inlines { text: cut(&s.text, budget), ..s.clone() })), - Inline::Code(c) => out.push(Inline::Code(Code { + Inline::Code(c) => out.push(Inline::Code(Box::new(Code { text: cut(&c.text, budget), - ..c.clone() - })), + ..Code::clone(c) + }))), Inline::Space(_) | Inline::SoftBreak(_) | Inline::LineBreak(_) => { whole(inline, 1, budget, &mut out) } diff --git a/crates/quarto-core/src/revealjs/auto_stretch.rs b/crates/quarto-core/src/revealjs/auto_stretch.rs index 90dedfa952..6c04a7192a 100644 --- a/crates/quarto-core/src/revealjs/auto_stretch.rs +++ b/crates/quarto-core/src/revealjs/auto_stretch.rs @@ -291,7 +291,7 @@ fn hoist_figure(f: &mut Figure, fallback_id: &str) -> Vec { } let img_source = image.source_info.clone(); let mut blocks = vec![Block::Plain(Plain { - content: vec![Inline::Image(image)], + content: vec![Inline::Image(Box::new(image))], source_info: img_source, })]; @@ -325,11 +325,11 @@ fn caption_paragraph(mut content: Vec) -> Block { vec!["caption".to_string()], LinkedHashMap::new(), ); - content.push(Inline::Attr(InlineAttr::new( + content.push(Inline::Attr(Box::new(InlineAttr::new( caption_attr, AttrSourceInfo::empty(), SourceInfo::generated(By::revealjs()), - ))); + )))); Block::Paragraph(Paragraph { content, source_info: SourceInfo::generated(By::revealjs()), @@ -519,7 +519,7 @@ mod tests { for (k, v) in kvs { m.insert(k.to_string(), v.to_string()); } - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: ( String::new(), classes.iter().map(|s| s.to_string()).collect(), @@ -530,7 +530,7 @@ mod tests { source_info: si(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn para(inlines: Vec) -> Block { @@ -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/footer_logo.rs b/crates/quarto-core/src/revealjs/footer_logo.rs index 056d87c510..b344acd1dd 100644 --- a/crates/quarto-core/src/revealjs/footer_logo.rs +++ b/crates/quarto-core/src/revealjs/footer_logo.rs @@ -348,14 +348,14 @@ mod tests { #[test] fn footer_slot_renders_inline_links() { - let link = Inline::Link(Link { + let link = Inline::Link(Box::new(Link { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![str_inline("Quarto")], target: ("https://quarto.org".to_string(), String::new()), source_info: si(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), target_source: quarto_pandoc_types::attr::TargetSourceInfo::empty(), - }); + })); let m = meta_with_footer_center(inlines(vec![str_inline("© "), link])); let html = footer_slot_html(&m).expect("footer slot"); assert!( 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 a0aaada7c3..420a10a649 100644 --- a/crates/quarto-core/src/stage/stages/equation_number.rs +++ b/crates/quarto-core/src/stage/stages/equation_number.rs @@ -181,7 +181,7 @@ pub fn encode_number(span: &mut Span, number: &str, encoding: NumberEncoding) -> span.attr.1.push(EQ_SIBLING_NUMBER_CLASS.to_string()); span.content.insert( 1, - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: ( String::new(), vec![EQ_NUMBER_LABEL_CLASS.to_string()], @@ -193,7 +193,7 @@ pub fn encode_number(span: &mut Span, number: &str, encoding: NumberEncoding) -> })], source_info, attr_source: AttrSourceInfo::empty(), - }), + })), ); } NumberEncoding::Writer => {} @@ -246,12 +246,12 @@ mod tests { } fn math(math_type: MathType, text: &str) -> Inline { - Inline::Math(Math { + Inline::Math(Box::new(Math { math_type, text: text.to_string(), source_info: si(), text_source: None, - }) + })) } /// The span `CrossrefRenderTransform` emits for a numbered equation. @@ -354,12 +354,12 @@ mod tests { let text = SourceInfo::original(FileId(0), 2, 10); let mut span = numbered_span( "1", - vec![Inline::Math(Math { + vec![Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: "e = mc^2".to_string(), source_info: node, text_source: Some(text), - })], + }))], ); assert!(encode_number(&mut span, "1", NumberEncoding::TexTag)); let Inline::Math(math) = &span.content[0] else { @@ -389,12 +389,12 @@ mod tests { let text = SourceInfo::original(FileId(0), 2, 3); let mut span = numbered_span( "2", - vec![Inline::Math(Math { + vec![Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: "x".to_string(), source_info: node, text_source: Some(text), - })], + }))], ); assert!(encode_number(&mut span, "2", NumberEncoding::Qquad)); let Inline::Math(math) = &span.content[0] else { @@ -525,14 +525,19 @@ mod tests { use quarto_pandoc_types::custom::{CustomNode, Slot}; use quarto_pandoc_types::inline::Emph; - let eq = || Inline::Span(numbered_span("1", vec![math(MathType::DisplayMath, "x")])); + let eq = || { + Inline::Span(Box::new(numbered_span( + "1", + vec![math(MathType::DisplayMath, "x")], + ))) + }; let mut custom = CustomNode::new("Anything", quarto_pandoc_types::attr::empty_attr(), si()); custom .slots .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()], @@ -540,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); @@ -592,10 +597,10 @@ mod tests { #[test] fn walker_removes_the_attribute_even_when_it_cannot_encode() { - let mut blocks = vec![para(vec![Inline::Span(numbered_span( + let mut blocks = vec![para(vec![Inline::Span(Box::new(numbered_span( "1", vec![math(MathType::InlineMath, "x")], - ))])]; + )))])]; let out = walk(&mut blocks, NumberEncoding::TexTag); assert_eq!((out.encoded, out.non_canonical), (0, 1)); let Block::Paragraph(p) = &blocks[0] else { @@ -612,12 +617,12 @@ mod tests { fn walker_ignores_spans_without_the_attribute() { let mut span = numbered_span("1", vec![math(MathType::DisplayMath, "x")]); span.attr.2.clear(); - let mut blocks = vec![para(vec![Inline::Span(span.clone())])]; + let mut blocks = vec![para(vec![Inline::Span(Box::new(span.clone()))])]; let out = walk(&mut blocks, NumberEncoding::TexTag); assert_eq!((out.encoded, out.non_canonical), (0, 0)); let Block::Paragraph(p) = &blocks[0] else { panic!() }; - assert_eq!(p.content[0], Inline::Span(span)); + assert_eq!(p.content[0], Inline::Span(Box::new(span))); } } diff --git a/crates/quarto-core/src/stage/stages/include_expansion.rs b/crates/quarto-core/src/stage/stages/include_expansion.rs index a4b63b168e..6c6d4e78e2 100644 --- a/crates/quarto-core/src/stage/stages/include_expansion.rs +++ b/crates/quarto-core/src/stage/stages/include_expansion.rs @@ -931,13 +931,13 @@ mod tests { fn make_include_paragraph(path: &str) -> Block { Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(Shortcode { + content: vec![Inline::Shortcode(Box::new(Shortcode { is_escaped: false, name: "include".to_string(), positional_args: vec![ShortcodeArg::String(path.to_string())], keyword_args: hashlink::LinkedHashMap::new(), source_info: SourceInfo::original(FileId(0), 0, 30), - })], + }))], source_info: SourceInfo::original(FileId(0), 0, 30), }) } @@ -997,13 +997,13 @@ mod tests { #[test] fn extract_include_path_non_include_shortcode() { let block = Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(Shortcode { + content: vec![Inline::Shortcode(Box::new(Shortcode { is_escaped: false, name: "meta".to_string(), positional_args: vec![ShortcodeArg::String("title".to_string())], keyword_args: hashlink::LinkedHashMap::new(), source_info: SourceInfo::for_test(), - })], + }))], source_info: SourceInfo::for_test(), }); assert_eq!(extract_include_path(&block), None); @@ -1018,13 +1018,13 @@ mod tests { text: "some text ".to_string(), source_info: SourceInfo::for_test(), }), - Inline::Shortcode(Shortcode { + Inline::Shortcode(Box::new(Shortcode { is_escaped: false, name: "include".to_string(), positional_args: vec![ShortcodeArg::String("file.qmd".to_string())], keyword_args: hashlink::LinkedHashMap::new(), source_info: SourceInfo::for_test(), - }), + })), ], source_info: SourceInfo::for_test(), }); @@ -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 c0ffcfabe5..6feee8a60e 100644 --- a/crates/quarto-core/src/stage/stages/include_resolve.rs +++ b/crates/quarto-core/src/stage/stages/include_resolve.rs @@ -996,20 +996,20 @@ 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 { Block::Paragraph(Paragraph { - content: vec![Inline::RawInline(RawInline { + content: vec![Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: text.to_string(), source_info: SourceInfo::for_test(), - })], + }))], source_info: SourceInfo::for_test(), }) } @@ -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); @@ -1278,32 +1278,32 @@ mod tests { // (RawInline + Str + Quoted + Str + RawInline). let si = SourceInfo::for_test(); let inlines = vec![ - Inline::RawInline(RawInline { + Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: "".to_string(), source_info: si.clone(), - }), + })), ]; let parsed_value = ConfigValue { value: ConfigValueKind::PandocInlines(inlines), 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 51b3a939bf..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,33 +591,33 @@ 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(Image { + Inline::Image(Box::new(Image { attr: empty_attr(), content: vec![], target: (src.to_string(), String::new()), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn link(content: Vec, target: &str) -> Inline { - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: empty_attr(), content, target: (target.to_string(), String::new()), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn note(content: Vec) -> Inline { Inline::Note(Note { diff --git a/crates/quarto-core/src/stage/stages/lmd_desugar.rs b/crates/quarto-core/src/stage/stages/lmd_desugar.rs index f43bc5220f..a3116b4771 100644 --- a/crates/quarto-core/src/stage/stages/lmd_desugar.rs +++ b/crates/quarto-core/src/stage/stages/lmd_desugar.rs @@ -316,7 +316,7 @@ mod tests { .content .iter() .filter_map(|i| match i { - Inline::Code(c) => Some(c), + Inline::Code(c) => Some(&**c), _ => None, }) .collect(); diff --git a/crates/quarto-core/src/stage/stages/lmd_execute.rs b/crates/quarto-core/src/stage/stages/lmd_execute.rs index 25672f8d33..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,13 +185,13 @@ 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 /// holding its desugared expression. fn displayed_inline_cell(expr: &str, source_info: &SourceInfo) -> Inline { - Inline::Code(Code { + Inline::Code(Box::new(Code { attr: ( String::new(), vec![LMD_LANG.to_string()], @@ -200,7 +200,7 @@ fn displayed_inline_cell(expr: &str, source_info: &SourceInfo) -> Inline { text: expr.to_string(), source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn cell_error( @@ -1552,7 +1552,7 @@ mod tests { .content .iter() .filter_map(|i| match i { - Inline::Code(c) => Some(c), + Inline::Code(c) => Some(&**c), _ => None, }) .collect(); diff --git a/crates/quarto-core/src/stage/stages/math_js.rs b/crates/quarto-core/src/stage/stages/math_js.rs index 2f2dafc29a..72865988d0 100644 --- a/crates/quarto-core/src/stage/stages/math_js.rs +++ b/crates/quarto-core/src/stage/stages/math_js.rs @@ -28,7 +28,7 @@ //! //! This stage manipulates only the publicly-described AST: //! -//! - It reads `Inline::Math(MathType, String)` and the +//! - It reads `Inline::Math(Box::new(MathType, String))` and the //! `CustomNode("Equation")` wrapper that //! [`crate::transforms::EquationLabelTransform`] produces from //! `$$…$$ {#eq-…}` syntax. @@ -557,21 +557,21 @@ mod tests { } fn inline_math(text: &str) -> Inline { - Inline::Math(Math { + Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text: text.to_string(), source_info: SourceInfo::for_test(), text_source: None, - }) + })) } fn display_math(text: &str) -> Inline { - Inline::Math(Math { + Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: text.to_string(), source_info: SourceInfo::for_test(), text_source: None, - }) + })) } fn str_inline(s: &str) -> Inline { @@ -833,7 +833,7 @@ mod tests { let math = run_and_get_math( vec![Block::Paragraph(Paragraph { - content: vec![Inline::Custom(node)], + content: vec![Inline::Custom(Box::new(node))], source_info: SourceInfo::for_test(), })], empty_meta(), @@ -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 d614580259..34ad217b97 100644 --- a/crates/quarto-core/src/stage/stages/math_ml.rs +++ b/crates/quarto-core/src/stage/stages/math_ml.rs @@ -197,20 +197,20 @@ fn convert_math(math: &Math) -> (Option, Vec) { MathType::InlineMath => "inline", MathType::DisplayMath => "display", }; - let replacement = Inline::Span(Span { + let replacement = Inline::Span(Box::new(Span { attr: ( String::new(), vec!["math".to_string(), class.to_string()], Default::default(), ), - content: vec![Inline::RawInline(RawInline { + content: vec![Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: mathml, source_info: math.source_info.clone(), - })], + }))], source_info: math.source_info.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); (Some(replacement), conversion.diagnostics) } @@ -236,12 +236,12 @@ mod tests { } fn math(math_type: MathType, text: &str) -> Inline { - Inline::Math(Math { + Inline::Math(Box::new(Math { math_type, text: text.to_string(), source_info: si(), text_source: None, - }) + })) } fn para(content: Vec) -> Block { @@ -338,12 +338,12 @@ mod tests { // `$x + \bogus y$` at file bytes 0..14; text at 1..13. let node = SourceInfo::original(FileId(0), 0, 14); let text = SourceInfo::original(FileId(0), 1, 13); - let mut blocks = vec![para(vec![Inline::Math(Math { + let mut blocks = vec![para(vec![Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text: "x + \\bogus y".to_string(), source_info: node, text_source: Some(text), - })])]; + }))])]; let out = convert_document(&mut blocks); let loc = out.diagnostics[0].location.as_ref().expect("located"); // `\bogus` is at text offsets 4..10, so file bytes 5..11. @@ -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/stage/stages/prefetch_remote_images.rs b/crates/quarto-core/src/stage/stages/prefetch_remote_images.rs index 59840b5b20..07ffdf1f98 100644 --- a/crates/quarto-core/src/stage/stages/prefetch_remote_images.rs +++ b/crates/quarto-core/src/stage/stages/prefetch_remote_images.rs @@ -237,12 +237,12 @@ impl PipelineStage for PrefetchRemoteImagesStage { } Err(_) if typst => { // typst.lua would fetch the URL again and exit 83 on failure. - *inline = Inline::Span(Span { + *inline = Inline::Span(Box::new(Span { attr: Default::default(), content: std::mem::take(&mut img.content), source_info: img.source_info.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); } Err(_) => {} } diff --git a/crates/quarto-core/src/template.rs b/crates/quarto-core/src/template.rs index 297837f38c..98ad1d8e2b 100644 --- a/crates/quarto-core/src/template.rs +++ b/crates/quarto-core/src/template.rs @@ -2368,45 +2368,45 @@ 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![Inline::Str(Str { text: "quoted".to_string(), source_info: dummy_source_info(), })], source_info: dummy_source_info(), - })]; + }))]; let text = inlines_to_text(&inlines); assert_eq!(text, "\"quoted\""); } #[test] fn test_inlines_to_text_code() { - let inlines = vec![Inline::Code(Code { + let inlines = vec![Inline::Code(Box::new(Code { attr: quarto_pandoc_types::attr::Attr::default(), text: "code()".to_string(), source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = inlines_to_text(&inlines); assert_eq!(text, "code()"); } #[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: dummy_source_info(), text_source: None, - })]; + }))]; let text = inlines_to_text(&inlines); assert_eq!(text, "x^2"); } #[test] fn test_inlines_to_text_link() { - let inlines = vec![Inline::Link(Link { + let inlines = vec![Inline::Link(Box::new(Link { attr: quarto_pandoc_types::attr::Attr::default(), content: vec![Inline::Str(Str { text: "link text".to_string(), @@ -2416,14 +2416,14 @@ mod tests { source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), target_source: quarto_pandoc_types::attr::TargetSourceInfo::empty(), - })]; + }))]; let text = inlines_to_text(&inlines); assert_eq!(text, "link text"); } #[test] fn test_inlines_to_text_span() { - let inlines = vec![Inline::Span(Span { + let inlines = vec![Inline::Span(Box::new(Span { attr: quarto_pandoc_types::attr::Attr::default(), content: vec![Inline::Str(Str { text: "span content".to_string(), @@ -2431,7 +2431,7 @@ mod tests { })], source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = inlines_to_text(&inlines); assert_eq!(text, "span content"); } @@ -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")); } @@ -4284,12 +4284,12 @@ mod tests { /// A `Code` inline `\`text\``. fn code_inline(text: &str) -> Inline { - Inline::Code(Code { + Inline::Code(Box::new(Code { attr: empty_attr(), text: text.to_string(), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn str_inline(text: &str) -> Inline { 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/announcement_render.rs b/crates/quarto-core/src/transforms/announcement_render.rs index 8e00d1e5df..66a9e9e630 100644 --- a/crates/quarto-core/src/transforms/announcement_render.rs +++ b/crates/quarto-core/src/transforms/announcement_render.rs @@ -191,7 +191,7 @@ mod tests { } fn link(url: &str, text: &str) -> Inline { - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: Default::default(), content: vec![Inline::Str(Str { text: text.to_string(), @@ -201,7 +201,7 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } struct Run { diff --git a/crates/quarto-core/src/transforms/appendix.rs b/crates/quarto-core/src/transforms/appendix.rs index f0b12c3bd0..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. @@ -483,7 +483,7 @@ fn create_citation_section(meta: &ConfigValue) -> Option { text: "For attribution, please cite this work as: ".to_string(), source_info: source_info.clone(), }), - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: (String::new(), Vec::new(), LinkedHashMap::new()), content: vec![Inline::Str(Str { text: url.clone(), @@ -493,7 +493,7 @@ fn create_citation_section(meta: &ConfigValue) -> Option { source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), target_source: quarto_pandoc_types::attr::TargetSourceInfo::empty(), - }), + })), ] } else { // If no URL, just note that citation info is available @@ -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/book_cover_image.rs b/crates/quarto-core/src/transforms/book_cover_image.rs index 6a49e8adfa..368b0958af 100644 --- a/crates/quarto-core/src/transforms/book_cover_image.rs +++ b/crates/quarto-core/src/transforms/book_cover_image.rs @@ -180,7 +180,7 @@ impl AstTransform for BookCoverImageTransform { .as_deref() .map(split_string_to_inlines) .unwrap_or_default(); - let image = Inline::Image(Image { + let image = Inline::Image(Box::new(Image { attr: ( String::new(), vec!["quarto-cover-image".to_string(), "nolightbox".to_string()], @@ -191,7 +191,7 @@ impl AstTransform for BookCoverImageTransform { source_info: SourceInfo::generated(By::programmatic_config()), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); let paragraph = Block::Paragraph(Paragraph { content: vec![image], source_info: SourceInfo::generated(By::programmatic_config()), 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 18cde56a89..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 @@ -321,7 +321,7 @@ fn resolve_callout( Some(mut inlines) if title_is_user_supplied && !is_crossref => { inlines.insert( 0, - Inline::Span(quarto_pandoc_types::inline::Span { + Inline::Span(Box::new(quarto_pandoc_types::inline::Span { attr: make_attr(&["screen-reader-only"]), content: vec![Inline::Str(Str { text: callout_display_name(&callout_type, terms), @@ -329,7 +329,7 @@ fn resolve_callout( })], source_info: SourceInfo::generated(By::callout()), attr_source: AttrSourceInfo::empty(), - }), + })), ); Some(inlines) } @@ -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![ @@ -480,22 +480,22 @@ fn build_titled_content( // Trailing toggle button. header_content.push(Block::Plain(Plain { - content: vec![Inline::RawInline(RawInline { + content: vec![Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: "
" .to_string(), source_info: source_info.clone(), - })], + }))], 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 @@ -574,11 +574,11 @@ fn icon_container_div(source_info: &SourceInfo, icon: bool) -> Div { Div { attr: make_attr(&["callout-icon-container"]), content: vec![Block::Plain(Plain { - content: vec![Inline::RawInline(RawInline { + content: vec![Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: format!("", i_class), source_info: source_info.clone(), - })], + }))], source_info: source_info.clone(), })], source_info: source_info.clone(), @@ -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 74226f1ad6..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(), - }), + })), ); } }); @@ -545,11 +545,11 @@ fn attach_ast_payloads(ast: &mut Pandoc, decls: &[ComponentDecl]) { let text = ast_script(&json[0]); span.content.insert( 0, - Inline::RawInline(quarto_pandoc_types::inline::RawInline { + Inline::RawInline(Box::new(quarto_pandoc_types::inline::RawInline { format: "html".to_string(), text, source_info: span.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 { @@ -745,12 +745,12 @@ mod tests { fn span_para(id: &str, classes: &[&str]) -> Block { Block::Paragraph(Paragraph { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr: attr(id, classes), content: vec![], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: SourceInfo::for_test(), }) } @@ -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 837963fb76..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"); @@ -1256,7 +1256,7 @@ mod tests { "VISIBLE-DIV", ); let visible_span = Block::Paragraph(quarto_pandoc_types::block::Paragraph { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr: attr(&["content-visible"], &[("when-format", "docx")]), content: vec![Inline::Str(quarto_pandoc_types::inline::Str { text: "VISIBLE-SPAN".to_string(), @@ -1268,16 +1268,16 @@ mod tests { quarto_source_map::By::unknown(), ), attr_source: AttrSourceInfo::empty(), - })], + }))], 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, @@ -1371,7 +1371,7 @@ mod tests { } fn inline_span(classes: &[&str], kvs: &[(&str, &str)], text: &str) -> Inline { - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: attr(classes, kvs), content: vec![Inline::Str(Str { text: text.to_string(), @@ -1379,7 +1379,7 @@ mod tests { })], source_info: SourceInfo::generated(By::unknown()), attr_source: AttrSourceInfo::empty(), - }) + })) } fn inline_str(text: &str) -> Inline { @@ -1401,7 +1401,7 @@ mod tests { SourceInfo::generated(By::unknown()), ); node.slots.insert("content".into(), Slot::Inlines(content)); - Inline::Custom(node) + Inline::Custom(Box::new(node)) } /// `Inline::Custom` with a single `Slot::Inline` (the *singular*, @@ -1419,7 +1419,7 @@ mod tests { ); node.slots .insert("content".into(), Slot::Inline(Box::new(inline))); - Inline::Custom(node) + Inline::Custom(Box::new(node)) } fn paragraph(content: Vec) -> Block { @@ -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/cross_chapter_crossref_resolve.rs b/crates/quarto-core/src/transforms/cross_chapter_crossref_resolve.rs index 7cfb279ae7..5d7dc7c19b 100644 --- a/crates/quarto-core/src/transforms/cross_chapter_crossref_resolve.rs +++ b/crates/quarto-core/src/transforms/cross_chapter_crossref_resolve.rs @@ -260,7 +260,7 @@ mod tests { "resolved": false, "label_upper": false, }); - Inline::Custom(node) + Inline::Custom(Box::new(node)) } fn para(content: Vec) -> Block { diff --git a/crates/quarto-core/src/transforms/crossref_index.rs b/crates/quarto-core/src/transforms/crossref_index.rs index a3ac1cecf6..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); @@ -1388,21 +1388,21 @@ mod tests { fn eq_para(id: &str, math_text: &str) -> Block { use quarto_pandoc_types::inline::{Math, MathType, Span}; Block::Paragraph(Paragraph { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr: ( id.to_string(), vec!["quarto-math-with-attribute".to_string()], LinkedHashMap::new(), ), - content: vec![Inline::Math(Math { + content: vec![Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: math_text.to_string(), source_info: si(), text_source: None, - })], + }))], source_info: si(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: si(), }) } diff --git a/crates/quarto-core/src/transforms/crossref_render.rs b/crates/quarto-core/src/transforms/crossref_render.rs index b6c143f2b2..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); } } } @@ -223,7 +223,7 @@ fn inject_header_number(h: &mut Header, terms: Option<&LanguageTerms>, fs: &Floa return; }; let source_info = h.source_info.clone(); - let number_span = Inline::Span(Span { + let number_span = Inline::Span(Box::new(Span { attr: ( String::new(), vec!["header-section-number".to_string()], @@ -235,7 +235,7 @@ fn inject_header_number(h: &mut Header, terms: Option<&LanguageTerms>, fs: &Floa })], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); let mut prefix = if h.level == 1 && fs.chapter_seed.as_ref().is_some_and(|s| s.is_appendix) { let title = terms .and_then(|t| t.crossref_prefix("apx")) @@ -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 @@ -1060,7 +1060,7 @@ fn theorem_label_inlines( content: strong_content, source_info: source_info.clone(), }); - let span = Inline::Span(Span { + let span = Inline::Span(Box::new(Span { attr: ( String::new(), vec!["theorem-title".to_string()], @@ -1069,7 +1069,7 @@ fn theorem_label_inlines( content: vec![strong], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); vec![ span, @@ -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 @@ -1220,12 +1220,12 @@ fn render_equation(node: CustomNode, fs: &FloatState) -> Inline { _ => vec![], }; - Inline::Span(Span { + Inline::Span(Box::new(Span { attr, content, source_info, attr_source: AttrSourceInfo::empty(), - }) + })) } /// Convert a CrossrefResolvedRef custom node into a `Link` inline. @@ -1339,14 +1339,14 @@ fn render_resolved_ref(node: CustomNode, terms: Option<&LanguageTerms>, fs: &Flo classes.push("quarto-unresolved-ref".to_string()); } - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: (String::new(), classes, hashlink::LinkedHashMap::new()), content, target, source_info, attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } /// Build the link text for a resolved `sec` ref: "Section 1.2" by default, @@ -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 { @@ -1610,7 +1610,7 @@ mod tests { } fn cite(id: &str) -> Inline { - Inline::Cite(Cite { + Inline::Cite(Box::new(Cite { citations: vec![Citation { id: id.to_string(), prefix: vec![], @@ -1622,17 +1622,17 @@ mod tests { }], content: vec![str_inline(&format!("@{}", id))], source_info: si(), - }) + })) } 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 @@ -1891,15 +1891,15 @@ mod tests { // Q1 reads `fig-align` from the contained Image and strips it. let mut img_attr: LinkedHashMap = LinkedHashMap::new(); img_attr.insert("fig-align".to_string(), "left".to_string()); - let img = Inline::Image(quarto_pandoc_types::inline::Image { + let img = Inline::Image(Box::new(quarto_pandoc_types::inline::Image { attr: (String::new(), Vec::new(), img_attr), content: vec![], target: ("img.png".to_string(), String::new()), source_info: si(), 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]); @@ -2153,15 +2153,15 @@ mod tests { // a native Figure with no id — is wrapped in // `Div(.quarto-figure .quarto-figure-)`, with the figure's id // (when present) moving to the wrapper, Q1's `renderHtmlFigure`. - let img = Inline::Image(quarto_pandoc_types::inline::Image { + let img = Inline::Image(Box::new(quarto_pandoc_types::inline::Image { attr: (String::new(), Vec::new(), LinkedHashMap::new()), content: vec![], target: ("img.png".to_string(), String::new()), source_info: si(), 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!( @@ -2403,7 +2403,7 @@ mod tests { "resolved": false, "label_upper": false, }); - Inline::Custom(node) + Inline::Custom(Box::new(node)) } /// The real P5 unit path for one cross-chapter ref: aggregate the @@ -2467,7 +2467,7 @@ mod tests { let Inline::Link(link) = &p.content[0] else { panic!("expected Link, got {:?}", p.content[0]); }; - link.clone() + *link.clone() } #[tokio::test] @@ -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 { @@ -3065,15 +3065,15 @@ mod tests { /// A native `Figure` with a crossref id and a `[Plain[...]]` caption — /// what `![cap](img){#fig-x}` parses to. fn native_figure_with_plain_caption(id: &str, cap: &str) -> Block { - let img = Inline::Image(quarto_pandoc_types::inline::Image { + let img = Inline::Image(Box::new(quarto_pandoc_types::inline::Image { attr: (String::new(), Vec::new(), LinkedHashMap::new()), content: vec![], target: ("img.png".to_string(), String::new()), source_info: si(), 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] @@ -3438,21 +3438,21 @@ mod tests { fn eq_para(id: &str, math_text: &str) -> Block { Block::Paragraph(Paragraph { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr: ( id.to_string(), vec!["quarto-math-with-attribute".to_string()], LinkedHashMap::new(), ), - content: vec![Inline::Math(Math { + content: vec![Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: math_text.to_string(), source_info: si(), text_source: None, - })], + }))], source_info: si(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: si(), }) } @@ -3493,12 +3493,12 @@ mod tests { let mut slots = LinkedHashMap::new(); slots.insert( "content".to_string(), - Slot::Inlines(vec![Inline::Math(Math { + Slot::Inlines(vec![Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: "x".to_string(), source_info: si(), text_source: None, - })]), + }))]), ); let node = CustomNode { type_name: EQUATION.to_string(), @@ -3529,21 +3529,21 @@ mod tests { let node = SourceInfo::original(FileId(0), 0, 12); let text = SourceInfo::original(FileId(0), 2, 10); let block = Block::Paragraph(Paragraph { - content: vec![Inline::Span(Span { + content: vec![Inline::Span(Box::new(Span { attr: ( "eq-einstein".to_string(), vec!["quarto-math-with-attribute".to_string()], LinkedHashMap::new(), ), - content: vec![Inline::Math(Math { + content: vec![Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: "e = mc^2".to_string(), source_info: node.clone(), text_source: Some(text.clone()), - })], + }))], source_info: node.clone(), attr_source: AttrSourceInfo::empty(), - })], + }))], source_info: node, }); let ast = run_full(vec![block]).await; diff --git a/crates/quarto-core/src/transforms/crossref_resolve.rs b/crates/quarto-core/src/transforms/crossref_resolve.rs index 7e048c5592..06452145a7 100644 --- a/crates/quarto-core/src/transforms/crossref_resolve.rs +++ b/crates/quarto-core/src/transforms/crossref_resolve.rs @@ -240,7 +240,7 @@ fn resolve_inline( if let Inline::Cite(cite) = inline && let Some(replacement) = classify_cite(cite, reg, index, chapter_seed, diags) { - *inline = Inline::Custom(replacement); + *inline = Inline::Custom(Box::new(replacement)); } } @@ -466,7 +466,7 @@ mod tests { } fn make_cite(id: &str) -> Inline { - Inline::Cite(Cite { + Inline::Cite(Box::new(Cite { citations: vec![Citation { id: id.to_string(), prefix: vec![], @@ -481,7 +481,7 @@ mod tests { source_info: si(), })], source_info: si(), - }) + })) } fn make_multi_cite(ids: &[&str]) -> Inline { @@ -497,11 +497,11 @@ mod tests { id_source: None, }) .collect(); - Inline::Cite(Cite { + Inline::Cite(Box::new(Cite { citations, content: vec![], source_info: si(), - }) + })) } fn make_index_with(ids: &[(&str, &str)]) -> CrossrefIndex { diff --git a/crates/quarto-core/src/transforms/editorial_marks_ooxml.rs b/crates/quarto-core/src/transforms/editorial_marks_ooxml.rs index f3ecc71b23..d8c8e1074d 100644 --- a/crates/quarto-core/src/transforms/editorial_marks_ooxml.rs +++ b/crates/quarto-core/src/transforms/editorial_marks_ooxml.rs @@ -189,7 +189,7 @@ impl Converter { let mut out: Inlines = Vec::with_capacity(list.len()); for inline in std::mem::take(list) { match inline { - Inline::Span(span) => self.span(span, &mut out), + Inline::Span(span) => self.span(*span, &mut out), mut other => { inline_children_mut(&mut other, self); out.push(other); @@ -207,7 +207,7 @@ impl Converter { Some(MarkKind::Comment) if self.fold_comments > 0 => { strip_mark_classes(&mut span); Neutralizer.inlines(&mut span.content); - out.push(Inline::Span(span)); + out.push(Inline::Span(Box::new(span))); } Some(kind) => match self.target { Target::Docx => self.docx_span(kind, span, out), @@ -221,7 +221,7 @@ impl Converter { self.commented_range(span, range, out); } else { self.inline_list(&mut span.content); - out.push(Inline::Span(span)); + out.push(Inline::Span(Box::new(span))); } } } @@ -239,7 +239,7 @@ impl Converter { .to_string(); span.attr_source = AttrSourceInfo::empty(); self.inline_list(&mut span.content); - out.push(Inline::Span(span)); + out.push(Inline::Span(Box::new(span))); } MarkKind::Highlight => { // The writer applies a highlight only to a bare span: no id, classes or @@ -274,7 +274,7 @@ impl Converter { }), ); if let Inline::Span(s) = taken { - comments.push(s); + comments.push(*s); } } comments.reverse(); @@ -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); @@ -558,14 +558,14 @@ fn pptx_run(kind: MarkKind, author: Option<&str>, text: &str) -> Inline { ), ), }; - Inline::RawInline(RawInline { + Inline::RawInline(Box::new(RawInline { format: "openxml".to_string(), text: format!( "{rpr}{}", xml_escape(&text) ), source_info: generated(), - }) + })) } #[cfg(test)] @@ -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 cee9a4fd18..716f3f20d9 100644 --- a/crates/quarto-core/src/transforms/editorial_marks_typst.rs +++ b/crates/quarto-core/src/transforms/editorial_marks_typst.rs @@ -150,19 +150,19 @@ fn generated() -> SourceInfo { } fn raw_inline(text: String) -> Inline { - Inline::RawInline(RawInline { + Inline::RawInline(Box::new(RawInline { format: "typst".to_string(), text, source_info: generated(), - }) + })) } 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/equation_label.rs b/crates/quarto-core/src/transforms/equation_label.rs index f44d3d81f8..7703bb8841 100644 --- a/crates/quarto-core/src/transforms/equation_label.rs +++ b/crates/quarto-core/src/transforms/equation_label.rs @@ -13,7 +13,7 @@ //! Span(id="eq-xxx", classes=["quarto-math-with-attribute"], [Math(DisplayMath, text)]) //! ``` //! -//! This transform converts such Spans into `Inline::Custom(CustomNode("Equation"))` +//! This transform converts such Spans into `Inline::Custom(Box::new(CustomNode("Equation")))` //! carrying the standard crossref triple (`ref_type`, `kind`, `identifier`) in //! `plain_data`, so the shared crossref indexer and resolver handle equations //! uniformly alongside block-level crossref targets. @@ -167,7 +167,7 @@ fn transform_inline(inline: &mut Inline) { if let Inline::Span(span) = inline && let Some(node) = try_convert_equation(span) { - *inline = Inline::Custom(node); + *inline = Inline::Custom(Box::new(node)); } } @@ -242,21 +242,21 @@ mod tests { } fn display_math_span(id: &str, text: &str) -> Inline { - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: ( id.to_string(), vec![MATH_WITH_ATTR_CLASS.to_string()], LinkedHashMap::new(), ), - content: vec![Inline::Math(Math { + content: vec![Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: text.to_string(), source_info: si(), text_source: None, - })], + }))], source_info: si(), attr_source: AttrSourceInfo::empty(), - }) + })) } #[test] @@ -287,21 +287,21 @@ mod tests { #[test] fn leaves_non_equation_span_alone() { // Span with quarto-math-with-attribute but id doesn't start with eq-. - let mut inline = Inline::Span(Span { + let mut inline = Inline::Span(Box::new(Span { attr: ( "fig-plot".to_string(), vec![MATH_WITH_ATTR_CLASS.to_string()], LinkedHashMap::new(), ), - content: vec![Inline::Math(Math { + content: vec![Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: "x^2".to_string(), source_info: si(), text_source: None, - })], + }))], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); transform_inline(&mut inline); assert!(matches!(inline, Inline::Span(_))); } @@ -309,42 +309,42 @@ mod tests { #[test] fn leaves_inline_math_alone() { // InlineMath (not DisplayMath) should not be converted. - let mut inline = Inline::Span(Span { + let mut inline = Inline::Span(Box::new(Span { attr: ( "eq-inline".to_string(), vec![MATH_WITH_ATTR_CLASS.to_string()], LinkedHashMap::new(), ), - content: vec![Inline::Math(Math { + content: vec![Inline::Math(Box::new(Math { math_type: MathType::InlineMath, text: "x^2".to_string(), source_info: si(), text_source: None, - })], + }))], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); transform_inline(&mut inline); assert!(matches!(inline, Inline::Span(_))); } #[test] fn leaves_span_without_marker_class() { - let mut inline = Inline::Span(Span { + let mut inline = Inline::Span(Box::new(Span { attr: ( "eq-foo".to_string(), vec!["other-class".to_string()], LinkedHashMap::new(), ), - content: vec![Inline::Math(Math { + content: vec![Inline::Math(Box::new(Math { math_type: MathType::DisplayMath, text: "x".to_string(), source_info: si(), text_source: None, - })], + }))], source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); transform_inline(&mut inline); assert!(matches!(inline, Inline::Span(_))); } diff --git a/crates/quarto-core/src/transforms/example_embed.rs b/crates/quarto-core/src/transforms/example_embed.rs index b2b976ce6f..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 @@ -432,7 +432,7 @@ fn with_number_label( order: u64, source_info: &SourceInfo, ) -> Vec { - let label = Inline::Span(Span { + let label = Inline::Span(Box::new(Span { attr: ( String::new(), vec![LABEL_CLASS.to_string()], @@ -446,7 +446,7 @@ fn with_number_label( })], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }); + })); let sep = Inline::Str(Str { text: ": ".to_string(), source_info: source_info.clone(), @@ -628,7 +628,7 @@ mod tests { fn source_link(text: &str, href: &str) -> Block { Block::Paragraph(Paragraph { - content: vec![Inline::Link(Link { + content: vec![Inline::Link(Box::new(Link { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![Inline::Str(Str { text: text.to_string(), @@ -638,7 +638,7 @@ mod tests { source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: dummy_source_info(), }) } @@ -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 c61a431682..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); @@ -837,7 +837,7 @@ mod tests { attrs.insert(k.to_string(), v.to_string()); } Block::Plain(Plain { - content: vec![Inline::Image(Image { + content: vec![Inline::Image(Box::new(Image { attr: ( String::new(), classes.into_iter().map(String::from).collect(), @@ -848,18 +848,18 @@ mod tests { source_info: si(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: si(), }) } 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/footer_render.rs b/crates/quarto-core/src/transforms/footer_render.rs index 891b6da014..cd8e525a28 100644 --- a/crates/quarto-core/src/transforms/footer_render.rs +++ b/crates/quarto-core/src/transforms/footer_render.rs @@ -348,7 +348,7 @@ mod tests { fn footer_image(url: &str, alt: &str) -> quarto_pandoc_types::inline::Inline { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; use quarto_pandoc_types::inline::{Image, Inline, Str}; - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: Default::default(), content: vec![Inline::Str(Str { text: alt.to_string(), @@ -358,13 +358,13 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn footer_link(url: &str, text: &str) -> quarto_pandoc_types::inline::Inline { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; use quarto_pandoc_types::inline::{Inline, Link, Str}; - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: Default::default(), content: vec![Inline::Str(Str { text: text.to_string(), @@ -374,7 +374,7 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } // --- Phase 2 behavior preserved ------------------------------- diff --git a/crates/quarto-core/src/transforms/footnotes.rs b/crates/quarto-core/src/transforms/footnotes.rs index e9f14b4a0a..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"), @@ -905,7 +905,7 @@ mod tests { Block::Paragraph(Paragraph { content: vec![ make_str("Ref."), - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: ( String::new(), vec!["quarto-note-reference".to_string()], @@ -914,19 +914,21 @@ mod tests { content: vec![], source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }), + })), ], 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 f3da14185e..3dfe8d6edf 100644 --- a/crates/quarto-core/src/transforms/footnotes_resolve.rs +++ b/crates/quarto-core/src/transforms/footnotes_resolve.rs @@ -410,7 +410,7 @@ fn create_footnote_ref(number: usize, source_info: &SourceInfo, is_margin: bool) // The link inside the superscript // Target is a tuple: (url, title) - let link = Inline::Link(Link { + let link = Inline::Link(Box::new(Link { attr: ( String::new(), vec!["footnote-ref".to_string()], @@ -424,7 +424,7 @@ fn create_footnote_ref(number: usize, source_info: &SourceInfo, is_margin: bool) source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), target_source: quarto_pandoc_types::attr::TargetSourceInfo::empty(), - }); + })); // Build the class list for the outer span let classes = if is_margin { @@ -436,7 +436,7 @@ fn create_footnote_ref(number: usize, source_info: &SourceInfo, is_margin: bool) // Wrap in a Span with the fnref ID, then in Superscript // Actually, Pandoc puts the ID on the superscript, but we don't have that field. // Let's use a Span wrapper. - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: (fnref_id, classes, LinkedHashMap::new()), content: vec![Inline::Superscript(Superscript { content: vec![link], @@ -444,7 +444,7 @@ fn create_footnote_ref(number: usize, source_info: &SourceInfo, is_margin: bool) })], source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), - }) + })) } /// Create the footnotes section block. @@ -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. @@ -502,7 +502,7 @@ fn create_footnote_item(footnote: &CollectedFootnote) -> Blocks { // Create the backlink // Target is a tuple: (url, title) - let backlink = Inline::Link(Link { + let backlink = Inline::Link(Box::new(Link { attr: ( String::new(), vec!["footnote-back".to_string()], @@ -516,7 +516,7 @@ fn create_footnote_item(footnote: &CollectedFootnote) -> Blocks { source_info: source_info.clone(), attr_source: AttrSourceInfo::empty(), target_source: quarto_pandoc_types::attr::TargetSourceInfo::empty(), - }); + })); // Convert content to blocks and append backlink to last paragraph let mut content_blocks = footnote.content.clone(); @@ -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 8df48aabb8..f6c349754f 100644 --- a/crates/quarto-core/src/transforms/image_alt_normalize.rs +++ b/crates/quarto-core/src/transforms/image_alt_normalize.rs @@ -206,17 +206,17 @@ 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, long: Some(vec![plain(text(caption))]), source_info: si(), }, - content: vec![plain(vec![Inline::Image(image(caption, kvs))])], + 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). @@ -258,10 +258,10 @@ mod tests { #[test] fn fig_alt_on_a_bare_image_becomes_its_alt_text() { - let mut out = run(vec![para(vec![Inline::Image(image( + let mut out = run(vec![para(vec![Inline::Image(Box::new(image( "", &[("fig-alt", "Alt A"), ("width", "50%")], - ))])]); + )))])]); assert_eq!( images(&mut out), vec![("Alt A".into(), vec![("width".into(), "50%".into())])] @@ -270,10 +270,10 @@ mod tests { #[test] fn alt_text_is_tokenized_like_pandoc() { - let mut out = run(vec![para(vec![Inline::Image(image( + let mut out = run(vec![para(vec![Inline::Image(Box::new(image( "", &[("fig-alt", "Two words")], - ))])]); + )))])]); let Block::Paragraph(p) = &mut out[0] else { panic!() }; @@ -302,19 +302,19 @@ mod tests { #[test] fn alt_key_value_is_folded_like_pandoc() { - let mut out = run(vec![para(vec![Inline::Image(image( + let mut out = run(vec![para(vec![Inline::Image(Box::new(image( "", &[("alt", "KV A")], - ))])]); + )))])]); assert_eq!(images(&mut out), vec![("KV A".into(), vec![])]); } #[test] fn fig_alt_beats_alt_and_both_are_removed() { - let mut out = run(vec![para(vec![Inline::Image(image( + let mut out = run(vec![para(vec![Inline::Image(Box::new(image( "", &[("alt", "loses"), ("fig-alt", "wins")], - ))])]); + )))])]); assert_eq!(images(&mut out), vec![("wins".into(), vec![])]); } @@ -368,14 +368,14 @@ mod tests { alignment: Alignment::Default, row_span: 1, col_span: 1, - content: vec![plain(vec![Inline::Image(image( + content: vec![plain(vec![Inline::Image(Box::new(image( "", &[("fig-alt", "In cell")], - ))])], + )))])], 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,12 +410,12 @@ mod tests { }, source_info: si(), attr_source: AttrSourceInfo::empty(), - }); + })); let note = para(vec![Inline::Note(Note { - content: vec![para(vec![Inline::Image(image( + content: vec![para(vec![Inline::Image(Box::new(image( "", &[("fig-alt", "In note")], - ))])], + )))])], source_info: si(), })]); let mut out = run(vec![table, note]); @@ -430,7 +430,7 @@ mod tests { let mut out = run(vec![float( "fig", &[("fig-alt", "Div alt")], - vec![plain(vec![Inline::Image(image("", &[]))])], + vec![plain(vec![Inline::Image(Box::new(image("", &[])))])], "Div caption", )]); let Block::Custom(node) = &out[0] else { @@ -446,8 +446,8 @@ mod tests { "fig", &[("fig-alt", "Float")], vec![plain(vec![ - Inline::Image(image("", &[("fig-alt", "Own")])), - Inline::Image(image("", &[])), + Inline::Image(Box::new(image("", &[("fig-alt", "Own")]))), + Inline::Image(Box::new(image("", &[]))), ])], "cap", )]); @@ -462,7 +462,7 @@ mod tests { let sub = float( "fig", &[], - vec![plain(vec![Inline::Image(image("", &[]))])], + vec![plain(vec![Inline::Image(Box::new(image("", &[])))])], "(a)", ); let mut out = run(vec![float( @@ -482,7 +482,10 @@ mod tests { }; n.set_slot( "caption_long", - Slot::Blocks(vec![plain(vec![Inline::Image(image("icon", &[]))])]), + Slot::Blocks(vec![plain(vec![Inline::Image(Box::new(image( + "icon", + &[], + )))])]), ); let mut out = run(vec![node]); assert_eq!(images(&mut out), vec![("icon".into(), vec![])]); @@ -493,7 +496,7 @@ mod tests { let mut out = run(vec![float( "tbl", &[("fig-alt", "Not mine")], - vec![plain(vec![Inline::Image(image("", &[]))])], + vec![plain(vec![Inline::Image(Box::new(image("", &[])))])], "cap", )]); let Block::Custom(node) = &out[0] else { 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 0995e31e8d..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 @@ -681,7 +681,7 @@ impl Walker<'_> { let Inline::Image(img) = std::mem::replace(inline, placeholder) else { unreachable!("matched an Image above"); }; - *inline = Inline::Link(self.wrap(img)); + *inline = Inline::Link(Box::new(self.wrap(*img))); } } // A footnote's body is not part of the figure (but an image @@ -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};")); @@ -751,7 +751,7 @@ impl Walker<'_> { Link { attr: (String::new(), vec![LIGHTBOX.to_string()], kvs), target: (img.target.0.clone(), title.unwrap_or_default()), - content: vec![Inline::Image(img)], + content: vec![Inline::Image(Box::new(img))], source_info, attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), @@ -1176,7 +1176,7 @@ mod tests { fn image_para(src: &str, alt: &str) -> Block { Block::Plain(Plain { - content: vec![Inline::Image(Image { + content: vec![Inline::Image(Box::new(Image { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![Inline::Str(Str { text: alt.to_string(), @@ -1186,7 +1186,7 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: SourceInfo::for_test(), }) } @@ -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_external.rs b/crates/quarto-core/src/transforms/link_external.rs index 7d161419ed..6d237368de 100644 --- a/crates/quarto-core/src/transforms/link_external.rs +++ b/crates/quarto-core/src/transforms/link_external.rs @@ -285,7 +285,7 @@ fn is_localhost(rest: &str) -> bool { fn icon_span(name: &str, link: &Link) -> Inline { let mut kv = hashlink::LinkedHashMap::new(); kv.insert("aria-hidden".to_string(), "true".to_string()); - Inline::Span(Span { + Inline::Span(Box::new(Span { attr: ( String::new(), vec![ @@ -298,7 +298,7 @@ fn icon_span(name: &str, link: &Link) -> Inline { content: Vec::new(), source_info: link.source_info.clone(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn invalid_filter_warning( @@ -359,7 +359,7 @@ mod tests { } fn link(href: &str, classes: &[&str], kv: &[(&str, &str)]) -> Inline { - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: ( String::new(), classes.iter().map(|c| c.to_string()).collect(), @@ -375,7 +375,7 @@ mod tests { source_info: si(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn para(content: Vec) -> Vec { diff --git a/crates/quarto-core/src/transforms/link_rewrite.rs b/crates/quarto-core/src/transforms/link_rewrite.rs index a00654157a..9e997f125c 100644 --- a/crates/quarto-core/src/transforms/link_rewrite.rs +++ b/crates/quarto-core/src/transforms/link_rewrite.rs @@ -588,7 +588,7 @@ mod tests { } fn link_inline(url: &str, text: &str) -> Inline { - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: Attr::default(), content: vec![Inline::Str(Str { text: text.to_string(), @@ -598,14 +598,14 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } /// Like `link_inline`, but stamps `target_source.url` with a real /// `SourceInfo` so tests can assert that diagnostics receive the /// link URL's source location. fn link_inline_with_url_source(url: &str, text: &str, url_source: SourceInfo) -> Inline { - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: Attr::default(), content: vec![Inline::Str(Str { text: text.to_string(), @@ -618,11 +618,11 @@ mod tests { url: Some(url_source), title: None, }, - }) + })) } fn image_inline(url: &str, alt: &str) -> Inline { - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: Attr::default(), content: vec![Inline::Str(Str { text: alt.to_string(), @@ -632,7 +632,7 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn para(content: Vec) -> Block { @@ -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 @@ -1254,7 +1254,7 @@ mod tests { for (k, v) in kv { map.insert(k.to_string(), v.to_string()); } - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: (String::new(), vec![], map), content: vec![Inline::Str(Str { text: text.to_string(), @@ -1264,7 +1264,7 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } /// Like [`run`], but with control over the llms view: when 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 751a71e2a9..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; } @@ -551,26 +551,26 @@ fn synthesize_listing_list( ItemTarget::None => title, ItemTarget::Href(raw_href) => { let href = retarget_href(raw_href, cx); - vec![Inline::Link(quarto_pandoc_types::inline::Link { + vec![Inline::Link(Box::new(quarto_pandoc_types::inline::Link { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: title, target: (href, String::new()), source_info: gen_si(), attr_source: AttrSourceInfo::empty(), target_source: quarto_pandoc_types::TargetSourceInfo::empty(), - })] + }))] } ItemTarget::Document { output_href, .. } => { let based = relativize(&cx.cur_dir, output_href); let href = retarget_href(&based, cx); - vec![Inline::Link(quarto_pandoc_types::inline::Link { + vec![Inline::Link(Box::new(quarto_pandoc_types::inline::Link { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: title, target: (href, String::new()), source_info: gen_si(), attr_source: AttrSourceInfo::empty(), target_source: quarto_pandoc_types::TargetSourceInfo::empty(), - })] + }))] } }; let meta_bits: Vec<&str> = [item.date.as_deref(), item.author.as_deref()] @@ -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). @@ -1450,7 +1450,7 @@ mod tests { for (k, v) in kv { map.insert(k.to_string(), v.to_string()); } - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: (String::new(), vec![], map), content: vec![Inline::Str(Str { text: "x".to_string(), @@ -1460,7 +1460,7 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn para(content: Vec) -> Block { @@ -1481,7 +1481,7 @@ mod tests { if let Some(content) = content { for i in content { if let Inline::Link(l) = i { - links.push(l); + links.push(&**l); } } } 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 efaf2e0c00..044a09c1ed 100644 --- a/crates/quarto-core/src/transforms/metadata_normalize.rs +++ b/crates/quarto-core/src/transforms/metadata_normalize.rs @@ -660,84 +660,90 @@ mod tests { #[tokio::test] async fn test_inlines_quoted_single() { - let inlines = vec![Inline::Quoted(quarto_pandoc_types::inline::Quoted { - quote_type: quarto_pandoc_types::inline::QuoteType::SingleQuote, - content: vec![Inline::Str(Str { - text: "quoted".to_string(), + let inlines = vec![Inline::Quoted(Box::new( + quarto_pandoc_types::inline::Quoted { + quote_type: quarto_pandoc_types::inline::QuoteType::SingleQuote, + content: vec![Inline::Str(Str { + text: "quoted".to_string(), + source_info: dummy_source_info(), + })], source_info: dummy_source_info(), - })], - source_info: dummy_source_info(), - })]; + }, + ))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "'quoted'"); } #[tokio::test] async fn test_inlines_quoted_double() { - let inlines = vec![Inline::Quoted(quarto_pandoc_types::inline::Quoted { - quote_type: quarto_pandoc_types::inline::QuoteType::DoubleQuote, - content: vec![Inline::Str(Str { - text: "quoted".to_string(), + let inlines = vec![Inline::Quoted(Box::new( + quarto_pandoc_types::inline::Quoted { + quote_type: quarto_pandoc_types::inline::QuoteType::DoubleQuote, + content: vec![Inline::Str(Str { + text: "quoted".to_string(), + source_info: dummy_source_info(), + })], source_info: dummy_source_info(), - })], - source_info: dummy_source_info(), - })]; + }, + ))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "\"quoted\""); } #[tokio::test] async fn test_inlines_cite() { - let inlines = vec![Inline::Cite(quarto_pandoc_types::inline::Cite { + let inlines = vec![Inline::Cite(Box::new(quarto_pandoc_types::inline::Cite { citations: vec![], content: vec![Inline::Str(Str { text: "citation".to_string(), source_info: dummy_source_info(), })], source_info: dummy_source_info(), - })]; + }))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "citation"); } #[tokio::test] async fn test_inlines_code() { - let inlines = vec![Inline::Code(quarto_pandoc_types::inline::Code { + let inlines = vec![Inline::Code(Box::new(quarto_pandoc_types::inline::Code { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), text: "code()".to_string(), source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "code()"); } #[tokio::test] async fn test_inlines_math() { - let inlines = vec![Inline::Math(quarto_pandoc_types::inline::Math { + let inlines = vec![Inline::Math(Box::new(quarto_pandoc_types::inline::Math { math_type: quarto_pandoc_types::inline::MathType::InlineMath, text: "E=mc^2".to_string(), source_info: dummy_source_info(), text_source: None, - })]; + }))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "E=mc^2"); } #[tokio::test] async fn test_inlines_raw_inline() { - let inlines = vec![Inline::RawInline(quarto_pandoc_types::inline::RawInline { - format: "html".to_string(), - text: "bold".to_string(), - source_info: dummy_source_info(), - })]; + let inlines = vec![Inline::RawInline(Box::new( + quarto_pandoc_types::inline::RawInline { + format: "html".to_string(), + text: "bold".to_string(), + source_info: dummy_source_info(), + }, + ))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "bold"); } #[tokio::test] async fn test_inlines_link() { - let inlines = vec![Inline::Link(quarto_pandoc_types::inline::Link { + let inlines = vec![Inline::Link(Box::new(quarto_pandoc_types::inline::Link { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![Inline::Str(Str { text: "link text".to_string(), @@ -747,31 +753,33 @@ mod tests { source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), target_source: quarto_pandoc_types::attr::TargetSourceInfo::empty(), - })]; + }))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "link text"); } #[tokio::test] async fn test_inlines_image() { - let inlines = vec![Inline::Image(quarto_pandoc_types::inline::Image { - attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), - content: vec![Inline::Str(Str { - text: "alt text".to_string(), + let inlines = vec![Inline::Image(Box::new( + quarto_pandoc_types::inline::Image { + attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), + content: vec![Inline::Str(Str { + text: "alt text".to_string(), + source_info: dummy_source_info(), + })], + target: ("image.png".to_string(), String::new()), source_info: dummy_source_info(), - })], - target: ("image.png".to_string(), String::new()), - source_info: dummy_source_info(), - attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - target_source: quarto_pandoc_types::attr::TargetSourceInfo::empty(), - })]; + attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), + target_source: quarto_pandoc_types::attr::TargetSourceInfo::empty(), + }, + ))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "alt text"); } #[tokio::test] async fn test_inlines_span() { - let inlines = vec![Inline::Span(quarto_pandoc_types::inline::Span { + let inlines = vec![Inline::Span(Box::new(quarto_pandoc_types::inline::Span { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![Inline::Str(Str { text: "span content".to_string(), @@ -779,59 +787,65 @@ mod tests { })], source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + }))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "span content"); } #[tokio::test] async fn test_inlines_insert() { - let inlines = vec![Inline::Insert(quarto_pandoc_types::inline::Insert { - attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), - content: vec![Inline::Str(Str { - text: "inserted".to_string(), + let inlines = vec![Inline::Insert(Box::new( + quarto_pandoc_types::inline::Insert { + attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), + content: vec![Inline::Str(Str { + text: "inserted".to_string(), + source_info: dummy_source_info(), + })], source_info: dummy_source_info(), - })], - source_info: dummy_source_info(), - attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), + }, + ))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "inserted"); } #[tokio::test] async fn test_inlines_delete() { - let inlines = vec![Inline::Delete(quarto_pandoc_types::inline::Delete { - attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), - content: vec![Inline::Str(Str { - text: "deleted".to_string(), + let inlines = vec![Inline::Delete(Box::new( + quarto_pandoc_types::inline::Delete { + attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), + content: vec![Inline::Str(Str { + text: "deleted".to_string(), + source_info: dummy_source_info(), + })], source_info: dummy_source_info(), - })], - source_info: dummy_source_info(), - attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), + }, + ))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "deleted"); } #[tokio::test] async fn test_inlines_highlight() { - let inlines = vec![Inline::Highlight(quarto_pandoc_types::inline::Highlight { - attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), - content: vec![Inline::Str(Str { - text: "highlighted".to_string(), + let inlines = vec![Inline::Highlight(Box::new( + quarto_pandoc_types::inline::Highlight { + attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), + content: vec![Inline::Str(Str { + text: "highlighted".to_string(), + source_info: dummy_source_info(), + })], source_info: dummy_source_info(), - })], - source_info: dummy_source_info(), - attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - })]; + attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), + }, + ))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "highlighted"); } #[tokio::test] async fn test_inlines_edit_comment() { - let inlines = vec![Inline::EditComment( + let inlines = vec![Inline::EditComment(Box::new( quarto_pandoc_types::inline::EditComment { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![Inline::Str(Str { @@ -841,7 +855,7 @@ mod tests { source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), }, - )]; + ))]; let text = inlines_to_plain_text(&inlines); assert_eq!(text, "comment"); } @@ -870,13 +884,13 @@ mod tests { text: "before".to_string(), source_info: dummy_source_info(), }), - Inline::Shortcode(quarto_pandoc_types::shortcode::Shortcode { + Inline::Shortcode(Box::new(quarto_pandoc_types::shortcode::Shortcode { name: "test".to_string(), is_escaped: false, positional_args: vec![], keyword_args: hashlink::LinkedHashMap::new(), source_info: dummy_source_info(), - }), + })), Inline::Str(Str { text: "after".to_string(), source_info: dummy_source_info(), @@ -936,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() {}"); } @@ -949,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
    "); } @@ -976,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 { @@ -989,7 +1003,7 @@ mod tests { })], ], source_info: dummy_source_info(), - })]; + }))]; let text = blocks_to_plain_text(&blocks); assert_eq!(text, "item1\nitem2\n"); } @@ -1034,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"); } @@ -1048,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")], @@ -1056,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"); } @@ -1067,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")]), @@ -1097,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"); } @@ -1106,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")]), @@ -1116,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/navbar_render.rs b/crates/quarto-core/src/transforms/navbar_render.rs index 052be81af9..bb5a8b1696 100644 --- a/crates/quarto-core/src/transforms/navbar_render.rs +++ b/crates/quarto-core/src/transforms/navbar_render.rs @@ -397,7 +397,7 @@ mod tests { fn nav_image(url: &str, alt: &str) -> quarto_pandoc_types::inline::Inline { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; use quarto_pandoc_types::inline::{Image, Inline, Str}; - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: Default::default(), content: vec![Inline::Str(Str { text: alt.to_string(), @@ -407,13 +407,13 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn nav_link(url: &str, text: &str) -> quarto_pandoc_types::inline::Inline { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; use quarto_pandoc_types::inline::{Inline, Link, Str}; - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: Default::default(), content: vec![Inline::Str(Str { text: text.to_string(), @@ -423,7 +423,7 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } /// Like `run_with`, but wires a website resolver pinned at a @@ -1091,7 +1091,7 @@ mod tests { let title_cv = ConfigValue { value: quarto_pandoc_types::config_value::ConfigValueKind::PandocInlines(vec![ - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: Default::default(), content: vec![Inline::Str(Str { text: "Brand".to_string(), @@ -1101,7 +1101,7 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }), + })), ]), source_info: SourceInfo::for_test(), merge_op: Default::default(), diff --git a/crates/quarto-core/src/transforms/navigation_href.rs b/crates/quarto-core/src/transforms/navigation_href.rs index 3e0d6b66a8..a30eafcfd1 100644 --- a/crates/quarto-core/src/transforms/navigation_href.rs +++ b/crates/quarto-core/src/transforms/navigation_href.rs @@ -955,27 +955,27 @@ mod tests { fn test_image(url: &str) -> quarto_pandoc_types::inline::Inline { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; use quarto_pandoc_types::inline::{Image, Inline}; - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: Default::default(), content: vec![], target: (url.to_string(), String::new()), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn test_link(url: &str) -> quarto_pandoc_types::inline::Inline { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; use quarto_pandoc_types::inline::{Inline, Link}; - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: Default::default(), content: vec![], target: (url.to_string(), String::new()), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn plain( @@ -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 592d3a6e24..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 { @@ -178,11 +178,11 @@ fn tabset_source() -> SourceInfo { } fn raw_html(text: String) -> Inline { - Inline::RawInline(RawInline { + Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text, source_info: tabset_source(), - }) + })) } /// Resolve one Tabset CustomNode to the Bootstrap Div structure, @@ -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/resource_collector.rs b/crates/quarto-core/src/transforms/resource_collector.rs index 8f626e0c0f..2b236eae62 100644 --- a/crates/quarto-core/src/transforms/resource_collector.rs +++ b/crates/quarto-core/src/transforms/resource_collector.rs @@ -578,14 +578,14 @@ mod tests { /// Build an `Image` inline with the given target URL. fn image(url: &str) -> Inline { - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![], target: (url.to_string(), String::new()), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn para(inlines: Vec) -> Block { @@ -666,7 +666,7 @@ mod tests { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Image(Image { + content: vec![Inline::Image(Box::new(Image { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![Inline::Str(Str { text: "alt text".to_string(), @@ -676,7 +676,7 @@ mod tests { source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: dummy_source_info(), })], }; @@ -713,14 +713,14 @@ mod tests { #[tokio::test] async fn test_dedupes_repeated_urls() { let image = |url: &str| { - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![], target: (url.to_string(), String::new()), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) }; let mut ast = Pandoc { @@ -797,14 +797,14 @@ mod tests { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Image(Image { + content: vec![Inline::Image(Box::new(Image { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![], target: ("https://example.com/image.png".to_string(), String::new()), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: dummy_source_info(), })], }; @@ -828,14 +828,14 @@ mod tests { let mut ast = Pandoc { meta: quarto_pandoc_types::ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Image(Image { + content: vec![Inline::Image(Box::new(Image { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![], target: ("data:image/png;base64,abc123".to_string(), String::new()), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - })], + }))], source_info: dummy_source_info(), })], }; diff --git a/crates/quarto-core/src/transforms/responsive_image.rs b/crates/quarto-core/src/transforms/responsive_image.rs index e4cccb1c99..3a05f0e38a 100644 --- a/crates/quarto-core/src/transforms/responsive_image.rs +++ b/crates/quarto-core/src/transforms/responsive_image.rs @@ -276,14 +276,14 @@ mod tests { } fn image(classes: &[&str], kvs: &[(&str, &str)]) -> Inline { - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: attr(classes, kvs), content: vec![], target: ("wide.png".to_string(), String::new()), source_info: si(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn para(inlines: Vec) -> Block { @@ -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![ @@ -528,25 +528,25 @@ mod tests { content: vec![img()], source_info: si(), }), - Inline::Quoted(Quoted { + Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::DoubleQuote, content: vec![img()], source_info: si(), - }), - Inline::Span(Span { + })), + Inline::Span(Box::new(Span { attr: attr(&[], &[]), content: vec![img()], source_info: si(), attr_source: AttrSourceInfo::empty(), - }), - Inline::Link(Link { + })), + Inline::Link(Box::new(Link { attr: attr(&[], &[]), content: vec![img()], target: ("x".to_string(), String::new()), source_info: si(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }), + })), Inline::Note(Note { content: vec![para(vec![img()])], source_info: si(), @@ -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 1d96fbcfaf..6ad9a4e5ce 100644 --- a/crates/quarto-core/src/transforms/shortcode_resolve.rs +++ b/crates/quarto-core/src/transforms/shortcode_resolve.rs @@ -36,8 +36,8 @@ use quarto_pandoc_types::block::{ }; use quarto_pandoc_types::config_value::{ConfigValue, ConfigValueKind}; use quarto_pandoc_types::inline::{ - Cite, Delete, EditComment, Emph, Highlight, Image, Inline, Insert, Link, Note, Quoted, - SmallCaps, Span, Str, Strikeout, Strong, Subscript, Superscript, Underline, + Emph, Image, Inline, Inlines, Link, Note, SmallCaps, Span, Str, Strikeout, Strong, Subscript, + Superscript, Underline, }; use quarto_pandoc_types::pandoc::Pandoc; use quarto_pandoc_types::shortcode::{Shortcode, ShortcodeArg}; @@ -926,6 +926,13 @@ fn stamp_shortcode_anchors( } } +/// [`stamp_inline`] over a list. +fn stamp_inlines(inlines: &mut Inlines, name: &str, token_arc: &Arc) { + for child in inlines.iter_mut() { + stamp_inline(child, name, token_arc); + } +} + /// Stamp the Invocation anchor on a single inline and recurse into its children. fn stamp_inline(inline: &mut Inline, name: &str, token_arc: &Arc) { let new_si = enrich_or_create(inline.source_info(), name, token_arc); @@ -937,20 +944,16 @@ fn stamp_inline(inline: &mut Inline, name: &str, token_arc: &Arc) { | Inline::Strikeout(Strikeout { content, .. }) | Inline::Superscript(Superscript { content, .. }) | Inline::Subscript(Subscript { content, .. }) - | Inline::SmallCaps(SmallCaps { content, .. }) - | Inline::Insert(Insert { content, .. }) - | Inline::Delete(Delete { content, .. }) - | Inline::Highlight(Highlight { content, .. }) - | Inline::Quoted(Quoted { content, .. }) - | Inline::Cite(Cite { content, .. }) - | Inline::Link(Link { content, .. }) - | Inline::Image(Image { content, .. }) - | Inline::Span(Span { content, .. }) - | Inline::EditComment(EditComment { content, .. }) => { - for child in content.iter_mut() { - stamp_inline(child, name, token_arc); - } - } + | Inline::SmallCaps(SmallCaps { content, .. }) => stamp_inlines(content, name, token_arc), + Inline::Insert(n) => stamp_inlines(&mut n.content, name, token_arc), + Inline::Delete(n) => stamp_inlines(&mut n.content, name, token_arc), + Inline::Highlight(n) => stamp_inlines(&mut n.content, name, token_arc), + Inline::Quoted(n) => stamp_inlines(&mut n.content, name, token_arc), + Inline::Cite(n) => stamp_inlines(&mut n.content, name, token_arc), + Inline::Link(n) => stamp_inlines(&mut n.content, name, token_arc), + Inline::Image(n) => stamp_inlines(&mut n.content, name, token_arc), + Inline::Span(n) => stamp_inlines(&mut n.content, name, token_arc), + Inline::EditComment(n) => stamp_inlines(&mut n.content, name, token_arc), Inline::Note(Note { content, .. }) => { for child in content.iter_mut() { stamp_block(child, name, token_arc); @@ -1009,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); } @@ -1019,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); @@ -1039,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); } @@ -1056,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); @@ -1784,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, @@ -1804,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; } @@ -1822,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; @@ -1833,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, @@ -1850,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; @@ -2133,32 +2151,50 @@ fn recurse_inline<'a>( | Inline::Strikeout(Strikeout { content, .. }) | Inline::Superscript(Superscript { content, .. }) | Inline::Subscript(Subscript { content, .. }) - | Inline::SmallCaps(SmallCaps { content, .. }) - | Inline::Insert(Insert { content, .. }) - | Inline::Delete(Delete { content, .. }) - | Inline::Highlight(Highlight { content, .. }) => { + | Inline::SmallCaps(SmallCaps { content, .. }) => { resolve_inlines(content, transform, metadata, diagnostics, lua_engine).await; } - Inline::Quoted(Quoted { content, .. }) => { - resolve_inlines(content, transform, metadata, diagnostics, lua_engine).await; + Inline::Insert(n) => { + resolve_inlines(&mut n.content, transform, metadata, diagnostics, lua_engine).await; } - Inline::Cite(Cite { content, .. }) => { - resolve_inlines(content, transform, metadata, diagnostics, lua_engine).await; + Inline::Delete(n) => { + resolve_inlines(&mut n.content, transform, metadata, diagnostics, lua_engine).await; } - Inline::Link(Link { - attr, - content, - target, - source_info, - .. - }) - | Inline::Image(Image { - attr, - content, - target, - source_info, - .. - }) => { + Inline::Highlight(n) => { + resolve_inlines(&mut n.content, transform, metadata, diagnostics, lua_engine).await; + } + Inline::Quoted(n) => { + resolve_inlines(&mut n.content, transform, metadata, diagnostics, lua_engine).await; + } + Inline::Cite(n) => { + resolve_inlines(&mut n.content, transform, metadata, diagnostics, lua_engine).await; + } + Inline::Link(_) | Inline::Image(_) => { + // `Link` and `Image` are boxed in `Inline`, so one or-pattern + // cannot destructure both; pick the fields apart here. + let (attr, content, target, source_info) = match inline { + Inline::Link(link) => { + let Link { + attr, + content, + target, + source_info, + .. + } = &mut **link; + (attr, content, target, source_info) + } + Inline::Image(image) => { + let Image { + attr, + content, + target, + source_info, + .. + } = &mut **image; + (attr, content, target, source_info) + } + _ => unreachable!("outer arm matched Link or Image"), + }; expand_attr_value_shortcodes( attr, transform, @@ -2184,12 +2220,13 @@ fn recurse_inline<'a>( Inline::Note(Note { content, .. }) => { resolve_blocks(content, transform, metadata, diagnostics, lua_engine).await; } - Inline::Span(Span { - attr, - content, - source_info, - .. - }) => { + Inline::Span(span) => { + let Span { + attr, + content, + source_info, + .. + } = &mut **span; expand_attr_value_shortcodes( attr, transform, @@ -2201,8 +2238,8 @@ fn recurse_inline<'a>( .await; resolve_inlines(content, transform, metadata, diagnostics, lua_engine).await; } - Inline::EditComment(EditComment { content, .. }) => { - resolve_inlines(content, transform, metadata, diagnostics, lua_engine).await; + Inline::EditComment(n) => { + resolve_inlines(&mut n.content, transform, metadata, diagnostics, lua_engine).await; } Inline::Custom(custom) => { // Resolve shortcodes in custom inline node slots @@ -2829,7 +2866,7 @@ mod tests { text: "Title: ".to_string(), source_info: dummy_source_info(), }), - Inline::Shortcode(make_shortcode("meta", vec!["title"])), + Inline::Shortcode(Box::new(make_shortcode("meta", vec!["title"]))), ], source_info: dummy_source_info(), })], @@ -2873,7 +2910,10 @@ mod tests { dummy_source_info(), ), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode("meta", vec!["missing"]))], + content: vec![Inline::Shortcode(Box::new(make_shortcode( + "meta", + vec!["missing"], + )))], source_info: dummy_source_info(), })], }; @@ -2948,7 +2988,10 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode("block-test", vec![]))], + content: vec![Inline::Shortcode(Box::new(make_shortcode( + "block-test", + vec![], + )))], source_info: dummy_source_info(), })], }; @@ -2990,7 +3033,7 @@ mod tests { text: "Before: ".to_string(), source_info: dummy_source_info(), }), - Inline::Shortcode(make_shortcode("block-test", vec![])), + Inline::Shortcode(Box::new(make_shortcode("block-test", vec![]))), ], source_info: dummy_source_info(), })], @@ -3060,7 +3103,7 @@ mod tests { text: "Before: ".to_string(), source_info: dummy_source_info(), }), - Inline::Shortcode(make_shortcode("always-block", vec![])), + Inline::Shortcode(Box::new(make_shortcode("always-block", vec![]))), ], source_info: dummy_source_info(), })], @@ -3102,13 +3145,13 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(Shortcode { + content: vec![Inline::Shortcode(Box::new(Shortcode { is_escaped: true, name: "meta".to_string(), positional_args: vec![ShortcodeArg::String("title".to_string())], keyword_args: hashlink::LinkedHashMap::new(), source_info: dummy_source_info(), - })], + }))], source_info: dummy_source_info(), })], }; @@ -3165,7 +3208,7 @@ mod tests { text: "Sub ".to_string(), source_info: dummy_source_info(), }), - Inline::Shortcode(make_shortcode("meta", vec![key])), + Inline::Shortcode(Box::new(make_shortcode("meta", vec![key]))), ]), source_info: dummy_source_info(), merge_op: Default::default(), @@ -3322,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(), @@ -3331,7 +3374,7 @@ mod tests { text: text.to_string(), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }) + })) } fn code_text(block: &Block) -> &str { @@ -3414,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()], @@ -3423,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 { @@ -3578,7 +3621,7 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode("hello", vec![]))], + content: vec![Inline::Shortcode(Box::new(make_shortcode("hello", vec![])))], source_info: dummy_source_info(), })], }; @@ -3630,7 +3673,7 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode("read", vec![]))], + content: vec![Inline::Shortcode(Box::new(make_shortcode("read", vec![])))], source_info: dummy_source_info(), })], }; @@ -3688,7 +3731,7 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode("greet", vec![]))], + content: vec![Inline::Shortcode(Box::new(make_shortcode("greet", vec![])))], source_info: dummy_source_info(), })], }; @@ -3744,7 +3787,10 @@ mod tests { let mut ast = Pandoc { meta, blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode("meta", vec!["title"]))], + content: vec![Inline::Shortcode(Box::new(make_shortcode( + "meta", + vec!["title"], + )))], source_info: dummy_source_info(), })], }; @@ -3784,7 +3830,10 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode("nonexistent", vec![]))], + content: vec![Inline::Shortcode(Box::new(make_shortcode( + "nonexistent", + vec![], + )))], source_info: dummy_source_info(), })], }; @@ -3839,7 +3888,7 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode("break", vec![]))], + content: vec![Inline::Shortcode(Box::new(make_shortcode("break", vec![])))], source_info: dummy_source_info(), })], }; @@ -3884,7 +3933,10 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode("pagebreak", vec![]))], + content: vec![Inline::Shortcode(Box::new(make_shortcode( + "pagebreak", + vec![], + )))], source_info: dummy_source_info(), })], }; @@ -3941,11 +3993,11 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode_with_si( + content: vec![Inline::Shortcode(Box::new(make_shortcode_with_si( "typed", vec![], tok.clone(), - ))], + )))], source_info: dummy_source_info(), })], }; @@ -4080,11 +4132,11 @@ mod tests { dummy_source_info(), ), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode_with_si( + content: vec![Inline::Shortcode(Box::new(make_shortcode_with_si( "meta", vec!["title"], tok.clone(), - ))], + )))], source_info: dummy_source_info(), })], }; @@ -4126,11 +4178,11 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode_with_si( + content: vec![Inline::Shortcode(Box::new(make_shortcode_with_si( "multi", vec![], tok.clone(), - ))], + )))], source_info: dummy_source_info(), })], }; @@ -4185,10 +4237,10 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_escaped_shortcode_with_si( + content: vec![Inline::Shortcode(Box::new(make_escaped_shortcode_with_si( "meta", tok.clone(), - ))], + )))], source_info: dummy_source_info(), })], }; @@ -4224,11 +4276,11 @@ mod tests { let mut ast = Pandoc { meta: ConfigValue::default(), blocks: vec![Block::Paragraph(Paragraph { - content: vec![Inline::Shortcode(make_shortcode_with_si( + content: vec![Inline::Shortcode(Box::new(make_shortcode_with_si( "bogus", vec![], tok.clone(), - ))], + )))], source_info: dummy_source_info(), })], }; @@ -4276,12 +4328,12 @@ mod tests { ), blocks: vec![Block::Paragraph(Paragraph { content: vec![ - Inline::Shortcode(make_shortcode_with_si( + Inline::Shortcode(Box::new(make_shortcode_with_si( "meta", vec!["title"], tok.clone(), - )), - Inline::Shortcode(make_shortcode_with_si("multi", vec![], tok)), + ))), + Inline::Shortcode(Box::new(make_shortcode_with_si("multi", vec![], tok))), ], source_info: dummy_source_info(), })], @@ -4322,8 +4374,16 @@ mod tests { ), blocks: vec![Block::Paragraph(Paragraph { content: vec![ - Inline::Shortcode(make_shortcode_with_si("meta", vec!["title"], tok.clone())), - Inline::Shortcode(make_shortcode_with_si("multi", vec![], tok.clone())), + Inline::Shortcode(Box::new(make_shortcode_with_si( + "meta", + vec!["title"], + tok.clone(), + ))), + Inline::Shortcode(Box::new(make_shortcode_with_si( + "multi", + vec![], + tok.clone(), + ))), ], source_info: dummy_source_info(), })], diff --git a/crates/quarto-core/src/transforms/sidebar_render.rs b/crates/quarto-core/src/transforms/sidebar_render.rs index 817b4720f6..4ba6de0429 100644 --- a/crates/quarto-core/src/transforms/sidebar_render.rs +++ b/crates/quarto-core/src/transforms/sidebar_render.rs @@ -527,7 +527,7 @@ mod tests { fn nav_image(url: &str, alt: &str) -> quarto_pandoc_types::inline::Inline { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; use quarto_pandoc_types::inline::{Image, Inline, Str}; - Inline::Image(Image { + Inline::Image(Box::new(Image { attr: Default::default(), content: vec![Inline::Str(Str { text: alt.to_string(), @@ -537,13 +537,13 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } fn nav_link(url: &str, text: &str) -> quarto_pandoc_types::inline::Inline { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; use quarto_pandoc_types::inline::{Inline, Link, Str}; - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: Default::default(), content: vec![Inline::Str(Str { text: text.to_string(), @@ -553,7 +553,7 @@ mod tests { source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } /// Defect 2 of bd-page-footer-image-items-stmpikgo, sidebar 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 ef88d75d81..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. @@ -342,12 +342,12 @@ mod tests { text: "Branding with ".to_string(), source_info: dummy_source_info(), }), - Inline::Code(Code { + Inline::Code(Box::new(Code { attr: empty_attr(), text: "_brand.yml".to_string(), source_info: dummy_source_info(), attr_source: AttrSourceInfo::empty(), - }), + })), ]; let mut ast = Pandoc { meta: ConfigValue::new_map( @@ -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/src/transforms/toc_render.rs b/crates/quarto-core/src/transforms/toc_render.rs index 186ceda495..d13824b776 100644 --- a/crates/quarto-core/src/transforms/toc_render.rs +++ b/crates/quarto-core/src/transforms/toc_render.rs @@ -225,34 +225,34 @@ pub(crate) fn strip_links_and_notes(inlines: &[Inline]) -> Inlines { content: strip_links_and_notes(&s.content), ..s.clone() })), - Inline::Quoted(q) => out.push(Inline::Quoted(Quoted { + Inline::Quoted(q) => out.push(Inline::Quoted(Box::new(Quoted { content: strip_links_and_notes(&q.content), - ..q.clone() - })), - Inline::Cite(c) => out.push(Inline::Cite(Cite { + ..*q.clone() + }))), + Inline::Cite(c) => out.push(Inline::Cite(Box::new(Cite { content: strip_links_and_notes(&c.content), - ..c.clone() - })), - Inline::Span(s) => out.push(Inline::Span(Span { + ..*c.clone() + }))), + Inline::Span(s) => out.push(Inline::Span(Box::new(Span { content: strip_links_and_notes(&s.content), - ..s.clone() - })), - Inline::Insert(i) => out.push(Inline::Insert(Insert { + ..*s.clone() + }))), + Inline::Insert(i) => out.push(Inline::Insert(Box::new(Insert { content: strip_links_and_notes(&i.content), - ..i.clone() - })), - Inline::Delete(d) => out.push(Inline::Delete(Delete { + ..*i.clone() + }))), + Inline::Delete(d) => out.push(Inline::Delete(Box::new(Delete { content: strip_links_and_notes(&d.content), - ..d.clone() - })), - Inline::Highlight(h) => out.push(Inline::Highlight(Highlight { + ..*d.clone() + }))), + Inline::Highlight(h) => out.push(Inline::Highlight(Box::new(Highlight { content: strip_links_and_notes(&h.content), - ..h.clone() - })), - Inline::EditComment(e) => out.push(Inline::EditComment(EditComment { + ..*h.clone() + }))), + Inline::EditComment(e) => out.push(Inline::EditComment(Box::new(EditComment { content: strip_links_and_notes(&e.content), - ..e.clone() - })), + ..*e.clone() + }))), // An Image's `content` is alt text, not rendered children — // leave the node intact so the writer emits the ``. Inline::Image(_) @@ -744,24 +744,24 @@ mod tests { text: "Using a ".to_string(), source_info: dummy_source_info(), }), - Inline::Quoted(Quoted { + Inline::Quoted(Box::new(Quoted { quote_type: QuoteType::DoubleQuote, content: vec![Inline::Str(Str { text: "raw".to_string(), source_info: dummy_source_info(), })], source_info: dummy_source_info(), - }), + })), Inline::Str(Str { text: " ".to_string(), source_info: dummy_source_info(), }), - Inline::Code(Code { + Inline::Code(Box::new(Code { text: "volume".to_string(), attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), - }), + })), ]; let html = render_toc_label(&title); @@ -783,7 +783,7 @@ mod tests { text: "See ".to_string(), source_info: dummy_source_info(), }), - Inline::Link(Link { + Inline::Link(Box::new(Link { attr: (String::new(), vec![], hashlink::LinkedHashMap::new()), content: vec![Inline::Str(Str { text: "the docs".to_string(), @@ -793,7 +793,7 @@ mod tests { source_info: dummy_source_info(), attr_source: quarto_pandoc_types::attr::AttrSourceInfo::empty(), target_source: quarto_pandoc_types::attr::TargetSourceInfo::empty(), - }), + })), Inline::Note(Note { content: vec![], source_info: dummy_source_info(), diff --git a/crates/quarto-core/src/typst_citations.rs b/crates/quarto-core/src/typst_citations.rs index c635f7e314..75f2a0c47e 100644 --- a/crates/quarto-core/src/typst_citations.rs +++ b/crates/quarto-core/src/typst_citations.rs @@ -226,7 +226,7 @@ mod tests { } fn cite(keys: &[&str]) -> Inline { - Inline::Cite(Cite { + Inline::Cite(Box::new(Cite { citations: keys .iter() .map(|k| Citation { @@ -241,7 +241,7 @@ mod tests { .collect(), content: vec![], source_info: si(), - }) + })) } fn doc(inlines: Vec) -> Vec { 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 df6c2dda1a..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 { @@ -111,7 +111,7 @@ fn leaves_existing_annotation_alone() { #[test] fn annotates_inline_code() { use hashlink::LinkedHashMap; - let inline = Inline::Code(Code { + let inline = Inline::Code(Box::new(Code { attr: ( String::new(), vec!["python".to_string()], @@ -120,7 +120,7 @@ fn annotates_inline_code() { text: "print(42)".to_string(), source_info: empty_source_info(), attr_source: empty_attr_source(), - }); + })); let mut doc = empty_pandoc(); doc.blocks.push(Block::Paragraph(Paragraph { content: Inlines::from(vec![inline]), @@ -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!() @@ -258,12 +258,12 @@ fn does_not_mark_inline_code_without_grammar() { use hashlink::LinkedHashMap; let mut doc = empty_pandoc(); doc.blocks.push(Block::Paragraph(Paragraph { - content: Inlines::from(vec![Inline::Code(Code { + content: Inlines::from(vec![Inline::Code(Box::new(Code { attr: (String::new(), vec!["ini".to_string()], LinkedHashMap::new()), text: "a = 1".to_string(), source_info: empty_source_info(), attr_source: empty_attr_source(), - })]), + }))]), source_info: empty_source_info(), })); annotate_pandoc(&mut doc, None).expect("annotate must not error"); 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-lsp-core/src/analysis.rs b/crates/quarto-lsp-core/src/analysis.rs index 619b6bd4ea..7ebc5ebc82 100644 --- a/crates/quarto-lsp-core/src/analysis.rs +++ b/crates/quarto-lsp-core/src/analysis.rs @@ -238,7 +238,7 @@ fn collect_symbols_from_blocks( } } // Paragraphs can contain inline crossref targets — labelled - // display equations end up as `Inline::Custom("Equation")` + // display equations end up as `Inline::Custom(Box::new("Equation"))` // inside a paragraph after `EquationLabelTransform` runs. Block::Paragraph(p) => { collect_crossref_symbols_from_inlines(&p.content, ctx, symbols); diff --git a/crates/quarto-navigation/src/render_html.rs b/crates/quarto-navigation/src/render_html.rs index 172faba3f7..a2c5b7a70b 100644 --- a/crates/quarto-navigation/src/render_html.rs +++ b/crates/quarto-navigation/src/render_html.rs @@ -1723,16 +1723,16 @@ mod tests { use quarto_pandoc_types::config_value::ConfigValueKind; use quarto_pandoc_types::inline::Image; - let image = Inline::Image(Image { + let image = Inline::Image(Box::new(Image { attr: Default::default(), content: vec![str_inline("cap")], target: ("images/logo.svg".to_string(), String::new()), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), 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(), }; @@ -1781,14 +1781,14 @@ mod tests { ) -> Inline { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; use quarto_pandoc_types::inline::Link; - Inline::Link(Link { + Inline::Link(Box::new(Link { attr, content: vec![str_inline(text)], target: (url.to_string(), target_title.to_string()), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }) + })) } /// A link's id/class/kv attributes reach the emitted `
    ` @@ -1834,20 +1834,20 @@ mod tests { fn attributed_span_keeps_wrapper_attrless_span_unwrapped() { use quarto_pandoc_types::attr::AttrSourceInfo; use quarto_pandoc_types::inline::Span; - let attributed = inlines_to_html(&[Inline::Span(Span { + let attributed = inlines_to_html(&[Inline::Span(Box::new(Span { attr: test_attr("spid", &["scls"], &[]), content: vec![str_inline("sp")], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - })]); + }))]); assert_eq!(attributed, "sp"); - let attrless = inlines_to_html(&[Inline::Span(Span { + let attrless = inlines_to_html(&[Inline::Span(Box::new(Span { attr: Default::default(), content: vec![str_inline("plain")], source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - })]); + }))]); assert_eq!(attrless, "plain"); } @@ -1857,12 +1857,12 @@ mod tests { use quarto_pandoc_types::attr::AttrSourceInfo; use quarto_pandoc_types::inline::Code; let code = |attr| { - Inline::Code(Code { + Inline::Code(Box::new(Code { attr, text: "x".to_string(), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }) + })) }; assert_eq!( inlines_to_html(&[code(test_attr("", &["numberLines"], &[]))]), @@ -2767,11 +2767,11 @@ mod tests { fn footer_item_with_own_anchor_does_not_nest_anchors() { use quarto_pandoc_types::inline::RawInline; let raw = |t: &str| { - Inline::RawInline(RawInline { + Inline::RawInline(Box::new(RawInline { format: "html".to_string(), text: t.to_string(), source_info: SourceInfo::for_test(), - }) + })) }; let text = ConfigValue::new_inlines( vec![ @@ -2830,23 +2830,23 @@ mod tests { fn inlines_to_html_link_and_code() { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; let inlines = vec![ - Inline::Link(quarto_pandoc_types::inline::Link { + Inline::Link(Box::new(quarto_pandoc_types::inline::Link { attr: (String::new(), vec![], Default::default()), content: vec![str_inline("site")], target: ("https://example.com".to_string(), String::new()), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }), + })), Inline::Space(quarto_pandoc_types::inline::Space { source_info: SourceInfo::for_test(), }), - Inline::Code(quarto_pandoc_types::inline::Code { + Inline::Code(Box::new(quarto_pandoc_types::inline::Code { attr: (String::new(), vec![], Default::default()), text: "x & y".to_string(), source_info: SourceInfo::for_test(), attr_source: AttrSourceInfo::empty(), - }), + })), ]; let out = inlines_to_html(&inlines); assert!(out.contains("site")); @@ -2867,14 +2867,16 @@ mod tests { attr.0 = "brand-img".to_string(); attr.1.push("footer-logo".to_string()); attr.2.insert("width".to_string(), "32".to_string()); - let inlines = vec![Inline::Image(quarto_pandoc_types::inline::Image { - attr, - content: vec![str_inline("Posit logo")], - target: ("../../images/logo.svg".to_string(), "The title".to_string()), - source_info: SourceInfo::for_test(), - attr_source: AttrSourceInfo::empty(), - target_source: TargetSourceInfo::empty(), - })]; + let inlines = vec![Inline::Image(Box::new( + quarto_pandoc_types::inline::Image { + attr, + content: vec![str_inline("Posit logo")], + target: ("../../images/logo.svg".to_string(), "The title".to_string()), + source_info: SourceInfo::for_test(), + attr_source: AttrSourceInfo::empty(), + target_source: TargetSourceInfo::empty(), + }, + ))]; let out = inlines_to_html(&inlines); assert_eq!( out, @@ -2889,14 +2891,16 @@ mod tests { #[test] fn inlines_to_html_image_minimal_and_escaped() { use quarto_pandoc_types::attr::{AttrSourceInfo, TargetSourceInfo}; - let inlines = vec![Inline::Image(quarto_pandoc_types::inline::Image { - attr: Default::default(), - content: vec![str_inline("a & b")], - target: ("x\"y.svg".to_string(), String::new()), - source_info: SourceInfo::for_test(), - attr_source: AttrSourceInfo::empty(), - target_source: TargetSourceInfo::empty(), - })]; + let inlines = vec![Inline::Image(Box::new( + quarto_pandoc_types::inline::Image { + attr: Default::default(), + content: vec![str_inline("a & b")], + target: ("x\"y.svg".to_string(), String::new()), + source_info: SourceInfo::for_test(), + attr_source: AttrSourceInfo::empty(), + target_source: TargetSourceInfo::empty(), + }, + ))]; let out = inlines_to_html(&inlines); assert_eq!(out, "\"a"); } 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); } diff --git a/crates/quarto-pandoc-types/src/inline.rs b/crates/quarto-pandoc-types/src/inline.rs index 94dac9508f..07e67669eb 100644 --- a/crates/quarto-pandoc-types/src/inline.rs +++ b/crates/quarto-pandoc-types/src/inline.rs @@ -9,6 +9,15 @@ use crate::custom::CustomNode; use crate::shortcode::Shortcode; use serde::{Deserialize, Serialize}; +/// An inline element. +/// +/// **Layout.** Every element of every `Vec` occupies +/// `size_of::()` bytes, whichever variant it holds, and most +/// inlines are `Str` and `Space`. So the variants whose payload is larger +/// than `Str`'s are boxed: they are rare, and unboxed they set the size of +/// every word and space in a document (`Link` alone made this enum 360 +/// 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 Inline { Str(Str), @@ -19,40 +28,44 @@ pub enum Inline { Superscript(Superscript), Subscript(Subscript), SmallCaps(SmallCaps), - Quoted(Quoted), - Cite(Cite), - Code(Code), + Quoted(Box), + Cite(Box), + Code(Box), Space(Space), SoftBreak(SoftBreak), LineBreak(LineBreak), - Math(Math), - RawInline(RawInline), - Link(Link), - Image(Image), + Math(Box), + RawInline(Box), + Link(Box), + Image(Box), Note(Note), - Span(Span), + Span(Box), // quarto extensions // after desugaring, these nodes should not appear in a document - Shortcode(Shortcode), + Shortcode(Box), NoteReference(NoteReference), // this is used to represent commonmark attributes in the document in places // where they are not directly attached to a block, like in headings and tables - Attr(InlineAttr), + Attr(Box), // CriticMarkup-like extensions - Insert(Insert), - Delete(Delete), - Highlight(Highlight), - EditComment(EditComment), + Insert(Box), + Delete(Box), + Highlight(Box), + EditComment(Box), /// Custom node for Quarto inline extensions /// /// Parsed from Spans with special class names. When serialized to Pandoc JSON, /// these are converted to wrapper Spans 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 `Str`'s: box it rather than raise the bound. +const _: () = assert!(std::mem::size_of::() <= 8 * std::mem::size_of::()); + impl Inline { pub fn source_info(&self) -> &quarto_source_map::SourceInfo { match self { @@ -399,7 +412,7 @@ macro_rules! impl_as_inline { $( impl AsInline for $type { fn as_inline(self) -> Inline { - Inline::$type(self) + Inline::$type(self.into()) } } )* @@ -443,7 +456,7 @@ impl_as_inline!( impl AsInline for InlineAttr { fn as_inline(self) -> Inline { - Inline::Attr(self) + Inline::Attr(Box::new(self)) } } @@ -461,14 +474,14 @@ pub fn make_span_inline( ) -> Inline { // non-empty targets are never Underline or SmallCaps if !is_empty_target(&target) { - return Inline::Link(Link { + return Inline::Link(Box::new(Link { attr, content, target, source_info, attr_source, target_source, - }); + })); } if attr.1.contains(&"smallcaps".to_string()) { let mut new_attr = attr.clone(); @@ -535,12 +548,12 @@ pub fn make_span_inline( }); } - Inline::Span(Span { + Inline::Span(Box::new(Span { attr, content, source_info, attr_source, - }) + })) } pub fn make_cite_inline( @@ -592,7 +605,7 @@ pub fn make_cite_inline( for inline in inlines { if cite.is_none() { if let Inline::Cite(c) = inline { - cite = Some(c); + cite = Some(*c); } else { prefix.push(inline); } @@ -642,11 +655,11 @@ pub fn make_cite_inline( } }) .collect(); - Inline::Cite(Cite { + Inline::Cite(Box::new(Cite { citations, content: vec![], source_info, - }) + })) } /// Split a string into Inlines, matching Pandoc's `B.text` from pandoc-types Builder.hs. @@ -771,7 +784,7 @@ mod tests { // in unsupported contexts (e.g., grid tables) // Create a Cite with two citations already in it - let multi_cite = Inline::Cite(Cite { + let multi_cite = Inline::Cite(Box::new(Cite { citations: vec![ make_citation( "knuth1984", @@ -786,7 +799,7 @@ mod tests { ], content: vec![], source_info: dummy_source_info(), - }); + })); // Now call make_cite_inline with content that includes this multi-citation Cite // along with a prefix "see" @@ -826,11 +839,11 @@ mod tests { #[test] fn test_make_cite_inline_with_single_citation_still_works() { // Test that the normal case (single citation) still works - let single_cite = Inline::Cite(Cite { + let single_cite = Inline::Cite(Box::new(Cite { citations: vec![make_citation("knuth1984", vec![], vec![])], content: vec![], source_info: dummy_source_info(), - }); + })); let content = vec![ make_str("see"), @@ -1118,16 +1131,16 @@ mod tests { #[test] fn test_make_cite_inline_with_semicolon_separator() { // Test multiple citations separated by semicolons - let cite1 = Inline::Cite(Cite { + let cite1 = Inline::Cite(Box::new(Cite { citations: vec![make_citation("ref1", vec![], vec![])], content: vec![], source_info: dummy_source_info(), - }); - let cite2 = Inline::Cite(Cite { + })); + let cite2 = Inline::Cite(Box::new(Cite { citations: vec![make_citation("ref2", vec![], vec![])], content: vec![], source_info: dummy_source_info(), - }); + })); let content = vec![cite1, make_str(";"), cite2]; @@ -1162,11 +1175,11 @@ mod tests { hash: 0, id_source: None, }; - let cite = Inline::Cite(Cite { + let cite = Inline::Cite(Box::new(Cite { citations: vec![citation], content: vec![], source_info: dummy_source_info(), - }); + })); let content = vec![cite]; @@ -1550,13 +1563,13 @@ mod tests { #[test] fn source_info_shortcode() { let si = test_si(1, 10, 30); - let inline = Inline::Shortcode(crate::shortcode::Shortcode { + let inline = Inline::Shortcode(Box::new(crate::shortcode::Shortcode { is_escaped: false, name: "include".into(), positional_args: vec![], keyword_args: hashlink::LinkedHashMap::new(), source_info: si.clone(), - }); + })); assert_eq!(inline.source_info(), &si); } @@ -1569,10 +1582,10 @@ mod tests { classes: vec![], attributes: vec![], }; - let inline = Inline::Attr(InlineAttr::new_from_attr_source( + let inline = Inline::Attr(Box::new(InlineAttr::new_from_attr_source( ("myid".into(), vec![], LinkedHashMap::new()), attr_source, - )); + ))); assert_eq!(inline.source_info(), &id_si); } @@ -1586,10 +1599,10 @@ mod tests { classes: vec![Some(class_si)], attributes: vec![], }; - let inline = Inline::Attr(InlineAttr::new_from_attr_source( + let inline = Inline::Attr(Box::new(InlineAttr::new_from_attr_source( ("myid".into(), vec!["myclass".into()], LinkedHashMap::new()), attr_source, - )); + ))); // The combined source_info should be a Concat with 2 pieces match inline.source_info() { quarto_source_map::SourceInfo::Concat { pieces } => { @@ -1602,14 +1615,14 @@ mod tests { #[test] fn source_info_link() { let si = test_si(4, 0, 30); - let inline = Inline::Link(Link { + let inline = Inline::Link(Box::new(Link { attr: (String::new(), vec![], LinkedHashMap::new()), content: vec![], target: ("url".into(), String::new()), source_info: si.clone(), attr_source: AttrSourceInfo::empty(), target_source: TargetSourceInfo::empty(), - }); + })); assert_eq!(inline.source_info(), &si); }