Skip to content

Commit ff4e817

Browse files
committed
new syntax for footnote defns in inline and block styles
1 parent a208a60 commit ff4e817

File tree

20 files changed

+19362
-17112
lines changed

20 files changed

+19362
-17112
lines changed

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ The main documentation for this repository is located at:
1313
- When a cd command fails for you, that means you're confused about the current directory. In this situations, ALWAYS run `pwd` before doing anything else.
1414
- use `jq` instead of `python3 -m json.tool` for pretty-printing. When processing JSON in a shell pipeline, prefer `jq` when possible.
1515
- Always create a plan. Always work on the plan one item at a time.
16+
- In the tree-sitter-markdown and tree-sitter-markdown-inline directories, you rebuild the parsers using "tree-sitter generate; tree-sitter build". Make sure the shell is in the correct directory before running those. Every time you change the tree-sitter parsers, rebuild them and run "tree-sitter test". If the tests fail, fix the code. Only change tree-sitter tests you've just added; do not touch any other tests. If you end up getting stuck there, stop and ask for my help.

crates/quarto-markdown-pandoc/src/filters.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,17 @@ pub fn topdown_traverse_block(block: Block, filter: &mut Filter) -> Blocks {
731731
},
732732
)]
733733
}
734+
Block::NoteDefinitionFencedBlock(refdef) => {
735+
// Process the block content of the fenced reference definition
736+
let content = topdown_traverse_blocks(refdef.content, filter);
737+
vec![Block::NoteDefinitionFencedBlock(
738+
crate::pandoc::block::NoteDefinitionFencedBlock {
739+
id: refdef.id,
740+
content,
741+
source_info: refdef.source_info,
742+
},
743+
)]
744+
}
734745
}
735746
}
736747

crates/quarto-markdown-pandoc/src/pandoc/block.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub enum Block {
3434
// quarto extensions
3535
BlockMetadata(MetaBlock),
3636
NoteDefinitionPara(NoteDefinitionPara),
37+
NoteDefinitionFencedBlock(NoteDefinitionFencedBlock),
3738
}
3839

3940
pub type Blocks = Vec<Block>;
@@ -136,6 +137,13 @@ pub struct NoteDefinitionPara {
136137
pub source_info: SourceInfo,
137138
}
138139

140+
#[derive(Debug, Clone, PartialEq)]
141+
pub struct NoteDefinitionFencedBlock {
142+
pub id: String,
143+
pub content: Blocks,
144+
pub source_info: SourceInfo,
145+
}
146+
139147
impl_source_location!(
140148
// blocks
141149
Plain,
@@ -154,7 +162,8 @@ impl_source_location!(
154162
Div,
155163
// quarto extensions
156164
MetaBlock,
157-
NoteDefinitionPara
165+
NoteDefinitionPara,
166+
NoteDefinitionFencedBlock
158167
);
159168

160169
fn make_block_leftover(node: &tree_sitter::Node, input_bytes: &[u8]) -> Block {

crates/quarto-markdown-pandoc/src/pandoc/treesitter.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::pandoc::treesitter_utils::language_attribute::process_language_attrib
2323
use crate::pandoc::treesitter_utils::latex_span::process_latex_span;
2424
use crate::pandoc::treesitter_utils::link_title::process_link_title;
2525
use crate::pandoc::treesitter_utils::list_marker::process_list_marker;
26+
use crate::pandoc::treesitter_utils::note_definition_fenced_block::process_note_definition_fenced_block;
2627
use crate::pandoc::treesitter_utils::note_definition_para::process_note_definition_para;
2728
use crate::pandoc::treesitter_utils::note_reference::process_note_reference;
2829
use crate::pandoc::treesitter_utils::numeric_character_reference::process_numeric_character_reference;
@@ -417,6 +418,7 @@ fn native_visitor<T: Write>(
417418
"language"
418419
| "note_reference_id"
419420
| "ref_id_specifier"
421+
| "fenced_div_note_id"
420422
| "citation_id_suppress_author"
421423
| "citation_id_author_in_text"
422424
| "link_destination"
@@ -459,6 +461,7 @@ fn native_visitor<T: Write>(
459461
"citation" => process_citation(node, node_text, children),
460462
"note_reference" => process_note_reference(node, children),
461463
"inline_ref_def" => process_note_definition_para(node, children),
464+
"note_definition_fenced_block" => process_note_definition_fenced_block(node, children),
462465
"shortcode" | "shortcode_escaped" => process_shortcode(node, children),
463466
"shortcode_keyword_param" => process_shortcode_keyword_param(buf, node, children),
464467
"shortcode_boolean" => process_shortcode_boolean(node, input_bytes),

crates/quarto-markdown-pandoc/src/pandoc/treesitter_utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub mod language_attribute;
2323
pub mod latex_span;
2424
pub mod link_title;
2525
pub mod list_marker;
26+
pub mod note_definition_fenced_block;
2627
pub mod note_definition_para;
2728
pub mod note_reference;
2829
pub mod numeric_character_reference;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* note_definition_fenced_block.rs
3+
*
4+
* Functions for processing note definition fenced block nodes in the tree-sitter AST.
5+
*
6+
* Copyright (c) 2025 Posit, PBC
7+
*/
8+
9+
use crate::pandoc::block::{Block, Blocks, NoteDefinitionFencedBlock};
10+
use crate::pandoc::location::node_source_info;
11+
12+
use super::pandocnativeintermediate::PandocNativeIntermediate;
13+
14+
pub fn process_note_definition_fenced_block(
15+
node: &tree_sitter::Node,
16+
children: Vec<(String, PandocNativeIntermediate)>,
17+
) -> PandocNativeIntermediate {
18+
let mut id = String::new();
19+
let mut content: Blocks = Vec::new();
20+
21+
for (node_name, child) in children {
22+
if node_name == "fenced_div_note_id" {
23+
if let PandocNativeIntermediate::IntermediateBaseText(text, _) = child {
24+
// The text includes the ^ prefix, strip it
25+
id = text.strip_prefix('^').unwrap_or(&text).to_string();
26+
} else {
27+
panic!("Expected BaseText in fenced_div_note_id, got {:?}", child);
28+
}
29+
} else if node_name == "block_continuation" {
30+
// This is a marker node, we don't need to do anything with it
31+
} else {
32+
match child {
33+
PandocNativeIntermediate::IntermediateBlock(block) => {
34+
content.push(block);
35+
}
36+
PandocNativeIntermediate::IntermediateSection(blocks) => {
37+
content.extend(blocks);
38+
}
39+
_ => {
40+
// Ignore other intermediate nodes
41+
}
42+
}
43+
}
44+
}
45+
46+
PandocNativeIntermediate::IntermediateBlock(Block::NoteDefinitionFencedBlock(
47+
NoteDefinitionFencedBlock {
48+
id,
49+
content,
50+
source_info: node_source_info(node),
51+
},
52+
))
53+
}

crates/quarto-markdown-pandoc/src/readers/json.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,37 @@ fn read_block(value: &Value) -> Result<Block> {
10781078
},
10791079
))
10801080
}
1081+
"NoteDefinitionFencedBlock" => {
1082+
let c = obj
1083+
.get("c")
1084+
.ok_or_else(|| JsonReadError::MissingField("c".to_string()))?;
1085+
let arr = c.as_array().ok_or_else(|| {
1086+
JsonReadError::InvalidType(
1087+
"NoteDefinitionFencedBlock content must be array".to_string(),
1088+
)
1089+
})?;
1090+
if arr.len() != 2 {
1091+
return Err(JsonReadError::InvalidType(
1092+
"NoteDefinitionFencedBlock array must have 2 elements".to_string(),
1093+
));
1094+
}
1095+
let id = arr[0]
1096+
.as_str()
1097+
.ok_or_else(|| {
1098+
JsonReadError::InvalidType(
1099+
"NoteDefinitionFencedBlock id must be string".to_string(),
1100+
)
1101+
})?
1102+
.to_string();
1103+
let content = read_blocks(&arr[1])?;
1104+
Ok(Block::NoteDefinitionFencedBlock(
1105+
crate::pandoc::block::NoteDefinitionFencedBlock {
1106+
id,
1107+
content,
1108+
source_info: SourceInfo::new(filename, range),
1109+
},
1110+
))
1111+
}
10811112
_ => Err(JsonReadError::UnsupportedVariant(format!("Block: {}", t))),
10821113
}
10831114
}

crates/quarto-markdown-pandoc/src/writers/json.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,11 @@ fn write_block(block: &Block) -> Value {
312312
"c": [refdef.id, write_inlines(&refdef.content)],
313313
"l": write_location(refdef),
314314
}),
315+
Block::NoteDefinitionFencedBlock(refdef) => json!({
316+
"t": "NoteDefinitionFencedBlock",
317+
"c": [refdef.id, write_blocks(&refdef.content)],
318+
"l": write_location(refdef),
319+
}),
315320
}
316321
}
317322

crates/quarto-markdown-pandoc/src/writers/qmd.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,22 @@ fn write_inlinerefdef(
565565
Ok(())
566566
}
567567

568+
fn write_fenced_note_definition(
569+
refdef: &crate::pandoc::block::NoteDefinitionFencedBlock,
570+
buf: &mut dyn std::io::Write,
571+
) -> std::io::Result<()> {
572+
writeln!(buf, "::: ^{}", refdef.id)?;
573+
for (i, block) in refdef.content.iter().enumerate() {
574+
if i > 0 {
575+
// Add a blank line between blocks
576+
writeln!(buf)?;
577+
}
578+
write_block(block, buf)?;
579+
}
580+
writeln!(buf, ":::")?;
581+
Ok(())
582+
}
583+
568584
fn write_table(table: &Table, buf: &mut dyn std::io::Write) -> std::io::Result<()> {
569585
// Collect all rows (header + body rows)
570586
let mut all_rows = Vec::new();
@@ -1120,6 +1136,9 @@ fn write_block(block: &crate::pandoc::Block, buf: &mut dyn std::io::Write) -> st
11201136
Block::NoteDefinitionPara(refdef) => {
11211137
write_inlinerefdef(refdef, buf)?;
11221138
}
1139+
Block::NoteDefinitionFencedBlock(refdef) => {
1140+
write_fenced_note_definition(refdef, buf)?;
1141+
}
11231142
}
11241143
Ok(())
11251144
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Note Definition Fenced Block - Multiple Paragraphs
2+
3+
::: ^my-note
4+
First paragraph in the note.
5+
6+
Second paragraph in the note.
7+
8+
Third paragraph in the note.
9+
:::

0 commit comments

Comments
 (0)