use std::iter::Iterator;
use parsing::{Scope, ScopeStack, BasicScopeStackOp, ScopeStackOp, MatchPower, ATOM_LEN_BITS};
use super::selector::ScopeSelector;
use super::theme::Theme;
use super::style::{Style, StyleModifier, FontStyle, BLACK, WHITE};
#[derive(Debug)]
pub struct Highlighter<'a> {
theme: &'a Theme,
single_selectors: Vec<(Scope, StyleModifier)>,
multi_selectors: Vec<(ScopeSelector, StyleModifier)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HighlightState {
styles: Vec<Style>,
pub path: ScopeStack,
}
#[derive(Debug)]
pub struct HighlightIterator<'a, 'b> {
index: usize,
pos: usize,
changes: &'a [(usize, ScopeStackOp)],
text: &'b str,
highlighter: &'a Highlighter<'a>,
state: &'a mut HighlightState,
}
impl HighlightState {
pub fn new(highlighter: &Highlighter, initial_stack: ScopeStack) -> HighlightState {
let mut initial_styles = vec![highlighter.get_default()];
for i in 0..initial_stack.len() {
let style = initial_styles[i];
style.apply(highlighter.get_style(initial_stack.bottom_n(i)));
initial_styles.push(style);
}
HighlightState {
styles: initial_styles,
path: initial_stack,
}
}
}
impl<'a, 'b> HighlightIterator<'a, 'b> {
pub fn new(state: &'a mut HighlightState,
changes: &'a [(usize, ScopeStackOp)],
text: &'b str,
highlighter: &'a Highlighter)
-> HighlightIterator<'a, 'b> {
HighlightIterator {
index: 0,
pos: 0,
changes: changes,
text: text,
highlighter: highlighter,
state: state,
}
}
}
impl<'a, 'b> Iterator for HighlightIterator<'a, 'b> {
type Item = (Style, &'b str);
fn next(&mut self) -> Option<(Style, &'b str)> {
if self.pos == self.text.len() && self.index >= self.changes.len() {
return None;
}
let (end, command) = if self.index < self.changes.len() {
self.changes[self.index].clone()
} else {
(self.text.len(), ScopeStackOp::Noop)
};
let style = *self.state.styles.last().unwrap();
let text = &self.text[self.pos..end];
{
let m_path = &mut self.state.path;
let m_styles = &mut self.state.styles;
let highlighter = &self.highlighter;
m_path.apply_with_hook(&command, |op, cur_stack| {
match op {
BasicScopeStackOp::Push(_) => {
let style = *m_styles.last().unwrap();
m_styles.push(style.apply(highlighter.get_new_style(cur_stack)));
}
BasicScopeStackOp::Pop => {
m_styles.pop();
}
}
});
}
self.pos = end;
self.index += 1;
if text.is_empty() {
self.next()
} else {
Some((style, text))
}
}
}
impl<'a> Highlighter<'a> {
pub fn new(theme: &'a Theme) -> Highlighter<'a> {
let mut single_selectors = Vec::new();
let mut multi_selectors = Vec::new();
for item in &theme.scopes {
for sel in &item.scope.selectors {
if let Some(scope) = sel.extract_single_scope() {
single_selectors.push((scope, item.style));
} else {
multi_selectors.push((sel.clone(), item.style));
}
}
}
single_selectors.sort_by(|a, b| b.0.len().cmp(&a.0.len()));
Highlighter {
theme: theme,
single_selectors: single_selectors,
multi_selectors: multi_selectors,
}
}
pub fn get_default(&self) -> Style {
Style {
foreground: self.theme.settings.foreground.unwrap_or(BLACK),
background: self.theme.settings.background.unwrap_or(WHITE),
font_style: FontStyle::empty(),
}
}
pub fn get_style(&self, path: &[Scope]) -> StyleModifier {
let max_item = self.theme
.scopes
.iter()
.filter_map(|item| {
item.scope
.does_match(path)
.map(|score| (score, item))
})
.max_by_key(|&(score, _)| score)
.map(|(_, item)| item);
StyleModifier {
foreground: max_item.and_then(|item| item.style.foreground),
background: max_item.and_then(|item| item.style.background),
font_style: max_item.and_then(|item| item.style.font_style),
}
}
pub fn get_new_style(&self, path: &[Scope]) -> StyleModifier {
let last_scope = path[path.len() - 1];
let single_res = self.single_selectors
.iter()
.find(|a| a.0.is_prefix_of(last_scope));
let mult_res = self.multi_selectors
.iter()
.filter_map(|&(ref sel, ref style)| sel.does_match(path).map(|score| (score, style)))
.max_by_key(|&(score, _)| score);
if let Some((score, style)) = mult_res {
let mut single_score: f64 = -1.0;
if let Some(&(scope, _)) = single_res {
single_score = (scope.len() as f64) *
((ATOM_LEN_BITS * ((path.len() - 1) as u16)) as f64).exp2();
}
if MatchPower(single_score) < score {
return *style;
}
}
if let Some(&(_, ref style)) = single_res {
return *style;
}
StyleModifier {
foreground: None,
background: None,
font_style: None,
}
}
}
#[cfg(feature = "assets")]
#[cfg(test)]
mod tests {
use super::*;
use highlighting::{ThemeSet, Style, Color, FontStyle};
use parsing::{ SyntaxSet, ScopeStack, ParseState};
#[test]
fn can_parse() {
let ps = SyntaxSet::load_from_folder("testdata/Packages").unwrap();
let mut state = {
let syntax = ps.find_syntax_by_name("Ruby on Rails").unwrap();
ParseState::new(syntax)
};
let ts = ThemeSet::load_defaults();
let highlighter = Highlighter::new(&ts.themes["base16-ocean.dark"]);
let mut highlight_state = HighlightState::new(&highlighter, ScopeStack::new());
let line = "module Bob::Wow::Troll::Five; 5; end";
let ops = state.parse_line(line);
let iter = HighlightIterator::new(&mut highlight_state, &ops[..], line, &highlighter);
let regions: Vec<(Style, &str)> = iter.collect();
assert_eq!(regions[11],
(Style {
foreground: Color {
r: 208,
g: 135,
b: 112,
a: 0xFF,
},
background: Color {
r: 43,
g: 48,
b: 59,
a: 0xFF,
},
font_style: FontStyle::empty(),
},
"5"));
}
}