use crate::{
Block, BlockMetadata, DelimitedBlockType, Error, InlineNode, Paragraph, Plain,
grammar::{
ParserState,
document::{
callouts::resolve_verbatim_callouts,
delimited::{DelimitedKind, DelimitedParams},
metadata::extract_source_attributes,
},
helpers::BlockParsingMetadata,
location_walk::walk_inline_nodes_mut,
},
model::{
PositionalAttribute, Substitution,
substitution::{SubstitutionPlan, VERBATIM},
},
};
#[cfg(feature = "pre-spec-subs")]
use crate::grammar::inline_processing::process_verbatim_inlines;
pub(super) fn verbatim_inner<'input>(
state: &mut ParserState<'input>,
block_metadata: &BlockParsingMetadata<'input>,
metadata: &mut BlockMetadata<'input>,
p: &DelimitedParams<'input>,
) -> Result<DelimitedBlockType<'input>, Error> {
if let Some(language) = p.lang {
metadata.prepend_synthetic_positional_attribute(PositionalAttribute {
value: language,
substitutions: false,
location: None,
});
metadata.style = Some("source");
}
extract_source_attributes(state, metadata);
metadata.move_positional_attributes_to_attributes();
let content_location = state.create_block_location(p.content_start, p.content_end, p.offset);
let (inlines, callouts) = resolve_verbatim_callouts(
state,
p.content,
content_location,
block_metadata
.substitutions
.enabled(&Substitution::Callouts),
!metadata.attributes.contains_key("line-comment"),
);
state.pending_callouts.extend(callouts);
let inlines = resolve_verbatim_inlines(state, block_metadata, inlines)?;
Ok(if p.kind == DelimitedKind::Literal {
DelimitedBlockType::DelimitedLiteral(inlines)
} else {
DelimitedBlockType::DelimitedListing(inlines)
})
}
pub(super) fn verbatim_substitutions(metadata: &BlockParsingMetadata<'_>) -> SubstitutionPlan {
#[cfg(feature = "pre-spec-subs")]
if let Some(spec) = &metadata.metadata.substitutions {
return SubstitutionPlan::from_substitutions(&spec.resolve(VERBATIM));
}
let _ = metadata;
SubstitutionPlan::from_substitutions(VERBATIM)
}
fn needs_verbatim_inlines(substitutions: SubstitutionPlan) -> bool {
[
Substitution::Quotes,
Substitution::Attributes,
Substitution::Macros,
]
.iter()
.any(|substitution| substitutions.enabled(substitution))
}
#[cfg(feature = "pre-spec-subs")]
pub(super) fn resolve_verbatim_inlines<'a>(
state: &mut ParserState<'a>,
metadata: &BlockParsingMetadata<'_>,
inlines: Vec<InlineNode<'a>>,
) -> Result<Vec<InlineNode<'a>>, Error> {
let substitutions = verbatim_substitutions(metadata);
if !needs_verbatim_inlines(substitutions) {
return Ok(inlines);
}
let metadata = BlockParsingMetadata {
substitutions,
..BlockParsingMetadata::default()
};
let mut resolved = Vec::new();
for node in inlines {
if let InlineNode::VerbatimText(text) = node {
resolved.extend(process_verbatim_inlines(
state,
&metadata,
text.location.absolute_start,
text.content,
)?);
} else {
resolved.push(node);
}
}
Ok(resolved)
}
#[cfg(not(feature = "pre-spec-subs"))]
pub(super) fn resolve_verbatim_inlines<'a>(
_state: &mut ParserState<'a>,
_metadata: &BlockParsingMetadata<'_>,
inlines: Vec<InlineNode<'a>>,
) -> Result<Vec<InlineNode<'a>>, Error> {
Ok(inlines)
}
pub(super) fn get_literal_paragraph<'input>(
state: &mut ParserState<'input>,
content: &'input str,
start: usize,
content_start: usize,
end: usize,
offset: usize,
block_metadata: &BlockParsingMetadata<'input>,
) -> Result<Block<'input>, Error> {
tracing::debug!(
input_len = content.len(),
"paragraph starts with a space - switching to literal block"
);
let mut metadata = block_metadata.metadata.clone();
metadata.move_positional_attributes_to_attributes();
metadata.style = Some("literal");
let location = state.create_block_location(start, end, offset);
let substitutions = verbatim_substitutions(block_metadata);
let (mut inlines, callouts) = resolve_verbatim_callouts(
state,
content,
state.create_block_location(content_start, end, offset),
substitutions.enabled(&Substitution::Callouts),
!metadata.attributes.contains_key("line-comment"),
);
if callouts.is_empty() && !needs_verbatim_inlines(substitutions) {
for node in &mut inlines {
if let InlineNode::VerbatimText(text) = node {
*node = InlineNode::PlainText(Plain {
content: text.content,
location: location.clone(),
escaped: false,
});
}
}
} else {
inlines = resolve_verbatim_inlines(state, block_metadata, inlines)?;
}
state.pending_callouts.extend(callouts);
let all_lines_have_leading_space = content
.lines()
.all(|line| line.is_empty() || line.starts_with(' '));
if all_lines_have_leading_space {
for node in &mut inlines {
walk_inline_nodes_mut(node, &mut |node| {
let starts_line = node.location().start.column == 1;
let content = if let InlineNode::VerbatimText(text) = node {
&mut text.content
} else if let InlineNode::PlainText(text) = node {
&mut text.content
} else {
return;
};
let mut first = true;
*content = state.intern_join(
content.split_inclusive('\n').map(|line| {
let strip = !first || starts_line;
first = false;
if strip {
line.strip_prefix(' ').unwrap_or(line)
} else {
line
}
}),
"",
);
});
}
}
Ok(Block::Paragraph(Paragraph {
source_text: Some(content),
content: inlines,
metadata,
title: block_metadata.title.clone(),
location,
}))
}