use std::collections::HashMap;
use itertools::Itertools;
use ropey::Rope;
use tower_lsp::lsp_types::{Range, SemanticToken, SemanticTokenType};
use tree_sitter_bend::HIGHLIGHTS_QUERY;
use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent};
use super::document::Document;
use crate::language::bend;
lazy_static::lazy_static! {
pub static ref NAME_TO_TOKEN_TYPE: HashMap<&'static str, SemanticTokenType> = {
HashMap::from([
("variable", SemanticTokenType::VARIABLE),
("variable.parameter", SemanticTokenType::PARAMETER),
("variable.member", SemanticTokenType::ENUM_MEMBER),
("property", SemanticTokenType::TYPE),
("keyword", SemanticTokenType::KEYWORD),
("keyword.conditional", SemanticTokenType::KEYWORD),
("keyword.function", SemanticTokenType::KEYWORD),
("keyword.return", SemanticTokenType::KEYWORD),
("keyword.repeat", SemanticTokenType::KEYWORD),
("keyword.type", SemanticTokenType::KEYWORD),
("string", SemanticTokenType::STRING),
("function", SemanticTokenType::FUNCTION),
("function.call", SemanticTokenType::FUNCTION),
("type", SemanticTokenType::TYPE),
("character", SemanticTokenType::STRING),
("character.special", SemanticTokenType::STRING),
("number", SemanticTokenType::NUMBER),
("number.float", SemanticTokenType::NUMBER),
("comment", SemanticTokenType::COMMENT),
("operator", SemanticTokenType::OPERATOR),
])
};
pub static ref LEGEND_TOKEN_TYPE: Vec<SemanticTokenType> =
NAME_TO_TOKEN_TYPE.values().cloned().unique().collect();
pub static ref HIGHLIGHT_NAMES: Vec<&'static str> =
NAME_TO_TOKEN_TYPE.keys().copied().collect();
pub static ref HIGHLIGHT_INDEX_TO_LSP_INDEX: HashMap<usize, usize> = {
let token_type_index: HashMap<SemanticTokenType, usize> = LEGEND_TOKEN_TYPE.iter().enumerate().map(|(i, v)| (v.clone(), i)).collect();
let highlight_index: HashMap<&&str, usize> = HIGHLIGHT_NAMES.iter().enumerate().map(|(i, v)| (v, i)).collect();
NAME_TO_TOKEN_TYPE.iter().map(|(key, val)| (highlight_index[key], token_type_index[val])).collect()
};
pub static ref HIGHLIGHTER_CONFIG: HighlightConfiguration = {
let mut config = HighlightConfiguration::new(bend(), "bend", HIGHLIGHTS_QUERY, "", "").unwrap();
config.configure(&HIGHLIGHT_NAMES);
config
};
}
pub fn semantic_tokens(doc: &mut Document, range: Option<Range>) -> Vec<SemanticToken> {
let code = doc.text.to_string(); let highlights = doc
.highlighter
.highlight(&HIGHLIGHTER_CONFIG, code.as_bytes(), None, |_| None)
.unwrap();
let range = range.map(|r| {
let rstart = doc.text.line_to_byte(r.start.line as usize);
let rend = doc.text.line_to_byte(r.end.line as usize + 1);
rstart..rend
});
let mut tokens = vec![]; let mut types = vec![]; let mut pre_line = 0; let mut pre_start = 0; for event in highlights {
match event {
Result::Ok(HighlightEvent::HighlightStart(h)) => types.push(h.0),
Result::Ok(HighlightEvent::HighlightEnd) => drop(types.pop()),
Result::Ok(HighlightEvent::Source { mut start, end }) => {
if let Some(range) = &range {
if end < range.start {
continue;
}
if range.end < start {
break;
}
}
let token = types
.last()
.and_then(|curr| HIGHLIGHT_INDEX_TO_LSP_INDEX.get(curr))
.and_then(|type_index| {
while start < end && char::from(doc.text.byte(start)).is_whitespace() {
start += 1;
}
make_semantic_token(
&doc.text,
start..end,
*type_index as u32,
&mut pre_line,
&mut pre_start,
)
});
if let Some(token) = token {
tokens.push(token);
}
}
Err(_) => { }
}
}
tokens
}
fn make_semantic_token(
code: &Rope,
range: std::ops::Range<usize>,
token_type: u32,
pre_line: &mut u32,
pre_start: &mut u32,
) -> Option<SemanticToken> {
let line = code.try_byte_to_line(range.start).ok()? as u32;
let first = code.try_line_to_char(line as usize).ok()? as u32;
let start = (code.try_byte_to_char(range.start).ok()? as u32).checked_sub(first)?;
let delta_line = line.checked_sub(*pre_line)?;
let delta_start = if delta_line == 0 {
start.checked_sub(*pre_start)?
} else {
start
};
*pre_line = line;
*pre_start = start;
Some(SemanticToken {
delta_line,
delta_start,
length: code.byte_slice(range).chars().count() as u32,
token_type,
token_modifiers_bitset: 0,
})
}
#[test]
fn token_capture_test() {
let code: Rope = r#"
def main():
return "Hi!"
"#
.into();
let mut highlighter = tree_sitter_highlight::Highlighter::new();
let config = &HIGHLIGHTER_CONFIG;
let text = code.to_string();
let highlights = highlighter
.highlight(&config, text.as_bytes(), None, |_| None)
.unwrap();
let mut stack = vec![];
for event in highlights {
match event.unwrap() {
HighlightEvent::HighlightStart(k) => {
let name = HIGHLIGHT_NAMES[k.0];
stack.push(name);
println!("> start {}", name);
}
HighlightEvent::Source { start, end } => {
println!("> {start}-{end}: {:?}", &text[start..end])
}
HighlightEvent::HighlightEnd => {
println!("> end {}", stack.pop().unwrap());
}
}
}
println!();
let highlights = highlighter
.highlight(&config, text.as_bytes(), None, |_| None)
.unwrap();
let mut tokens = vec![];
let mut stack = vec![];
let mut pre_line = 0;
let mut pre_start = 0;
for event in highlights {
match event {
Result::Ok(HighlightEvent::HighlightStart(h)) => stack.push(h.0),
Result::Ok(HighlightEvent::HighlightEnd) => drop(stack.pop()),
Result::Ok(HighlightEvent::Source { mut start, end }) => {
stack
.last()
.and_then(|curr| HIGHLIGHT_INDEX_TO_LSP_INDEX.get(curr))
.and_then(|type_index| {
while start < end && char::from(code.byte(start)).is_whitespace() {
start += 1;
}
println!(
"{}-{} {:?}: {}",
start,
end,
&text[start..end],
LEGEND_TOKEN_TYPE[*type_index as usize].as_str()
);
make_semantic_token(
&code,
start..end,
*type_index as u32,
&mut pre_line,
&mut pre_start,
)
})
.map(|token| tokens.push(token));
}
Err(_) => { }
}
}
println!();
println!("> got {} tokens", tokens.len());
for token in tokens {
println!("{:?}", token);
}
}