use cairo_lang_doc::db::DocGroup;
use cairo_lang_filesystem::span::TextOffset;
use cairo_lang_syntax::node::{
SyntaxNode, TypedSyntaxNode, ast, green::GreenNodeDetails, kind::SyntaxKind,
};
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
use itertools::Itertools;
use lsp_types::SemanticToken;
use super::token_kind::SemanticTokenKind;
use crate::{
ide::markdown::COMMENT_TOKEN_PREFIX_LEN,
ide::semantic_highlighting::encoder::{EncodedToken, TokenEncoder},
lang::db::AnalysisDatabase,
};
#[derive(Default)]
pub struct SemanticTokensTraverser {
encoder: TokenEncoder,
offset_to_kind_lookahead: UnorderedHashMap<TextOffset, SemanticTokenKind>,
}
impl SemanticTokensTraverser {
pub fn get_semantic_tokens<'db>(
&mut self,
db: &'db AnalysisDatabase,
node: SyntaxNode<'db>,
) -> Vec<SemanticToken> {
let green_node = node.green_node(db);
match &green_node.details {
GreenNodeDetails::Token(_) => match green_node.kind {
SyntaxKind::TokenSingleLineDocComment | SyntaxKind::TokenSingleLineInnerComment => {
self.encode_single_line_comment_tokens(db, node)
}
_ => self.find_semantic_tokens_for_syntax_token(
db,
node,
&node.text(db).expect("Node text should be available").to_string(db),
green_node.kind,
),
},
GreenNodeDetails::Node { .. } => {
let mut semantic_tokens = vec![];
let children = node.get_children(db);
self.mark_future_tokens_for_node(db, node, green_node.kind);
for child in children.iter() {
semantic_tokens.extend(self.get_semantic_tokens(db, *child));
}
semantic_tokens
}
}
}
fn find_semantic_tokens_for_syntax_token<'db>(
&mut self,
db: &'db AnalysisDatabase,
node: SyntaxNode<'db>,
token_text: &str,
green_node_kind: SyntaxKind,
) -> Vec<SemanticToken> {
if green_node_kind == SyntaxKind::TokenNewline {
self.encoder.next_line();
return vec![];
}
let width = token_text.len() as u32;
let maybe_semantic_kind = self
.offset_to_kind_lookahead
.remove(&node.offset(db))
.or_else(|| SemanticTokenKind::from_syntax_node(db, node));
if let Some(semantic_kind) = maybe_semantic_kind {
let text = node.text(db).expect("Node text should be available").to_string(db);
if text.contains('\n') {
self.get_tokens_from_multiline_syntax_node(semantic_kind, &text)
} else {
vec![self.get_semantic_token(width, &semantic_kind)]
}
} else {
self.encoder.skip(width);
vec![]
}
}
fn encode_single_line_comment_tokens<'db>(
&mut self,
db: &'db AnalysisDatabase,
node: SyntaxNode<'db>,
) -> Vec<SemanticToken> {
let mut tokens = Vec::new();
self.encoder.skip(COMMENT_TOKEN_PREFIX_LEN as u32);
let token_text = node.text(db).expect("Node text should be available").to_string(db);
let content = &token_text[COMMENT_TOKEN_PREFIX_LEN..];
let link_ranges: Vec<(usize, usize)> = db
.get_embedded_markdown_links(node)
.into_iter()
.map(|link| {
let base_span = node.span(db);
let start = (link.link_span.start - base_span.start).as_u32() as usize;
let end = (link.link_span.end - base_span.start).as_u32() as usize;
(start, end)
})
.collect();
let mut cursor = COMMENT_TOKEN_PREFIX_LEN;
for (start, end) in link_ranges.into_iter().sorted_by_key(|(s, _)| *s) {
if start > cursor {
self.encoder.skip((start - cursor) as u32);
}
assert!(end > start, "Incorrect link range");
tokens.push(
self.get_semantic_token((end - start) as u32, &SemanticTokenKind::IntraDocLink),
);
cursor = end;
}
if cursor < content.len() {
self.encoder.skip((content.len() - cursor) as u32);
}
tokens
}
fn get_tokens_from_multiline_syntax_node(
&mut self,
node_semantic_kind: SemanticTokenKind,
node_text: &str,
) -> Vec<SemanticToken> {
let mut tokens = vec![];
for line in node_text.split_inclusive('\n') {
tokens.push(self.get_semantic_token(line.len() as u32, &node_semantic_kind));
if line.ends_with('\n') {
self.encoder.next_line();
}
}
tokens
}
fn mark_future_tokens_for_node<'db>(
&mut self,
db: &'db AnalysisDatabase,
node: SyntaxNode<'db>,
green_node_kind: SyntaxKind,
) {
match green_node_kind {
SyntaxKind::Param => {
self.mark_future_token(
ast::Param::from_syntax_node(db, node).name(db).as_syntax_node().offset(db),
SemanticTokenKind::Parameter,
);
}
SyntaxKind::FunctionWithBody => {
self.mark_future_token(
ast::FunctionWithBody::from_syntax_node(db, node)
.declaration(db)
.name(db)
.as_syntax_node()
.offset(db),
SemanticTokenKind::Function,
);
}
SyntaxKind::ItemStruct => self.mark_future_token(
ast::ItemStruct::from_syntax_node(db, node).name(db).as_syntax_node().offset(db),
SemanticTokenKind::Struct,
),
SyntaxKind::ItemEnum => self.mark_future_token(
ast::ItemEnum::from_syntax_node(db, node).name(db).as_syntax_node().offset(db),
SemanticTokenKind::Enum,
),
_ => {}
}
}
fn get_semantic_token(
&mut self,
width: u32,
assumed_semantic_kind: &SemanticTokenKind,
) -> SemanticToken {
let EncodedToken { delta_line, delta_start } = self.encoder.encode(width);
SemanticToken {
delta_line,
delta_start,
length: width,
token_type: *assumed_semantic_kind as u32,
token_modifiers_bitset: 0,
}
}
fn mark_future_token(&mut self, offset: TextOffset, semantic_kind: SemanticTokenKind) {
self.offset_to_kind_lookahead.insert(offset, semantic_kind);
}
}