feat(render): list_ignore_newline / text_ignore_newline = false → <br>
Implements the deferred half of these options: a soft line break inside a
paragraph / list item can render as <br /> instead of a space.
- Parser: soft breaks now emit a dedicated InlineNode::SoftBreak node
(parse_inline_seq K::Newline + the list-continuation synthetic join) instead
of collapsing to Text(" "). Added SoftBreakNode + the variant.
- All inline consumers treat SoftBreak as whitespace: AST visitor (no-op),
text extraction (index.rs + lib.rs → space), nav/semantic_tokens span_of_inline,
semantic emit (skip like Text), parser span_of_inline.
- Renderer: render_inlines_break renders top-level SoftBreaks as the
context-specific string; render_paragraph uses text_ignore_newline,
render_list_item uses list_ignore_newline; default render_inline → space.
with_newline_handling builder; export.rs wires both from HtmlConfig.
Default (true/true = upstream default) behaviour is unchanged (space). Updated
3 core tests + 1 parser test that asserted the old Text(" ") shape to treat
SoftBreak as whitespace. New test render_page_html_ignore_newline_controls_soft_breaks.
Gap doc: P3 Config item closed — only the deferred markdown cluster remains.
Full rust suite + clippy clean; Neovim 307, Vim 301/18/21.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,12 @@ pub struct HtmlRenderer {
|
||||
/// vimwiki `emoji_enable` (default `false` in the renderer; set from
|
||||
/// config): substitute `:alias:` shortcodes with their emoji glyph.
|
||||
emoji_enable: bool,
|
||||
/// vimwiki `text_ignore_newline` (default `true`): a soft break in a
|
||||
/// paragraph renders as a space; `false` → `<br />`.
|
||||
text_ignore_newline: bool,
|
||||
/// vimwiki `list_ignore_newline` (default `true`): a soft break in a list
|
||||
/// item renders as a space; `false` → `<br />`.
|
||||
list_ignore_newline: bool,
|
||||
}
|
||||
|
||||
impl Default for HtmlRenderer {
|
||||
@@ -80,9 +86,20 @@ impl HtmlRenderer {
|
||||
header_numbering_sym: String::new(),
|
||||
valid_html_tags: Vec::new(),
|
||||
emoji_enable: false,
|
||||
text_ignore_newline: true,
|
||||
list_ignore_newline: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set vimwiki `text_ignore_newline` / `list_ignore_newline`. When `false`,
|
||||
/// a soft line break in a paragraph / list item renders as `<br />`
|
||||
/// instead of a space.
|
||||
pub fn with_newline_handling(mut self, text_ignore: bool, list_ignore: bool) -> Self {
|
||||
self.text_ignore_newline = text_ignore;
|
||||
self.list_ignore_newline = list_ignore;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the inline HTML tags allowed through export unescaped (vimwiki
|
||||
/// `valid_html_tags`). `span` is always added so colour spans render.
|
||||
pub fn with_valid_html_tags(mut self, mut tags: Vec<String>) -> Self {
|
||||
@@ -294,7 +311,8 @@ impl HtmlRenderer {
|
||||
|
||||
fn render_paragraph(&self, n: &ParagraphNode, w: &mut dyn Write) -> io::Result<()> {
|
||||
w.write_all(b"<p>")?;
|
||||
self.render_inlines(&n.children, w)?;
|
||||
let sb = if self.text_ignore_newline { " " } else { "<br />" };
|
||||
self.render_inlines_break(&n.children, sb, w)?;
|
||||
w.write_all(b"</p>\n")
|
||||
}
|
||||
|
||||
@@ -371,7 +389,8 @@ impl HtmlRenderer {
|
||||
}
|
||||
None => w.write_all(b"<li>")?,
|
||||
}
|
||||
self.render_inlines(&n.children, w)?;
|
||||
let sb = if self.list_ignore_newline { " " } else { "<br />" };
|
||||
self.render_inlines_break(&n.children, sb, w)?;
|
||||
if let Some(sublist) = &n.sublist {
|
||||
w.write_all(b"\n")?;
|
||||
self.render_list(sublist, w)?;
|
||||
@@ -526,6 +545,24 @@ impl HtmlRenderer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Like [`render_inlines`] but renders top-level soft breaks as `soft_break`
|
||||
/// (a space or `<br />`). Used by paragraphs / list items, which are the
|
||||
/// only blocks that contain `SoftBreak` nodes.
|
||||
fn render_inlines_break(
|
||||
&self,
|
||||
nodes: &[InlineNode],
|
||||
soft_break: &str,
|
||||
w: &mut dyn Write,
|
||||
) -> io::Result<()> {
|
||||
for n in nodes {
|
||||
match n {
|
||||
InlineNode::SoftBreak(_) => w.write_all(soft_break.as_bytes())?,
|
||||
_ => self.render_inline(n, w)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_inline(&self, n: &InlineNode, w: &mut dyn Write) -> io::Result<()> {
|
||||
match n {
|
||||
InlineNode::Text(t) => self.render_text(t, w),
|
||||
@@ -543,6 +580,9 @@ impl HtmlRenderer {
|
||||
InlineNode::ExternalLink(t) => self.render_external_link(t, w),
|
||||
InlineNode::Transclusion(t) => self.render_transclusion(t, w),
|
||||
InlineNode::RawUrl(t) => self.render_raw_url(t, w),
|
||||
// Default rendering (e.g. inside a heading): a space. Paragraphs
|
||||
// and list items use render_inlines_break for `<br />` support.
|
||||
InlineNode::SoftBreak(_) => w.write_all(b" "),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user