diff options
| author | Yong He <yonghe@outlook.com> | 2022-06-22 19:58:34 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-06-22 19:58:34 -0700 |
| commit | 07a380d72a13899a84cbdc35692be7a3d9246dcb (patch) | |
| tree | 68e77f2e9682b3b7c3debd745604a494439e5b25 /source/slang/slang-language-server.cpp | |
| parent | e5a75563a1ba2e378353af8b937b8b7bb0fe2c2b (diff) | |
More Language Server Improvements. (#2289)
Diffstat (limited to 'source/slang/slang-language-server.cpp')
| -rw-r--r-- | source/slang/slang-language-server.cpp | 261 |
1 files changed, 228 insertions, 33 deletions
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; |
