diff --git a/Cargo.toml b/Cargo.toml index 2194014..193369e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "catmark" -version = "0.2.0" +version = "0.2.1" authors = [ "Xavier Bestel " ] license = "GPL-3.0" description = "Console printer for CommonMark" @@ -13,3 +13,11 @@ term_size = "0.3" syntect = "1.3" unicode-segmentation = "1.1" unicode-width = "0.1" +html2runes = { git = "https://github.com/Ruin0x11/html2runes" } + +[lib] +path = "src/lib.rs" + +[[bin]] +name = "catmark" +path = "src/main.rs" \ No newline at end of file diff --git a/src/ansi_renderer.rs b/src/ansi_renderer.rs index 8c9154f..9ccf856 100644 --- a/src/ansi_renderer.rs +++ b/src/ansi_renderer.rs @@ -6,6 +6,7 @@ use std::borrow::Cow; +use html2runes; use pulldown_cmark::{Event, Tag}; use pulldown_cmark::Event::{Start, End, Text, Html, InlineHtml, SoftBreak, HardBreak, FootnoteReference}; @@ -15,7 +16,25 @@ use syntect::parsing::SyntaxSet; use syntect::highlighting; use syntect::parsing::syntax_definition::SyntaxDefinition; -use dombox::{DomBox, BorderType, DomColor, TermColor, BoxKind, split_at_in_place}; +use super::dombox::{DomBox, BorderType, DomColor, TermColor, BoxKind, split_at_in_place}; +use super::OutputKind; + +pub fn push_ansi<'a, I: Iterator>>( + iter: I, + width: u16, + kind: OutputKind, +) -> String { + let syntaxes = SyntaxSet::load_defaults_newlines(); + let themes = highlighting::ThemeSet::load_defaults(); + let mut ctx = Ctx::new(iter, &syntaxes, &themes); + let mut root = ctx.build(width); + root.layout(); + let ansi_strings = root.render(&kind); + + ansi_strings.into_iter().fold(String::new(), |s, ansi| { + s + &ansi.to_string() + }) +} struct Ctx<'a, 'b, I> { iter: I, @@ -28,6 +47,175 @@ struct Ctx<'a, 'b, I> { highline: Option>, } +impl<'a, 'b, I: Iterator>> Ctx<'a, 'b, I> { + fn tag_paragraph(&mut self, parent: &mut DomBox<'a>) { + let child = parent.add_block(); + self.build_dom(child); + child.size.border.bottom = 1; + } + + fn tag_rule(&mut self, parent: &mut DomBox<'a>) { + let child = parent.add_block(); + child.style.extend = true; + child.size.border.bottom = 1; + child.style.border_type = BorderType::Thin; + child.style.fg = DomColor::from_dark(TermColor::Yellow); + } + + fn tag_header(&mut self, parent: &mut DomBox<'a>, level: i32) { + let child = parent.add_header(level as u8); + child.size.border.bottom = 1; + + if level == 1 { + child.size.border.top = 1; + child.size.border.left = 1; + child.size.border.right = 1; + } + + child.style.border_type = match level { + 1 => BorderType::Thin, + 2 => BorderType::Bold, + 3 => BorderType::Double, + 4 => BorderType::Thin, + 5 => BorderType::Dash, + _ => BorderType::Thin, + }; + + child.style.fg = DomColor::from_dark(TermColor::Purple); + self.build_dom(child); + } + + fn tag_block_quote(&mut self, parent: &mut DomBox<'a>) { + { + let child = parent.add_block(); + self.build_dom(child); + child.size.border.left = 1; + child.style.border_type = BorderType::Thin; + child.style.fg = DomColor::from_dark(TermColor::Cyan); + } + let newline = parent.add_block(); // XXX ugly + newline.add_text(Cow::from("")); + } + + fn tag_code_block(&mut self, parent: &mut DomBox<'a>, info: Cow<'a, str>) { + { + let indent = parent.style.indent; + let child = parent.add_block(); + child.style.fg = DomColor::from_dark(TermColor::White); + child.style.bg = DomColor::from_dark(TermColor::Black); + child.style.indent = indent + 2; + + // NOTE: Just assume the language is rust if the language + // is omitted, since many docs don't have the 'rust' tag in + // code blocks + self.syntax = match self.syntaxes.find_syntax_by_token(&info) { + Some(syn) => Some(syn), + None => self.syntaxes.find_syntax_by_token("rust"), + }; + + if let Some(syn) = self.syntax { + self.highline = Some(HighlightLines::new(syn, &self.themes.themes[self.theme])); + + self.build_dom(child); + } + } + let newline = parent.add_block(); // XXX ugly + newline.add_text(Cow::from("")); + } + + fn tag_list(&mut self, parent: &mut DomBox<'a>, start_opt: Option) { + match start_opt { + Some(start) => { + let child = parent.add_list(Some(start as u16)); + self.build_dom(child); + child.size.border.bottom = 1; + } + None => { + let child = parent.add_list(None); + self.build_dom(child); + child.size.border.bottom = 1; + } + } + } + + fn tag_item(&mut self, parent: &mut DomBox<'a>) { + { + let bullet = parent.add_bullet(); + bullet.style.fg = DomColor::from_light(TermColor::Yellow); + bullet.size.border.right = 1; + } + let child = parent.add_block(); + self.build_dom(child); + } + + fn tag_emphasis(&mut self, parent: &mut DomBox<'a>) { + let child = parent.add_inline(); + child.style.italic = true; + self.build_dom(child); + } + + fn tag_strong(&mut self, parent: &mut DomBox<'a>) { + let child = parent.add_inline(); + child.style.bold = true; + self.build_dom(child); + } + + fn tag_code(&mut self, parent: &mut DomBox<'a>) { + let child = parent.add_inline(); + child.style.fg = DomColor::from_dark(TermColor::White); + child.style.bg = DomColor::from_dark(TermColor::Black); + self.build_dom(child); + } + + fn tag_link(&mut self, parent: &mut DomBox<'a>, dest: Cow<'a, str>) { + if let Some(mut links) = self.links.take() { + { + let child = links.add_text(dest); + child.style.fg = DomColor::from_dark(TermColor::Blue); + child.style.underline = true; + } + { + links.add_break(); + } + self.links = Some(links); + } + let child = parent.add_inline(); + child.style.underline = true; + child.style.fg = DomColor::from_dark(TermColor::Blue); + self.build_dom(child); + } + + fn tag_image(&mut self, parent: &mut DomBox<'a>, dest: Cow<'a, str>, title: Cow<'a, str>) { + { + let child = parent.add_text(title); + child.style.fg = DomColor::from_light(TermColor::Black); + child.style.bg = DomColor::from_dark(TermColor::Yellow); + } + { + let child = parent.add_text(dest); + child.style.fg = DomColor::from_dark(TermColor::Blue); + child.style.bg = DomColor::from_dark(TermColor::Yellow); + child.style.underline = true; + } + let child = parent.add_inline(); + child.style.italic = true; + self.build_dom(child); + + } + + fn tag_footnote(&mut self, _parent: &mut DomBox<'a>, name: Cow<'a, str>) { + if let Some(mut footnotes) = self.footnotes.take() { + { + let child = footnotes.add_text(name); + child.style.fg = DomColor::from_dark(TermColor::Green); + child.style.underline = true; + } + self.build_dom(&mut footnotes); + self.footnotes = Some(footnotes); + } + } +} + impl<'a, 'b, I: Iterator>> Ctx<'a, 'b, I> { pub fn new(iter: I, syntaxes: &'b SyntaxSet, themes: &'b highlighting::ThemeSet) -> Self { Ctx { @@ -41,6 +229,7 @@ impl<'a, 'b, I: Iterator>> Ctx<'a, 'b, I> { highline: None, } } + fn build(&mut self, width: u16) -> DomBox<'a> { self.links = Some(DomBox::new_block()); self.footnotes = Some(DomBox::new_block()); @@ -54,334 +243,181 @@ impl<'a, 'b, I: Iterator>> Ctx<'a, 'b, I> { } root } + fn build_dom(&mut self, parent: &mut DomBox<'a>) { loop { match self.iter.next() { Some(event) => { match event { - Start(tag) => { - match tag { - Tag::Paragraph => { - let child = parent.add_block(); - self.build_dom(child); - child.size.border.bottom = 1; - } - Tag::Rule => { - let child = parent.add_block(); - child.style.extend = true; - child.size.border.bottom = 1; - child.style.border_type = BorderType::Thin; - child.style.fg = DomColor::from_dark(TermColor::Yellow); - } - Tag::Header(level) => { - let child = parent.add_header(level as u8); - child.size.border.bottom = 1; - match level { - 1 => { - child.size.border.top = 1; - child.size.border.left = 1; - child.size.border.right = 1; - child.style.border_type = BorderType::Thin; - } - 2 => { - child.style.border_type = BorderType::Bold; - } - 3 => { - child.style.border_type = BorderType::Double; - } - 4 => { - child.style.border_type = BorderType::Thin; - } - 5 => { - child.style.border_type = BorderType::Dash; - } - 6 => {} - bad => panic!("wrong heading size {}", bad), - } - child.style.fg = DomColor::from_dark(TermColor::Purple); - self.build_dom(child); - } - Tag::Table(_) => {} - Tag::TableHead => {} - Tag::TableRow => {} - Tag::TableCell => {} - Tag::BlockQuote => { - { - let child = parent.add_block(); - self.build_dom(child); - child.size.border.left = 1; - child.style.border_type = BorderType::Thin; - child.style.fg = DomColor::from_dark(TermColor::Cyan); - } - let newline = parent.add_block(); // XXX ugly - newline.add_text(Cow::from("")); - } - Tag::CodeBlock(info) => { - { - let child = parent.add_block(); - child.style.code = true; - child.style.fg = DomColor::from_dark(TermColor::White); - child.style.bg = DomColor::from_dark(TermColor::Black); - self.syntax = self.syntaxes.find_syntax_by_token(&info); - if let Some(syn) = self.syntax { - self.highline = - Some(HighlightLines::new(syn, - &self.themes.themes - [self.theme])); - } - self.build_dom(child); - } - let newline = parent.add_block(); // XXX ugly - newline.add_text(Cow::from("")); - } - Tag::List(Some(start)) => { - let child = parent.add_list(Some(start as u16)); - self.build_dom(child); - child.size.border.bottom = 1; - } - Tag::List(None) => { - let child = parent.add_list(None); - self.build_dom(child); - child.size.border.bottom = 1; - } - Tag::Item => { - { - let bullet = parent.add_bullet(); - bullet.style.fg = DomColor::from_light(TermColor::Yellow); - bullet.size.border.right = 1; - } - let child = parent.add_block(); - self.build_dom(child); - } - Tag::Emphasis => { - let child = parent.add_inline(); - child.style.italic = true; - self.build_dom(child); - } - Tag::Strong => { - let child = parent.add_inline(); - child.style.bold = true; - self.build_dom(child); - } - Tag::Code => { - let child = parent.add_inline(); - child.style.code = true; - child.style.fg = DomColor::from_dark(TermColor::White); - child.style.bg = DomColor::from_dark(TermColor::Black); - self.build_dom(child); - } - Tag::Link(dest, _) => { - if let Some(mut links) = self.links.take() { - { - let child = links.add_text(dest); - child.style.fg = DomColor::from_dark(TermColor::Blue); - child.style.underline = true; - } - { - links.add_break(); - } - self.links = Some(links); - } - let child = parent.add_inline(); - child.style.underline = true; - child.style.fg = DomColor::from_dark(TermColor::Blue); - self.build_dom(child); - } - Tag::Image(dest, title) => { - { - let child = parent.add_text(title); - child.style.fg = DomColor::from_light(TermColor::Black); - child.style.bg = DomColor::from_dark(TermColor::Yellow); - } - { - let child = parent.add_text(dest); - child.style.fg = DomColor::from_dark(TermColor::Blue); - child.style.bg = DomColor::from_dark(TermColor::Yellow); - child.style.underline = true; - } - let child = parent.add_inline(); - child.style.italic = true; - self.build_dom(child); - } - Tag::FootnoteDefinition(name) => { - if let Some(mut footnotes) = self.footnotes.take() { - { - let child = footnotes.add_text(name); - child.style.fg = DomColor::from_dark(TermColor::Green); - child.style.underline = true; - } - self.build_dom(&mut footnotes); - self.footnotes = Some(footnotes); - } - } - } - } + Start(tag) => self.on_tag_start(parent, tag), End(tag) => { - match tag { - Tag::Paragraph => { - break; - } - Tag::Rule => {} - Tag::Header(_) => { - break; - } - Tag::Table(_) => {} - Tag::TableHead => {} - Tag::TableRow => {} - Tag::TableCell => {} - Tag::BlockQuote => { - break; - } - Tag::CodeBlock(_) => { - self.highline = None; - self.syntax = None; - break; - } - Tag::List(None) => { - for child in &mut parent.children { - { - if let BoxKind::ListBullet = child.kind { - child.add_text(Cow::from("*")); - } - } - } - break; - } - Tag::List(Some(start)) => { - let mut i = start; - // TODO resize all bullets like the last one - //let end = start + node.children.len() / 2; - for child in &mut parent.children { - { - if let BoxKind::ListBullet = child.kind { - child.add_text(Cow::from(i.to_string())); - i += 1; - } - } - } - break; - } - Tag::Item => { - break; - } - Tag::Emphasis => { - break; - } - Tag::Strong => { - break; - } - Tag::Code => { - break; - } - Tag::Link(_, _) => { - break; - } - Tag::Image(_, _) => { - break; - } - Tag::FootnoteDefinition(_) => { - break; - } - } - } - Text(mut text) => { - if let Some(ref mut h) = self.highline { - match text { - Cow::Borrowed(text) => { - let ranges = h.highlight(&text); - for (style, mut text) in ranges { - let mut add_break = false; - if text.len() > 0 { - // check if text ends with a newline - let bytes = text.as_bytes(); - if bytes[bytes.len() - 1] == 10 { - add_break = true; - } - } - if add_break { - text = &text[..text.len() - 1]; - } - { - let child = parent.add_text(Cow::Borrowed(text)); - child.style.fg = - DomColor::from_color(style.foreground.r, - style.foreground.g, - style.foreground.b); - child.style.bold |= - style - .font_style - .intersects(highlighting::FONT_STYLE_BOLD); - child.style.italic |= - style - .font_style - .intersects(highlighting::FONT_STYLE_ITALIC); - child.style.underline |= - style - .font_style - .intersects(highlighting::FONT_STYLE_UNDERLINE); - } - if add_break { - parent.add_break(); - } - } - } - Cow::Owned(_text) => { - unimplemented!(); - } - } - } else { - let mut add_break = false; - if text.len() > 0 { - // check if text ends with a newline - let bytes = text.as_bytes(); - if bytes[bytes.len() - 1] == 10 { - add_break = true; - } - } - if add_break { - let pos = text.len() - 1; - split_at_in_place(&mut text, pos); - } - parent.add_text(text); - if add_break { - parent.add_break(); - } + let should_end = self.on_tag_end(parent, tag); + if should_end { + break; } } - Html(html) => { - let child = parent.add_text(html); - child.style.fg = DomColor::from_light(TermColor::Red); - } - InlineHtml(html) => { - let child = parent.add_text(html); - child.style.fg = DomColor::from_light(TermColor::Red); - } - SoftBreak => { + Text(text) => self.on_text(parent, text), + Html(html) | InlineHtml(html) => add_html(parent, html), + SoftBreak | HardBreak => { parent.add_break(); } - HardBreak => { - parent.add_break(); + FootnoteReference(name) => add_footnote(parent, name), + } + } + None => break, + } + } + } + fn on_tag_start(&mut self, parent: &mut DomBox<'a>, tag: Tag<'a>) { + match tag { + Tag::Paragraph => self.tag_paragraph(parent), + Tag::Rule => self.tag_rule(parent), + Tag::Header(level) => self.tag_header(parent, level), + Tag::BlockQuote => self.tag_block_quote(parent), + Tag::CodeBlock(info) => self.tag_code_block(parent, info), + Tag::List(start_opt) => self.tag_list(parent, start_opt), + Tag::Item => self.tag_item(parent), + Tag::Emphasis => self.tag_emphasis(parent), + Tag::Strong => self.tag_strong(parent), + Tag::Code => self.tag_code(parent), + Tag::Link(dest, _) => self.tag_link(parent, dest), + Tag::Image(dest, title) => self.tag_image(parent, dest, title), + Tag::FootnoteDefinition(name) => self.tag_footnote(parent, name), + + Tag::Table(_) | Tag::TableHead | Tag::TableRow | Tag::TableCell => {} + } + } + + fn on_tag_end(&mut self, parent: &mut DomBox<'a>, tag: Tag<'a>) -> bool { + match tag { + Tag::Paragraph | + Tag::Header(_) | + Tag::BlockQuote | + Tag::Item | + Tag::Emphasis | + Tag::Strong | + Tag::Code | + Tag::Link(_, _) | + Tag::Image(_, _) | + Tag::FootnoteDefinition(_) => true, + + Tag::CodeBlock(_) => { + self.highline = None; + self.syntax = None; + true + } + Tag::List(None) => { + for child in &mut parent.children { + { + if let BoxKind::ListBullet = child.kind { + child.add_text(Cow::from("*")); } - FootnoteReference(name) => { - let child = parent.add_text(name); - child.style.fg = DomColor::from_dark(TermColor::Green); - child.style.underline = true; + } + } + true + } + Tag::List(Some(start)) => { + let mut i = start; + // TODO resize all bullets like the last one + //let end = start + node.children.len() / 2; + for child in &mut parent.children { + { + if let BoxKind::ListBullet = child.kind { + child.add_text(Cow::from(i.to_string())); + i += 1; } } } - None => break, + true } + + Tag::Rule | Tag::Table(_) | Tag::TableHead | Tag::TableRow | Tag::TableCell => false, + } + } + + + fn on_text(&mut self, parent: &mut DomBox<'a>, text: Cow<'a, str>) { + if let Some(ref mut h) = self.highline { + add_highlighted_text(parent, text, h) + } else { + add_plain_text(parent, text); } } } -pub fn push_ansi<'a, I: Iterator>>(iter: I, width: u16) { - let syntaxes = SyntaxSet::load_defaults_newlines(); - let themes = highlighting::ThemeSet::load_defaults(); - let mut ctx = Ctx::new(iter, &syntaxes, &themes); - let mut root = ctx.build(width); - //println!("root:\n{:#?}\n", root); - root.layout(); - //println!("root:\n{:#?}\n", root); - root.render(); + +fn add_plain_text<'a>(parent: &mut DomBox<'a>, mut text: Cow<'a, str>) { + let mut add_break = false; + if text.len() > 0 { + // check if text ends with a newline + let bytes = text.as_bytes(); + if bytes[bytes.len() - 1] == 10 { + add_break = true; + } + } + if add_break { + let pos = text.len() - 1; + split_at_in_place(&mut text, pos); + } + parent.add_text(text); + if add_break { + parent.add_break(); + } +} + +fn add_highlighted_text<'a, 'b>( + parent: &mut DomBox<'a>, + text: Cow<'a, str>, + highline: &mut HighlightLines<'b>, +) { + match text { + Cow::Borrowed(text) => { + let ranges = highline.highlight(&text); + for (style, mut text) in ranges { + let mut add_break = false; + if text.len() > 0 { + // check if text ends with a newline + let bytes = text.as_bytes(); + if bytes[bytes.len() - 1] == 10 { + add_break = true; + } + } + if add_break { + text = &text[..text.len() - 1]; + } + { + let child = parent.add_text(Cow::Borrowed(text)); + child.style.fg = DomColor::from_color( + style.foreground.r, + style.foreground.g, + style.foreground.b, + ); + child.style.bold |= style.font_style.intersects(highlighting::FONT_STYLE_BOLD); + child.style.italic |= + style.font_style.intersects(highlighting::FONT_STYLE_ITALIC); + child.style.underline |= style.font_style.intersects( + highlighting::FONT_STYLE_UNDERLINE, + ); + } + if add_break { + parent.add_break(); + } + } + } + Cow::Owned(_text) => { + unimplemented!(); + } + } +} + +fn add_html<'a>(parent: &mut DomBox<'a>, html: Cow<'a, str>) { + let text = html2runes::html_to_text(&html.clone().to_mut()); + let child = parent.add_text(Cow::from(text)); + child.style.fg = DomColor::from_light(TermColor::Red); +} + +fn add_footnote<'a>(parent: &mut DomBox<'a>, name: Cow<'a, str>) { + let child = parent.add_text(name); + child.style.fg = DomColor::from_dark(TermColor::Green); + child.style.underline = true; } diff --git a/src/dombox.rs b/src/dombox.rs index 918a8b1..f9420e2 100644 --- a/src/dombox.rs +++ b/src/dombox.rs @@ -8,11 +8,13 @@ use std::fmt; use std::borrow::Cow; use ansi_term::{Style, Colour}; -use ansi_term::{ANSIString, ANSIStrings}; +use ansi_term::ANSIString; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; +use super::OutputKind; + fn findsplit(s: &str, pos: usize) -> usize { if let Some(n) = UnicodeSegmentation::grapheme_indices(s, true).nth(pos) { return n.0; @@ -118,8 +120,8 @@ pub struct DomStyle { pub underline: bool, pub strikethrough: bool, pub italic: bool, - pub code: bool, // XXX useless ? pub extend: bool, + pub indent: u16, pub align: TextAlign, pub border_type: BorderType, pub top_nb_type: BorderType, @@ -129,34 +131,42 @@ pub struct DomStyle { } impl DomStyle { - pub fn to_ansi(&self) -> Style { - let mut astyle = Style::new(); - match self.fg.index() { - None => {} - Some(idx) => { - astyle = astyle.fg(Colour::Fixed(idx)); - } - } - match self.bg.index() { - None => {} - Some(idx) => { - astyle = astyle.on(Colour::Fixed(idx)); + pub fn to_ansi(&self, kind: &OutputKind) -> Style { + match *kind { + OutputKind::Plain => Style::new(), + OutputKind::Color => { + let mut astyle = Style::new(); + match self.fg.index() { + None => {} + Some(idx) => { + astyle = astyle.fg(Colour::Fixed(idx)); + } + } + match self.bg.index() { + None => {} + Some(idx) => { + astyle = astyle.on(Colour::Fixed(idx)); + } + } + if self.bold { + astyle = astyle.bold(); + } + if self.underline { + astyle = astyle.underline(); + } + if self.strikethrough { + astyle = astyle.strikethrough(); + } + if self.italic { + astyle = astyle.italic(); + } + astyle } } - if self.bold { - astyle = astyle.bold(); - } - if self.underline { - astyle = astyle.underline(); - } - if self.strikethrough { - astyle = astyle.strikethrough(); - } - if self.italic { - astyle = astyle.italic(); - } - astyle } + + #[cfg(never)] + pub fn merge(&mut self, other: DomStyle) -> DomStyle {} } #[derive(Debug, Clone)] @@ -184,18 +194,20 @@ struct BoxCursor { impl fmt::Display for BoxCursor { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, - "[{} {}] [{} {} +{} +{}] [+{} +{} -{} -{}]", - self.x, - self.y, - self.container.content.x, - self.container.content.y, - self.container.content.w, - self.container.content.h, - self.container.border.top, - self.container.border.left, - self.container.border.bottom, - self.container.border.right) + write!( + f, + "[{} {}] [{} {} +{} +{}] [+{} +{} -{} -{}]", + self.x, + self.y, + self.container.content.x, + self.container.content.y, + self.container.content.w, + self.container.content.h, + self.container.border.top, + self.container.border.left, + self.container.border.bottom, + self.container.border.right + ) } } @@ -205,6 +217,24 @@ pub struct BoxSize { pub border: Edges, } +impl BoxSize { + pub fn width_plus_border(&self) -> u16 { + self.content.w + self.border.left + self.border.right + } + + pub fn height_plus_border(&self) -> u16 { + self.content.h + self.border.top + self.border.bottom + } + + pub fn right(&self) -> u16 { + self.content.x + self.content.w + } + + pub fn bottom(&self) -> u16 { + self.content.y + self.content.h + } +} + #[derive(Default, Debug, Copy, Clone)] pub struct Rect { x: u16, @@ -260,91 +290,94 @@ impl<'a> DomBox<'a> { match self.children.last() { Some(&DomBox { kind: BoxKind::InlineContainer, .. }) => {} _ => { - self.children - .push(DomBox { - size: Default::default(), - kind: BoxKind::InlineContainer, - style: self.style.clone(), - children: vec![], - }); + self.children.push(DomBox { + size: Default::default(), + kind: BoxKind::InlineContainer, + style: self.style.clone(), + children: vec![], + }); } } self.children.last_mut().unwrap() } } } + + #[cfg(never)] + pub fn add_dom(&mut self, dom: DomBox<'a>) -> &mut DomBox<'a> { + if dom.is_inline { + inline_container = self.get_inline_container(); + inline_container.push(dom); + inline_container.children.last_mut.clone() + } else { + self.children_push(dom); + self.children.last_mut().unwrap() + } + } + pub fn add_text(&mut self, text: Cow<'a, str>) -> &mut DomBox<'a> { let inline_container = self.get_inline_container(); - inline_container - .children - .push(DomBox { - size: Default::default(), - kind: BoxKind::Text(text), - style: inline_container.style.clone(), - children: vec![], - }); + inline_container.children.push(DomBox { + size: Default::default(), + kind: BoxKind::Text(text), + style: inline_container.style.clone(), + children: vec![], + }); inline_container.children.last_mut().unwrap() } pub fn add_inline(&mut self) -> &mut DomBox<'a> { let inline_container = self.get_inline_container(); - inline_container - .children - .push(DomBox { - size: Default::default(), - kind: BoxKind::Inline, - style: inline_container.style.clone(), - children: vec![], - }); + inline_container.children.push(DomBox { + size: Default::default(), + kind: BoxKind::Inline, + style: inline_container.style.clone(), + children: vec![], + }); inline_container.children.last_mut().unwrap() } pub fn add_block(&mut self) -> &mut DomBox<'a> { - self.children - .push(DomBox { - size: Default::default(), - kind: BoxKind::Block, - style: self.style.clone(), - children: vec![], - }); + self.children.push(DomBox { + size: Default::default(), + kind: BoxKind::Block, + style: self.style.clone(), + children: vec![], + }); self.children.last_mut().unwrap() } pub fn add_header(&mut self, level: u8) -> &mut DomBox<'a> { - self.children - .push(DomBox { - size: Default::default(), - kind: BoxKind::Header(level), - style: self.style.clone(), - children: vec![], - }); + self.children.push(DomBox { + size: Default::default(), + kind: BoxKind::Header(level), + style: self.style.clone(), + children: vec![], + }); self.children.last_mut().unwrap() } pub fn add_list(&mut self, start: Option) -> &mut DomBox<'a> { - self.children - .push(DomBox { - size: Default::default(), - kind: BoxKind::List(start), - style: self.style.clone(), - children: vec![], - }); + self.children.push(DomBox { + size: Default::default(), + kind: BoxKind::List(start), + style: self.style.clone(), + children: vec![], + }); self.children.last_mut().unwrap() } pub fn add_bullet(&mut self) -> &mut DomBox<'a> { - self.children - .push(DomBox { - size: Default::default(), - kind: BoxKind::ListBullet, - style: self.style.clone(), - children: vec![], - }); + self.children.push(DomBox { + size: Default::default(), + kind: BoxKind::ListBullet, + style: self.style.clone(), + children: vec![], + }); self.children.last_mut().unwrap() } pub fn add_break(&mut self) -> &mut DomBox<'a> { - self.children - .push(DomBox { - size: Default::default(), - kind: BoxKind::Break, - style: self.style.clone(), - children: vec![], - }); + self.children.push(DomBox { + size: Default::default(), + kind: BoxKind::Break, + style: self.style.clone(), + children: vec![], + }); self.children.last_mut().unwrap() } pub fn layout(&mut self) { @@ -355,10 +388,11 @@ impl<'a> DomBox<'a> { }; self.layout_generic(&mut cursor); } - fn inline_children_loop(&mut self, - res: LayoutRes>, - dorej: bool) - -> LayoutRes> { + fn inline_children_loop( + &mut self, + res: LayoutRes>, + dorej: bool, + ) -> LayoutRes> { let mut res = res; let mut subcursor = BoxCursor { x: self.size.content.x, @@ -370,11 +404,11 @@ impl<'a> DomBox<'a> { if let BoxKind::Break = self.children[i].kind { self.children.remove(i); res = LayoutRes::CutHere(DomBox { - kind: self.kind.clone(), - size: self.size.clone(), - style: self.style.clone(), - children: self.children.split_off(i), - }); + kind: self.kind.clone(), + size: self.size.clone(), + style: self.style.clone(), + children: self.children.split_off(i), + }); break; } match self.children[i].layout_generic(&mut subcursor) { @@ -382,11 +416,11 @@ impl<'a> DomBox<'a> { LayoutRes::CutHere(next) => { self.children.insert(i + 1, next); res = LayoutRes::CutHere(DomBox { - kind: self.kind.clone(), - size: self.size.clone(), - style: self.style.clone(), - children: self.children.split_off(i + 1), - }); + kind: self.kind.clone(), + size: self.size.clone(), + style: self.style.clone(), + children: self.children.split_off(i + 1), + }); break; } LayoutRes::Reject => { @@ -398,11 +432,11 @@ impl<'a> DomBox<'a> { } } else { res = LayoutRes::CutHere(DomBox { - kind: self.kind.clone(), - size: self.size.clone(), - style: self.style.clone(), - children: self.children.split_off(i), - }); + kind: self.kind.clone(), + size: self.size.clone(), + style: self.style.clone(), + children: self.children.split_off(i), + }); } break; } @@ -431,10 +465,11 @@ impl<'a> DomBox<'a> { self.size.content.y = cursor.y + self.size.border.top; self.size.content.h = 0; self.size.content.w = if cursor.container.content.w - cursor.x + - cursor.container.content.x > - self.size.border.left + self.size.border.right { + cursor.container.content.x > + self.size.border.left + self.size.border.right + { cursor.container.content.w - cursor.x + cursor.container.content.x - - self.size.border.left - self.size.border.right + self.size.border.left - self.size.border.right } else { 1 }; @@ -450,21 +485,15 @@ impl<'a> DomBox<'a> { self.children.remove(i); continue; } - match self.children[i].layout_generic(&mut subcursor) { - LayoutRes::Normal => (), - LayoutRes::CutHere(next) => self.children.insert(i + 1, next), - LayoutRes::Reject => { - panic!("can't reject a {:?}", self.children[i].kind); - } - } - self.size.content.h += self.children[i].size.content.h + - self.children[i].size.border.top + - self.children[i].size.border.bottom; - if self.children[i].size.content.w + self.children[i].size.border.left + - self.children[i].size.border.right > max_width { - max_width = self.children[i].size.content.w + self.children[i].size.border.left + - self.children[i].size.border.right; + + self.layout_child(&mut subcursor, i); + + self.size.content.h += self.children[i].size.height_plus_border(); + + if self.children[i].size.width_plus_border() > max_width { + max_width = self.children[i].size.width_plus_border(); } + i += 1; } if !self.style.extend { @@ -472,21 +501,33 @@ impl<'a> DomBox<'a> { } if let BoxKind::ListBullet = self.kind { // XXX ugly - cursor.x += self.size.content.w + self.size.border.left + self.size.border.right; + cursor.x += self.size.width_plus_border(); } else { cursor.x = cursor.container.content.x; - cursor.y += self.size.content.h + self.size.border.top + self.size.border.bottom; + cursor.y += self.size.height_plus_border(); } + res } + + fn layout_child(&mut self, cursor: &mut BoxCursor, i: usize) { + match self.children[i].layout_generic(cursor) { + LayoutRes::Normal => (), + LayoutRes::CutHere(next) => self.children.insert(i + 1, next), + LayoutRes::Reject => { + panic!("can't reject a {:?}", self.children[i].kind); + } + } + } + fn layout_list(&mut self, cursor: &mut BoxCursor) -> LayoutRes> { let res = LayoutRes::Normal; - self.size.content.w = if cursor.container.content.w > - self.size.border.left + self.size.border.right { - cursor.container.content.w - self.size.border.left - self.size.border.right - } else { - 1 - }; + self.size.content.w = + if cursor.container.content.w > self.size.border.left + self.size.border.right { + cursor.container.content.w - self.size.border.left - self.size.border.right + } else { + 1 + }; self.size.content.h = 0; self.size.content.x = cursor.x + self.size.border.left; self.size.content.y = cursor.y + self.size.border.top; @@ -497,49 +538,33 @@ impl<'a> DomBox<'a> { }; let mut i = 0; while i < self.children.len() { + self.layout_child(&mut subcursor, i); match self.children[i].kind { - BoxKind::ListBullet => { - match self.children[i].layout_generic(&mut subcursor) { - LayoutRes::Normal => (), - LayoutRes::CutHere(next) => self.children.insert(i + 1, next), - LayoutRes::Reject => { - panic!("can't reject a {:?}", self.children[i].kind); - } - } - } + BoxKind::ListBullet => (), BoxKind::Block => { - match self.children[i].layout_generic(&mut subcursor) { - LayoutRes::Normal => (), - LayoutRes::CutHere(next) => self.children.insert(i + 1, next), - LayoutRes::Reject => { - panic!("can't reject a {:?}", self.children[i].kind); - } - } - self.size.content.h += self.children[i].size.content.h + - self.children[i].size.border.top + - self.children[i].size.border.bottom; + self.size.content.h += self.children[i].size.height_plus_border(); } _ => panic!("can't layout a {:?} in a List", self.children[i].kind), } i += 1; } - cursor.y += self.size.content.h + self.size.border.top + self.size.border.bottom; + cursor.y += self.size.height_plus_border(); res } // this is a line, and when split will be 2 lines fn layout_inline_container(&mut self, cursor: &mut BoxCursor) -> LayoutRes> { let mut res = LayoutRes::Normal; - self.size.content.w = if cursor.container.content.w > - self.size.border.left + self.size.border.right { - cursor.container.content.w - self.size.border.left - self.size.border.right - } else { - 1 - }; + self.size.content.w = + if cursor.container.content.w > self.size.border.left + self.size.border.right { + cursor.container.content.w - self.size.border.left - self.size.border.right + } else { + 1 + }; self.size.content.h = 1; self.size.content.x = cursor.x + self.size.border.left; self.size.content.y = cursor.y + self.size.border.top; res = self.inline_children_loop(res, false); - cursor.y += self.size.content.h + self.size.border.top + self.size.border.bottom; + cursor.y += self.size.height_plus_border(); res } // this one can ask to be splitted if needs be, in this case the returned @@ -550,7 +575,7 @@ impl<'a> DomBox<'a> { self.size.content.x = cursor.x + self.size.border.left; self.size.content.y = cursor.y + self.size.border.top; self.size.content.w = cursor.container.content.w - (cursor.x - cursor.container.content.x) - - (self.size.border.left + self.size.border.right); + (self.size.border.left + self.size.border.right); match self.kind { BoxKind::Text(ref mut text) => { let width = UnicodeWidthStr::width(&text[..]) as u16; @@ -560,11 +585,11 @@ impl<'a> DomBox<'a> { let pos = findsplit(text, self.size.content.w as usize); let remains = split_at_in_place(text, pos); res = LayoutRes::CutHere(DomBox { - kind: BoxKind::Text(remains), - size: self.size.clone(), - style: self.style.clone(), - children: vec![], - }); + kind: BoxKind::Text(remains), + size: self.size.clone(), + style: self.style.clone(), + children: vec![], + }); } else { self.size.content.w = width; } @@ -579,64 +604,76 @@ impl<'a> DomBox<'a> { cursor.x += self.size.content.w; res } - pub fn render(&mut self) { + + pub fn render(&mut self, kind: &OutputKind) -> Vec> { let mut strings = Vec::new(); - for line in 0..(self.size.content.h + self.size.border.top + self.size.border.bottom) { - self.render_line(line, &mut strings); + for line in 0..(self.size.height_plus_border()) { + self.render_line(line, &mut strings, kind); strings.push(Style::default().paint("\n")); } - println!("{}", ANSIStrings(&strings)); + + strings } - fn render_line(&self, line: u16, strings: &mut Vec>) -> (u16, u16) { + + fn render_line( + &self, + line: u16, + strings: &mut Vec>, + kind: &OutputKind, + ) -> (u16, u16) { if line < self.size.content.y - self.size.border.top || - line >= self.size.content.y + self.size.content.h + self.size.border.bottom { + line >= self.size.bottom() + self.size.border.bottom + { // out of the box, don't render anything return (0, 0); } - if line < self.size.content.y || line >= self.size.content.y + self.size.content.h { - return self.render_borderline(line, strings); + if line < self.size.content.y || line >= self.size.bottom() { + return self.render_borderline(line, strings, kind); } - self.render_borderside(true, strings); + self.render_borderside(true, strings, kind); let mut pos = self.size.content.x; match self.kind { BoxKind::Text(ref text) => { - let s = self.style.to_ansi().paint(text.to_string()); + let s = self.style.to_ansi(kind).paint(text.to_string()); strings.push(s); pos += UnicodeWidthStr::width(&text[..]) as u16; - assert!(pos <= self.size.content.x + self.size.content.w); + assert!(pos <= self.size.right()); } _ => { for child in &self.children { let insert_point = strings.len() as u16; - let (start, len) = child.render_line(line, strings); + let (start, len) = child.render_line(line, strings, kind); if len == 0 { continue; } assert!(start >= pos); - assert!(start + len <= self.size.content.x + self.size.content.w); + assert!(start + len <= self.size.right()); if start > pos { - self.render_charline(' ', start - pos, Some(insert_point), strings); + self.render_charline(' ', start - pos, Some(insert_point), strings, kind); } pos = start + len; } - assert!(pos <= self.size.content.x + self.size.content.w); + assert!(pos <= self.size.right()); } } - if pos < self.size.content.x + self.size.content.w { - self.render_charline(' ', - self.size.content.x + self.size.content.w - pos, - None, - strings); + if pos < self.size.right() { + self.render_charline(' ', self.size.right() - pos, None, strings, kind); } - self.render_borderside(false, strings); - return (self.size.content.x - self.size.border.left, - self.size.content.w + self.size.border.left + self.size.border.right); + self.render_borderside(false, strings, kind); + return ( + self.size.content.x - self.size.border.left, + self.size.width_plus_border(), + ); } - fn render_borderline(&self, line: u16, strings: &mut Vec>) -> (u16, u16) { + + fn render_borderline( + &self, + line: u16, + strings: &mut Vec>, + kind: &OutputKind, + ) -> (u16, u16) { let is_top = line < self.size.content.y; - let mut s = String::with_capacity(((self.size.content.w + self.size.border.left + - self.size.border.right) * - 4) as usize); + let mut s = String::with_capacity(((self.size.width_plus_border()) * 4) as usize); for _ in 0..self.size.border.left { match self.style.border_type { _ => { @@ -666,12 +703,20 @@ impl<'a> DomBox<'a> { for _ in 0..self.size.border.right { s.push(if is_top { '┐' } else { '┘' }); } - let s = self.style.to_ansi().paint(s); + let s = self.style.to_ansi(kind).paint(s); strings.push(s); - return (self.size.content.x - self.size.border.left, - self.size.content.w + self.size.border.left + self.size.border.right); + return ( + self.size.content.x - self.size.border.left, + self.size.width_plus_border(), + ); } - fn render_borderside(&self, is_left: bool, strings: &mut Vec>) { + + fn render_borderside( + &self, + is_left: bool, + strings: &mut Vec>, + kind: &OutputKind, + ) { let width = if is_left { self.size.border.left } else { @@ -697,19 +742,23 @@ impl<'a> DomBox<'a> { } } } - let s = self.style.to_ansi().paint(s); + let s = self.style.to_ansi(kind).paint(s); strings.push(s); } - fn render_charline(&self, - c: char, - n: u16, - insert: Option, - strings: &mut Vec>) { + + fn render_charline( + &self, + c: char, + n: u16, + insert: Option, + strings: &mut Vec>, + kind: &OutputKind, + ) { let mut s = String::with_capacity((n * 4) as usize); for _ in 0..n { s.push(c); } - let s = self.style.to_ansi().paint(s); + let s = self.style.to_ansi(kind).paint(s); if let Some(insert) = insert { strings.insert(insert as usize, s); } else { diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..63bd5e8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,32 @@ +// Copyright 2016 Xavier Bestel - All rights reserved. +// +// GPL goes here + +//! Markdown (CommonMark) ANSI renderer. + +extern crate pulldown_cmark; +extern crate syntect; +extern crate ansi_term; +extern crate term_size; +extern crate unicode_segmentation; +extern crate unicode_width; +extern crate html2runes; + +mod ansi_renderer; +mod dombox; + +use pulldown_cmark::Parser; +use pulldown_cmark::{Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES}; + +pub enum OutputKind { + Color, + Plain, +} + +pub fn render_ansi(text: &str, width: u16, kind: OutputKind) -> String { + let mut opts = Options::empty(); + opts.insert(OPTION_ENABLE_TABLES); + opts.insert(OPTION_ENABLE_FOOTNOTES); + let p = Parser::new_ext(&text, opts); + ansi_renderer::push_ansi(p, width, kind) +} diff --git a/src/main.rs b/src/main.rs index 269f7ce..8a6a848 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,27 +10,18 @@ extern crate ansi_term; extern crate term_size; extern crate unicode_segmentation; extern crate unicode_width; +extern crate html2runes; -mod ansi_renderer; -mod dombox; - -use pulldown_cmark::Parser; -use pulldown_cmark::{Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES}; +extern crate catmark; use std::io; use std::env; use std::fs::File; use std::io::Read; -pub const DEFAULT_COLS: u16 = 80; +use catmark::OutputKind; -fn render_ansi(text: &str, width: u16) { - let mut opts = Options::empty(); - opts.insert(OPTION_ENABLE_TABLES); - opts.insert(OPTION_ENABLE_FOOTNOTES); - let p = Parser::new_ext(&text, opts); - ansi_renderer::push_ansi(p, width); -} +pub const DEFAULT_COLS: u16 = 80; pub fn main() { let mut input = String::new(); @@ -40,12 +31,13 @@ pub fn main() { } if let Some(arg1) = env::args().nth(1) { let mut f = File::open(arg1).expect("unable to open file"); - f.read_to_string(&mut input) - .expect("unable to read file"); + f.read_to_string(&mut input).expect("unable to read file"); } else { - io::stdin() - .read_to_string(&mut input) - .expect("unable to read stdin"); + io::stdin().read_to_string(&mut input).expect( + "unable to read stdin", + ); } - render_ansi(&input, width); + let result = catmark::render_ansi(&input, width, OutputKind::Color); + + println!("{}", result); }