diff options
Diffstat (limited to 'source/slang/slang-language-server.cpp')
| -rw-r--r-- | source/slang/slang-language-server.cpp | 805 |
1 files changed, 409 insertions, 396 deletions
diff --git a/source/slang/slang-language-server.cpp b/source/slang/slang-language-server.cpp index d81e7dd8b..9f7329e2c 100644 --- a/source/slang/slang-language-server.cpp +++ b/source/slang/slang-language-server.cpp @@ -12,61 +12,29 @@ #include "../core/slang-secure-crt.h" #include "../core/slang-range.h" #include "../../slang-com-helper.h" +#include "../compiler-core/slang-json-native.h" #include "../compiler-core/slang-json-rpc-connection.h" #include "../compiler-core/slang-language-server-protocol.h" #include "slang-language-server.h" #include "slang-workspace-version.h" #include "slang-language-server-ast-lookup.h" -#include "slang-language-server-collect-member.h" +#include "slang-language-server-completion.h" #include "slang-language-server-semantic-tokens.h" #include "slang-ast-print.h" #include "slang-doc-markdown-writer.h" +#include "../../tools/platform/performance-counter.h" namespace Slang { using namespace LanguageServerProtocol; -class LanguageServer +ArrayView<const char*> getCommitChars() { -public: - RefPtr<JSONRPCConnection> m_connection; - ComPtr<slang::IGlobalSession> m_session; - RefPtr<Workspace> m_workspace; - Dictionary<String, String> m_lastPublishedDiagnostics; - time_t m_lastDiagnosticUpdateTime = 0; - - bool m_quit = false; - List<LanguageServerProtocol::WorkspaceFolder> m_workspaceFolders; - - SlangResult init(const LanguageServerProtocol::InitializeParams& args); - SlangResult execute(); - void update(); - SlangResult didOpenTextDocument(const LanguageServerProtocol::DidOpenTextDocumentParams& args); - SlangResult didCloseTextDocument( - const LanguageServerProtocol::DidCloseTextDocumentParams& args); - SlangResult didChangeTextDocument( - const LanguageServerProtocol::DidChangeTextDocumentParams& args); - SlangResult hover(const LanguageServerProtocol::HoverParams& args, const JSONValue& responseId); - SlangResult gotoDefinition(const LanguageServerProtocol::DefinitionParams& args, const JSONValue& responseId); - SlangResult completion( - const LanguageServerProtocol::CompletionParams& args, const JSONValue& responseId); - SlangResult completionResolve( - const LanguageServerProtocol::CompletionItem& args, const JSONValue& responseId); - SlangResult semanticTokens( - const LanguageServerProtocol::SemanticTokensParams& args, const JSONValue& responseId); - SlangResult signatureHelp( - const LanguageServerProtocol::SignatureHelpParams& args, const JSONValue& responseId); - - List<LanguageServerProtocol::CompletionItem> collectMembers( - WorkspaceVersion* wsVersion, Module* module, Expr* baseExpr); - -private: - SlangResult _executeSingle(); - slang::IGlobalSession* getOrCreateGlobalSession(); - void resetDiagnosticUpdateTime(); - void publishDiagnostics(); -}; - + static const char* _commitCharsArray[] = {",", ".", ";", ":", "(", ")", "[", "]", + "<", ">", "{", "}", "*", "&", "^", "%", + "!", "-", "=", "+", "|", "/", "?", " "}; + return makeArrayView(_commitCharsArray, SLANG_COUNT_OF(_commitCharsArray)); +} SlangResult LanguageServer::init(const InitializeParams& args) { @@ -105,14 +73,8 @@ String uriToCanonicalPath(const String& uri) return canonnicalPath; } -SlangResult LanguageServer::_executeSingle() +SlangResult LanguageServer::parseNextMessage() { - // If we don't have a message, we can quit for now - if (!m_connection->hasMessage()) - { - return SLANG_OK; - } - const JSONRPCMessageType msgType = m_connection->getMessageType(); switch (msgType) @@ -121,8 +83,6 @@ SlangResult LanguageServer::_executeSingle() { JSONRPCCall call; SLANG_RETURN_ON_FAIL(m_connection->getRPCOrSendError(&call)); - - // Do different things if (call.method == ExitParams::methodName) { m_quit = true; @@ -143,15 +103,14 @@ SlangResult LanguageServer::_executeSingle() InitializeResult result; result.serverInfo.name = "SlangLanguageServer"; result.serverInfo.version = "1.0"; - result.capabilities.positionEncoding = "utf-8"; + result.capabilities.positionEncoding = "utf-16"; result.capabilities.textDocumentSync.openClose = true; - result.capabilities.textDocumentSync.change = (int)TextDocumentSyncKind::Full; + result.capabilities.textDocumentSync.change = (int)TextDocumentSyncKind::Incremental; + result.capabilities.workspace.workspaceFolders.supported = true; + result.capabilities.workspace.workspaceFolders.changeNotifications = false; result.capabilities.hoverProvider = true; result.capabilities.definitionProvider = true; - const char* commitChars[] = {",", ".", ";", ":", "(", ")", "[", "]", - "<", ">", "{", "}", "*", "&", "^", "%", - "!", "-", "=", "+", "|", "/", "?"}; - for (auto ch : commitChars) + for (auto ch : getCommitChars()) result.capabilities.completionProvider.allCommitCharacters.add(ch); result.capabilities.completionProvider.triggerCharacters.add("."); result.capabilities.completionProvider.triggerCharacters.add(":"); @@ -166,84 +125,49 @@ SlangResult LanguageServer::_executeSingle() m_connection->sendResult(&result, call.id); return SLANG_OK; } - else if (call.method == DidOpenTextDocumentParams::methodName) - { - DidOpenTextDocumentParams args; - SLANG_RETURN_ON_FAIL( - m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); - return didOpenTextDocument(args); - } - else if (call.method == DidCloseTextDocumentParams::methodName) - { - DidCloseTextDocumentParams args; - SLANG_RETURN_ON_FAIL( - m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); - return didCloseTextDocument(args); - } - else if (call.method == DidChangeTextDocumentParams::methodName) - { - DidChangeTextDocumentParams args; - SLANG_RETURN_ON_FAIL( - m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); - return didChangeTextDocument(args); - } - else if (call.method == HoverParams::methodName) - { - HoverParams args; - SLANG_RETURN_ON_FAIL( - m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); - return hover(args, call.id); - } - else if (call.method == DefinitionParams::methodName) - { - DefinitionParams args; - SLANG_RETURN_ON_FAIL( - m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); - return gotoDefinition(args, call.id); - } - else if (call.method == CompletionParams::methodName) - { - CompletionParams args; - SLANG_RETURN_ON_FAIL( - m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); - return completion(args, call.id); - } - else if (call.method == SemanticTokensParams::methodName) - { - SemanticTokensParams args; - SLANG_RETURN_ON_FAIL( - m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); - return semanticTokens(args, call.id); - } - else if (call.method == SignatureHelpParams::methodName) - { - SignatureHelpParams args; - SLANG_RETURN_ON_FAIL( - m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); - return signatureHelp(args, call.id); - } - else if (call.method == "completionItem/resolve") - { - CompletionItem args; - SLANG_RETURN_ON_FAIL( - m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); - return completionResolve(args, call.id); - - } else if (call.method == "initialized") { + sendConfigRequest(); + registerCapability("workspace/didChangeConfiguration"); + m_initialized = true; return SLANG_OK; } - else if (call.method.startsWith("$/")) + else { - // Ignore. + queueJSONCall(call); return SLANG_OK; } - else + + } + case JSONRPCMessageType::Result: + { + JSONResultResponse response; + SLANG_RETURN_ON_FAIL(m_connection->getRPCOrSendError(&response)); + auto responseId = (int)m_connection->getContainer()->asInteger(response.id); + switch (responseId) { - return m_connection->sendError(JSONRPC::ErrorCode::MethodNotFound, call.id); + case kConfigResponseId: + if (response.result.getKind() == JSONValue::Kind::Array) + { + auto arr = m_connection->getContainer()->getArray(response.result); + if (arr.getCount() > 0) + { + updatePredefinedMacros(arr[0]); + } + } + break; } + return SLANG_OK; } + case JSONRPCMessageType::Error: + { +#if 0 // Enable for debug only + JSONRPCErrorResponse error; + SLANG_RETURN_ON_FAIL(m_connection->getRPCOrSendError(&error)); +#endif + return SLANG_OK; + } + break; default: { return m_connection->sendError( @@ -268,6 +192,14 @@ String getDeclSignatureString(DeclRef<Decl> declRef, ASTBuilder* astBuilder) ASTPrinter::OptionFlag::ParamNames | ASTPrinter::OptionFlag::NoInternalKeywords | ASTPrinter::OptionFlag::SimplifiedBuiltinType); printer.addDeclSignature(declRef); + if (auto varDecl = as<VarDeclBase>(declRef.getDecl())) + { + auto& sb = printer.getStringBuilder(); + if (auto litExpr = as<LiteralExpr>(varDecl->initExpr)) + { + sb << " = " << litExpr->token.getContent(); + } + } return printer.getString(); } return "unknown"; @@ -332,6 +264,15 @@ SlangResult LanguageServer::hover( const LanguageServerProtocol::HoverParams& args, const JSONValue& responseId) { String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + RefPtr<DocumentVersion> doc; + if (!m_workspace->openedDocuments.TryGetValue(canonicalPath, doc)) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + Index line, col; + doc->zeroBasedUTF16LocToOneBasedUTF8Loc(args.position.line, args.position.character, line, col); + auto version = m_workspace->getCurrentVersion(); Module* parsedModule = version->getOrLoadModule(canonicalPath); if (!parsedModule) @@ -340,12 +281,13 @@ SlangResult LanguageServer::hover( return SLANG_OK; } auto findResult = findASTNodesAt( + doc.Ptr(), version->linkage->getSourceManager(), parsedModule->getModuleDecl(), ASTLookupType::Decl, canonicalPath.getUnownedSlice(), - args.position.line + 1, - args.position.character + 1); + line, + col); if (findResult.getCount() == 0 || findResult[0].path.getCount() == 0) { m_connection->sendResult(NullResponse::get(), responseId); @@ -375,10 +317,18 @@ SlangResult LanguageServer::hover( hover.range.start.line = int(nodeHumaneLoc.line - 1); hover.range.end.line = int(nodeHumaneLoc.line - 1); hover.range.start.character = int(nodeHumaneLoc.column - 1); - if (declRef.getName()) + auto name = declRef.getName(); + if (auto ctorDecl = declRef.as<ConstructorDecl>()) + { + auto parent = ctorDecl.getDecl()->parentDecl; + if (parent) + { + name = parent->getName(); + } + } + if (name) { - hover.range.end.character = - int(nodeHumaneLoc.column + declRef.getName()->text.getLength() - 1); + hover.range.end.character = int(nodeHumaneLoc.column + name->text.getLength() - 1); } } }; @@ -413,6 +363,15 @@ SlangResult LanguageServer::gotoDefinition( const LanguageServerProtocol::DefinitionParams& args, const JSONValue& responseId) { String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + RefPtr<DocumentVersion> doc; + if (!m_workspace->openedDocuments.TryGetValue(canonicalPath, doc)) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + Index line, col; + doc->zeroBasedUTF16LocToOneBasedUTF8Loc(args.position.line, args.position.character, line, col); + auto version = m_workspace->getCurrentVersion(); Module* parsedModule = version->getOrLoadModule(canonicalPath); if (!parsedModule) @@ -421,12 +380,13 @@ SlangResult LanguageServer::gotoDefinition( return SLANG_OK; } auto findResult = findASTNodesAt( + doc.Ptr(), version->linkage->getSourceManager(), parsedModule->getModuleDecl(), ASTLookupType::Decl, canonicalPath.getUnownedSlice(), - args.position.line + 1, - args.position.character + 1); + line, + col); if (findResult.getCount() == 0 || findResult[0].path.getCount() == 0) { m_connection->sendResult(NullResponse::get(), responseId); @@ -444,7 +404,9 @@ SlangResult LanguageServer::gotoDefinition( if (declRefExpr->declRef.getDecl()) { auto location = version->linkage->getSourceManager()->getHumaneLoc( - declRefExpr->declRef.getNameLoc(), SourceLocType::Actual); + declRefExpr->declRef.getNameLoc().isValid() ? declRefExpr->declRef.getNameLoc() + : declRefExpr->declRef.getLoc(), + SourceLocType::Actual); auto name = declRefExpr->declRef.getName(); locations.add(LocationResult{location, name ? (int)name->text.getLength() : 0}); } @@ -485,28 +447,22 @@ SlangResult LanguageServer::gotoDefinition( for (auto loc : locations) { Location result; - result.uri = URI::fromLocalFilePath(loc.loc.pathInfo.foundPath.getUnownedSlice()).uri; - result.range.start.line = int(loc.loc.line - 1); - result.range.start.character = int(loc.loc.column - 1); - result.range.end = result.range.start; - result.range.end.character += loc.length; - results.add(result); + if (File::exists(loc.loc.pathInfo.foundPath)) + { + result.uri = + URI::fromLocalFilePath(loc.loc.pathInfo.foundPath.getUnownedSlice()).uri; + result.range.start.line = int(loc.loc.line - 1); + result.range.start.character = int(loc.loc.column - 1); + result.range.end = result.range.start; + result.range.end.character += loc.length; + results.add(result); + } } m_connection->sendResult(&results, responseId); return SLANG_OK; } } -bool _isIdentifierChar(char ch) -{ - return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_'; -} - -bool _isWhitespaceChar(char ch) -{ - return ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t'; -} - SlangResult LanguageServer::completion( const LanguageServerProtocol::CompletionParams& args, const JSONValue& responseId) { @@ -518,39 +474,16 @@ SlangResult LanguageServer::completion( m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } - - auto cursorOffset = doc->getOffset(args.position.line + 1, args.position.character + 1); + Index utf8Line, utf8Col; + doc->zeroBasedUTF16LocToOneBasedUTF8Loc( + args.position.line, args.position.character, utf8Line, utf8Col); + auto cursorOffset = doc->getOffset(utf8Line, utf8Col); if (cursorOffset == -1 || doc->getText().getLength() == 0) { m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } - // Scan backward until we locate a '.' or ':'. - if (cursorOffset == doc->getText().getLength()) - 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] != ':')) - { - m_connection->sendResult(NullResponse::get(), responseId); - return SLANG_OK; - } - Index line = 0; - Index col = 0; - doc->offsetToLineCol(cursorOffset, line, col); + auto version = m_workspace->getCurrentVersion(); Module* parsedModule = version->getOrLoadModule(canonicalPath); if (!parsedModule) @@ -558,48 +491,26 @@ SlangResult LanguageServer::completion( m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } - auto findResult = findASTNodesAt( - version->linkage->getSourceManager(), - parsedModule->getModuleDecl(), - ASTLookupType::Decl, - canonicalPath.getUnownedSlice(), - line, - col); - if (findResult.getCount() != 1) + + CompletionContext context; + context.server = this; + context.cursorOffset = cursorOffset; + context.version = version; + context.doc = doc.Ptr(); + context.parsedModule = parsedModule; + context.responseId = responseId; + context.canonicalPath = canonicalPath.getUnownedSlice(); + context.line = utf8Line; + context.col = utf8Col; + if (SLANG_SUCCEEDED(context.tryCompleteMember())) { - m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } - if (findResult[0].path.getCount() == 0) + if (SLANG_SUCCEEDED(context.tryCompleteHLSLSemantic())) { - m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } - 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())) - { - baseExpr = staticMemberExpr->baseExpression; - } - 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())) - { - m_connection->sendResult(NullResponse::get(), responseId); - return SLANG_OK; - } - - List<LanguageServerProtocol::CompletionItem> items = collectMembers(version, parsedModule, baseExpr); - m_connection->sendResult(&items, responseId); + m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } @@ -628,6 +539,13 @@ SlangResult LanguageServer::semanticTokens( { String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + RefPtr<DocumentVersion> doc; + if (!m_workspace->openedDocuments.TryGetValue(canonicalPath, doc)) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + auto version = m_workspace->getCurrentVersion(); Module* parsedModule = version->getOrLoadModule(canonicalPath); if (!parsedModule) @@ -636,7 +554,18 @@ SlangResult LanguageServer::semanticTokens( return SLANG_OK; } - auto tokens = getSemanticTokens(version->linkage, parsedModule, canonicalPath.getUnownedSlice()); + auto tokens = getSemanticTokens(version->linkage, parsedModule, canonicalPath.getUnownedSlice(), doc.Ptr()); + for (auto& token : tokens) + { + Index line, col; + doc->oneBasedUTF8LocToZeroBasedUTF16Loc(token.line, token.col, line, col); + Index lineEnd, colEnd; + doc->oneBasedUTF8LocToZeroBasedUTF16Loc( + token.line, token.col + token.length, lineEnd, colEnd); + token.line = (int)line; + token.col = (int)col; + token.length = (int)(colEnd - col); + } SemanticTokens response; response.resultId = ""; response.data = getEncodedTokens(tokens); @@ -648,6 +577,14 @@ SlangResult LanguageServer::signatureHelp( const LanguageServerProtocol::SignatureHelpParams& args, const JSONValue& responseId) { String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + RefPtr<DocumentVersion> doc; + if (!m_workspace->openedDocuments.TryGetValue(canonicalPath, doc)) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + Index line, col; + doc->zeroBasedUTF16LocToOneBasedUTF8Loc(args.position.line, args.position.character, line, col); auto version = m_workspace->getCurrentVersion(); Module* parsedModule = version->getOrLoadModule(canonicalPath); @@ -658,12 +595,13 @@ SlangResult LanguageServer::signatureHelp( } auto findResult = findASTNodesAt( + doc.Ptr(), version->linkage->getSourceManager(), parsedModule->getModuleDecl(), ASTLookupType::Invoke, canonicalPath.getUnownedSlice(), - args.position.line + 1, - args.position.character + 1); + line, + col); if (findResult.getCount() == 0) { @@ -673,6 +611,7 @@ SlangResult LanguageServer::signatureHelp( AppExprBase* appExpr = nullptr; auto& declPath = findResult[0].path; + Loc currentLoc = {args.position.line + 1, args.position.character + 1}; for (Index i = declPath.getCount() - 1; i >= 0; i--) { if (auto expr = as<AppExprBase>(declPath[i])) @@ -681,8 +620,14 @@ SlangResult LanguageServer::signatureHelp( // This allows us to skip the invoke expr nodes for operators/implcit casts. if (expr->argumentDelimeterLocs.getCount()) { - appExpr = expr; - break; + auto start = Loc::fromSourceLoc(version->linkage->getSourceManager(), expr->argumentDelimeterLocs.getFirst()); + auto end = Loc::fromSourceLoc( + version->linkage->getSourceManager(), expr->argumentDelimeterLocs.getLast()); + if (start < currentLoc && currentLoc <= end) + { + appExpr = expr; + break; + } } } } @@ -746,7 +691,18 @@ SlangResult LanguageServer::signatureHelp( }; if (auto declRefExpr = as<DeclRefExpr>(funcExpr)) { - addDeclRef(declRefExpr->declRef); + if (auto aggDecl = as<AggTypeDecl>(declRefExpr->declRef.getDecl())) + { + // Look for initializers + for (auto member : aggDecl->getMembersOfType<ConstructorDecl>()) + { + addDeclRef(DeclRef<Decl>(member, declRefExpr->declRef.substitutions)); + } + } + else + { + addDeclRef(declRefExpr->declRef); + } } else if (auto overloadedExpr = as<OverloadedExpr>(funcExpr)) { @@ -773,171 +729,6 @@ SlangResult LanguageServer::signatureHelp( return SLANG_OK; } - -List<LanguageServerProtocol::CompletionItem> LanguageServer::collectMembers(WorkspaceVersion* version, Module* module, Expr* baseExpr) -{ - List<LanguageServerProtocol::CompletionItem> result; - auto linkage = version->linkage; - Type* type = baseExpr->type.type; - if (auto typeType = as<TypeType>(type)) - { - type = typeType->type; - } - version->currentCompletionItems.clear(); - if (type) - { - if (as<ArithmeticExpressionType>(type)) - { - // 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(); - for (int i = 0; i < elementCount; i++) - { - CompletionItem item; - item.data = 0; - item.detail = typeStr; - item.kind = LanguageServerProtocol::kCompletionItemKindVariable; - item.label = memberNames[i]; - result.add(item); - } - } - else - { - DiagnosticSink sink; - MemberCollectingContext context(linkage, module, &sink); - context.astBuilder = linkage->getASTBuilder(); - collectMembersInType(&context, type); - HashSet<String> deduplicateSet; - for (auto member : context.members) - { - 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); - } - } - - for (auto& item : result) - { - switch (item.kind) - { - case LanguageServerProtocol::kCompletionItemKindMethod: - item.commitCharacters.add("("); - item.commitCharacters.add("["); - item.commitCharacters.add(" "); - break; - default: - item.commitCharacters.add("("); - item.commitCharacters.add(")"); - item.commitCharacters.add("."); - item.commitCharacters.add(";"); - item.commitCharacters.add(":"); - item.commitCharacters.add(","); - item.commitCharacters.add("<"); - item.commitCharacters.add(">"); - item.commitCharacters.add("["); - item.commitCharacters.add("]"); - item.commitCharacters.add("{"); - item.commitCharacters.add("}"); - item.commitCharacters.add("-"); - item.commitCharacters.add("*"); - item.commitCharacters.add("/"); - item.commitCharacters.add("%"); - item.commitCharacters.add("+"); - item.commitCharacters.add("="); - item.commitCharacters.add("&"); - item.commitCharacters.add("|"); - item.commitCharacters.add("!"); - item.commitCharacters.add(" "); - break; - } - } - } - return result; -} - void LanguageServer::publishDiagnostics() { time_t timeNow = 0; @@ -982,28 +773,236 @@ void LanguageServer::publishDiagnostics() } } +void LanguageServer::updatePredefinedMacros(const JSONValue& macros) +{ + if (macros.isValid()) + { + auto container = m_connection->getContainer(); + JSONToNativeConverter converter(container, m_connection->getSink()); + List<String> predefinedMacros; + if (SLANG_SUCCEEDED(converter.convert(macros, &predefinedMacros))) + { + if (m_workspace->updatePredefinedMacros(predefinedMacros)) + { + m_connection->sendCall( + UnownedStringSlice("workspace/semanticTokens/refresh"), JSONValue::makeInt(0)); + } + } + } +} + +void LanguageServer::sendConfigRequest() +{ + ConfigurationParams args; + ConfigurationItem item; + item.section = "slang.predefinedMacros"; + args.items.add(item); + m_connection->sendCall( + ConfigurationParams::methodName, &args, JSONValue::makeInt(kConfigResponseId)); +} + +void LanguageServer::registerCapability(const char* methodName) +{ + RegistrationParams args; + Registration reg; + reg.method = methodName; + reg.id = reg.method; + args.registrations.add(reg); + m_connection->sendCall( + UnownedStringSlice("client/registerCapability"), &args, JSONValue::makeInt(999)); +} + +void LanguageServer::logMessage(int type, String message) +{ + LanguageServerProtocol::LogMessageParams args; + args.type = type; + args.message = message; + m_connection->sendCall(LanguageServerProtocol::LogMessageParams::methodName, &args); +} + +SlangResult LanguageServer::queueJSONCall(JSONRPCCall call) +{ + Command cmd; + cmd.id = PersistentJSONValue(call.id, m_connection->getContainer()); + cmd.method = call.method; + if (call.method == DidOpenTextDocumentParams::methodName) + { + DidOpenTextDocumentParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.openDocArgs = args; + } + else if (call.method == DidCloseTextDocumentParams::methodName) + { + DidCloseTextDocumentParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.closeDocArgs = args; + } + else if (call.method == DidChangeTextDocumentParams::methodName) + { + DidChangeTextDocumentParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.changeDocArgs = args; + } + else if (call.method == HoverParams::methodName) + { + HoverParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.hoverArgs = args; + } + else if (call.method == DefinitionParams::methodName) + { + DefinitionParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.definitionArgs = args; + } + else if (call.method == CompletionParams::methodName) + { + CompletionParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.completionArgs = args; + } + else if (call.method == SemanticTokensParams::methodName) + { + SemanticTokensParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.semanticTokenArgs = args; + } + else if (call.method == SignatureHelpParams::methodName) + { + SignatureHelpParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.signatureHelpArgs = args; + } + else if (call.method == "completionItem/resolve") + { + CompletionItem args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.completionResolveArgs = args; + } + else if (call.method == DidChangeConfigurationParams::methodName) + { + DidChangeConfigurationParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + // We need to process it now instead of sending to queue. + // This is because there is reference to JSONValue that is only available here. + return didChangeConfiguration(args); + } + else if (call.method == "$/cancelRequest") + { + CancelParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.cancelArgs = args; + } + commands.add(_Move(cmd)); + return SLANG_OK; +} + +SlangResult LanguageServer::runCommand(Command& call) +{ + // Do different things + if (call.method == DidOpenTextDocumentParams::methodName) + { + return didOpenTextDocument(call.openDocArgs.get()); + } + else if (call.method == DidCloseTextDocumentParams::methodName) + { + return didCloseTextDocument(call.closeDocArgs.get()); + } + else if (call.method == DidChangeTextDocumentParams::methodName) + { + return didChangeTextDocument(call.changeDocArgs.get()); + } + else if (call.method == HoverParams::methodName) + { + return hover(call.hoverArgs.get(), call.id); + } + else if (call.method == DefinitionParams::methodName) + { + return gotoDefinition(call.definitionArgs.get(), call.id); + } + else if (call.method == CompletionParams::methodName) + { + return completion(call.completionArgs.get(), call.id); + } + else if (call.method == SemanticTokensParams::methodName) + { + return semanticTokens(call.semanticTokenArgs.get(), call.id); + } + else if (call.method == SignatureHelpParams::methodName) + { + return signatureHelp(call.signatureHelpArgs.get(), call.id); + } + else if (call.method == "completionItem/resolve") + { + return completionResolve(call.completionResolveArgs.get(), call.id); + } + else if (call.method == DidChangeConfigurationParams::methodName) + { + return didChangeConfiguration(call.changeConfigArgs.get()); + } + else if (call.method.startsWith("$/")) + { + // Ignore. + return SLANG_OK; + } + else + { + return m_connection->sendError(JSONRPC::ErrorCode::MethodNotFound, call.id); + } +} + +void LanguageServer::processCommands() +{ + HashSet<int64_t> canceledIDs; + for (auto& cmd : commands) + { + if (cmd.method == "$/cancelRequest") + { + auto id = cmd.cancelArgs.get().id; + if (id > 0) + { + canceledIDs.Add(id); + } + } + } + const int kErrorRequestCanceled = -32800; + for (auto& cmd : commands) + { + if (cmd.id.getKind() == JSONValue::Kind::Integer && canceledIDs.Contains(cmd.id.asInteger())) + { + m_connection->sendError((JSONRPC::ErrorCode)kErrorRequestCanceled, cmd.id); + } + else + { + runCommand(cmd); + } + } +} + SlangResult LanguageServer::didCloseTextDocument(const DidCloseTextDocumentParams& args) { String canonicalPath = uriToCanonicalPath(args.textDocument.uri); - m_workspace->openedDocuments.Remove(canonicalPath); - m_workspace->invalidate(); + m_workspace->closeDoc(canonicalPath); resetDiagnosticUpdateTime(); return SLANG_OK; } SlangResult LanguageServer::didChangeTextDocument(const DidChangeTextDocumentParams& args) { String canonicalPath = uriToCanonicalPath(args.textDocument.uri); - - RefPtr<DocumentVersion> doc; - if (m_workspace->openedDocuments.TryGetValue(canonicalPath, doc)) - { - doc->setText(args.contentChanges[0].text.getUnownedSlice()); - } - m_workspace->invalidate(); + for (auto change : args.contentChanges) + m_workspace->changeDoc(canonicalPath, change.range, change.text); resetDiagnosticUpdateTime(); return SLANG_OK; } +SlangResult LanguageServer::didChangeConfiguration( + const LanguageServerProtocol::DidChangeConfigurationParams& args) +{ + SLANG_UNUSED(args); + sendConfigRequest(); + return SLANG_OK; +} + void LanguageServer::update() { if (!m_workspace) @@ -1013,24 +1012,38 @@ void LanguageServer::update() SlangResult LanguageServer::execute() { - m_connection = new JSONRPCConnection(); m_connection->initWithStdStreams(); while (m_connection->isActive() && !m_quit) { // Consume all messages first. + commands.clear(); + auto start = platform::PerformanceCounter::now(); while (true) { m_connection->tryReadMessage(); if (!m_connection->hasMessage()) break; - const SlangResult res = _executeSingle(); - + 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. 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 " + << String(int(workTime * 1000)) << "ms"; + logMessage(3, msgBuilder.ProduceString()); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } return SLANG_OK; |
