summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--source/compiler-core/slang-language-server-protocol.cpp38
-rw-r--r--source/compiler-core/slang-language-server-protocol.h83
-rw-r--r--source/core/slang-io.h6
-rw-r--r--source/core/slang-string.cpp15
-rw-r--r--source/core/slang-string.h1
-rw-r--r--source/slang/slang-language-server-completion.cpp297
-rw-r--r--source/slang/slang-language-server-completion.h14
-rw-r--r--source/slang/slang-language-server.cpp127
-rw-r--r--source/slang/slang-language-server.h5
-rw-r--r--source/slang/slang-workspace-version.cpp3
10 files changed, 528 insertions, 61 deletions
diff --git a/source/compiler-core/slang-language-server-protocol.cpp b/source/compiler-core/slang-language-server-protocol.cpp
index d2950b164..628baf08d 100644
--- a/source/compiler-core/slang-language-server-protocol.cpp
+++ b/source/compiler-core/slang-language-server-protocol.cpp
@@ -150,6 +150,17 @@ static const StructRttiInfo _makeRangeRtti()
}
const StructRttiInfo Range::g_rttiInfo = _makeRangeRtti();
+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 _makeDidOpenTextDocumentRtti()
{
DidOpenTextDocumentParams obj;
@@ -460,6 +471,22 @@ static const StructRttiInfo _makeCompletionItemRtti()
}
const StructRttiInfo CompletionItem::g_rttiInfo = _makeCompletionItemRtti();
+static const StructRttiInfo _makeTextEditCompletionItemRtti()
+{
+ TextEditCompletionItem obj;
+ StructRttiBuilder builder(&obj, "LanguageServerProtocol::TextEditCompletionItem", nullptr);
+ builder.addField("label", &obj.label, StructRttiInfo::Flag::Optional);
+ builder.addField("detail", &obj.detail, StructRttiInfo::Flag::Optional);
+ builder.addField("kind", &obj.kind, StructRttiInfo::Flag::Optional);
+ builder.addField("documentation", &obj.documentation, StructRttiInfo::Flag::Optional);
+ builder.addField("data", &obj.data, StructRttiInfo::Flag::Optional);
+ builder.addField("textEdit", &obj.textEdit, StructRttiInfo::Flag::Optional);
+ builder.addField("commitCharacters", &obj.commitCharacters, StructRttiInfo::Flag::Optional);
+ builder.ignoreUnknownFields();
+ return builder.make();
+}
+const StructRttiInfo TextEditCompletionItem::g_rttiInfo = _makeTextEditCompletionItemRtti();
+
static const StructRttiInfo _makeSemanticTokensParamsRtti()
{
SemanticTokensParams obj;
@@ -641,17 +668,6 @@ 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;
diff --git a/source/compiler-core/slang-language-server-protocol.h b/source/compiler-core/slang-language-server-protocol.h
index fbc79acca..316c15c04 100644
--- a/source/compiler-core/slang-language-server-protocol.h
+++ b/source/compiler-core/slang-language-server-protocol.h
@@ -176,6 +176,24 @@ struct Range
static const StructRttiInfo g_rttiInfo;
};
+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;
+
+};
+
struct DidOpenTextDocumentParams
{
TextDocumentItem textDocument;
@@ -605,6 +623,53 @@ struct CompletionItem
static const StructRttiInfo g_rttiInfo;
};
+struct TextEditCompletionItem
+{
+ /**
+ * The label of this completion item.
+ *
+ * The label property is also by default the text that
+ * is inserted when selecting this completion.
+ *
+ * If label details are provided the label itself should
+ * be an unqualified name of the completion item.
+ */
+ String label;
+
+ /**
+ * The kind of this completion item. Based of the kind
+ * an icon is chosen by the editor. The standardized set
+ * of available values is defined in `CompletionItemKind`.
+ */
+ CompletionItemKind kind = CompletionItemKind(0);
+
+ /**
+ * A human-readable string with additional information
+ * about this item, like type or symbol information.
+ */
+ String detail;
+
+ /**
+ * A human-readable string that represents a doc-comment.
+ */
+ MarkupContent documentation;
+
+ TextEdit textEdit;
+
+ /**
+ * An optional set of characters that when pressed while this completion is
+ * active will accept it first and then type that character. *Note* that all
+ * commit characters should have `length=1` and that superfluous characters
+ * will be ignored.
+ */
+ List<String> commitCharacters;
+
+ // Additional data.
+ String data;
+
+ static const StructRttiInfo g_rttiInfo;
+};
+
struct SemanticTokensParams : WorkDoneProgressParams
{
TextDocumentIdentifier textDocument;
@@ -921,24 +986,6 @@ struct InlayHintParams
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;
diff --git a/source/core/slang-io.h b/source/core/slang-io.h
index 43812aaf9..4de246289 100644
--- a/source/core/slang-io.h
+++ b/source/core/slang-io.h
@@ -63,6 +63,12 @@ namespace Slang
static const char kPathDelimiter = '/';
+#if SLANG_WINDOWS_FAMILY
+ static const char kOSCanonicalPathDelimiter = '\\';
+#else
+ static const char kOSCanonicalPathDelimiter = '/';
+#endif
+
/// Finds all all the items in the specified directory, that matches the pattern.
///
/// @param directoryPath The directory to do the search in. If the directory is not found, SLANG_E_NOT_FOUND is returned
diff --git a/source/core/slang-string.cpp b/source/core/slang-string.cpp
index 03de07ee1..c02ba048d 100644
--- a/source/core/slang-string.cpp
+++ b/source/core/slang-string.cpp
@@ -134,7 +134,7 @@ namespace Slang
if (otherSize > thisSize)
return false;
- return UnownedStringSlice(begin(), begin() + otherSize) == other;
+ return head(otherSize) == other;
}
bool UnownedStringSlice::startsWith(char const* str) const
@@ -142,6 +142,17 @@ namespace Slang
return startsWith(UnownedTerminatedStringSlice(str));
}
+ bool UnownedStringSlice::startsWithCaseInsensitive(UnownedStringSlice const& other) const
+ {
+ UInt thisSize = getLength();
+ UInt otherSize = other.getLength();
+
+ if (otherSize > thisSize)
+ return false;
+
+ return head(otherSize).caseInsensitiveEquals(other);
+ }
+
bool UnownedStringSlice::endsWith(UnownedStringSlice const& other) const
{
@@ -474,6 +485,8 @@ namespace Slang
{
auto oldLength = getLength();
auto textLength = textEnd - textBegin;
+ if (textLength <= 0)
+ return;
auto newLength = oldLength + textLength;
diff --git a/source/core/slang-string.h b/source/core/slang-string.h
index 59d441b76..69ca7e2c3 100644
--- a/source/core/slang-string.h
+++ b/source/core/slang-string.h
@@ -154,6 +154,7 @@ namespace Slang
/// True if contents is a single char of c
SLANG_FORCE_INLINE bool isChar(char c) const { return getLength() == 1 && m_begin[0] == c; }
+ bool startsWithCaseInsensitive(UnownedStringSlice const& other) const;
bool startsWith(UnownedStringSlice const& other) const;
bool startsWith(char const* str) const;
diff --git a/source/slang/slang-language-server-completion.cpp b/source/slang/slang-language-server-completion.cpp
index 5eee1dba7..adb30df6e 100644
--- a/source/slang/slang-language-server-completion.cpp
+++ b/source/slang/slang-language-server-completion.cpp
@@ -8,6 +8,9 @@
#include "slang-check-impl.h"
#include "slang-syntax.h"
+#include "../core/slang-char-util.h"
+#include <chrono>
+
namespace Slang
{
@@ -94,6 +97,300 @@ SlangResult CompletionContext::tryCompleteAttributes()
return SLANG_OK;
}
+List<LanguageServerProtocol::TextEditCompletionItem> CompletionContext::gatherFileAndModuleCompletionItems(
+ const String& prefixPath,
+ bool translateModuleName,
+ bool isImportString,
+ Index lineIndex,
+ Index fileNameEnd,
+ Index sectionStart,
+ Index sectionEnd,
+ char closingChar)
+{
+ struct FileEnumerationContext
+ {
+ List<LanguageServerProtocol::TextEditCompletionItem> items;
+ HashSet<String> itemSet;
+ CompletionContext* completionContext;
+ String path;
+ String workspaceRoot;
+ bool translateModuleName;
+ bool isImportString;
+ } context;
+ context.completionContext = this;
+ context.translateModuleName = translateModuleName;
+ context.isImportString = isImportString;
+ if (version->workspace->rootDirectories.getCount())
+ context.workspaceRoot = version->workspace->rootDirectories[0];
+ if (context.workspaceRoot.getLength() &&
+ context.workspaceRoot[context.workspaceRoot.getLength() - 1] !=
+ Path::kOSCanonicalPathDelimiter)
+ {
+ context.workspaceRoot = context.workspaceRoot + String(Path::kOSCanonicalPathDelimiter);
+ }
+
+ auto addCandidate = [&](const String& path)
+ {
+ context.path = path;
+ if (path.getUnownedSlice().endsWithCaseInsensitive(prefixPath.getUnownedSlice()))
+ {
+ OSFileSystem::getExtSingleton()->enumeratePathContents(
+ path.getBuffer(),
+ [](SlangPathType pathType, const char* name, void* userData)
+ {
+ FileEnumerationContext* context = (FileEnumerationContext*)userData;
+ LanguageServerProtocol::TextEditCompletionItem item;
+ if (pathType == SLANG_PATH_TYPE_DIRECTORY)
+ {
+ item.label = name;
+ item.kind = LanguageServerProtocol::kCompletionItemKindFolder;
+ if (item.label.indexOf('.') != -1)
+ return;
+ }
+ else
+ {
+ auto nameSlice = UnownedStringSlice(name);
+ if (context->isImportString || context->translateModuleName)
+ {
+ if (!nameSlice.endsWithCaseInsensitive(".slang"))
+ return;
+ }
+ StringBuilder nameSB;
+ auto fileName = UnownedStringSlice(name);
+ if (context->translateModuleName || context->isImportString)
+ fileName = fileName.head(nameSlice.getLength() - 6);
+ for (auto ch : fileName)
+ {
+ if (context->translateModuleName)
+ {
+ switch (ch)
+ {
+ case '-':
+ nameSB.appendChar('_');
+ break;
+ case '.':
+ // Ignore any file items that contains a "."
+ return;
+ default:
+ nameSB.appendChar(ch);
+ break;
+ }
+ }
+ else
+ {
+ nameSB.appendChar(ch);
+ }
+ }
+ item.label = nameSB.ProduceString();
+ item.kind = LanguageServerProtocol::kCompletionItemKindFile;
+ }
+ if (item.label.getLength())
+ {
+ auto key = String(item.kind) + item.label;
+ if (context->itemSet.Add(key))
+ {
+ item.detail = Path::combine(context->path, String(name));
+ Path::getCanonical(item.detail, item.detail);
+
+ if (item.detail.getUnownedSlice().startsWithCaseInsensitive(context->workspaceRoot.getUnownedSlice()))
+ {
+ item.detail = item.detail.getUnownedSlice().tail(context->workspaceRoot.getLength());
+ }
+ context->items.add(item);
+ }
+ }
+ },
+ &context);
+ }
+ };
+
+ // A big workspace may take a long time to enumerate, thus we limit the amount
+ // of time allowed to scan the file directory.
+
+ auto startTime = std::chrono::high_resolution_clock::now();
+ bool isIncomplete = false;
+
+ for (auto& searchPath : this->version->workspace->additionalSearchPaths)
+ {
+ auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count();
+ if (elapsedTime > 200)
+ {
+ isIncomplete = true;
+ break;
+ }
+ addCandidate(searchPath);
+ }
+ if (this->version->workspace->searchInWorkspace)
+ {
+ for (auto& searchPath : this->version->workspace->workspaceSearchPaths)
+ {
+ auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count();
+ if (elapsedTime > 200)
+ {
+ isIncomplete = true;
+ break;
+ }
+ addCandidate(searchPath);
+ }
+ }
+ for (auto& item : context.items)
+ {
+ item.textEdit.range.start.line = (int)lineIndex;
+ item.textEdit.range.end.line = (int)lineIndex;
+ if (!translateModuleName && item.kind == LanguageServerProtocol::kCompletionItemKindFile)
+ {
+ item.textEdit.range.start.character = (int)sectionStart;
+ item.textEdit.range.end.character = (int)fileNameEnd;
+ item.textEdit.newText = item.label;
+ if (closingChar)
+ item.textEdit.newText.appendChar(closingChar);
+ }
+ else
+ {
+ item.textEdit.newText = item.label;
+ item.textEdit.range.start.character = (int)sectionStart;
+ item.textEdit.range.end.character = (int)sectionEnd;
+ }
+ }
+
+ if (commitCharacterBehavior != CommitCharacterBehavior::Disabled && !isIncomplete)
+ {
+ for (auto& item : context.items)
+ {
+ for (auto ch : getCommitChars())
+ item.commitCharacters.add(ch);
+ }
+ }
+ return context.items;
+}
+
+SlangResult CompletionContext::tryCompleteImport()
+{
+ static auto importStr = UnownedStringSlice("import ");
+ auto lineContent = doc->getLine(line);
+ Index pos = lineContent.indexOf(importStr);
+ if (pos == -1)
+ return SLANG_FAIL;
+ auto lineBeforeImportKeyword = lineContent.head(pos).trim();
+ if (lineBeforeImportKeyword.getLength() != 0 && lineBeforeImportKeyword != "__exported")
+ return SLANG_FAIL;
+
+ pos += importStr.getLength();
+ while (pos < lineContent.getLength() && pos < col - 1 && CharUtil::isWhitespace(lineContent[pos]))
+ pos++;
+ if (pos < lineContent.getLength() && lineContent[pos] == '"')
+ {
+ return tryCompleteRawFileName(lineContent, pos, true);
+ }
+
+ StringBuilder prefixSB;
+ Index lastPos = col - 2;
+ if (lastPos < 0)
+ return SLANG_FAIL;
+ while (lastPos >= pos && lineContent[lastPos] != '.')
+ {
+ if (lineContent[lastPos] == ';')
+ return SLANG_FAIL;
+ lastPos--;
+ }
+ UnownedStringSlice prefixSlice;
+ if (lastPos > pos)
+ prefixSlice = lineContent.subString(pos, lastPos - pos);
+ Index sectionEnd = col - 1;
+ while (sectionEnd < lineContent.getLength() && (lineContent[sectionEnd] != '.' && lineContent[sectionEnd] != ';'))
+ sectionEnd++;
+ Index fileNameEnd = sectionEnd;
+ while (fileNameEnd < lineContent.getLength() && lineContent[fileNameEnd] != ';')
+ fileNameEnd++;
+ for (auto ch : prefixSlice)
+ {
+ if (ch == '.')
+ prefixSB.appendChar(Path::kOSCanonicalPathDelimiter);
+ else if (ch == '_')
+ prefixSB.appendChar('-');
+ else
+ prefixSB.appendChar(ch);
+ }
+ auto prefix = prefixSB.ProduceString();
+ auto items = gatherFileAndModuleCompletionItems(
+ prefix, true, false, line - 1, fileNameEnd, lastPos + 1, sectionEnd, 0);
+ server->m_connection->sendResult(&items, responseId);
+ return SLANG_OK;
+}
+
+SlangResult CompletionContext::tryCompleteRawFileName(UnownedStringSlice lineContent, Index pos, bool isImportString)
+{
+ while (pos < lineContent.getLength() && (lineContent[pos] != '\"' && lineContent[pos] != '<'))
+ pos++;
+ char closingChar = '"';
+ if (pos < lineContent.getLength() && lineContent[pos] == '<')
+ closingChar = '>';
+ pos++;
+ StringBuilder prefixSB;
+ Index lastPos = col - 2;
+ if (lastPos < 0)
+ return SLANG_FAIL;
+ while (lastPos >= pos && (lineContent[lastPos] != '/' && lineContent[lastPos] != '\\'))
+ {
+ if (lineContent[lastPos] == '\"' || lineContent[lastPos] == '>')
+ return SLANG_FAIL;
+ lastPos--;
+ }
+ Index sectionEnd = col - 1;
+ if (sectionEnd < 0)
+ return SLANG_FAIL;
+ while (sectionEnd < lineContent.getLength() &&
+ (lineContent[sectionEnd] != '\"' && lineContent[sectionEnd] != '>' &&
+ lineContent[sectionEnd] != '/' && lineContent[sectionEnd] != '\\'))
+ {
+ sectionEnd++;
+ }
+ Index fileNameEnd = sectionEnd;
+ while (fileNameEnd < lineContent.getLength() && lineContent[fileNameEnd] != ';')
+ fileNameEnd++;
+ UnownedStringSlice prefixSlice;
+ if (lastPos > pos)
+ prefixSlice = lineContent.subString(pos, lastPos - pos);
+ for (auto ch : prefixSlice)
+ {
+ if (ch == '/' || ch == '\\')
+ prefixSB.appendChar(Path::kOSCanonicalPathDelimiter);
+ else
+ prefixSB.appendChar(ch);
+ }
+ auto prefix = prefixSB.ProduceString();
+ auto items = gatherFileAndModuleCompletionItems(
+ prefix,
+ false,
+ isImportString,
+ line - 1,
+ fileNameEnd,
+ lastPos + 1,
+ sectionEnd,
+ closingChar);
+ server->m_connection->sendResult(&items, responseId);
+ return SLANG_OK;
+}
+
+SlangResult CompletionContext::tryCompleteInclude()
+{
+ auto lineContent = doc->getLine(line);
+ if (!lineContent.startsWith("#"))
+ return SLANG_FAIL;
+
+ static auto includeStr = UnownedStringSlice("include ");
+ Index pos = lineContent.indexOf(includeStr);
+ if (pos == -1)
+ return SLANG_FAIL;
+ for (Index i = 1; i < pos; i++)
+ {
+ if (!CharUtil::isWhitespace(lineContent[i]))
+ return SLANG_FAIL;
+ }
+ pos += includeStr.getLength();
+ return tryCompleteRawFileName(lineContent, pos, false);
+}
+
SlangResult CompletionContext::tryCompleteMemberAndSymbol()
{
List<LanguageServerProtocol::CompletionItem> items = collectMembersAndSymbols();
diff --git a/source/slang/slang-language-server-completion.h b/source/slang/slang-language-server-completion.h
index 8c0c782ee..45082e0d3 100644
--- a/source/slang/slang-language-server-completion.h
+++ b/source/slang/slang-language-server-completion.h
@@ -31,10 +31,24 @@ struct CompletionContext
SlangResult tryCompleteMemberAndSymbol();
SlangResult tryCompleteHLSLSemantic();
SlangResult tryCompleteAttributes();
+ SlangResult tryCompleteImport();
+ SlangResult tryCompleteInclude();
+ SlangResult tryCompleteRawFileName(UnownedStringSlice lineContent, Index fileNameStartPos, bool isImportString);
+
+
List<LanguageServerProtocol::CompletionItem> collectMembersAndSymbols();
List<LanguageServerProtocol::CompletionItem> createSwizzleCandidates(
Type* baseType, IntegerLiteralValue elementCount[2]);
List<LanguageServerProtocol::CompletionItem> collectAttributes();
+ List<LanguageServerProtocol::TextEditCompletionItem> gatherFileAndModuleCompletionItems(
+ const String& prefixPath,
+ bool translateModuleName,
+ bool isImportString,
+ Index lineIndex,
+ Index fileNameEnd,
+ Index sectionStart,
+ Index sectionEnd,
+ char closingChar);
};
} // namespace Slang
diff --git a/source/slang/slang-language-server.cpp b/source/slang/slang-language-server.cpp
index 7dc2f6dcc..673b91593 100644
--- a/source/slang/slang-language-server.cpp
+++ b/source/slang/slang-language-server.cpp
@@ -123,6 +123,8 @@ SlangResult LanguageServer::parseNextMessage()
result.capabilities.completionProvider.triggerCharacters.add(".");
result.capabilities.completionProvider.triggerCharacters.add(":");
result.capabilities.completionProvider.triggerCharacters.add("[");
+ result.capabilities.completionProvider.triggerCharacters.add("\"");
+ result.capabilities.completionProvider.triggerCharacters.add("/");
result.capabilities.completionProvider.resolveProvider = true;
result.capabilities.completionProvider.workDoneToken = "";
result.capabilities.semanticTokensProvider.full = true;
@@ -347,6 +349,26 @@ void appendDefinitionLocation(StringBuilder& sb, Workspace* workspace, const Hum
sb << "Defined in " << pathSlice << "(" << loc.line << ")\n";
}
+HumaneSourceLoc getModuleLoc(SourceManager* manager, ModuleDecl* moduleDecl)
+{
+ if (moduleDecl)
+ {
+ if (moduleDecl->members.getCount() &&
+ moduleDecl->members[0])
+ {
+ auto loc = moduleDecl->members[0]->loc;
+ if (loc.isValid())
+ {
+ auto location = manager->getHumaneLoc(loc, SourceLocType::Actual);
+ location.line = 1;
+ location.column = 1;
+ return location;
+ }
+ }
+ }
+ return HumaneSourceLoc();
+}
+
SlangResult LanguageServer::hover(
const LanguageServerProtocol::HoverParams& args, const JSONValue& responseId)
{
@@ -429,6 +451,27 @@ SlangResult LanguageServer::hover(
LookupResultItem& item = overloadedExpr->lookupResult2.item;
fillDeclRefHoverInfo(item.declRef);
}
+ else if (auto importDecl = as<ImportDecl>(leafNode))
+ {
+ auto moduleLoc = getModuleLoc(version->linkage->getSourceManager(), importDecl->importedModuleDecl);
+ if (moduleLoc.pathInfo.hasFoundPath())
+ {
+ String path = moduleLoc.pathInfo.foundPath;
+ Path::getCanonical(path, path);
+ sb << path;
+ auto humaneLoc = version->linkage->getSourceManager()->getHumaneLoc(
+ importDecl->startLoc, SourceLocType::Actual);
+ Index utf16Line, utf16Col;
+ doc->oneBasedUTF8LocToZeroBasedUTF16Loc(humaneLoc.line, humaneLoc.column, utf16Line, utf16Col);
+ hover.range.start.line = (int)utf16Line;
+ hover.range.start.character = (int)utf16Col;
+ humaneLoc = version->linkage->getSourceManager()->getHumaneLoc(
+ importDecl->endLoc, SourceLocType::Actual);
+ doc->oneBasedUTF8LocToZeroBasedUTF16Loc(humaneLoc.line, humaneLoc.column, utf16Line, utf16Col);
+ hover.range.end.line = (int)utf16Line;
+ hover.range.end.character = (int)utf16Col;
+ }
+ }
else if (auto decl = as<Decl>(leafNode))
{
fillDeclRefHoverInfo(DeclRef<Decl>(decl, nullptr));
@@ -529,18 +572,10 @@ SlangResult LanguageServer::gotoDefinition(
}
else if (auto importDecl = as<ImportDecl>(leafNode))
{
- if (importDecl->importedModuleDecl)
+ auto location = getModuleLoc(version->linkage->getSourceManager(), importDecl->importedModuleDecl);
+ if (location.pathInfo.hasFoundPath())
{
- if (importDecl->importedModuleDecl->members.getCount() &&
- importDecl->importedModuleDecl->members[0])
- {
- auto loc = importDecl->importedModuleDecl->members[0]->loc;
- if (loc.isValid())
- {
- auto location = version->linkage->getSourceManager()->getHumaneLoc(loc, SourceLocType::Actual);
- locations.add(LocationResult{location, 0});
- }
- }
+ locations.add(LocationResult{ location, 0 });
}
}
if (locations.getCount() == 0)
@@ -630,38 +665,58 @@ SlangResult LanguageServer::completion(
return SLANG_OK;
}
- // Insert a completion request token at cursor position.
- auto originalText = doc->getText();
- StringBuilder newText;
- newText << originalText.getUnownedSlice().head(cursorOffset + 1) << "#?"
- << originalText.getUnownedSlice().tail(cursorOffset + 1);
- doc->setText(newText.ProduceString());
- auto restoreDocText = makeDeferred([&]() { doc->setText(originalText); });
-
// Always create a new workspace version for the completion request since we
// will use a modified source.
auto version = m_workspace->createVersionForCompletion();
auto moduleName = getMangledNameFromNameString(canonicalPath.getUnownedSlice());
version->linkage->contentAssistInfo.cursorLine = utf8Line;
version->linkage->contentAssistInfo.cursorCol = utf8Col;
- Module* parsedModule = version->getOrLoadModule(canonicalPath);
- if (!parsedModule)
- {
- m_connection->sendResult(NullResponse::get(), responseId);
- return SLANG_OK;
- }
-
Slang::CompletionContext context;
context.server = this;
context.cursorOffset = cursorOffset;
context.version = version;
context.doc = doc.Ptr();
- context.parsedModule = parsedModule;
context.responseId = responseId;
context.canonicalPath = canonicalPath.getUnownedSlice();
context.line = utf8Line;
context.col = utf8Col;
context.commitCharacterBehavior = m_commitCharacterBehavior;
+
+ if (SLANG_SUCCEEDED(context.tryCompleteInclude()))
+ {
+ return SLANG_OK;
+ }
+ if (SLANG_SUCCEEDED(context.tryCompleteImport()))
+ {
+ return SLANG_OK;
+ }
+
+ if (args.context.triggerKind ==
+ LanguageServerProtocol::kCompletionTriggerKindTriggerCharacter &&
+ (args.context.triggerCharacter == "\"" || args.context.triggerCharacter == "/"))
+ {
+ // Trigger characters '"' and '/' are for include only.
+ m_connection->sendResult(NullResponse::get(), responseId);
+ return SLANG_OK;
+ }
+
+
+ // Insert a completion request token at cursor position.
+ auto originalText = doc->getText();
+ StringBuilder newText;
+ newText << originalText.getUnownedSlice().head(cursorOffset + 1) << "#?"
+ << originalText.getUnownedSlice().tail(cursorOffset + 1);
+ doc->setText(newText.ProduceString());
+ auto restoreDocText = makeDeferred([&]() { doc->setText(originalText); });
+
+ Module* parsedModule = version->getOrLoadModule(canonicalPath);
+ if (!parsedModule)
+ {
+ m_connection->sendResult(NullResponse::get(), responseId);
+ return SLANG_OK;
+ }
+
+ context.parsedModule = parsedModule;
if (SLANG_SUCCEEDED(context.tryCompleteAttributes()))
{
return SLANG_OK;
@@ -689,8 +744,19 @@ SlangResult LanguageServer::completion(
}
SlangResult LanguageServer::completionResolve(
- const LanguageServerProtocol::CompletionItem& args, const JSONValue& responseId)
+ const LanguageServerProtocol::CompletionItem& args, const LanguageServerProtocol::TextEditCompletionItem& editItem, const JSONValue& responseId)
{
+ if (args.data.getLength() == 0)
+ {
+ if (editItem.textEdit.newText.getLength())
+ {
+ m_connection->sendResult(&editItem, responseId);
+ return SLANG_OK;
+ }
+ m_connection->sendResult(&args, responseId);
+ return SLANG_OK;
+ }
+
LanguageServerProtocol::CompletionItem resolvedItem = args;
int itemId = StringToInt(args.data);
auto version = m_workspace->getCurrentCompletionVersion();
@@ -1455,6 +1521,9 @@ SlangResult LanguageServer::queueJSONCall(JSONRPCCall call)
Slang::LanguageServerProtocol::CompletionItem args;
SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id));
cmd.completionResolveArgs = args;
+ Slang::LanguageServerProtocol::TextEditCompletionItem editArgs;
+ SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &editArgs, call.id));
+ cmd.textEditCompletionResolveArgs = editArgs;
}
else if (call.method == DocumentSymbolParams::methodName)
{
@@ -1541,7 +1610,7 @@ SlangResult LanguageServer::runCommand(Command& call)
}
else if (call.method == "completionItem/resolve")
{
- return completionResolve(call.completionResolveArgs.get(), call.id);
+ return completionResolve(call.completionResolveArgs.get(), call.textEditCompletionResolveArgs.get(), call.id);
}
else if (call.method == DocumentSymbolParams::methodName)
{
diff --git a/source/slang/slang-language-server.h b/source/slang/slang-language-server.h
index 7f297d18b..26894b3bd 100644
--- a/source/slang/slang-language-server.h
+++ b/source/slang/slang-language-server.h
@@ -58,6 +58,7 @@ struct Command
Optional<LanguageServerProtocol::CompletionParams> completionArgs;
Optional<LanguageServerProtocol::CompletionItem> completionResolveArgs;
+ Optional<LanguageServerProtocol::TextEditCompletionItem> textEditCompletionResolveArgs;
Optional<LanguageServerProtocol::DocumentSymbolParams> documentSymbolArgs;
Optional<LanguageServerProtocol::InlayHintParams> inlayHintArgs;
Optional<LanguageServerProtocol::DocumentFormattingParams> formattingArgs;
@@ -87,7 +88,7 @@ public:
Verbose
};
bool m_initialized = false;
- TraceOptions m_traceOptions = TraceOptions::Messages;
+ TraceOptions m_traceOptions = TraceOptions::Off;
CommitCharacterBehavior m_commitCharacterBehavior = CommitCharacterBehavior::MembersOnly;
RefPtr<JSONRPCConnection> m_connection;
ComPtr<slang::IGlobalSession> m_session;
@@ -115,7 +116,7 @@ public:
SlangResult completion(
const LanguageServerProtocol::CompletionParams& args, const JSONValue& responseId);
SlangResult completionResolve(
- const LanguageServerProtocol::CompletionItem& args, const JSONValue& responseId);
+ const LanguageServerProtocol::CompletionItem& args, const LanguageServerProtocol::TextEditCompletionItem& editItem, const JSONValue& responseId);
SlangResult semanticTokens(
const LanguageServerProtocol::SemanticTokensParams& args, const JSONValue& responseId);
SlangResult signatureHelp(
diff --git a/source/slang/slang-workspace-version.cpp b/source/slang/slang-workspace-version.cpp
index f09b8666d..2451290f5 100644
--- a/source/slang/slang-workspace-version.cpp
+++ b/source/slang/slang-workspace-version.cpp
@@ -167,6 +167,9 @@ void Workspace::init(List<URI> rootDirURI, slang::IGlobalSession* globalSession)
auto nameSlice = UnownedStringSlice(name);
if (pathType == SLANG_PATH_TYPE_DIRECTORY)
{
+ // Ignore directories starting with '.'
+ if (nameSlice.getLength() && nameSlice[0] == '.')
+ return;
dirContext->workList.add(Path::combine(dirContext->currentPath, name));
}
else if (nameSlice.endsWithCaseInsensitive(".slang") || nameSlice.endsWithCaseInsensitive(".hlsl"))