1434 lines
56 KiB
C++
1434 lines
56 KiB
C++
module;
|
|
|
|
export module lsp.test.provider.json_provider_coverage;
|
|
|
|
import std;
|
|
|
|
import lsp.test.framework;
|
|
|
|
import lsp.codec.facade;
|
|
import lsp.core.dispatcher;
|
|
import lsp.language.ast;
|
|
import lsp.manager.manager_hub;
|
|
import lsp.protocol;
|
|
import lsp.provider.manifest;
|
|
import lsp.scheduler.async_executor;
|
|
import lsp.test.provider.fixtures;
|
|
import tree_sitter;
|
|
|
|
export namespace lsp::test::provider
|
|
{
|
|
class JsonProviderCoverageTests
|
|
{
|
|
public:
|
|
static void Register(TestRunner& runner);
|
|
|
|
private:
|
|
static TestResult TestAllProvidersJsonCoverage();
|
|
};
|
|
}
|
|
|
|
namespace lsp::test::provider
|
|
{
|
|
namespace
|
|
{
|
|
namespace codec = lsp::codec;
|
|
namespace provider = lsp::provider;
|
|
|
|
struct SeededRequestParams
|
|
{
|
|
std::optional<protocol::LSPAny> code_lens;
|
|
std::optional<protocol::LSPAny> document_link;
|
|
std::optional<protocol::LSPAny> inlay_hint;
|
|
std::optional<protocol::LSPAny> call_hierarchy_incoming_item;
|
|
std::optional<protocol::LSPAny> call_hierarchy_outgoing_item;
|
|
std::optional<protocol::LSPAny> type_hierarchy_item;
|
|
std::optional<protocol::LSPAny> code_action;
|
|
std::optional<protocol::LSPAny> workspace_symbol;
|
|
};
|
|
|
|
struct ProviderEnv
|
|
{
|
|
scheduler::AsyncExecutor scheduler{ 1 };
|
|
manager::ManagerHub hub{};
|
|
core::RequestDispatcher dispatcher{};
|
|
|
|
ProviderEnv()
|
|
{
|
|
hub.Initialize();
|
|
dispatcher.SetRequestScheduler(&scheduler);
|
|
dispatcher.SetManagerHub(&hub);
|
|
provider::RegisterAllProviders(dispatcher);
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
std::string SerializeOrThrow(const T& obj)
|
|
{
|
|
auto json = codec::Serialize(obj);
|
|
assertTrue(json.has_value(), "Failed to serialize LSP JSON");
|
|
return json.value();
|
|
}
|
|
|
|
template<typename T>
|
|
T DeserializeOrThrow(const std::string& json)
|
|
{
|
|
auto parsed = codec::Deserialize<T>(json);
|
|
assertTrue(parsed.has_value(), "Failed to deserialize LSP JSON");
|
|
return parsed.value();
|
|
}
|
|
|
|
protocol::Position FindPosition(const std::string& content, const std::string& marker, bool after_marker = false)
|
|
{
|
|
auto pos = content.find(marker);
|
|
assertTrue(pos != std::string::npos, "Marker not found in fixture");
|
|
|
|
protocol::Position result{};
|
|
for (std::size_t i = 0; i < pos; ++i)
|
|
{
|
|
if (content[i] == '\n')
|
|
{
|
|
result.line++;
|
|
result.character = 0;
|
|
}
|
|
else
|
|
{
|
|
result.character++;
|
|
}
|
|
}
|
|
|
|
if (after_marker)
|
|
{
|
|
result.character += static_cast<protocol::uinteger>(marker.size());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protocol::LSPObject ToTextDocument(const std::string& uri)
|
|
{
|
|
return protocol::LSPObject{ { "uri", uri } };
|
|
}
|
|
|
|
protocol::LSPObject ToPosition(const protocol::Position& pos)
|
|
{
|
|
return protocol::LSPObject{
|
|
{ "line", static_cast<protocol::integer>(pos.line) },
|
|
{ "character", static_cast<protocol::integer>(pos.character) },
|
|
};
|
|
}
|
|
|
|
protocol::LSPObject ToRange(const protocol::Range& range)
|
|
{
|
|
return protocol::LSPObject{
|
|
{ "start", ToPosition(range.start) },
|
|
{ "end", ToPosition(range.end) },
|
|
};
|
|
}
|
|
|
|
protocol::Range FullDocumentRange(const std::string& content)
|
|
{
|
|
protocol::Range range{};
|
|
range.start.line = 0;
|
|
range.start.character = 0;
|
|
|
|
protocol::uinteger line_count = 0;
|
|
protocol::uinteger last_line_len = 0;
|
|
for (char ch : content)
|
|
{
|
|
if (ch == '\n')
|
|
{
|
|
line_count++;
|
|
last_line_len = 0;
|
|
}
|
|
else
|
|
{
|
|
last_line_len++;
|
|
}
|
|
}
|
|
|
|
range.end.line = line_count;
|
|
range.end.character = last_line_len;
|
|
return range;
|
|
}
|
|
|
|
std::optional<protocol::LSPAny> FirstArrayItem(const protocol::LSPAny& any)
|
|
{
|
|
if (!any.Is<protocol::LSPArray>())
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
const auto& array = any.Get<protocol::LSPArray>();
|
|
if (array.empty())
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
return array.front();
|
|
}
|
|
|
|
protocol::LSPAny BuildCompletionResolveItem(const std::string& uri)
|
|
{
|
|
protocol::LSPObject data;
|
|
data["ctx"] = "new";
|
|
data["class"] = "Widget";
|
|
data["unit"] = "MainUnit";
|
|
data["uri"] = uri;
|
|
|
|
protocol::LSPObject item;
|
|
item["label"] = "Widget";
|
|
item["data"] = std::move(data);
|
|
return protocol::LSPAny(std::move(item));
|
|
}
|
|
|
|
std::optional<protocol::LSPAny> BuildRequestParams(std::string_view method,
|
|
const SeededRequestParams& seeded,
|
|
const std::string& main_uri,
|
|
const std::string& main_content,
|
|
const std::string& rename_uri,
|
|
const std::string& rename_content,
|
|
const std::string& code_action_uri,
|
|
const std::string& code_action_content,
|
|
const protocol::LSPArray& code_action_diagnostics)
|
|
{
|
|
if (method == "textDocument/completion")
|
|
{
|
|
auto completion_pos = FindPosition(main_content, "new Wid", true);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["position"] = ToPosition(completion_pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "completionItem/resolve")
|
|
{
|
|
return BuildCompletionResolveItem(main_uri);
|
|
}
|
|
|
|
if (method == "textDocument/definition")
|
|
{
|
|
auto def_pos = FindPosition(main_content, "UnitFunc(1);", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["position"] = ToPosition(def_pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/hover")
|
|
{
|
|
auto hover_pos = FindPosition(main_content, "UnitFunc(1);", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["position"] = ToPosition(hover_pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/signatureHelp")
|
|
{
|
|
auto sig_pos = FindPosition(main_content, "UnitFunc(1);", false);
|
|
sig_pos.character += static_cast<protocol::uinteger>(std::string("UnitFunc(").size());
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["position"] = ToPosition(sig_pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/references")
|
|
{
|
|
auto pos = FindPosition(rename_content, "target := target", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(rename_uri);
|
|
params["position"] = ToPosition(pos);
|
|
params["context"] = protocol::LSPObject{ { "includeDeclaration", true } };
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/linkedEditingRange")
|
|
{
|
|
auto pos = FindPosition(rename_content, "target := target", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(rename_uri);
|
|
params["position"] = ToPosition(pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/documentHighlight")
|
|
{
|
|
auto pos = FindPosition(rename_content, "target := target", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(rename_uri);
|
|
params["position"] = ToPosition(pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/prepareRename")
|
|
{
|
|
auto pos = FindPosition(rename_content, "target := target", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(rename_uri);
|
|
params["position"] = ToPosition(pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/rename")
|
|
{
|
|
auto pos = FindPosition(rename_content, "target := target", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(rename_uri);
|
|
params["position"] = ToPosition(pos);
|
|
params["newName"] = "renamed_target";
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/documentSymbol")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/symbol")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["query"] = protocol::string("Workspace");
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/semanticTokens/full")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/semanticTokens/full/delta")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["previousResultId"] = "0";
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/semanticTokens/range")
|
|
{
|
|
protocol::Range range{};
|
|
range.start.line = 0;
|
|
range.start.character = 0;
|
|
range.end.line = 5;
|
|
range.end.character = 0;
|
|
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["range"] = ToRange(range);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/codeAction")
|
|
{
|
|
auto range = FullDocumentRange(code_action_content);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(code_action_uri);
|
|
params["range"] = ToRange(range);
|
|
params["context"] = protocol::LSPObject{
|
|
{ "diagnostics", code_action_diagnostics },
|
|
};
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/typeDefinition")
|
|
{
|
|
auto pos = FindPosition(main_content, "obj: Widget", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["position"] = ToPosition(pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/implementation")
|
|
{
|
|
auto pos = FindPosition(main_content, "UnitFunc(1);", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["position"] = ToPosition(pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/documentLink")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/codeLens")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "codeLens/resolve")
|
|
{
|
|
if (seeded.code_lens)
|
|
{
|
|
return *seeded.code_lens;
|
|
}
|
|
|
|
protocol::Range range{};
|
|
range.start.line = 0;
|
|
range.start.character = 0;
|
|
range.end.line = 0;
|
|
range.end.character = 0;
|
|
|
|
protocol::LSPObject data;
|
|
data["kind"] = protocol::string("references");
|
|
data["count"] = static_cast<protocol::integer>(2);
|
|
|
|
protocol::LSPObject lens;
|
|
lens["range"] = ToRange(range);
|
|
lens["data"] = protocol::LSPAny(std::move(data));
|
|
return protocol::LSPAny(std::move(lens));
|
|
}
|
|
|
|
if (method == "documentLink/resolve")
|
|
{
|
|
if (seeded.document_link)
|
|
{
|
|
return *seeded.document_link;
|
|
}
|
|
|
|
auto pos = FindPosition(main_content, "WorkspaceUnit", false);
|
|
protocol::Range range;
|
|
range.start = pos;
|
|
range.end = pos;
|
|
range.end.character += static_cast<protocol::uinteger>(std::string("WorkspaceUnit").size());
|
|
|
|
protocol::LSPObject data;
|
|
data["kind"] = protocol::string("unit");
|
|
data["name"] = protocol::string("WorkspaceUnit");
|
|
data["baseUri"] = protocol::string(main_uri);
|
|
|
|
protocol::LSPObject link;
|
|
link["range"] = ToRange(range);
|
|
link["data"] = protocol::LSPAny(std::move(data));
|
|
return protocol::LSPAny(std::move(link));
|
|
}
|
|
|
|
if (method == "textDocument/foldingRange")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/selectionRange")
|
|
{
|
|
auto pos = FindPosition(main_content, "UnitFunc(1);", false);
|
|
protocol::LSPArray positions;
|
|
positions.emplace_back(ToPosition(pos));
|
|
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["positions"] = std::move(positions);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/prepareCallHierarchy")
|
|
{
|
|
auto pos = FindPosition(main_content, "UnitFunc(1);", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["position"] = ToPosition(pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "callHierarchy/incomingCalls")
|
|
{
|
|
if (seeded.call_hierarchy_incoming_item)
|
|
{
|
|
protocol::LSPObject params;
|
|
params["item"] = *seeded.call_hierarchy_incoming_item;
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
auto pos = FindPosition(main_content, "UnitFunc(1);", false);
|
|
|
|
protocol::Range range{};
|
|
range.start = pos;
|
|
range.end = pos;
|
|
range.end.character += static_cast<protocol::uinteger>(std::string("UnitFunc").size());
|
|
|
|
protocol::LSPObject item;
|
|
item["name"] = protocol::string("UnitFunc");
|
|
item["kind"] = static_cast<protocol::integer>(protocol::SymbolKind::Function);
|
|
item["tags"] = protocol::LSPArray{};
|
|
item["uri"] = protocol::string(main_uri);
|
|
item["range"] = ToRange(range);
|
|
item["selectionRange"] = ToRange(range);
|
|
|
|
protocol::LSPObject params;
|
|
params["item"] = protocol::LSPAny(std::move(item));
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "callHierarchy/outgoingCalls")
|
|
{
|
|
if (seeded.call_hierarchy_outgoing_item)
|
|
{
|
|
protocol::LSPObject params;
|
|
params["item"] = *seeded.call_hierarchy_outgoing_item;
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
auto pos = FindPosition(main_content, "TestDefinitions();", false);
|
|
|
|
protocol::Range range{};
|
|
range.start = pos;
|
|
range.end = pos;
|
|
range.end.character += static_cast<protocol::uinteger>(std::string("TestDefinitions").size());
|
|
|
|
protocol::LSPObject item;
|
|
item["name"] = protocol::string("TestDefinitions");
|
|
item["kind"] = static_cast<protocol::integer>(protocol::SymbolKind::Function);
|
|
item["tags"] = protocol::LSPArray{};
|
|
item["uri"] = protocol::string(main_uri);
|
|
item["range"] = ToRange(range);
|
|
item["selectionRange"] = ToRange(range);
|
|
|
|
protocol::LSPObject params;
|
|
params["item"] = protocol::LSPAny(std::move(item));
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/prepareTypeHierarchy")
|
|
{
|
|
auto pos = FindPosition(main_content, "Widget = class", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["position"] = ToPosition(pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "typeHierarchy/supertypes")
|
|
{
|
|
if (seeded.type_hierarchy_item)
|
|
{
|
|
protocol::LSPObject params;
|
|
params["item"] = *seeded.type_hierarchy_item;
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
auto pos = FindPosition(main_content, "Widget = class", false);
|
|
|
|
protocol::Range range{};
|
|
range.start = pos;
|
|
range.end = pos;
|
|
range.end.character += static_cast<protocol::uinteger>(std::string("Widget").size());
|
|
|
|
protocol::LSPObject item;
|
|
item["name"] = protocol::string("Widget");
|
|
item["kind"] = static_cast<protocol::integer>(protocol::SymbolKind::Class);
|
|
item["tags"] = protocol::LSPArray{};
|
|
item["uri"] = protocol::string(main_uri);
|
|
item["range"] = ToRange(range);
|
|
item["selectionRange"] = ToRange(range);
|
|
|
|
protocol::LSPObject params;
|
|
params["item"] = protocol::LSPAny(std::move(item));
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "typeHierarchy/subtypes")
|
|
{
|
|
if (seeded.type_hierarchy_item)
|
|
{
|
|
protocol::LSPObject params;
|
|
params["item"] = *seeded.type_hierarchy_item;
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
auto pos = FindPosition(main_content, "Widget = class", false);
|
|
|
|
protocol::Range range{};
|
|
range.start = pos;
|
|
range.end = pos;
|
|
range.end.character += static_cast<protocol::uinteger>(std::string("Widget").size());
|
|
|
|
protocol::LSPObject item;
|
|
item["name"] = protocol::string("Widget");
|
|
item["kind"] = static_cast<protocol::integer>(protocol::SymbolKind::Class);
|
|
item["tags"] = protocol::LSPArray{};
|
|
item["uri"] = protocol::string(main_uri);
|
|
item["range"] = ToRange(range);
|
|
item["selectionRange"] = ToRange(range);
|
|
|
|
protocol::LSPObject params;
|
|
params["item"] = protocol::LSPAny(std::move(item));
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/inlayHint")
|
|
{
|
|
auto range = FullDocumentRange(main_content);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["range"] = ToRange(range);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "inlayHint/resolve")
|
|
{
|
|
if (seeded.inlay_hint)
|
|
{
|
|
return *seeded.inlay_hint;
|
|
}
|
|
|
|
protocol::Position pos{};
|
|
pos.line = 0;
|
|
pos.character = 0;
|
|
|
|
protocol::LSPObject data;
|
|
data["detail"] = protocol::string("param: int");
|
|
|
|
protocol::LSPObject hint;
|
|
hint["position"] = ToPosition(pos);
|
|
hint["label"] = protocol::string("param:");
|
|
hint["kind"] = static_cast<protocol::integer>(protocol::InlayHintKind::Parameter);
|
|
hint["data"] = protocol::LSPAny(std::move(data));
|
|
return protocol::LSPAny(std::move(hint));
|
|
}
|
|
|
|
if (method == "textDocument/documentColor")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/colorPresentation")
|
|
{
|
|
protocol::Range range{};
|
|
range.start.line = 0;
|
|
range.start.character = 0;
|
|
range.end.line = 0;
|
|
range.end.character = 7;
|
|
|
|
protocol::LSPObject color;
|
|
color["red"] = protocol::decimal(1.0);
|
|
color["green"] = protocol::decimal(0.0);
|
|
color["blue"] = protocol::decimal(0.0);
|
|
color["alpha"] = protocol::decimal(1.0);
|
|
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["color"] = std::move(color);
|
|
params["range"] = ToRange(range);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/diagnostic")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(code_action_uri);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/diagnostic")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["previousResultIds"] = protocol::LSPArray{};
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "codeAction/resolve")
|
|
{
|
|
if (seeded.code_action)
|
|
{
|
|
return *seeded.code_action;
|
|
}
|
|
|
|
protocol::LSPObject action;
|
|
action["title"] = protocol::string("Fix");
|
|
action["kind"] = protocol::string(protocol::CodeActionKindLiterals::QuickFix);
|
|
action["data"] = protocol::LSPAny(protocol::LSPObject{ { "kind", protocol::string("quickfix") } });
|
|
return protocol::LSPAny(std::move(action));
|
|
}
|
|
|
|
if (method == "workspaceSymbol/resolve")
|
|
{
|
|
if (seeded.workspace_symbol)
|
|
{
|
|
return *seeded.workspace_symbol;
|
|
}
|
|
|
|
protocol::LSPObject symbol;
|
|
symbol["name"] = protocol::string("WorkspaceUnit");
|
|
symbol["kind"] = static_cast<protocol::integer>(protocol::SymbolKind::Module);
|
|
symbol["location"] = protocol::LSPObject{
|
|
{ "uri", protocol::string(main_uri) },
|
|
{ "range", ToRange(FullDocumentRange(main_content)) },
|
|
};
|
|
return protocol::LSPAny(std::move(symbol));
|
|
}
|
|
|
|
if (method == "workspace/executeCommand")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["command"] = protocol::string("tsl.noop");
|
|
params["arguments"] = protocol::LSPArray{};
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/willCreateFiles")
|
|
{
|
|
auto file_uri = ToUri(FixturePath("workspace/workspace_script.tsl"));
|
|
|
|
protocol::LSPArray files;
|
|
files.emplace_back(protocol::LSPObject{
|
|
{ "uri", file_uri },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["files"] = std::move(files);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/willDeleteFiles")
|
|
{
|
|
auto file_uri = ToUri(FixturePath("workspace/workspace_script.tsl"));
|
|
|
|
protocol::LSPArray files;
|
|
files.emplace_back(protocol::LSPObject{
|
|
{ "uri", file_uri },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["files"] = std::move(files);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/willRenameFiles")
|
|
{
|
|
auto file_uri = ToUri(FixturePath("workspace/workspace_script.tsl"));
|
|
|
|
protocol::LSPArray files;
|
|
files.emplace_back(protocol::LSPObject{
|
|
{ "oldUri", file_uri },
|
|
{ "newUri", file_uri },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["files"] = std::move(files);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/formatting")
|
|
{
|
|
protocol::LSPObject options;
|
|
options["tabSize"] = static_cast<protocol::integer>(4);
|
|
options["insertSpaces"] = true;
|
|
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["options"] = std::move(options);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/rangeFormatting")
|
|
{
|
|
protocol::LSPObject options;
|
|
options["tabSize"] = static_cast<protocol::integer>(4);
|
|
options["insertSpaces"] = true;
|
|
|
|
auto range = FullDocumentRange(main_content);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["range"] = ToRange(range);
|
|
params["options"] = std::move(options);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/onTypeFormatting")
|
|
{
|
|
protocol::LSPObject options;
|
|
options["tabSize"] = static_cast<protocol::integer>(4);
|
|
options["insertSpaces"] = true;
|
|
|
|
auto pos = FindPosition(main_content, "return", false);
|
|
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["position"] = ToPosition(pos);
|
|
params["ch"] = protocol::string(";");
|
|
params["options"] = std::move(options);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/inlineValue")
|
|
{
|
|
protocol::LSPObject context;
|
|
context["frameId"] = static_cast<protocol::integer>(0);
|
|
context["stoppedLocation"] = ToRange(FullDocumentRange(main_content));
|
|
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["range"] = ToRange(FullDocumentRange(main_content));
|
|
params["context"] = std::move(context);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/moniker")
|
|
{
|
|
auto pos = FindPosition(main_content, "UnitFunc(1);", false);
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(main_uri);
|
|
params["position"] = ToPosition(pos);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "client/registerCapability")
|
|
{
|
|
protocol::LSPArray registrations;
|
|
registrations.emplace_back(protocol::LSPObject{
|
|
{ "id", protocol::string("reg_1") },
|
|
{ "method", protocol::string("workspace/didChangeConfiguration") },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["registrations"] = std::move(registrations);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "client/unregisterCapability")
|
|
{
|
|
protocol::LSPArray unregistrations;
|
|
unregistrations.emplace_back(protocol::LSPObject{
|
|
{ "id", protocol::string("reg_1") },
|
|
{ "method", protocol::string("workspace/didChangeConfiguration") },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["unregistrations"] = std::move(unregistrations);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "window/workDoneProgress/create")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["token"] = protocol::string("progress_token");
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "window/showMessageRequest")
|
|
{
|
|
protocol::LSPArray actions;
|
|
actions.emplace_back(protocol::LSPObject{
|
|
{ "title", protocol::string("OK") },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["type"] = static_cast<protocol::integer>(protocol::MessageType::Info);
|
|
params["message"] = protocol::string("Test showMessageRequest");
|
|
params["actions"] = std::move(actions);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "window/showDocument")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["uri"] = protocol::string(main_uri);
|
|
params["takeFocus"] = true;
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/configuration")
|
|
{
|
|
protocol::LSPArray items;
|
|
items.emplace_back(protocol::LSPObject{
|
|
{ "scopeUri", protocol::string(main_uri) },
|
|
{ "section", protocol::string("tsl") },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["items"] = std::move(items);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/applyEdit")
|
|
{
|
|
protocol::Range range{};
|
|
range.start.line = 0;
|
|
range.start.character = 0;
|
|
range.end = range.start;
|
|
|
|
protocol::LSPObject text_edit;
|
|
text_edit["range"] = ToRange(range);
|
|
text_edit["newText"] = protocol::string("");
|
|
|
|
protocol::LSPArray edits;
|
|
edits.emplace_back(std::move(text_edit));
|
|
|
|
protocol::LSPObject changes;
|
|
changes[main_uri] = protocol::LSPAny(std::move(edits));
|
|
|
|
protocol::LSPObject edit;
|
|
edit["changes"] = std::move(changes);
|
|
|
|
protocol::LSPObject params;
|
|
params["edit"] = std::move(edit);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/workspaceFolders")
|
|
{
|
|
return protocol::LSPAny(protocol::LSPObject{});
|
|
}
|
|
|
|
if (method == "workspace/codeLens/refresh" ||
|
|
method == "workspace/diagnostic/refresh" ||
|
|
method == "workspace/inlayHint/refresh" ||
|
|
method == "workspace/inlineValue/refresh" ||
|
|
method == "workspace/semanticTokens/refresh")
|
|
{
|
|
return protocol::LSPAny(protocol::LSPObject{});
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<protocol::LSPAny> BuildNotificationParams(std::string_view method,
|
|
const std::string& uri,
|
|
const std::string& content,
|
|
int version)
|
|
{
|
|
if (method == "textDocument/didOpen")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = protocol::LSPObject{
|
|
{ "uri", uri },
|
|
{ "languageId", "tsl" },
|
|
{ "version", version },
|
|
{ "text", content },
|
|
};
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/didChange")
|
|
{
|
|
protocol::LSPObject change;
|
|
protocol::Range full_range{};
|
|
full_range.start.line = 0;
|
|
full_range.start.character = 0;
|
|
full_range.end.line = 9999;
|
|
full_range.end.character = 0;
|
|
change["range"] = ToRange(full_range);
|
|
change["text"] = content;
|
|
|
|
protocol::LSPArray changes;
|
|
changes.emplace_back(std::move(change));
|
|
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = protocol::LSPObject{
|
|
{ "uri", uri },
|
|
{ "version", version },
|
|
};
|
|
params["contentChanges"] = std::move(changes);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/didClose")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["textDocument"] = ToTextDocument(uri);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "$/setTrace")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["value"] = protocol::TraceValueLiterals::Off;
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "$/cancelRequest")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["id"] = "json_cancel_me";
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/didCreateFiles")
|
|
{
|
|
auto file_uri = ToUri(FixturePath("workspace/workspace_script.tsl"));
|
|
|
|
protocol::LSPArray files;
|
|
files.emplace_back(protocol::LSPObject{
|
|
{ "uri", file_uri },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["files"] = std::move(files);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/didDeleteFiles")
|
|
{
|
|
auto file_uri = ToUri(FixturePath("workspace/workspace_script.tsl"));
|
|
|
|
protocol::LSPArray files;
|
|
files.emplace_back(protocol::LSPObject{
|
|
{ "uri", file_uri },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["files"] = std::move(files);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/didRenameFiles")
|
|
{
|
|
auto file_uri = ToUri(FixturePath("workspace/workspace_script.tsl"));
|
|
|
|
protocol::LSPArray files;
|
|
files.emplace_back(protocol::LSPObject{
|
|
{ "oldUri", file_uri },
|
|
{ "newUri", file_uri },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["files"] = std::move(files);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/didChangeWatchedFiles")
|
|
{
|
|
auto file_uri = ToUri(FixturePath("workspace/workspace_script.tsl"));
|
|
|
|
protocol::LSPObject change;
|
|
change["uri"] = file_uri;
|
|
change["type"] = static_cast<protocol::integer>(protocol::FileChangeType::Changed);
|
|
|
|
protocol::LSPArray changes;
|
|
changes.emplace_back(std::move(change));
|
|
|
|
protocol::LSPObject params;
|
|
params["changes"] = std::move(changes);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/didChangeConfiguration")
|
|
{
|
|
protocol::LSPObject tsl;
|
|
tsl["format"] = true;
|
|
|
|
protocol::LSPObject settings;
|
|
settings["tsl"] = std::move(tsl);
|
|
|
|
protocol::LSPObject params;
|
|
params["settings"] = std::move(settings);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "workspace/didChangeWorkspaceFolders")
|
|
{
|
|
auto workspace_uri = ToUri(FixturePath("workspace"));
|
|
|
|
protocol::LSPArray added;
|
|
added.emplace_back(protocol::LSPObject{
|
|
{ "uri", workspace_uri },
|
|
{ "name", "workspace" },
|
|
});
|
|
|
|
protocol::LSPObject event;
|
|
event["added"] = std::move(added);
|
|
event["removed"] = protocol::LSPArray{};
|
|
|
|
protocol::LSPObject params;
|
|
params["event"] = std::move(event);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "window/logMessage")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["type"] = static_cast<protocol::integer>(protocol::MessageType::Log);
|
|
params["message"] = protocol::string("Test logMessage");
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "window/showMessage")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["type"] = static_cast<protocol::integer>(protocol::MessageType::Info);
|
|
params["message"] = protocol::string("Test showMessage");
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "telemetry/event")
|
|
{
|
|
protocol::LSPObject params;
|
|
params["event"] = protocol::string("test_event");
|
|
params["value"] = static_cast<protocol::integer>(1);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "textDocument/publishDiagnostics")
|
|
{
|
|
protocol::Range range{};
|
|
range.start.line = 0;
|
|
range.start.character = 0;
|
|
range.end.line = 0;
|
|
range.end.character = 1;
|
|
|
|
protocol::LSPArray diagnostics;
|
|
diagnostics.emplace_back(protocol::LSPObject{
|
|
{ "range", ToRange(range) },
|
|
{ "message", protocol::string("Test diagnostic") },
|
|
});
|
|
|
|
protocol::LSPObject params;
|
|
params["uri"] = protocol::string(uri);
|
|
params["version"] = static_cast<protocol::integer>(version);
|
|
params["diagnostics"] = std::move(diagnostics);
|
|
return protocol::LSPAny(std::move(params));
|
|
}
|
|
|
|
if (method == "initialized")
|
|
{
|
|
return protocol::LSPAny(protocol::LSPObject{});
|
|
}
|
|
|
|
if (method == "exit")
|
|
{
|
|
return protocol::LSPAny(protocol::LSPObject{});
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
protocol::LSPArray BuildDiagnosticsFromSyntaxErrors(TSTree* tree, const std::string& content)
|
|
{
|
|
protocol::LSPArray diagnostics;
|
|
if (!tree)
|
|
{
|
|
return diagnostics;
|
|
}
|
|
|
|
auto errors = language::ast::Deserializer::DiagnoseSyntax(ts_tree_root_node(tree), content);
|
|
diagnostics.reserve(errors.size());
|
|
for (const auto& error : errors)
|
|
{
|
|
protocol::LSPObject diagnostic;
|
|
protocol::Range range{};
|
|
range.start.line = error.location.start_line;
|
|
range.start.character = error.location.start_column;
|
|
range.end.line = error.location.end_line;
|
|
range.end.character = error.location.end_column;
|
|
|
|
diagnostic["range"] = ToRange(range);
|
|
diagnostic["message"] = error.message;
|
|
diagnostics.emplace_back(std::move(diagnostic));
|
|
}
|
|
return diagnostics;
|
|
}
|
|
}
|
|
|
|
void JsonProviderCoverageTests::Register(TestRunner& runner)
|
|
{
|
|
runner.addTest("json provider coverage (all providers)", TestAllProvidersJsonCoverage);
|
|
}
|
|
|
|
TestResult JsonProviderCoverageTests::TestAllProvidersJsonCoverage()
|
|
{
|
|
TestResult result{ "", true, "ok" };
|
|
ProviderEnv env;
|
|
|
|
auto workspace_uri = ToUri(FixturePath("workspace"));
|
|
|
|
auto main_path = FixturePath("main_unit.tsf");
|
|
auto main_content = ReadTextFile(main_path);
|
|
auto main_uri = ToUri(main_path);
|
|
|
|
auto type_hierarchy_path = FixturePath("type_hierarchy_unit.tsf");
|
|
auto type_hierarchy_content = ReadTextFile(type_hierarchy_path);
|
|
auto type_hierarchy_uri = ToUri(type_hierarchy_path);
|
|
|
|
auto inlay_hint_path = FixturePath("inlay_hint_case.tsl");
|
|
auto inlay_hint_content = ReadTextFile(inlay_hint_path);
|
|
auto inlay_hint_uri = ToUri(inlay_hint_path);
|
|
|
|
auto rename_path = FixturePath("rename_case.tsl");
|
|
auto rename_content = ReadTextFile(rename_path);
|
|
auto rename_uri = ToUri(rename_path);
|
|
|
|
auto code_action_path = FixturePath("code_action_missing_semicolon.tsl");
|
|
auto code_action_content = ReadTextFile(code_action_path);
|
|
auto code_action_uri = ToUri(code_action_path);
|
|
|
|
{
|
|
protocol::LSPArray folders;
|
|
folders.emplace_back(protocol::LSPObject{
|
|
{ "uri", workspace_uri },
|
|
{ "name", "workspace" },
|
|
});
|
|
|
|
protocol::LSPObject init_params;
|
|
init_params["trace"] = protocol::string(protocol::TraceValueLiterals::Off);
|
|
init_params["workspaceFolders"] = std::move(folders);
|
|
|
|
protocol::RequestMessage init_request;
|
|
init_request.id = "init";
|
|
init_request.method = "initialize";
|
|
init_request.params = protocol::LSPAny(std::move(init_params));
|
|
|
|
auto init_json = SerializeOrThrow(init_request);
|
|
auto parsed_init = DeserializeOrThrow<protocol::RequestMessage>(init_json);
|
|
|
|
auto init_response_json = env.dispatcher.Dispatch(parsed_init);
|
|
auto init_response_any = codec::Deserialize<protocol::LSPAny>(init_response_json);
|
|
assertTrue(init_response_any.has_value(), "Initialize response should be valid JSON");
|
|
env.scheduler.WaitAll();
|
|
}
|
|
|
|
{
|
|
protocol::NotificationMessage initialized;
|
|
initialized.method = "initialized";
|
|
initialized.params = protocol::LSPAny(protocol::LSPObject{});
|
|
auto json = SerializeOrThrow(initialized);
|
|
auto parsed = DeserializeOrThrow<protocol::NotificationMessage>(json);
|
|
env.dispatcher.Dispatch(parsed);
|
|
}
|
|
|
|
auto send_notification = [&](std::string_view method,
|
|
const std::string& uri,
|
|
const std::string& content,
|
|
int version) {
|
|
protocol::NotificationMessage notification;
|
|
notification.method = std::string(method);
|
|
auto params = BuildNotificationParams(method, uri, content, version);
|
|
assertTrue(params.has_value(), "Missing notification params builder for: " + std::string(method));
|
|
notification.params = *params;
|
|
|
|
auto json = SerializeOrThrow(notification);
|
|
auto parsed = DeserializeOrThrow<protocol::NotificationMessage>(json);
|
|
env.dispatcher.Dispatch(parsed);
|
|
};
|
|
|
|
send_notification("textDocument/didOpen", main_uri, main_content, 1);
|
|
send_notification("textDocument/didOpen", type_hierarchy_uri, type_hierarchy_content, 1);
|
|
send_notification("textDocument/didOpen", inlay_hint_uri, inlay_hint_content, 1);
|
|
send_notification("textDocument/didOpen", rename_uri, rename_content, 1);
|
|
send_notification("textDocument/didOpen", code_action_uri, code_action_content, 1);
|
|
send_notification("textDocument/didChange", main_uri, main_content, 2);
|
|
|
|
auto code_action_tree = env.hub.parser().GetTree(code_action_uri);
|
|
auto code_action_diagnostics = BuildDiagnosticsFromSyntaxErrors(code_action_tree, code_action_content);
|
|
|
|
env.scheduler.Submit("json_cancel_me", []() -> std::optional<std::string> {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
|
return std::string("done");
|
|
});
|
|
|
|
{
|
|
auto methods = env.dispatcher.GetSupportedNotifications();
|
|
std::sort(methods.begin(), methods.end());
|
|
|
|
for (const auto& method : methods)
|
|
{
|
|
if (method == "exit" ||
|
|
method == "textDocument/didOpen" ||
|
|
method == "textDocument/didChange" ||
|
|
method == "textDocument/didClose" ||
|
|
method == "initialized")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
protocol::NotificationMessage notification;
|
|
notification.method = method;
|
|
|
|
auto params = BuildNotificationParams(method, main_uri, main_content, 1);
|
|
assertTrue(params.has_value(), "Missing notification params builder for: " + method);
|
|
notification.params = *params;
|
|
|
|
try
|
|
{
|
|
auto json = SerializeOrThrow(notification);
|
|
auto parsed = DeserializeOrThrow<protocol::NotificationMessage>(json);
|
|
env.dispatcher.Dispatch(parsed);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
throw std::runtime_error("Notification method failed: " + method + " (" + e.what() + ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
env.scheduler.WaitAll();
|
|
|
|
SeededRequestParams seeded;
|
|
auto dispatch_request_result = [&](std::string_view method, protocol::LSPAny params) -> std::optional<protocol::LSPAny> {
|
|
protocol::RequestMessage request;
|
|
request.id = protocol::string("seed_" + std::string(method));
|
|
request.method = std::string(method);
|
|
request.params = std::move(params);
|
|
|
|
try
|
|
{
|
|
auto json = SerializeOrThrow(request);
|
|
auto parsed = DeserializeOrThrow<protocol::RequestMessage>(json);
|
|
auto response_json = env.dispatcher.Dispatch(parsed);
|
|
|
|
auto response = codec::Deserialize<protocol::ResponseMessage>(response_json);
|
|
if (!response.has_value() || !response->result.has_value())
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
return response->result.value();
|
|
}
|
|
catch (const std::exception&)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
|
|
if (auto result = dispatch_request_result("textDocument/codeLens",
|
|
protocol::LSPAny(protocol::LSPObject{
|
|
{ "textDocument", ToTextDocument(main_uri) },
|
|
})))
|
|
{
|
|
seeded.code_lens = FirstArrayItem(*result);
|
|
}
|
|
|
|
if (auto result = dispatch_request_result("textDocument/documentLink",
|
|
protocol::LSPAny(protocol::LSPObject{
|
|
{ "textDocument", ToTextDocument(main_uri) },
|
|
})))
|
|
{
|
|
seeded.document_link = FirstArrayItem(*result);
|
|
}
|
|
|
|
if (auto result = dispatch_request_result("textDocument/inlayHint",
|
|
protocol::LSPAny(protocol::LSPObject{
|
|
{ "textDocument", ToTextDocument(inlay_hint_uri) },
|
|
{ "range", ToRange(FullDocumentRange(inlay_hint_content)) },
|
|
})))
|
|
{
|
|
seeded.inlay_hint = FirstArrayItem(*result);
|
|
}
|
|
|
|
auto call_incoming_pos = FindPosition(main_content, "UnitFunc(1);", false);
|
|
if (auto result = dispatch_request_result("textDocument/prepareCallHierarchy",
|
|
protocol::LSPAny(protocol::LSPObject{
|
|
{ "textDocument", ToTextDocument(main_uri) },
|
|
{ "position", ToPosition(call_incoming_pos) },
|
|
})))
|
|
{
|
|
seeded.call_hierarchy_incoming_item = FirstArrayItem(*result);
|
|
}
|
|
|
|
auto call_outgoing_pos = FindPosition(main_content, "TestDefinitions();", false);
|
|
if (auto result = dispatch_request_result("textDocument/prepareCallHierarchy",
|
|
protocol::LSPAny(protocol::LSPObject{
|
|
{ "textDocument", ToTextDocument(main_uri) },
|
|
{ "position", ToPosition(call_outgoing_pos) },
|
|
})))
|
|
{
|
|
seeded.call_hierarchy_outgoing_item = FirstArrayItem(*result);
|
|
}
|
|
|
|
auto type_pos = FindPosition(type_hierarchy_content, "Mid = class", false);
|
|
if (auto result = dispatch_request_result("textDocument/prepareTypeHierarchy",
|
|
protocol::LSPAny(protocol::LSPObject{
|
|
{ "textDocument", ToTextDocument(type_hierarchy_uri) },
|
|
{ "position", ToPosition(type_pos) },
|
|
})))
|
|
{
|
|
seeded.type_hierarchy_item = FirstArrayItem(*result);
|
|
}
|
|
|
|
if (auto result = dispatch_request_result("workspace/symbol",
|
|
protocol::LSPAny(protocol::LSPObject{
|
|
{ "query", protocol::string("Workspace") },
|
|
})))
|
|
{
|
|
seeded.workspace_symbol = FirstArrayItem(*result);
|
|
}
|
|
|
|
if (auto result = dispatch_request_result("textDocument/codeAction",
|
|
protocol::LSPAny(protocol::LSPObject{
|
|
{ "textDocument", ToTextDocument(code_action_uri) },
|
|
{ "range", ToRange(FullDocumentRange(code_action_content)) },
|
|
{ "context", protocol::LSPObject{ { "diagnostics", code_action_diagnostics } } },
|
|
})))
|
|
{
|
|
seeded.code_action = FirstArrayItem(*result);
|
|
}
|
|
|
|
{
|
|
auto methods = env.dispatcher.GetSupportedRequests();
|
|
std::sort(methods.begin(), methods.end());
|
|
|
|
protocol::integer request_counter = 1;
|
|
for (const auto& method : methods)
|
|
{
|
|
if (method == "initialize" || method == "shutdown")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
protocol::RequestMessage request;
|
|
request.id = "req_" + std::to_string(++request_counter);
|
|
request.method = method;
|
|
|
|
auto params = BuildRequestParams(method,
|
|
seeded,
|
|
main_uri,
|
|
main_content,
|
|
rename_uri,
|
|
rename_content,
|
|
code_action_uri,
|
|
code_action_content,
|
|
code_action_diagnostics);
|
|
assertTrue(params.has_value(), "Missing request params builder for: " + method);
|
|
request.params = *params;
|
|
|
|
try
|
|
{
|
|
auto json = SerializeOrThrow(request);
|
|
auto parsed = DeserializeOrThrow<protocol::RequestMessage>(json);
|
|
auto response_json = env.dispatcher.Dispatch(parsed);
|
|
auto response = codec::Deserialize<protocol::ResponseMessage>(response_json);
|
|
assertTrue(response.has_value(), "Request response should deserialize");
|
|
assertFalse(response->error.has_value(), "Request should not return error for: " + method);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
throw std::runtime_error("Request method failed: " + method + " (" + e.what() + ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
send_notification("textDocument/didClose", main_uri, "", 0);
|
|
send_notification("textDocument/didClose", rename_uri, "", 0);
|
|
send_notification("textDocument/didClose", code_action_uri, "", 0);
|
|
|
|
{
|
|
protocol::RequestMessage shutdown;
|
|
shutdown.id = "shutdown";
|
|
shutdown.method = "shutdown";
|
|
shutdown.params = protocol::LSPAny(protocol::LSPObject{});
|
|
|
|
auto json = SerializeOrThrow(shutdown);
|
|
auto parsed = DeserializeOrThrow<protocol::RequestMessage>(json);
|
|
auto response_json = env.dispatcher.Dispatch(parsed);
|
|
auto response_any = codec::Deserialize<protocol::LSPAny>(response_json);
|
|
assertTrue(response_any.has_value(), "Shutdown response should be valid JSON");
|
|
}
|
|
|
|
env.scheduler.WaitAll();
|
|
return result;
|
|
}
|
|
}
|