summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/compiler-core/slang-language-server-protocol.cpp30
-rw-r--r--source/compiler-core/slang-language-server-protocol.h87
-rw-r--r--source/slang/slang-language-server-ast-lookup.cpp6
-rw-r--r--source/slang/slang-language-server-document-symbols.cpp213
-rw-r--r--source/slang/slang-language-server-document-symbols.h14
-rw-r--r--source/slang/slang-language-server-semantic-tokens.cpp6
-rw-r--r--source/slang/slang-language-server.cpp38
-rw-r--r--source/slang/slang-language-server.h3
-rw-r--r--source/slang/slang-parser.cpp17
-rw-r--r--source/slang/slang-workspace-version.cpp6
10 files changed, 413 insertions, 7 deletions
diff --git a/source/compiler-core/slang-language-server-protocol.cpp b/source/compiler-core/slang-language-server-protocol.cpp
index 713d209dd..51799f05e 100644
--- a/source/compiler-core/slang-language-server-protocol.cpp
+++ b/source/compiler-core/slang-language-server-protocol.cpp
@@ -215,6 +215,7 @@ static const StructRttiInfo _makeServerCapabilitiesRtti()
builder.addField("completionProvider", &obj.completionProvider);
builder.addField("semanticTokensProvider", &obj.semanticTokensProvider);
builder.addField("signatureHelpProvider", &obj.signatureHelpProvider);
+ builder.addField("documentSymbolProvider", &obj.documentSymbolProvider);
builder.ignoreUnknownFields();
return builder.make();
}
@@ -574,6 +575,35 @@ const StructRttiInfo LogMessageParams::g_rttiInfo = _makeLogMessageParamsRtti();
const UnownedStringSlice LogMessageParams::methodName =
UnownedStringSlice::fromLiteral("window/logMessage");
+static const StructRttiInfo _makeDocumentSymbolParamsRtti()
+{
+ DocumentSymbolParams obj;
+ StructRttiBuilder builder(
+ &obj, "LanguageServerProtocol::DocumentSymbolParams", &WorkDoneProgressParams::g_rttiInfo);
+ builder.addField("textDocument", &obj.textDocument);
+ builder.ignoreUnknownFields();
+ return builder.make();
+}
+const StructRttiInfo DocumentSymbolParams::g_rttiInfo = _makeDocumentSymbolParamsRtti();
+const UnownedStringSlice DocumentSymbolParams::methodName =
+ UnownedStringSlice::fromLiteral("textDocument/documentSymbol");
+
+static const StructRttiInfo _makeDocumentSymbolRtti()
+{
+ DocumentSymbol obj;
+ StructRttiBuilder builder(
+ &obj, "LanguageServerProtocol::DocumentSymbol", nullptr);
+ builder.addField("name", &obj.name);
+ builder.addField("detail", &obj.detail);
+ builder.addField("kind", &obj.kind);
+ builder.addField("range", &obj.range);
+ builder.addField("selectionRange", &obj.selectionRange);
+ builder.addField("children", &obj.children);
+ builder.ignoreUnknownFields();
+ return builder.make();
+}
+const StructRttiInfo DocumentSymbol::g_rttiInfo = _makeDocumentSymbolRtti();
+
} // namespace LanguageServerProtocol
}
diff --git a/source/compiler-core/slang-language-server-protocol.h b/source/compiler-core/slang-language-server-protocol.h
index 11446bd0b..7d9d8d5cb 100644
--- a/source/compiler-core/slang-language-server-protocol.h
+++ b/source/compiler-core/slang-language-server-protocol.h
@@ -238,6 +238,7 @@ struct ServerCapabilities
TextDocumentSyncOptions textDocumentSync;
bool hoverProvider = false;
bool definitionProvider = false;
+ bool documentSymbolProvider = false;
CompletionOptions completionProvider;
SemanticTokensOptions semanticTokensProvider;
SignatureHelpOptions signatureHelpProvider;
@@ -740,5 +741,91 @@ struct LogMessageParams
static const UnownedStringSlice methodName;
};
+struct DocumentSymbolParams : WorkDoneProgressParams
+{
+ /**
+ * The text document.
+ */
+ TextDocumentIdentifier textDocument;
+
+ static const StructRttiInfo g_rttiInfo;
+ static const UnownedStringSlice methodName;
+};
+
+typedef int SymbolKind;
+const int kSymbolKindFile = 1;
+const int kSymbolKindModule = 2;
+const int kSymbolKindNamespace = 3;
+const int kSymbolKindPackage = 4;
+const int kSymbolKindClass = 5;
+const int kSymbolKindMethod = 6;
+const int kSymbolKindProperty = 7;
+const int kSymbolKindField = 8;
+const int kSymbolKindConstructor = 9;
+const int kSymbolKindEnum = 10;
+const int kSymbolKindInterface = 11;
+const int kSymbolKindFunction = 12;
+const int kSymbolKindVariable = 13;
+const int kSymbolKindConstant = 14;
+const int kSymbolKindString = 15;
+const int kSymbolKindNumber = 16;
+const int kSymbolKindBoolean = 17;
+const int kSymbolKindArray = 18;
+const int kSymbolKindObject = 19;
+const int kSymbolKindKey = 20;
+const int kSymbolKindNull = 21;
+const int kSymbolKindEnumMember = 22;
+const int kSymbolKindStruct = 23;
+const int kSymbolKindEvent = 24;
+const int kSymbolKindOperator = 25;
+const int kSymbolKindTypeParameter = 26;
+
+/**
+ * Represents programming constructs like variables, classes, interfaces etc.
+ * that appear in a document. Document symbols can be hierarchical and they
+ * 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 {
+
+ /**
+ * 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
+ * white spaces.
+ */
+ String name;
+
+ /**
+ * More detail for this symbol, e.g the signature of a function.
+ */
+ String detail;
+
+ /**
+ * The kind of this symbol.
+ */
+ SymbolKind kind;
+
+ /**
+ * The range enclosing this symbol not including leading/trailing whitespace
+ * but everything else like comments. This information is typically used to
+ * determine if the clients cursor is inside the symbol to reveal in the
+ * symbol in the UI.
+ */
+ Range range;
+
+ /**
+ * The range that should be selected and revealed when this symbol is being
+ * picked, e.g. the name of a function. Must be contained by the `range`.
+ */
+ Range selectionRange;
+
+ /**
+ * Children of this symbol, e.g. properties of a class.
+ */
+ List<DocumentSymbol> children;
+
+ static const StructRttiInfo g_rttiInfo;
+};
+
} // namespace LanguageServerProtocol
} // namespace Slang
diff --git a/source/slang/slang-language-server-ast-lookup.cpp b/source/slang/slang-language-server-ast-lookup.cpp
index 3a35e699b..0e5a68687 100644
--- a/source/slang/slang-language-server-ast-lookup.cpp
+++ b/source/slang/slang-language-server-ast-lookup.cpp
@@ -542,6 +542,12 @@ bool _findAstNodeImpl(ASTLookupContext& context, SyntaxNode* node)
if (visitor.dispatchIfNotNull(typedefDecl->type.exp))
return true;
}
+ else if (auto extDecl = as<ExtensionDecl>(node))
+ {
+ ASTLookupExprVisitor visitor(&context);
+ if (visitor.dispatchIfNotNull(extDecl->targetType.exp))
+ return true;
+ }
for (auto modifier : decl->modifiers)
{
if (auto hlslSemantic = as<HLSLSemantic>(modifier))
diff --git a/source/slang/slang-language-server-document-symbols.cpp b/source/slang/slang-language-server-document-symbols.cpp
new file mode 100644
index 000000000..b2b213b02
--- /dev/null
+++ b/source/slang/slang-language-server-document-symbols.cpp
@@ -0,0 +1,213 @@
+#include "slang-language-server-document-symbols.h"
+
+namespace Slang
+{
+ struct GetDocumentSymbolContext
+ {
+ HashSet<Decl*> processedDecls;
+ DocumentVersion* doc;
+ Linkage* linkage;
+ UnownedStringSlice fileName;
+ };
+
+ static LanguageServerProtocol::SymbolKind _getSymbolKind(Decl* decl)
+ {
+ if (as<StructDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindStruct;
+ }
+ if (as<ClassDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindClass;
+ }
+ if (as<InterfaceDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindInterface;
+ }
+ if (as<FuncDecl>(decl))
+ {
+ return as<AggTypeDecl>(decl->parentDecl) ? LanguageServerProtocol::kSymbolKindMethod
+ : LanguageServerProtocol::kSymbolKindFunction;
+ }
+ if (as<PropertyDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindProperty;
+ }
+ if (as<ConstructorDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindConstructor;
+ }
+ if (as<AssocTypeDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindTypeParameter;
+ }
+ if (as<VarDeclBase>(decl))
+ {
+ if (decl->findModifier<ConstModifier>())
+ return LanguageServerProtocol::kSymbolKindConstant;
+ return as<AggTypeDecl>(decl->parentDecl) ? LanguageServerProtocol::kSymbolKindField
+ : LanguageServerProtocol::kSymbolKindVariable;
+ }
+ if (as<TypeDefDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindClass;
+ }
+ if (as<GenericTypeParamDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindTypeParameter;
+ }
+ if (as<GenericValueParamDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindConstant;
+ }
+ if (as<EnumDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindEnum;
+ }
+ if (as<EnumCaseDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindEnumMember;
+ }
+ if (as<NamespaceDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindNamespace;
+ }
+ if (as<ExtensionDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindClass;
+ }
+ if (as<SubscriptDecl>(decl))
+ {
+ return LanguageServerProtocol::kSymbolKindOperator;
+ }
+ return -1;
+ }
+ static SourceLoc _findClosingSourceLoc(Decl* decl)
+ {
+ if (auto func = as<FunctionDeclBase>(decl))
+ {
+ if (auto block = as<BlockStmt>(func->body))
+ {
+ return block->closingSourceLoc;
+ }
+ else if (func->body)
+ {
+ return func->body->loc;
+ }
+ }
+ if (auto container = as<ContainerDecl>(decl))
+ {
+ return container->closingSourceLoc;
+ }
+ return SourceLoc();
+ }
+
+ static NameLoc _getDeclRefExprNameLoc(Expr* expr)
+ {
+ if (auto varExpr = as<VarExpr>(expr))
+ {
+ return NameLoc(varExpr->name, varExpr->loc);
+ }
+ else if (auto appBase = as<AppExprBase>(expr))
+ {
+ return _getDeclRefExprNameLoc(appBase->functionExpr);
+ }
+ return NameLoc();
+ }
+ static NameLoc _getDeclNameLoc(Decl* decl)
+ {
+ if (auto extDecl = as<ExtensionDecl>(decl))
+ {
+ return _getDeclRefExprNameLoc(extDecl->targetType.exp);
+ }
+ return decl->nameAndLoc;
+ }
+
+ static void _getDocumentSymbolsImpl(
+ GetDocumentSymbolContext& context,
+ Decl* parent,
+ List<LanguageServerProtocol::DocumentSymbol>& childSymbols)
+ {
+ auto containerDecl = as<ContainerDecl>(parent);
+ if (!containerDecl)
+ return;
+ if (!context.processedDecls.Add(parent))
+ return;
+ auto srcManager = context.linkage->getSourceManager();
+ for (auto child : containerDecl->members)
+ {
+ if (auto genericDecl = as<GenericDecl>(child))
+ {
+ child = genericDecl->inner;
+ }
+ LanguageServerProtocol::SymbolKind kind = _getSymbolKind(child);
+ if (kind <= 0)
+ continue;
+ NameLoc nameLoc = _getDeclNameLoc(child);
+ if (!nameLoc.name)
+ continue;
+ if (nameLoc.name->text.getLength() == 0)
+ continue;
+ if (!nameLoc.loc.isValid())
+ continue;
+ auto humaneLoc = srcManager->getHumaneLoc(nameLoc.loc, SourceLocType::Actual);
+ if (humaneLoc.line == 0)
+ continue;
+ if (context.fileName.endsWithCaseInsensitive(
+ Path::getFileName(humaneLoc.pathInfo.foundPath).getUnownedSlice()))
+ {
+ LanguageServerProtocol::DocumentSymbol sym;
+ sym.name = nameLoc.name->text;
+ sym.kind = kind;
+ Index line, col;
+ context.doc->oneBasedUTF8LocToZeroBasedUTF16Loc(
+ humaneLoc.line, humaneLoc.column, line, col);
+ sym.selectionRange.start.line = (int)line;
+ sym.selectionRange.start.character = (int)col;
+ sym.selectionRange.end.line = (int)line;
+ sym.selectionRange.end.character = (int)(col + nameLoc.name->text.getLength());
+ sym.range.start.line = (int)line;
+ sym.range.start.character = 0;
+ sym.range.end.line = (int)line;
+ sym.range.end.character = sym.selectionRange.end.character;
+ // Now try to find the end of the decl.
+ auto closingLoc = _findClosingSourceLoc(child);
+ if (closingLoc.isValid())
+ {
+ auto closingHumaneLoc = srcManager->getHumaneLoc(closingLoc, SourceLocType::Actual);
+ context.doc->oneBasedUTF8LocToZeroBasedUTF16Loc(
+ closingHumaneLoc.line, closingHumaneLoc.column, line, col);
+ sym.range.end.line = (int)line;
+ sym.range.end.character = (int)col;
+ }
+ if (auto childContainerDecl = as<ContainerDecl>(child))
+ {
+ // Recurse
+ bool shouldRecurse = true;
+ if (as<CallableDecl>(child))
+ shouldRecurse = false;
+ if (as<PropertyDecl>(child))
+ shouldRecurse = false;
+ if (shouldRecurse)
+ {
+ _getDocumentSymbolsImpl(context, child, sym.children);
+ }
+ }
+ childSymbols.add(_Move(sym));
+ }
+ }
+ }
+
+List<LanguageServerProtocol::DocumentSymbol> getDocumentSymbols(
+ Linkage* linkage, Module* module, UnownedStringSlice fileName, DocumentVersion* doc)
+{
+ GetDocumentSymbolContext context;
+ context.fileName = fileName;
+ context.doc = doc;
+ context.linkage = linkage;
+ List<LanguageServerProtocol::DocumentSymbol> result;
+ _getDocumentSymbolsImpl(context, module->getModuleDecl(), result);
+ return result;
+}
+
+} // namespace Slang
diff --git a/source/slang/slang-language-server-document-symbols.h b/source/slang/slang-language-server-document-symbols.h
new file mode 100644
index 000000000..65e84a6b2
--- /dev/null
+++ b/source/slang/slang-language-server-document-symbols.h
@@ -0,0 +1,14 @@
+#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
+{
+List<LanguageServerProtocol::DocumentSymbol> getDocumentSymbols(
+ Linkage* linkage, Module* module, UnownedStringSlice fileName, DocumentVersion* doc);
+} // namespace Slang
diff --git a/source/slang/slang-language-server-semantic-tokens.cpp b/source/slang/slang-language-server-semantic-tokens.cpp
index 806fa69ba..ff9056e56 100644
--- a/source/slang/slang-language-server-semantic-tokens.cpp
+++ b/source/slang/slang-language-server-semantic-tokens.cpp
@@ -373,6 +373,10 @@ void ASTIterator<CallbackFunc>::visitDecl(DeclBase* decl)
{
visitExpr(typedefDecl->type.exp);
}
+ else if (auto extDecl = as<ExtensionDecl>(decl))
+ {
+ visitExpr(extDecl->targetType.exp);
+ }
if (auto container = as<ContainerDecl>(decl))
{
for (auto member : container->members)
@@ -436,7 +440,7 @@ List<SemanticToken> getSemanticTokens(Linkage* linkage, Module* module, UnownedS
List<SemanticToken> result;
auto maybeInsertToken = [&](const SemanticToken& token)
{
- if (token.line >= 0 && token.col >= 0 && token.length > 0 &&
+ if (token.line > 0 && token.col > 0 && token.length > 0 &&
token.type != SemanticTokenType::NormalText)
result.add(token);
};
diff --git a/source/slang/slang-language-server.cpp b/source/slang/slang-language-server.cpp
index 5aa8d0e14..1ad0e28e9 100644
--- a/source/slang/slang-language-server.cpp
+++ b/source/slang/slang-language-server.cpp
@@ -20,6 +20,7 @@
#include "slang-language-server-ast-lookup.h"
#include "slang-language-server-completion.h"
#include "slang-language-server-semantic-tokens.h"
+#include "slang-language-server-document-symbols.h"
#include "slang-ast-print.h"
#include "slang-doc-markdown-writer.h"
#include "../../tools/platform/performance-counter.h"
@@ -110,12 +111,13 @@ SlangResult LanguageServer::parseNextMessage()
result.capabilities.workspace.workspaceFolders.changeNotifications = false;
result.capabilities.hoverProvider = true;
result.capabilities.definitionProvider = true;
- for (auto ch : getCommitChars())
- result.capabilities.completionProvider.allCommitCharacters.add(ch);
+ result.capabilities.documentSymbolProvider = true;
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("(");
@@ -731,6 +733,28 @@ SlangResult LanguageServer::signatureHelp(
return SLANG_OK;
}
+SlangResult LanguageServer::documentSymbol(
+ const LanguageServerProtocol::DocumentSymbolParams& 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<DocumentSymbol> symbols = getDocumentSymbols(version->linkage, parsedModule, canonicalPath.getUnownedSlice(), doc.Ptr());
+ m_connection->sendResult(&symbols, responseId);
+ return SLANG_OK;
+}
+
void LanguageServer::publishDiagnostics()
{
time_t timeNow = 0;
@@ -923,6 +947,12 @@ SlangResult LanguageServer::queueJSONCall(JSONRPCCall call)
SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id));
cmd.completionResolveArgs = args;
}
+ else if (call.method == DocumentSymbolParams::methodName)
+ {
+ DocumentSymbolParams args;
+ SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id));
+ cmd.documentSymbolArgs = args;
+ }
else if (call.method == DidChangeConfigurationParams::methodName)
{
DidChangeConfigurationParams args;
@@ -980,6 +1010,10 @@ SlangResult LanguageServer::runCommand(Command& call)
{
return completionResolve(call.completionResolveArgs.get(), call.id);
}
+ else if (call.method == DocumentSymbolParams::methodName)
+ {
+ return documentSymbol(call.documentSymbolArgs.get(), call.id);
+ }
else if (call.method == DidChangeConfigurationParams::methodName)
{
return didChangeConfiguration(call.changeConfigArgs.get());
diff --git a/source/slang/slang-language-server.h b/source/slang/slang-language-server.h
index 898037144..b873576e8 100644
--- a/source/slang/slang-language-server.h
+++ b/source/slang/slang-language-server.h
@@ -56,6 +56,7 @@ struct Command
Optional<LanguageServerProtocol::CompletionParams> completionArgs;
Optional<LanguageServerProtocol::CompletionItem> completionResolveArgs;
+ Optional<LanguageServerProtocol::DocumentSymbolParams> documentSymbolArgs;
Optional<LanguageServerProtocol::DidChangeConfigurationParams> changeConfigArgs;
Optional<LanguageServerProtocol::SignatureHelpParams> signatureHelpArgs;
Optional<LanguageServerProtocol::DefinitionParams> definitionArgs;
@@ -103,6 +104,8 @@ public:
const LanguageServerProtocol::SemanticTokensParams& args, const JSONValue& responseId);
SlangResult signatureHelp(
const LanguageServerProtocol::SignatureHelpParams& args, const JSONValue& responseId);
+ SlangResult documentSymbol(
+ const LanguageServerProtocol::DocumentSymbolParams& args, const JSONValue& responseId);
private:
SlangResult parseNextMessage();
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp
index aaf175812..c8a990ab1 100644
--- a/source/slang/slang-parser.cpp
+++ b/source/slang/slang-parser.cpp
@@ -2889,7 +2889,6 @@ namespace Slang
decl->targetType = parser->ParseTypeExp();
parseOptionalInheritanceClause(parser, decl);
parseDeclBody(parser, decl);
-
return decl;
}
@@ -3199,9 +3198,14 @@ namespace Slang
if( parser->tokenReader.peekTokenType() == TokenType::LBrace )
{
decl->body = parser->parseBlockStatement();
+ if (auto block = as<BlockStmt>(decl->body))
+ {
+ decl->closingSourceLoc = block->closingSourceLoc;
+ }
}
else
{
+ decl->closingSourceLoc = parser->tokenReader.peekLoc();
parser->ReadToken(TokenType::Semicolon);
}
@@ -3216,14 +3220,18 @@ namespace Slang
if( AdvanceIf(parser, TokenType::LBrace) )
{
// We want to parse nested "accessor" declarations
- while( !AdvanceIfMatch(parser, MatchedTokenType::CurlyBraces) )
+ Token closingToken;
+ while (!AdvanceIfMatch(parser, MatchedTokenType::CurlyBraces, &closingToken))
{
auto accessor = parseAccessorDecl(parser);
AddMember(decl, accessor);
}
+ decl->closingSourceLoc = closingToken.loc;
}
else
{
+ decl->closingSourceLoc = parser->tokenReader.peekLoc();
+
parser->ReadToken(TokenType::Semicolon);
// empty body should be treated like `{ get; }`
@@ -3970,8 +3978,8 @@ namespace Slang
{
parseOptionalInheritanceClause(parser, decl);
parser->ReadToken(TokenType::LBrace);
-
- while(!AdvanceIfMatch(parser, MatchedTokenType::CurlyBraces))
+ Token closingToken;
+ while (!AdvanceIfMatch(parser, MatchedTokenType::CurlyBraces, &closingToken))
{
EnumCaseDecl* caseDecl = parseEnumCaseDecl(parser);
AddMember(decl, caseDecl);
@@ -3981,6 +3989,7 @@ namespace Slang
parser->ReadToken(TokenType::Comma);
}
+ decl->closingSourceLoc = closingToken.loc;
return decl;
});
}
diff --git a/source/slang/slang-workspace-version.cpp b/source/slang/slang-workspace-version.cpp
index 2742c083c..781d8a3bc 100644
--- a/source/slang/slang-workspace-version.cpp
+++ b/source/slang/slang-workspace-version.cpp
@@ -390,6 +390,12 @@ ArrayView<Index> DocumentVersion::getUTF16Boundaries(Index line)
void DocumentVersion::oneBasedUTF8LocToZeroBasedUTF16Loc(
Index inLine, Index inCol, Index& outLine, Index& outCol)
{
+ if (inLine <= 0)
+ {
+ outLine = 0;
+ outCol = 0;
+ }
+
Index rsLine = inLine - 1;
auto line = lines[rsLine];
auto bounds = getUTF16Boundaries(inLine);