use crate::parsing::{ScopeStack, ParseState, SyntaxReference, SyntaxSet, ScopeStackOp};
use crate::highlighting::{Highlighter, HighlightState, HighlightIterator, Theme, Style};
use std::io::{self, BufReader};
use std::fs::File;
use std::path::Path;
use crate::CrashError;
pub struct HighlightLines<'a> {
highlighter: Highlighter<'a>,
parse_state: ParseState,
highlight_state: HighlightState,
}
impl<'a> HighlightLines<'a> {
pub fn new(syntax: &SyntaxReference, theme: &'a Theme) -> HighlightLines<'a> {
let highlighter = Highlighter::new(theme);
let highlight_state = HighlightState::new(&highlighter, ScopeStack::new());
HighlightLines {
highlighter,
parse_state: ParseState::new(syntax),
highlight_state,
}
}
pub fn highlight<'b>(&mut self, line: &'b str, syntax_set: &SyntaxSet) -> Result<Vec<(Style, &'b str)>, CrashError> {
let ops = self.parse_state.parse_line(line, syntax_set)?;
let iter =
HighlightIterator::new(&mut self.highlight_state, &ops[..], line, &self.highlighter);
Ok(iter.collect())
}
}
pub struct HighlightFile<'a> {
pub reader: BufReader<File>,
pub highlight_lines: HighlightLines<'a>,
}
impl<'a> HighlightFile<'a> {
pub fn new<P: AsRef<Path>>(path_obj: P,
ss: &SyntaxSet,
theme: &'a Theme)
-> io::Result<HighlightFile<'a>> {
let path: &Path = path_obj.as_ref();
let f = File::open(path)?;
let syntax = ss.find_syntax_for_file(path)?
.unwrap_or_else(|| ss.find_syntax_plain_text());
Ok(HighlightFile {
reader: BufReader::new(f),
highlight_lines: HighlightLines::new(syntax, theme),
})
}
}
#[derive(Debug)]
pub struct ScopeRangeIterator<'a> {
ops: &'a [(usize, ScopeStackOp)],
line: &'a str,
index: usize,
last_str_index: usize,
}
impl<'a> ScopeRangeIterator<'a> {
pub fn new(ops: &'a [(usize, ScopeStackOp)], line: &'a str) -> ScopeRangeIterator<'a> {
ScopeRangeIterator {
ops,
line,
index: 0,
last_str_index: 0,
}
}
}
static NOOP_OP: ScopeStackOp = ScopeStackOp::Noop;
impl<'a> Iterator for ScopeRangeIterator<'a> {
type Item = (std::ops::Range<usize>, &'a ScopeStackOp);
fn next(&mut self) -> Option<Self::Item> {
if self.index > self.ops.len() {
return None;
}
let next_str_i = if self.index == self.ops.len() {
self.line.len()
} else {
self.ops[self.index].0
};
let range = self.last_str_index..next_str_i;
self.last_str_index = next_str_i;
let op = if self.index == 0 {
&NOOP_OP
} else {
&self.ops[self.index - 1].1
};
self.index += 1;
Some((range, op))
}
}
#[derive(Debug)]
pub struct ScopeRegionIterator<'a> {
range_iter: ScopeRangeIterator<'a>,
}
impl<'a> ScopeRegionIterator<'a> {
pub fn new(ops: &'a [(usize, ScopeStackOp)], line: &'a str) -> ScopeRegionIterator<'a> {
ScopeRegionIterator {
range_iter: ScopeRangeIterator::new(ops, line),
}
}
}
impl<'a> Iterator for ScopeRegionIterator<'a> {
type Item = (&'a str, &'a ScopeStackOp);
fn next(&mut self) -> Option<Self::Item> {
let (range, op) = self.range_iter.next()?;
Some((&self.range_iter.line[range], op))
}
}
#[cfg(all(feature = "assets", any(feature = "dump-load", feature = "dump-load-rs")))]
#[cfg(test)]
mod tests {
use super::*;
use crate::parsing::{SyntaxSet, ParseState, ScopeStack};
use crate::highlighting::ThemeSet;
use std::str::FromStr;
#[test]
fn can_highlight_lines() {
let ss = SyntaxSet::load_defaults_nonewlines();
let ts = ThemeSet::load_defaults();
let syntax = ss.find_syntax_by_extension("rs").unwrap();
let mut h = HighlightLines::new(syntax, &ts.themes["base16-ocean.dark"]);
let ranges = h.highlight("pub struct Wow { hi: u64 }", &ss).unwrap();
assert!(ranges.len() > 4);
}
#[test]
fn can_highlight_file() {
let ss = SyntaxSet::load_defaults_nonewlines();
let ts = ThemeSet::load_defaults();
HighlightFile::new("testdata/highlight_test.erb",
&ss,
&ts.themes["base16-ocean.dark"])
.unwrap();
}
#[test]
fn can_find_regions() {
let ss = SyntaxSet::load_defaults_nonewlines();
let mut state = ParseState::new(ss.find_syntax_by_extension("rb").unwrap());
let line = "lol =5+2";
let ops = state.parse_line(line, &ss).unwrap();
let mut stack = ScopeStack::new();
let mut token_count = 0;
for (s, op) in ScopeRegionIterator::new(&ops, line) {
stack.apply(op);
if s.is_empty() { continue;
}
if token_count == 1 {
assert_eq!(stack, ScopeStack::from_str("source.ruby keyword.operator.assignment.ruby").unwrap());
assert_eq!(s, "=");
}
token_count += 1;
println!("{:?} {}", s, stack);
}
assert_eq!(token_count, 5);
}
#[test]
fn can_find_regions_with_trailing_newline() {
let ss = SyntaxSet::load_defaults_newlines();
let mut state = ParseState::new(ss.find_syntax_by_extension("rb").unwrap());
let lines = ["# hello world\n", "lol=5+2\n"];
let mut stack = ScopeStack::new();
for line in lines.iter() {
let ops = state.parse_line(line, &ss).unwrap();
println!("{:?}", ops);
let mut iterated_ops: Vec<&ScopeStackOp> = Vec::new();
for (_, op) in ScopeRegionIterator::new(&ops, line) {
stack.apply(op);
iterated_ops.push(op);
println!("{:?}", op);
}
let all_ops = ops.iter().map(|t| &t.1);
assert_eq!(all_ops.count(), iterated_ops.len() - 1); }
}
}