summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2022-06-27 15:36:00 -0700
committerGitHub <noreply@github.com>2022-06-27 15:36:00 -0700
commitb7638b8fffe78ade657f361cadc08dffc8c10acf (patch)
treee27a141cfc6a9cc77356b8cba27b41c495d4ee27 /source
parent62d16a23b0ecd72dc624abd7e10b373c40adaa90 (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>
Diffstat (limited to 'source')
-rw-r--r--source/compiler-core/slang-language-server-protocol.cpp118
-rw-r--r--source/compiler-core/slang-language-server-protocol.h242
-rw-r--r--source/compiler-core/slang-lexer.cpp7
-rw-r--r--source/core/slang-string.cpp2
-rw-r--r--source/slang/slang-check-expr.cpp18
-rw-r--r--source/slang/slang-language-server-auto-format.cpp213
-rw-r--r--source/slang/slang-language-server-auto-format.h25
-rw-r--r--source/slang/slang-language-server-inlay-hints.cpp120
-rw-r--r--source/slang/slang-language-server-inlay-hints.h21
-rw-r--r--source/slang/slang-language-server.cpp292
-rw-r--r--source/slang/slang-language-server.h18
-rw-r--r--source/slang/slang-parser.cpp33
12 files changed, 1085 insertions, 24 deletions
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'>&#13;&#10; </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