use std::collections::HashMap;
use syntect::parsing::{SyntaxReference, SyntaxSet};
pub fn highlight(
code: &str,
lang: &str,
syntax_map: &HashMap<String, String>,
reset: &str,
ss: &SyntaxSet,
) -> Option<String> {
let syntax = find_syntax(ss, lang)?;
let mut state = syntect::parsing::ParseState::new(syntax);
let mut result = String::new();
let lines: Vec<&str> = code.split('\n').collect();
for (i, line) in lines.iter().enumerate() {
if i > 0 {
result.push('\n');
}
let ops = state.parse_line(line, ss).ok()?;
let regions = scope_regions(line, &ops);
for (text, scope_stack) in ®ions {
if text.is_empty() {
continue;
}
let token_type = scope_to_token_type(scope_stack);
match lookup_style(syntax_map, &token_type) {
Some(ansi) if !ansi.is_empty() => {
result.push_str(ansi);
result.push_str(text);
result.push_str(reset);
}
_ => {
result.push_str(text);
}
}
}
}
Some(result)
}
fn scope_regions<'a>(
line: &'a str,
ops: &[(usize, syntect::parsing::ScopeStackOp)],
) -> Vec<(&'a str, Vec<syntect::parsing::Scope>)> {
use syntect::parsing::ScopeStackOp;
let mut regions = Vec::new();
let mut stack = Vec::new();
let mut pos = 0;
for &(offset, ref op) in ops {
if offset > pos {
let text = &line[pos..offset];
regions.push((text, stack.clone()));
pos = offset;
}
match op {
ScopeStackOp::Push(scope) => {
stack.push(*scope);
}
ScopeStackOp::Pop(count) => {
for _ in 0..*count {
stack.pop();
}
}
ScopeStackOp::Restore => {
stack.clear();
}
_ => {}
}
}
if pos < line.len() {
regions.push((&line[pos..], stack));
}
regions
}
fn scope_to_token_type(scopes: &[syntect::parsing::Scope]) -> String {
let scope = scopes
.iter()
.rev()
.find(|s| {
let s_str = s.build_string();
!s_str.starts_with("source.") && !s_str.starts_with("meta.")
})
.or_else(|| scopes.last());
match scope {
Some(s) => {
let s_str = s.build_string();
let parts: Vec<&str> = s_str.split('.').collect();
match parts.len() {
0 => String::new(),
1 => parts[0].to_string(),
_ => format!("{}_{}", parts[0], parts[1]),
}
}
None => String::new(),
}
}
fn lookup_style<'a>(
syntax_map: &'a HashMap<String, String>,
token_type: &str,
) -> Option<&'a String> {
if let Some(style) = syntax_map.get(token_type) {
return Some(style);
}
if let Some(pos) = token_type.rfind('_') {
lookup_style(syntax_map, &token_type[..pos])
} else {
None
}
}
fn find_syntax<'a>(ss: &'a SyntaxSet, lang: &str) -> Option<&'a SyntaxReference> {
ss.find_syntax_by_token(lang)
.or_else(|| ss.find_syntax_by_name(lang))
.or_else(|| {
let lower = lang.to_lowercase();
ss.syntaxes()
.iter()
.find(|s| s.name.to_lowercase() == lower)
})
}