Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: handle code overflow #320

Merged
merged 1 commit into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading