Implement nested formatting
This commit is contained in:
parent
c53afefb67
commit
3837af387a
7 changed files with 78 additions and 57 deletions
|
|
@ -66,7 +66,6 @@ explicit_iter_loop = "warn"
|
|||
filter_map_next = "warn"
|
||||
flat_map_option = "warn"
|
||||
float_cmp = "warn"
|
||||
format_push_string = "warn"
|
||||
from_iter_instead_of_collect = "warn"
|
||||
if_not_else = "warn"
|
||||
ignore_without_reason = "warn"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const LEXMAP: LexMap = &[
|
|||
}),
|
||||
];
|
||||
|
||||
fn lex(text: &str, map: LexMap, config: &Config) -> Vec<Token> {
|
||||
fn lex(text: &str, map: LexMap, config: &Config, blocking: bool) -> Vec<Token> {
|
||||
let mut tokens: Vec<Token> = Vec::default();
|
||||
let mut state = state::State::default();
|
||||
|
||||
|
|
@ -38,14 +38,16 @@ fn lex(text: &str, map: LexMap, config: &Config) -> Vec<Token> {
|
|||
continue;
|
||||
}
|
||||
|
||||
if context::block::parse(
|
||||
lexeme,
|
||||
&mut state,
|
||||
&mut tokens,
|
||||
&mut iterator,
|
||||
config,
|
||||
) {
|
||||
continue;
|
||||
if blocking {
|
||||
if context::block::parse(
|
||||
lexeme,
|
||||
&mut state,
|
||||
&mut tokens,
|
||||
&mut iterator,
|
||||
config,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if point::parse(lexeme, &mut state, &mut tokens, &mut iterator) {
|
||||
|
|
@ -79,8 +81,14 @@ fn parse(tokens: &[Token]) -> String {
|
|||
tokens.iter().map(Token::render).collect::<String>()
|
||||
}
|
||||
|
||||
pub(super) fn read(text: &str, config: &Config) -> String {
|
||||
parse(&lex(text, LEXMAP, config))
|
||||
/// Apply end-to-end point and inline parsing for nested contexts, such as
|
||||
/// inside the displayed text of other tokens like anchors and list items
|
||||
pub fn nest(input: &str, config: &Config) -> String {
|
||||
parse(&lex(input, LEXMAP, config, false))
|
||||
}
|
||||
|
||||
pub(super) fn read(input: &str, config: &Config) -> String {
|
||||
parse(&lex(input, LEXMAP, config, true))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@ pub fn parse(
|
|||
log!("Block Context: None -> List on {lexeme}");
|
||||
state.context.block = Block::List;
|
||||
state.buffers.list.candidate.ordered = lexeme.match_char('+');
|
||||
return super::list::parse(lexeme, state, tokens, iterator);
|
||||
return super::list::parse(
|
||||
lexeme, state, tokens, iterator, config,
|
||||
);
|
||||
} else if Paragraph::probe(lexeme) {
|
||||
log!("Block Context: None -> Paragraph on {lexeme}");
|
||||
state.context.block = Block::Paragraph;
|
||||
|
|
@ -78,7 +80,7 @@ pub fn parse(
|
|||
}
|
||||
},
|
||||
Block::List => {
|
||||
return super::list::parse(lexeme, state, tokens, iterator);
|
||||
return super::list::parse(lexeme, state, tokens, iterator, config);
|
||||
},
|
||||
}
|
||||
false
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ use crate::{
|
|||
syntax::content::parser::{
|
||||
context::Block,
|
||||
lexeme::Lexeme,
|
||||
nest,
|
||||
state::{ListBuffer, State},
|
||||
token::{Token, item::Item},
|
||||
},
|
||||
types::Config,
|
||||
};
|
||||
|
||||
/// Handles open list contexts until a list is fully parsed.
|
||||
|
|
@ -23,6 +25,7 @@ pub fn parse(
|
|||
state: &mut State,
|
||||
tokens: &mut Vec<Token>,
|
||||
iterator: &mut Peekable<Iter<'_, Lexeme>>,
|
||||
config: &Config,
|
||||
) -> bool {
|
||||
let buffer = &mut state.buffers.list;
|
||||
let candidate = &mut buffer.candidate;
|
||||
|
|
@ -49,6 +52,7 @@ pub fn parse(
|
|||
}
|
||||
if item_candidate.depth.is_some() {
|
||||
// if the current item candidate has a known depth, push it
|
||||
item_candidate.text = nest(&item_candidate.text, config);
|
||||
candidate.items.push(item_candidate.clone());
|
||||
}
|
||||
// push list candidate, reset state and exit context
|
||||
|
|
@ -60,6 +64,7 @@ pub fn parse(
|
|||
} else if lexeme.match_char('\n') {
|
||||
// found end of item, push it and reset state
|
||||
log!("Accepting item candidate {item_candidate}");
|
||||
item_candidate.text = nest(&item_candidate.text, config);
|
||||
candidate.items.push(item_candidate.clone());
|
||||
*item_candidate = Item::default();
|
||||
buffer.depth = 0;
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ use crate::{
|
|||
Parseable as _,
|
||||
parser::{
|
||||
lexeme::Lexeme,
|
||||
token::{
|
||||
Token, oblique::Oblique, bold::Bold, underline::Underline,
|
||||
strike::Strike,
|
||||
},
|
||||
state::State,
|
||||
token::{
|
||||
Token, bold::Bold, checkbox::CheckBox, oblique::Oblique,
|
||||
strike::Strike, underline::Underline,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -50,6 +50,12 @@ pub fn parse(
|
|||
tokens.push(Token::Bold(Bold::new(!state.switches.bold)));
|
||||
state.switches.bold = !state.switches.bold;
|
||||
return true;
|
||||
} else if CheckBox::probe(lexeme) {
|
||||
log!("CheckBox probed: {lexeme}");
|
||||
tokens.push(Token::CheckBox(CheckBox::lex(lexeme)));
|
||||
iterator.next();
|
||||
iterator.next();
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
use std::fmt::Write as _;
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
syntax::content::{Lexeme, Parseable, parser::token::item::Item},
|
||||
};
|
||||
|
||||
|
|
@ -23,41 +20,31 @@ impl Parseable for List {
|
|||
fn render(&self) -> String {
|
||||
let tag = if self.ordered { "ol" } else { "ul" };
|
||||
let mut output = String::new();
|
||||
|
||||
let indent_width = self
|
||||
.items
|
||||
.windows(2)
|
||||
.find_map(|pair| {
|
||||
let a = pair[0].depth?;
|
||||
let b = pair[1].depth?;
|
||||
(b > a).then_some(b - a)
|
||||
})
|
||||
.unwrap_or(1);
|
||||
let scale = self.scale_indent();
|
||||
|
||||
let mut iterator = self.items.iter().peekable();
|
||||
|
||||
while let Some(item) = iterator.next() {
|
||||
let current_level = item.depth.unwrap_or(0) / indent_width;
|
||||
let next_level = iterator.peek().and_then(|n| n.depth).unwrap_or(0)
|
||||
/ indent_width;
|
||||
let level = item.depth.unwrap_or(0).strict_div(scale);
|
||||
let next_level = iterator
|
||||
.peek()
|
||||
.and_then(|n| n.depth)
|
||||
.unwrap_or(0)
|
||||
.strict_div(scale);
|
||||
|
||||
output.push_str("<li>");
|
||||
output.push_str(&item.text);
|
||||
output.push_str(&format!("<li>{}", item.text));
|
||||
|
||||
if next_level > current_level {
|
||||
// Open nested list(s), keep <li> open
|
||||
for _ in 0..(next_level - current_level) {
|
||||
if next_level > level {
|
||||
// open nested lists
|
||||
for _ in 0..(next_level.saturating_sub(level)) {
|
||||
output.push_str(&format!("<{tag}>\n"));
|
||||
}
|
||||
} else {
|
||||
// close current <li>
|
||||
// close current item
|
||||
output.push_str("</li>");
|
||||
|
||||
// close nested lists inline
|
||||
for _ in 0..(current_level - next_level) {
|
||||
// close nested lists
|
||||
for _ in 0..(level.saturating_sub(next_level)) {
|
||||
output.push_str(&format!("</{tag}></li>"));
|
||||
}
|
||||
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +60,21 @@ impl List {
|
|||
items: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn scale_indent(&self) -> u8 {
|
||||
let width = self
|
||||
.items
|
||||
.windows(2)
|
||||
.find_map(|pair| {
|
||||
let outer = pair.first()?.depth?;
|
||||
let inner = pair.get(1)?.depth?;
|
||||
(inner > outer).then_some(inner.saturating_sub(outer))
|
||||
})
|
||||
.unwrap_or(1);
|
||||
|
||||
assert!(width != 0, "Width of zero can't be a divisor");
|
||||
width
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for List {
|
||||
|
|
|
|||
|
|
@ -516,8 +516,6 @@ en is only possible thanks to a number of projects and people:
|
|||
|
||||
[nodes.Roadmap]
|
||||
text = """
|
||||
- [x] Setup tests
|
||||
- [ ] Improve content syntax parser coverage
|
||||
- [x] Redirects
|
||||
- [ ] Strip/render some syntax in Tree text preview
|
||||
- [x] Drop-down navigation
|
||||
|
|
@ -557,19 +555,19 @@ text = """
|
|||
- [ ] Specialization <-> Generalization
|
||||
- [ ] Custom connection kinds
|
||||
- [x] External anchors
|
||||
- [ ] Richer text formatting
|
||||
- [ ] Nested formatting
|
||||
- [x] Richer text formatting
|
||||
- [x] Nested formatting
|
||||
- [x] Headers
|
||||
- [x] Preformatted blocks
|
||||
- [x] Oblique,
|
||||
- [x] Underline
|
||||
- [x] Strikethrough
|
||||
- [x] Bold
|
||||
- [x] Inline code
|
||||
- [x] _Oblique_,
|
||||
- [x] __Underline__
|
||||
- [x] ~~Strikethrough~~
|
||||
- [x] *Bold*
|
||||
- [x] `Inline code`
|
||||
- [x] Lists
|
||||
- [ ] Nested lists
|
||||
- [x] Nested lists
|
||||
- [x] Checkboxes
|
||||
- [ ] Move this roadmap to en
|
||||
- [x] Move this roadmap to en
|
||||
- [ ] Full-text search
|
||||
- [ ] Begin centralizing state
|
||||
- [ ] Reduce O(n) calls in the formats module
|
||||
|
|
@ -577,6 +575,7 @@ text = """
|
|||
- [ ] Render to filesystem
|
||||
- [ ] Single-page rendering
|
||||
- [ ] Input formats
|
||||
- [ ] Frontmatter format
|
||||
- [ ] Multi-file graphs
|
||||
- [ ] Multi-graph
|
||||
- [ ] Themes
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue