use std::cmp::Reverse;
use std::{convert::Infallible, ptr, str::FromStr};
use crate::Error;
use crate::grammar::{Grammar, ScopeId, ScopeSpan};
use crate::raw::{RawStyle, RawTheme};
use crate::util::{next_char_boundary, previous_char_boundary, trim_line_end};
#[derive(Debug)]
pub struct Theme {
pub name: String,
pub default: Style,
rules: Vec<ThemeRule>,
}
#[derive(Debug)]
struct ThemeRule {
selector: String,
style: Style,
}
#[derive(Debug, Default)]
pub(crate) struct StyleCache {
key: Option<CacheKey>,
styles: Vec<Style>,
}
type CacheKey = (usize, usize, usize);
#[derive(Debug, Default)]
pub(crate) struct StyleScratch {
boundaries: Vec<usize>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Segment {
start: usize,
end: usize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct StyleSpan {
pub start: usize,
pub end: usize,
pub scope: Option<ScopeId>,
pub style: Style,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Rgb {
pub r: u8,
pub g: u8,
pub b: u8,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ParseRgbError;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct FontStyle(u8);
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Style {
pub foreground: Option<Rgb>,
pub font_style: FontStyle,
}
impl Theme {
pub fn compile(raw: &RawTheme) -> Self {
let mut default = Style::default();
let mut rules = Vec::new();
let raw_rules = raw.settings.as_deref().or(raw.token_colors.as_deref());
for rule in raw_rules.unwrap_or_default() {
let style = Style::from_raw(&rule.settings);
let selectors = rule.scope_selectors();
if selectors.is_empty() {
default = style;
} else {
rules.extend(
selectors
.into_iter()
.map(|selector| ThemeRule { selector, style }),
);
}
}
Self {
name: raw.name.clone(),
default,
rules,
}
}
pub fn parse(input: &str) -> Result<Self, Error> {
input.parse()
}
pub fn style_for_scope(&self, scope: &str) -> Style {
let mut style = self.default;
let mut best = 0;
for rule in &self.rules {
if rule.matches(scope) && rule.selector.len() >= best {
best = rule.selector.len();
style = rule.style.merge(style);
}
}
style
}
pub fn style_spans(
&self,
grammar: &Grammar,
line: &str,
scopes: &[ScopeSpan],
) -> Vec<StyleSpan> {
let mut cache = StyleCache::default();
let mut scratch = StyleScratch::default();
let mut output = Vec::new();
cache.refresh(self, grammar);
self.style_line_into(grammar, line, scopes, &cache, &mut scratch, &mut output);
output
}
pub(crate) fn style_line_into(
&self,
grammar: &Grammar,
line: &str,
scopes: &[ScopeSpan],
cache: &StyleCache,
scratch: &mut StyleScratch,
output: &mut Vec<StyleSpan>,
) {
output.clear();
let line = trim_line_end(line);
if line.is_empty() {
return;
}
scratch.collect_boundaries(line, scopes);
for segment in scratch.segments() {
output.push(self.style_segment(grammar, scopes, cache, segment));
}
}
fn style_segment(
&self,
grammar: &Grammar,
scopes: &[ScopeSpan],
cache: &StyleCache,
segment: Segment,
) -> StyleSpan {
let Some(span) = segment.best_covering(scopes, grammar) else {
return segment.styled(None, self.default);
};
let style = cache
.style_for(span.scope)
.unwrap_or_else(|| self.style_for_scope_id(grammar, span.scope));
segment.styled(Some(span.scope), style)
}
fn style_for_scope_id(&self, grammar: &Grammar, scope: ScopeId) -> Style {
grammar
.scopes
.get(scope.index())
.map_or(self.default, |name| self.style_for_scope(name))
}
}
impl FromStr for Theme {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let raw = RawTheme::from_str(input)?;
Ok(Self::compile(&raw))
}
}
impl ThemeRule {
fn matches(&self, scope: &str) -> bool {
scope == self.selector
|| (scope.len() > self.selector.len()
&& scope.starts_with(&self.selector)
&& scope.as_bytes().get(self.selector.len()) == Some(&b'.'))
}
}
impl StyleCache {
pub(crate) fn refresh(&mut self, theme: &Theme, grammar: &Grammar) {
let key = (
ptr::from_ref(theme).addr(),
ptr::from_ref(grammar).addr(),
grammar.scopes.len(),
);
if self.key == Some(key) {
return;
}
self.styles.clear();
self.styles.extend(
grammar
.scopes
.iter()
.map(|scope| theme.style_for_scope(scope)),
);
self.key = Some(key);
}
fn style_for(&self, scope: ScopeId) -> Option<Style> {
self.styles.get(scope.index()).copied()
}
}
impl StyleScratch {
pub(crate) fn clear_line(&mut self) {
self.boundaries.clear();
}
fn collect_boundaries(&mut self, line: &str, scopes: &[ScopeSpan]) {
self.boundaries.clear();
self.boundaries.push(0);
self.boundaries.push(line.len());
for span in scopes {
let start = next_char_boundary(line, span.start);
let end = previous_char_boundary(line, span.end);
if start < end {
self.boundaries.push(start);
self.boundaries.push(end);
}
}
self.boundaries.sort_unstable();
self.boundaries.dedup();
}
fn segments(&self) -> impl Iterator<Item = Segment> + '_ {
self.boundaries.windows(2).filter_map(|window| {
let [start, end] = *window else {
return None;
};
(start < end).then_some(Segment { start, end })
})
}
}
impl Segment {
fn styled(self, scope: Option<ScopeId>, style: Style) -> StyleSpan {
StyleSpan {
start: self.start,
end: self.end,
scope,
style,
}
}
fn best_covering<'a>(self, spans: &'a [ScopeSpan], grammar: &Grammar) -> Option<&'a ScopeSpan> {
spans
.iter()
.filter(|span| span.start <= self.start && span.end >= self.end)
.min_by_key(|span| {
let scope_len = grammar
.scopes
.get(span.scope.index())
.map_or(0, String::len);
(span.end - span.start, Reverse(scope_len))
})
}
}
impl FromStr for Rgb {
type Err = ParseRgbError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let hex = input.strip_prefix('#').ok_or(ParseRgbError)?;
if hex.len() != 6 {
return Err(ParseRgbError);
}
let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| ParseRgbError)?;
let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| ParseRgbError)?;
let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| ParseRgbError)?;
Ok(Self { r, g, b })
}
}
impl FontStyle {
pub const BOLD: Self = Self(1 << 0);
pub const ITALIC: Self = Self(1 << 1);
pub const UNDERLINE: Self = Self(1 << 2);
pub const STRIKETHROUGH: Self = Self(1 << 3);
pub const fn empty() -> Self {
Self(0)
}
pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
pub fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
}
impl FromStr for FontStyle {
type Err = Infallible;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let mut style = Self::empty();
for flag in input.split_whitespace() {
match flag {
"bold" => style.insert(Self::BOLD),
"italic" => style.insert(Self::ITALIC),
"underline" => style.insert(Self::UNDERLINE),
"strikethrough" => style.insert(Self::STRIKETHROUGH),
_ => {}
}
}
Ok(style)
}
}
impl Style {
fn from_raw(raw: &RawStyle) -> Self {
let mut style = Self::default();
if let Some(foreground) = &raw.foreground {
style.foreground = foreground.parse().ok();
}
if let Some(flags) = &raw.font_style {
style.font_style = flags.parse().unwrap_or_else(|error| match error {});
}
style
}
pub fn merge(self, base: Self) -> Self {
let mut font_style = base.font_style;
font_style.insert(self.font_style);
Self {
foreground: self.foreground.or(base.foreground),
font_style,
}
}
pub const fn fg(mut self, color: Rgb) -> Self {
self.foreground = Some(color);
self
}
pub fn bold(mut self) -> Self {
self.font_style.insert(FontStyle::BOLD);
self
}
pub fn italic(mut self) -> Self {
self.font_style.insert(FontStyle::ITALIC);
self
}
pub fn underline(mut self) -> Self {
self.font_style.insert(FontStyle::UNDERLINE);
self
}
pub fn strikethrough(mut self) -> Self {
self.font_style.insert(FontStyle::STRIKETHROUGH);
self
}
}