diff options
30 files changed, 625 insertions, 156 deletions
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj b/build/visual-studio/compiler-core/compiler-core.vcxproj index 48426f392..8c47086a7 100644 --- a/build/visual-studio/compiler-core/compiler-core.vcxproj +++ b/build/visual-studio/compiler-core/compiler-core.vcxproj @@ -294,6 +294,7 @@ <ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc-connection.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-value.h" />
+ <ClInclude Include="..\..\..\source\compiler-core\slang-language-server-protocol.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-lexer-diagnostic-defs.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-lexer.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-llvm-compiler.h" />
@@ -328,6 +329,7 @@ <ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc-connection.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-value.cpp" />
+ <ClCompile Include="..\..\..\source\compiler-core\slang-language-server-protocol.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-lexer.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-llvm-compiler.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-name-convention-util.cpp" />
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters index 2db3e73b1..1fc409048 100644 --- a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters +++ b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters @@ -69,6 +69,9 @@ <ClInclude Include="..\..\..\source\compiler-core\slang-json-value.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\compiler-core\slang-language-server-protocol.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\compiler-core\slang-lexer-diagnostic-defs.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -167,6 +170,9 @@ <ClCompile Include="..\..\..\source\compiler-core\slang-json-value.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\compiler-core\slang-language-server-protocol.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\compiler-core\slang-lexer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/build/visual-studio/slang/slang.vcxproj b/build/visual-studio/slang/slang.vcxproj index dffc48700..a2ba8be7a 100644 --- a/build/visual-studio/slang/slang.vcxproj +++ b/build/visual-studio/slang/slang.vcxproj @@ -408,7 +408,6 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\ <ClInclude Include="..\..\..\source\slang\slang-ir.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server-ast-lookup.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server-collect-member.h" />
- <ClInclude Include="..\..\..\source\slang\slang-language-server-protocol.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" />
@@ -558,7 +557,6 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\ <ClCompile Include="..\..\..\source\slang\slang-ir.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server-ast-lookup.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server-collect-member.cpp" />
- <ClCompile Include="..\..\..\source\slang\slang-language-server-protocol.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 8721de464..9784bfef5 100644 --- a/build/visual-studio/slang/slang.vcxproj.filters +++ b/build/visual-studio/slang/slang.vcxproj.filters @@ -321,9 +321,6 @@ <ClInclude Include="..\..\..\source\slang\slang-language-server-collect-member.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="..\..\..\source\slang\slang-language-server-protocol.h">
- <Filter>Header Files</Filter>
- </ClInclude>
<ClInclude Include="..\..\..\source\slang\slang-language-server-semantic-tokens.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -767,9 +764,6 @@ <ClCompile Include="..\..\..\source\slang\slang-language-server-collect-member.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="..\..\..\source\slang\slang-language-server-protocol.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-json-native.cpp b/source/compiler-core/slang-json-native.cpp index d268fffc2..c7a90df4c 100644 --- a/source/compiler-core/slang-json-native.cpp +++ b/source/compiler-core/slang-json-native.cpp @@ -308,7 +308,7 @@ SlangResult NativeToJSONConverter::_structToJSON(const StructRttiInfo* structRtt // Do the super class first if (structRttiInfo->m_super) { - SLANG_RETURN_ON_FAIL(_structToJSON(structRttiInfo, src, outPairs)); + SLANG_RETURN_ON_FAIL(_structToJSON(structRttiInfo->m_super, src, outPairs)); } const Byte* base = (const Byte*)src; diff --git a/source/slang/slang-language-server-protocol.cpp b/source/compiler-core/slang-language-server-protocol.cpp index c557be7ec..3e8907b9a 100644 --- a/source/slang/slang-language-server-protocol.cpp +++ b/source/compiler-core/slang-language-server-protocol.cpp @@ -15,14 +15,23 @@ static const StructRttiInfo _makeTextDocumentSyncOptionsRtti() } const StructRttiInfo TextDocumentSyncOptions::g_rttiInfo = _makeTextDocumentSyncOptionsRtti(); +static const StructRttiInfo _makeWorkDoneProgressParamsRtti() +{ + WorkDoneProgressParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::WorkDoneProgressParams", nullptr); + builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo WorkDoneProgressParams::g_rttiInfo = _makeWorkDoneProgressParamsRtti(); + static const StructRttiInfo _makeCompletionOptionsRtti() { CompletionOptions obj; - StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionOptions", nullptr); + StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionOptions", &WorkDoneProgressParams::g_rttiInfo); builder.addField("triggerCharacters", &obj.triggerCharacters); builder.addField("resolveProvider", &obj.resolveProvider); builder.addField("allCommitCharacters", &obj.allCommitCharacters); - builder.addField("workDoneToken", &obj.workDoneToken); builder.ignoreUnknownFields(); return builder.make(); } @@ -314,23 +323,12 @@ static const StructRttiInfo _makeTextDocumentPositionParamsRtti() } const StructRttiInfo TextDocumentPositionParams::g_rttiInfo = _makeTextDocumentPositionParamsRtti(); -static const StructRttiInfo _makeWorkDoneProgressParamsRtti() -{ - WorkDoneProgressParams obj; - StructRttiBuilder builder(&obj, "LanguageServerProtocol::WorkDoneProgressParams", nullptr); - builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional); - builder.ignoreUnknownFields(); - return builder.make(); -} -const StructRttiInfo WorkDoneProgressParams::g_rttiInfo = _makeWorkDoneProgressParamsRtti(); - static const StructRttiInfo _makeHoverParamsRtti() { HoverParams obj; - StructRttiBuilder builder(&obj, "LanguageServerProtocol::HoverParams", nullptr); + StructRttiBuilder builder(&obj, "LanguageServerProtocol::HoverParams", &WorkDoneProgressParams::g_rttiInfo); builder.addField("textDocument", &obj.textDocument); builder.addField("position", &obj.position); - builder.addField("workDoneToken", &obj, StructRttiInfo::Flag::Optional); builder.ignoreUnknownFields(); return builder.make(); } @@ -363,10 +361,9 @@ const StructRttiInfo Hover::g_rttiInfo = _makeHoverRtti(); static const StructRttiInfo _makeDefinitionParamsRtti() { DefinitionParams obj; - StructRttiBuilder builder(&obj, "LanguageServerProtocol::DefinitionParams", nullptr); + StructRttiBuilder builder(&obj, "LanguageServerProtocol::DefinitionParams", &WorkDoneProgressParams::g_rttiInfo); builder.addField("textDocument", &obj.textDocument); builder.addField("position", &obj.position); - builder.addField("workDoneToken", &obj, StructRttiInfo::Flag::Optional); builder.ignoreUnknownFields(); return builder.make(); } @@ -377,10 +374,9 @@ const UnownedStringSlice DefinitionParams::methodName = static const StructRttiInfo _makeCompletionParamsRtti() { CompletionParams obj; - StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionParams", nullptr); + StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionParams", &WorkDoneProgressParams::g_rttiInfo); builder.addField("textDocument", &obj.textDocument); builder.addField("position", &obj.position); - builder.addField("workDoneToken", &obj, StructRttiInfo::Flag::Optional); builder.ignoreUnknownFields(); return builder.make(); } @@ -406,9 +402,8 @@ const StructRttiInfo CompletionItem::g_rttiInfo = _makeCompletionItemRtti(); static const StructRttiInfo _makeSemanticTokensParamsRtti() { SemanticTokensParams obj; - StructRttiBuilder builder(&obj, "LanguageServerProtocol::SemanticTokensParams", nullptr); + StructRttiBuilder builder(&obj, "LanguageServerProtocol::SemanticTokensParams", &WorkDoneProgressParams::g_rttiInfo); builder.addField("textDocument", &obj.textDocument); - builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional); builder.ignoreUnknownFields(); return builder.make(); } @@ -430,10 +425,9 @@ const StructRttiInfo SemanticTokens::g_rttiInfo = _makeSemanticTokensRtti(); static const StructRttiInfo _makeSignatureHelpParamsRtti() { SignatureHelpParams obj; - StructRttiBuilder builder(&obj, "LanguageServerProtocol::SignatureHelpParams", nullptr); + StructRttiBuilder builder(&obj, "LanguageServerProtocol::SignatureHelpParams", &WorkDoneProgressParams::g_rttiInfo); builder.addField("textDocument", &obj.textDocument); builder.addField("position", &obj.position); - builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional); builder.ignoreUnknownFields(); return builder.make(); } diff --git a/source/slang/slang-language-server-protocol.h b/source/compiler-core/slang-language-server-protocol.h index 29fbaa701..a23ba0130 100644 --- a/source/slang/slang-language-server-protocol.h +++ b/source/compiler-core/slang-language-server-protocol.h @@ -245,13 +245,11 @@ struct InitializeResult static const StructRttiInfo g_rttiInfo; }; -struct ShutdownParams -{ +struct ShutdownParams { static const UnownedStringSlice methodName; }; -struct ExitParams -{ +struct ExitParams { static const UnownedStringSlice methodName; }; @@ -377,16 +375,16 @@ struct TextDocumentPositionParams }; struct HoverParams - : TextDocumentPositionParams - , WorkDoneProgressParams + : WorkDoneProgressParams + ,TextDocumentPositionParams { static const StructRttiInfo g_rttiInfo; static const UnownedStringSlice methodName; }; struct DefinitionParams - : TextDocumentPositionParams - , WorkDoneProgressParams + : WorkDoneProgressParams + , TextDocumentPositionParams { static const StructRttiInfo g_rttiInfo; static const UnownedStringSlice methodName; @@ -424,8 +422,8 @@ struct Hover }; struct CompletionParams - : TextDocumentPositionParams - , WorkDoneProgressParams + : WorkDoneProgressParams + , TextDocumentPositionParams { static const StructRttiInfo g_rttiInfo; static const UnownedStringSlice methodName; @@ -532,8 +530,8 @@ struct SemanticTokens }; struct SignatureHelpParams - : TextDocumentPositionParams - , WorkDoneProgressParams + : WorkDoneProgressParams + , TextDocumentPositionParams { static const UnownedStringSlice methodName; diff --git a/source/core/slang-io.cpp b/source/core/slang-io.cpp index 162e49980..c11ba85e0 100644 --- a/source/core/slang-io.cpp +++ b/source/core/slang-io.cpp @@ -4,6 +4,7 @@ #include "../../slang-com-helper.h" #include "slang-string-util.h" +#include "slang-char-util.h" #ifndef __STDC__ # define __STDC__ 1 @@ -918,7 +919,90 @@ namespace Slang return SLANG_OK; } + + String URI::getPath() const + { + Index startIndex = uri.indexOf("://"); + if (startIndex == -1) + return String(); + startIndex += 3; + Index endIndex = uri.indexOf('?'); + if (endIndex == -1) + endIndex = uri.getLength(); + StringBuilder sb; +#if SLANG_WINDOWS_FAMILY + if (uri[startIndex] == '/') + startIndex++; +#endif + for (Index i = startIndex; i < endIndex;) + { + auto ch = uri[i]; + if (ch == '%') + { + Int charVal = CharUtil::getHexDigitValue(uri[i + 1]) * 16 + + CharUtil::getHexDigitValue(uri[i + 2]); + sb.appendChar((char)charVal); + i += 3; + } + else + { + sb.appendChar(uri[i]); + i++; + } + } + return sb.ProduceString(); + } + StringSlice URI::getProtocol() const + { + Index separatorIndex = uri.indexOf("://"); + if (separatorIndex != -1) + return uri.subString(0, separatorIndex); + return StringSlice(); + } -} + bool URI::isSafeURIChar(char ch) + { + return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || + ch == '-' || ch == '_' || ch == '/' || ch == '.'; + } + URI URI::fromLocalFilePath(UnownedStringSlice path) + { + URI uri; + StringBuilder sb; + sb << "file://"; + +#if SLANG_WINDOWS_FAMILY + sb << "/"; +#endif + + for (auto ch : path) + { + if (isSafeURIChar(ch)) + { + sb.appendChar(ch); + } + else if (ch == '\\') + { + sb.appendChar('/'); + } + else + { + char buffer[32]; + int length = IntToAscii(buffer, (int)ch, 16); + ReverseInternalAscii(buffer, length); + sb << "%" << buffer; + } + } + return URI::fromString(sb.getUnownedSlice()); + } + + URI URI::fromString(UnownedStringSlice uriString) + { + URI uri; + uri.uri = uriString; + return uri; + } + +} diff --git a/source/core/slang-io.h b/source/core/slang-io.h index de761cf4b..51b1d45e5 100644 --- a/source/core/slang-io.h +++ b/source/core/slang-io.h @@ -157,6 +157,23 @@ namespace Slang static SlangResult remove(const String& path); }; + struct URI + { + String uri; + bool operator==(const URI& other) const { return uri == other.uri; } + bool operator!=(const URI& other) const { return uri != other.uri; } + + HashCode getHashCode() const { return uri.getHashCode(); } + + bool isLocalFile() { return uri.startsWith("file://"); }; + String getPath() const; + StringSlice getProtocol() const; + + static URI fromLocalFilePath(UnownedStringSlice path); + static URI fromString(UnownedStringSlice uriString); + static bool isSafeURIChar(char ch); + }; + // Helper class to clean up temporary files on dtor class TemporaryFileSet: public RefObject { diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp index 5282f01a6..c4b654072 100644 --- a/source/core/slang-string-util.cpp +++ b/source/core/slang-string-util.cpp @@ -645,4 +645,28 @@ ComPtr<ISlangBlob> StringUtil::createStringBlob(const String& string) return (cur == end) ? SLANG_OK : SLANG_FAIL; } +int StringUtil::parseIntAndAdvancePos(UnownedStringSlice text, Index& pos) +{ + int result = 0; + while (text[pos] == ' ' && pos < text.getLength()) + { + pos++; + continue; + } + while (pos < text.getLength()) + { + if (text[pos] >= '0' && text[pos] <= '9') + { + result *= 10; + result += text[pos] - '0'; + pos++; + } + else + { + break; + } + } + return result; +} + } // namespace Slang diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h index 03f503ed2..b7576da1d 100644 --- a/source/core/slang-string-util.h +++ b/source/core/slang-string-util.h @@ -109,6 +109,11 @@ struct StringUtil /// Convert into int64_t. Returns SLANG_OK on success. static SlangResult parseInt64(const UnownedStringSlice& text, int64_t& out); + + /// Parse an integer from text starting at pos until the end or the first non-digit char. + /// Modifies pos to the position where parsing ends. + /// Returns parsed integer. + static int parseIntAndAdvancePos(UnownedStringSlice text, Index& pos); }; /* A helper class that allows parsing of lines from text with iteration. Uses StringUtil::extractLine for the actual underlying implementation. */ diff --git a/source/slang/slang-check-modifier.cpp b/source/slang/slang-check-modifier.cpp index 1d9e30c95..176609106 100644 --- a/source/slang/slang-check-modifier.cpp +++ b/source/slang/slang-check-modifier.cpp @@ -80,6 +80,8 @@ namespace Slang AttributeDecl* SemanticsVisitor::lookUpAttributeDecl(Name* attributeName, Scope* scope) { + if (!attributeName) + return nullptr; // We start by looking for an existing attribute matching // the name `attributeName`. // diff --git a/source/slang/slang-language-server.cpp b/source/slang/slang-language-server.cpp index dc3b4fe89..180e17ed9 100644 --- a/source/slang/slang-language-server.cpp +++ b/source/slang/slang-language-server.cpp @@ -13,7 +13,7 @@ #include "../core/slang-range.h" #include "../../slang-com-helper.h" #include "../compiler-core/slang-json-rpc-connection.h" -#include "slang-language-server-protocol.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" diff --git a/source/slang/slang-workspace-version.cpp b/source/slang/slang-workspace-version.cpp index 29404c2d0..1e92c31ca 100644 --- a/source/slang/slang-workspace-version.cpp +++ b/source/slang/slang-workspace-version.cpp @@ -1,7 +1,6 @@ #include "slang-workspace-version.h" #include "../core/slang-io.h" #include "../core/slang-file-system.h" -#include "../core/slang-char-util.h" #include "../compiler-core/slang-lexer.h" namespace Slang @@ -93,18 +92,18 @@ void parseDiagnostics(Dictionary<String, DocumentDiagnostics>& diagnostics, Stri LanguageServerProtocol::Diagnostic diagnostic; Index pos = lparentIndex + 1; - int lineLoc = StringUtil::parseInt(line, pos); + int lineLoc = StringUtil::parseIntAndAdvancePos(line, pos); if (lineLoc == 0) lineLoc = 1; diagnostic.range.end.line = diagnostic.range.start.line = lineLoc - 1; pos++; - int colLoc = StringUtil::parseInt(line, pos); + int colLoc = StringUtil::parseIntAndAdvancePos(line, pos); if (colLoc == 0) colLoc = 1; diagnostic.range.end.character = diagnostic.range.start.character = colLoc - 1; if (pos >= line.getLength()) continue; - line = line.subString(colonIndex + 3, line.getLength()); + line = line.tail(colonIndex + 3); colonIndex = line.indexOf(':'); if (colonIndex == -1) continue; @@ -125,13 +124,13 @@ void parseDiagnostics(Dictionary<String, DocumentDiagnostics>& diagnostics, Stri continue; } pos = line.indexOf(' '); - diagnostic.code = StringUtil::parseInt(line, pos); - diagnostic.message = line.subString(colonIndex + 2, line.getLength()); + diagnostic.code = StringUtil::parseIntAndAdvancePos(line, pos); + diagnostic.message = line.tail(colonIndex + 2); if (lineIndex + 1 < lines.getCount() && lines[lineIndex].startsWith("^+")) { lineIndex++; pos = 2; - auto tokenLength = StringUtil::parseInt(lines[lineIndex], pos); + auto tokenLength = StringUtil::parseIntAndAdvancePos(line, pos); diagnostic.range.end.character += tokenLength; } diagnosticList.messages.Add(diagnostic); @@ -192,89 +191,6 @@ void* Workspace::getInterface(const Guid& uuid) return nullptr; } -String URI::getPath() const -{ - Index startIndex = uri.indexOf("://"); - if (startIndex == -1) return String(); - startIndex += 3; - Index endIndex = uri.indexOf('?'); - if (endIndex == -1) - endIndex = uri.getLength(); - StringBuilder sb; -#if SLANG_WINDOWS_FAMILY - if (uri[startIndex] == '/') - startIndex++; -#endif - for (Index i = startIndex; i < endIndex;) - { - auto ch = uri[i]; - if (ch == '%') - { - Int charVal = CharUtil::getHexDigitValue(uri[i + 1]) * 16 + - CharUtil::getHexDigitValue(uri[i + 2]); - sb.appendChar((char)charVal); - i += 3; - } - else - { - sb.appendChar(uri[i]); - i++; - } - } - return sb.ProduceString(); -} - -StringSlice URI::getProtocol() const -{ - Index separatorIndex = uri.indexOf("://"); - if (separatorIndex != -1) - return uri.subString(0, separatorIndex); - return StringSlice(); -} - -bool URI::isSafeURIChar(char ch) -{ - return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || - ch == '-' || ch == '_' || ch == '/' || ch == '.'; -} - -URI URI::fromLocalFilePath(UnownedStringSlice path) -{ - URI uri; - StringBuilder sb; - sb << "file://"; - -#if SLANG_WINDOWS_FAMILY - sb << "/"; -#endif - - for (auto ch : path) - { - if (isSafeURIChar(ch)) - { - sb.appendChar(ch); - } - else if (ch == '\\') - { - sb.appendChar('/'); - } - else - { - char buffer[32]; - int length = IntToAscii(buffer, (int)ch, 16); - ReverseInternalAscii(buffer, length); - sb << "%" << buffer; - } - } - return URI::fromString(sb.getUnownedSlice()); -} - -URI URI::fromString(UnownedStringSlice uriString) -{ - URI uri; - uri.uri = uriString; - return uri; -} void DocumentVersion::setText(const String& newText) { text = newText; diff --git a/source/slang/slang-workspace-version.h b/source/slang/slang-workspace-version.h index 58811ed7d..2aa2619f1 100644 --- a/source/slang/slang-workspace-version.h +++ b/source/slang/slang-workspace-version.h @@ -5,32 +5,12 @@ #include "../../slang.h" #include "../core/slang-basic.h" #include "../core/slang-com-object.h" -#include "slang-language-server-protocol.h" +#include "../compiler-core/slang-language-server-protocol.h" #include "slang-compiler.h" #include "slang-doc-ast.h" namespace Slang { - struct URI - { - String uri; - bool operator==(const URI& other) const - { - return uri == other.uri; - } - bool operator!=(const URI& other) const { return uri != other.uri; } - - HashCode getHashCode() const { return uri.getHashCode(); } - - bool isLocalFile() { return uri.startsWith("file://"); }; - String getPath() const; - StringSlice getProtocol() const; - - static URI fromLocalFilePath(UnownedStringSlice path); - static URI fromString(UnownedStringSlice uriString); - static bool isSafeURIChar(char ch); - }; - class Workspace; class DocumentVersion : public RefObject diff --git a/tests/language-server/robustness-1.slang b/tests/language-server/robustness-1.slang new file mode 100644 index 000000000..fa9036900 --- /dev/null +++ b/tests/language-server/robustness-1.slang @@ -0,0 +1,11 @@ +//TEST:LANG_SERVER: +// +// tests empty subscript argument. + +void main() +{ +//HOVER:8,13 + float3 arr[3]; + arr[].; + +} diff --git a/tests/language-server/robustness-1.slang.expected.txt b/tests/language-server/robustness-1.slang.expected.txt new file mode 100644 index 000000000..4bfd5a31d --- /dev/null +++ b/tests/language-server/robustness-1.slang.expected.txt @@ -0,0 +1,9 @@ +-------- +range: 7,11 - 7,14 +content: +``` +vector<float,3>[3] arr +``` +{REDACTED}.slang(8) + + diff --git a/tests/language-server/robustness-2.slang b/tests/language-server/robustness-2.slang new file mode 100644 index 000000000..bd73d25ed --- /dev/null +++ b/tests/language-server/robustness-2.slang @@ -0,0 +1,16 @@ +//TEST:LANG_SERVER: +// +// tests broken generic parameter. + +void main() +{ + float3 x + vector<float,3> y; + +} + +enum Kind +{ +//HOVER:15,6 + Foo +} diff --git a/tests/language-server/robustness-2.slang.expected.txt b/tests/language-server/robustness-2.slang.expected.txt new file mode 100644 index 000000000..2236aea91 --- /dev/null +++ b/tests/language-server/robustness-2.slang.expected.txt @@ -0,0 +1,12 @@ +-------- +range: 14,4 - 14,7 +content: +``` +Kind Kind.Foo +``` + + + +{REDACTED}.slang(15) + + diff --git a/tests/language-server/robustness-3.slang b/tests/language-server/robustness-3.slang new file mode 100644 index 000000000..29835eb70 --- /dev/null +++ b/tests/language-server/robustness-3.slang @@ -0,0 +1,12 @@ +//TEST:LANG_SERVER: +// +// tests broken subscript decl. +//HOVER:7,24 +struct MyType +{ + __subscript(int index) + { + get(){return index;} + se + } +} diff --git a/tests/language-server/robustness-3.slang.expected.txt b/tests/language-server/robustness-3.slang.expected.txt new file mode 100644 index 000000000..000ad6add --- /dev/null +++ b/tests/language-server/robustness-3.slang.expected.txt @@ -0,0 +1,12 @@ +-------- +range: 6,20 - 6,25 +content: +``` +int index +``` + + + +{REDACTED}.slang(7) + + diff --git a/tests/language-server/robustness-4.slang b/tests/language-server/robustness-4.slang new file mode 100644 index 000000000..d1f110f5c --- /dev/null +++ b/tests/language-server/robustness-4.slang @@ -0,0 +1,5 @@ +//TEST:LANG_SERVER: +// +//Broken syntax +//HOVER:1,1 +[] diff --git a/tests/language-server/robustness-4.slang.expected.txt b/tests/language-server/robustness-4.slang.expected.txt new file mode 100644 index 000000000..dd2c29b31 --- /dev/null +++ b/tests/language-server/robustness-4.slang.expected.txt @@ -0,0 +1,3 @@ +-------- +null + diff --git a/tests/language-server/smoke.slang b/tests/language-server/smoke.slang new file mode 100644 index 000000000..194902586 --- /dev/null +++ b/tests/language-server/smoke.slang @@ -0,0 +1,33 @@ +//TEST(smoke):LANG_SERVER: +//COMPLETE:31,21 +//HOVER:25,30 +//SIGNATURE:25,40 +interface IFoo +{ + /** + Returns the sum of the contents. + */ + int getSum(); +} + +struct MyType : IFoo +{ + int getSum() { return 0; } +} + +struct Pair<T:IFoo, U: IFoo> : IFoo +{ + T first; + U second; + /** + Returns the sum of the contents. + */ + int getSum() { return first.getSum() + second.getSum(); } +} + +void m() +{ + Pair<MyType, Pair<MyType, MyType>> v; + v.first = v.second.first; + +} diff --git a/tests/language-server/smoke.slang.expected.txt b/tests/language-server/smoke.slang.expected.txt new file mode 100644 index 000000000..5f0bd3915 --- /dev/null +++ b/tests/language-server/smoke.slang.expected.txt @@ -0,0 +1,25 @@ +-------- +first: 6 ().;:,<>[]{}-*/%+=&|! +second: 6 ().;:,<>[]{}-*/%+=&|! +getSum: 2 ([ +-------- +range: 24,26 - 24,31 +content: +``` +Pair.T Pair<Pair.T, Pair.U>.first +``` + + + +{REDACTED}.slang(20) + +-------- +activeParameter: 0 +activeSignature: 0 +func IFoo.getSum() -> int: + +Returns the sum of the contents. + +{REDACTED}.slang(10) + + diff --git a/tests/language-server/vector-member.slang b/tests/language-server/vector-member.slang new file mode 100644 index 000000000..5d624e56d --- /dev/null +++ b/tests/language-server/vector-member.slang @@ -0,0 +1,7 @@ +//TEST:LANG_SERVER: +void f() +{ + float4 v; +//COMPLETE:6,7 + v. +} diff --git a/tests/language-server/vector-member.slang.expected.txt b/tests/language-server/vector-member.slang.expected.txt new file mode 100644 index 000000000..d93d9183f --- /dev/null +++ b/tests/language-server/vector-member.slang.expected.txt @@ -0,0 +1,6 @@ +-------- +x: 6 float ().;:,<>[]{}-*/%+=&|! +y: 6 float ().;:,<>[]{}-*/%+=&|! +z: 6 float ().;:,<>[]{}-*/%+=&|! +w: 6 float ().;:,<>[]{}-*/%+=&|! + diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp index f0d92b810..753ae5569 100644 --- a/tools/slang-test/slang-test-main.cpp +++ b/tools/slang-test/slang-test-main.cpp @@ -31,6 +31,7 @@ #include "../../source/compiler-core/slang-downstream-compiler.h" #include "../../source/compiler-core/slang-nvrtc-compiler.h" +#include "../../source/compiler-core/slang-language-server-protocol.h" #define STB_IMAGE_IMPLEMENTATION #include "external/stb/stb_image.h" @@ -1329,6 +1330,276 @@ TestResult runDocTest(TestContext* context, TestInput& input) return result; } +TestResult runLanguageServerTest(TestContext* context, TestInput& input) +{ + RefPtr<JSONRPCConnection> connection; + if (SLANG_FAILED(context->createLanguageServerJSONRPCConnection(connection))) + { + return TestResult::Fail; + } + if (context->isCollectingRequirements()) + { + connection->sendCall(LanguageServerProtocol::ExitParams::methodName, JSONValue::makeInt(0)); + return TestResult::Pass; + } + LanguageServerProtocol::InitializeParams initParams; + LanguageServerProtocol::WorkspaceFolder wsFolder; + wsFolder.name = "test"; + String fullPath; + Path::getCanonical(input.filePath, fullPath); + wsFolder.uri = URI::fromLocalFilePath(Path::getParentDirectory(fullPath).getUnownedSlice()).uri; + initParams.workspaceFolders.add(wsFolder); + if (SLANG_FAILED(connection->sendCall( + LanguageServerProtocol::InitializeParams::methodName, &initParams, JSONValue::makeInt(0)))) + { + return TestResult::Fail; + } + if (SLANG_FAILED(connection->waitForResult(-1))) + { + return TestResult::Fail; + } + + LanguageServerProtocol::InitializeResult initResult; + if (SLANG_FAILED(connection->getMessage(&initResult))) + { + return TestResult::Fail; + } + + // Send open document call. + String testFileContent; + + if (SLANG_FAILED(File::readAllText(input.filePath, testFileContent))) + { + return TestResult::Fail; + } + + LanguageServerProtocol::DidOpenTextDocumentParams openDocParams; + openDocParams.textDocument.version = 0; + openDocParams.textDocument.uri = URI::fromLocalFilePath(fullPath.getUnownedSlice()).uri; + openDocParams.textDocument.text = testFileContent; + connection->sendCall( + LanguageServerProtocol::DidOpenTextDocumentParams::methodName, + &openDocParams, + JSONValue::makeInt(1)); + List<LanguageServerProtocol::PublishDiagnosticsParams> diagnostics; + bool diagnosticsReceived = false; + auto waitForNonDiagnosticResponse = [&]() -> SlangResult + { + repeat: + if (SLANG_FAILED(connection->waitForResult(10000))) + return SLANG_FAIL; + if (connection->getMessageType() == JSONRPCMessageType::Call) + { + JSONRPCCall call; + connection->getRPC(&call); + if (call.method == "textDocument/publishDiagnostics") + { + diagnosticsReceived = true; + LanguageServerProtocol::PublishDiagnosticsParams arg; + if (SLANG_FAILED(connection->getMessage(&arg))) + return SLANG_FAIL; + diagnostics.add(arg); + goto repeat; + } + } + return SLANG_OK; + }; + + List<UnownedStringSlice> lines; + StringUtil::calcLines(testFileContent.getUnownedSlice(), lines); + + StringBuilder actualOutputSB; + auto parseLocation = [&](UnownedStringSlice text, Index startPos, Int& linePos, Int& colPos) + { + linePos = StringUtil::parseIntAndAdvancePos(text.trimStart(), startPos); + startPos++; + colPos = StringUtil::parseIntAndAdvancePos(text.trimStart(), startPos); + return startPos; + }; + int callId = 2; + for (auto line : lines) + { + if (line.startsWith("//COMPLETE:")) + { + auto arg = line.tail(UnownedStringSlice("//COMPLETE:").getLength()); + Int linePos, colPos; + parseLocation(arg, 0, linePos, colPos); + + LanguageServerProtocol::CompletionParams params; + params.position.line = int(linePos - 1); + params.position.character = int(colPos - 1); + params.textDocument.uri = openDocParams.textDocument.uri; + if (SLANG_FAILED(connection->sendCall( + LanguageServerProtocol::CompletionParams::methodName, + ¶ms, + JSONValue::makeInt(callId++)))) + { + return TestResult::Fail; + } + if (SLANG_FAILED(waitForNonDiagnosticResponse())) + return TestResult::Fail; + actualOutputSB << "--------\n"; + LanguageServerProtocol::NullResponse nullResponse; + List<LanguageServerProtocol::CompletionItem> completionItems; + if (SLANG_SUCCEEDED(connection->getMessage(&nullResponse))) + { + actualOutputSB << "null\n"; + } + else if (SLANG_SUCCEEDED(connection->getMessage(&completionItems))) + { + for (auto item : completionItems) + { + actualOutputSB << item.label << ": " << item.kind << " " << item.detail << " "; + for (auto ch : item.commitCharacters) + actualOutputSB << ch; + actualOutputSB << "\n"; + } + } + } + else if (line.startsWith("//SIGNATURE:")) + { + auto arg = line.tail(UnownedStringSlice("//SIGNATURE:").getLength()); + Int linePos, colPos; + parseLocation(arg, 0, linePos, colPos); + + LanguageServerProtocol::SignatureHelpParams params; + params.position.line = int(linePos - 1); + params.position.character = int(colPos - 1); + params.textDocument.uri = openDocParams.textDocument.uri; + if (SLANG_FAILED(connection->sendCall( + LanguageServerProtocol::SignatureHelpParams::methodName, + ¶ms, + JSONValue::makeInt(callId++)))) + { + return TestResult::Fail; + } + if (SLANG_FAILED(waitForNonDiagnosticResponse())) + return TestResult::Fail; + actualOutputSB << "--------\n"; + LanguageServerProtocol::NullResponse nullResponse; + LanguageServerProtocol::SignatureHelp sigInfo; + if (SLANG_SUCCEEDED(connection->getMessage(&nullResponse))) + { + actualOutputSB << "null\n"; + } + else if (SLANG_SUCCEEDED(connection->getMessage(&sigInfo))) + { + actualOutputSB << "activeParameter: " << sigInfo.activeParameter << "\n"; + actualOutputSB << "activeSignature: " << sigInfo.activeSignature << "\n"; + for (auto item : sigInfo.signatures) + { + actualOutputSB << item.label << ":"; + for (auto param : item.parameters) + { + actualOutputSB << " (" << param.label[0] << "," << param.label[1] << ")"; + } + actualOutputSB << "\n"; + actualOutputSB << item.documentation.value << "\n"; + } + } + } + else if (line.startsWith("//HOVER:")) + { + auto arg = line.tail(UnownedStringSlice("//HOVER:").getLength()); + Int linePos, colPos; + parseLocation(arg, 0, linePos, colPos); + + LanguageServerProtocol::HoverParams params; + params.position.line = int(linePos - 1); + params.position.character = int(colPos - 1); + params.textDocument.uri = openDocParams.textDocument.uri; + if (SLANG_FAILED(connection->sendCall( + LanguageServerProtocol::HoverParams::methodName, + ¶ms, + JSONValue::makeInt(callId++)))) + { + return TestResult::Fail; + } + if (SLANG_FAILED(waitForNonDiagnosticResponse())) + return TestResult::Fail; + actualOutputSB << "--------\n"; + LanguageServerProtocol::NullResponse nullResponse; + LanguageServerProtocol::Hover hover; + if (SLANG_SUCCEEDED(connection->getMessage(&nullResponse))) + { + actualOutputSB << "null\n"; + } + else if (SLANG_SUCCEEDED(connection->getMessage(&hover))) + { + actualOutputSB << "range: " << hover.range.start.line << "," + << hover.range.start.character << " - " << hover.range.end.line + << "," << hover.range.end.character; + actualOutputSB << "\ncontent:\n" << hover.contents.value << "\n"; + } + } + else if (line.startsWith("//DIAGNOSTICS")) + { + if (!diagnosticsReceived) + { + waitForNonDiagnosticResponse(); + } + actualOutputSB << "--------\n"; + for (auto item : diagnostics) + { + actualOutputSB << item.uri << "\n"; + for (auto msg : item.diagnostics) + { + actualOutputSB << msg.range.start.line << "," << msg.range.start.character + << "-" << msg.range.end.line << "," << msg.range.end.character + << " " << msg.message; + } + } + } + } + connection->sendCall(LanguageServerProtocol::ExitParams::methodName, JSONValue::makeInt(0)); + + auto outputStem = input.outputStem; + String expectedOutputPath = outputStem + ".expected.txt"; + String expectedOutput; + + Slang::File::readAllText(expectedOutputPath, expectedOutput); + expectedOutput = expectedOutput.trim(); + + TestResult result = TestResult::Pass; + + auto actualOutput = actualOutputSB.ProduceString(); + + // Redact absolute file names from actualOutput + List<UnownedStringSlice> outputLines; + StringUtil::calcLines(actualOutput.getUnownedSlice(), outputLines); + StringBuilder redactedSB; + for (auto line : outputLines) + { + Index extIdx = line.indexOf(UnownedStringSlice(".slang")); + if (extIdx == -1) + { + redactedSB << line << "\n"; + continue; + } + redactedSB << "{REDACTED}" << line.tail(extIdx) << "\n"; + } + + actualOutput = redactedSB.ProduceString().trim(); + + if (!_areResultsEqual(input.testOptions->type, expectedOutput, actualOutput)) + { + context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput); + result = TestResult::Fail; + } + + // If the test failed, then we write the actual output to a file + // so that we can easily diff it from the command line and + // diagnose the problem. + if (result == TestResult::Fail) + { + String actualOutputPath = outputStem + ".actual"; + Slang::File::writeAllText(actualOutputPath, actualOutput); + + context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput); + } + return result; +} + TestResult runSimpleTest(TestContext* context, TestInput& input) { // need to execute the stand-alone Slang compiler on the file, and compare its output to what we expect @@ -2985,6 +3256,8 @@ static const TestCommandInfo s_testCommandInfos[] = { "PERFORMANCE_PROFILE", &runPerformanceProfile, 0 }, { "COMPILE", &runCompile, 0 }, { "DOC", &runDocTest, 0 }, + { "LANG_SERVER", &runLanguageServerTest, 0}, + }; const TestCommandInfo* _findTestCommandInfoByCommand(const UnownedStringSlice& name) diff --git a/tools/slang-test/test-context.cpp b/tools/slang-test/test-context.cpp index cdafbc30d..6bf7406e6 100644 --- a/tools/slang-test/test-context.cpp +++ b/tools/slang-test/test-context.cpp @@ -171,6 +171,30 @@ SlangResult TestContext::_createJSONRPCConnection(RefPtr<JSONRPCConnection>& out return SLANG_OK; } +SlangResult TestContext::createLanguageServerJSONRPCConnection(RefPtr<JSONRPCConnection>& out) +{ + RefPtr<Process> process; + + { + CommandLine cmdLine; + cmdLine.setExecutableLocation(ExecutableLocation(exeDirectoryPath, "slangd")); + SLANG_RETURN_ON_FAIL(Process::create(cmdLine, Process::Flag::AttachDebugger, process)); + } + + Stream* writeStream = process->getStream(StdStreamType::In); + RefPtr<BufferedReadStream> readStream( + new BufferedReadStream(process->getStream(StdStreamType::Out))); + + RefPtr<HTTPPacketConnection> connection = new HTTPPacketConnection(readStream, writeStream); + RefPtr<JSONRPCConnection> rpcConnection = new JSONRPCConnection; + + SLANG_RETURN_ON_FAIL( + rpcConnection->init(connection, JSONRPCConnection::CallStyle::Object, process)); + + out = rpcConnection; + + return SLANG_OK; +} void TestContext::destroyRPCConnection() { diff --git a/tools/slang-test/test-context.h b/tools/slang-test/test-context.h index adb3e5ad2..284206efb 100644 --- a/tools/slang-test/test-context.h +++ b/tools/slang-test/test-context.h @@ -162,6 +162,7 @@ class TestContext void setTestReporter(TestReporter* reporter); TestReporter* getTestReporter(); + SlangResult createLanguageServerJSONRPCConnection(Slang::RefPtr<Slang::JSONRPCConnection>& out); std::mutex mutex; |
