documents: presses read back through quotes, code, HTML and front matter
Each drawn character finds its written one, skipping only white space, escapes and quote marks, so a press lands on the character itself. Code, front matter and link definitions read back line for line. An HTML block keeps each text run's source byte, and an alert keeps its text past the marker; alert() now takes the source. A secondary press refuses a target its write-back changed, and its focus no longer opens an empty document. Run's element stack moves to tree/stack.rs and the HTML-block reader to tree/html_block.rs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5af11fe489
commit
83d747d87e
16 files changed
+868
-480
No files matched your search
+21
-94
@@ -7,11 +7,14 @@ use super::markdown::{span, tree, walk};
|
||||
mod blocks;
|
||||
mod emoji;
|
||||
mod html;
|
||||
mod html_block;
|
||||
mod sources;
|
||||
mod stack;
|
||||
|
||||
use blocks::html_block_text;
|
||||
pub use blocks::{AlertKind, DocumentHeading, alert, is_toc};
|
||||
use html::InlineTag;
|
||||
use html_block::html_block_text;
|
||||
pub(crate) use sources::lines_at;
|
||||
use stack::close;
|
||||
|
||||
/// How a stretch of inline text prints.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -47,8 +50,8 @@ pub enum Node {
|
||||
Code {
|
||||
language: Option<String>,
|
||||
text: String,
|
||||
/// Where `text` starts in the source, if written as drawn.
|
||||
from: Option<usize>,
|
||||
/// Where its lines start in the source.
|
||||
from: usize,
|
||||
},
|
||||
/// Items and blocks; ordered from `start`; tasks carry checkboxes.
|
||||
List {
|
||||
@@ -82,8 +85,11 @@ pub enum Node {
|
||||
},
|
||||
/// `[toc]`: the file's headings.
|
||||
Toc(Vec<DocumentHeading>),
|
||||
/// Front matter, its fences cut.
|
||||
FrontMatter(String),
|
||||
/// Front matter, its fences cut; lines start at `from`.
|
||||
FrontMatter {
|
||||
text: String,
|
||||
from: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// A tree read against its source and its link definitions.
|
||||
@@ -153,90 +159,6 @@ impl Run {
|
||||
let soft = shown.replace("\r\n", " ").replace(['\r', '\n'], " ");
|
||||
self.text.text.push_str(&soft);
|
||||
}
|
||||
|
||||
fn push(&mut self, opened: Opened) {
|
||||
*self.named.entry(opened.name.clone()).or_default() += 1;
|
||||
self.open.push(opened);
|
||||
}
|
||||
|
||||
/// The innermost open link's address.
|
||||
fn link(&self) -> Option<String> {
|
||||
self.open.iter().rev().find_map(|opened| opened.url.clone())
|
||||
}
|
||||
|
||||
/// Opens or closes an element for an inline HTML tag.
|
||||
fn html(&mut self, written: &str) {
|
||||
match html::inline_tag(written) {
|
||||
InlineTag::Text(shown) => self.push_text(&shown),
|
||||
InlineTag::Open { name, style, href } => self.start(&name, style, href),
|
||||
InlineTag::Close(name) => {
|
||||
self.shortcodes.split();
|
||||
self.end(&name);
|
||||
}
|
||||
InlineTag::Break => {
|
||||
self.shortcodes.split();
|
||||
self.text.text.push('\n');
|
||||
}
|
||||
InlineTag::Gone => self.shortcodes.split(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens `name`; a new `a` ends the open one.
|
||||
fn start(&mut self, name: &str, style: Option<Style>, url: Option<String>) {
|
||||
if name == "a" {
|
||||
self.end("a");
|
||||
}
|
||||
self.shortcodes.split();
|
||||
self.push(Opened {
|
||||
name: name.to_string(),
|
||||
start: self.text.text.len(),
|
||||
style,
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
||||
/// Ends the innermost `name`, as HTML5 repairs nesting.
|
||||
fn end(&mut self, name: &str) {
|
||||
if self.count(name) == 0 {
|
||||
return;
|
||||
}
|
||||
self.shortcodes.split();
|
||||
let at = self
|
||||
.open
|
||||
.iter()
|
||||
.rposition(|opened| opened.name == name)
|
||||
.expect("a counted name is open");
|
||||
let end = self.text.text.len();
|
||||
if let Some((_, from)) = &mut self.owed {
|
||||
*from = (*from).min(at);
|
||||
}
|
||||
let closed = self.open.split_off(at);
|
||||
for opened in &closed {
|
||||
close(&mut self.text, opened, end);
|
||||
*self.named.get_mut(&opened.name).expect("counted") -= 1;
|
||||
}
|
||||
for opened in closed.into_iter().skip(1) {
|
||||
if html::reopens(&opened.name) {
|
||||
self.push(Opened {
|
||||
start: end,
|
||||
..opened
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Records `opened` as styled up to `end`.
|
||||
fn close(text: &mut Inline, opened: &Opened, end: usize) {
|
||||
if opened.start >= end {
|
||||
return;
|
||||
}
|
||||
if let Some(style) = opened.style {
|
||||
text.styles.push((opened.start..end, style));
|
||||
}
|
||||
if let Some(url) = &opened.url {
|
||||
text.links.push((opened.start..end, url.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
/// `markdown` as blocks.
|
||||
@@ -322,7 +244,7 @@ impl<'a> Reader<'a> {
|
||||
mdast::Node::Code(code) => out.push(Node::Code {
|
||||
language: code.lang.clone(),
|
||||
text: code.value.clone(),
|
||||
from: sources::code_from(self.source, node, &code.value),
|
||||
from: sources::body(self.source, node),
|
||||
}),
|
||||
mdast::Node::Math(math) => out.push(Node::Math(math.value.clone())),
|
||||
mdast::Node::List(list) => {
|
||||
@@ -363,9 +285,14 @@ impl<'a> Reader<'a> {
|
||||
label: self.label(note),
|
||||
body: self.nested(¬e.children),
|
||||
}),
|
||||
mdast::Node::Html(html) => text_block(html_block_text(&html.value), out),
|
||||
mdast::Node::Yaml(yaml) => out.push(Node::FrontMatter(yaml.value.clone())),
|
||||
mdast::Node::Toml(toml) => out.push(Node::FrontMatter(toml.value.clone())),
|
||||
mdast::Node::Html(html) => {
|
||||
text_block(html_block_text(&html.value, self.source, span(node)), out)
|
||||
}
|
||||
mdast::Node::Yaml(mdast::Yaml { value, .. })
|
||||
| mdast::Node::Toml(mdast::Toml { value, .. }) => out.push(Node::FrontMatter {
|
||||
text: value.clone(),
|
||||
from: sources::body(self.source, node),
|
||||
}),
|
||||
mdast::Node::Definition(_) => {}
|
||||
other => unreachable!("{other:?} is no block outside MDX"),
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user