use crate::syntax::{
Commenting, Highlight, HighlightState, Highlighter, MultiCommentType, Plain, color,
};
use logos::Logos;
use ratatui::style::Color;
#[derive(Logos, Debug)]
#[logos(skip r"[ \t\n]+")]
enum PythonToken {
#[token("and")]
#[token("as")]
#[token("assert")]
#[token("async")]
#[token("await")]
#[token("class")]
#[token("def")]
#[token("del")]
#[token("from")]
#[token("global")]
#[token("import")]
#[token("in")]
#[token("is")]
#[token("lambda")]
#[token("nonlocal")]
#[token("not")]
#[token("or")]
#[token("with")]
Keyword,
#[token("break")]
#[token("continue")]
#[token("elif")]
#[token("else")]
#[token("except")]
#[token("finally")]
#[token("for")]
#[token("if")]
#[token("match")]
#[token("case")]
#[token("pass")]
#[token("try")]
#[token("raise")]
#[token("while")]
#[token("yield")]
#[token("return")]
Flow,
#[token("True")]
#[token("False")]
#[token("None")]
Literal,
#[regex("@[[:alpha:]_][[:alnum:]_.]*")]
Decorator,
#[regex(r#"\"([^\\\"]|\\.)*\""#)]
#[regex(r"'([^\\']|\\.)*'")]
String,
#[token("\"\"\"")]
#[token("'''")]
MultiLineString,
#[regex("#.*", allow_greedy = true)]
Comment,
#[regex("[[:alpha:]_][[:alnum:]_.]*")]
Variable,
}
impl TryFrom<PythonToken> for Highlight {
type Error = ();
fn try_from(t: PythonToken) -> Result<Highlight, ()> {
match t {
PythonToken::Keyword => Ok(color::KEYWORD),
PythonToken::Flow => Ok(color::FLOW),
PythonToken::Literal => Ok(Color::LightMagenta.into()),
PythonToken::Decorator => Ok(Color::Cyan.into()),
PythonToken::String | PythonToken::MultiLineString => Ok(color::STRING),
PythonToken::Comment => Ok(color::COMMENT),
PythonToken::Variable => Err(()),
}
}
}
#[derive(Debug)]
pub struct Python;
impl std::fmt::Display for Python {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
"Python".fmt(f)
}
}
impl Plain for PythonToken {
fn is_comment_start(&self) -> bool {
matches!(self, Self::MultiLineString)
}
}
impl Commenting for PythonToken {
fn is_comment_end(&self) -> bool {
matches!(self, Self::MultiLineString)
}
}
#[derive(Logos, Debug)]
#[logos(skip r"[ \t\n]+")]
enum MultiLineString {
#[token("\"\"\"")]
#[token("'''")]
StartEnd,
}
impl From<MultiLineString> for PythonToken {
fn from(s: MultiLineString) -> Self {
match s {
MultiLineString::StartEnd => Self::MultiLineString,
}
}
}
impl Commenting for MultiLineString {
fn is_comment_end(&self) -> bool {
true
}
}
#[derive(Logos, Debug)]
#[logos(skip r"[ \t\n]+")]
enum PythonDef {
#[regex("def [[:alpha:]_][[:alnum:]_.]*")]
#[regex("class [[:alpha:]_][[:alnum:]_.]*")]
Definition,
}
impl Highlighter for Python {
fn highlight<'s>(
&self,
s: &'s str,
state: &'s mut crate::syntax::HighlightState,
) -> Box<dyn Iterator<Item = (Highlight, std::ops::Range<usize>)> + 's> {
use crate::syntax::EitherLexer;
let lexer: EitherLexer<PythonToken, MultiLineString> = EitherLexer::new(state, s);
Box::new(lexer.filter_map(move |(t, r)| {
match state {
HighlightState::Normal => t
.ok()
.inspect(|t| {
if t.is_comment_start() {
*state = HighlightState::Commenting;
}
})
.and_then(|t| Highlight::try_from(t).ok())
.map(|c| (c, r)),
HighlightState::Commenting => Some(match t {
Ok(end) if end.is_comment_end() => {
*state = HighlightState::default();
(Highlight::try_from(end).ok()?, r)
}
_ => (color::STRING, r),
}),
}
}))
}
fn underline(
&self,
) -> Option<for<'s> fn(&'s str) -> Box<dyn Iterator<Item = std::ops::Range<usize>> + 's>> {
Some(|s| {
Box::new(
PythonDef::lexer(s)
.spanned()
.filter_map(|(t, r)| t.ok().map(|_| r)),
)
})
}
fn multicomment(&self) -> Option<MultiCommentType> {
Some(MultiCommentType::Unidirectional(|acc, s| {
MultiLineString::lexer(s).fold(acc, |acc, s| match s {
Ok(MultiLineString::StartEnd) => match acc {
HighlightState::Normal => HighlightState::Commenting,
HighlightState::Commenting => HighlightState::Normal,
},
Err(()) => acc,
})
}))
}
}