markdown parser new way
This commit is contained in:
parent
9eb37da669
commit
dac4c649f7
4 changed files with 196 additions and 177 deletions
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,187 +13,194 @@ 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() }
|
||||||
|
}
|
||||||
|
|
||||||
match block {
|
pub fn get_spans(self) -> Vec<(&'a str, Attrs<'b>)> {
|
||||||
Block::Header(span, level) => {
|
self.spans
|
||||||
for item in span {
|
}
|
||||||
match level {
|
|
||||||
1 => {
|
pub fn run<'block>(&mut self, block: &'block Vec<Block>)
|
||||||
let attrs = attrs.metrics(metrics(24.0));
|
where
|
||||||
attrs.weight(Weight::BOLD);
|
'block: 'b,
|
||||||
result.extend(parse_span(item, attrs));
|
{
|
||||||
|
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));
|
self.spans.push(("\n", Attrs::new()));
|
||||||
attrs.weight(Weight::BOLD);
|
}
|
||||||
result.extend(parse_span(item, attrs));
|
Block::Paragraph(span) => {
|
||||||
}
|
for item in span {
|
||||||
3 => {
|
self.parse_span(item, attrs);
|
||||||
let attrs = attrs.metrics(metrics(20.0));
|
}
|
||||||
attrs.weight(Weight::BOLD);
|
self.spans.push(("\n", Attrs::new()));
|
||||||
result.extend(parse_span(item, attrs));
|
}
|
||||||
}
|
Block::Blockquote(blockquote) => {
|
||||||
4 => {
|
for item in blockquote {
|
||||||
let attrs = attrs.metrics(metrics(18.0));
|
let attrs = attrs.family(Family::Monospace);
|
||||||
attrs.weight(Weight::BOLD);
|
self.parse_block(item, attrs);
|
||||||
result.extend(parse_span(item, attrs));
|
}
|
||||||
}
|
self.spans.push(("\n", Attrs::new()));
|
||||||
5 => {
|
}
|
||||||
let attrs = attrs.metrics(metrics(16.0));
|
Block::CodeBlock(lang, code) => {
|
||||||
attrs.weight(Weight::BOLD);
|
let extension = if let Some(lang) = lang {
|
||||||
result.extend(parse_span(item, attrs));
|
language_to_extension(lang)
|
||||||
}
|
} else {
|
||||||
6 => {
|
"txt"
|
||||||
let attrs = attrs.metrics(metrics(14.0));
|
};
|
||||||
attrs.weight(Weight::BOLD);
|
|
||||||
result.extend(parse_span(item, attrs));
|
let attrs = attrs.family(Family::Monospace);
|
||||||
}
|
let code_block = highlight_code(code, extension, attrs);
|
||||||
_ => result.extend(parse_span(item, 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));
|
Span::Strong(strong) => {
|
||||||
}
|
for item in strong {
|
||||||
Block::Paragraph(span) => {
|
let attrs = attrs.weight(Weight::BOLD);
|
||||||
for item in span {
|
self.parse_span(item, attrs);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
ListItem::Simple(simple) => {
|
||||||
match item {
|
for item in simple {
|
||||||
ListItem::Simple(simple) => {
|
self.parse_span(item, Attrs::new());
|
||||||
for item in simple {
|
}
|
||||||
result.extend(parse_span(item, attrs));
|
|
||||||
}
|
}
|
||||||
}
|
ListItem::Paragraph(block) => {
|
||||||
ListItem::Paragraph(block) => {
|
for item in block {
|
||||||
for item in block {
|
self.parse_block(item, Attrs::new());
|
||||||
result.extend(parse_block(item, attrs));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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",
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue