#include "slang-language-server-semantic-tokens.h" #include "../core/slang-char-util.h" #include "slang-ast-iterator.h" #include "slang-ast-support-types.h" #include "slang-visitor.h" #include namespace Slang { const char* kSemanticTokenTypes[] = { "type", "enumMember", "variable", "parameter", "function", "property", "namespace", "keyword", "macro", "string"}; static const int kInitTokenLegnth = 6; static_assert( SLANG_COUNT_OF(kSemanticTokenTypes) == (int)SemanticTokenType::NormalText, "kSemanticTokenTypes must match SemanticTokenType"); SemanticToken _createSemanticToken(SourceManager* manager, SourceLoc loc, Name* name) { SemanticToken token; auto humaneLoc = manager->getHumaneLoc(loc, SourceLocType::Actual); token.line = (int)(humaneLoc.line); token.col = (int)(humaneLoc.column); token.length = name ? (int)(name->text.getLength()) : 0; token.type = SemanticTokenType::NormalText; return token; } // We don't want to semantic highlight a synthetic name, like $init, () etc, // because they aren't actually appearing in the source code. bool isHighlightableName(Name* name) { if (!name) return false; for (auto ch : name->text) { if (!CharUtil::isAlphaOrDigit(ch) && ch != '_') return false; } return true; } List getSemanticTokens( Linkage* linkage, Module* module, UnownedStringSlice fileName, DocumentVersion* doc) { auto manager = linkage->getSourceManager(); auto cbufferName = linkage->getNamePool()->getName(toSlice("ConstantBuffer")); List result; auto maybeInsertToken = [&](const SemanticToken& token) { if (token.line > 0 && token.col > 0 && token.length > 0 && token.type != SemanticTokenType::NormalText) result.add(token); }; auto handleDeclRef = [&](DeclRef declRef, Expr* originalExpr, Name* name, SourceLoc loc) { if (!declRef) return; auto decl = declRef.getDecl(); if (auto genDecl = as(decl)) decl = genDecl->inner; if (!decl) return; if (!name) name = declRef.getDecl()->getName(); if (!isHighlightableName(name)) return; // Don't look at the expr if it is defined in a different file. if (!manager->getHumaneLoc(loc, SourceLocType::Actual) .pathInfo.foundPath.getUnownedSlice() .endsWithCaseInsensitive(fileName)) return; if (decl->hasModifier()) return; SemanticToken token = _createSemanticToken(manager, loc, name); auto target = decl; if (as(target)) { if (target->hasModifier()) return; token.type = SemanticTokenType::Type; if (name == cbufferName) { token.length = doc->getTokenLength(token.line, token.col); } } else if (as(target)) { token.type = SemanticTokenType::Type; token.length = doc->getTokenLength(token.line, token.col); } else if (as(target)) { token.type = SemanticTokenType::Type; } else if (as(target)) { token.type = SemanticTokenType::Property; } else if (as(target)) { token.type = SemanticTokenType::Parameter; } else if (as(target)) { if (as(originalExpr) || as(originalExpr)) { return; } token.type = SemanticTokenType::Variable; } else if (as(target)) { token.type = SemanticTokenType::Function; } else if (as(target)) { token.type = SemanticTokenType::EnumMember; } else if (as(target)) { token.type = SemanticTokenType::Namespace; } if (as(target)) { if (target->hasModifier()) return; } maybeInsertToken(token); }; iterateASTWithLanguageServerFilter( fileName, manager, module->getModuleDecl(), [&](SyntaxNode* node) { if (auto decl = as(node)) { for (auto modifier : decl->modifiers) { if (as(modifier)) return; if (as(modifier)) return; } } if (auto declRefExpr = as(node)) { handleDeclRef( declRefExpr->declRef, declRefExpr->originalExpr, declRefExpr->name, declRefExpr->loc); } else if (auto overloadedExpr = as(node)) { if (overloadedExpr->lookupResult2.items.getCount()) { handleDeclRef( overloadedExpr->lookupResult2.items[0].declRef, overloadedExpr->originalExpr, overloadedExpr->name, overloadedExpr->loc); } } else if (auto accessorDecl = as(node)) { SemanticToken token = _createSemanticToken(manager, accessorDecl->loc, accessorDecl->getName()); token.type = SemanticTokenType::Keyword; maybeInsertToken(token); } else if (auto typeDecl = as(node)) { if (typeDecl->getName()) { SemanticToken token = _createSemanticToken(manager, typeDecl->getNameLoc(), typeDecl->getName()); token.type = SemanticTokenType::Type; maybeInsertToken(token); } } else if (auto aggTypeDeclBase = as(node)) { if (!aggTypeDeclBase->getName()) return; SemanticToken token = _createSemanticToken( manager, aggTypeDeclBase->getNameLoc(), aggTypeDeclBase->getName()); token.type = SemanticTokenType::Type; maybeInsertToken(token); } else if (auto enumCase = as(node)) { if (enumCase->getName()) { SemanticToken token = _createSemanticToken(manager, enumCase->getNameLoc(), enumCase->getName()); token.type = SemanticTokenType::EnumMember; maybeInsertToken(token); } } else if (auto propertyDecl = as(node)) { if (propertyDecl->getName()) { SemanticToken token = _createSemanticToken( manager, propertyDecl->getNameLoc(), propertyDecl->getName()); token.type = SemanticTokenType::Property; maybeInsertToken(token); } } else if (auto funcDecl = as(node)) { if (isHighlightableName(funcDecl->getName())) { SemanticToken token = _createSemanticToken(manager, funcDecl->getNameLoc(), funcDecl->getName()); token.type = SemanticTokenType::Function; maybeInsertToken(token); } } else if (auto ctorDecl = as(node)) { if (ctorDecl->getName()) { SemanticToken token = _createSemanticToken(manager, ctorDecl->getNameLoc(), ctorDecl->getName()); token.type = SemanticTokenType::Function; token.length = kInitTokenLegnth; maybeInsertToken(token); } } else if (auto paramDecl = as(node)) { if (paramDecl->getName()) { SemanticToken token = _createSemanticToken( manager, paramDecl->getNameLoc(), paramDecl->getName()); token.type = SemanticTokenType::Parameter; maybeInsertToken(token); } } else if (auto varDecl = as(node)) { if (varDecl->getName()) { SemanticToken token = _createSemanticToken(manager, varDecl->getNameLoc(), varDecl->getName()); token.type = SemanticTokenType::Variable; maybeInsertToken(token); } } else if (auto attr = as(node)) { if (attr->getKeywordName()) { SemanticToken token = _createSemanticToken(manager, attr->originalIdentifierToken.loc, nullptr); token.length = (int)attr->originalIdentifierToken.getContentLength(); token.type = SemanticTokenType::Type; maybeInsertToken(token); // Insert capability names as enum cases. if (as(attr)) { for (auto arg : attr->args) { if (auto varExpr = as(arg)) { if (varExpr->name) { SemanticToken capToken = _createSemanticToken(manager, varExpr->loc, nullptr); capToken.length = (int)varExpr->name->text.getLength(); capToken.type = SemanticTokenType::EnumMember; maybeInsertToken(capToken); } } } } } } else if (auto targetCase = as(node)) { SemanticToken token = _createSemanticToken( manager, targetCase->capabilityToken.loc, targetCase->capabilityToken.getName()); token.type = SemanticTokenType::EnumMember; maybeInsertToken(token); } else if (auto spirvAsmExpr = as(node)) { // Highlight opcodes and enums. for (auto inst : spirvAsmExpr->insts) { // OpCode { SemanticToken token = _createSemanticToken( manager, inst.opcode.token.loc, inst.opcode.token.getName()); token.type = SemanticTokenType::Function; maybeInsertToken(token); } // Other operands for (auto operand : inst.operands) { SemanticTokenType operandTokenType = SemanticTokenType::NormalText; switch (operand.flavor) { case SPIRVAsmOperand::Flavor::NamedValue: case SPIRVAsmOperand::Flavor::BuiltinVar: operandTokenType = SemanticTokenType::EnumMember; break; case SPIRVAsmOperand::Flavor::ResultMarker: case SPIRVAsmOperand::Flavor::TruncateMarker: case SPIRVAsmOperand::Flavor::GLSL450Set: operandTokenType = SemanticTokenType::Macro; break; case SPIRVAsmOperand::Flavor::Id: operandTokenType = SemanticTokenType::String; break; } if (operandTokenType != SemanticTokenType::NormalText) { SemanticToken token = _createSemanticToken( manager, operand.token.loc, operand.token.getName()); token.type = operandTokenType; maybeInsertToken(token); } } } } }); // Insert macro tokens. auto& preprocessorInfo = linkage->contentAssistInfo.preprocessorInfo; for (auto& invocation : preprocessorInfo.macroInvocations) { if (!invocation.name) continue; // Don't look at the expr if it is defined in a different file. auto humaneLoc = manager->getHumaneLoc(invocation.loc, SourceLocType::Actual); if (!humaneLoc.pathInfo.foundPath.getUnownedSlice().endsWithCaseInsensitive(fileName)) continue; SemanticToken token; token.line = (int)(humaneLoc.line); token.col = (int)(humaneLoc.column); token.length = (int)(invocation.name->text.getLength()); token.type = SemanticTokenType::Macro; maybeInsertToken(token); } return result; } List getEncodedTokens(List& tokens) { List result; if (tokens.getCount() == 0) return result; std::sort(tokens.begin(), tokens.end()); // Encode the first token as is. result.add((uint32_t)tokens[0].line); result.add((uint32_t)tokens[0].col); result.add((uint32_t)tokens[0].length); result.add((uint32_t)tokens[0].type); result.add(0); // Encode the rest tokens as deltas. uint32_t prevLine = (uint32_t)tokens[0].line; uint32_t prevCol = (uint32_t)tokens[0].col; for (Index i = 1; i < tokens.getCount(); i++) { uint32_t thisLine = (uint32_t)tokens[i].line; uint32_t thisCol = (uint32_t)tokens[i].col; if (thisLine == prevLine && thisCol == prevCol) continue; uint32_t deltaLine = thisLine - prevLine; uint32_t deltaCol = deltaLine == 0 ? thisCol - prevCol : thisCol; result.add(deltaLine); result.add(deltaCol); result.add((uint32_t)tokens[i].length); result.add((uint32_t)tokens[i].type); result.add(0); prevLine = thisLine; prevCol = thisCol; } return result; } } // namespace Slang