summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2022-06-22 19:58:34 -0700
committerGitHub <noreply@github.com>2022-06-22 19:58:34 -0700
commit07a380d72a13899a84cbdc35692be7a3d9246dcb (patch)
tree68e77f2e9682b3b7c3debd745604a494439e5b25 /source
parente5a75563a1ba2e378353af8b937b8b7bb0fe2c2b (diff)
More Language Server Improvements. (#2289)
Diffstat (limited to 'source')
-rw-r--r--source/compiler-core/slang-json-rpc-connection.h2
-rw-r--r--source/compiler-core/slang-lexer.cpp5
-rw-r--r--source/compiler-core/slang-token-defs.h2
-rw-r--r--source/slang/slang-ast-decl.h3
-rw-r--r--source/slang/slang-ast-iterator.h424
-rw-r--r--source/slang/slang-ast-support-types.h15
-rw-r--r--source/slang/slang-check-decl.cpp53
-rw-r--r--source/slang/slang-check-expr.cpp65
-rw-r--r--source/slang/slang-check-impl.h4
-rw-r--r--source/slang/slang-check-modifier.cpp47
-rwxr-xr-xsource/slang/slang-compiler.h21
-rw-r--r--source/slang/slang-content-assist-info.h112
-rw-r--r--source/slang/slang-language-server-ast-lookup.cpp35
-rw-r--r--source/slang/slang-language-server-completion.cpp599
-rw-r--r--source/slang/slang-language-server-completion.h17
-rw-r--r--source/slang/slang-language-server-semantic-tokens.cpp438
-rw-r--r--source/slang/slang-language-server-semantic-tokens.h2
-rw-r--r--source/slang/slang-language-server.cpp261
-rw-r--r--source/slang/slang-language-server.h29
-rw-r--r--source/slang/slang-lookup.cpp73
-rw-r--r--source/slang/slang-mangle.cpp37
-rw-r--r--source/slang/slang-mangle.h1
-rw-r--r--source/slang/slang-parser.cpp41
-rw-r--r--source/slang/slang-preprocessor.cpp66
-rw-r--r--source/slang/slang-preprocessor.h4
-rw-r--r--source/slang/slang-workspace-version.cpp122
-rw-r--r--source/slang/slang-workspace-version.h39
-rw-r--r--source/slang/slang.cpp25
28 files changed, 1555 insertions, 987 deletions
diff --git a/source/compiler-core/slang-json-rpc-connection.h b/source/compiler-core/slang-json-rpc-connection.h
index 07b7cc347..18749229c 100644
--- a/source/compiler-core/slang-json-rpc-connection.h
+++ b/source/compiler-core/slang-json-rpc-connection.h
@@ -148,6 +148,8 @@ public:
/// it will become invalid in most usage scenarios.
PersistentJSONValue getPersistentValue(const JSONValue& value) { return PersistentJSONValue(value, &m_container, SourceLoc()); }
+ HTTPPacketConnection* getUnderlyingConnection() { return m_connection.Ptr(); }
+
/// Dtor
~JSONRPCConnection() { disconnect(); }
diff --git a/source/compiler-core/slang-lexer.cpp b/source/compiler-core/slang-lexer.cpp
index d149a88bc..fd0255575 100644
--- a/source/compiler-core/slang-lexer.cpp
+++ b/source/compiler-core/slang-lexer.cpp
@@ -1167,6 +1167,9 @@ namespace Slang
switch(_peek(lexer))
{
case '#': _advance(lexer); return TokenType::PoundPound;
+
+ case '?': _advance(lexer); return TokenType::CompletionRequest;
+
default:
return TokenType::Pound;
}
@@ -1337,7 +1340,7 @@ namespace Slang
}
}
- if (tokenType == TokenType::Identifier)
+ if (tokenType == TokenType::Identifier || tokenType == TokenType::CompletionRequest)
{
token.setName(m_namePool->getName(token.getContent()));
}
diff --git a/source/compiler-core/slang-token-defs.h b/source/compiler-core/slang-token-defs.h
index fdbe81e17..45b4912e7 100644
--- a/source/compiler-core/slang-token-defs.h
+++ b/source/compiler-core/slang-token-defs.h
@@ -90,6 +90,8 @@ PUNCTUATION(PoundPound, "##")
PUNCTUATION(Scope, "::")
+PUNCTUATION(CompletionRequest, "#?")
+
#undef PUNCTUATION
// Un-define the `TOKEN` macro so that client doesn't have to
diff --git a/source/slang/slang-ast-decl.h b/source/slang/slang-ast-decl.h
index 65eaea4f1..9d2c99f14 100644
--- a/source/slang/slang-ast-decl.h
+++ b/source/slang/slang-ast-decl.h
@@ -406,6 +406,9 @@ class ImportDecl : public Decl
// The module that actually got imported
ModuleDecl* importedModuleDecl = nullptr;
+ SourceLoc startLoc;
+ SourceLoc endLoc;
+
SLANG_UNREFLECTED
// The scope that we want to import into
Scope* scope = nullptr;
diff --git a/source/slang/slang-ast-iterator.h b/source/slang/slang-ast-iterator.h
new file mode 100644
index 000000000..417f93d0d
--- /dev/null
+++ b/source/slang/slang-ast-iterator.h
@@ -0,0 +1,424 @@
+#pragma once
+#include "slang-syntax.h"
+
+namespace Slang
+{
+template <typename Callback>
+struct ASTIterator
+{
+ const Callback& callback;
+ UnownedStringSlice fileName;
+ SourceManager* sourceManager;
+ ASTIterator(const Callback& func, SourceManager* manager, UnownedStringSlice sourceFileName)
+ : callback(func)
+ , fileName(sourceFileName)
+ , sourceManager(manager)
+ {}
+
+ void visitDecl(DeclBase* decl);
+ void visitExpr(Expr* expr);
+ void visitStmt(Stmt* stmt);
+
+ void maybeDispatchCallback(SyntaxNode* node)
+ {
+ if (node)
+ {
+ callback(node);
+ }
+ }
+
+ struct ASTIteratorExprVisitor : public ExprVisitor<ASTIteratorExprVisitor>
+ {
+ public:
+ ASTIterator* iterator;
+ ASTIteratorExprVisitor(ASTIterator* iter)
+ : iterator(iter)
+ {}
+ void dispatchIfNotNull(Expr* expr)
+ {
+ if (!expr)
+ return;
+ expr->accept(this, nullptr);
+ }
+ bool visitExpr(Expr*) { return false; }
+ void visitBoolLiteralExpr(BoolLiteralExpr* expr) { iterator->maybeDispatchCallback(expr); }
+ void visitNullPtrLiteralExpr(NullPtrLiteralExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ }
+ void visitIntegerLiteralExpr(IntegerLiteralExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ }
+ void visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ }
+ void visitStringLiteralExpr(StringLiteralExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ }
+ void visitIncompleteExpr(IncompleteExpr* expr) { iterator->maybeDispatchCallback(expr); }
+ void visitIndexExpr(IndexExpr* subscriptExpr)
+ {
+ iterator->maybeDispatchCallback(subscriptExpr);
+ dispatchIfNotNull(subscriptExpr->baseExpression);
+ dispatchIfNotNull(subscriptExpr->indexExpression);
+ }
+
+ void visitParenExpr(ParenExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->base);
+ }
+
+ void visitAssignExpr(AssignExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->left);
+ dispatchIfNotNull(expr->right);
+ }
+
+ void visitGenericAppExpr(GenericAppExpr* genericAppExpr)
+ {
+ iterator->maybeDispatchCallback(genericAppExpr);
+
+ dispatchIfNotNull(genericAppExpr->functionExpr);
+ for (auto arg : genericAppExpr->arguments)
+ dispatchIfNotNull(arg);
+ }
+
+ void visitSharedTypeExpr(SharedTypeExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->base.exp);
+ }
+
+ void visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ }
+
+ void visitInvokeExpr(InvokeExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+
+ dispatchIfNotNull(expr->functionExpr);
+ dispatchIfNotNull(expr->originalFunctionExpr);
+
+ for (auto arg : expr->arguments)
+ dispatchIfNotNull(arg);
+ }
+
+ void visitVarExpr(VarExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->originalExpr);
+ }
+
+ void visitTryExpr(TryExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->base);
+ }
+
+ void visitTypeCastExpr(TypeCastExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+
+ dispatchIfNotNull(expr->functionExpr);
+ for (auto arg : expr->arguments)
+ dispatchIfNotNull(arg);
+ }
+
+ void visitDerefExpr(DerefExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->base);
+ }
+ void visitMatrixSwizzleExpr(MatrixSwizzleExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->base);
+ }
+ void visitSwizzleExpr(SwizzleExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->base);
+ }
+ void visitOverloadedExpr(OverloadedExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->base);
+ }
+ void visitOverloadedExpr2(OverloadedExpr2* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->base);
+ for (auto candidate : expr->candidiateExprs)
+ {
+ dispatchIfNotNull(candidate);
+ }
+ }
+ void visitAggTypeCtorExpr(AggTypeCtorExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->base.exp);
+ for (auto arg : expr->arguments)
+ {
+ dispatchIfNotNull(arg);
+ }
+ }
+ void visitCastToSuperTypeExpr(CastToSuperTypeExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->valueArg);
+ }
+ void visitModifierCastExpr(ModifierCastExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->valueArg);
+ }
+ void visitLetExpr(LetExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ iterator->visitDecl(expr->decl);
+ dispatchIfNotNull(expr->body);
+ }
+ void visitExtractExistentialValueExpr(ExtractExistentialValueExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ }
+
+ void visitDeclRefExpr(DeclRefExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->originalExpr);
+ }
+
+ void visitStaticMemberExpr(StaticMemberExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->baseExpression);
+ }
+
+ void visitMemberExpr(MemberExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->baseExpression);
+ }
+
+ void visitInitializerListExpr(InitializerListExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ for (auto arg : expr->args)
+ {
+ dispatchIfNotNull(arg);
+ }
+ }
+
+ void visitThisExpr(ThisExpr* expr) { iterator->maybeDispatchCallback(expr); }
+ void visitThisTypeExpr(ThisTypeExpr* expr) { iterator->maybeDispatchCallback(expr); }
+ void visitAndTypeExpr(AndTypeExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->left.exp);
+ dispatchIfNotNull(expr->right.exp);
+ }
+ void visitModifiedTypeExpr(ModifiedTypeExpr* expr)
+ {
+ iterator->maybeDispatchCallback(expr);
+ dispatchIfNotNull(expr->base.exp);
+ }
+ };
+
+ struct ASTIteratorStmtVisitor : public StmtVisitor<ASTIteratorStmtVisitor>
+ {
+ ASTIterator* iterator;
+ ASTIteratorStmtVisitor(ASTIterator* iter)
+ : iterator(iter)
+ {}
+
+ void dispatchIfNotNull(Stmt* stmt)
+ {
+ if (!stmt)
+ return;
+ stmt->accept(this, nullptr);
+ }
+
+ void visitDeclStmt(DeclStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ iterator->visitDecl(stmt->decl);
+ }
+
+ void visitBlockStmt(BlockStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ dispatchIfNotNull(stmt->body);
+ }
+
+ void visitSeqStmt(SeqStmt* seqStmt)
+ {
+ iterator->maybeDispatchCallback(seqStmt);
+ for (auto stmt : seqStmt->stmts)
+ dispatchIfNotNull(stmt);
+ }
+
+ void visitBreakStmt(BreakStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
+
+ void visitContinueStmt(ContinueStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
+
+ void visitDoWhileStmt(DoWhileStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ iterator->visitExpr(stmt->predicate);
+ dispatchIfNotNull(stmt->statement);
+ }
+
+ void visitForStmt(ForStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ dispatchIfNotNull(stmt->initialStatement);
+ iterator->visitExpr(stmt->predicateExpression);
+ iterator->visitExpr(stmt->sideEffectExpression);
+ dispatchIfNotNull(stmt->statement);
+ }
+
+ void visitCompileTimeForStmt(CompileTimeForStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ }
+
+ void visitSwitchStmt(SwitchStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ iterator->visitExpr(stmt->condition);
+ dispatchIfNotNull(stmt->body);
+ }
+
+ void visitCaseStmt(CaseStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ iterator->visitExpr(stmt->expr);
+ }
+
+ void visitDefaultStmt(DefaultStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
+
+ void visitIfStmt(IfStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ iterator->visitExpr(stmt->predicate);
+ dispatchIfNotNull(stmt->positiveStatement);
+ dispatchIfNotNull(stmt->negativeStatement);
+ }
+
+ void visitUnparsedStmt(UnparsedStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
+
+ void visitEmptyStmt(EmptyStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
+
+ void visitDiscardStmt(DiscardStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
+
+ void visitReturnStmt(ReturnStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ iterator->visitExpr(stmt->expression);
+ }
+
+ void visitWhileStmt(WhileStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ iterator->visitExpr(stmt->predicate);
+ dispatchIfNotNull(stmt->statement);
+ }
+
+ void visitGpuForeachStmt(GpuForeachStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
+
+ void visitExpressionStmt(ExpressionStmt* stmt)
+ {
+ iterator->maybeDispatchCallback(stmt);
+ iterator->visitExpr(stmt->expression);
+ }
+ };
+};
+
+template <typename CallbackFunc>
+void ASTIterator<CallbackFunc>::visitDecl(DeclBase* decl)
+{
+ // Don't look at the decl if it is defined in a different file.
+ if (!as<ModuleDecl>(decl) && !sourceManager->getHumaneLoc(decl->loc, SourceLocType::Actual)
+ .pathInfo.foundPath.getUnownedSlice()
+ .endsWithCaseInsensitive(fileName))
+ return;
+
+ maybeDispatchCallback(decl);
+ if (auto funcDecl = as<FunctionDeclBase>(decl))
+ {
+ visitStmt(funcDecl->body);
+ visitExpr(funcDecl->returnType.exp);
+ }
+ else if (auto propertyDecl = as<PropertyDecl>(decl))
+ {
+ visitExpr(propertyDecl->type.exp);
+ }
+ else if (auto varDecl = as<VarDeclBase>(decl))
+ {
+ visitExpr(varDecl->type.exp);
+ visitExpr(varDecl->initExpr);
+ }
+ else if (auto genericDecl = as<GenericDecl>(decl))
+ {
+ visitDecl(genericDecl->inner);
+ }
+ else if (auto typeConstraint = as<TypeConstraintDecl>(decl))
+ {
+ visitExpr(typeConstraint->getSup().exp);
+ }
+ else if (auto typedefDecl = as<TypeDefDecl>(decl))
+ {
+ visitExpr(typedefDecl->type.exp);
+ }
+ else if (auto extDecl = as<ExtensionDecl>(decl))
+ {
+ visitExpr(extDecl->targetType.exp);
+ }
+ if (auto container = as<ContainerDecl>(decl))
+ {
+ for (auto member : container->members)
+ {
+ visitDecl(member);
+ }
+ }
+}
+template <typename CallbackFunc>
+void ASTIterator<CallbackFunc>::visitExpr(Expr* expr)
+{
+ ASTIteratorExprVisitor visitor(this);
+ visitor.dispatchIfNotNull(expr);
+}
+template <typename CallbackFunc>
+void ASTIterator<CallbackFunc>::visitStmt(Stmt* stmt)
+{
+ ASTIteratorStmtVisitor visitor(this);
+ visitor.dispatchIfNotNull(stmt);
+}
+
+template <typename Func>
+void iterateAST(
+ UnownedStringSlice fileName, SourceManager* manager, SyntaxNode* node, const Func& f)
+{
+ ASTIterator<Func> iter(f, manager, fileName);
+ if (auto decl = as<Decl>(node))
+ {
+ iter.visitDecl(decl);
+ }
+ else if (auto expr = as<Expr>(node))
+ {
+ iter.visitExpr(expr);
+ }
+ else if (auto stmt = as<Stmt>(node))
+ {
+ iter.visitStmt(stmt);
+ }
+}
+} // namespace Slang
diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h
index e3dea7df1..c1e6a0132 100644
--- a/source/slang/slang-ast-support-types.h
+++ b/source/slang/slang-ast-support-types.h
@@ -1120,6 +1120,7 @@ namespace Slang
{
None = 0,
IgnoreBaseInterfaces = 1 << 0,
+ Completion = 1 << 1, ///< Lookup all applicable decls for code completion suggestions
};
class SerialRefObject;
@@ -1323,26 +1324,26 @@ namespace Slang
{
return items.getCount() > 1 ? items[0].declRef.getName() : item.declRef.getName();
}
- LookupResultItem* begin()
+ LookupResultItem* begin() const
{
if (isValid())
{
if (isOverloaded())
- return items.begin();
+ return const_cast<LookupResultItem*>(items.begin());
else
- return &item;
+ return const_cast<LookupResultItem*>(&item);
}
else
return nullptr;
}
- LookupResultItem* end()
+ LookupResultItem* end() const
{
if (isValid())
{
if (isOverloaded())
- return items.end();
+ return const_cast<LookupResultItem*>(items.end());
else
- return &item + 1;
+ return const_cast<LookupResultItem*>(&item + 1);
}
else
return nullptr;
@@ -1359,6 +1360,8 @@ namespace Slang
LookupMask mask = LookupMask::Default;
LookupOptions options = LookupOptions::None;
+
+ bool isCompletionRequest() const { return ((int)options & (int)LookupOptions::Completion) != 0; }
};
struct WitnessTable;
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index bb762c1c6..7871d35dd 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -698,6 +698,17 @@ namespace Slang
return;
}
+ // If we should skip the checking, return now.
+ // A common case to skip checking is for the function bodies when we are in
+ // the language server. In that case we only care about the function bodies in a
+ // specific module and can skip checking the reference modules until they
+ // are being opened/edited later.
+ if (shouldSkipChecking(decl, state))
+ {
+ decl->setCheckState(state);
+ return;
+ }
+
// Set the flag that indicates we are checking this declaration,
// so that the cycle check above will catch us before we go
// into any infinite loops.
@@ -835,6 +846,48 @@ namespace Slang
return true;
}
+ bool SemanticsVisitor::shouldSkipChecking(Decl* decl, DeclCheckState state)
+ {
+ if (state != DeclCheckState::Checked)
+ return false;
+ // If we are in language server, we should skip checking all the function bodies
+ // except for the module or function that the user cared about.
+ // This optimization helps reduce the response time.
+ if (!getLinkage()->isInLanguageServer())
+ {
+ return false;
+ }
+ if (auto funcDecl = as<FunctionDeclBase>(decl))
+ {
+ auto& assistInfo = getLinkage()->contentAssistInfo;
+ // If this func is not defined in the primary module, skip checking its body.
+ auto moduleDecl = getModuleDecl(decl);
+ if (moduleDecl && moduleDecl->getName() != assistInfo.primaryModuleName)
+ return true;
+ if (funcDecl->body)
+ {
+ auto humaneLoc = getLinkage()->getSourceManager()->getHumaneLoc(
+ decl->loc, SourceLocType::Actual);
+ if (humaneLoc.pathInfo.foundPath != assistInfo.primaryModulePath)
+ {
+ return true;
+ }
+ if (assistInfo.checkingMode == ContentAssistCheckingMode::Completion)
+ {
+ // For completion requests, we skip all funtion bodies except for the one
+ // that the current cursor is in.
+ auto closingLoc = getLinkage()->getSourceManager()->getHumaneLoc(
+ funcDecl->closingSourceLoc, SourceLocType::Actual);
+
+ if (assistInfo.cursorLine < humaneLoc.line ||
+ assistInfo.cursorLine > closingLoc.line)
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
void SemanticsVisitor::_validateCircularVarDefinition(VarDeclBase* varDecl)
{
// The easiest way to test if the declaration is circular is to
diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp
index 13552cc61..3b308c46a 100644
--- a/source/slang/slang-check-expr.cpp
+++ b/source/slang/slang-check-expr.cpp
@@ -514,6 +514,19 @@ namespace Slang
return ConstructDeclRefExpr(item.declRef, bb, loc, originalExpr);
}
+ void SemanticsVisitor::suggestCompletionItems(
+ CompletionSuggestions::ScopeKind scopeKind, LookupResult const& lookupResult)
+ {
+ auto& suggestions = getLinkage()->contentAssistInfo.completionSuggestions;
+ suggestions.clear();
+ suggestions.scopeKind = scopeKind;
+ for (auto item : lookupResult)
+ {
+ suggestions.candidateItems.add(item);
+ }
+ }
+
+
Expr* SemanticsVisitor::createLookupResultExpr(
Name* name,
LookupResult const& lookupResult,
@@ -1472,6 +1485,15 @@ namespace Slang
auto lookupResult = lookUp(
m_astBuilder,
this, expr->name, expr->scope);
+ if (expr->name == getSession()->getCompletionRequestTokenName())
+ {
+ auto scopeKind = CompletionSuggestions::ScopeKind::Expr;
+ if (!m_parentFunc)
+ scopeKind = CompletionSuggestions::ScopeKind::Decl;
+ suggestCompletionItems(scopeKind, lookupResult);
+ return expr;
+ }
+
if (lookupResult.isValid())
{
return createLookupResultExpr(
@@ -1673,6 +1695,17 @@ namespace Slang
bool anyDuplicates = false;
int zeroIndexOffset = -1;
+ if (memberRefExpr->name == getSession()->getCompletionRequestTokenName())
+ {
+ auto& suggestions = getLinkage()->contentAssistInfo.completionSuggestions;
+ suggestions.clear();
+ suggestions.scopeKind = CompletionSuggestions::ScopeKind::Swizzle;
+ suggestions.swizzleBaseType =
+ memberRefExpr->baseExpression ? memberRefExpr->baseExpression->type : nullptr;
+ suggestions.elementCount[0] = baseElementRowCount;
+ suggestions.elementCount[1] = baseElementColCount;
+ }
+
String swizzleText = getText(memberRefExpr->name);
auto cursor = swizzleText.begin();
@@ -1831,7 +1864,16 @@ namespace Slang
bool elementUsed[4] = { false, false, false, false };
bool anyDuplicates = false;
bool anyError = false;
-
+ if (memberRefExpr->name == getSession()->getCompletionRequestTokenName())
+ {
+ auto& suggestions = getLinkage()->contentAssistInfo.completionSuggestions;
+ suggestions.clear();
+ suggestions.scopeKind = CompletionSuggestions::ScopeKind::Swizzle;
+ suggestions.swizzleBaseType =
+ memberRefExpr->baseExpression ? memberRefExpr->baseExpression->type : nullptr;
+ suggestions.elementCount[0] = baseElementCount;
+ suggestions.elementCount[1] = 0;
+ }
auto swizzleText = getText(memberRefExpr->name);
for (Index i = 0; i < swizzleText.getLength(); i++)
@@ -1952,6 +1994,10 @@ namespace Slang
return lookupMemberResultFailure(expr, baseType);
}
+ if (expr->name == getSession()->getCompletionRequestTokenName())
+ {
+ suggestCompletionItems(CompletionSuggestions::ScopeKind::Member, lookupResult);
+ }
return createLookupResultExpr(
expr->name,
lookupResult,
@@ -2007,11 +2053,11 @@ namespace Slang
// For now let's just be expedient and disallow all of that, because
// we can always add it back in later.
- // If the lookup result is overloaded, then we want to filter
+ // If the lookup result is valid, then we want to filter
// it to just those candidates that can be referenced statically,
// and ignore any that would only be allowed as instance members.
//
- if(lookupResult.isOverloaded())
+ if(lookupResult.isValid())
{
// We track both the usable items, and whether or
// not there were any non-static items that need
@@ -2019,7 +2065,7 @@ namespace Slang
//
bool anyNonStatic = false;
List<LookupResultItem> staticItems;
- for (auto item : lookupResult.items)
+ for (auto item : lookupResult)
{
// Is this item usable as a static member?
if (isUsableAsStaticMember(item))
@@ -2042,6 +2088,7 @@ namespace Slang
if (staticItems.getCount())
{
lookupResult.items = staticItems;
+ lookupResult.item = staticItems[0];
}
else
{
@@ -2057,7 +2104,10 @@ namespace Slang
// If there were no non-static items, then the `items`
// array already represents what we'd get by filtering...
}
-
+ if (expr->name == getSession()->getCompletionRequestTokenName())
+ {
+ suggestCompletionItems(CompletionSuggestions::ScopeKind::Member, lookupResult);
+ }
return createLookupResultExpr(
expr->name,
lookupResult,
@@ -2217,7 +2267,10 @@ namespace Slang
{
return lookupMemberResultFailure(expr, baseType);
}
-
+ if (expr->name == getSession()->getCompletionRequestTokenName())
+ {
+ suggestCompletionItems(CompletionSuggestions::ScopeKind::Member, lookupResult);
+ }
return createLookupResultExpr(
expr->name,
lookupResult,
diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h
index bc037b0c2..5ef853b62 100644
--- a/source/slang/slang-check-impl.h
+++ b/source/slang/slang-check-impl.h
@@ -660,6 +660,7 @@ namespace Slang
///
void _validateCircularVarDefinition(VarDeclBase* varDecl);
+ bool shouldSkipChecking(Decl* decl, DeclCheckState state);
public:
bool ValuesAreEqual(
@@ -1647,6 +1648,9 @@ namespace Slang
//
void importModuleIntoScope(Scope* scope, ModuleDecl* moduleDecl);
+
+ void suggestCompletionItems(
+ CompletionSuggestions::ScopeKind scopeKind, LookupResult const& lookupResult);
};
struct SemanticsExprVisitor
diff --git a/source/slang/slang-check-modifier.cpp b/source/slang/slang-check-modifier.cpp
index 429479a38..c6a33930b 100644
--- a/source/slang/slang-check-modifier.cpp
+++ b/source/slang/slang-check-modifier.cpp
@@ -78,6 +78,19 @@ namespace Slang
// Do nothing with modifiers for now
}
+ static bool _isDeclAllowedAsAttribute(DeclRef<Decl> declRef)
+ {
+ if (as<AttributeDecl>(declRef.getDecl()))
+ return true;
+ auto structDecl = as<StructDecl>(declRef.getDecl());
+ if (!structDecl)
+ return false;
+ auto attrUsageAttr = structDecl->findModifier<AttributeUsageAttribute>();
+ if (!attrUsageAttr)
+ return false;
+ return true;
+ }
+
AttributeDecl* SemanticsVisitor::lookUpAttributeDecl(Name* attributeName, Scope* scope)
{
if (!attributeName)
@@ -88,7 +101,29 @@ namespace Slang
{
// Look up the name and see what attributes we find.
//
- auto lookupResult = lookUp(m_astBuilder, this, attributeName, scope, LookupMask::Attribute);
+ LookupMask lookupMask = LookupMask::Attribute;
+ if (attributeName == getSession()->getCompletionRequestTokenName())
+ {
+ lookupMask =
+ LookupMask((uint32_t)LookupMask::Attribute | (uint32_t)LookupMask::type);
+ }
+
+ auto lookupResult = lookUp(m_astBuilder, this, attributeName, scope, lookupMask);
+
+ if (attributeName == getSession()->getCompletionRequestTokenName())
+ {
+ // If this is a completion request, add the lookup result to linkage.
+ auto& suggestions = getLinkage()->contentAssistInfo.completionSuggestions;
+ suggestions.clear();
+ suggestions.scopeKind = CompletionSuggestions::ScopeKind::Attribute;
+ for (auto& item : lookupResult)
+ {
+ if (_isDeclAllowedAsAttribute(item.declRef))
+ {
+ suggestions.candidateItems.add(item);
+ }
+ }
+ }
// If the result was overloaded, then that means there
// are multiple attributes matching the name, and we
@@ -709,6 +744,16 @@ namespace Slang
return checkAttribute(hlslUncheckedAttribute, syntaxNode);
}
+
+ if (auto hlslSemantic = as<HLSLSimpleSemantic>(m))
+ {
+ if (hlslSemantic->name.getName() == getSession()->getCompletionRequestTokenName())
+ {
+ getLinkage()->contentAssistInfo.completionSuggestions.scopeKind =
+ CompletionSuggestions::ScopeKind::HLSLSemantics;
+ }
+ }
+
// Default behavior is to leave things as they are,
// and assume that modifiers are mostly already checked.
//
diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h
index 280bdf688..e7114fc2c 100755
--- a/source/slang/slang-compiler.h
+++ b/source/slang/slang-compiler.h
@@ -21,7 +21,7 @@
#include "slang-preprocessor.h"
#include "slang-profile.h"
#include "slang-syntax.h"
-
+#include "slang-content-assist-info.h"
#include "slang-serialize-ir-types.h"
@@ -1671,14 +1671,6 @@ namespace Slang
/// lookup additional loaded modules.
typedef Dictionary<Name*, Module*> LoadedModuleDictionary;
- class Linkage;
- class IModuleCache
- {
- public:
- virtual RefPtr<Module> tryLoadModule(Linkage* linkage, String filePath) = 0;
- virtual void storeModule(Linkage* linkage, String filePath, RefPtr<Module> module) = 0;
- };
-
/// A context for loading and re-using code modules.
class Linkage : public RefObject, public slang::ISession
{
@@ -1754,7 +1746,7 @@ namespace Slang
slang::SessionFlags m_flag = 0;
void setFlags(slang::SessionFlags flags) { m_flag = flags; }
- bool isInLanguageServer() { return (m_flag & slang::kSessionFlag_LanguageServer) != 0; }
+ bool isInLanguageServer() { return contentAssistInfo.checkingMode != ContentAssistCheckingMode::None; }
/// Get the parent session for this linkage
Session* getSessionImpl() { return m_session; }
@@ -1799,8 +1791,6 @@ namespace Slang
TypeCheckingCache* m_typeCheckingCache = nullptr;
- void setModuleCache(IModuleCache* cache) { m_moduleCache = cache; }
-
// Modules that have been dynamically loaded via `import`
//
// This is a list of unique modules loaded, in the order they were encountered.
@@ -1822,6 +1812,8 @@ namespace Slang
// The resulting specialized IR module for each entry point request
List<RefPtr<IRModule>> compiledModules;
+ ContentAssistInfo contentAssistInfo;
+
/// File system implementation to use when loading files from disk.
///
/// If this member is `null`, a default implementation that tries
@@ -1935,8 +1927,6 @@ namespace Slang
RefPtr<Session> m_retainedSession;
- IModuleCache* m_moduleCache = nullptr;
-
/// Tracks state of modules currently being loaded.
///
/// This information is used to diagnose cases where
@@ -3017,6 +3007,8 @@ namespace Slang
/// Get the built in linkage -> handy to get the stdlibs from
Linkage* getBuiltinLinkage() const { return m_builtinLinkage; }
+ Name* getCompletionRequestTokenName() const { return m_completionTokenName; }
+
void init();
void addBuiltinSource(
@@ -3034,6 +3026,7 @@ namespace Slang
RefPtr<DownstreamCompilerSet> m_downstreamCompilerSet; ///< Information about all available downstream compilers.
RefPtr<DownstreamCompiler> m_downstreamCompilers[int(PassThroughMode::CountOf)]; ///< A downstream compiler for a pass through
DownstreamCompilerLocatorFunc m_downstreamCompilerLocators[int(PassThroughMode::CountOf)];
+ Name* m_completionTokenName = nullptr; ///< The name of a completion request token.
private:
diff --git a/source/slang/slang-content-assist-info.h b/source/slang/slang-content-assist-info.h
new file mode 100644
index 000000000..bd2c4b7d9
--- /dev/null
+++ b/source/slang/slang-content-assist-info.h
@@ -0,0 +1,112 @@
+// slang-content-assist-info.h
+
+#pragma once
+
+#include "slang-syntax.h"
+#include "../../slang.h"
+
+namespace Slang
+{
+
+struct CompletionSuggestions
+{
+ enum class ScopeKind
+ {
+ Invalid,
+ Member,
+ Swizzle,
+ Decl,
+ Stmt,
+ Expr,
+ Attribute,
+ HLSLSemantics
+ };
+ ScopeKind scopeKind = ScopeKind::Invalid;
+ List<LookupResultItem> candidateItems;
+ Type* swizzleBaseType = nullptr;
+ IntegerLiteralValue elementCount[2] = {0, 0};
+
+ void clear()
+ {
+ scopeKind = ScopeKind::Invalid;
+ candidateItems.clear();
+ elementCount[0] = 0;
+ elementCount[1] = 0;
+ swizzleBaseType = nullptr;
+ }
+};
+
+struct MacroDefinitionContentAssistInfo
+{
+ struct Param
+ {
+ Name* name;
+ bool isVariadic;
+ };
+
+ Name* name;
+ SourceLoc loc;
+ List<Param> params;
+ List<Token> tokenList;
+};
+
+struct MacroInvocationContentAssistInfo
+{
+ Name* name;
+ SourceLoc loc;
+};
+
+struct FileIncludeContentAssistInfo
+{
+ SourceLoc loc;
+ int length;
+ String path;
+};
+
+struct PreprocessorContentAssistInfo
+{
+ List<MacroDefinitionContentAssistInfo> macroDefinitions;
+ List<MacroInvocationContentAssistInfo> macroInvocations;
+ List<FileIncludeContentAssistInfo> fileIncludes;
+};
+
+enum class ContentAssistCheckingMode
+{
+ // Language server not enabled.
+ None,
+
+ // General full checking for semantic token/document symbol/goto-defintion features.
+ General,
+
+ // Checking for completion request only. Will ignore checking all function bodies
+ // except for the function the user is editing.
+ Completion
+};
+
+// This struct wraps all input/output data that is used by the language server to provide
+// content assist support.
+struct ContentAssistInfo
+{
+ // The mode the semantics checking should be operating on. Provided by the
+ // language server.
+ ContentAssistCheckingMode checkingMode = ContentAssistCheckingMode::None;
+ // The primary module from which the current content assist request is made. Provided by the
+ // language server.
+ Name* primaryModuleName = nullptr;
+ // The primary module path from which the current content assist request is made. Provided by the
+ // language server.
+ String primaryModulePath;
+ // The cursor location at which a completion request is made. Provided by the language server.
+ Index cursorLine = 0;
+ // The cursor location at which a completion request is made. Provided by the language server.
+ Index cursorCol = 0;
+
+ // The result candidate items for a completion request. Filled in during semantics checking.
+ CompletionSuggestions completionSuggestions;
+
+ // The preprocessors definitions and invocations found during preprocessing. Filled in during
+ // preprocessing.
+ PreprocessorContentAssistInfo preprocessorInfo;
+};
+
+}
diff --git a/source/slang/slang-language-server-ast-lookup.cpp b/source/slang/slang-language-server-ast-lookup.cpp
index 0e5a68687..6792a18e7 100644
--- a/source/slang/slang-language-server-ast-lookup.cpp
+++ b/source/slang/slang-language-server-ast-lookup.cpp
@@ -78,11 +78,13 @@ bool _isLocInRange(ASTLookupContext* context, SourceLoc start, SourceLoc end)
{
auto startLoc = context->sourceManager->getHumaneLoc(start, SourceLocType::Actual);
auto endLoc = context->sourceManager->getHumaneLoc(end, SourceLocType::Actual);
-
+
Loc s{startLoc.line, startLoc.column};
Loc e{endLoc.line, endLoc.column};
Loc c{context->line, context->col};
- return s <= c && c <= e;
+ return s <= c && c <= e &&
+ startLoc.pathInfo.foundPath.getUnownedSlice().endsWithCaseInsensitive(
+ context->sourceFileName);
}
bool _findAstNodeImpl(ASTLookupContext& context, SyntaxNode* node);
@@ -149,6 +151,8 @@ public:
PushNode pushNodeRAII(context, expr);
if (dispatchIfNotNull(expr->functionExpr))
return true;
+ if (dispatchIfNotNull(expr->originalFunctionExpr))
+ return true;
for (auto arg : expr->arguments)
if (dispatchIfNotNull(arg))
return true;
@@ -317,7 +321,7 @@ public:
context->results.add(result);
return true;
}
- return false;
+ return dispatchIfNotNull(expr->originalExpr);
}
bool visitStaticMemberExpr(StaticMemberExpr* expr)
@@ -548,6 +552,16 @@ bool _findAstNodeImpl(ASTLookupContext& context, SyntaxNode* node)
if (visitor.dispatchIfNotNull(extDecl->targetType.exp))
return true;
}
+ else if (auto importDecl = as<ImportDecl>(node))
+ {
+ if (_isLocInRange(&context, importDecl->startLoc, importDecl->endLoc))
+ {
+ ASTLookupResult result;
+ result.path = context.nodePath;
+ context.results.add(_Move(result));
+ return true;
+ }
+ }
for (auto modifier : decl->modifiers)
{
if (auto hlslSemantic = as<HLSLSemantic>(modifier))
@@ -562,6 +576,21 @@ bool _findAstNodeImpl(ASTLookupContext& context, SyntaxNode* node)
return true;
}
}
+ else if (auto attribute = as<AttributeBase>(modifier))
+ {
+ if (attribute->getKeywordName() &&
+ _isLocInRange(
+ &context,
+ attribute->getKeywordNameAndLoc().loc,
+ attribute->getKeywordName()->text.getLength()))
+ {
+ ASTLookupResult result;
+ result.path = context.nodePath;
+ result.path.add(attribute);
+ context.results.add(result);
+ return true;
+ }
+ }
}
if (auto container = as<ContainerDecl>(node))
{
diff --git a/source/slang/slang-language-server-completion.cpp b/source/slang/slang-language-server-completion.cpp
index c47e951fb..5eee1dba7 100644
--- a/source/slang/slang-language-server-completion.cpp
+++ b/source/slang/slang-language-server-completion.cpp
@@ -5,18 +5,29 @@
#include "slang-language-server.h"
#include "slang-ast-all.h"
-#include "slang-syntax.h"
#include "slang-check-impl.h"
+#include "slang-syntax.h"
namespace Slang
{
-static bool _isIdentifierChar(char ch)
-{
- return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_';
-}
-
-static bool _isWhitespaceChar(char ch) { return ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t'; }
+static const char* kDeclKeywords[] = {
+ "throws", "static", "const", "in", "out", "inout",
+ "ref", "__subscript", "__init", "property", "get", "set",
+ "class", "struct", "interface", "public", "private", "internal",
+ "protected", "typedef", "typealias", "uniform", "export", "groupshared",
+ "extension", "associatedtype", "namespace", "This", "using",
+ "__generic", "__exported", "import", "enum", "cbuffer", "tbuffer", "func"};
+static const char* kStmtKeywords[] = {
+ "if", "else", "switch", "case", "default", "return",
+ "try", "throw", "throws", "catch", "while", "for",
+ "do", "static", "const", "in", "out", "inout",
+ "ref", "__subscript", "__init", "property", "get", "set",
+ "class", "struct", "interface", "public", "private", "internal",
+ "protected", "typedef", "typealias", "uniform", "export", "groupshared",
+ "extension", "associatedtype", "this", "namespace", "This", "using",
+ "__generic", "__exported", "import", "enum", "break", "continue",
+ "discard", "defer", "cbuffer", "tbuffer", "func"};
static const char* hlslSemanticNames[] = {
"register",
@@ -54,416 +65,294 @@ static const char* hlslSemanticNames[] = {
SlangResult CompletionContext::tryCompleteHLSLSemantic()
{
- auto findResult = findASTNodesAt(
- doc,
- version->linkage->getSourceManager(),
- parsedModule->getModuleDecl(),
- ASTLookupType::Decl,
- canonicalPath,
- line,
- col);
- if (findResult.getCount() == 1 && findResult[0].path.getCount() != 0)
- {
- if (auto semantic = as<HLSLSemantic>(findResult[0].path.getLast()))
- {
- List<LanguageServerProtocol::CompletionItem> items;
- for (auto name : hlslSemanticNames)
- {
- LanguageServerProtocol::CompletionItem item;
- item.label = name;
- item.kind = LanguageServerProtocol::kCompletionItemKindKeyword;
- for (auto ch : getCommitChars())
- item.commitCharacters.add(ch);
- items.add(item);
- }
- server->m_connection->sendResult(&items, responseId);
- return SLANG_OK;
- }
- }
- return SLANG_FAIL;
-}
-
-SlangResult CompletionContext::tryCompleteMember()
-{
- // Scan backward until we locate a '.' or ':'.
- if (cursorOffset > 0)
- cursorOffset--;
- while (cursorOffset > 0 && _isWhitespaceChar(doc->getText()[cursorOffset]))
- {
- cursorOffset--;
- }
- while (cursorOffset > 0 && _isIdentifierChar(doc->getText()[cursorOffset]))
- {
- cursorOffset--;
- }
- while (cursorOffset > 0 && _isWhitespaceChar(doc->getText()[cursorOffset]))
- {
- cursorOffset--;
- }
- if (cursorOffset > 0 && doc->getText()[cursorOffset] == ':')
- cursorOffset--;
- if (cursorOffset <= 0 ||
- (doc->getText()[cursorOffset] != '.' && doc->getText()[cursorOffset] != ':'))
- {
- return SLANG_FAIL;
- }
- doc->offsetToLineCol(cursorOffset, line, col);
- auto findResult = findASTNodesAt(
- doc,
- version->linkage->getSourceManager(),
- parsedModule->getModuleDecl(),
- ASTLookupType::Decl,
- canonicalPath,
- line,
- col);
- if (findResult.getCount() != 1)
+ if (version->linkage->contentAssistInfo.completionSuggestions.scopeKind !=
+ CompletionSuggestions::ScopeKind::HLSLSemantics)
{
return SLANG_FAIL;
}
- if (findResult[0].path.getCount() == 0)
- {
- return SLANG_FAIL;
- }
- Expr* baseExpr = nullptr;
- if (auto memberExpr = as<MemberExpr>(findResult[0].path.getLast()))
- {
- baseExpr = memberExpr->baseExpression;
- }
- else if (auto staticMemberExpr = as<StaticMemberExpr>(findResult[0].path.getLast()))
+ List<LanguageServerProtocol::CompletionItem> items;
+ for (auto name : hlslSemanticNames)
{
- baseExpr = staticMemberExpr->baseExpression;
+ LanguageServerProtocol::CompletionItem item;
+ item.label = name;
+ item.kind = LanguageServerProtocol::kCompletionItemKindKeyword;
+ items.add(item);
}
- else if (auto swizzleExpr = as<SwizzleExpr>(findResult[0].path.getLast()))
- {
- baseExpr = swizzleExpr->base;
- }
- else if (auto matSwizzleExpr = as<MatrixSwizzleExpr>(findResult[0].path.getLast()))
- {
- baseExpr = matSwizzleExpr->base;
- }
- if (!baseExpr || !baseExpr->type.type ||
- baseExpr->type.type->equals(version->linkage->getASTBuilder()->getErrorType()))
+ server->m_connection->sendResult(&items, responseId);
+ return SLANG_OK;
+}
+
+SlangResult CompletionContext::tryCompleteAttributes()
+{
+ if (version->linkage->contentAssistInfo.completionSuggestions.scopeKind !=
+ CompletionSuggestions::ScopeKind::Attribute)
{
return SLANG_FAIL;
}
-
- List<LanguageServerProtocol::CompletionItem> items = collectMembers(baseExpr);
+ List<LanguageServerProtocol::CompletionItem> items = collectAttributes();
server->m_connection->sendResult(&items, responseId);
return SLANG_OK;
}
-// The following collectMember* functions implement the logic to collect all members from a parsed type.]
-// The flow is mostly the same as `lookupMemberInType`, but instead of looking for a specific name,
-// we collect all members we see.
-
-struct MemberCollectingContext
+SlangResult CompletionContext::tryCompleteMemberAndSymbol()
{
- ASTBuilder* astBuilder;
- List<Decl*> members;
- bool includeInstanceMembers = true;
- SharedSemanticsContext semanticsContext;
- MemberCollectingContext(Linkage* linkage, Module* module, DiagnosticSink* sink)
- : semanticsContext(linkage, module, sink)
- {}
-};
-
-void collectMembersInTypeDeclImpl(MemberCollectingContext* context, DeclRef<Decl> declRef);
-
-void collectMembersInType(MemberCollectingContext* context, Type* type);
+ List<LanguageServerProtocol::CompletionItem> items = collectMembersAndSymbols();
+ server->m_connection->sendResult(&items, responseId);
+ return SLANG_OK;
+}
-void collectMembersInType(MemberCollectingContext* context, Type* type)
+List<LanguageServerProtocol::CompletionItem> CompletionContext::collectMembersAndSymbols()
{
- if (auto pointerLikeType = as<PointerLikeType>(type))
- {
- collectMembersInType(context, pointerLikeType->elementType);
- return;
- }
-
- if (auto declRefType = as<DeclRefType>(type))
- {
- auto declRef = declRefType->declRef;
-
- collectMembersInTypeDeclImpl(
- context,
- declRef);
- }
- else if (auto nsType = as<NamespaceType>(type))
- {
- auto declRef = nsType->declRef;
-
- collectMembersInTypeDeclImpl(context, declRef);
- }
- else if (auto extractExistentialType = as<ExtractExistentialType>(type))
- {
- // We want lookup to be performed on the underlying interface type of the existential,
- // but we need to have a this-type substitution applied to ensure that the result of
- // lookup will have a comparable substitution applied (allowing things like associated
- // types, etc. used in the signature of a method to resolve correctly).
- //
- auto interfaceDeclRef = extractExistentialType->getSpecializedInterfaceDeclRef();
- collectMembersInTypeDeclImpl(context, interfaceDeclRef);
- }
- else if (auto thisType = as<ThisType>(type))
+ auto linkage = version->linkage;
+ if (linkage->contentAssistInfo.completionSuggestions.scopeKind ==
+ CompletionSuggestions::ScopeKind::Swizzle)
{
- auto interfaceType = DeclRefType::create(context->astBuilder, thisType->interfaceDeclRef);
- collectMembersInType(context, interfaceType);
+ return createSwizzleCandidates(
+ linkage->contentAssistInfo.completionSuggestions.swizzleBaseType,
+ linkage->contentAssistInfo.completionSuggestions.elementCount);
}
- else if (auto andType = as<AndType>(type))
+ List<LanguageServerProtocol::CompletionItem> result;
+ bool useCommitChars = true;
+ bool addKeywords = false;
+ switch (linkage->contentAssistInfo.completionSuggestions.scopeKind)
{
- auto leftType = andType->left;
- auto rightType = andType->right;
- collectMembersInType(context, leftType);
- collectMembersInType(context, rightType);
+ case CompletionSuggestions::ScopeKind::Member:
+ useCommitChars = (commitCharacterBehavior == CommitCharacterBehavior::MembersOnly || commitCharacterBehavior == CommitCharacterBehavior::All);
+ break;
+ case CompletionSuggestions::ScopeKind::Expr:
+ case CompletionSuggestions::ScopeKind::Decl:
+ case CompletionSuggestions::ScopeKind::Stmt:
+ useCommitChars = (commitCharacterBehavior == CommitCharacterBehavior::All);
+ addKeywords = true;
+ break;
+ default:
+ return result;
}
-}
-
-void collectMembersInTypeDeclImpl(
- MemberCollectingContext* context,
- DeclRef<Decl> declRef)
-{
- if (declRef.getDecl()->checkState.getState() < DeclCheckState::ReadyForLookup)
- return;
-
- if (auto genericTypeParamDeclRef = declRef.as<GenericTypeParamDecl>())
+ HashSet<String> deduplicateSet;
+ for (Index i = 0;
+ i < linkage->contentAssistInfo.completionSuggestions.candidateItems.getCount();
+ i++)
{
- // If the type we are doing lookup in is a generic type parameter,
- // then the members it provides can only be discovered by looking
- // at the constraints that are placed on that type.
- auto genericDeclRef = genericTypeParamDeclRef.getParent().as<GenericDecl>();
- assert(genericDeclRef);
-
- for (auto constraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(genericDeclRef))
+ auto& suggestedItem = linkage->contentAssistInfo.completionSuggestions.candidateItems[i];
+ auto member = suggestedItem.declRef.getDecl();
+ if (auto genericDecl = as<GenericDecl>(member))
+ member = genericDecl->inner;
+ if (!member)
+ continue;
+ if (!member->getName())
+ continue;
+ LanguageServerProtocol::CompletionItem item;
+ item.label = member->getName()->text;
+ item.kind = LanguageServerProtocol::kCompletionItemKindKeyword;
+ if (as<TypeConstraintDecl>(member))
{
- if (constraintDeclRef.decl->checkState.getState() < DeclCheckState::ReadyForLookup)
- {
- continue;
- }
+ continue;
+ }
+ if (as<ConstructorDecl>(member))
+ {
+ continue;
+ }
+ if (as<SubscriptDecl>(member))
+ {
+ continue;
+ }
+ if (item.label.getLength() == 0)
+ continue;
+ if (!_isIdentifierChar(item.label[0]))
+ continue;
+ if (item.label.startsWith("$"))
+ continue;
+ if (!deduplicateSet.Add(item.label))
+ continue;
- collectMembersInType(
- context,
- getSup(context->astBuilder, constraintDeclRef));
+ if (as<StructDecl>(member))
+ {
+ item.kind = LanguageServerProtocol::kCompletionItemKindStruct;
}
- }
- else if (declRef.as<AssocTypeDecl>() || declRef.as<GlobalGenericParamDecl>())
- {
- for (auto constraintDeclRef :
- getMembersOfType<TypeConstraintDecl>(declRef.as<ContainerDecl>()))
+ else if (as<ClassDecl>(member))
{
- if (constraintDeclRef.decl->checkState.getState() < DeclCheckState::ReadyForLookup)
- {
- continue;
- }
- collectMembersInType(context, getSup(context->astBuilder, constraintDeclRef));
+ item.kind = LanguageServerProtocol::kCompletionItemKindClass;
}
- }
- else if (auto namespaceDecl = declRef.as<NamespaceDecl>())
- {
- for (auto member : namespaceDecl.getDecl()->members)
+ else if (as<InterfaceDecl>(member))
{
- if (member->getName())
- {
- context->members.add(member);
- }
+ item.kind = LanguageServerProtocol::kCompletionItemKindInterface;
+ }
+ else if (as<SimpleTypeDecl>(member))
+ {
+ item.kind = LanguageServerProtocol::kCompletionItemKindClass;
+ }
+ else if (as<PropertyDecl>(member))
+ {
+ item.kind = LanguageServerProtocol::kCompletionItemKindProperty;
+ }
+ else if (as<EnumDecl>(member))
+ {
+ item.kind = LanguageServerProtocol::kCompletionItemKindEnum;
+ }
+ else if (as<VarDeclBase>(member))
+ {
+ item.kind = LanguageServerProtocol::kCompletionItemKindVariable;
+ }
+ else if (as<EnumCaseDecl>(member))
+ {
+ item.kind = LanguageServerProtocol::kCompletionItemKindEnumMember;
+ }
+ else if (as<CallableDecl>(member))
+ {
+ item.kind = LanguageServerProtocol::kCompletionItemKindMethod;
+ }
+ else if (as<AssocTypeDecl>(member))
+ {
+ item.kind = LanguageServerProtocol::kCompletionItemKindClass;
}
+ item.data = String(i);
+ result.add(item);
}
- else if (auto aggTypeDeclBaseRef = declRef.as<AggTypeDeclBase>())
+ if (addKeywords)
{
- // In this case we are peforming lookup in the context of an aggregate
- // type or an `extension`, so the first thing to do is to look for
- // matching members declared directly in the body of the type/`extension`.
- //
- for (auto member : aggTypeDeclBaseRef.getDecl()->members)
+ if (linkage->contentAssistInfo.completionSuggestions.scopeKind ==
+ CompletionSuggestions::ScopeKind::Decl)
{
- if (member->getName())
+ for (auto keyword : kDeclKeywords)
{
- if (!context->includeInstanceMembers)
- {
- // Skip non-static members.
- if (as<PropertyDecl>(member))
- continue;
- if (as<SubscriptDecl>(member))
- continue;
- if (as<VarDeclBase>(member) || as<FuncDecl>(member))
- {
- if (!member->findModifier<HLSLStaticModifier>())
- {
- continue;
- }
- }
- }
- context->members.add(member);
+ if (!deduplicateSet.Add(keyword))
+ continue;
+ LanguageServerProtocol::CompletionItem item;
+ item.label = keyword;
+ item.kind = LanguageServerProtocol::kCompletionItemKindKeyword;
+ item.data = "-1";
+ result.add(item);
}
}
-
- if (auto aggTypeDeclRef = aggTypeDeclBaseRef.as<AggTypeDecl>())
+ else
{
- auto extensions =
- context->semanticsContext.getCandidateExtensionsForTypeDecl(aggTypeDeclRef);
- for (auto extDecl : extensions)
+ for (auto keyword : kStmtKeywords)
{
- // TODO: check if the extension can be applied before including its members.
- // TODO: eventually we need to insert a breadcrumb here so that
- // the constructed result can somehow indicate that a member
- // was found through an extension.
- //
- collectMembersInTypeDeclImpl(
- context,
- DeclRef<Decl>(extDecl, nullptr));
+ if (!deduplicateSet.Add(keyword))
+ continue;
+ LanguageServerProtocol::CompletionItem item;
+ item.label = keyword;
+ item.kind = LanguageServerProtocol::kCompletionItemKindKeyword;
+ item.data = "-1";
+ result.add(item);
}
}
-
- // For both aggregate types and their `extension`s, we want lookup to follow
- // through the declared inheritance relationships on each declaration.
- //
- for (auto inheritanceDeclRef : getMembersOfType<InheritanceDecl>(aggTypeDeclBaseRef))
+
+ for (auto& def : linkage->contentAssistInfo.preprocessorInfo.macroDefinitions)
{
- // Some things that are syntactically `InheritanceDecl`s don't actually
- // represent a subtype/supertype relationship, and thus we shouldn't
- // include members from the base type when doing lookup in the
- // derived type.
- //
- if (inheritanceDeclRef.getDecl()->hasModifier<IgnoreForLookupModifier>())
+ if (!def.name)
continue;
-
- collectMembersInType(
- context, getSup(context->astBuilder, inheritanceDeclRef));
+ auto& text = def.name->text;
+ if (!deduplicateSet.Add(text))
+ continue;
+ LanguageServerProtocol::CompletionItem item;
+ item.label = text;
+ item.kind = LanguageServerProtocol::kCompletionItemKindKeyword;
+ item.data = "-1";
+ result.add(item);
}
}
+ if (useCommitChars)
+ {
+ for (auto& item : result)
+ {
+ for (auto ch : getCommitChars())
+ item.commitCharacters.add(ch);
+ }
+ }
+ return result;
}
-List<LanguageServerProtocol::CompletionItem> CompletionContext::collectMembers(Expr* baseExpr)
+List<LanguageServerProtocol::CompletionItem> CompletionContext::createSwizzleCandidates(
+ Type* type, IntegerLiteralValue elementCount[2])
{
List<LanguageServerProtocol::CompletionItem> result;
- auto linkage = version->linkage;
- Type* type = baseExpr->type.type;
- bool isInstance = true;
- if (auto typeType = as<TypeType>(type))
+ // Hard code members for vector and matrix types.
+ result.clear();
+ if (auto vectorType = as<VectorExpressionType>(type))
{
- type = typeType->type;
- isInstance = false;
+ const char* memberNames[4] = {"x", "y", "z", "w"};
+ Type* elementType = nullptr;
+ elementType = vectorType->elementType;
+ String typeStr;
+ if (elementType)
+ typeStr = elementType->toString();
+ auto count = Math::Min((int)elementCount[0], 4);
+ for (int i = 0; i < count; i++)
+ {
+ LanguageServerProtocol::CompletionItem item;
+ item.data = 0;
+ item.detail = typeStr;
+ item.kind = LanguageServerProtocol::kCompletionItemKindVariable;
+ item.label = memberNames[i];
+ result.add(item);
+ }
}
- version->currentCompletionItems.clear();
- if (type)
+ else if (auto matrixType = as<MatrixExpressionType>(type))
{
- if (isInstance && as<ArithmeticExpressionType>(type))
+ Type* elementType = nullptr;
+ elementType = matrixType->getElementType();
+ String typeStr;
+ if (elementType)
{
- // Hard code members for vector and matrix types.
- result.clear();
- version->currentCompletionItems.clear();
- int elementCount = 0;
- Type* elementType = nullptr;
- const char* memberNames[4] = {"x", "y", "z", "w"};
- if (auto vectorType = as<VectorExpressionType>(type))
- {
- if (auto elementCountVal = as<ConstantIntVal>(vectorType->elementCount))
- {
- elementCount = (int)elementCountVal->value;
- elementType = vectorType->elementType;
- }
- }
- else if (auto matrixType = as<MatrixExpressionType>(type))
- {
- if (auto elementCountVal = as<ConstantIntVal>(matrixType->getRowCount()))
- {
- elementCount = (int)elementCountVal->value;
- elementType = matrixType->getRowType();
- }
- }
- String typeStr;
- if (elementType)
- typeStr = elementType->toString();
- elementCount = Math::Min(elementCount, 4);
- for (int i = 0; i < elementCount; i++)
+ typeStr = elementType->toString();
+ }
+ int rowCount = Math::Min((int)elementCount[0], 4);
+ int colCount = Math::Min((int)elementCount[1], 4);
+ StringBuilder nameSB;
+ for (int i = 0; i < rowCount; i++)
+ {
+ for (int j = 0; j < colCount; j++)
{
LanguageServerProtocol::CompletionItem item;
item.data = 0;
item.detail = typeStr;
item.kind = LanguageServerProtocol::kCompletionItemKindVariable;
- item.label = memberNames[i];
+ nameSB.Clear();
+ nameSB << "_m" << i << j;
+ item.label = nameSB.ToString();
+ result.add(item);
+ nameSB.Clear();
+ nameSB << "_" << i + 1 << j + 1;
+ item.label = nameSB.ToString();
result.add(item);
}
}
- else
+ }
+ for (auto& item : result)
+ {
+ for (auto ch : getCommitChars())
+ item.commitCharacters.add(ch);
+ }
+ return result;
+}
+
+List<LanguageServerProtocol::CompletionItem> CompletionContext::collectAttributes()
+{
+ List<LanguageServerProtocol::CompletionItem> result;
+ for (auto& item : version->linkage->contentAssistInfo.completionSuggestions.candidateItems)
+ {
+ if (auto attrDecl = as<AttributeDecl>(item.declRef.getDecl()))
{
- DiagnosticSink sink;
- MemberCollectingContext context(linkage, parsedModule, &sink);
- context.astBuilder = linkage->getASTBuilder();
- context.includeInstanceMembers = isInstance;
- collectMembersInType(&context, type);
- HashSet<String> deduplicateSet;
- for (auto member : context.members)
+ if (attrDecl->getName())
{
- LanguageServerProtocol::CompletionItem item;
- item.label = member->getName()->text;
- item.kind = 0;
- if (as<TypeConstraintDecl>(member))
- {
- continue;
- }
- if (as<ConstructorDecl>(member))
- {
- continue;
- }
- if (as<SubscriptDecl>(member))
- {
- continue;
- }
-
- if (item.label.startsWith("$"))
- continue;
- if (!deduplicateSet.Add(item.label))
- continue;
-
- if (as<StructDecl>(member))
- {
- item.kind = LanguageServerProtocol::kCompletionItemKindStruct;
- }
- else if (as<ClassDecl>(member))
- {
- item.kind = LanguageServerProtocol::kCompletionItemKindClass;
- }
- else if (as<InterfaceDecl>(member))
- {
- item.kind = LanguageServerProtocol::kCompletionItemKindInterface;
- }
- else if (as<SimpleTypeDecl>(member))
- {
- item.kind = LanguageServerProtocol::kCompletionItemKindClass;
- }
- else if (as<PropertyDecl>(member))
- {
- item.kind = LanguageServerProtocol::kCompletionItemKindProperty;
- }
- else if (as<EnumDecl>(member))
- {
- item.kind = LanguageServerProtocol::kCompletionItemKindEnum;
- }
- else if (as<VarDeclBase>(member))
- {
- item.kind = LanguageServerProtocol::kCompletionItemKindVariable;
- }
- else if (as<EnumCaseDecl>(member))
- {
- item.kind = LanguageServerProtocol::kCompletionItemKindEnumMember;
- }
- else if (as<CallableDecl>(member))
- {
- item.kind = LanguageServerProtocol::kCompletionItemKindMethod;
- }
- else if (as<AssocTypeDecl>(member))
- {
- item.kind = LanguageServerProtocol::kCompletionItemKindClass;
- }
- item.data = String(version->currentCompletionItems.getCount());
- result.add(item);
- version->currentCompletionItems.add(member);
+ LanguageServerProtocol::CompletionItem resultItem;
+ resultItem.kind = LanguageServerProtocol::kCompletionItemKindKeyword;
+ resultItem.label = attrDecl->getName()->text;
+ result.add(resultItem);
}
}
-
- for (auto& item : result)
+ else if (auto decl = as<AggTypeDecl>(item.declRef.getDecl()))
{
- for (auto ch : getCommitChars())
- item.commitCharacters.add(ch);
+ if (decl->getName())
+ {
+ LanguageServerProtocol::CompletionItem resultItem;
+ resultItem.kind = LanguageServerProtocol::kCompletionItemKindStruct;
+ resultItem.label = decl->getName()->text;
+ if (resultItem.label.endsWith("Attribute"))
+ resultItem.label.reduceLength(resultItem.label.getLength() - 9);
+ result.add(resultItem);
+ }
}
}
return result;
diff --git a/source/slang/slang-language-server-completion.h b/source/slang/slang-language-server-completion.h
index 73aad6cd1..8c0c782ee 100644
--- a/source/slang/slang-language-server-completion.h
+++ b/source/slang/slang-language-server-completion.h
@@ -2,11 +2,19 @@
#pragma once
#include "slang-workspace-version.h"
+#include "slang-language-server-ast-lookup.h"
namespace Slang
{
class LanguageServer;
+enum class CommitCharacterBehavior
+{
+ Disabled,
+ MembersOnly,
+ All
+};
+
struct CompletionContext
{
LanguageServer* server;
@@ -16,12 +24,17 @@ struct CompletionContext
Module* parsedModule;
JSONValue responseId;
UnownedStringSlice canonicalPath;
+ CommitCharacterBehavior commitCharacterBehavior;
Int line;
Int col;
- SlangResult tryCompleteMember();
+ SlangResult tryCompleteMemberAndSymbol();
SlangResult tryCompleteHLSLSemantic();
- List<LanguageServerProtocol::CompletionItem> collectMembers(Expr* baseExpr);
+ SlangResult tryCompleteAttributes();
+ List<LanguageServerProtocol::CompletionItem> collectMembersAndSymbols();
+ List<LanguageServerProtocol::CompletionItem> createSwizzleCandidates(
+ Type* baseType, IntegerLiteralValue elementCount[2]);
+ List<LanguageServerProtocol::CompletionItem> collectAttributes();
};
} // namespace Slang
diff --git a/source/slang/slang-language-server-semantic-tokens.cpp b/source/slang/slang-language-server-semantic-tokens.cpp
index ff9056e56..6b3725828 100644
--- a/source/slang/slang-language-server-semantic-tokens.cpp
+++ b/source/slang/slang-language-server-semantic-tokens.cpp
@@ -1,423 +1,24 @@
#include "slang-language-server-semantic-tokens.h"
#include "slang-visitor.h"
#include "slang-ast-support-types.h"
+#include "slang-ast-iterator.h"
#include "../core/slang-char-util.h"
#include <algorithm>
namespace Slang
{
-template<typename Callback>
-struct ASTIterator
-{
- const Callback& callback;
- UnownedStringSlice fileName;
- SourceManager* sourceManager;
- ASTIterator(const Callback& func, SourceManager* manager, UnownedStringSlice sourceFileName)
- : callback(func)
- , fileName(sourceFileName)
- , sourceManager(manager)
- {}
-
- void visitDecl(DeclBase* decl);
- void visitExpr(Expr* expr);
- void visitStmt(Stmt* stmt);
-
- void maybeDispatchCallback(SyntaxNode* node)
- {
- if (node)
- {
- callback(node);
- }
- }
-
- struct ASTIteratorExprVisitor : public ExprVisitor<ASTIteratorExprVisitor>
- {
- public:
- ASTIterator* iterator;
- ASTIteratorExprVisitor(ASTIterator* iter)
- : iterator(iter)
- {}
- void dispatchIfNotNull(Expr* expr)
- {
- if (!expr)
- return;
- expr->accept(this, nullptr);
- }
- bool visitExpr(Expr*) { return false; }
- void visitBoolLiteralExpr(BoolLiteralExpr* expr) { iterator->maybeDispatchCallback(expr); }
- void visitNullPtrLiteralExpr(NullPtrLiteralExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- }
- void visitIntegerLiteralExpr(IntegerLiteralExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- }
- void visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- }
- void visitStringLiteralExpr(StringLiteralExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- }
- void visitIncompleteExpr(IncompleteExpr* expr) { iterator->maybeDispatchCallback(expr); }
- void visitIndexExpr(IndexExpr* subscriptExpr)
- {
- iterator->maybeDispatchCallback(subscriptExpr);
- dispatchIfNotNull(subscriptExpr->baseExpression);
- dispatchIfNotNull(subscriptExpr->indexExpression);
- }
-
- void visitParenExpr(ParenExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->base);
- }
-
- void visitAssignExpr(AssignExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->left);
- dispatchIfNotNull(expr->right);
- }
-
- void visitGenericAppExpr(GenericAppExpr* genericAppExpr)
- {
- iterator->maybeDispatchCallback(genericAppExpr);
-
- dispatchIfNotNull(genericAppExpr->functionExpr);
- for (auto arg : genericAppExpr->arguments)
- dispatchIfNotNull(arg);
- }
-
- void visitSharedTypeExpr(SharedTypeExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->base.exp);
- }
-
- void visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr) { iterator->maybeDispatchCallback(expr); }
-
- void visitInvokeExpr(InvokeExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
-
- dispatchIfNotNull(expr->functionExpr);
- for (auto arg : expr->arguments)
- dispatchIfNotNull(arg);
- }
-
- void visitVarExpr(VarExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->originalExpr);
- }
-
- void visitTryExpr(TryExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->base);
- }
-
- void visitTypeCastExpr(TypeCastExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
-
- dispatchIfNotNull(expr->functionExpr);
- for (auto arg : expr->arguments)
- dispatchIfNotNull(arg);
- }
-
- void visitDerefExpr(DerefExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->base);
- }
- void visitMatrixSwizzleExpr(MatrixSwizzleExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->base);
- }
- void visitSwizzleExpr(SwizzleExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->base);
- }
- void visitOverloadedExpr(OverloadedExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->base);
- }
- void visitOverloadedExpr2(OverloadedExpr2* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->base);
- for (auto candidate : expr->candidiateExprs)
- {
- dispatchIfNotNull(candidate);
- }
- }
- void visitAggTypeCtorExpr(AggTypeCtorExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->base.exp);
- for (auto arg : expr->arguments)
- {
- dispatchIfNotNull(arg);
- }
- }
- void visitCastToSuperTypeExpr(CastToSuperTypeExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->valueArg);
- }
- void visitModifierCastExpr(ModifierCastExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->valueArg);
- }
- void visitLetExpr(LetExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- iterator->visitDecl(expr->decl);
- dispatchIfNotNull(expr->body);
- }
- void visitExtractExistentialValueExpr(ExtractExistentialValueExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- }
-
- void visitDeclRefExpr(DeclRefExpr* expr) { iterator->maybeDispatchCallback(expr); }
-
- void visitStaticMemberExpr(StaticMemberExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->baseExpression);
- }
-
- void visitMemberExpr(MemberExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->baseExpression);
- }
-
- void visitInitializerListExpr(InitializerListExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- for (auto arg : expr->args)
- {
- dispatchIfNotNull(arg);
- }
- }
-
- void visitThisExpr(ThisExpr* expr) { iterator->maybeDispatchCallback(expr); }
- void visitThisTypeExpr(ThisTypeExpr* expr) { iterator->maybeDispatchCallback(expr); }
- void visitAndTypeExpr(AndTypeExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->left.exp);
- dispatchIfNotNull(expr->right.exp);
- }
- void visitModifiedTypeExpr(ModifiedTypeExpr* expr)
- {
- iterator->maybeDispatchCallback(expr);
- dispatchIfNotNull(expr->base.exp);
- }
- };
-
- struct ASTIteratorStmtVisitor : public StmtVisitor<ASTIteratorStmtVisitor>
- {
- ASTIterator* iterator;
- ASTIteratorStmtVisitor(ASTIterator* iter)
- : iterator(iter)
- {}
-
- void dispatchIfNotNull(Stmt* stmt)
- {
- if (!stmt)
- return;
- stmt->accept(this, nullptr);
- }
-
- void visitDeclStmt(DeclStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- iterator->visitDecl(stmt->decl);
- }
-
- void visitBlockStmt(BlockStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- dispatchIfNotNull(stmt->body);
- }
-
- void visitSeqStmt(SeqStmt* seqStmt)
- {
- iterator->maybeDispatchCallback(seqStmt);
- for (auto stmt : seqStmt->stmts)
- dispatchIfNotNull(stmt);
- }
-
- void visitBreakStmt(BreakStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
-
- void visitContinueStmt(ContinueStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
-
- void visitDoWhileStmt(DoWhileStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- iterator->visitExpr(stmt->predicate);
- dispatchIfNotNull(stmt->statement);
- }
-
- void visitForStmt(ForStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- dispatchIfNotNull(stmt->initialStatement);
- iterator->visitExpr(stmt->predicateExpression);
- iterator->visitExpr(stmt->sideEffectExpression);
- dispatchIfNotNull(stmt->statement);
- }
-
- void visitCompileTimeForStmt(CompileTimeForStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- }
-
- void visitSwitchStmt(SwitchStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- iterator->visitExpr(stmt->condition);
- dispatchIfNotNull(stmt->body);
- }
-
- void visitCaseStmt(CaseStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- iterator->visitExpr(stmt->expr);
- }
-
- void visitDefaultStmt(DefaultStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
-
- void visitIfStmt(IfStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- iterator->visitExpr(stmt->predicate);
- dispatchIfNotNull(stmt->positiveStatement);
- dispatchIfNotNull(stmt->negativeStatement);
- }
-
- void visitUnparsedStmt(UnparsedStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
-
- void visitEmptyStmt(EmptyStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
-
- void visitDiscardStmt(DiscardStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
-
- void visitReturnStmt(ReturnStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- iterator->visitExpr(stmt->expression);
- }
-
- void visitWhileStmt(WhileStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- iterator->visitExpr(stmt->predicate);
- dispatchIfNotNull(stmt->statement);
- }
-
- void visitGpuForeachStmt(GpuForeachStmt* stmt) { iterator->maybeDispatchCallback(stmt); }
-
- void visitExpressionStmt(ExpressionStmt* stmt)
- {
- iterator->maybeDispatchCallback(stmt);
- iterator->visitExpr(stmt->expression);
- }
- };
-};
-
-template <typename CallbackFunc>
-void ASTIterator<CallbackFunc>::visitDecl(DeclBase* decl)
-{
- // Don't look at the decl if it is defined in a different file.
- if (!as<ModuleDecl>(decl) &&
- !sourceManager->getHumaneLoc(decl->loc, SourceLocType::Actual)
- .pathInfo.foundPath.getUnownedSlice()
- .endsWithCaseInsensitive(fileName))
- return;
-
- maybeDispatchCallback(decl);
- if (auto funcDecl = as<FunctionDeclBase>(decl))
- {
- visitStmt(funcDecl->body);
- visitExpr(funcDecl->returnType.exp);
- }
- else if (auto propertyDecl = as<PropertyDecl>(decl))
- {
- visitExpr(propertyDecl->type.exp);
- }
- else if (auto varDecl = as<VarDeclBase>(decl))
- {
- visitExpr(varDecl->type.exp);
- visitExpr(varDecl->initExpr);
- }
- else if (auto genericDecl = as<GenericDecl>(decl))
- {
- visitDecl(genericDecl->inner);
- }
- else if (auto typeConstraint = as<TypeConstraintDecl>(decl))
- {
- visitExpr(typeConstraint->getSup().exp);
- }
- else if (auto typedefDecl = as<TypeDefDecl>(decl))
- {
- visitExpr(typedefDecl->type.exp);
- }
- else if (auto extDecl = as<ExtensionDecl>(decl))
- {
- visitExpr(extDecl->targetType.exp);
- }
- if (auto container = as<ContainerDecl>(decl))
- {
- for (auto member : container->members)
- {
- visitDecl(member);
- }
- }
-}
-template <typename CallbackFunc>
-void ASTIterator<CallbackFunc>::visitExpr(Expr* expr)
-{
- ASTIteratorExprVisitor visitor(this);
- visitor.dispatchIfNotNull(expr);
-}
-template <typename CallbackFunc>
-void ASTIterator<CallbackFunc>::visitStmt(Stmt* stmt)
-{
- ASTIteratorStmtVisitor visitor(this);
- visitor.dispatchIfNotNull(stmt);
-}
-
-template <typename Func>
-void iterateAST(UnownedStringSlice fileName, SourceManager* manager, SyntaxNode* node, const Func& f)
-{
- ASTIterator<Func> iter(f, manager, fileName);
- if (auto decl = as<Decl>(node))
- {
- iter.visitDecl(decl);
- }
- else if (auto expr = as<Expr>(node))
- {
- iter.visitExpr(expr);
- }
- else if (auto stmt = as<Stmt>(node))
- {
- iter.visitStmt(stmt);
- }
-}
const char* kSemanticTokenTypes[] = {
- "type", "enumMember", "variable", "parameter", "function", "property", "namespace", "keyword" };
+ "type",
+ "enumMember",
+ "variable",
+ "parameter",
+ "function",
+ "property",
+ "namespace",
+ "keyword",
+ "macro"
+};
static_assert(SLANG_COUNT_OF(kSemanticTokenTypes) == (int)SemanticTokenType::NormalText, "kSemanticTokenTypes must match SemanticTokenType");
@@ -585,6 +186,23 @@ List<SemanticToken> getSemanticTokens(Linkage* linkage, Module* module, UnownedS
}
}
});
+ // 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;
}
diff --git a/source/slang/slang-language-server-semantic-tokens.h b/source/slang/slang-language-server-semantic-tokens.h
index c7cd8b63a..4a7368fcc 100644
--- a/source/slang/slang-language-server-semantic-tokens.h
+++ b/source/slang/slang-language-server-semantic-tokens.h
@@ -11,7 +11,7 @@ namespace Slang
{
enum class SemanticTokenType
{
- Type, EnumMember, Variable, Parameter, Function, Property, Namespace, Keyword, NormalText
+ Type, EnumMember, Variable, Parameter, Function, Property, Namespace, Keyword, Macro, NormalText
};
extern const char* kSemanticTokenTypes[(int)SemanticTokenType::NormalText];
diff --git a/source/slang/slang-language-server.cpp b/source/slang/slang-language-server.cpp
index 1ad0e28e9..619aaaacd 100644
--- a/source/slang/slang-language-server.cpp
+++ b/source/slang/slang-language-server.cpp
@@ -8,7 +8,6 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
-#include <thread>
#include "../core/slang-secure-crt.h"
#include "../core/slang-range.h"
#include "../../slang-com-helper.h"
@@ -23,6 +22,7 @@
#include "slang-language-server-document-symbols.h"
#include "slang-ast-print.h"
#include "slang-doc-markdown-writer.h"
+#include "slang-mangle.h"
#include "../../tools/platform/performance-counter.h"
namespace Slang
@@ -114,6 +114,7 @@ SlangResult LanguageServer::parseNextMessage()
result.capabilities.documentSymbolProvider = true;
result.capabilities.completionProvider.triggerCharacters.add(".");
result.capabilities.completionProvider.triggerCharacters.add(":");
+ result.capabilities.completionProvider.triggerCharacters.add("[");
result.capabilities.completionProvider.resolveProvider = true;
result.capabilities.completionProvider.workDoneToken = "";
for (auto ch : getCommitChars())
@@ -152,11 +153,12 @@ SlangResult LanguageServer::parseNextMessage()
if (response.result.getKind() == JSONValue::Kind::Array)
{
auto arr = m_connection->getContainer()->getArray(response.result);
- if (arr.getCount() == 3)
+ if (arr.getCount() == 4)
{
updatePredefinedMacros(arr[0]);
updateSearchPaths(arr[1]);
updateSearchInWorkspace(arr[2]);
+ updateCommitCharacters(arr[3]);
}
}
break;
@@ -223,7 +225,7 @@ static String _formatDocumentation(String doc)
auto trimedLine = lines[i].trimStart();
if (i > 0)
{
- if (trimedLine.startsWith("\\") && lines[i - 1].trim().getLength() != 0)
+ if ((trimedLine.startsWith("\\") || trimedLine.startsWith("@")) && lines[i - 1].trim().getLength() != 0)
{
result << " \n";
}
@@ -232,15 +234,19 @@ static String _formatDocumentation(String doc)
result << "\n";
}
}
- if (trimedLine.startsWith("\\returns "))
+ if (trimedLine.startsWith("\\") || trimedLine.startsWith("@"))
{
- trimedLine = trimedLine.subString(9, trimedLine.getLength());
- result << "**returns** ";
- }
- else if (trimedLine.startsWith("\\return "))
- {
- trimedLine = trimedLine.subString(8, trimedLine.getLength());
- result << "**Returns** ";
+ trimedLine = trimedLine.tail(1);
+ if (trimedLine.startsWith("returns "))
+ {
+ trimedLine = trimedLine.subString(8, trimedLine.getLength());
+ result << "**returns** ";
+ }
+ else if (trimedLine.startsWith("return "))
+ {
+ trimedLine = trimedLine.subString(7, trimedLine.getLength());
+ result << "**Returns** ";
+ }
}
result << trimedLine;
}
@@ -294,6 +300,8 @@ SlangResult LanguageServer::hover(
col);
if (findResult.getCount() == 0 || findResult[0].path.getCount() == 0)
{
+ if (SLANG_SUCCEEDED(tryGetMacroHoverInfo(version, doc, line, col, responseId)))
+ return SLANG_OK;
m_connection->sendResult(NullResponse::get(), responseId);
return SLANG_OK;
}
@@ -393,6 +401,10 @@ SlangResult LanguageServer::gotoDefinition(
col);
if (findResult.getCount() == 0 || findResult[0].path.getCount() == 0)
{
+ if (SLANG_SUCCEEDED(tryGotoMacroDefinition(version, doc, line, col, responseId)))
+ return SLANG_OK;
+ if (SLANG_SUCCEEDED(tryGotoFileInclude(version, doc, line, responseId)))
+ return SLANG_OK;
m_connection->sendResult(NullResponse::get(), responseId);
return SLANG_OK;
}
@@ -438,7 +450,22 @@ SlangResult LanguageServer::gotoDefinition(
locations.add(LocationResult{location, name ? (int)name->text.getLength() : 0});
}
}
-
+ }
+ else if (auto importDecl = as<ImportDecl>(leafNode))
+ {
+ if (importDecl->importedModuleDecl)
+ {
+ if (importDecl->importedModuleDecl->members.getCount() &&
+ importDecl->importedModuleDecl->members[0])
+ {
+ auto loc = importDecl->importedModuleDecl->members[0]->loc;
+ if (loc.isValid())
+ {
+ auto location = version->linkage->getSourceManager()->getHumaneLoc(loc, SourceLocType::Actual);
+ locations.add(LocationResult{location, 0});
+ }
+ }
+ }
}
if (locations.getCount() == 0)
{
@@ -467,6 +494,16 @@ SlangResult LanguageServer::gotoDefinition(
}
}
+template <typename Func> struct Deferred
+{
+ Func f;
+ Deferred(const Func& func)
+ : f(func)
+ {}
+ ~Deferred() { f(); }
+};
+template <typename Func> Deferred<Func> makeDeferred(const Func& f) { return Deferred<Func>(f); }
+
SlangResult LanguageServer::completion(
const LanguageServerProtocol::CompletionParams& args, const JSONValue& responseId)
{
@@ -487,8 +524,28 @@ SlangResult LanguageServer::completion(
m_connection->sendResult(NullResponse::get(), responseId);
return SLANG_OK;
}
-
- auto version = m_workspace->getCurrentVersion();
+
+ // Ajust cursor position to the beginning of the current/last identifier.
+ cursorOffset--;
+ while (cursorOffset > 0 && _isIdentifierChar(doc->getText()[cursorOffset]))
+ {
+ cursorOffset--;
+ }
+
+ // Insert a completion request token at cursor position.
+ auto originalText = doc->getText();
+ StringBuilder newText;
+ newText << originalText.getUnownedSlice().head(cursorOffset + 1) << "#?"
+ << originalText.getUnownedSlice().tail(cursorOffset + 1);
+ doc->setText(newText.ProduceString());
+ auto restoreDocText = makeDeferred([&]() { doc->setText(originalText); });
+
+ // Always create a new workspace version for the completion request since we
+ // will use a modified source.
+ auto version = m_workspace->createVersionForCompletion();
+ auto moduleName = getMangledNameFromNameString(canonicalPath.getUnownedSlice());
+ version->linkage->contentAssistInfo.cursorLine = utf8Line;
+ version->linkage->contentAssistInfo.cursorCol = utf8Col;
Module* parsedModule = version->getOrLoadModule(canonicalPath);
if (!parsedModule)
{
@@ -506,7 +563,8 @@ SlangResult LanguageServer::completion(
context.canonicalPath = canonicalPath.getUnownedSlice();
context.line = utf8Line;
context.col = utf8Col;
- if (SLANG_SUCCEEDED(context.tryCompleteMember()))
+ context.commitCharacterBehavior = m_commitCharacterBehavior;
+ if (SLANG_SUCCEEDED(context.tryCompleteAttributes()))
{
return SLANG_OK;
}
@@ -514,6 +572,10 @@ SlangResult LanguageServer::completion(
{
return SLANG_OK;
}
+ if (SLANG_SUCCEEDED(context.tryCompleteMemberAndSymbol()))
+ {
+ return SLANG_OK;
+ }
m_connection->sendResult(NullResponse::get(), responseId);
return SLANG_OK;
}
@@ -523,14 +585,19 @@ SlangResult LanguageServer::completionResolve(
{
LanguageServerProtocol::CompletionItem resolvedItem = args;
int itemId = StringToInt(args.data);
- auto version = m_workspace->getCurrentVersion();
- if (itemId >= 0 && itemId < version->currentCompletionItems.getCount())
+ auto version = m_workspace->getCurrentCompletionVersion();
+ if (!version || !version->linkage)
{
- auto decl = version->currentCompletionItems[itemId];
- resolvedItem.detail = getDeclSignatureString(
- DeclRef<Decl>(decl, nullptr), version->linkage->getASTBuilder());
+ m_connection->sendResult(&resolvedItem, responseId);
+ return SLANG_OK;
+ }
+ auto& candidateItems = version->linkage->contentAssistInfo.completionSuggestions.candidateItems;
+ if (itemId >= 0 && itemId < candidateItems.getCount())
+ {
+ auto declRef = candidateItems[itemId].declRef;
+ resolvedItem.detail = getDeclSignatureString(declRef, version->linkage->getASTBuilder());
StringBuilder docSB;
- _tryGetDocumentation(docSB, version, decl);
+ _tryGetDocumentation(docSB, version, declRef.getDecl());
resolvedItem.documentation.value = docSB.ProduceString();
resolvedItem.documentation.kind = "markdown";
}
@@ -817,14 +884,14 @@ void LanguageServer::updatePredefinedMacros(const JSONValue& macros)
}
}
-void LanguageServer::updateSearchPaths(const JSONValue& macros)
+void LanguageServer::updateSearchPaths(const JSONValue& value)
{
- if (macros.isValid())
+ if (value.isValid())
{
auto container = m_connection->getContainer();
JSONToNativeConverter converter(container, m_connection->getSink());
List<String> searchPaths;
- if (SLANG_SUCCEEDED(converter.convert(macros, &searchPaths)))
+ if (SLANG_SUCCEEDED(converter.convert(value, &searchPaths)))
{
if (m_workspace->updateSearchPaths(searchPaths))
{
@@ -835,14 +902,14 @@ void LanguageServer::updateSearchPaths(const JSONValue& macros)
}
}
-void LanguageServer::updateSearchInWorkspace(const JSONValue& macros)
+void LanguageServer::updateSearchInWorkspace(const JSONValue& value)
{
- if (macros.isValid())
+ if (value.isValid())
{
auto container = m_connection->getContainer();
JSONToNativeConverter converter(container, m_connection->getSink());
bool searchPaths;
- if (SLANG_SUCCEEDED(converter.convert(macros, &searchPaths)))
+ if (SLANG_SUCCEEDED(converter.convert(value, &searchPaths)))
{
if (m_workspace->updateSearchInWorkspace(searchPaths))
{
@@ -853,6 +920,32 @@ void LanguageServer::updateSearchInWorkspace(const JSONValue& macros)
}
}
+void LanguageServer::updateCommitCharacters(const JSONValue& jsonValue)
+{
+ if (jsonValue.isValid())
+ {
+ auto container = m_connection->getContainer();
+ JSONToNativeConverter converter(container, m_connection->getSink());
+ String value;
+ if (SLANG_SUCCEEDED(converter.convert(jsonValue, &value)))
+ {
+ if (value == "on")
+ {
+ m_commitCharacterBehavior = CommitCharacterBehavior::All;
+ }
+ else if (value == "off")
+ {
+ m_commitCharacterBehavior = CommitCharacterBehavior::Disabled;
+ }
+ else
+ {
+ m_commitCharacterBehavior = CommitCharacterBehavior::MembersOnly;
+ }
+ }
+ }
+}
+
+
void LanguageServer::sendConfigRequest()
{
ConfigurationParams args;
@@ -863,6 +956,8 @@ void LanguageServer::sendConfigRequest()
args.items.add(item);
item.section = "slang.searchInAllWorkspaceDirectories";
args.items.add(item);
+ item.section = "slang.enableCommitCharactersInAutoCompletion";
+ args.items.add(item);
m_connection->sendCall(
ConfigurationParams::methodName,
&args,
@@ -888,6 +983,106 @@ void LanguageServer::logMessage(int type, String message)
m_connection->sendCall(LanguageServerProtocol::LogMessageParams::methodName, &args);
}
+SlangResult LanguageServer::tryGetMacroHoverInfo(
+ WorkspaceVersion* version, DocumentVersion* doc, Index line, Index col, JSONValue responseId)
+{
+ Index startOffset = 0;
+ auto identifier = doc->peekIdentifier(line, col, startOffset);
+ if (identifier.getLength() == 0)
+ return SLANG_FAIL;
+ auto def = version->tryGetMacroDefinition(identifier);
+ if (!def)
+ return SLANG_FAIL;
+ LanguageServerProtocol::Hover hover;
+ doc->offsetToLineCol(startOffset, line, col);
+ Index outLine, outCol;
+ doc->oneBasedUTF8LocToZeroBasedUTF16Loc(line, col, outLine, outCol);
+ hover.range.start.line = (int)outLine;
+ hover.range.start.character = (int)outCol;
+ hover.range.end.line = (int)outLine;
+ hover.range.end.character = (int)(outCol + identifier.getLength());
+ StringBuilder sb;
+ sb << "```\n#define " << identifier;
+ if (def->params.getCount())
+ {
+ sb << "(";
+ bool isFirst = true;
+ for (auto param : def->params)
+ {
+ if (!isFirst)
+ sb << ", ";
+ if (param.isVariadic)
+ sb << "...";
+ else if (param.name)
+ sb << param.name->text;
+ isFirst = false;
+ }
+ sb << ")";
+ }
+ for (auto& token : def->tokenList)
+ {
+ sb << " ";
+ sb << token.getContent();
+ }
+ sb << "\n```\n\n";
+ auto humaneLoc =
+ version->linkage->getSourceManager()->getHumaneLoc(def->loc, SourceLocType::Actual);
+ sb << "Defined in " << humaneLoc.pathInfo.foundPath << "(" << humaneLoc.line << ")\n";
+ hover.contents.kind = "markdown";
+ hover.contents.value = sb.ProduceString();
+ m_connection->sendResult(&hover, responseId);
+ return SLANG_OK;
+}
+
+SlangResult LanguageServer::tryGotoMacroDefinition(
+ WorkspaceVersion* version, DocumentVersion* doc, Index line, Index col, JSONValue responseId)
+{
+ Index startOffset = 0;
+ auto identifier = doc->peekIdentifier(line, col, startOffset);
+ if (identifier.getLength() == 0)
+ return SLANG_FAIL;
+ auto def = version->tryGetMacroDefinition(identifier);
+ if (!def)
+ return SLANG_FAIL;
+ auto humaneLoc =
+ version->linkage->getSourceManager()->getHumaneLoc(def->loc, SourceLocType::Actual);
+ LanguageServerProtocol::Location result;
+ result.uri = URI::fromLocalFilePath(humaneLoc.pathInfo.foundPath.getUnownedSlice()).uri;
+ Index outLine, outCol;
+ doc->oneBasedUTF8LocToZeroBasedUTF16Loc(humaneLoc.line, humaneLoc.column, outLine, outCol);
+ result.range.start.line = (int)outLine;
+ result.range.start.character = (int)outCol;
+ result.range.end.line = (int)outLine;
+ result.range.end.character = (int)(outCol + identifier.getLength());
+ m_connection->sendResult(&result, responseId);
+ return SLANG_OK;
+}
+
+SlangResult LanguageServer::tryGotoFileInclude(
+ WorkspaceVersion* version, DocumentVersion* doc, Index line, JSONValue responseId)
+{
+ auto lineContent = doc->getLine(line).trim();
+ if (!lineContent.startsWith("#") || lineContent.indexOf(UnownedStringSlice("include")) == -1)
+ return SLANG_FAIL;
+ for (auto& include : version->linkage->contentAssistInfo.preprocessorInfo.fileIncludes)
+ {
+ auto includeLoc =
+ version->linkage->getSourceManager()->getHumaneLoc(include.loc, SourceLocType::Actual);
+ if (includeLoc.line == line && includeLoc.pathInfo.foundPath == doc->getPath())
+ {
+ LanguageServerProtocol::Location result;
+ result.uri = URI::fromLocalFilePath(include.path.getUnownedSlice()).uri;
+ result.range.start.line = 0;
+ result.range.start.character = 0;
+ result.range.end.line = 0;
+ result.range.end.character = 0;
+ m_connection->sendResult(&result, responseId);
+ return SLANG_OK;
+ }
+ }
+ return SLANG_FAIL;
+}
+
SlangResult LanguageServer::queueJSONCall(JSONRPCCall call)
{
Command cmd;
@@ -943,7 +1138,7 @@ SlangResult LanguageServer::queueJSONCall(JSONRPCCall call)
}
else if (call.method == "completionItem/resolve")
{
- CompletionItem args;
+ Slang::LanguageServerProtocol::CompletionItem args;
SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id));
cmd.completionResolveArgs = args;
}
@@ -1105,23 +1300,23 @@ SlangResult LanguageServer::execute()
break;
parseNextMessage();
}
- auto parseTime = platform::PerformanceCounter::getElapsedTimeInSeconds(start);
auto parseEnd = platform::PerformanceCounter::now();
processCommands();
- // Now we can use this time to reparse user's code, report diagnostics, etc.
+
+ // Report diagnostics if it hasn't been updated for a while.
update();
+
auto workTime = platform::PerformanceCounter::getElapsedTimeInSeconds(parseEnd);
if (commands.getCount() > 0 && m_initialized)
{
StringBuilder msgBuilder;
- msgBuilder << "Server processed " << commands.getCount() << " commands, parsed in "
- << String(int(parseTime * 1000)) << "ms, executed in "
+ msgBuilder << "Server processed " << commands.getCount() << " commands, executed in "
<< String(int(workTime * 1000)) << "ms";
logMessage(3, msgBuilder.ProduceString());
}
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ m_connection->getUnderlyingConnection()->waitForResult(1000);
}
return SLANG_OK;
diff --git a/source/slang/slang-language-server.h b/source/slang/slang-language-server.h
index b873576e8..17dfc0f16 100644
--- a/source/slang/slang-language-server.h
+++ b/source/slang/slang-language-server.h
@@ -5,6 +5,7 @@
#include "../compiler-core/slang-json-rpc-connection.h"
#include "slang-workspace-version.h"
+#include "slang-language-server-completion.h"
namespace Slang
{
@@ -75,6 +76,7 @@ private:
public:
bool m_initialized = false;
+ CommitCharacterBehavior m_commitCharacterBehavior = CommitCharacterBehavior::MembersOnly;
RefPtr<JSONRPCConnection> m_connection;
ComPtr<slang::IGlobalSession> m_session;
RefPtr<Workspace> m_workspace;
@@ -113,18 +115,41 @@ private:
void resetDiagnosticUpdateTime();
void publishDiagnostics();
void updatePredefinedMacros(const JSONValue& macros);
- void updateSearchPaths(const JSONValue& macros);
- void updateSearchInWorkspace(const JSONValue& macros);
+ void updateSearchPaths(const JSONValue& value);
+ void updateSearchInWorkspace(const JSONValue& value);
+ void updateCommitCharacters(const JSONValue& value);
void sendConfigRequest();
void registerCapability(const char* methodName);
void logMessage(int type, String message);
+ SlangResult tryGetMacroHoverInfo(
+ WorkspaceVersion* version,
+ DocumentVersion* doc,
+ Index line,
+ Index col,
+ JSONValue responseId);
+ SlangResult tryGotoMacroDefinition(
+ WorkspaceVersion* version,
+ DocumentVersion* doc,
+ Index line,
+ Index col,
+ JSONValue responseId);
+ SlangResult tryGotoFileInclude(
+ WorkspaceVersion* version,
+ DocumentVersion* doc,
+ Index line,
+ JSONValue responseId);
List<Command> commands;
SlangResult queueJSONCall(JSONRPCCall call);
SlangResult runCommand(Command& cmd);
void processCommands();
};
+inline bool _isIdentifierChar(char ch)
+{
+ return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_';
+}
+
SLANG_API SlangResult runLanguageServer();
} // namespace Slang
diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp
index a77478ccb..8c69e2013 100644
--- a/source/slang/slang-lookup.cpp
+++ b/source/slang/slang-lookup.cpp
@@ -2,6 +2,7 @@
#include "slang-lookup.h"
#include "../compiler-core/slang-name.h"
+#include "slang-check-impl.h"
namespace Slang {
@@ -199,27 +200,45 @@ static void _lookUpDirectAndTransparentMembers(
{
ContainerDecl* containerDecl = containerDeclRef.getDecl();
- // Ensure that the lookup dictionary in the container is up to date
- if (!containerDecl->isMemberDictionaryValid())
+
+ if (request.isCompletionRequest())
{
- buildMemberDictionary(containerDecl);
+ // If we are looking up for completion suggestions,
+ // return all the members that are available.
+ for (auto member : containerDecl->members)
+ {
+ if (!DeclPassesLookupMask(member, request.mask))
+ continue;
+ AddToLookupResult(
+ result,
+ CreateLookupResultItem(
+ DeclRef<Decl>(member, containerDeclRef.substitutions), inBreadcrumbs));
+ }
}
+ else
+ {
+ // Ensure that the lookup dictionary in the container is up to date
+ if (!containerDecl->isMemberDictionaryValid())
+ {
+ buildMemberDictionary(containerDecl);
+ }
- // Look up the declarations with the chosen name in the container.
- Decl* firstDecl = nullptr;
- containerDecl->memberDictionary.TryGetValue(name, firstDecl);
+ // Look up the declarations with the chosen name in the container.
+ Decl* firstDecl = nullptr;
+ containerDecl->memberDictionary.TryGetValue(name, firstDecl);
- // Now iterate over those declarations (if any) and see if
- // we find any that meet our filtering criteria.
- // For example, we might be filtering so that we only consider
- // type declarations.
- for (auto m = firstDecl; m; m = m->nextInContainerWithSameName)
- {
- if (!DeclPassesLookupMask(m, request.mask))
- continue;
+ // Now iterate over those declarations (if any) and see if
+ // we find any that meet our filtering criteria.
+ // For example, we might be filtering so that we only consider
+ // type declarations.
+ for (auto m = firstDecl; m; m = m->nextInContainerWithSameName)
+ {
+ if (!DeclPassesLookupMask(m, request.mask))
+ continue;
- // The declaration passed the test, so add it!
- AddToLookupResult(result, CreateLookupResultItem(DeclRef<Decl>(m, containerDeclRef.substitutions), inBreadcrumbs));
+ // The declaration passed the test, so add it!
+ AddToLookupResult(result, CreateLookupResultItem(DeclRef<Decl>(m, containerDeclRef.substitutions), inBreadcrumbs));
+ }
}
// TODO(tfoley): should we look up in the transparent decls
@@ -937,9 +956,11 @@ static void _lookUpInScopes(
if (result.isValid())
{
- // If it's overloaded or the decl we have is of an overloadable type then we just keep going
+ // If it's overloaded or the decl we have is of an overloadable type, or if we are
+ // looking up for completion suggestions then we just keep going
if (result.isOverloaded() ||
- _isDeclOverloadable(result.item.declRef.getDecl()))
+ _isDeclOverloadable(result.item.declRef.getDecl()) ||
+ ((int32_t)request.options & (int32_t)LookupOptions::Completion) != 0)
{
continue;
}
@@ -964,21 +985,14 @@ LookupResult lookUp(
request.semantics = semantics;
request.scope = scope;
request.mask = mask;
-
+ if (semantics && semantics->getSession() &&
+ name == semantics->getSession()->getCompletionRequestTokenName())
+ request.options = (LookupOptions)((int)request.options | (int)LookupOptions::Completion);
LookupResult result;
_lookUpInScopes(astBuilder, name, request, result);
return result;
}
-void lookUpMemberImpl(
- ASTBuilder* astBuilder,
- SemanticsVisitor* semantics,
- Name* name,
- Type* type,
- LookupResult& ioResult,
- BreadcrumbInfo* inBreadcrumbs,
- LookupMask mask);
-
LookupResult lookUpMember(
ASTBuilder* astBuilder,
SemanticsVisitor* semantics,
@@ -991,6 +1005,9 @@ LookupResult lookUpMember(
request.semantics = semantics;
request.mask = mask;
request.options = options;
+ if (semantics && semantics->getSession() &&
+ name == semantics->getSession()->getCompletionRequestTokenName())
+ request.options = (LookupOptions)((int)request.options | (int)LookupOptions::Completion);
LookupResult result;
_lookUpMembersInType(astBuilder, name, type, request, result, nullptr);
diff --git a/source/slang/slang-mangle.cpp b/source/slang/slang-mangle.cpp
index ca6dcecd7..c230776db 100644
--- a/source/slang/slang-mangle.cpp
+++ b/source/slang/slang-mangle.cpp
@@ -37,11 +37,8 @@ namespace Slang
context->sb.append(value);
}
- void emitName(
- ManglingContext* context,
- Name* name)
+ void emitNameImpl(ManglingContext* context, UnownedStringSlice str)
{
- String str = getText(name);
Index length = str.getLength();
// If the name consists of only traditional "identifer characters"
@@ -50,10 +47,14 @@ namespace Slang
bool allAllowed = true;
for (auto c : str)
{
- if (('a' <= c) && (c <= 'z')) continue;
- if (('A' <= c) && (c <= 'Z')) continue;
- if (('0' <= c) && (c <= '9')) continue;
- if (c == '_') continue;
+ if (('a' <= c) && (c <= 'z'))
+ continue;
+ if (('A' <= c) && (c <= 'Z'))
+ continue;
+ if (('0' <= c) && (c <= '9'))
+ continue;
+ if (c == '_')
+ continue;
allAllowed = false;
break;
@@ -84,9 +85,8 @@ namespace Slang
//
for (auto c : str)
{
- if (('a' <= c) && (c <= 'z')
- || ('A' <= c) && (c <= 'Z')
- || ('0' <= c) && (c <= '9'))
+ if (('a' <= c) && (c <= 'z') || ('A' <= c) && (c <= 'Z') ||
+ ('0' <= c) && (c <= '9'))
{
encoded.append(c);
}
@@ -119,6 +119,14 @@ namespace Slang
// target, rather than adding complexity here.
}
+ void emitName(
+ ManglingContext* context,
+ Name* name)
+ {
+ String str = getText(name);
+ emitNameImpl(context, str.getUnownedSlice());
+ }
+
void emitVal(
ManglingContext* context,
Val* val);
@@ -593,6 +601,13 @@ namespace Slang
return context.sb.ProduceString();
}
+ String getMangledNameFromNameString(const UnownedStringSlice& name)
+ {
+ ManglingContext context(nullptr);
+ emitNameImpl(&context, name);
+ return context.sb.ProduceString();
+ }
+
String getHashedName(const UnownedStringSlice& mangledName)
{
HashCode64 hash = getStableHashCode64(mangledName.begin(), mangledName.getLength());
diff --git a/source/slang/slang-mangle.h b/source/slang/slang-mangle.h
index e579ebfda..723b7250e 100644
--- a/source/slang/slang-mangle.h
+++ b/source/slang/slang-mangle.h
@@ -13,6 +13,7 @@ namespace Slang
String getMangledName(ASTBuilder* astBuilder, Decl* decl);
String getMangledName(ASTBuilder* astBuilder, DeclRef<Decl> const & declRef);
String getMangledName(ASTBuilder* astBuilder, DeclRefBase const & declRef);
+ String getMangledNameFromNameString(const UnownedStringSlice& name);
String getHashedName(const UnownedStringSlice& mangledName);
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp
index c8a990ab1..f6d85ade9 100644
--- a/source/slang/slang-parser.cpp
+++ b/source/slang/slang-parser.cpp
@@ -854,6 +854,8 @@ namespace Slang
{
parser->ReadToken(TokenType::Scope);
}
+ if (parser->LookAheadToken(TokenType::CompletionRequest))
+ return parser->ReadToken();
const Token firstIdentifier = parser->ReadToken(TokenType::Identifier);
if (initialTokenType != TokenType::Scope && parser->tokenReader.peekTokenType() != TokenType::Scope)
@@ -942,6 +944,7 @@ namespace Slang
break;
parser->ReadToken(TokenType::Comma);
+
}
if (hasDoubleBracket)
@@ -1133,6 +1136,7 @@ namespace Slang
auto decl = parser->astBuilder->create<ImportDecl>();
decl->scope = parser->currentScope;
+ decl->startLoc = parser->tokenReader.peekLoc();
if (peekTokenType(parser) == TokenType::StringLiteral)
{
@@ -1162,7 +1166,7 @@ namespace Slang
decl->moduleNameAndLoc = moduleNameAndLoc;
}
-
+ decl->endLoc = parser->tokenReader.peekLoc();
parser->ReadToken(TokenType::Semicolon);
return decl;
@@ -1538,7 +1542,10 @@ namespace Slang
_parseOptSemantics(parser, decl);
decl->body = parseOptBody(parser);
-
+ if (auto block = as<BlockStmt>(decl->body))
+ {
+ decl->closingSourceLoc = block->closingSourceLoc;
+ }
parser->PopScope();
return decl;
@@ -2686,6 +2693,13 @@ namespace Slang
semantic->name = parser->ReadToken(TokenType::Identifier);
return semantic;
}
+ else if (parser->LookAheadToken(TokenType::CompletionRequest))
+ {
+ HLSLSimpleSemantic* semantic = parser->astBuilder->create<HLSLSimpleSemantic>();
+ parser->FillPosition(semantic);
+ semantic->name = parser->ReadToken();
+ return semantic;
+ }
else
{
// expect an identifier, just to produce an error message
@@ -2966,6 +2980,9 @@ namespace Slang
{
InterfaceDecl* decl = parser->astBuilder->create<InterfaceDecl>();
parser->FillPosition(decl);
+
+ AdvanceIf(parser, TokenType::CompletionRequest);
+
decl->nameAndLoc = NameLoc(parser->ReadToken(TokenType::Identifier));
parseOptionalInheritanceClause(parser, decl);
@@ -3147,6 +3164,9 @@ namespace Slang
decl->body = parseOptBody(parser);
+ if (auto block = as<BlockStmt>(decl->body))
+ decl->closingSourceLoc = block->closingSourceLoc;
+
parser->PopScope();
return decl;
}
@@ -3469,6 +3489,8 @@ namespace Slang
decl->returnType = parser->ParseTypeExp();
}
decl->body = parseOptBody(parser);
+ if (auto blockStmt = as<BlockStmt>(decl->body))
+ decl->closingSourceLoc = blockStmt->closingSourceLoc;
parser->PopScope();
return decl;
});
@@ -3914,9 +3936,11 @@ namespace Slang
ParseSquareBracketAttributes(this, &modifierLink);
}
+ // Skip completion request token to prevent producing a type named completion request.
+ AdvanceIf(this, TokenType::CompletionRequest);
+
// TODO: support `struct` declaration without tag
rs->nameAndLoc = expectIdentifier(this);
-
return parseOptGenericDecl(this, [&](GenericDecl*)
{
// We allow for an inheritance clause on a `struct`
@@ -3931,6 +3955,9 @@ namespace Slang
{
ClassDecl* rs = astBuilder->create<ClassDecl>();
ReadToken("class");
+
+ AdvanceIf(this, TokenType::CompletionRequest);
+
FillPosition(rs);
rs->nameAndLoc = expectIdentifier(this);
@@ -3969,6 +3996,8 @@ namespace Slang
//
AdvanceIf(parser, "class");
+ AdvanceIf(parser, TokenType::CompletionRequest);
+
parser->FillPosition(decl);
decl->nameAndLoc = expectIdentifier(parser);
@@ -5586,7 +5615,7 @@ namespace Slang
return constExpr;
}
-
+ case TokenType::CompletionRequest:
case TokenType::Identifier:
{
// We will perform name lookup here so that we can find syntax
@@ -5608,7 +5637,7 @@ namespace Slang
varExpr->scope = parser->currentScope;
parser->FillPosition(varExpr);
- auto nameAndLoc = expectIdentifier(parser);
+ auto nameAndLoc = NameLoc(parser->ReadToken());
varExpr->name = nameAndLoc.name;
if(peekTokenType(parser) == TokenType::OpLess)
@@ -5725,7 +5754,7 @@ namespace Slang
parser->ReadToken(TokenType::Dot);
parser->FillPosition(memberExpr);
memberExpr->name = expectIdentifier(parser).name;
-
+
if (peekTokenType(parser) == TokenType::OpLess)
expr = maybeParseGenericApp(parser, memberExpr);
else
diff --git a/source/slang/slang-preprocessor.cpp b/source/slang/slang-preprocessor.cpp
index ea0b06a31..0167b4a58 100644
--- a/source/slang/slang-preprocessor.cpp
+++ b/source/slang/slang-preprocessor.cpp
@@ -556,7 +556,6 @@ private:
// and a macro *invocation*, similar to how we distinguish a function definition
// from a call to that function.
-
/// A definition of a macro
struct MacroDefinition
{
@@ -721,6 +720,10 @@ struct MacroInvocation : InputStream
Index getArgCount() { return m_args.getCount(); }
+ SourceLoc getInvocationLoc() { return m_macroInvocationLoc; }
+
+ MacroDefinition* getMacroDefinition() { return m_macro; }
+
private:
// Macro invocations are created as part of applying macro expansion
// to a stream, so the `ExpansionInputStream` type takes responsibility
@@ -944,6 +947,8 @@ struct InputFile
ExpansionInputStream* getExpansionStream() { return m_expansionStream; }
+ bool isIncludedFile() { return m_parent != nullptr; }
+
private:
friend struct Preprocessor;
@@ -1010,6 +1015,9 @@ struct Preprocessor
/// Stores the initiating macro source location.
SourceLoc initiatingMacroSourceLoc;
+ /// Stores macro definition and invocation info for language server.
+ PreprocessorContentAssistInfo* contentAssistInfo = nullptr;
+
NamePool* getNamePool() { return namePool; }
SourceManager* getSourceManager() { return sourceManager; }
@@ -1020,7 +1028,51 @@ struct Preprocessor
void popInputFile();
};
+static void reportMacroDefinitionForContentAssist(Preprocessor* preprocessor, MacroDefinition* def)
+{
+ if (!preprocessor->contentAssistInfo)
+ return;
+
+ MacroDefinitionContentAssistInfo info;
+ info.name = def->getName();
+ info.loc = def->getLoc();
+ info.tokenList = def->tokens.m_tokens;
+ for (auto param : def->params)
+ {
+ MacroDefinitionContentAssistInfo::Param p;
+ p.isVariadic = param.isVariadic;
+ p.name = param.nameLoc.name;
+ info.params.add(p);
+ }
+ preprocessor->contentAssistInfo->macroDefinitions.add(info);
+}
+
+static void reportMacroInvocationForContentAssist(
+ Preprocessor* preprocessor, MacroInvocation* invocation)
+{
+ if (!preprocessor->contentAssistInfo)
+ return;
+ if (preprocessor->m_currentInputFile && preprocessor->m_currentInputFile->isIncludedFile())
+ return;
+ MacroInvocationContentAssistInfo info;
+ info.name = invocation->getMacroDefinition()->getName();
+ info.loc = invocation->getInvocationLoc();
+
+ preprocessor->contentAssistInfo->macroInvocations.add(info);
+}
+static void reportIncludeFileForContentAssist(Preprocessor* preprocessor, Token token, String path)
+{
+ if (!preprocessor->contentAssistInfo)
+ return;
+ if (preprocessor->m_currentInputFile && preprocessor->m_currentInputFile->isIncludedFile())
+ return;
+ FileIncludeContentAssistInfo info;
+ info.loc = token.loc;
+ info.length = (int)token.getContentLength();
+ info.path = path;
+ preprocessor->contentAssistInfo->fileIncludes.add(info);
+}
//static Token AdvanceToken(Preprocessor* preprocessor);
@@ -1127,6 +1179,8 @@ void MacroInvocation::prime(MacroInvocation* nextBusyMacroInvocation)
_initCurrentOpStream();
m_lookaheadToken = _readTokenImpl();
+
+ reportMacroInvocationForContentAssist(m_preprocessor, this);
}
void ExpansionInputStream::_pushMacroInvocation(
@@ -2969,6 +3023,8 @@ static void HandleIncludeDirective(PreprocessorDirectiveContext* context)
return;
}
+ reportIncludeFileForContentAssist(context->m_preprocessor, pathToken, filePathInfo.foundPath);
+
// Do all checking related to the end of this directive before we push a new stream,
// just to avoid complications where that check would need to deal with
// a switch of input stream
@@ -3318,6 +3374,8 @@ static void HandleDefineDirective(PreprocessorDirectiveContext* context)
}
_parseMacroOps(context->m_preprocessor, macro, mapParamNameToIndex);
+
+ reportMacroDefinitionForContentAssist(context->m_preprocessor, macro);
}
// Handle a `#undef` directive
@@ -3848,6 +3906,7 @@ static void DefineMacro(
}
preprocessor->globalEnv.macros[keyName] = macro;
+ reportMacroDefinitionForContentAssist(preprocessor, macro);
}
// read the entire input into tokens
@@ -3945,6 +4004,10 @@ TokenList preprocessSource(
desc.namePool = linkage->getNamePool();
desc.sourceManager = linkage->getSourceManager();
+ if (linkage->isInLanguageServer())
+ {
+ desc.contentAssistInfo = &linkage->contentAssistInfo.preprocessorInfo;
+ }
return preprocessSource(file, desc);
}
@@ -3963,6 +4026,7 @@ TokenList preprocessSource(
preprocessor.endOfFileToken.type = TokenType::EndOfFile;
preprocessor.endOfFileToken.flags = TokenFlag::AtStartOfLine;
+ preprocessor.contentAssistInfo = desc.contentAssistInfo;
// Add builtin macros
{
diff --git a/source/slang/slang-preprocessor.h b/source/slang/slang-preprocessor.h
index 5f66be405..4d7721d31 100644
--- a/source/slang/slang-preprocessor.h
+++ b/source/slang/slang-preprocessor.h
@@ -11,6 +11,7 @@ namespace Slang {
class DiagnosticSink;
class Linkage;
+struct PreprocessorContentAssistInfo;
namespace preprocessor
{
@@ -53,6 +54,9 @@ struct PreprocessorDesc
/// Optional: handler for callbacks invoked during preprocessing
PreprocessorHandler* handler = nullptr;
+
+ /// Optional: additional information for code assist.
+ PreprocessorContentAssistInfo* contentAssistInfo = nullptr;
};
/// Take a source `file` and preprocess it into a list of tokens.
diff --git a/source/slang/slang-workspace-version.cpp b/source/slang/slang-workspace-version.cpp
index 781d8a3bc..0930ca0de 100644
--- a/source/slang/slang-workspace-version.cpp
+++ b/source/slang/slang-workspace-version.cpp
@@ -3,6 +3,8 @@
#include "../core/slang-file-system.h"
#include "../compiler-core/slang-lexer.h"
#include "slang-serialize-container.h"
+#include "slang-mangle.h"
+#include "slang-check-impl.h"
namespace Slang
{
@@ -30,10 +32,9 @@ DocumentVersion* Workspace::openDoc(String path, String text)
{
RefPtr<DocumentVersion> doc = new DocumentVersion();
doc->setText(text.getUnownedSlice());
- doc->setURI(URI::fromLocalFilePath(path.getUnownedSlice()));
+ doc->setPath(path);
openedDocuments[path] = doc;
workspaceSearchPaths.Add(Path::getParentDirectory(path));
- moduleCache.invalidate(path);
invalidate();
return doc.Ptr();
}
@@ -51,15 +52,18 @@ void Workspace::changeDoc(const String& path, LanguageServerProtocol::Range rang
auto originalText = doc->getText().getUnownedSlice();
StringBuilder newText;
newText << originalText.head(startOffset) << text << originalText.tail(endOffset);
- doc->setText(newText.ProduceString());
+ changeDoc(doc.Ptr(), newText.ProduceString());
}
- moduleCache.invalidate(path);
+}
+
+void Workspace::changeDoc(DocumentVersion* doc, const String& newText)
+{
+ doc->setText(newText);
invalidate();
}
void Workspace::closeDoc(const String& path)
{
- moduleCache.invalidate(path);
openedDocuments.Remove(path);
invalidate();
}
@@ -269,7 +273,6 @@ RefPtr<WorkspaceVersion> Workspace::createWorkspaceVersion()
slang::SessionDesc desc = {};
desc.fileSystem = this;
desc.targetCount = 1;
- desc.flags = slang::kSessionFlag_LanguageServer;
slang::TargetDesc targetDesc = {};
targetDesc.profile = slangGlobalSession->findProfile("sm_6_6");
desc.targets = &targetDesc;
@@ -308,13 +311,7 @@ RefPtr<WorkspaceVersion> Workspace::createWorkspaceVersion()
ComPtr<slang::ISession> session;
slangGlobalSession->createSession(desc, session.writeRef());
version->linkage = static_cast<Linkage*>(session.get());
- // TODO(yong): module cache does improves performance by 30%. However there are some issues
- // that prevents the deserialization to resolve the imported decls from the correct module.
- // This doesn't lead to crash, but may cause problems. We can enable this when the issues
- // are fixed.
-#if 0
- version->linkage->setModuleCache(&moduleCache);
-#endif
+ version->linkage->contentAssistInfo.checkingMode = ContentAssistCheckingMode::General;
return version;
}
@@ -337,7 +334,13 @@ WorkspaceVersion* Workspace::getCurrentVersion()
currentVersion = createWorkspaceVersion();
return currentVersion.Ptr();
}
-
+WorkspaceVersion* Workspace::createVersionForCompletion()
+{
+ currentCompletionVersion = createWorkspaceVersion();
+ currentCompletionVersion->linkage->contentAssistInfo.checkingMode =
+ ContentAssistCheckingMode::Completion;
+ return currentCompletionVersion.Ptr();
+}
void* Workspace::getInterface(const Guid& uuid)
{
if (uuid == ISlangUnknown::getTypeGuid() || uuid == ISlangFileSystem::getTypeGuid())
@@ -416,6 +419,21 @@ static bool _isIdentifierChar(char ch)
return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch == '_';
}
+UnownedStringSlice DocumentVersion::peekIdentifier(Index& offset)
+{
+ Index start = offset;
+ Index end = offset;
+ while (start >= 0 && _isIdentifierChar(text[start]))
+ start--;
+ while (end < text.getLength() && _isIdentifierChar(text[end]))
+ end++;
+ offset = start + 1;
+ if (end > offset)
+ return text.getUnownedSlice().subString(start + 1, end - start - 1);
+ return UnownedStringSlice("");
+}
+
+
int DocumentVersion::getTokenLength(Index line, Index col)
{
auto offset = getOffset(line, col);
@@ -454,8 +472,19 @@ Module* WorkspaceVersion::getOrLoadModule(String path)
return nullptr;
ComPtr<ISlangBlob> diagnosticBlob;
RefPtr<StringBlob> sourceBlob = new StringBlob((*doc)->getText());
+ auto moduleName = getMangledNameFromNameString(path.getUnownedSlice());
+ linkage->contentAssistInfo.primaryModuleName = linkage->getNamePool()->getName(moduleName);
+ linkage->contentAssistInfo.primaryModulePath = path;
+ // Note:
+ // The module at `path` may have already been loaded into the linkage previously
+ // due to an `import`. However that module won't get fully checked in when the checker
+ // is in language server mode to speed things up.
+ // Therefore, we always call `loadModuleFromSource` to load a fresh module instead of
+ // trying to reuse the existing one through `findOrImportModule`, this will result in
+ // redundant parsing and storage, but it saves us from the hassle of handling
+ // incremental/lazy checking on a previously loaded module.
auto parsedModule = linkage->loadModuleFromSource(
- Path::getFileNameWithoutExt(path).getBuffer(),
+ moduleName.getBuffer(),
path.getBuffer(),
sourceBlob.Ptr(),
diagnosticBlob.writeRef());
@@ -474,63 +503,22 @@ Module* WorkspaceVersion::getOrLoadModule(String path)
return static_cast<Module*>(parsedModule);
}
-RefPtr<Module> SerializedModuleCache::tryLoadModule(
- Linkage* linkage, String filePath)
+MacroDefinitionContentAssistInfo* WorkspaceVersion::tryGetMacroDefinition(UnownedStringSlice name)
{
- Path::getCanonical(filePath, filePath);
- if (List<uint8_t>* rawData = serializedModules.TryGetValue(filePath))
+ if (macroDefinitions.Count() == 0)
{
- RefPtr<MemoryStreamBase> memStream =
- new MemoryStreamBase(FileAccess::Read, rawData->getBuffer(), rawData->getCount());
- RiffContainer riffContainer;
- RiffUtil::read(memStream.Ptr(), riffContainer);
- SerialContainerData outData;
- SerialContainerUtil::ReadOptions options;
- options.linkage = linkage;
- options.namePool = linkage->getNamePool();
- options.session = linkage->getSessionImpl();
- options.sharedASTBuilder = linkage->getASTBuilder()->getSharedASTBuilder();
- options.astBuilder = linkage->getASTBuilder();
- DiagnosticSink sink(linkage->getSourceManager(), Lexer::sourceLocationLexer);
- options.sink = &sink;
- options.sourceManager = linkage->getSourceManager();
- SLANG_RETURN_NULL_ON_FAIL(SerialContainerUtil::read(&riffContainer, options, outData));
- if (outData.modules.getCount() == 1)
+ // build dictionary.
+ for (auto& def : linkage->contentAssistInfo.preprocessorInfo.macroDefinitions)
{
- RefPtr<Module> module = new Module(linkage, linkage->getASTBuilder());
- auto moduleDecl = as<ModuleDecl>(outData.modules[0].astRootNode);
- if (moduleDecl)
- {
- moduleDecl->module = module.Ptr();
- module->setModuleDecl(moduleDecl);
- return module;
- }
+ macroDefinitions[def.name] = &def;
}
}
- return nullptr;
-}
-
-void SerializedModuleCache::storeModule(
- Linkage* linkage, String filePath, RefPtr<Module> module)
-{
- Path::getCanonical(filePath, filePath);
- RiffContainer container;
- SerialContainerUtil::WriteOptions options;
- options.sourceManager = linkage->getSourceManager();
- options.compressionType = SerialCompressionType::None;
- options.optionFlags = SerialOptionFlag::SourceLocation | SerialOptionFlag::ASTModule;
- SerialContainerData data;
- SerialContainerData::Module moduleData;
- moduleData.astBuilder = linkage->getASTBuilder();
- moduleData.astRootNode = module->getModuleDecl();
- moduleData.irModule = nullptr;
- data.modules.add(moduleData);
- SerialContainerUtil::write(data, options, &container);
- RefPtr<OwnedMemoryStream> memStream = new OwnedMemoryStream(FileAccess::Write);
- RiffUtil::write(&container, memStream);
- List<uint8_t> rawData;
- memStream->swapContents(rawData);
- serializedModules[filePath] = _Move(rawData);
+ MacroDefinitionContentAssistInfo* result = nullptr;
+ auto namePtr = linkage->getNamePool()->tryGetName(name);
+ if (!namePtr)
+ return nullptr;
+ macroDefinitions.TryGetValue(namePtr, result);
+ return result;
}
} // namespace Slang
diff --git a/source/slang/slang-workspace-version.h b/source/slang/slang-workspace-version.h
index dea914bb5..38abb6ef2 100644
--- a/source/slang/slang-workspace-version.h
+++ b/source/slang/slang-workspace-version.h
@@ -17,15 +17,18 @@ namespace Slang
{
private:
URI uri;
+ String path;
String text;
List<UnownedStringSlice> lines;
List<List<Index>> utf16CharStarts;
public:
- void setURI(URI newURI)
+ void setPath(String filePath)
{
- uri = newURI;
+ path = filePath;
+ uri = URI::fromLocalFilePath(path.getUnownedSlice());
}
URI getURI() { return uri; }
+ String getPath() { return path; }
const String& getText() { return text; }
void setText(const String& newText);
@@ -39,6 +42,14 @@ namespace Slang
// Get starting offset of line.
Index getLineStart(UnownedStringSlice line) { return line.begin() - text.begin(); }
+ UnownedStringSlice peekIdentifier(Index line, Index col, Index& offset)
+ {
+ offset = getOffset(line, col);
+ return peekIdentifier(offset);
+ }
+
+ UnownedStringSlice peekIdentifier(Index& offset);
+
// Get offset from 1-based, utf-8 encoding location.
Index getOffset(Index lineIndex, Index colIndex)
{
@@ -95,33 +106,20 @@ namespace Slang
String originalOutput;
};
-
- class SerializedModuleCache
- : public RefObject
- , public IModuleCache
- {
- public:
- Dictionary<String, List<uint8_t>> serializedModules;
-
- void invalidate(const String& path) { serializedModules.Remove(path); }
- virtual RefPtr<Module> tryLoadModule(Linkage* linkage, String filePath) override;
- virtual void storeModule(Linkage* linkage, String filePath, RefPtr<Module> module) override;
- };
-
class WorkspaceVersion : public RefObject
{
private:
Dictionary<String, Module*> modules;
Dictionary<ModuleDecl*, RefPtr<ASTMarkup>> markupASTs;
+ Dictionary<Name*, MacroDefinitionContentAssistInfo*> macroDefinitions;
void parseDiagnostics(String compilerOutput);
public:
Workspace* workspace;
RefPtr<Linkage> linkage;
Dictionary<String, DocumentDiagnostics> diagnostics;
- List<Decl*> currentCompletionItems;
ASTMarkup* getOrCreateMarkupAST(ModuleDecl* module);
-
Module* getOrLoadModule(String path);
+ MacroDefinitionContentAssistInfo* tryGetMacroDefinition(UnownedStringSlice name);
};
struct OwnedPreprocessorMacroDefinition
@@ -135,19 +133,21 @@ namespace Slang
{
private:
RefPtr<WorkspaceVersion> currentVersion;
+ RefPtr<WorkspaceVersion> currentCompletionVersion;
RefPtr<WorkspaceVersion> createWorkspaceVersion();
public:
List<String> rootDirectories;
List<String> additionalSearchPaths;
OrderedHashSet<String> workspaceSearchPaths;
List<OwnedPreprocessorMacroDefinition> predefinedMacros;
- SerializedModuleCache moduleCache;
bool searchInWorkspace = true;
slang::IGlobalSession* slangGlobalSession;
Dictionary<String, RefPtr<DocumentVersion>> openedDocuments;
DocumentVersion* openDoc(String path, String text);
void changeDoc(const String& path, LanguageServerProtocol::Range range, const String& text);
+ void changeDoc(DocumentVersion* doc, const String& newText);
+
void closeDoc(const String& path);
// Update predefined macro settings. Returns true if the new settings are different from existing ones.
@@ -158,7 +158,8 @@ namespace Slang
void init(List<URI> rootDirURI, slang::IGlobalSession* globalSession);
void invalidate();
WorkspaceVersion* getCurrentVersion();
-
+ WorkspaceVersion* getCurrentCompletionVersion() { return currentCompletionVersion.Ptr(); }
+ WorkspaceVersion* createVersionForCompletion();
public:
// Inherited via ISlangFileSystem
SLANG_COM_OBJECT_IUNKNOWN_ALL
diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp
index 02b1efedf..13ef0cd81 100644
--- a/source/slang/slang.cpp
+++ b/source/slang/slang.cpp
@@ -133,6 +133,7 @@ void Session::init()
// Initialize name pool
getNamePool()->setRootNamePool(getRootNamePool());
+ m_completionTokenName = getNamePool()->getName("#?");
m_sharedLibraryLoader = DefaultSharedLibraryLoader::getSingleton();
@@ -933,7 +934,7 @@ SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModule(
{
DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer);
- if (m_flag & slang::kSessionFlag_LanguageServer)
+ if (isInLanguageServer())
{
sink.setFlags(DiagnosticSink::Flag::HumaneLoc | DiagnosticSink::Flag::LanguageServer);
}
@@ -962,7 +963,7 @@ SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModuleFromSource(
slang::IBlob** outDiagnostics)
{
DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer);
- if (m_flag & slang::kSessionFlag_LanguageServer)
+ if (isInLanguageServer())
{
sink.setFlags(DiagnosticSink::Flag::HumaneLoc | DiagnosticSink::Flag::LanguageServer);
}
@@ -2611,11 +2612,6 @@ void Linkage::loadParsedModule(
}
}
loadedModulesList.add(loadedModule);
-
- if (m_moduleCache)
- {
- m_moduleCache->storeModule(this, pathInfo.foundPath, loadedModule);
- }
}
Module* Linkage::loadModule(String const& name)
@@ -2638,7 +2634,7 @@ void Linkage::_diagnoseErrorInImportedModule(
{
sink->diagnose(info->importLoc, Diagnostics::errorInImportedModule, info->name);
}
- if ((m_flag & slang::kSessionFlag_LanguageServer) == 0)
+ if (!isInLanguageServer())
{
sink->diagnose(SourceLoc(), Diagnostics::complationCeased);
}
@@ -2799,19 +2795,6 @@ RefPtr<Module> Linkage::findOrImportModule(
if (mapPathToLoadedModule.TryGetValue(filePathInfo.getMostUniqueIdentity(), loadedModule))
return loadedModule;
- // Is this module in user provided cache?
- // (yong): module cache is intended to speed up language server reparsing.
- // currently it is *not* enabled in language server.
- if (m_moduleCache)
- {
- loadedModule = m_moduleCache->tryLoadModule(this, filePathInfo.foundPath);
- if (loadedModule)
- {
- mapPathToLoadedModule[filePathInfo.getMostUniqueIdentity()] = loadedModule;
- return loadedModule;
- }
- }
-
// Try to load it
ComPtr<ISlangBlob> fileContents;
if(SLANG_FAILED(includeSystem.loadFile(filePathInfo, fileContents)))