Skip to content

Commit

Permalink
feat: handle code overflow
Browse files Browse the repository at this point in the history
  • Loading branch information
mfontanini committed Aug 2, 2024
1 parent 7dba250 commit e45f21a
Show file tree
Hide file tree
Showing 17 changed files with 304 additions and 288 deletions.
10 changes: 5 additions & 5 deletions src/ansi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl AnsiSplitter {
for p in line.ansi_parse() {
match p {
Output::TextBlock(text) => {
self.current_line.0.push(Text::new(text, self.current_style.clone()));
self.current_line.0.push(Text::new(text, self.current_style));
}
Output::Escape(s) => self.handle_escape(&s),
}
Expand Down Expand Up @@ -70,10 +70,10 @@ impl<'a> GraphicsCode<'a> {
for value in codes {
match value {
0 => *style = TextStyle::default(),
1 => *style = style.clone().bold(),
3 => *style = style.clone().italics(),
4 => *style = style.clone().underlined(),
9 => *style = style.clone().strikethrough(),
1 => *style = (*style).bold(),
3 => *style = (*style).italics(),
4 => *style = (*style).underlined(),
9 => *style = (*style).strikethrough(),
30 => style.colors.foreground = Some(Color::Black),
40 => style.colors.background = Some(Color::Black),
31 => style.colors.foreground = Some(Color::Red),
Expand Down
10 changes: 5 additions & 5 deletions src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,8 @@ where
mod test {
use super::*;
use crate::{
presentation::{
AsRenderOperations, BlockLine, BlockLineText, RenderAsync, RenderAsyncState, Slide, SlideBuilder,
},
markdown::text::WeightedTextBlock,
presentation::{AsRenderOperations, BlockLine, RenderAsync, RenderAsyncState, Slide, SlideBuilder},
render::properties::WindowSize,
style::{Color, Colors},
theme::{Alignment, Margin},
Expand Down Expand Up @@ -156,10 +155,11 @@ mod test {
#[case(RenderOperation::RenderText{line: String::from("asd").into(), alignment: Default::default()})]
#[case(RenderOperation::RenderBlockLine(
BlockLine{
text: BlockLineText::Preformatted("".into()),
prefix: "".into(),
text: WeightedTextBlock::from("".to_string()),
alignment: Default::default(),
block_length: 42,
unformatted_length: 1337
block_color: None,
}
))]
#[case(RenderOperation::RenderDynamic(Rc::new(Dynamic)))]
Expand Down
10 changes: 5 additions & 5 deletions src/markdown/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,14 @@ impl<'a> InlinesParser<'a> {
let data = node.data.borrow();
match &data.value {
NodeValue::Text(text) => {
self.pending_text.push(Text::new(text.clone(), style.clone()));
self.pending_text.push(Text::new(text.clone(), style));
}
NodeValue::Code(code) => {
self.pending_text.push(Text::new(code.literal.clone(), TextStyle::default().code()));
}
NodeValue::Strong => self.process_children(node, style.clone().bold())?,
NodeValue::Emph => self.process_children(node, style.clone().italics())?,
NodeValue::Strikethrough => self.process_children(node, style.clone().strikethrough())?,
NodeValue::Strong => self.process_children(node, style.bold())?,
NodeValue::Emph => self.process_children(node, style.italics())?,
NodeValue::Strikethrough => self.process_children(node, style.strikethrough())?,
NodeValue::SoftBreak => self.pending_text.push(Text::from(" ")),
NodeValue::Link(link) => self.pending_text.push(Text::new(link.url.clone(), TextStyle::default().link())),
NodeValue::LineBreak => {
Expand Down Expand Up @@ -381,7 +381,7 @@ impl<'a> InlinesParser<'a> {

fn process_children(&mut self, node: &'a AstNode<'a>, style: TextStyle) -> ParseResult<()> {
for node in node.children() {
self.process_node(node, style.clone())?;
self.process_node(node, style)?;
}
Ok(())
}
Expand Down
81 changes: 49 additions & 32 deletions src/markdown/text.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
use super::elements::{Text, TextBlock};
use crate::style::TextStyle;
use std::mem;
use unicode_width::UnicodeWidthChar;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

/// A weighted block of text.
///
/// The weight of a character is its given by its width in unicode.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct WeightedTextBlock(Vec<WeightedText>);
pub(crate) struct WeightedTextBlock {
text: Vec<WeightedText>,
width: usize,
}

impl WeightedTextBlock {
/// Split this line into chunks of at most `max_length` width.
pub(crate) fn split(&self, max_length: usize) -> SplitTextIter {
SplitTextIter::new(&self.0, max_length)
SplitTextIter::new(&self.text, max_length)
}

/// The total width of this line.
pub(crate) fn width(&self) -> usize {
self.0.iter().map(|text| text.width()).sum()
self.width
}

/// Get an iterator to the underlying text chunks.
#[cfg(test)]
pub(crate) fn iter_texts(&self) -> impl Iterator<Item = &WeightedText> {
self.0.iter()
self.text.iter()
}
}

Expand All @@ -37,6 +40,7 @@ impl From<Vec<Text>> for WeightedTextBlock {
fn from(mut texts: Vec<Text>) -> Self {
let mut output = Vec::new();
let mut index = 0;
let mut width = 0;
// Compact chunks so any consecutive chunk with the same style is merged into the same block.
while index < texts.len() {
let mut target = mem::replace(&mut texts[index], Text::from(""));
Expand All @@ -46,17 +50,25 @@ impl From<Vec<Text>> for WeightedTextBlock {
target.content.push_str(&current_content);
current += 1;
}
width += target.content.width();
output.push(target.into());
index = current;
}
Self(output)
Self { text: output, width }
}
}

impl From<String> for WeightedTextBlock {
fn from(text: String) -> Self {
let texts = vec![WeightedText::from(text)];
Self(texts)
let width = text.width();
let text = vec![WeightedText::from(text)];
Self { text, width }
}
}

impl From<&str> for WeightedTextBlock {
fn from(text: &str) -> Self {
Self::from(text.to_string())
}
}

Expand All @@ -75,14 +87,13 @@ pub(crate) struct WeightedText {

impl WeightedText {
fn to_ref(&self) -> WeightedTextRef {
WeightedTextRef { text: &self.text.content, accumulators: &self.accumulators, style: self.text.style.clone() }
WeightedTextRef { text: &self.text.content, accumulators: &self.accumulators, style: self.text.style }
}

pub(crate) fn width(&self) -> usize {
self.accumulators.last().map(|a| a.width).unwrap_or(0)
self.to_ref().width()
}

#[cfg(test)]
pub(crate) fn text(&self) -> &Text {
&self.text
}
Expand Down Expand Up @@ -197,7 +208,7 @@ impl<'a> WeightedTextRef<'a> {
let leading_char_count = self.text[0..from].chars().count();
let output_char_count = text.chars().count();
let character_lengths = &self.accumulators[leading_char_count..leading_char_count + output_char_count + 1];
WeightedTextRef { text, accumulators: character_lengths, style: self.style.clone() }
WeightedTextRef { text, accumulators: character_lengths, style: self.style }
}

fn trim_start(self) -> Self {
Expand Down Expand Up @@ -304,63 +315,69 @@ mod test {

#[test]
fn split_at_full_length() {
let text = WeightedTextBlock(vec![WeightedText::from("hello world")]);
let text = WeightedTextBlock::from("hello world");
let lines = join_lines(text.split(11));
let expected = vec!["hello world"];
assert_eq!(lines, expected);
}

#[test]
fn no_split_necessary() {
let text = WeightedTextBlock(vec![WeightedText::from("short"), WeightedText::from("text")]);
let text = WeightedTextBlock { text: vec![WeightedText::from("short"), WeightedText::from("text")], width: 0 };
let lines = join_lines(text.split(50));
let expected = vec!["short text"];
assert_eq!(lines, expected);
}

#[test]
fn split_lines_single() {
let text = WeightedTextBlock(vec![WeightedText::from("this is a slightly long line")]);
let text = WeightedTextBlock { text: vec![WeightedText::from("this is a slightly long line")], width: 0 };
let lines = join_lines(text.split(6));
let expected = vec!["this", "is a", "slight", "ly", "long", "line"];
assert_eq!(lines, expected);
}

#[test]
fn split_lines_multi() {
let text = WeightedTextBlock(vec![
WeightedText::from("this is a slightly long line"),
WeightedText::from("another chunk"),
WeightedText::from("yet some other piece"),
]);
let text = WeightedTextBlock {
text: vec![
WeightedText::from("this is a slightly long line"),
WeightedText::from("another chunk"),
WeightedText::from("yet some other piece"),
],
width: 0,
};
let lines = join_lines(text.split(10));
let expected = vec!["this is a", "slightly", "long line", "another", "chunk yet", "some other", "piece"];
assert_eq!(lines, expected);
}

#[test]
fn long_splits() {
let text = WeightedTextBlock(vec![
WeightedText::from("this is a slightly long line"),
WeightedText::from("another chunk"),
WeightedText::from("yet some other piece"),
]);
let text = WeightedTextBlock {
text: vec![
WeightedText::from("this is a slightly long line"),
WeightedText::from("another chunk"),
WeightedText::from("yet some other piece"),
],
width: 0,
};
let lines = join_lines(text.split(50));
let expected = vec!["this is a slightly long line another chunk yet some", "other piece"];
assert_eq!(lines, expected);
}

#[test]
fn prefixed_by_whitespace() {
let text = WeightedTextBlock(vec![WeightedText::from(" * bullet")]);
let text = WeightedTextBlock::from(" * bullet");
let lines = join_lines(text.split(50));
let expected = vec![" * bullet"];
assert_eq!(lines, expected);
}

#[test]
fn utf8_character() {
let text = WeightedTextBlock(vec![WeightedText::from("• A")]);
let text = WeightedTextBlock::from("• A");
let lines = join_lines(text.split(50));
let expected = vec!["• A"];
assert_eq!(lines, expected);
Expand All @@ -369,7 +386,7 @@ mod test {
#[test]
fn many_utf8_characters() {
let content = "█████ ██";
let text = WeightedTextBlock(vec![WeightedText::from(content)]);
let text = WeightedTextBlock::from(content);
let lines = join_lines(text.split(3));
let expected = vec!["███", "██", "██"];
assert_eq!(lines, expected);
Expand All @@ -378,7 +395,7 @@ mod test {
#[test]
fn no_whitespaces_ascii() {
let content = "X".repeat(10);
let text = WeightedTextBlock(vec![WeightedText::from(content)]);
let text = WeightedTextBlock::from(content);
let lines = join_lines(text.split(3));
let expected = vec!["XXX", "XXX", "XXX", "X"];
assert_eq!(lines, expected);
Expand All @@ -387,7 +404,7 @@ mod test {
#[test]
fn no_whitespaces_utf8() {
let content = "─".repeat(10);
let text = WeightedTextBlock(vec![WeightedText::from(content)]);
let text = WeightedTextBlock::from(content);
let lines = join_lines(text.split(3));
let expected = vec!["───", "───", "───", "─"];
assert_eq!(lines, expected);
Expand All @@ -396,7 +413,7 @@ mod test {
#[test]
fn wide_characters() {
let content = "Hello world";
let text = WeightedTextBlock(vec![WeightedText::from(content)]);
let text = WeightedTextBlock::from(content);
let lines = join_lines(text.split(10));
// Each word is 10 characters long
let expected = vec!["Hello", "world"];
Expand All @@ -411,6 +428,6 @@ mod test {
#[case::split_merged(&["hello".into(), Text::new(" ", TextStyle::default().bold()), Text::new("w", TextStyle::default().bold()), "orld".into()], 3)]
fn compaction(#[case] texts: &[Text], #[case] expected: usize) {
let block = WeightedTextBlock::from(texts.to_vec());
assert_eq!(block.0.len(), expected);
assert_eq!(block.text.len(), expected);
}
}
13 changes: 4 additions & 9 deletions src/presentation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
custom::OptionsConfig,
markdown::text::WeightedTextBlock,
markdown::text::{WeightedText, WeightedTextBlock},
media::image::Image,
render::properties::WindowSize,
style::{Color, Colors},
Expand Down Expand Up @@ -554,18 +554,13 @@ pub(crate) struct PresentationThemeMetadata {
/// A line of preformatted text to be rendered.
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct BlockLine {
pub(crate) text: BlockLineText,
pub(crate) unformatted_length: u16,
pub(crate) prefix: WeightedText,
pub(crate) text: WeightedTextBlock,
pub(crate) block_length: u16,
pub(crate) block_color: Option<Color>,
pub(crate) alignment: Alignment,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum BlockLineText {
Preformatted(String),
Weighted(WeightedTextBlock),
}

/// A render operation.
///
/// Render operations are primitives that allow the input markdown file to be decoupled with what
Expand Down
Loading

0 comments on commit e45f21a

Please sign in to comment.