diff options
| author | Yong He <yonghe@outlook.com> | 2022-06-27 15:36:00 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-06-27 15:36:00 -0700 |
| commit | b7638b8fffe78ade657f361cadc08dffc8c10acf (patch) | |
| tree | e27a141cfc6a9cc77356b8cba27b41c495d4ee27 | |
| parent | 62d16a23b0ecd72dc624abd7e10b373c40adaa90 (diff) | |
Language server fixes and improvements (#2304)
* Language server: Inlay hints.
* Signature help for base exprs that is not a declref.
* Fix checking of jvp operator.
* Fix.
* Add clang-format based auto formatting.
* Fix clang error.
* Fix clang-format discovery logic.
* Fine tune auto formatting and completion experience.
* Update macos workflow.
* Fixes to configurations.
* Fix parser recovery to trigger completion for index exprs.
* Typo fix.
Co-authored-by: Yong He <yhe@nvidia.com>
18 files changed, 1129 insertions, 24 deletions
diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 423678e07..1c8a33dd8 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -106,6 +106,7 @@ jobs: 7z a slang-macos-dist.zip slangc timeout 1000 gon ./extras/macos-notarize.json - name: UploadNotarizedBinary + if: always() uses: softprops/action-gh-release@v1 with: files: | diff --git a/build/visual-studio/slang/slang.vcxproj b/build/visual-studio/slang/slang.vcxproj index c20633c34..9abc7869b 100644 --- a/build/visual-studio/slang/slang.vcxproj +++ b/build/visual-studio/slang/slang.vcxproj @@ -416,8 +416,10 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\ <ClInclude Include="..\..\..\source\slang\slang-ir-wrap-structured-buffers.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server-ast-lookup.h" />
+ <ClInclude Include="..\..\..\source\slang\slang-language-server-auto-format.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server-completion.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server-document-symbols.h" />
+ <ClInclude Include="..\..\..\source\slang\slang-language-server-inlay-hints.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server-semantic-tokens.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server.h" />
<ClInclude Include="..\..\..\source\slang\slang-legalize-types.h" />
@@ -572,8 +574,10 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\ <ClCompile Include="..\..\..\source\slang\slang-ir-wrap-structured-buffers.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server-ast-lookup.cpp" />
+ <ClCompile Include="..\..\..\source\slang\slang-language-server-auto-format.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server-completion.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server-document-symbols.cpp" />
+ <ClCompile Include="..\..\..\source\slang\slang-language-server-inlay-hints.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server-semantic-tokens.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-legalize-types.cpp" />
diff --git a/build/visual-studio/slang/slang.vcxproj.filters b/build/visual-studio/slang/slang.vcxproj.filters index cb3e0f278..a3f82db41 100644 --- a/build/visual-studio/slang/slang.vcxproj.filters +++ b/build/visual-studio/slang/slang.vcxproj.filters @@ -345,12 +345,18 @@ <ClInclude Include="..\..\..\source\slang\slang-language-server-ast-lookup.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\slang\slang-language-server-auto-format.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\slang\slang-language-server-completion.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\slang\slang-language-server-document-symbols.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\slang\slang-language-server-inlay-hints.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\slang\slang-language-server-semantic-tokens.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -809,12 +815,18 @@ <ClCompile Include="..\..\..\source\slang\slang-language-server-ast-lookup.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\slang\slang-language-server-auto-format.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\slang\slang-language-server-completion.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\slang\slang-language-server-document-symbols.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\slang\slang-language-server-inlay-hints.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\slang\slang-language-server-semantic-tokens.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/source/compiler-core/slang-language-server-protocol.cpp b/source/compiler-core/slang-language-server-protocol.cpp index 51799f05e..d2950b164 100644 --- a/source/compiler-core/slang-language-server-protocol.cpp +++ b/source/compiler-core/slang-language-server-protocol.cpp @@ -25,6 +25,27 @@ static const StructRttiInfo _makeWorkDoneProgressParamsRtti() } const StructRttiInfo WorkDoneProgressParams::g_rttiInfo = _makeWorkDoneProgressParamsRtti(); +static const StructRttiInfo _makeInlayHintOptionsRtti() +{ + InlayHintOptions obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::InlayHintOptions", nullptr); + builder.addField("resolveProvider", &obj.resolveProvider); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo InlayHintOptions::g_rttiInfo = _makeInlayHintOptionsRtti(); + +static const StructRttiInfo _makeDocumentOnTypeFormattingOptionsRtti() +{ + DocumentOnTypeFormattingOptions obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::DocumentOnTypeFormattingOptions", nullptr); + builder.addField("firstTriggerCharacter", &obj.firstTriggerCharacter); + builder.addField("moreTriggerCharacter", &obj.moreTriggerCharacter); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo DocumentOnTypeFormattingOptions::g_rttiInfo = _makeDocumentOnTypeFormattingOptionsRtti(); + static const StructRttiInfo _makeCompletionOptionsRtti() { CompletionOptions obj; @@ -211,6 +232,10 @@ static const StructRttiInfo _makeServerCapabilitiesRtti() builder.addField("textDocumentSync", &obj.textDocumentSync); builder.addField("workspace", &obj.workspace); builder.addField("hoverProvider", &obj.hoverProvider); + builder.addField("inlayHintProvider", &obj.inlayHintProvider); + builder.addField("documentOnTypeFormattingProvider", &obj.documentOnTypeFormattingProvider); + builder.addField("documentFormattingProvider", &obj.documentFormattingProvider); + builder.addField("documentRangeFormattingProvider", &obj.documentRangeFormattingProvider); builder.addField("definitionProvider", &obj.definitionProvider); builder.addField("completionProvider", &obj.completionProvider); builder.addField("semanticTokensProvider", &obj.semanticTokensProvider); @@ -382,6 +407,17 @@ static const StructRttiInfo _makeHoverRtti() } const StructRttiInfo Hover::g_rttiInfo = _makeHoverRtti(); +static const StructRttiInfo _makeCompletionContextRtti() +{ + CompletionContext obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionContext", nullptr); + builder.addField("triggerKind", &obj.triggerKind); + builder.addField("triggerCharacter", &obj.triggerCharacter, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo CompletionContext::g_rttiInfo = _makeCompletionContextRtti(); + static const StructRttiInfo _makeDefinitionParamsRtti() { DefinitionParams obj; @@ -401,6 +437,7 @@ static const StructRttiInfo _makeCompletionParamsRtti() StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionParams", &WorkDoneProgressParams::g_rttiInfo); builder.addField("textDocument", &obj.textDocument); builder.addField("position", &obj.position); + builder.addField("context", &obj.context, StructRttiInfo::Flag::Optional); builder.ignoreUnknownFields(); return builder.make(); } @@ -604,6 +641,87 @@ static const StructRttiInfo _makeDocumentSymbolRtti() } const StructRttiInfo DocumentSymbol::g_rttiInfo = _makeDocumentSymbolRtti(); +static const StructRttiInfo _makeTextEditRtti() +{ + TextEdit obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::TextEdit", nullptr); + builder.addField("range", &obj.range); + builder.addField("newText", &obj.newText); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo TextEdit::g_rttiInfo = _makeTextEditRtti(); + +static const StructRttiInfo _makeInlayHintParamsRtti() +{ + InlayHintParams obj; + StructRttiBuilder builder( + &obj, "LanguageServerProtocol::InlayHintParams", nullptr); + builder.addField("range", &obj.range); + builder.addField("textDocument", &obj.textDocument); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo InlayHintParams::g_rttiInfo = _makeInlayHintParamsRtti(); +const UnownedStringSlice InlayHintParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/inlayHint"); + +static const StructRttiInfo _makeInlayHintRtti() +{ + InlayHint obj; + StructRttiBuilder builder( + &obj, "LanguageServerProtocol::InlayHint", nullptr); + builder.addField("position", &obj.position); + builder.addField("label", &obj.label); + builder.addField("kind", &obj.kind); + builder.addField("paddingLeft", &obj.paddingLeft); + builder.addField("paddingRight", &obj.paddingRight); + builder.addField("textEdits", &obj.textEdits); + + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo InlayHint::g_rttiInfo = _makeInlayHintRtti(); + +static const StructRttiInfo _makeDocumentFormattingParamsRtti() +{ + DocumentFormattingParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::DocumentFormattingParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo DocumentFormattingParams::g_rttiInfo = _makeDocumentFormattingParamsRtti(); +const UnownedStringSlice DocumentFormattingParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/formatting"); + +static const StructRttiInfo _makeDocumentRangeFormattingParamsRtti() +{ + DocumentRangeFormattingParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::DocumentRangeFormattingParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.addField("range", &obj.range); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo DocumentRangeFormattingParams::g_rttiInfo = _makeDocumentRangeFormattingParamsRtti(); +const UnownedStringSlice DocumentRangeFormattingParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/rangeFormatting"); + +static const StructRttiInfo _makeDocumentOnTypeFormattingParamsRtti() +{ + DocumentOnTypeFormattingParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::DocumentOnTypeFormattingParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.addField("position", &obj.position); + builder.addField("ch", &obj.ch); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo DocumentOnTypeFormattingParams::g_rttiInfo = _makeDocumentOnTypeFormattingParamsRtti(); +const UnownedStringSlice DocumentOnTypeFormattingParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/onTypeFormatting"); + } // namespace LanguageServerProtocol } diff --git a/source/compiler-core/slang-language-server-protocol.h b/source/compiler-core/slang-language-server-protocol.h index 7d9d8d5cb..fbc79acca 100644 --- a/source/compiler-core/slang-language-server-protocol.h +++ b/source/compiler-core/slang-language-server-protocol.h @@ -232,6 +232,37 @@ struct WorkspaceCapabilities static const StructRttiInfo g_rttiInfo; }; +/** + * Inlay hint options used during static registration. + * + * @since 3.17.0 + */ +struct InlayHintOptions +{ + /** + * The server provides support to resolve additional + * information for an inlay hint item. + */ + bool resolveProvider = false; + static const StructRttiInfo g_rttiInfo; + +}; + +struct DocumentOnTypeFormattingOptions +{ + /** + * A character on which formatting should be triggered, like `{`. + */ + String firstTriggerCharacter; + + /** + * More trigger characters. + */ + List<String> moreTriggerCharacter; + + static const StructRttiInfo g_rttiInfo; +}; + struct ServerCapabilities { String positionEncoding; @@ -239,6 +270,10 @@ struct ServerCapabilities bool hoverProvider = false; bool definitionProvider = false; bool documentSymbolProvider = false; + bool documentFormattingProvider = false; + bool documentRangeFormattingProvider = false; + DocumentOnTypeFormattingOptions documentOnTypeFormattingProvider; + InlayHintOptions inlayHintProvider; CompletionOptions completionProvider; SemanticTokensOptions semanticTokensProvider; SignatureHelpOptions signatureHelpProvider; @@ -336,7 +371,7 @@ struct Diagnostic * The diagnostic's severity. Can be omitted. If omitted it is up to the * client to interpret diagnostics as error, warning, info or hint. */ - DiagnosticSeverity severity; + DiagnosticSeverity severity = 1; /** * The diagnostic's code, which might appear in the user interface. @@ -452,10 +487,48 @@ struct Hover static const StructRttiInfo g_rttiInfo; }; +typedef int CompletionTriggerKind; +const CompletionTriggerKind kCompletionTriggerKindInvoked = 1; + +/** + * Completion was triggered by a trigger character specified by + * the `triggerCharacters` properties of the + * `CompletionRegistrationOptions`. + */ +const CompletionTriggerKind kCompletionTriggerKindTriggerCharacter = 2; + +/** + * Completion was re-triggered as the current completion list is incomplete. + */ +const CompletionTriggerKind kCompletionTriggerKindTriggerForIncompleteCompletions = 3; + +/** + * Contains additional information about the context in which a completion + * request is triggered. + */ +struct CompletionContext +{ + /** + * How the completion was triggered. + */ + CompletionTriggerKind triggerKind = 1; + + /** + * The trigger character (a single character) that has trigger code + * complete. Is undefined if + * `triggerKind !== CompletionTriggerKind.TriggerCharacter` + */ + String triggerCharacter; + + static const StructRttiInfo g_rttiInfo; +}; + struct CompletionParams : WorkDoneProgressParams , TextDocumentPositionParams { + CompletionContext context; + static const StructRttiInfo g_rttiInfo; static const UnownedStringSlice methodName; }; @@ -786,8 +859,8 @@ const int kSymbolKindTypeParameter = 26; * have two ranges: one that encloses its definition and one that points to its * most interesting range, e.g. the range of an identifier. */ -struct DocumentSymbol { - +struct DocumentSymbol +{ /** * The name of this symbol. Will be displayed in the user interface and * therefore must not be an empty string or a string only consisting of @@ -803,7 +876,7 @@ struct DocumentSymbol { /** * The kind of this symbol. */ - SymbolKind kind; + SymbolKind kind = 0; /** * The range enclosing this symbol not including leading/trailing whitespace @@ -827,5 +900,166 @@ struct DocumentSymbol { static const StructRttiInfo g_rttiInfo; }; +/** + * A parameter literal used in inlay hint requests. + * + * @since 3.17.0 + */ +struct InlayHintParams +{ + /** + * The text document. + */ + TextDocumentIdentifier textDocument; + + /** + * The visible document range for which inlay hints should be computed. + */ + Range range; + + static const StructRttiInfo g_rttiInfo; + static const UnownedStringSlice methodName; +}; + +struct TextEdit +{ + /** + * The range of the text document to be manipulated. To insert + * text into a document create a range where start === end. + */ + Range range; + + /** + * The string to be inserted. For delete operations use an + * empty string. + */ + String newText; + + static const StructRttiInfo g_rttiInfo; + +}; + +typedef int InlayHintKind; +const int kInlayHintKindType = 1; +const int kInlayHintKindParameter = 2; + +/** + * Inlay hint information. + * + * @since 3.17.0 + */ +struct InlayHint +{ + /** + * The position of this hint. + */ + Position position; + + /** + * The label of this hint. A human readable string or an array of + * InlayHintLabelPart label parts. + * + * *Note* that neither the string nor the label part can be empty. + */ + String label; + + /** + * The kind of this hint. Can be omitted in which case the client + * should fall back to a reasonable default. + */ + InlayHintKind kind = 1; + + List<TextEdit> textEdits; + + /** + * Render padding before the hint. + * + * Note: Padding should use the editor's background color, not the + * background color of the hint itself. That means padding can be used + * to visually align/separate an inlay hint. + */ + bool paddingLeft = false; + + /** + * Render padding after the hint. + * + * Note: Padding should use the editor's background color, not the + * background color of the hint itself. That means padding can be used + * to visually align/separate an inlay hint. + */ + bool paddingRight = false; + + static const StructRttiInfo g_rttiInfo; + +}; + +struct DocumentOnTypeFormattingParams +{ + /** + * The document to format. + */ + TextDocumentIdentifier textDocument; + + /** + * The position around which the on type formatting should happen. + * This is not necessarily the exact position where the character denoted + * by the property `ch` got typed. + */ + Position position; + + /** + * The character that has been typed that triggered the formatting + * on type request. That is not necessarily the last character that + * got inserted into the document since the client could auto insert + * characters as well (e.g. like automatic brace completion). + */ + String ch; + + /** + * The formatting options. + */ + //FormattingOptions options; + + static const StructRttiInfo g_rttiInfo; + static const UnownedStringSlice methodName; +}; + +struct DocumentRangeFormattingParams +{ + /** + * The document to format. + */ + TextDocumentIdentifier textDocument; + + /** + * The range to format + */ + Range range; + + /** + * The format options + */ + //FormattingOptions options; + + static const StructRttiInfo g_rttiInfo; + static const UnownedStringSlice methodName; +}; + +struct DocumentFormattingParams +{ + /** + * The document to format. + */ + TextDocumentIdentifier textDocument; + + /** + * The format options + */ + //FormattingOptions options; + + static const StructRttiInfo g_rttiInfo; + static const UnownedStringSlice methodName; +}; + } // namespace LanguageServerProtocol } // namespace Slang diff --git a/source/compiler-core/slang-lexer.cpp b/source/compiler-core/slang-lexer.cpp index fd0255575..4a6d7d392 100644 --- a/source/compiler-core/slang-lexer.cpp +++ b/source/compiler-core/slang-lexer.cpp @@ -1340,9 +1340,12 @@ namespace Slang } } - if (tokenType == TokenType::Identifier || tokenType == TokenType::CompletionRequest) + if (m_namePool) { - token.setName(m_namePool->getName(token.getContent())); + if (tokenType == TokenType::Identifier || tokenType == TokenType::CompletionRequest) + { + token.setName(m_namePool->getName(token.getContent())); + } } return token; diff --git a/source/core/slang-string.cpp b/source/core/slang-string.cpp index 9b3575ad8..03de07ee1 100644 --- a/source/core/slang-string.cpp +++ b/source/core/slang-string.cpp @@ -633,7 +633,7 @@ namespace Slang const char* chars = m_begin; const char firstChar = inChars[0]; - for (Int i = 0; i < len - inLen; ++i) + for (Int i = 0; i <= len - inLen; ++i) { if (chars[i] == firstChar && in == UnownedStringSlice(chars + i, inLen)) { diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index 576220c02..df58b11ed 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -1514,13 +1514,14 @@ namespace Slang // Check/Resolve inner function declaration. expr->baseFunction = CheckTerm(expr->baseFunction); + auto astBuilder = this->getASTBuilder(); + if(auto primalType = as<FuncType>(expr->baseFunction->type)) { // Resolve JVP type here. // Note that this type checking needs to be in sync with // the auto-generation logic in slang-ir-jvp-diff.cpp - auto astBuilder = this->getASTBuilder(); FuncType* jvpType = astBuilder->create<FuncType>(); // Only float types can be differentiated for now. @@ -1529,9 +1530,12 @@ namespace Slang // void otherwise. // if (primalType->resultType->equals(astBuilder->getFloatType())) - jvpType->resultType = astBuilder->getFloatType(); - else - jvpType->resultType = astBuilder->getVoidType(); + jvpType->resultType = astBuilder->getFloatType(); + else + { + //TODO(yong): issue proper diagnostic here. + jvpType->resultType = astBuilder->getVoidType(); + } // No support for differentiating function that throw errors, for now. SLANG_ASSERT(primalType->errorType->equals(astBuilder->getBottomType())); @@ -1553,7 +1557,11 @@ namespace Slang else { // Error - UNREACHABLE_RETURN(nullptr); + expr->type = astBuilder->getErrorType(); + if (!as<ErrorType>(expr->baseFunction->type)) + { + getSink()->diagnose(expr->baseFunction->loc, Diagnostics::expectedFunction, expr->baseFunction->type); + } } return expr; diff --git a/source/slang/slang-language-server-auto-format.cpp b/source/slang/slang-language-server-auto-format.cpp new file mode 100644 index 000000000..8d31c44c6 --- /dev/null +++ b/source/slang/slang-language-server-auto-format.cpp @@ -0,0 +1,213 @@ +#include "slang-language-server-auto-format.h" +#include "../core/slang-char-util.h" +#include "../compiler-core/slang-lexer.h" + +namespace Slang +{ + +String findClangFormatTool() +{ + String processName = String("clang-format") + String(Process::getExecutableSuffix()); + if (File::exists(processName)) + return processName; + RefPtr<Process> proc; + CommandLine cmdLine; + cmdLine.setExecutableLocation(ExecutableLocation(processName)); + if (Process::create(cmdLine, 0, proc) == SLANG_OK) + { + auto inStream = proc->getStream(StdStreamType::In); + if (inStream) inStream->close(); + proc->kill(0); + return processName; + } + auto fileName = + Slang::SharedLibraryUtils::getSharedLibraryFileName((void*)slang_createGlobalSession); + auto dirName = Slang::Path::getParentDirectory(fileName); + auto localProcess = Path::combine(dirName, processName); + if (File::exists(localProcess)) + return localProcess; + + Index vsCodeLoc = dirName.indexOf(".vscode"); + if (vsCodeLoc != -1) + { + // If we still cannot find clang-format, try to use the clang-format bundled with VSCode's C++ extension. + String vsCodeExtDir = dirName.subString(0, vsCodeLoc + 7); + vsCodeExtDir = Path::combine(vsCodeExtDir, "extensions"); + struct CallbackContext + { + String foundPath; + String parentDir; + String processName; + } callbackContext; + callbackContext.processName = processName; + callbackContext.parentDir = vsCodeExtDir; + OSFileSystem::getExtSingleton()->enumeratePathContents(vsCodeExtDir.getBuffer(), [](SlangPathType /*pathType*/, const char* name, void* userData) + { + CallbackContext* context = (CallbackContext*)userData; + if (UnownedStringSlice(name).indexOf(UnownedStringSlice("ms-vscode.cpptools-")) != -1) + { + String candidateFileName = Path::combine(Path::combine(context->parentDir, name, "LLVM"), "bin", context->processName); + if (File::exists(candidateFileName)) + context->foundPath = candidateFileName; + } + }, & callbackContext); + if (callbackContext.foundPath.getLength()) + return callbackContext.foundPath; + } + return String(); +} + +void translateXmlEscape(StringBuilder& sb, UnownedStringSlice text) +{ + if (text.getLength() == 0) + return; + if (text[0] == '#') + { + Int charVal = 0; + StringUtil::parseInt(text.tail(1), charVal); + if (charVal != 0) + sb.appendChar((char)charVal); + } + else if (text == "lt") + { + sb.appendChar('<'); + } + else if (text == "gt") + { + sb.appendChar('>'); + } + else if (text == "amp") + { + sb.appendChar('&'); + } + else if (text == "apos") + { + sb.appendChar('\''); + } + else if (text == "quot") + { + sb.appendChar('\"'); + } +} + +String parseXmlText(UnownedStringSlice text) +{ + StringBuilder sb; + Index pos = 0; + for (; pos < text.getLength();) + { + if (text[pos] == '&') + { + pos++; + Index endPos = pos; + while (endPos < text.getLength() && text[endPos] != ';') + endPos++; + auto escapedToken = text.subString(pos, endPos - pos); + pos = endPos + 1; + translateXmlEscape(sb, escapedToken); + } + else + { + sb.appendChar(text[pos]); + pos++; + } + } + return sb.ProduceString(); +} + +List<Edit> formatSource(UnownedStringSlice text, Index lineStart, Index lineEnd, Index cursorOffset, const FormatOptions& options) +{ + List<Edit> edits; + + String clangProcessName = options.clangFormatLocation; + CommandLine cmdLine; + cmdLine.setExecutableLocation(ExecutableLocation(clangProcessName)); + cmdLine.addArg("--assume-filename=source.cs"); + if (cursorOffset != -1) + { + cmdLine.addArg("--cursor=" + String(cursorOffset)); + } + if (lineStart != -1) + { + cmdLine.addArg("--lines=" + String(lineStart) + ":" + String(lineEnd + 1)); + } + cmdLine.addArg("--output-replacements-xml"); + if (options.style.getLength()) + { + cmdLine.addArg("-style"); + cmdLine.addArg(options.style); + } + RefPtr<Process> proc; + if (SLANG_FAILED(Process::create(cmdLine, 0, proc))) + return edits; + + auto inStream = proc->getStream(StdStreamType::In); + inStream->write(text.begin(), text.getLength()); + char terminator = '\0'; + inStream->write(&terminator, 1); + inStream->flush(); + inStream->close(); + ExecuteResult result; + ProcessUtil::readUntilTermination(proc, result); + + /* + Example result of clang-format: + + <?xml version='1.0'?> + <replacements xml:space='preserve' incomplete_format='false'> + <replacement offset='26' length='2'> </replacement> + </replacements> + */ + + // Adhoc parsing of clang-format's result. + List<UnownedStringSlice> lines; + auto offsetStr = UnownedStringSlice("offset="); + auto lengthStr = UnownedStringSlice("length="); + auto endStr = UnownedStringSlice("</replacement>"); + auto replacementStr = UnownedStringSlice("<replacement "); + StringUtil::calcLines(result.standardOutput.getUnownedSlice(), lines); + for (auto line : lines) + { + line = line.trim(); + if (!line.startsWith(replacementStr)) + continue; + line = line.tail(replacementStr.getLength()); + Index pos = line.indexOf(offsetStr); + if (pos == -1) + continue; + pos += offsetStr.getLength(); + if (pos < line.getLength() && line[pos] == '\'') + pos++; + Edit edt; + edt.offset = StringUtil::parseIntAndAdvancePos(line, pos); + pos = line.indexOf(lengthStr); + if (pos == -1) + continue; + pos += lengthStr.getLength(); + if (pos < line.getLength() && line[pos] == '\'') + pos++; + edt.length = StringUtil::parseIntAndAdvancePos(line, pos); + line = line.tail(pos); + pos = line.indexOf('>'); + if (pos == -1) + continue; + line = line.tail(pos + 1); + Index endPos = line.indexOf(endStr); + if (endPos == -1) + continue; + line = line.head(endPos); + edt.text = parseXmlText(line); + // For on-type formatting, don't make any changes beyond the current cursor position. + if (cursorOffset != -1 && edt.offset >= cursorOffset) + break; + // Never allow clang-format to put the semicolon after `}` in its own line. + if (edt.offset < text.getLength() && edt.length == 0 && text[edt.offset] == ';' && edt.offset >0 && text[edt.offset - 1] == '}') + continue; + + edits.add(edt); + } + return edits; +} + + +} // namespace Slang diff --git a/source/slang/slang-language-server-auto-format.h b/source/slang/slang-language-server-auto-format.h new file mode 100644 index 000000000..8bc51a1d3 --- /dev/null +++ b/source/slang/slang-language-server-auto-format.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../../slang.h" +#include "../core/slang-basic.h" +#include "slang-workspace-version.h" + +namespace Slang +{ +struct Edit +{ + Index offset; + Index length; + String text; +}; + +struct FormatOptions +{ + String clangFormatLocation; + String style = "{BasedOnStyle: Microsoft}"; +}; + +String findClangFormatTool(); +List<Edit> formatSource(UnownedStringSlice text, Index lineStart, Index lineEnd, Index cursorOffset, const FormatOptions& options); + +} // namespace Slang diff --git a/source/slang/slang-language-server-inlay-hints.cpp b/source/slang/slang-language-server-inlay-hints.cpp new file mode 100644 index 000000000..22c9ce21e --- /dev/null +++ b/source/slang/slang-language-server-inlay-hints.cpp @@ -0,0 +1,120 @@ +#include "slang-language-server-inlay-hints.h" +#include "slang-visitor.h" +#include "slang-ast-support-types.h" +#include "slang-ast-iterator.h" +#include "slang-language-server.h" +#include "../core/slang-char-util.h" + +namespace Slang +{ +List<LanguageServerProtocol::InlayHint> getInlayHints( + Linkage* linkage, + Module* module, + UnownedStringSlice fileName, + DocumentVersion* doc, + LanguageServerProtocol::Range range, + const InlayHintOptions& options) +{ + List<LanguageServerProtocol::InlayHint> result; + auto manager = linkage->getSourceManager(); + auto docText = doc->getText().getUnownedSlice(); + iterateAST(fileName, manager, module->getModuleDecl(), [&](SyntaxNode* node) + { + if (auto invokeExpr = as<InvokeExpr>(node)) + { + if (!options.showParameterNames) + return; + auto humaneLoc = manager->getHumaneLoc(node->loc); + if (humaneLoc.line - 1 < range.start.line || humaneLoc.line - 1 > range.end.line) + return; + if (humaneLoc.pathInfo.foundPath != fileName) + return; + auto funcExpr = as<DeclRefExpr>(invokeExpr->functionExpr); + if (!funcExpr) + return; + if (as<ConstructorDecl>(funcExpr->declRef.getDecl())) + return; + auto callable = as<CallableDecl>(funcExpr->declRef.getDecl()); + if (!callable) + return; + auto params = callable->getParameters(); + Index i = 0; + for (auto param : params) + { + if (i >= invokeExpr->argumentDelimeterLocs.getCount() - 1) + break; + if (auto name = param->getName()) + { + LanguageServerProtocol::InlayHint hint; + auto loc = manager->getHumaneLoc(invokeExpr->argumentDelimeterLocs[i]); + auto offset = doc->getOffset(loc.line, loc.column); + offset++; + while (offset < docText.getLength() && CharUtil::isWhitespace(docText[offset])) + offset++; + Index posLine, posCol; + doc->offsetToLineCol(offset, posLine, posCol); + Index utf16line, utf16col; + doc->oneBasedUTF8LocToZeroBasedUTF16Loc(posLine, posCol, utf16line, utf16col); + hint.position.line = (int)utf16line; + hint.position.character = (int)utf16col; + hint.paddingLeft = false; + hint.kind = LanguageServerProtocol::kInlayHintKindParameter; + StringBuilder lblSb; + if (param->hasModifier<OutModifier>()) lblSb << "out "; + else if (param->hasModifier<InOutModifier>()) lblSb << "inout "; + else if (param->hasModifier<RefModifier>()) lblSb << "ref "; + lblSb << name->text; + lblSb << ":"; + hint.label = lblSb.ProduceString(); + result.add(hint); + } + i++; + } + } + else if (auto varDecl = as<VarDeclBase>(node)) + { + if (!options.showDeducedType) + return; + auto humaneLoc = manager->getHumaneLoc(node->loc); + if (humaneLoc.line - 1 < range.start.line || humaneLoc.line - 1 > range.end.line) + return; + if (humaneLoc.pathInfo.foundPath != fileName) + return; + if (varDecl->type.exp) + return; + if (!varDecl->type.type) + return; + if (as<ErrorType>(varDecl->type.type)) + return; + if (!varDecl->getName()) + return; + + LanguageServerProtocol::InlayHint hint; + auto loc = manager->getHumaneLoc(varDecl->nameAndLoc.loc); + auto offset = doc->getOffset(loc.line, loc.column); + offset++; + while (offset < docText.getLength() && _isIdentifierChar(docText[offset])) + offset++; + Index posLine, posCol; + doc->offsetToLineCol(offset, posLine, posCol); + Index utf16line, utf16col; + doc->oneBasedUTF8LocToZeroBasedUTF16Loc(posLine, posCol, utf16line, utf16col); + hint.position.line = (int)utf16line; + hint.position.character = (int)utf16col; + hint.kind = LanguageServerProtocol::kInlayHintKindType; + StringBuilder lblSb; + lblSb << ": " << varDecl->type.type->toString(); + hint.label = lblSb.ProduceString(); + + LanguageServerProtocol::TextEdit edit; + edit.range.start = hint.position; + edit.range.end = hint.position; + edit.newText = " " + hint.label; + hint.textEdits.add(edit); + result.add(hint); + } + }); + return result; +} + +} // namespace Slang diff --git a/source/slang/slang-language-server-inlay-hints.h b/source/slang/slang-language-server-inlay-hints.h new file mode 100644 index 000000000..50ea67c45 --- /dev/null +++ b/source/slang/slang-language-server-inlay-hints.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../../slang.h" +#include "../core/slang-basic.h" +#include "slang-ast-all.h" +#include "slang-syntax.h" +#include "slang-compiler.h" +#include "slang-workspace-version.h" + +namespace Slang +{ + +struct InlayHintOptions +{ + bool showDeducedType = false; + bool showParameterNames = false; +}; + +List<LanguageServerProtocol::InlayHint> getInlayHints( + Linkage* linkage, Module* module, UnownedStringSlice fileName, DocumentVersion* doc, LanguageServerProtocol::Range range, const InlayHintOptions& options); +} // namespace Slang diff --git a/source/slang/slang-language-server.cpp b/source/slang/slang-language-server.cpp index 619aaaacd..0818aea07 100644 --- a/source/slang/slang-language-server.cpp +++ b/source/slang/slang-language-server.cpp @@ -10,6 +10,7 @@ #include <time.h> #include "../core/slang-secure-crt.h" #include "../core/slang-range.h" +#include "../core/slang-char-util.h" #include "../../slang-com-helper.h" #include "../compiler-core/slang-json-native.h" #include "../compiler-core/slang-json-rpc-connection.h" @@ -20,6 +21,7 @@ #include "slang-language-server-completion.h" #include "slang-language-server-semantic-tokens.h" #include "slang-language-server-document-symbols.h" +#include "slang-language-server-inlay-hints.h" #include "slang-ast-print.h" #include "slang-doc-markdown-writer.h" #include "slang-mangle.h" @@ -103,7 +105,7 @@ SlangResult LanguageServer::parseNextMessage() InitializeResult result; result.serverInfo.name = "SlangLanguageServer"; - result.serverInfo.version = "1.0"; + result.serverInfo.version = "1.3"; result.capabilities.positionEncoding = "utf-16"; result.capabilities.textDocumentSync.openClose = true; result.capabilities.textDocumentSync.change = (int)TextDocumentSyncKind::Incremental; @@ -112,13 +114,17 @@ SlangResult LanguageServer::parseNextMessage() result.capabilities.hoverProvider = true; result.capabilities.definitionProvider = true; result.capabilities.documentSymbolProvider = true; + result.capabilities.inlayHintProvider.resolveProvider = false; + result.capabilities.documentFormattingProvider = true; + result.capabilities.documentOnTypeFormattingProvider.firstTriggerCharacter = "}"; + result.capabilities.documentOnTypeFormattingProvider.moreTriggerCharacter.add(";"); + result.capabilities.documentOnTypeFormattingProvider.moreTriggerCharacter.add(":"); + result.capabilities.documentRangeFormattingProvider = 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()) - result.capabilities.completionProvider.allCommitCharacters.add(ch); result.capabilities.semanticTokensProvider.full = true; result.capabilities.semanticTokensProvider.range = false; result.capabilities.signatureHelpProvider.triggerCharacters.add("("); @@ -130,8 +136,8 @@ SlangResult LanguageServer::parseNextMessage() } else if (call.method == "initialized") { - sendConfigRequest(); registerCapability("workspace/didChangeConfiguration"); + sendConfigRequest(); m_initialized = true; return SLANG_OK; } @@ -153,12 +159,14 @@ SlangResult LanguageServer::parseNextMessage() if (response.result.getKind() == JSONValue::Kind::Array) { auto arr = m_connection->getContainer()->getArray(response.result); - if (arr.getCount() == 4) + if (arr.getCount() == 8) { updatePredefinedMacros(arr[0]); updateSearchPaths(arr[1]); updateSearchInWorkspace(arr[2]); updateCommitCharacters(arr[3]); + updateFormattingOptions(arr[4], arr[5]); + updateInlayHintOptions(arr[6], arr[7]); } } break; @@ -515,6 +523,21 @@ SlangResult LanguageServer::completion( m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } + + // Don't show completion at case label. + if (args.context.triggerKind == + LanguageServerProtocol::kCompletionTriggerKindTriggerCharacter && + args.context.triggerCharacter == ":") + { + auto line = doc->getLine((Int)args.position.line + 1); + auto prevCharPos = args.position.character - 2; + if (prevCharPos >= 0 && prevCharPos < line.getLength() && line[prevCharPos] != ':') + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + } + Index utf8Line, utf8Col; doc->zeroBasedUTF16LocToOneBasedUTF8Loc( args.position.line, args.position.character, utf8Line, utf8Col); @@ -532,6 +555,13 @@ SlangResult LanguageServer::completion( cursorOffset--; } + // Never show suggestions when the user is typing a number. + if (cursorOffset + 1 >= 0 && cursorOffset + 1 < doc->getText().getLength() && CharUtil::isDigit(doc->getText()[cursorOffset + 1])) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + // Insert a completion request token at cursor position. auto originalText = doc->getText(); StringBuilder newText; @@ -553,7 +583,7 @@ SlangResult LanguageServer::completion( return SLANG_OK; } - CompletionContext context; + Slang::CompletionContext context; context.server = this; context.cursorOffset = cursorOffset; context.version = version; @@ -568,6 +598,16 @@ SlangResult LanguageServer::completion( { return SLANG_OK; } + + // Don't general completion suggestions after typing '['. + if (args.context.triggerKind == + LanguageServerProtocol::kCompletionTriggerKindTriggerCharacter && + args.context.triggerCharacter == "[") + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + if (SLANG_SUCCEEDED(context.tryCompleteHLSLSemantic())) { return SLANG_OK; @@ -760,6 +800,43 @@ SlangResult LanguageServer::signatureHelp( } response.signatures.add(sigInfo); }; + + auto addFuncType = [&](FuncType* funcType) + { + SignatureInformation sigInfo; + + List<Slang::Range<Index>> paramRanges; + ASTPrinter printer( + version->linkage->getASTBuilder(), + ASTPrinter::OptionFlag::ParamNames | ASTPrinter::OptionFlag::NoInternalKeywords | + ASTPrinter::OptionFlag::SimplifiedBuiltinType); + + printer.getStringBuilder() << "func ("; + bool isFirst = true; + for (auto param : funcType->paramTypes) + { + if (!isFirst) + printer.getStringBuilder() << ", "; + Slang::Range<Index> range; + range.begin = printer.getStringBuilder().getLength(); + printer.addType(param); + range.end = printer.getStringBuilder().getLength(); + paramRanges.add(range); + isFirst = false; + } + printer.getStringBuilder() << ") -> "; + printer.addType(funcType->getResultType()); + sigInfo.label = printer.getString(); + for (auto& range : paramRanges) + { + ParameterInformation paramInfo; + paramInfo.label[0] = (uint32_t)range.begin; + paramInfo.label[1] = (uint32_t)range.end; + sigInfo.parameters.add(paramInfo); + } + response.signatures.add(sigInfo); + }; + if (auto declRefExpr = as<DeclRefExpr>(funcExpr)) { if (auto aggDecl = as<AggTypeDecl>(declRefExpr->declRef.getDecl())) @@ -782,6 +859,10 @@ SlangResult LanguageServer::signatureHelp( addDeclRef(item.declRef); } } + else if (auto funcType = as<FuncType>(funcExpr->type.type)) + { + addFuncType(funcType); + } response.activeSignature = 0; response.activeParameter = 0; for (int i = 1; i < appExpr->argumentDelimeterLocs.getCount(); i++) @@ -822,6 +903,114 @@ SlangResult LanguageServer::documentSymbol( return SLANG_OK; } +SlangResult LanguageServer::inlayHint(const LanguageServerProtocol::InlayHintParams& 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; + } + auto version = m_workspace->getCurrentVersion(); + Module* parsedModule = version->getOrLoadModule(canonicalPath); + if (!parsedModule) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + List<InlayHint> hints = getInlayHints( + version->linkage, + parsedModule, + canonicalPath.getUnownedSlice(), + doc.Ptr(), + args.range, + m_inlayHintOptions); + m_connection->sendResult(&hints, responseId); + return SLANG_OK; +} + +List<LanguageServerProtocol::TextEdit> translateTextEdits(DocumentVersion* doc, List<Edit>& edits) +{ + List<LanguageServerProtocol::TextEdit> result; + for (auto& edit : edits) + { + LanguageServerProtocol::TextEdit tedit; + Index line, col; + Index zeroBasedLine, zeroBasedCol; + doc->offsetToLineCol(edit.offset, line, col); + doc->oneBasedUTF8LocToZeroBasedUTF16Loc(line, col, zeroBasedLine, zeroBasedCol); + tedit.range.start.line = (int)zeroBasedLine; + tedit.range.start.character = (int)zeroBasedCol; + doc->offsetToLineCol(edit.offset + edit.length, line, col); + doc->oneBasedUTF8LocToZeroBasedUTF16Loc(line, col, zeroBasedLine, zeroBasedCol); + tedit.range.end.line = (int)zeroBasedLine; + tedit.range.end.character = (int)zeroBasedCol; + tedit.newText = edit.text; + result.add(_Move(tedit)); + } + return result; +} + +SlangResult LanguageServer::formatting(const LanguageServerProtocol::DocumentFormattingParams& 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; + } + if (m_formatOptions.clangFormatLocation.getLength() == 0) + m_formatOptions.clangFormatLocation = findClangFormatTool(); + auto edits = formatSource(doc->getText().getUnownedSlice(), -1, -1, -1, m_formatOptions); + auto textEdits = translateTextEdits(doc, edits); + m_connection->sendResult(&textEdits, responseId); + return SLANG_OK; +} + +SlangResult LanguageServer::rangeFormatting(const LanguageServerProtocol::DocumentRangeFormattingParams& 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; + } + if (m_formatOptions.clangFormatLocation.getLength() == 0) + m_formatOptions.clangFormatLocation = findClangFormatTool(); + auto edits = formatSource(doc->getText().getUnownedSlice(), args.range.start.line, args.range.end.line, -1, m_formatOptions); + auto textEdits = translateTextEdits(doc, edits); + m_connection->sendResult(&textEdits, responseId); + return SLANG_OK; +} + +SlangResult LanguageServer::onTypeFormatting(const LanguageServerProtocol::DocumentOnTypeFormattingParams& 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; + } + if (args.ch == ":" && !doc->getLine((Int)args.position.line + 1).trim().startsWith("case ")) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + if (m_formatOptions.clangFormatLocation.getLength() == 0) + m_formatOptions.clangFormatLocation = findClangFormatTool(); + Index line, col; + doc->zeroBasedUTF16LocToOneBasedUTF8Loc(args.position.line, args.position.character, line, col); + auto cursorOffset = doc->getOffset(line, col); + auto edits = formatSource(doc->getText().getUnownedSlice(), args.position.line, args.position.line, cursorOffset, m_formatOptions); + auto textEdits = translateTextEdits(doc, edits); + m_connection->sendResult(&textEdits, responseId); + return SLANG_OK; +} + void LanguageServer::publishDiagnostics() { time_t timeNow = 0; @@ -866,6 +1055,14 @@ void LanguageServer::publishDiagnostics() } } +void sendRefreshRequests(JSONRPCConnection* connection) +{ + connection->sendCall( + UnownedStringSlice("workspace/semanticTokens/refresh"), JSONValue::makeInt(0)); + connection->sendCall( + UnownedStringSlice("workspace/inlayHint/refresh"), JSONValue::makeInt(0)); +} + void LanguageServer::updatePredefinedMacros(const JSONValue& macros) { if (macros.isValid()) @@ -877,8 +1074,7 @@ void LanguageServer::updatePredefinedMacros(const JSONValue& macros) { if (m_workspace->updatePredefinedMacros(predefinedMacros)) { - m_connection->sendCall( - UnownedStringSlice("workspace/semanticTokens/refresh"), JSONValue::makeInt(0)); + sendRefreshRequests(m_connection); } } } @@ -895,8 +1091,7 @@ void LanguageServer::updateSearchPaths(const JSONValue& value) { if (m_workspace->updateSearchPaths(searchPaths)) { - m_connection->sendCall( - UnownedStringSlice("workspace/semanticTokens/refresh"), JSONValue::makeInt(0)); + sendRefreshRequests(m_connection); } } } @@ -913,8 +1108,7 @@ void LanguageServer::updateSearchInWorkspace(const JSONValue& value) { if (m_workspace->updateSearchInWorkspace(searchPaths)) { - m_connection->sendCall( - UnownedStringSlice("workspace/semanticTokens/refresh"), JSONValue::makeInt(0)); + sendRefreshRequests(m_connection); } } } @@ -945,6 +1139,32 @@ void LanguageServer::updateCommitCharacters(const JSONValue& jsonValue) } } +void LanguageServer::updateFormattingOptions(const JSONValue& clangFormatLoc, const JSONValue& clangFormatStyle) +{ + auto container = m_connection->getContainer(); + JSONToNativeConverter converter(container, m_connection->getSink()); + converter.convert(clangFormatLoc, &m_formatOptions.clangFormatLocation); + converter.convert(clangFormatStyle, &m_formatOptions.style); + if (m_formatOptions.style.getLength() == 0) + m_formatOptions.style = Slang::FormatOptions().style; +} + +void LanguageServer::updateInlayHintOptions(const JSONValue& deducedTypes, const JSONValue& parameterNames) +{ + auto container = m_connection->getContainer(); + JSONToNativeConverter converter(container, m_connection->getSink()); + bool showDeducedType = false; + bool showParameterNames = false; + converter.convert(deducedTypes, &showDeducedType); + converter.convert(parameterNames, &showParameterNames); + if (showDeducedType != m_inlayHintOptions.showDeducedType || showParameterNames != m_inlayHintOptions.showParameterNames) + { + m_connection->sendCall( + UnownedStringSlice("workspace/inlayHint/refresh"), JSONValue::makeInt(0)); + } + m_inlayHintOptions.showDeducedType = showDeducedType; + m_inlayHintOptions.showParameterNames = showParameterNames; +} void LanguageServer::sendConfigRequest() { @@ -958,6 +1178,14 @@ void LanguageServer::sendConfigRequest() args.items.add(item); item.section = "slang.enableCommitCharactersInAutoCompletion"; args.items.add(item); + item.section = "slang.format.clangFormatLocation"; + args.items.add(item); + item.section = "slang.format.clangFormatStyle"; + args.items.add(item); + item.section = "slang.inlayHints.deducedTypes"; + args.items.add(item); + item.section = "slang.inlayHints.parameterNames"; + args.items.add(item); m_connection->sendCall( ConfigurationParams::methodName, &args, @@ -1148,6 +1376,24 @@ SlangResult LanguageServer::queueJSONCall(JSONRPCCall call) SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); cmd.documentSymbolArgs = args; } + else if (call.method == DocumentFormattingParams::methodName) + { + DocumentFormattingParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.formattingArgs = args; + } + else if (call.method == DocumentRangeFormattingParams::methodName) + { + DocumentRangeFormattingParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.rangeFormattingArgs = args; + } + else if (call.method == DocumentOnTypeFormattingParams::methodName) + { + DocumentOnTypeFormattingParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.onTypeFormattingArgs = args; + } else if (call.method == DidChangeConfigurationParams::methodName) { DidChangeConfigurationParams args; @@ -1156,6 +1402,12 @@ SlangResult LanguageServer::queueJSONCall(JSONRPCCall call) // This is because there is reference to JSONValue that is only available here. return didChangeConfiguration(args); } + else if (call.method == InlayHintParams::methodName) + { + InlayHintParams args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + cmd.inlayHintArgs = args; + } else if (call.method == "$/cancelRequest") { CancelParams args; @@ -1213,6 +1465,22 @@ SlangResult LanguageServer::runCommand(Command& call) { return didChangeConfiguration(call.changeConfigArgs.get()); } + else if (call.method == InlayHintParams::methodName) + { + return inlayHint(call.inlayHintArgs.get(), call.id); + } + else if (call.method == DocumentOnTypeFormattingParams::methodName) + { + return onTypeFormatting(call.onTypeFormattingArgs.get(), call.id); + } + else if (call.method == DocumentRangeFormattingParams::methodName) + { + return rangeFormatting(call.rangeFormattingArgs.get(), call.id); + } + else if (call.method == DocumentFormattingParams::methodName) + { + return formatting(call.formattingArgs.get(), call.id); + } else if (call.method.startsWith("$/")) { // Ignore. diff --git a/source/slang/slang-language-server.h b/source/slang/slang-language-server.h index 17dfc0f16..963df65f6 100644 --- a/source/slang/slang-language-server.h +++ b/source/slang/slang-language-server.h @@ -6,6 +6,8 @@ #include "slang-workspace-version.h" #include "slang-language-server-completion.h" +#include "slang-language-server-auto-format.h" +#include "slang-language-server-inlay-hints.h" namespace Slang { @@ -58,6 +60,10 @@ struct Command Optional<LanguageServerProtocol::CompletionParams> completionArgs; Optional<LanguageServerProtocol::CompletionItem> completionResolveArgs; Optional<LanguageServerProtocol::DocumentSymbolParams> documentSymbolArgs; + Optional<LanguageServerProtocol::InlayHintParams> inlayHintArgs; + Optional<LanguageServerProtocol::DocumentFormattingParams> formattingArgs; + Optional<LanguageServerProtocol::DocumentRangeFormattingParams> rangeFormattingArgs; + Optional<LanguageServerProtocol::DocumentOnTypeFormattingParams> onTypeFormattingArgs; Optional<LanguageServerProtocol::DidChangeConfigurationParams> changeConfigArgs; Optional<LanguageServerProtocol::SignatureHelpParams> signatureHelpArgs; Optional<LanguageServerProtocol::DefinitionParams> definitionArgs; @@ -82,6 +88,8 @@ public: RefPtr<Workspace> m_workspace; Dictionary<String, String> m_lastPublishedDiagnostics; time_t m_lastDiagnosticUpdateTime = 0; + FormatOptions m_formatOptions; + Slang::InlayHintOptions m_inlayHintOptions; bool m_quit = false; List<LanguageServerProtocol::WorkspaceFolder> m_workspaceFolders; @@ -108,6 +116,14 @@ public: const LanguageServerProtocol::SignatureHelpParams& args, const JSONValue& responseId); SlangResult documentSymbol( const LanguageServerProtocol::DocumentSymbolParams& args, const JSONValue& responseId); + SlangResult inlayHint( + const LanguageServerProtocol::InlayHintParams& args, const JSONValue& responseId); + SlangResult formatting( + const LanguageServerProtocol::DocumentFormattingParams& args, const JSONValue& responseId); + SlangResult rangeFormatting( + const LanguageServerProtocol::DocumentRangeFormattingParams& args, const JSONValue& responseId); + SlangResult onTypeFormatting( + const LanguageServerProtocol::DocumentOnTypeFormattingParams& args, const JSONValue& responseId); private: SlangResult parseNextMessage(); @@ -118,6 +134,8 @@ private: void updateSearchPaths(const JSONValue& value); void updateSearchInWorkspace(const JSONValue& value); void updateCommitCharacters(const JSONValue& value); + void updateFormattingOptions(const JSONValue& clangFormatLoc, const JSONValue& clangFormatStyle); + void updateInlayHintOptions(const JSONValue& deducedTypes, const JSONValue& parameterNames); void sendConfigRequest(); void registerCapability(const char* methodName); diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index d168bf55c..a2a249f7e 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -98,6 +98,8 @@ namespace Slang Scope* outerScope = nullptr; Scope* currentScope = nullptr; + bool hasSeenCompletionToken = false; + TokenReader tokenReader; DiagnosticSink* sink; SourceLoc lastErrorLoc; @@ -1126,6 +1128,8 @@ namespace Slang static NameLoc expectIdentifier(Parser* parser) { + if (!parser->hasSeenCompletionToken && parser->LookAheadToken(TokenType::CompletionRequest)) + parser->hasSeenCompletionToken = true; return NameLoc(parser->ReadToken(TokenType::Identifier)); } @@ -4335,6 +4339,24 @@ namespace Slang // Expr* type = ParseType(); + if (type && hasSeenCompletionToken) + { + // If we encountered a completion token, just return the parsed expr + // as an ExprStmt. + for (auto tokenPos = startPos.tokenReaderCursor; + tokenPos && tokenPos < tokenReader.getCursor().tokenReaderCursor; + ++tokenPos) + { + if (tokenPos && tokenPos->type == TokenType::CompletionRequest) + { + auto exprStmt = astBuilder->create<ExpressionStmt>(); + exprStmt->loc = type->loc; + exprStmt->expression = type; + return exprStmt; + } + } + } + // We don't actually care about the type, though, so // don't retain it // @@ -5635,6 +5657,17 @@ namespace Slang return constExpr; } case TokenType::CompletionRequest: + { + VarExpr* varExpr = parser->astBuilder->create<VarExpr>(); + varExpr->scope = parser->currentScope; + parser->FillPosition(varExpr); + auto nameAndLoc = NameLoc(peekToken(parser)); + varExpr->name = nameAndLoc.name; + parser->hasSeenCompletionToken = true; + // Don't consume the token, instead we skip directly. + parser->ReadToken(TokenType::Identifier); + return varExpr; + } case TokenType::Identifier: { // We will perform name lookup here so that we can find syntax diff --git a/tests/language-server/member-completion-subscript.slang b/tests/language-server/member-completion-subscript.slang new file mode 100644 index 000000000..eafc278f4 --- /dev/null +++ b/tests/language-server/member-completion-subscript.slang @@ -0,0 +1,9 @@ +//TEST:LANG_SERVER: +//COMPLETE:7,19 CONTAINS index___ +void m() +{ + float3 vArray[3]; + let index___ = 1; + vArray[index__] + var c = 3; +} diff --git a/tests/language-server/member-completion-subscript.slang.expected.txt b/tests/language-server/member-completion-subscript.slang.expected.txt new file mode 100644 index 000000000..8b42272e2 --- /dev/null +++ b/tests/language-server/member-completion-subscript.slang.expected.txt @@ -0,0 +1 @@ +CONTAINS index___ diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp index ea5ada4b0..355f28a23 100644 --- a/tools/slang-test/slang-test-main.cpp +++ b/tools/slang-test/slang-test-main.cpp @@ -1716,6 +1716,23 @@ TestResult runLanguageServerTest(TestContext* context, TestInput& input) if (!_areResultsEqual(input.testOptions->type, expectedOutput, actualOutput)) { + if (expectedOutput.startsWith("CONTAINS")) + { + List<UnownedStringSlice> words; + List<UnownedStringSlice> expectedLines; + StringUtil::calcLines(expectedOutput.getUnownedSlice(), expectedLines); + if (expectedLines.getCount() >= 1) + { + StringUtil::split(expectedLines[0], ' ', words); + if (words.getCount() >= 2) + { + if (actualOutput.contains(words[1].trim())) + { + return result; + } + } + } + } context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput); result = TestResult::Fail; } |
