use crate::syntax::{Highlight, Highlighter};
use logos::Logos;
use ratatui::style::Color;
#[derive(Logos, Debug)]
#[logos(skip r"[ \t\n]+")]
enum MarkdownToken {
#[regex("`[^`]+`")]
Code,
#[regex(r"\*[^*]+\*")]
Emphasis,
Heading,
#[regex(r"\[[^]]+\]\([^)]+\)")]
Url,
#[regex(r"\[[^]]+\]")]
Link,
}
impl TryFrom<MarkdownToken> for Highlight {
type Error = ();
fn try_from(t: MarkdownToken) -> Result<Highlight, ()> {
use crate::syntax::Modifier;
match t {
MarkdownToken::Code => Ok(Highlight {
color: None,
modifier: Modifier::Italic,
}),
MarkdownToken::Emphasis => Ok(Highlight {
color: None,
modifier: Modifier::Bold,
}),
MarkdownToken::Heading => Ok(Highlight {
color: Some(Color::Blue),
modifier: Modifier::Underlined,
}),
MarkdownToken::Url => Ok(Color::Blue.into()),
MarkdownToken::Link => Ok(Color::Magenta.into()),
}
}
}
#[derive(Debug)]
pub struct Markdown;
impl std::fmt::Display for Markdown {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
"Markdown".fmt(f)
}
}
impl Highlighter for Markdown {
fn highlight<'s>(
&self,
s: &'s str,
_state: &'s mut crate::syntax::HighlightState,
) -> Box<dyn Iterator<Item = (Highlight, std::ops::Range<usize>)> + 's> {
if s.starts_with('#') {
Box::new(
Highlight::try_from(MarkdownToken::Heading)
.ok()
.map(|h| (h, 0..s.len()))
.into_iter(),
)
} else if s.starts_with(" ") || s.starts_with('\t') {
Box::new(
Highlight::try_from(MarkdownToken::Code)
.ok()
.map(|h| (h, 0..s.len()))
.into_iter(),
)
} else {
Box::new(MarkdownToken::lexer(s).spanned().filter_map(|(t, r)| {
t.ok()
.and_then(|t| Highlight::try_from(t).ok())
.map(|c| (c, r))
}))
}
}
}