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> {
let md = widget::text::markdown(self.markdown_text.clone());
let md = widget::text::markdown(&self.markdown_text);
widget::row().push(widget::scrollable(md)).into()
}

View file

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

View file

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