use glua_code_analysis::{GlobalId, LuaDeclId, LuaDocument};
use glua_parser::LuaSyntaxToken;
use lsp_types::{SemanticToken, SemanticTokenModifier, SemanticTokenType};
use rowan::{TextRange, TextSize};
use std::{
collections::{HashMap, HashSet},
vec::Vec,
};
pub struct CustomSemanticTokenType;
impl CustomSemanticTokenType {
pub const DELIMITER: SemanticTokenType = SemanticTokenType::new("delimiter");
pub const FIELD: SemanticTokenType = SemanticTokenType::new("field");
pub const LABEL: SemanticTokenType = SemanticTokenType::new("label");
}
pub struct CustomSemanticTokenModifier;
impl CustomSemanticTokenModifier {
pub const GLOBAL: SemanticTokenModifier = SemanticTokenModifier::new("global");
pub const LOCAL: SemanticTokenModifier = SemanticTokenModifier::new("local");
pub const CALLABLE: SemanticTokenModifier = SemanticTokenModifier::new("callable");
pub const OBJECT: SemanticTokenModifier = SemanticTokenModifier::new("object");
}
pub const SEMANTIC_TOKEN_TYPES: &[SemanticTokenType] = &[
SemanticTokenType::NAMESPACE,
SemanticTokenType::TYPE,
SemanticTokenType::CLASS,
SemanticTokenType::ENUM,
SemanticTokenType::INTERFACE,
SemanticTokenType::STRUCT,
SemanticTokenType::TYPE_PARAMETER,
SemanticTokenType::PARAMETER,
SemanticTokenType::VARIABLE,
SemanticTokenType::PROPERTY,
SemanticTokenType::ENUM_MEMBER,
SemanticTokenType::EVENT,
SemanticTokenType::FUNCTION,
SemanticTokenType::METHOD,
SemanticTokenType::MACRO,
SemanticTokenType::KEYWORD,
SemanticTokenType::MODIFIER,
SemanticTokenType::COMMENT,
SemanticTokenType::STRING,
SemanticTokenType::NUMBER,
SemanticTokenType::REGEXP,
SemanticTokenType::OPERATOR,
SemanticTokenType::DECORATOR,
CustomSemanticTokenType::DELIMITER,
CustomSemanticTokenType::FIELD,
CustomSemanticTokenType::LABEL,
];
pub const SEMANTIC_TOKEN_MODIFIERS: &[SemanticTokenModifier] = &[
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::DEFINITION,
SemanticTokenModifier::READONLY,
SemanticTokenModifier::STATIC,
SemanticTokenModifier::ABSTRACT,
SemanticTokenModifier::DEPRECATED,
SemanticTokenModifier::ASYNC,
SemanticTokenModifier::MODIFICATION,
SemanticTokenModifier::DOCUMENTATION,
SemanticTokenModifier::DEFAULT_LIBRARY,
CustomSemanticTokenModifier::GLOBAL,
CustomSemanticTokenModifier::LOCAL,
CustomSemanticTokenModifier::CALLABLE,
CustomSemanticTokenModifier::OBJECT,
];
#[derive(Debug)]
struct BasicSemanticTokenData {
line: u32,
col: u32,
length: u32,
typ: u32,
modifiers: u32,
}
#[derive(Debug)]
enum SemanticTokenData {
Basic(BasicSemanticTokenData),
MultiLine(Vec<BasicSemanticTokenData>),
}
#[derive(Debug)]
pub struct SemanticBuilder<'a> {
document: &'a LuaDocument<'a>,
multi_line_support: bool,
type_to_id: HashMap<SemanticTokenType, u32>,
modifier_to_id: HashMap<SemanticTokenModifier, u32>,
data: HashMap<TextSize, SemanticTokenData>,
string_special_range: HashSet<TextRange>,
class_like_global_cache: HashMap<GlobalId, bool>,
alias_target_cache:
HashMap<LuaDeclId, Option<(SemanticTokenType, Option<SemanticTokenModifier>)>>,
}
impl<'a> SemanticBuilder<'a> {
pub fn new(
document: &'a LuaDocument,
multi_line_support: bool,
types: Vec<SemanticTokenType>,
modifier: Vec<SemanticTokenModifier>,
) -> Self {
let mut type_to_id = HashMap::new();
for (i, ty) in types.into_iter().enumerate() {
type_to_id.insert(ty, i as u32);
}
let mut modifier_to_id = HashMap::new();
for (i, modifier) in modifier.into_iter().enumerate() {
modifier_to_id.insert(modifier, i as u32);
}
Self {
document,
multi_line_support,
type_to_id,
modifier_to_id,
data: HashMap::new(),
string_special_range: HashSet::new(),
class_like_global_cache: HashMap::new(),
alias_target_cache: HashMap::new(),
}
}
fn push_data(&mut self, range: TextRange, text: &str, typ: u32, modifiers: u32) -> Option<()> {
let position = range.start();
if self.data.contains_key(&position) {
return Some(());
}
let lsp_range = self.document.to_lsp_range(range)?;
let start_line = lsp_range.start.line;
let start_col = lsp_range.start.character;
let end_line = lsp_range.end.line;
if !self.multi_line_support && start_line != end_line {
let mut multi_line_data = vec![];
multi_line_data.push(BasicSemanticTokenData {
line: start_line,
col: start_col,
length: 9999,
typ,
modifiers,
});
for i in start_line + 1..end_line {
multi_line_data.push(BasicSemanticTokenData {
line: i,
col: 0,
length: 9999,
typ,
modifiers,
});
}
multi_line_data.push(BasicSemanticTokenData {
line: end_line,
col: 0,
length: lsp_range.end.character,
typ,
modifiers,
});
self.data
.insert(position, SemanticTokenData::MultiLine(multi_line_data));
} else {
let length = text.encode_utf16().count() as u32;
self.data.insert(
position,
SemanticTokenData::Basic(BasicSemanticTokenData {
line: start_line,
col: start_col,
length,
typ,
modifiers,
}),
);
}
Some(())
}
pub fn push(&mut self, token: &LuaSyntaxToken, ty: SemanticTokenType) -> Option<()> {
self.push_data(
token.text_range(),
token.text(),
*self.type_to_id.get(&ty)?,
0,
);
Some(())
}
pub fn push_with_modifier(
&mut self,
token: &LuaSyntaxToken,
ty: SemanticTokenType,
modifier: SemanticTokenModifier,
) -> Option<()> {
let typ = *self.type_to_id.get(&ty)?;
let modifier = 1 << *self.modifier_to_id.get(&modifier)?;
self.push_data(token.text_range(), token.text(), typ, modifier);
Some(())
}
pub fn push_with_modifier_force(
&mut self,
token: &LuaSyntaxToken,
ty: SemanticTokenType,
modifier: SemanticTokenModifier,
) -> Option<()> {
self.data.remove(&token.text_range().start());
self.push_with_modifier(token, ty, modifier)
}
pub fn push_force(&mut self, token: &LuaSyntaxToken, ty: SemanticTokenType) -> Option<()> {
self.data.remove(&token.text_range().start());
self.push(token, ty)
}
pub fn push_at_position(
&mut self,
position: TextSize,
length: u32,
ty: SemanticTokenType,
modifiers: Option<SemanticTokenModifier>,
) -> Option<()> {
let lsp_position = self.document.to_lsp_position(position)?;
let start_line = lsp_position.line;
let start_col = lsp_position.character;
self.data.insert(
position,
SemanticTokenData::Basic(BasicSemanticTokenData {
line: start_line,
col: start_col,
length,
typ: *self.type_to_id.get(&ty)?,
modifiers: modifiers.map_or(0, |m| 1 << *self.modifier_to_id.get(&m).unwrap_or(&0)),
}),
);
Some(())
}
pub fn cached_class_like_global(&self, global_id: &GlobalId) -> Option<bool> {
self.class_like_global_cache.get(global_id).copied()
}
pub fn cache_class_like_global(&mut self, global_id: GlobalId, is_class_like: bool) {
self.class_like_global_cache
.insert(global_id, is_class_like);
}
pub fn cached_alias_target(
&self,
decl_id: &LuaDeclId,
) -> Option<Option<(SemanticTokenType, Option<SemanticTokenModifier>)>> {
self.alias_target_cache.get(decl_id).cloned()
}
pub fn cache_alias_target(
&mut self,
decl_id: LuaDeclId,
target: Option<(SemanticTokenType, Option<SemanticTokenModifier>)>,
) {
self.alias_target_cache.insert(decl_id, target);
}
pub fn push_at_range(
&mut self,
token_text: &str,
range: TextRange,
ty: SemanticTokenType,
modifiers: &[SemanticTokenModifier],
) -> Option<()> {
let mut modifier = 0;
for m in modifiers {
modifier |= 1 << *self.modifier_to_id.get(m)?;
}
self.push_data(range, token_text, *self.type_to_id.get(&ty)?, modifier);
Some(())
}
#[allow(unused)]
pub fn push_with_modifiers(
&mut self,
token: &LuaSyntaxToken,
ty: SemanticTokenType,
modifiers: &[SemanticTokenModifier],
) -> Option<()> {
let typ = *self.type_to_id.get(&ty)?;
let mut modifier = 0;
for m in modifiers {
modifier |= 1 << *self.modifier_to_id.get(m)?;
}
self.push_data(token.text_range(), token.text(), typ, modifier);
Some(())
}
pub fn build(self) -> Vec<SemanticToken> {
let mut data: Vec<BasicSemanticTokenData> = vec![];
for (_, token_data) in self.data {
match token_data {
SemanticTokenData::Basic(basic_data) => {
data.push(basic_data);
}
SemanticTokenData::MultiLine(multi_data) => {
for basic_data in multi_data {
data.push(basic_data);
}
}
}
}
data.sort_by(|a, b| {
let line1 = a.line;
let line2 = b.line;
if line1 == line2 {
let character1 = a.col;
let character2 = b.col;
return character1.cmp(&character2);
}
line1.cmp(&line2)
});
let mut result = Vec::with_capacity(data.len());
let mut prev_line = 0;
let mut prev_col = 0;
for token_data in data {
let line_diff = token_data.line - prev_line;
if line_diff != 0 {
prev_col = 0;
}
let col_diff = token_data.col - prev_col;
result.push(SemanticToken {
delta_line: line_diff,
delta_start: col_diff,
length: token_data.length,
token_type: token_data.typ,
token_modifiers_bitset: token_data.modifiers,
});
prev_line = token_data.line;
prev_col = token_data.col;
}
result
}
pub fn add_special_string_range(&mut self, range: TextRange) {
self.string_special_range.insert(range);
}
pub fn is_special_string_range(&self, range: &TextRange) -> bool {
self.string_special_range.contains(range)
}
pub fn contains_position(&self, position: TextSize) -> bool {
self.data.contains_key(&position)
}
pub fn contains_token(&self, token: &LuaSyntaxToken) -> bool {
self.contains_position(token.text_range().start())
}
}