use super::semantic_token_builder::SemanticBuilder;
use crate::handlers::semantic_token::function_string_highlight::fun_string_highlight;
use crate::handlers::semantic_token::semantic_token_builder::{
SemanticTokenModifierKind, SemanticTokenTypeKind,
};
use crate::util::parse_desc;
use crate::{context::ClientId, handlers::semantic_token::language_injector::inject_language};
use emmylua_code_analysis::{
Emmyrc, LuaDecl, LuaDeclExtra, LuaMemberId, LuaMemberOwner, LuaSemanticDeclId, LuaType,
LuaTypeDeclId, SemanticDeclLevel, SemanticModel, WorkspaceId, parse_require_module_info,
};
use emmylua_parser::{
LuaAst, LuaAstNode, LuaAstToken, LuaCallArgList, LuaCallExpr, LuaComment, LuaDocFieldKey,
LuaDocGenericDecl, LuaDocGenericDeclList, LuaDocObjectFieldKey, LuaDocType, LuaExpr,
LuaGeneralToken, LuaKind, LuaLiteralToken, LuaNameToken, LuaSyntaxKind, LuaSyntaxNode,
LuaSyntaxToken, LuaTokenKind, LuaVarExpr,
};
use emmylua_parser_desc::{CodeBlockHighlightKind, DescItem, DescItemKind};
use lsp_types::SemanticToken;
use rowan::{NodeOrToken, TextRange, TextSize};
pub fn build_semantic_tokens(
semantic_model: &SemanticModel,
support_muliline_token: bool,
client_id: ClientId,
emmyrc: &Emmyrc,
) -> Option<Vec<SemanticToken>> {
let root = semantic_model.get_root();
let document = semantic_model.get_document();
let mut builder = SemanticBuilder::new(&document, support_muliline_token);
for node_or_token in root.syntax().descendants_with_tokens() {
match node_or_token {
NodeOrToken::Node(node) => {
build_node_semantic_token(semantic_model, &mut builder, node, emmyrc);
}
NodeOrToken::Token(token) => {
build_tokens_semantic_token(
semantic_model,
&mut builder,
&token,
client_id,
emmyrc,
);
}
}
}
Some(builder.build())
}
fn build_tokens_semantic_token(
_semantic_model: &SemanticModel,
builder: &mut SemanticBuilder,
token: &LuaSyntaxToken,
client_id: ClientId,
emmyrc: &Emmyrc,
) {
match token.kind().into() {
LuaTokenKind::TkLongString | LuaTokenKind::TkString => {
if !builder.is_special_string_range(&token.text_range()) {
builder.push(token, SemanticTokenTypeKind::String);
}
}
LuaTokenKind::TkAnd
| LuaTokenKind::TkBreak
| LuaTokenKind::TkDo
| LuaTokenKind::TkElse
| LuaTokenKind::TkElseIf
| LuaTokenKind::TkEnd
| LuaTokenKind::TkFor
| LuaTokenKind::TkFunction
| LuaTokenKind::TkGoto
| LuaTokenKind::TkIf
| LuaTokenKind::TkIn
| LuaTokenKind::TkNot
| LuaTokenKind::TkOr
| LuaTokenKind::TkRepeat
| LuaTokenKind::TkReturn
| LuaTokenKind::TkThen
| LuaTokenKind::TkUntil
| LuaTokenKind::TkWhile
| LuaTokenKind::TkGlobal => {
builder.push(token, SemanticTokenTypeKind::Keyword);
}
LuaTokenKind::TkLocal => {
if !client_id.is_vscode() {
builder.push(token, SemanticTokenTypeKind::Keyword);
}
}
LuaTokenKind::TkPlus
| LuaTokenKind::TkMinus
| LuaTokenKind::TkMul
| LuaTokenKind::TkDiv
| LuaTokenKind::TkIDiv
| LuaTokenKind::TkDot
| LuaTokenKind::TkConcat
| LuaTokenKind::TkEq
| LuaTokenKind::TkGe
| LuaTokenKind::TkLe
| LuaTokenKind::TkNe
| LuaTokenKind::TkShl
| LuaTokenKind::TkShr
| LuaTokenKind::TkLt
| LuaTokenKind::TkGt
| LuaTokenKind::TkMod
| LuaTokenKind::TkPow
| LuaTokenKind::TkLen
| LuaTokenKind::TkBitAnd
| LuaTokenKind::TkBitOr
| LuaTokenKind::TkBitXor
| LuaTokenKind::TkAssign => {
builder.push(token, SemanticTokenTypeKind::Operator);
}
LuaTokenKind::TkLeftBrace | LuaTokenKind::TkRightBrace => {
if let Some(parent) = token.parent()
&& !matches!(
parent.kind().into(),
LuaSyntaxKind::TableArrayExpr
| LuaSyntaxKind::TableEmptyExpr
| LuaSyntaxKind::TableObjectExpr
)
{
builder.push(token, SemanticTokenTypeKind::Operator);
}
}
LuaTokenKind::TkColon => {
if let Some(parent) = token.parent()
&& parent.kind() != LuaSyntaxKind::IndexExpr.into()
{
builder.push(token, SemanticTokenTypeKind::Operator);
}
}
LuaTokenKind::TkLeftBracket | LuaTokenKind::TkRightBracket => {
if let Some(parent) = token.parent()
&& matches!(
parent.kind().into(),
LuaSyntaxKind::TableFieldAssign | LuaSyntaxKind::IndexExpr
)
{
builder.push(token, SemanticTokenTypeKind::Delimiter);
} else {
builder.push(token, SemanticTokenTypeKind::Operator);
}
}
LuaTokenKind::TkLeftParen | LuaTokenKind::TkRightParen => {
if let Some(parent) = token.parent()
&& matches!(
parent.kind().into(),
LuaSyntaxKind::ParamList
| LuaSyntaxKind::CallArgList
| LuaSyntaxKind::ParenExpr
)
{
builder.push(token, SemanticTokenTypeKind::Delimiter);
} else {
builder.push(token, SemanticTokenTypeKind::Operator);
}
}
LuaTokenKind::TkTrue | LuaTokenKind::TkFalse | LuaTokenKind::TkNil => {
if is_doc_type_literal_token(token) {
builder.push(token, SemanticTokenTypeKind::Type);
} else {
builder.push_with_modifier(
token,
SemanticTokenTypeKind::Keyword,
SemanticTokenModifierKind::READONLY,
);
}
}
LuaTokenKind::TkComplex | LuaTokenKind::TkInt | LuaTokenKind::TkFloat => {
builder.push(token, SemanticTokenTypeKind::Number);
}
LuaTokenKind::TkTagClass
| LuaTokenKind::TkTagEnum
| LuaTokenKind::TkTagInterface
| LuaTokenKind::TkTagAlias
| LuaTokenKind::TkTagModule
| LuaTokenKind::TkTagField
| LuaTokenKind::TkTagType
| LuaTokenKind::TkTagParam
| LuaTokenKind::TkTagReturn
| LuaTokenKind::TkTagOverload
| LuaTokenKind::TkTagGeneric
| LuaTokenKind::TkTagSee
| LuaTokenKind::TkTagDeprecated
| LuaTokenKind::TkTagAsync
| LuaTokenKind::TkTagCast
| LuaTokenKind::TkTagOther
| LuaTokenKind::TkTagReadonly
| LuaTokenKind::TkTagDiagnostic
| LuaTokenKind::TkTagMeta
| LuaTokenKind::TkTagVersion
| LuaTokenKind::TkTagAs
| LuaTokenKind::TkTagNodiscard
| LuaTokenKind::TkTagOperator
| LuaTokenKind::TkTagMapping
| LuaTokenKind::TkTagNamespace
| LuaTokenKind::TkTagUsing
| LuaTokenKind::TkTagSource
| LuaTokenKind::TkTagReturnCast
| LuaTokenKind::TkTagReturnOverload
| LuaTokenKind::TkLanguage
| LuaTokenKind::TkTagAttribute
| LuaTokenKind::TKTagSchema => {
builder.push_with_modifier(
token,
SemanticTokenTypeKind::Keyword,
SemanticTokenModifierKind::DOCUMENTATION,
);
}
LuaTokenKind::TkDocKeyOf
| LuaTokenKind::TkDocExtends
| LuaTokenKind::TkDocNew
| LuaTokenKind::TkDocAs
| LuaTokenKind::TkDocIn
| LuaTokenKind::TkDocInfer
| LuaTokenKind::TkDocReadonly => {
builder.push_with_modifier(
token,
SemanticTokenTypeKind::Keyword,
SemanticTokenModifierKind::DOCUMENTATION,
);
}
LuaTokenKind::TkNormalStart | LuaTokenKind::TKNonStdComment => {
builder.push(token, SemanticTokenTypeKind::Comment);
}
LuaTokenKind::TkDocDetail => {
let rendering_description = token
.parent()
.is_some_and(|parent| parent.kind() == LuaSyntaxKind::DocDescription.into());
let description_parsing_is_enabled = emmyrc.semantic_tokens.render_documentation_markup;
if !(rendering_description && description_parsing_is_enabled) {
builder.push(token, SemanticTokenTypeKind::Comment);
}
}
LuaTokenKind::TkDocQuestion | LuaTokenKind::TkDocOr | LuaTokenKind::TkDocAnd => {
builder.push_with_modifier(
token,
SemanticTokenTypeKind::Operator,
SemanticTokenModifierKind::DOCUMENTATION,
);
}
LuaTokenKind::TkDocVisibility | LuaTokenKind::TkTagVisibility => {
builder.push_with_modifier(
token,
SemanticTokenTypeKind::Keyword,
SemanticTokenModifierKind::MODIFICATION | SemanticTokenModifierKind::DOCUMENTATION,
);
}
LuaTokenKind::TkDocVersionNumber => {
builder.push_with_modifier(
token,
SemanticTokenTypeKind::Number,
SemanticTokenModifierKind::DOCUMENTATION,
);
}
LuaTokenKind::TkStringTemplateType => {
builder.push_with_modifier(
token,
SemanticTokenTypeKind::String,
SemanticTokenModifierKind::DOCUMENTATION,
);
}
LuaTokenKind::TkDocMatch => {
builder.push_with_modifier(
token,
SemanticTokenTypeKind::Keyword,
SemanticTokenModifierKind::DOCUMENTATION,
);
}
LuaTokenKind::TKDocPath | LuaTokenKind::TkDocSeeContent => {
builder.push_with_modifier(
token,
SemanticTokenTypeKind::String,
SemanticTokenModifierKind::DOCUMENTATION,
);
}
LuaTokenKind::TkDocRegion | LuaTokenKind::TkDocEndRegion => {
builder.push(token, SemanticTokenTypeKind::Comment);
}
LuaTokenKind::TkDocStart | LuaTokenKind::TkDocContinue | LuaTokenKind::TkDocContinueOr => {
render_doc_at(builder, token)
}
_ => {}
}
}
fn is_doc_type_literal_token(token: &LuaSyntaxToken) -> bool {
token
.parent()
.is_some_and(|parent| parent.kind() == LuaSyntaxKind::TypeLiteral.into())
}
fn build_node_semantic_token(
semantic_model: &SemanticModel,
builder: &mut SemanticBuilder,
node: LuaSyntaxNode,
emmyrc: &Emmyrc,
) -> Option<()> {
match LuaAst::cast(node)? {
LuaAst::LuaDocTagClass(doc_class) => {
if let Some(name) = doc_class.get_name_token() {
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Class,
SemanticTokenModifierKind::DECLARATION,
);
}
if let Some(attribs) = doc_class.get_type_flag() {
for token in attribs.tokens::<LuaGeneralToken>() {
builder.push(token.syntax(), SemanticTokenTypeKind::Decorator);
}
}
if let Some(generic_list) = doc_class.get_generic_decl() {
render_type_parameter_list(builder, &generic_list);
}
}
LuaAst::LuaDocTagEnum(doc_enum) => {
let name = doc_enum.get_name_token()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Enum,
SemanticTokenModifierKind::DECLARATION,
);
if let Some(attribs) = doc_enum.get_type_flag() {
for token in attribs.tokens::<LuaGeneralToken>() {
builder.push(token.syntax(), SemanticTokenTypeKind::Decorator);
}
}
}
LuaAst::LuaDocTagAlias(doc_alias) => {
let name = doc_alias.get_name_token()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Type,
SemanticTokenModifierKind::DECLARATION,
);
if let Some(generic_decl_list) = doc_alias.get_generic_decl_list() {
render_type_parameter_list(builder, &generic_decl_list);
}
}
LuaAst::LuaDocTagField(doc_field) => {
if let Some(LuaDocFieldKey::Name(name)) = doc_field.get_field_key() {
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Property,
SemanticTokenModifierKind::DECLARATION,
);
}
}
LuaAst::LuaDocTagDiagnostic(doc_diagnostic) => {
let name = doc_diagnostic.get_action_token()?;
builder.push(name.syntax(), SemanticTokenTypeKind::Property);
if let Some(code_list) = doc_diagnostic.get_code_list() {
for code in code_list.get_codes() {
builder.push(code.syntax(), SemanticTokenTypeKind::Regexp);
}
}
}
LuaAst::LuaDocTagParam(doc_param) => {
let name = doc_param.get_name_token()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Parameter,
SemanticTokenModifierKind::DECLARATION,
);
}
LuaAst::LuaDocTagReturn(doc_return) => {
let type_name_list = doc_return.get_info_list();
for (_, name) in type_name_list {
if let Some(name) = name {
builder.push(name.syntax(), SemanticTokenTypeKind::Variable);
}
}
}
LuaAst::LuaDocTagReturnOverload(_) => {}
LuaAst::LuaDocTagCast(doc_cast) => {
if let Some(target_expr) = doc_cast.get_key_expr() {
match target_expr {
LuaExpr::NameExpr(name_expr) => {
builder.push(
name_expr.get_name_token()?.syntax(),
SemanticTokenTypeKind::Variable,
);
}
LuaExpr::IndexExpr(index_expr) => {
let position = index_expr.syntax().text_range().start();
let len = index_expr.syntax().text_range().len();
builder.push_at_position(
position,
len.into(),
SemanticTokenTypeKind::Variable,
None,
);
}
_ => {}
}
}
if let Some(NodeOrToken::Token(token)) = doc_cast.syntax().prev_sibling_or_token()
&& token.kind() == LuaKind::Token(LuaTokenKind::TkDocLongStart)
{
render_doc_at(builder, &token);
}
}
LuaAst::LuaDocTagAs(doc_as) => {
if let Some(NodeOrToken::Token(token)) = doc_as.syntax().prev_sibling_or_token()
&& token.kind() == LuaKind::Token(LuaTokenKind::TkDocLongStart)
{
render_doc_at(builder, &token);
}
}
LuaAst::LuaDocTagGeneric(doc_generic) => {
let type_parameter_list = doc_generic.get_generic_decl_list()?;
render_type_parameter_list(builder, &type_parameter_list);
}
LuaAst::LuaDocTagNamespace(doc_namespace) => {
let name = doc_namespace.get_name_token()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Namespace,
SemanticTokenModifierKind::DECLARATION,
);
}
LuaAst::LuaDocTagUsing(doc_using) => {
let name = doc_using.get_name_token()?;
builder.push(name.syntax(), SemanticTokenTypeKind::Namespace);
}
LuaAst::LuaParamName(param_name) => {
let name_token = param_name.get_name_token()?;
if builder.contains_token(name_token.syntax()) {
return Some(());
}
handle_name_node(semantic_model, builder, param_name.syntax(), &name_token);
}
LuaAst::LuaLocalName(local_name) => {
let name_token = local_name.get_name_token()?;
if builder.contains_token(name_token.syntax()) {
return Some(());
}
handle_name_node(semantic_model, builder, local_name.syntax(), &name_token);
}
LuaAst::LuaNameExpr(name_expr) => {
let name_token = name_expr.get_name_token()?;
if builder.contains_token(name_token.syntax()) {
return Some(());
}
handle_name_node(semantic_model, builder, name_expr.syntax(), &name_token)
.unwrap_or_else(|| {
let name_text = name_token.get_name_text();
builder.push(
name_token.syntax(),
default_identifier_token_type(name_text),
);
});
}
LuaAst::LuaForRangeStat(for_range_stat) => {
for name in for_range_stat.get_var_name_list() {
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Variable,
SemanticTokenModifierKind::DECLARATION,
);
}
}
LuaAst::LuaForStat(for_stat) => {
let name = for_stat.get_var_name()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Variable,
SemanticTokenModifierKind::DECLARATION,
);
}
LuaAst::LuaLocalFuncStat(local_func_stat) => {
let name = local_func_stat.get_local_name()?.get_name_token()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Function,
SemanticTokenModifierKind::DECLARATION,
);
}
LuaAst::LuaFuncStat(func_stat) => {
let func_name = func_stat.get_func_name()?;
match func_name {
LuaVarExpr::NameExpr(name_expr) => {
let name = name_expr.get_name_token()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Function,
SemanticTokenModifierKind::DECLARATION,
);
}
LuaVarExpr::IndexExpr(index_expr) => {
let name = index_expr.get_index_name_token()?;
builder.push_with_modifier(
&name,
SemanticTokenTypeKind::Method,
SemanticTokenModifierKind::DECLARATION,
);
}
}
}
LuaAst::LuaLocalAttribute(local_attribute) => {
let name = local_attribute.get_name_token()?;
builder.push(name.syntax(), SemanticTokenTypeKind::Keyword);
}
LuaAst::LuaCallExpr(call_expr) => {
let prefix = call_expr.get_prefix_expr()?;
match prefix {
LuaExpr::NameExpr(name_expr) => {
let name = name_expr.get_name_token()?;
let prefix_type = semantic_model
.infer_expr(LuaExpr::NameExpr(name_expr.clone()))
.ok();
if let Some(prefix_type) = prefix_type {
match prefix_type {
LuaType::Signature(signature) => {
if semantic_model
.get_db()
.get_module_index()
.is_meta_file(&signature.get_file_id())
{
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Function,
SemanticTokenModifierKind::DEFAULT_LIBRARY,
);
return Some(());
}
}
_ => {
if !prefix_type.is_function() {
return Some(());
}
}
}
}
builder.push(name.syntax(), SemanticTokenTypeKind::Function);
}
LuaExpr::IndexExpr(index_expr) => {
let name = index_expr.get_index_name_token()?;
if call_expr.get_args_list().is_some() {
builder.push(&name, SemanticTokenTypeKind::Method);
} else {
builder.push(&name, SemanticTokenTypeKind::Function);
}
}
_ => {}
}
}
LuaAst::LuaDocNameType(doc_name_type) => {
let name = doc_name_type.get_name_token()?;
let name_text = name.get_name_text();
if name_text == "self"
|| name_text == "nil"
|| name_text == "boolean"
|| name_text == "number"
|| name_text == "string"
|| name_text == "table"
|| name_text == "function"
|| name_text == "userdata"
|| name_text == "thread"
{
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Type,
SemanticTokenModifierKind::DEFAULT_LIBRARY,
);
} else {
builder.push(name.syntax(), SemanticTokenTypeKind::Type);
}
}
LuaAst::LuaDocObjectType(doc_object_type) => {
let fields = doc_object_type.get_fields();
for field in fields {
if let Some(field_key) = field.get_field_key()
&& let LuaDocObjectFieldKey::Name(name) = &field_key
{
builder.push(name.syntax(), SemanticTokenTypeKind::Property);
}
}
}
LuaAst::LuaDocFuncType(doc_func_type) => {
for name_token in doc_func_type.tokens::<LuaNameToken>() {
match name_token.get_name_text() {
"fun" => {
builder.push(name_token.syntax(), SemanticTokenTypeKind::Keyword);
}
"async" => {
builder.push_with_modifier(
name_token.syntax(),
SemanticTokenTypeKind::Keyword,
SemanticTokenModifierKind::ASYNC,
);
}
_ => {}
}
}
for param in doc_func_type.get_params() {
let name = param.get_name_token()?;
builder.push(name.syntax(), SemanticTokenTypeKind::Parameter);
}
}
LuaAst::LuaIndexExpr(index_expr) => {
if let Some(LuaExpr::NameExpr(prefix_name_expr)) = index_expr.get_prefix_expr()
&& let Some(prefix_name) = prefix_name_expr.get_name_token()
&& !builder.contains_token(prefix_name.syntax())
&& let Some(LuaSemanticDeclId::LuaDecl(prefix_decl_id)) = semantic_model.find_decl(
prefix_name_expr.syntax().clone().into(),
SemanticDeclLevel::NoTrace,
)
&& let Some(prefix_decl) = semantic_model
.get_db()
.get_decl_index()
.get_decl(&prefix_decl_id)
&& is_require_decl(semantic_model, prefix_decl)
{
builder.push(prefix_name.syntax(), SemanticTokenTypeKind::Namespace);
}
let name = index_expr.get_name_token()?;
if builder.contains_token(name.syntax()) {
return Some(());
}
let semantic_decl = semantic_model
.find_decl(name.syntax().clone().into(), SemanticDeclLevel::default());
if let Some(property_owner) = semantic_decl
&& let LuaSemanticDeclId::Member(member_id) = property_owner
{
let decl_type = semantic_model.get_type(member_id.into());
if decl_type.is_function() {
builder.push(name.syntax(), SemanticTokenTypeKind::Method);
return Some(());
}
if decl_type.is_def() {
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Class,
SemanticTokenModifierKind::READONLY,
);
return Some(());
}
let owner_id = semantic_model
.get_db()
.get_member_index()
.get_current_owner(&member_id);
if let Some(LuaMemberOwner::Type(type_id)) = owner_id
&& let Some(type_decl) = semantic_model
.get_db()
.get_type_index()
.get_type_decl(type_id)
&& type_decl.is_enum()
{
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::EnumMember,
SemanticTokenModifierKind::READONLY,
);
return Some(());
}
}
if index_expr
.syntax()
.parent()
.is_some_and(|p| p.kind() == LuaSyntaxKind::CallExpr.into())
{
builder.push(name.syntax(), SemanticTokenTypeKind::Method);
} else {
builder.push(name.syntax(), SemanticTokenTypeKind::Property);
}
}
LuaAst::LuaTableField(table_field) => {
let owner_id =
LuaMemberId::new(table_field.get_syntax_id(), semantic_model.get_file_id());
if let Some(member) = semantic_model
.get_db()
.get_member_index()
.get_member(&owner_id)
{
let owner_id = semantic_model
.get_db()
.get_member_index()
.get_current_owner(&member.get_id());
if let Some(LuaMemberOwner::Type(type_id)) = owner_id
&& let Some(type_decl) = semantic_model
.get_db()
.get_type_index()
.get_type_decl(type_id)
&& type_decl.is_enum()
{
if let Some(field_name) = table_field.get_field_key()?.get_name() {
builder.push_with_modifier(
field_name.syntax(),
SemanticTokenTypeKind::EnumMember,
SemanticTokenModifierKind::DECLARATION,
);
}
return Some(());
}
}
let value_type = semantic_model
.infer_expr(table_field.get_value_expr()?.clone())
.ok()?;
match value_type {
LuaType::Signature(_) | LuaType::DocFunction(_) => {
if let Some(field_name) = table_field.get_field_key()?.get_name() {
builder.push_with_modifier(
field_name.syntax(),
SemanticTokenTypeKind::Method,
SemanticTokenModifierKind::DECLARATION,
);
}
}
_ => {
if let Some(field_name) = table_field.get_field_key()?.get_name() {
builder.push_with_modifier(
field_name.syntax(),
SemanticTokenTypeKind::Property,
SemanticTokenModifierKind::DECLARATION,
);
}
}
}
}
LuaAst::LuaDocDescription(description) => {
if !emmyrc.semantic_tokens.render_documentation_markup {
for token in description.tokens::<LuaGeneralToken>() {
if matches!(
token.get_token_kind(),
LuaTokenKind::TkDocDetail | LuaTokenKind::TkNormalStart
) {
builder.push(token.syntax(), SemanticTokenTypeKind::Comment);
}
}
return None;
}
if let Some(start_token) = description.tokens::<LuaGeneralToken>().next() {
if start_token.get_text().starts_with('#') {
builder.push_at_position(
start_token.get_range().start(),
1,
SemanticTokenTypeKind::Comment,
None,
);
}
}
let desc_range = description.get_range();
let document = semantic_model.get_document();
let text = document.get_text();
let items = parse_desc(
semantic_model
.get_module()
.map(|m| m.workspace_id)
.unwrap_or(WorkspaceId::MAIN),
emmyrc,
text,
description,
None,
);
render_desc_ranges(builder, items, desc_range);
}
LuaAst::LuaDocTagLanguage(language) => {
let name = language.get_name_token()?;
builder.push(name.syntax(), SemanticTokenTypeKind::String);
let language_text = name.get_name_text();
let comment = language.ancestors::<LuaComment>().next()?;
inject_language(builder, language_text, comment);
}
LuaAst::LuaLiteralExpr(literal_expr) => {
let call_expr = literal_expr
.get_parent::<LuaCallArgList>()?
.get_parent::<LuaCallExpr>()?;
let literal_token = literal_expr.get_literal()?;
if let LuaLiteralToken::String(string_token) = literal_token
&& !builder.is_special_string_range(&string_token.get_range())
{
fun_string_highlight(builder, semantic_model, call_expr, &string_token);
}
}
LuaAst::LuaDocTagAttributeUse(tag_use) => {
if let Some(token) = tag_use.token_by_kind(LuaTokenKind::TkDocAttributeUse) {
builder.push(token.syntax(), SemanticTokenTypeKind::Keyword);
}
if let Some(token) = tag_use.syntax().last_token() {
builder.push(&token, SemanticTokenTypeKind::Keyword);
}
for attribute_use in tag_use.get_attribute_uses() {
if let Some(token) = attribute_use.get_type()?.get_name_token() {
builder.push_with_modifier(
token.syntax(),
SemanticTokenTypeKind::Decorator,
SemanticTokenModifierKind::DECLARATION
| SemanticTokenModifierKind::DEFAULT_LIBRARY,
);
}
}
}
LuaAst::LuaDocTagAttribute(tag_attribute) => {
if let Some(name) = tag_attribute.get_name_token() {
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Type,
SemanticTokenModifierKind::DECLARATION,
);
}
if let Some(LuaDocType::Attribute(attribute)) = tag_attribute.get_type() {
for param in attribute.get_params() {
if let Some(name) = param.get_name_token() {
builder.push(name.syntax(), SemanticTokenTypeKind::Parameter);
}
}
}
}
LuaAst::LuaDocInferType(infer_type) => {
if let Some(gen_decl) = infer_type.get_generic_decl() {
render_type_parameter(builder, &gen_decl);
}
if let Some(name) = infer_type.token::<LuaNameToken>() {
if name.get_name_text() == "infer" {
builder.push(name.syntax(), SemanticTokenTypeKind::Comment);
}
}
}
_ => {}
}
Some(())
}
fn handle_name_node(
semantic_model: &SemanticModel,
builder: &mut SemanticBuilder,
node: &LuaSyntaxNode,
name_token: &LuaNameToken,
) -> Option<()> {
let name_text = name_token.get_name_text();
if name_text == "self" {
builder.push_with_modifier(
name_token.syntax(),
SemanticTokenTypeKind::Variable,
SemanticTokenModifierKind::DEFINITION,
);
return Some(());
}
let semantic_decl = semantic_model.find_decl(node.clone().into(), SemanticDeclLevel::default());
if semantic_decl.is_none()
&& matches!(
name_text,
"_G" | "_ENV"
| "_VERSION"
| "arg"
| "package"
| "require"
| "load"
| "loadfile"
| "dofile"
| "print"
| "assert"
| "error"
| "warn"
| "type"
| "getmetatable"
| "setmetatable"
| "rawget"
| "rawset"
| "rawequal"
| "rawlen"
| "next"
| "pairs"
| "ipairs"
| "tostring"
| "tonumber"
| "select"
| "unpack"
| "pcall"
| "xpcall"
| "collectgarbage"
)
{
builder.push_with_modifier(
name_token.syntax(),
SemanticTokenTypeKind::Function,
SemanticTokenModifierKind::DEFAULT_LIBRARY | SemanticTokenModifierKind::READONLY,
);
return Some(());
}
let semantic_decl = semantic_decl?;
match semantic_decl {
LuaSemanticDeclId::Member(member_id) => {
let decl_type = semantic_model.get_type(member_id.into());
if matches!(decl_type, LuaType::Signature(_)) {
builder.push(name_token.syntax(), SemanticTokenTypeKind::Function);
return Some(());
}
}
LuaSemanticDeclId::LuaDecl(decl_id) => {
let decl = semantic_model
.get_db()
.get_decl_index()
.get_decl(&decl_id)?;
let decl_type = semantic_model.get_type(decl_id.into());
let base_type = if decl.is_param() {
SemanticTokenTypeKind::Parameter
} else {
SemanticTokenTypeKind::Variable
};
if is_require_decl(semantic_model, decl) {
builder.push_with_modifier(
name_token.syntax(),
SemanticTokenTypeKind::Class,
SemanticTokenModifierKind::READONLY,
);
return Some(());
}
let (token_type, mut modifier) = match decl_type {
LuaType::Def(_) => (SemanticTokenTypeKind::Class, None),
LuaType::Ref(ref_id) => {
if check_ref_is_require_def(semantic_model, decl, &ref_id).unwrap_or(false) {
(
SemanticTokenTypeKind::Class,
Some(SemanticTokenModifierKind::READONLY),
)
} else {
(base_type, None)
}
}
LuaType::Signature(signature) => {
let is_meta = semantic_model
.get_db()
.get_module_index()
.is_meta_file(&signature.get_file_id());
(
SemanticTokenTypeKind::Function,
is_meta.then_some(SemanticTokenModifierKind::DEFAULT_LIBRARY),
)
}
LuaType::DocFunction(_) => (SemanticTokenTypeKind::Function, None),
LuaType::Union(union) => {
if union.into_vec().iter().any(|typ| typ.is_function()) {
(SemanticTokenTypeKind::Function, None)
} else {
(base_type, None)
}
}
_ => {
let token_type = match &decl.extra {
LuaDeclExtra::Param {
idx, signature_id, ..
} => {
let signature = semantic_model
.get_db()
.get_signature_index()
.get(signature_id)?;
if let Some(param_info) = signature.get_param_info_by_id(*idx) {
if param_info.type_ref.is_function() {
SemanticTokenTypeKind::Function
} else {
SemanticTokenTypeKind::Parameter
}
} else {
SemanticTokenTypeKind::Parameter
}
}
_ => base_type,
};
(token_type, None)
}
};
if modifier.is_none() {
let file_id = semantic_model.get_file_id();
let ref_decl = semantic_model
.get_db()
.get_reference_index()
.get_decl_references(&file_id, &decl_id);
if let Some(ref_decl) = ref_decl
&& !ref_decl.mutable
{
modifier = Some(SemanticTokenModifierKind::READONLY);
}
}
let mut modifiers = SemanticTokenModifierKind::empty();
if decl.is_global() {
modifiers |= SemanticTokenModifierKind::STATIC;
}
if node.parent().is_some_and(|p| {
matches!(p.kind(),
k if k == LuaSyntaxKind::LocalName.into() ||
k == LuaSyntaxKind::ParamName.into() ||
k == LuaSyntaxKind::LocalFuncStat.into()
)
}) {
modifiers |= SemanticTokenModifierKind::DECLARATION;
}
if let Some(modifier) = modifier {
modifiers |= modifier;
}
builder.push_with_modifier(name_token.syntax(), token_type, modifiers);
return Some(());
}
_ => {}
}
builder.push(
name_token.syntax(),
default_identifier_token_type(name_text),
);
Some(())
}
fn default_identifier_token_type(name_text: &str) -> SemanticTokenTypeKind {
if name_text.chars().next().is_some_and(|c| c.is_uppercase()) {
SemanticTokenTypeKind::Class
} else {
SemanticTokenTypeKind::Variable
}
}
fn render_doc_at(builder: &mut SemanticBuilder, token: &LuaSyntaxToken) {
let text = token.text();
let mut start = 0;
let mut len = 0;
for (i, c) in text.char_indices() {
if matches!(c, '@' | '|') {
start = i;
if c == '|' && text[i + c.len_utf8()..].starts_with(['+', '>']) {
len = 2;
} else {
len = 1;
}
break;
}
}
builder.push_at_range(
TextRange::at(token.text_range().start(), TextSize::new(start as u32)),
SemanticTokenTypeKind::Comment,
None,
);
builder.push_at_range(
TextRange::at(
token.text_range().start() + TextSize::new(start as u32),
TextSize::new(len as u32),
),
SemanticTokenTypeKind::Keyword,
Some(SemanticTokenModifierKind::DOCUMENTATION),
);
}
fn render_desc_ranges(builder: &mut SemanticBuilder, items: Vec<DescItem>, desc_range: TextRange) {
let mut pos = desc_range.start();
for item in items {
if item.range.start() > pos {
let detail_range = TextRange::new(pos, item.range.start());
builder.push_at_range(detail_range, SemanticTokenTypeKind::Comment, None);
}
match item.kind {
DescItemKind::Code | DescItemKind::CodeBlock | DescItemKind::Ref => {
builder.push_at_range(
item.range,
SemanticTokenTypeKind::Variable,
Some(SemanticTokenModifierKind::DOCUMENTATION),
);
pos = item.range.end();
}
DescItemKind::Link | DescItemKind::JavadocLink => {
builder.push_at_range(
item.range,
SemanticTokenTypeKind::String,
Some(SemanticTokenModifierKind::DOCUMENTATION),
);
pos = item.range.end();
}
DescItemKind::Markup | DescItemKind::Arg => {
builder.push_at_range(
item.range,
SemanticTokenTypeKind::Operator,
Some(SemanticTokenModifierKind::DOCUMENTATION),
);
pos = item.range.end();
}
DescItemKind::CodeBlockHl(highlight_kind) => {
let token_type = match highlight_kind {
CodeBlockHighlightKind::Keyword => SemanticTokenTypeKind::Keyword,
CodeBlockHighlightKind::String => SemanticTokenTypeKind::String,
CodeBlockHighlightKind::Number => SemanticTokenTypeKind::Number,
CodeBlockHighlightKind::Comment => SemanticTokenTypeKind::Comment,
CodeBlockHighlightKind::Function => SemanticTokenTypeKind::Function,
CodeBlockHighlightKind::Class => SemanticTokenTypeKind::Class,
CodeBlockHighlightKind::Enum => SemanticTokenTypeKind::Enum,
CodeBlockHighlightKind::Variable => SemanticTokenTypeKind::Variable,
CodeBlockHighlightKind::Property => SemanticTokenTypeKind::Property,
CodeBlockHighlightKind::Decorator => SemanticTokenTypeKind::Decorator,
CodeBlockHighlightKind::Operators => SemanticTokenTypeKind::Operator,
_ => continue, };
builder.push_at_range(item.range, token_type, None);
pos = item.range.end();
}
_ => {}
}
}
if pos < desc_range.end() {
let detail_range = TextRange::new(pos, desc_range.end());
builder.push_at_range(detail_range, SemanticTokenTypeKind::Comment, None);
}
}
fn check_ref_is_require_def(
semantic_model: &SemanticModel,
decl: &LuaDecl,
ref_id: &LuaTypeDeclId,
) -> Option<bool> {
let module_info = parse_require_module_info(semantic_model, decl)?;
match &module_info.export_type {
Some(ty) => match ty {
LuaType::Def(id) => Some(id == ref_id),
_ => Some(false),
},
None => None,
}
}
fn is_require_decl(semantic_model: &SemanticModel, decl: &LuaDecl) -> bool {
parse_require_module_info(semantic_model, decl).is_some()
}
fn render_type_parameter_list(
builder: &mut SemanticBuilder,
type_parameter_list: &LuaDocGenericDeclList,
) {
for type_decl in type_parameter_list.get_generic_decl() {
render_type_parameter(builder, &type_decl);
}
}
fn render_type_parameter(builder: &mut SemanticBuilder, type_decl: &LuaDocGenericDecl) {
if let Some(name) = type_decl.get_name_token() {
builder.push_with_modifier(
name.syntax(),
SemanticTokenTypeKind::Type,
SemanticTokenModifierKind::DECLARATION,
);
}
}