#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)]
use unicode_width::UnicodeWidthStr;
pub mod themes;
pub trait Highlight {
#[allow(missing_docs)]
fn highlight(&self, input: &str) -> Vec<HighlightedSpan>;
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct HighlightedSpan {
pub range: std::ops::Range<usize>,
pub group: HighlightGroup,
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, strum_macros::EnumIter)]
pub enum HighlightGroup {
CtrlFlowKeyword,
OtherKeyword,
FunctionDef,
FunctionCall,
TyDef,
TyUse,
InterfaceDef,
InterfaceUse,
PrimitiveTy,
VariableDef,
VariableUse,
MemberDef,
MemberUse,
ConstantDef,
ConstantUse,
ModuleDef,
ModuleUse,
MacroDef,
MacroUse,
SpecialIdentDef,
SpecialIdentUse,
FunctionParam,
Number,
String,
StringDelimiter,
Character,
CharacterDelimiter,
Boolean,
PreProc,
Attribute,
Comment,
DocComment,
MemberOper,
PointerOper,
AssignOper,
BinaryOper,
OtherOper,
Delimiter,
Separator,
Terminator,
Error,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct StyledGrapheme {
pub grapheme: smol_str::SmolStr,
pub style: ResolvedStyle,
}
impl UnicodeWidthStr for StyledGrapheme {
fn width(&self) -> usize {
self.grapheme.as_str().width()
}
fn width_cjk(&self) -> usize {
self.grapheme.as_str().width_cjk()
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct Rgb {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl From<Rgb> for ansi_term::Colour {
fn from(rgb: Rgb) -> Self {
Self::RGB(rgb.r, rgb.g, rgb.b)
}
}
#[macro_export]
macro_rules! rgb {
($r:literal, $g:literal, $b:literal) => {
$crate::Rgb {
r: $r,
g: $g,
b: $b,
}
};
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default)]
pub struct Style {
pub fg_color: Option<Rgb>,
pub bg_color: Option<Rgb>,
pub is_bold: bool,
pub is_italic: bool,
pub is_underline: bool,
}
impl Style {
pub fn new() -> Self {
Self {
fg_color: None,
bg_color: None,
is_bold: false,
is_italic: false,
is_underline: false,
}
}
fn resolve(self, resolved: ResolvedStyle) -> ResolvedStyle {
ResolvedStyle {
fg_color: self.fg_color.unwrap_or(resolved.fg_color),
bg_color: self.bg_color.unwrap_or(resolved.bg_color),
is_bold: self.is_bold,
is_italic: self.is_italic,
is_underline: self.is_underline,
}
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct ResolvedStyle {
pub fg_color: Rgb,
pub bg_color: Rgb,
pub is_bold: bool,
pub is_italic: bool,
pub is_underline: bool,
}
impl From<ResolvedStyle> for ansi_term::Style {
fn from(style: ResolvedStyle) -> Self {
Self {
foreground: Some(style.fg_color.into()),
background: Some(style.bg_color.into()),
is_bold: style.is_bold,
is_italic: style.is_italic,
is_underline: style.is_underline,
is_dimmed: false,
is_blink: false,
is_reverse: false,
is_hidden: false,
is_strikethrough: false,
}
}
}
pub trait Theme {
fn default_style(&self) -> ResolvedStyle;
fn style(&self, group: HighlightGroup) -> Style;
}
pub fn render<H, T>(input: &str, highlighter: H, theme: T) -> Vec<StyledGrapheme>
where
H: Highlight,
T: Theme,
{
use std::collections::HashMap;
use strum::IntoEnumIterator;
use unicode_segmentation::UnicodeSegmentation;
let styles: HashMap<_, _> = HighlightGroup::iter()
.map(|group| (group, theme.style(group)))
.collect();
let spans = highlighter.highlight(input);
let num_chars = input.chars().count();
let mut output = Vec::with_capacity(num_chars);
'graphemes: for (idx, grapheme) in input.grapheme_indices(true) {
let grapheme = smol_str::SmolStr::from(grapheme);
for span in spans.iter() {
if span.range.contains(&idx) {
output.push(StyledGrapheme {
grapheme,
style: styles[&span.group].resolve(theme.default_style()),
});
continue 'graphemes;
}
}
output.push(StyledGrapheme {
grapheme,
style: theme.default_style(),
});
}
output
}