use glua_parser::{
LexerState, LuaAstNode, LuaAstToken, LuaComment, LuaLiteralExpr, LuaLiteralToken,
LuaStringToken, Reader, SourceRange,
};
use glua_parser_desc::{
CodeBlockHighlightKind, CodeBlockLang, DescItem, DescItemKind, ResultContainer, process_code,
};
use lsp_types::SemanticTokenType;
use rowan::{TextRange, TextSize};
use crate::handlers::semantic_token::semantic_token_builder::SemanticBuilder;
pub fn inject_language(
builder: &mut SemanticBuilder,
lang_name: &str,
comment: LuaComment,
) -> Option<()> {
let owner = comment.get_owner()?;
let lang = CodeBlockLang::try_parse(lang_name)?;
for literal in owner.descendants::<LuaLiteralExpr>() {
if let Some(LuaLiteralToken::String(str_token)) = literal.get_literal() {
process_inject_lang_string_token(builder, lang, &str_token);
}
}
Some(())
}
pub fn process_inject_lang_string_token(
builder: &mut SemanticBuilder,
lang: CodeBlockLang,
str_token: &LuaStringToken,
) -> Option<()> {
let code_block_info = divide_into_quote_and_code_block(str_token)?;
let code_block_range = code_block_info.code_block;
let code_block_source = SourceRange::from_start_end(
u32::from(code_block_range.start()) as usize,
u32::from(code_block_range.end()) as usize,
);
let code_block_str = str_token.slice(code_block_range)?;
let reader = Reader::new(code_block_str);
let mut result = InjectResult::new();
process_code(
&mut result,
code_block_source,
reader,
LexerState::Normal,
lang,
);
for desc_item in result.results() {
if let DescItemKind::CodeBlockHl(highlight_kind) = desc_item.kind {
let token_type = match highlight_kind {
CodeBlockHighlightKind::Keyword => SemanticTokenType::KEYWORD,
CodeBlockHighlightKind::String => SemanticTokenType::STRING,
CodeBlockHighlightKind::Number => SemanticTokenType::NUMBER,
CodeBlockHighlightKind::Comment => SemanticTokenType::COMMENT,
CodeBlockHighlightKind::Function => SemanticTokenType::FUNCTION,
CodeBlockHighlightKind::Class => SemanticTokenType::CLASS,
CodeBlockHighlightKind::Enum => SemanticTokenType::ENUM,
CodeBlockHighlightKind::Variable => SemanticTokenType::VARIABLE,
CodeBlockHighlightKind::Property => SemanticTokenType::PROPERTY,
CodeBlockHighlightKind::Decorator => SemanticTokenType::DECORATOR,
CodeBlockHighlightKind::Operators => SemanticTokenType::OPERATOR,
_ => continue, };
let sub_token_range = TextRange::new(
desc_item.range.start() + code_block_range.start(),
desc_item.range.end() + code_block_range.start(),
);
if let Some(token_text) = str_token.slice(sub_token_range) {
builder.push_at_range(token_text, sub_token_range, token_type, &[]);
}
}
}
for quote_range in code_block_info.quote_ranges {
let len = u32::from(quote_range.end() - quote_range.start());
builder.push_at_position(quote_range.start(), len, SemanticTokenType::STRING, None);
}
builder.add_special_string_range(str_token.get_range());
Some(())
}
struct InjectResult {
result: Vec<DescItem>,
}
impl ResultContainer for InjectResult {
fn results(&self) -> &Vec<DescItem> {
&self.result
}
fn results_mut(&mut self) -> &mut Vec<DescItem> {
&mut self.result
}
fn cursor_position(&self) -> Option<usize> {
None
}
}
impl InjectResult {
pub fn new() -> Self {
InjectResult { result: Vec::new() }
}
}
struct CodeBlockInfo {
code_block: TextRange,
quote_ranges: Vec<TextRange>,
}
fn divide_into_quote_and_code_block(str_token: &LuaStringToken) -> Option<CodeBlockInfo> {
let str_token_range = str_token.get_range();
let text = str_token.get_text();
let mut quote_ranges = Vec::new();
if text.is_empty() {
return None;
}
let mut code_block_start = str_token_range.start();
let mut code_block_end = str_token_range.end();
let range_start = str_token_range.start();
if text.starts_with("[[") || text.starts_with("[=") {
let mut equal_count = 0;
let mut start_end = 2;
for c in text.chars().skip(1) {
if c == '=' {
equal_count += 1;
} else if c == '[' {
start_end = 2 + equal_count;
break;
} else {
break;
}
}
let start_quote_range =
TextRange::new(range_start, range_start + TextSize::from(start_end as u32));
quote_ranges.push(start_quote_range);
code_block_start = start_quote_range.end();
let end_pattern = format!("]{}]", "=".repeat(equal_count));
if let Some(end_pos) = text.rfind(&end_pattern) {
let end_quote_start = range_start + rowan::TextSize::from(end_pos as u32);
let end_quote_range = TextRange::new(
end_quote_start,
range_start + rowan::TextSize::from(text.len() as u32),
);
quote_ranges.push(end_quote_range);
code_block_end = end_quote_range.start();
}
} else if text.starts_with('"') || text.starts_with('\'') {
let quote_char = text.chars().next().unwrap();
let start_quote_range = TextRange::new(range_start, range_start + TextSize::from(1));
quote_ranges.push(start_quote_range);
code_block_start = start_quote_range.end();
if text.len() > 1 && text.ends_with(quote_char) {
let end_quote_start = range_start + TextSize::from((text.len() - 1) as u32);
let end_quote_range = TextRange::new(
end_quote_start,
range_start + TextSize::from(text.len() as u32),
);
quote_ranges.push(end_quote_range);
code_block_end = end_quote_range.start();
}
}
if code_block_start > code_block_end {
return None;
}
Some(CodeBlockInfo {
code_block: TextRange::new(code_block_start, code_block_end),
quote_ranges,
})
}