markdown parser new way

This commit is contained in:
Piotr 2024-06-15 22:02:23 +02:00
parent 9eb37da669
commit dac4c649f7
4 changed files with 196 additions and 177 deletions

View file

@ -38,7 +38,7 @@ impl Application for Window {
} }
fn view(&self) -> cosmic::prelude::Element<Self::Message> { fn view(&self) -> cosmic::prelude::Element<Self::Message> {
let md = widget::text::markdown(self.markdown_text.clone()); let md = widget::text::markdown(&self.markdown_text);
widget::row().push(widget::scrollable(md)).into() widget::row().push(widget::scrollable(md)).into()
} }

View file

@ -2,6 +2,7 @@ pub mod parser;
use std::sync::{Mutex, OnceLock}; use std::sync::{Mutex, OnceLock};
use crate::{Element, Renderer};
use cosmic_text::{ use cosmic_text::{
Attrs, Buffer, Color, Edit, Editor, Family, FontSystem, Metrics, Shaping, SwashCache, Weight, Attrs, Buffer, Color, Edit, Editor, Family, FontSystem, Metrics, Shaping, SwashCache, Weight,
}; };
@ -11,8 +12,7 @@ use iced_core::{
widget::{self, tree}, widget::{self, tree},
Widget, Widget,
}; };
use markdown::tokenize;
use crate::{Element, Renderer};
static FONT_SYSTEM: OnceLock<Mutex<FontSystem>> = OnceLock::new(); static FONT_SYSTEM: OnceLock<Mutex<FontSystem>> = OnceLock::new();
@ -23,15 +23,25 @@ pub struct Markdown {
} }
impl Markdown { impl Markdown {
pub fn new(content: String) -> Self { pub fn new(content: &str) -> Self {
FONT_SYSTEM.get_or_init(|| Mutex::new(FontSystem::new())); FONT_SYSTEM.get_or_init(|| Mutex::new(FontSystem::new()));
let metrics = metrics(14.0); let metrics = metrics(14.0);
let buffer = Buffer::new_empty(metrics); let buffer = Buffer::new_empty(metrics);
let mut editor = Editor::new(buffer); let mut editor = Editor::new(buffer);
let mut parser = parser::Parser::new();
let blocks = tokenize(content);
editor.with_buffer_mut(|buffer| set_buffer_text(&content, buffer)); parser.run(Box::leak(Box::new(blocks)));
editor.with_buffer_mut(|buffer| {
set_buffer_text(
&mut FONT_SYSTEM.get().unwrap().lock().unwrap(),
&mut parser.get_spans(),
buffer,
)
});
Self { Self {
syntax_editor: Mutex::new(editor), syntax_editor: Mutex::new(editor),
@ -326,15 +336,17 @@ fn metrics(font_size: f32) -> Metrics {
Metrics::new(font_size, line_height) Metrics::new(font_size, line_height)
} }
fn set_buffer_text<'a>(text: &'a str, buffer: &mut Buffer) { fn set_buffer_text(
font_system: &mut FontSystem,
collect_spans: &mut [(&'static str, Attrs)],
buffer: &mut Buffer,
) {
let attrs = Attrs::new(); let attrs = Attrs::new();
attrs.family(Family::SansSerif); attrs.family(Family::SansSerif);
let spans: Vec<(&'a str, Attrs)> = parser::markdown_to_string(text, attrs);
buffer.set_rich_text( buffer.set_rich_text(
&mut FONT_SYSTEM.get().unwrap().lock().unwrap(), font_system,
spans.iter().copied(), collect_spans.iter().copied(),
attrs, attrs,
Shaping::Advanced, Shaping::Advanced,
) )

View file

@ -13,26 +13,36 @@ use syntect::{
use super::metrics; use super::metrics;
pub fn markdown_to_string<'a, 'b>(content: &'a str, attrs: Attrs<'b>) -> Vec<(&'a str, Attrs<'b>)> pub struct Parser<'a, 'b> {
where spans: Vec<(&'a str, Attrs<'b>)>,
'b: 'a,
{
let mut result: Vec<(&'a str, Attrs)> = Vec::new();
for block in tokenize(content) {
result.extend(parse_block(block, attrs));
result.push(("\n", attrs));
}
result
} }
fn parse_block<'a, 'b>(block: Block, attrs: Attrs<'b>) -> Vec<(&'a str, Attrs<'b>)> impl<'a, 'b> Parser<'a, 'b>
where where
'b: 'a, 'b: 'a,
{ {
let mut result: Vec<(&'a str, Attrs)> = Vec::new(); pub fn new() -> Self {
Self { spans: Vec::new() }
}
pub fn get_spans(self) -> Vec<(&'a str, Attrs<'b>)> {
self.spans
}
pub fn run<'block>(&mut self, block: &'block Vec<Block>)
where
'block: 'b,
{
for block in block {
self.parse_block(block, Attrs::new());
self.spans.push(("\n", Attrs::new()));
}
}
fn parse_block<'block>(&mut self, block: &'block Block, attrs: Attrs<'block>)
where
'block: 'b,
{
match block { match block {
Block::Header(span, level) => { Block::Header(span, level) => {
for item in span { for item in span {
@ -40,160 +50,157 @@ where
1 => { 1 => {
let attrs = attrs.metrics(metrics(24.0)); let attrs = attrs.metrics(metrics(24.0));
attrs.weight(Weight::BOLD); attrs.weight(Weight::BOLD);
result.extend(parse_span(item, attrs)); self.parse_span(item, attrs);
} }
2 => { 2 => {
let attrs = attrs.metrics(metrics(22.0)); let attrs = attrs.metrics(metrics(22.0));
attrs.weight(Weight::BOLD); attrs.weight(Weight::BOLD);
result.extend(parse_span(item, attrs)); self.parse_span(item, attrs);
} }
3 => { 3 => {
let attrs = attrs.metrics(metrics(20.0)); let attrs = attrs.metrics(metrics(20.0));
attrs.weight(Weight::BOLD); attrs.weight(Weight::BOLD);
result.extend(parse_span(item, attrs)); self.parse_span(item, attrs);
} }
4 => { 4 => {
let attrs = attrs.metrics(metrics(18.0)); let attrs = attrs.metrics(metrics(18.0));
attrs.weight(Weight::BOLD); attrs.weight(Weight::BOLD);
result.extend(parse_span(item, attrs)); self.parse_span(item, attrs);
} }
5 => { 5 => {
let attrs = attrs.metrics(metrics(16.0)); let attrs = attrs.metrics(metrics(16.0));
attrs.weight(Weight::BOLD); attrs.weight(Weight::BOLD);
result.extend(parse_span(item, attrs)); self.parse_span(item, attrs);
} }
6 => { 6 => {
let attrs = attrs.metrics(metrics(14.0)); let attrs = attrs.metrics(metrics(14.0));
attrs.weight(Weight::BOLD); attrs.weight(Weight::BOLD);
result.extend(parse_span(item, attrs)); self.parse_span(item, attrs);
} }
_ => result.extend(parse_span(item, attrs)), _ => self.parse_span(item, Attrs::new()),
} }
} }
result.push(("\n", attrs)); self.spans.push(("\n", Attrs::new()));
} }
Block::Paragraph(span) => { Block::Paragraph(span) => {
for item in span { for item in span {
result.extend(parse_span(item, attrs)); self.parse_span(item, attrs);
} }
result.push(("\n", attrs)); self.spans.push(("\n", Attrs::new()));
} }
Block::Blockquote(blockquote) => { Block::Blockquote(blockquote) => {
for item in blockquote { for item in blockquote {
let attrs = attrs.family(Family::Monospace); let attrs = attrs.family(Family::Monospace);
result.extend(parse_block(item, attrs)); self.parse_block(item, attrs);
} }
result.push(("\n", attrs)); self.spans.push(("\n", Attrs::new()));
} }
Block::CodeBlock(lang, code) => { Block::CodeBlock(lang, code) => {
if let Some(lang) = lang { let extension = if let Some(lang) = lang {
let code_block = highlight_code( language_to_extension(lang)
Box::leak(code.into_boxed_str()),
language_to_extension(lang),
attrs,
);
result.extend(code_block);
} else { } else {
result.push((Box::leak(code.into_boxed_str()), attrs)); "txt"
}; };
result.push(("\n", attrs)); let attrs = attrs.family(Family::Monospace);
let code_block = highlight_code(code, extension, attrs);
self.spans.extend(code_block);
self.spans.push(("\n", attrs));
} }
Block::OrderedList(listitem, _type) => { Block::OrderedList(listitem, _type) => {
for (num, item) in listitem.iter().enumerate() { for (num, item) in listitem.iter().enumerate() {
let attrs = attrs.family(Family::Serif); let attrs = attrs.family(Family::Serif);
result.push((Box::leak(format!("{}. ", num + 1).into_boxed_str()), attrs)); self.spans
result.extend(parse_listitem(item.to_owned(), attrs)); .push((Box::leak(Box::new(format!("{}. ", num + 1))), attrs));
result.push(("\n", attrs)); self.parse_listitem(item);
self.spans.push(("\n", attrs));
} }
result.push(("\n", attrs)); self.spans.push(("\n", Attrs::new()));
} }
Block::UnorderedList(listitem) => { Block::UnorderedList(listitem) => {
for item in listitem { for item in listitem {
let attrs = attrs.family(Family::Serif); let attrs = attrs.family(Family::Serif);
result.push((" - ", attrs)); self.spans.push((" - ", attrs));
result.extend(parse_listitem(item.to_owned(), attrs)); self.parse_listitem(item);
result.push(("\n", attrs)); self.spans.push(("\n", attrs));
} }
result.push(("\n", attrs)); self.spans.push(("\n", Attrs::new()));
} }
Block::Raw(raw_text) => { Block::Raw(raw_text) => {
result.push((Box::leak(raw_text.into_boxed_str()), attrs)); self.spans.push((raw_text, Attrs::new()));
self.spans.push(("\n", Attrs::new()));
result.push(("\n", attrs)); }
Block::Hr => self.spans.push(("\n", Attrs::new())),
} }
Block::Hr => result.push(("\n", attrs)),
} }
result fn parse_span<'c>(&mut self, span: &'c Span, attrs: Attrs<'c>)
} where
'c: 'b,
fn parse_span<'a>(span: Span, attrs: Attrs<'a>) -> Vec<(&'a str, Attrs)> { {
let mut result: Vec<(&'a str, Attrs)> = Vec::new();
match span { match span {
Span::Break => result.push(("\n", attrs)), Span::Break => self.spans.push(("\n", attrs)),
Span::Text(text) => result.push((Box::leak(text.into_boxed_str()), attrs)), Span::Text(text) => self.spans.push((text, attrs)),
Span::Code(code) => { Span::Code(code) => {
attrs.family(Family::Monospace); let attrs = attrs.family(Family::Monospace);
result.push((Box::leak(code.into_boxed_str()), attrs)); self.spans.push((code, attrs));
} }
Span::Link(name, url, _title) => { Span::Link(name, url, _title) => {
let color = Attrs::new().color(link_color());
let spans: &[(&str, Attrs)] = &[ let spans: &[(&str, Attrs)] = &[
(Box::leak(format!("{}: ", name).into_boxed_str()), attrs), (Box::leak(format!("{}: ", name).into_boxed_str()), attrs),
(Box::leak(url.into_boxed_str()), attrs.color(link_color())), (url, color),
]; ];
result.extend_from_slice(spans); self.spans.extend_from_slice(spans);
} }
Span::Image(alt, url, title) => { Span::Image(alt, url, title) => {
let color = Attrs::new().color(link_color());
let spans: &[(&str, Attrs)] = if let Some(title) = title { let spans: &[(&str, Attrs)] = if let Some(title) = title {
&[ &[
(Box::leak(format!("{}: ", title).into_boxed_str()), attrs), (Box::leak(format!("{}: ", title).into_boxed_str()), attrs),
(Box::leak(url.into_boxed_str()), attrs.color(link_color())), (url, color),
] ]
} else { } else {
&[ &[
(Box::leak(format!("{}: ", alt).into_boxed_str()), attrs), (Box::leak(format!("{}: ", alt).into_boxed_str()), attrs),
(Box::leak(url.into_boxed_str()), attrs.color(link_color())), (url, color),
] ]
}; };
result.extend_from_slice(spans); self.spans.extend_from_slice(spans);
} }
Span::Emphasis(emphasis) => { Span::Emphasis(emphasis) => {
for item in emphasis { for item in emphasis {
let attrs = attrs.family(Family::Cursive); let attrs = attrs.family(Family::Cursive);
result.extend(parse_span(item, attrs)); self.parse_span(item, attrs);
} }
} }
Span::Strong(strong) => { Span::Strong(strong) => {
for item in strong { for item in strong {
let attrs = attrs.weight(Weight::BOLD); let attrs = attrs.weight(Weight::BOLD);
result.extend(parse_span(item, attrs)); self.parse_span(item, attrs);
}
} }
} }
} }
result fn parse_listitem<'d>(&mut self, item: &'d ListItem)
} where
'd: 'b,
fn parse_listitem<'a>(item: ListItem, attrs: Attrs<'a>) -> Vec<(&'a str, Attrs)> { {
let mut result: Vec<(&'a str, Attrs)> = Vec::new();
match item { match item {
ListItem::Simple(simple) => { ListItem::Simple(simple) => {
for item in simple { for item in simple {
result.extend(parse_span(item, attrs)); self.parse_span(item, Attrs::new());
} }
} }
ListItem::Paragraph(block) => { ListItem::Paragraph(block) => {
for item in block { for item in block {
result.extend(parse_block(item, attrs)); self.parse_block(item, Attrs::new());
}
} }
} }
} }
result
} }
fn highlight_code<'a>( fn highlight_code<'a>(
@ -276,8 +283,8 @@ fn cosmic_syntax() -> ThemeSet {
theme_set theme_set
} }
fn language_to_extension(lang: String) -> &'static str { fn language_to_extension(lang: &str) -> &'static str {
match lang.as_str() { match lang {
"python" => "py", "python" => "py",
"javascript" => "js", "javascript" => "js",
"java" => "java", "java" => "java",

View file

@ -93,7 +93,7 @@ pub fn monotext<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme
#[cfg(feature = "markdown")] #[cfg(feature = "markdown")]
/// [`Markdown`] widget with md text parser /// [`Markdown`] widget with md text parser
pub fn markdown(content: String) -> crate::widget::markdown::Markdown { pub fn markdown(content: &str) -> crate::widget::markdown::Markdown {
use crate::widget::markdown; use crate::widget::markdown;
markdown::Markdown::new(content) markdown::Markdown::new(content)