Files
zesdex/src/view/markdown.rs
T

144 lines
5.5 KiB
Rust
Raw Normal View History

use ratatui::style::{Color, Modifier, Style};
use ratatui::text::Span;
pub fn render_markdown<'a>(text: &'a str, width: u16) -> Vec<Span<'a>> {
let mut spans = Vec::new();
let parser = pulldown_cmark::Parser::new(text);
let mut in_code_block = false;
for event in parser {
match event {
pulldown_cmark::Event::Start(tag) => {
match tag {
pulldown_cmark::Tag::CodeBlock(_) => {
in_code_block = true;
spans.push(Span::styled(
"```\n",
Style::default().fg(Color::DarkGray),
));
}
pulldown_cmark::Tag::Heading { level, .. } => {
let color = match level {
pulldown_cmark::HeadingLevel::H1 => Color::LightCyan,
pulldown_cmark::HeadingLevel::H2 => Color::Cyan,
_ => Color::White,
};
let prefix = match level {
pulldown_cmark::HeadingLevel::H1 => "# ",
pulldown_cmark::HeadingLevel::H2 => "## ",
pulldown_cmark::HeadingLevel::H3 => "### ",
_ => "# ",
};
spans.push(Span::styled(
prefix,
Style::default().fg(color).add_modifier(Modifier::BOLD),
));
}
pulldown_cmark::Tag::Paragraph => {}
pulldown_cmark::Tag::Emphasis => {}
pulldown_cmark::Tag::Strong => {}
pulldown_cmark::Tag::List(_) => {}
pulldown_cmark::Tag::Item => {
spans.push(Span::styled(
" * ",
Style::default().fg(Color::DarkGray),
));
}
pulldown_cmark::Tag::Link { dest_url, .. } => {
spans.push(Span::styled(
"[",
Style::default().fg(Color::Cyan),
));
spans.push(Span::styled(
format!("]({})", dest_url),
Style::default().fg(Color::Blue),
));
}
pulldown_cmark::Tag::BlockQuote(_) => {
spans.push(Span::styled(
"> ",
Style::default().fg(Color::DarkGray),
));
}
_ => {}
}
}
pulldown_cmark::Event::End(tag) => {
match tag {
pulldown_cmark::TagEnd::CodeBlock => {
in_code_block = false;
spans.push(Span::styled(
"\n```\n",
Style::default().fg(Color::DarkGray),
));
}
pulldown_cmark::TagEnd::Heading(_) => {
spans.push(Span::raw("\n"));
}
pulldown_cmark::TagEnd::Paragraph => {
spans.push(Span::raw("\n\n"));
}
pulldown_cmark::TagEnd::Emphasis => {}
pulldown_cmark::TagEnd::Strong => {}
pulldown_cmark::TagEnd::List(_) => {}
pulldown_cmark::TagEnd::Item => {
spans.push(Span::raw("\n"));
}
pulldown_cmark::TagEnd::Link => {}
pulldown_cmark::TagEnd::BlockQuote(_) => {
spans.push(Span::raw("\n"));
}
_ => {}
}
}
pulldown_cmark::Event::Text(text) => {
let s = text.to_string();
if in_code_block {
spans.push(Span::styled(
s,
Style::default().fg(Color::Green),
));
} else {
spans.push(Span::raw(s));
}
}
pulldown_cmark::Event::Code(text) => {
spans.push(Span::styled(
format!("`{}`", text),
Style::default().fg(Color::Green),
));
}
pulldown_cmark::Event::SoftBreak => {
spans.push(Span::raw("\n"));
}
pulldown_cmark::Event::HardBreak => {
spans.push(Span::raw("\n"));
}
_ => {}
}
}
if width > 0 {
let mut spans_out = Vec::new();
let mut line_len = 0;
for span in &spans {
let s = span.content.clone();
let text = s.as_ref();
let remaining = text.len();
if line_len + remaining > width as usize && line_len > 0 {
spans_out.push(Span::raw("\n"));
line_len = 0;
}
spans_out.push(Span::raw(text.to_string()));
if !text.contains('\n') {
line_len += remaining;
} else {
line_len = text.split('\n').next_back().unwrap_or("").len();
}
}
spans = spans_out;
}
spans
}