diff --git a/docs/development/roadmap.md b/docs/development/roadmap.md index e433d76..32a9c64 100644 --- a/docs/development/roadmap.md +++ b/docs/development/roadmap.md @@ -46,11 +46,11 @@ - [x] Headers - [x] Preformatted blocks - [x] Oblique, - - [ ] Underline - - [ ] Strikethrough - - [ ] Bold + - [x] Underline + - [x] Strikethrough + - [x] Bold - [x] Inline code - - [ ] Lists + - [x] Lists - [ ] Nested lists - [ ] Checkboxes - [ ] Move this roadmap to en diff --git a/src/syntax/content/parser.rs b/src/syntax/content/parser.rs index cfd5a1f..6ee1036 100644 --- a/src/syntax/content/parser.rs +++ b/src/syntax/content/parser.rs @@ -41,7 +41,7 @@ fn lex(text: &str, map: LexMap, config: &Config) -> Vec { continue; } - if point::parse(lexeme, &mut state, &mut tokens) { + if point::parse(lexeme, &mut state, &mut tokens, &mut iterator) { continue; } diff --git a/src/syntax/content/parser/context/inline.rs b/src/syntax/content/parser/context/inline.rs index b773e82..515b69f 100644 --- a/src/syntax/content/parser/context/inline.rs +++ b/src/syntax/content/parser/context/inline.rs @@ -5,10 +5,10 @@ use crate::{ syntax::content::{ Parseable as _, parser::{ - context, Inline, + Inline, context, lexeme::Lexeme, - state::State, - token::{Token, code::Code, anchor::Anchor}, + state::{AnchorBuffer, State}, + token::{Token, anchor::Anchor, code::Code}, }, }, }; @@ -29,7 +29,7 @@ pub fn parse( } else if Anchor::probe(lexeme) { log!("Inline Context: None -> Anchor on {lexeme}"); state.context.inline = Inline::Anchor; - state.buffers.anchor.clear(); + state.buffers.anchor = AnchorBuffer::default(); if lexeme.match_as_char('|') { state.buffers.anchor.candidate.leading = true; diff --git a/src/syntax/content/parser/point.rs b/src/syntax/content/parser/point.rs index fb4a918..098e9de 100644 --- a/src/syntax/content/parser/point.rs +++ b/src/syntax/content/parser/point.rs @@ -1,10 +1,15 @@ +use std::{iter::Peekable, slice::Iter}; + use crate::{ prelude::*, syntax::content::{ Parseable as _, parser::{ lexeme::Lexeme, - token::{Token, oblique::Oblique, bold::Bold}, + token::{ + Token, oblique::Oblique, bold::Bold, underline::Underline, + strike::Strike, + }, state::State, }, }, @@ -14,14 +19,28 @@ pub fn parse( lexeme: &Lexeme, state: &mut State, tokens: &mut Vec, + iterator: &mut Peekable>, ) -> bool { - if Oblique::probe(lexeme) { - log!("Oblique probed {lexeme}"); + if Underline::probe(lexeme) { + log!("Underline probed: {lexeme}"); + tokens + .push(Token::Underline(Underline::new(!state.switches.underline))); + state.switches.underline = !state.switches.underline; + iterator.next(); + return true; + } else if Oblique::probe(lexeme) { + log!("Oblique probed: {lexeme}"); tokens.push(Token::Oblique(Oblique::new(!state.switches.oblique))); state.switches.oblique = !state.switches.oblique; return true; + } else if Strike::probe(lexeme) { + log!("Strike probed: {lexeme}"); + tokens.push(Token::Strike(Strike::new(!state.switches.crossout))); + state.switches.crossout = !state.switches.crossout; + iterator.next(); + return true; } else if Bold::probe(lexeme) { - log!("Bold probed {lexeme}"); + log!("Bold probed: {lexeme}"); tokens.push(Token::Bold(Bold::new(!state.switches.bold))); state.switches.bold = !state.switches.bold; return true; diff --git a/src/syntax/content/parser/segment.rs b/src/syntax/content/parser/segment.rs index 53a7837..e41e57f 100644 --- a/src/syntax/content/parser/segment.rs +++ b/src/syntax/content/parser/segment.rs @@ -9,13 +9,15 @@ pub mod delimiter { pub flanking: Vec, pub punctuation: Vec, pub whitespace: Vec, + pub double: Vec, } impl Default for Delimiters { fn default() -> Self { Delimiters { atomic: vec!['`', '|'], - flanking: vec!['_', '*', '(', ')', '\'', '"'], + double: vec!['_', '~'], + flanking: vec!['_', '*', '~', '(', ')', '\'', '"'], punctuation: vec![',', '.', ';', ':', '?', '!'], whitespace: vec!['\n', ' '], } @@ -55,12 +57,21 @@ pub mod delimiter { let mut iterator = text.chars().peekable(); while let Some(c) = iterator.next() { - // if the current char is a boundary + // if current char is a boundary if delimiters.is_boundary(c) { atomized.push(c.to_string()); continue; - // if the current char is a flanking delimiter + // if current char is a double delimiter and the next its double + } else if delimiters.double.contains(&c) + && iterator.peek().is_some_and(|n| *n == c) + { + atomized.push(c.to_string()); + iterator.next(); + atomized.push(c.to_string()); + continue; + + // if current char is a flanking delimiter } else if delimiters.flanking.contains(&c) { // if next char is a boundary if iterator @@ -70,7 +81,7 @@ pub mod delimiter { atomized.push(c.to_string()); continue; - // if the previous char was whitespace + // if previous char was whitespace } else if let Some(last_string) = atomized.last() && let Some(last_char) = last_string.chars().last() && delimiters.whitespace.contains(&last_char) @@ -82,7 +93,7 @@ pub mod delimiter { // if there is a last atomized element if let Some(last) = atomized.last_mut() { - // if the last atomized element is a boundary + // if last atomized element is a boundary if delimiters.is_str_delimiter(last) { atomized.push(c.to_string()); } else { diff --git a/src/syntax/content/parser/state.rs b/src/syntax/content/parser/state.rs index e98e127..7228b9c 100644 --- a/src/syntax/content/parser/state.rs +++ b/src/syntax/content/parser/state.rs @@ -15,8 +15,10 @@ pub struct State { #[derive(Clone, Debug)] pub struct Switches { - pub oblique: bool, pub bold: bool, + pub oblique: bool, + pub crossout: bool, + pub underline: bool, } #[derive(Clone, Debug)] @@ -24,21 +26,13 @@ pub struct Buffers { pub anchor: AnchorBuffer, } -#[derive(Clone, Debug)] +#[derive(Default, Clone, Debug)] pub struct AnchorBuffer { pub candidate: Anchor, pub text: String, pub destination: String, } -impl AnchorBuffer { - pub fn clear(&mut self) { - self.candidate = Anchor::default(); - self.text = String::default(); - self.destination = String::default(); - } -} - impl std::fmt::Display for AnchorBuffer { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let display_text = if self.text.is_empty() { @@ -72,8 +66,10 @@ impl Default for State { }, dom_ids: HashMap::default(), switches: Switches { - oblique: false, bold: false, + crossout: false, + oblique: false, + underline: false, }, buffers: Buffers { anchor: AnchorBuffer { diff --git a/src/syntax/content/parser/token.rs b/src/syntax/content/parser/token.rs index 37f0c67..fb88989 100644 --- a/src/syntax/content/parser/token.rs +++ b/src/syntax/content/parser/token.rs @@ -12,12 +12,15 @@ pub mod oblique; pub mod paragraph; pub mod preformat; pub mod span; +pub mod strike; +pub mod underline; #[derive(Debug, Eq, PartialEq, Clone)] pub enum Token { Anchor(anchor::Anchor), Bold(bold::Bold), Code(code::Code), + Strike(strike::Strike), Header(header::Header), Item(item::Item), LineBreak(linebreak::LineBreak), @@ -27,6 +30,7 @@ pub enum Token { Paragraph(paragraph::Paragraph), PreFormat(preformat::PreFormat), Span(span::Span), + Underline(underline::Underline), } impl Token { @@ -35,6 +39,7 @@ impl Token { Token::Anchor(ref d) => d.render(), Token::Bold(ref d) => d.render(), Token::Code(ref d) => d.render(), + Token::Strike(ref d) => d.render(), Token::Header(ref d) => d.render(), Token::Item(ref d) => d.render(), Token::LineBreak(ref d) => d.render(), @@ -44,6 +49,7 @@ impl Token { Token::Paragraph(ref d) => d.render(), Token::PreFormat(ref d) => d.render(), Token::Span(ref d) => d.render(), + Token::Underline(ref d) => d.render(), } } } @@ -54,6 +60,7 @@ impl std::fmt::Display for Token { Token::Anchor(ref d) => format!("{d}"), Token::Bold(ref d) => format!("{d}"), Token::Code(ref d) => format!("{d}"), + Token::Strike(ref d) => format!("{d}"), Token::Header(ref d) => format!("{d}"), Token::Item(ref d) => format!("{d}"), Token::LineBreak(ref d) => format!("{d}"), @@ -63,6 +70,7 @@ impl std::fmt::Display for Token { Token::Paragraph(ref d) => format!("{d}"), Token::PreFormat(ref d) => format!("{d}"), Token::Span(ref d) => format!("{d}"), + Token::Underline(ref d) => format!("{d}"), }; write!(f, "Tk:{data}") diff --git a/src/syntax/content/parser/token/strike.rs b/src/syntax/content/parser/token/strike.rs new file mode 100644 index 0000000..049fde2 --- /dev/null +++ b/src/syntax/content/parser/token/strike.rs @@ -0,0 +1,58 @@ +use crate::{ + syntax::content::{Parseable, Lexeme}, +}; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Strike { + open: bool, +} + +impl Strike { + pub fn new(open: bool) -> Strike { + Strike { open } + } +} + +impl Parseable for Strike { + fn probe(lexeme: &Lexeme) -> bool { + lexeme.match_as_char('~') && lexeme.match_next_as_char('~') + } + + fn lex(_lexeme: &Lexeme) -> Strike { + panic!("Attempt to lex a strike tag directly from a lexeme") + } + + fn render(&self) -> String { + let tag = if self.open { "" } else { "" }; + String::from(tag) + } +} + +impl std::fmt::Display for Strike { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let display_open_state = if self.open { "open" } else { "closed" }; + write!(f, "Strike [{display_open_state}]") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn render() { + let code_open = Strike::new(true); + assert_eq!(code_open.render(), ""); + + let code_closed = Strike::new(false); + assert_eq!(code_closed.render(), ""); + } + + #[test] + #[should_panic( + expected = "Attempt to lex a strike tag directly from a lexeme" + )] + fn lex() { + Strike::lex(&Lexeme::new("", "")); + } +} diff --git a/src/syntax/content/parser/token/underline.rs b/src/syntax/content/parser/token/underline.rs new file mode 100644 index 0000000..866699d --- /dev/null +++ b/src/syntax/content/parser/token/underline.rs @@ -0,0 +1,61 @@ +use crate::{ + syntax::content::{Parseable, Lexeme}, +}; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Underline { + open: bool, +} + +impl Underline { + pub fn new(open: bool) -> Underline { + Underline { open } + } +} + +impl Parseable for Underline { + fn probe(lexeme: &Lexeme) -> bool { + lexeme.match_as_char('_') && lexeme.match_next_as_char('_') + } + + fn lex(_lexeme: &Lexeme) -> Underline { + panic!("Attempt to lex an underline tag directly from a lexeme") + } + + fn render(&self) -> String { + if self.open { + String::from("") + } else { + String::from("") + } + } +} + +impl std::fmt::Display for Underline { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let display_open_state = if self.open { "open" } else { "closed" }; + write!(f, "Underline [{display_open_state}]") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn render() { + let code_open = Underline::new(true); + assert_eq!(code_open.render(), ""); + + let code_closed = Underline::new(false); + assert_eq!(code_closed.render(), ""); + } + + #[test] + #[should_panic( + expected = "Attempt to lex an underline tag directly from a lexeme" + )] + fn lex() { + Underline::lex(&Lexeme::new("", "")); + } +}