New layout architecture

This commit is contained in:
mars 2022-10-09 02:03:02 -06:00
parent 2aa36d9b02
commit b7684c5f24
5 changed files with 126 additions and 161 deletions

View File

@ -32,6 +32,8 @@ lines.")
(h3 "Styling " (italic "even ") "works " (bold "in ") (strikethrough "headers."))
(h3 "Super long header: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
(ul
(li "Unordered list entry")
(li "Unordered list entry")

115
src/layout.rs Normal file
View File

@ -0,0 +1,115 @@
use crate::style::Stylesheet;
use crate::tag::*;
impl TextTag {
pub fn layout(&self, style: &Stylesheet, available_width: usize) -> Vec<String> {
let styled = self.style(style);
textwrap::wrap(&styled, available_width)
.into_iter()
.map(|s| s.to_string())
.collect()
}
pub fn style_children(&self, style: &Stylesheet) -> String {
let mut string = String::new();
for child in self.children.iter() {
let child_string = match child {
TextNode::Tag(tag) => tag.style(style),
TextNode::Text(string) => string.to_owned(),
};
string.push_str(&child_string);
}
string
}
pub fn style(&self, style: &Stylesheet) -> String {
let string = self.style_children(style);
use TextTagKind::*;
let painter = match self.kind {
Plain => return string,
Underline => style.underline,
Bold => style.bold,
Italic => style.italic,
Strikethrough => style.strikethrough,
};
painter.paint(string).to_string()
}
}
impl BlockTag {
pub fn layout(&self, style: &Stylesheet, available_width: usize) -> Vec<String> {
use BlockTagKind::*;
match self.kind {
H1 | H2 | H3 => {
const INDENT: usize = 4;
let styled = self.style_text_children(style);
let bullets = match self.kind {
H1 => "# ",
H2 => "## ",
H3 => "### ",
_ => unreachable!(),
};
let indent: String = std::iter::repeat(" ").take(INDENT).collect();
let options = textwrap::Options::new(available_width)
.initial_indent(&bullets)
.subsequent_indent(&indent);
let lines = textwrap::wrap(&styled, options);
let mut max_len = 0;
lines
.iter()
.enumerate()
.map(|(index, line)| {
let mut line = line.to_string();
max_len = max_len.max(line.len());
let painter = if index == lines.len() - 1 {
while line.len() < max_len {
line.push(' ');
}
&style.header_style_floor
} else {
&style.header_style_float
};
painter.paint(line).to_string()
})
.collect()
}
P => self.layout_text_children(style, available_width),
_ => unimplemented!(),
}
}
pub fn layout_text_children(&self, style: &Stylesheet, available_width: usize) -> Vec<String> {
let styled = self.style_text_children(style);
textwrap::wrap(&styled, available_width)
.into_iter()
.map(|s| s.to_string())
.collect()
}
pub fn style_text_children(&self, style: &Stylesheet) -> String {
let mut string = String::new();
for child in self.children.iter() {
match child {
BlockNode::Text(tag) => string.push_str(&tag.style(style)),
_ => panic!("{:?} node attempted to style a non-text child", self.kind),
}
}
string
}
}

View File

@ -2,150 +2,13 @@ use std::str::FromStr;
use strum::EnumString;
pub mod dom;
pub mod layout;
pub mod parse;
pub mod style;
pub mod tag;
use style::Stylesheet;
#[derive(Clone, Copy, Debug, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum TagKind {
H1,
H2,
H3,
P,
Underline,
Italic,
Bold,
Strikethrough,
Ul,
Ol,
}
#[derive(Clone, Debug)]
pub struct Tag {
pub kind: TagKind,
pub children: Vec<Node>,
}
impl Tag {
pub fn layout(&self, style: &Stylesheet, available_width: usize) -> Vec<String> {
use TagKind::*;
match self.kind {
H1 | H2 | H3 | P | Underline | Italic | Bold => {
let styled = self.style(style);
textwrap::wrap(&styled, available_width)
.into_iter()
.map(|s| s.to_string())
.collect()
}
Ul => {
if available_width <= style.list_indent {
panic!("Ran out of available width!");
}
let available_width = available_width - style.list_indent;
let styled = self.style_children(style);
let subsequent_indent: String =
std::iter::repeat(" ").take(style.list_indent).collect();
let options = textwrap::Options::new(available_width)
.initial_indent(&style.ul_prefix)
.subsequent_indent(&subsequent_indent);
textwrap::wrap(&styled, options)
.into_iter()
.map(|s| s.to_string())
.collect()
}
_ => unimplemented!(),
}
}
pub fn style_children(&self, style: &Stylesheet) -> String {
let mut string = String::new();
for child in self.children.iter() {
let child_string = match child {
Node::Tag(tag) => tag.style(style),
Node::String(string) => string.to_owned(),
};
string.push_str(&child_string);
}
string
}
pub fn style(&self, style: &Stylesheet) -> String {
use TagKind::*;
let prefix = match self.kind {
H1 => Some(&style.h1_prefix),
H2 => Some(&style.h2_prefix),
H3 => Some(&style.h3_prefix),
_ => None,
};
let string = self.style_children(style);
let style = match self.kind {
H1 => style.h1,
H2 => style.h2,
H3 => style.h3,
P => style.p,
Underline => style.underline,
Bold => style.bold,
Italic => style.italic,
Strikethrough => style.strikethrough,
_ => panic!("Cannot style {:?} tags", self.kind),
};
if let Some(prefix) = prefix {
let string = format!("{}{}", prefix, string);
style.paint(string).to_string()
} else {
style.paint(string).to_string()
}
}
}
#[derive(Clone, Debug)]
pub enum Node {
Tag(Tag),
String(String),
}
pub fn parse_cons(inner: lexpr::Cons) -> Tag {
let mut iter = inner.to_vec().0.into_iter();
let symbol = match iter.next() {
Some(lexpr::Value::Symbol(symbol)) => symbol,
_ => panic!("First element of cons must be a symbol"),
};
let kind = match TagKind::from_str(&symbol) {
Ok(kind) => kind,
_ => panic!("Unrecognized tag kind {}", symbol),
};
let mut children = Vec::new();
for node in iter {
let child = match node {
lexpr::Value::String(string) => Node::String(string.to_string()),
lexpr::Value::Cons(cons) => Node::Tag(parse_cons(cons)),
_ => unimplemented!("Invalid tag argument: {:#?}", node),
};
children.push(child);
}
Tag { kind, children }
}
fn main() {
let src_path = std::env::args().nth(1).unwrap();
let src = std::fs::read_to_string(src_path).unwrap();
@ -156,16 +19,10 @@ fn main() {
println!("{:#?}", expr);
let dom = parse::Parser::parse(&expr);
println!("{:#?}", dom);
let cons = expr.as_cons().unwrap().to_vec().0;
let style = Stylesheet::default();
let width = 40;
let width = 4;
for value in cons {
let tag_cons = value.as_cons().unwrap().to_owned();
let tag = parse_cons(tag_cons);
for tag in dom.tags.iter() {
for line in tag.layout(&style, width) {
println!("{}", line);
}

View File

@ -2,12 +2,9 @@ use ansi_term::Style;
#[derive(Clone, Debug)]
pub struct Stylesheet {
pub h1: Style,
pub h2: Style,
pub h3: Style,
pub h1_prefix: String,
pub h2_prefix: String,
pub h3_prefix: String,
pub header_style_float: Style,
pub header_style_floor: Style,
pub header_prefix: String,
pub p: Style,
pub underline: Style,
pub bold: Style,
@ -22,15 +19,10 @@ impl Default for Stylesheet {
fn default() -> Self {
use ansi_term::Color::*;
let header_style = Blue.bold().underline();
Self {
h1: header_style,
h2: header_style,
h3: header_style,
h1_prefix: "# ".to_string(),
h2_prefix: "## ".to_string(),
h3_prefix: "### ".to_string(),
header_style_float: Blue.bold(),
header_style_floor: Blue.bold().underline(),
header_prefix: "#".to_string(),
p: Style::new(),
underline: Style::new().underline(),
bold: Style::new().bold(),

View File

@ -9,7 +9,6 @@ pub enum TextTagKind {
Bold,
Underline,
Strikethrough,
A,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]