diff --git a/Cargo.lock b/Cargo.lock index 0e0713b..bccf304 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -873,6 +873,12 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c1bba4f227a4a53d12b653f50ca7bf10c9119ae2aba56aff9e0338b5c98f36a" +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -1535,6 +1541,7 @@ dependencies = [ "mime_guess", "modalkit", "open", + "pretty_assertions", "regex", "rpassword", "serde", @@ -2578,6 +2585,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -4519,6 +4536,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zeroize" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 1fbb7c4..c82915b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ features = ["macros", "net", "rt-multi-thread", "sync", "time"] [dev-dependencies] lazy_static = "1.4.0" +pretty_assertions = "1.4.0" [profile.release] lto = true diff --git a/src/message/html.rs b/src/message/html.rs index c5620ac..215e2ec 100644 --- a/src/message/html.rs +++ b/src/message/html.rs @@ -237,6 +237,7 @@ pub enum StyleTreeNode { Image(Option), List(StyleTreeChildren, ListStyle), Paragraph(Box), + Pre(Box), Reply(Box), Ruler, Style(Box, Style), @@ -311,6 +312,39 @@ impl StyleTreeNode { child.print(printer, style); printer.commit(); }, + StyleTreeNode::Pre(child) => { + let mut subp = printer.sub(2).literal(true); + let subw = subp.width(); + + child.print(&mut subp, style); + + printer.commit(); + printer.push_line( + vec![ + Span::styled(line::TOP_LEFT, style), + Span::styled(line::HORIZONTAL.repeat(subw), style), + Span::styled(line::TOP_RIGHT, style), + ] + .into(), + ); + + for mut line in subp.finish() { + line.0.insert(0, Span::styled(line::VERTICAL, style)); + line.0.push(Span::styled(line::VERTICAL, style)); + printer.push_line(line); + } + + printer.push_line( + vec![ + Span::styled(line::BOTTOM_LEFT, style), + Span::styled(line::HORIZONTAL.repeat(subw), style), + Span::styled(line::BOTTOM_RIGHT, style), + ] + .into(), + ); + + printer.commit(); + }, StyleTreeNode::Reply(child) => { if printer.hide_reply() { return; @@ -585,6 +619,7 @@ fn h2t(hdl: &Handle) -> StyleTreeChildren { // Other text blocks. "blockquote" => StyleTreeNode::Blockquote(c2t(&node.children.borrow())), "div" | "p" => StyleTreeNode::Paragraph(c2t(&node.children.borrow())), + "pre" => StyleTreeNode::Pre(c2t(&node.children.borrow())), // No children. "hr" => StyleTreeNode::Ruler, @@ -593,7 +628,7 @@ fn h2t(hdl: &Handle) -> StyleTreeChildren { "img" => StyleTreeNode::Image(attrs_to_alt(&attrs.borrow())), // These don't render in any special way. - "a" | "details" | "html" | "pre" | "summary" | "sub" | "sup" => { + "a" | "details" | "html" | "summary" | "sub" | "sup" => { *c2t(&node.children.borrow()) }, @@ -630,6 +665,7 @@ pub fn parse_matrix_html(s: &str) -> StyleTree { pub mod tests { use super::*; use crate::util::space_span; + use pretty_assertions::assert_eq; #[test] fn test_header() { @@ -1173,4 +1209,76 @@ pub mod tests { ]) ); } + + #[test] + fn test_pre_tag() { + let s = concat!( + "
",
+            "fn hello() -> usize {\n",
+            "    return 5;\n",
+            "}\n",
+            "
\n" + ); + let tree = parse_matrix_html(s); + let text = tree.to_text(25, Style::default(), true); + assert_eq!(text.lines.len(), 5); + assert_eq!( + text.lines[0], + Spans(vec![ + Span::raw(line::TOP_LEFT), + Span::raw(line::HORIZONTAL.repeat(23)), + Span::raw(line::TOP_RIGHT) + ]) + ); + assert_eq!( + text.lines[1], + Spans(vec![ + Span::raw(line::VERTICAL), + Span::raw("fn"), + Span::raw(" "), + Span::raw("hello"), + Span::raw("("), + Span::raw(")"), + Span::raw(" "), + Span::raw("-"), + Span::raw(">"), + Span::raw(" "), + Span::raw("usize"), + Span::raw(" "), + Span::raw("{"), + Span::raw(" "), + Span::raw(line::VERTICAL) + ]) + ); + assert_eq!( + text.lines[2], + Spans(vec![ + Span::raw(line::VERTICAL), + Span::raw(" "), + Span::raw("return"), + Span::raw(" "), + Span::raw("5"), + Span::raw(";"), + Span::raw(" "), + Span::raw(line::VERTICAL) + ]) + ); + assert_eq!( + text.lines[3], + Spans(vec![ + Span::raw(line::VERTICAL), + Span::raw("}"), + Span::raw(" ".repeat(22)), + Span::raw(line::VERTICAL) + ]) + ); + assert_eq!( + text.lines[4], + Spans(vec![ + Span::raw(line::BOTTOM_LEFT), + Span::raw(line::HORIZONTAL.repeat(23)), + Span::raw(line::BOTTOM_RIGHT) + ]) + ); + } } diff --git a/src/message/printer.rs b/src/message/printer.rs index 531b5b4..2d2b8b5 100644 --- a/src/message/printer.rs +++ b/src/message/printer.rs @@ -17,6 +17,7 @@ pub struct TextPrinter<'a> { alignment: Alignment, curr_spans: Vec>, curr_width: usize, + literal: bool, } impl<'a> TextPrinter<'a> { @@ -30,6 +31,7 @@ impl<'a> TextPrinter<'a> { alignment: Alignment::Left, curr_spans: vec![], curr_width: 0, + literal: false, } } @@ -38,6 +40,11 @@ impl<'a> TextPrinter<'a> { self } + pub fn literal(mut self, literal: bool) -> Self { + self.literal = literal; + self + } + pub fn hide_reply(&self) -> bool { self.hide_reply } @@ -56,6 +63,7 @@ impl<'a> TextPrinter<'a> { alignment: self.alignment, curr_spans: vec![], curr_width: 0, + literal: self.literal, } } @@ -162,11 +170,16 @@ impl<'a> TextPrinter<'a> { for mut word in UnicodeSegmentation::split_word_bounds(s) { if let "\n" | "\r\n" = word { + if self.literal { + self.commit(); + continue; + } + // Render embedded newlines as spaces. word = " "; } - if self.curr_width == 0 && word.chars().all(char::is_whitespace) { + if !self.literal && self.curr_width == 0 && word.chars().all(char::is_whitespace) { // Drop leading whitespace. continue; } @@ -182,7 +195,7 @@ impl<'a> TextPrinter<'a> { // Word doesn't fit on this line, so start a new one. self.commit(); - if word.chars().all(char::is_whitespace) { + if !self.literal && word.chars().all(char::is_whitespace) { // Drop leading whitespace. continue; }