223 lines
8.6 KiB
C++
223 lines
8.6 KiB
C++
module;
|
|
|
|
export module lsp.test.provider.json_flow;
|
|
|
|
import std;
|
|
|
|
import lsp.test.framework;
|
|
import lsp.provider.initialize.initialize;
|
|
import lsp.provider.text_document.did_open;
|
|
import lsp.provider.text_document.completion;
|
|
import lsp.provider.completion_item.resolve;
|
|
import lsp.provider.text_document.definition;
|
|
import lsp.core.dispacther;
|
|
import lsp.manager.manager_hub;
|
|
import lsp.scheduler.async_executor;
|
|
import lsp.protocol;
|
|
import lsp.codec.facade;
|
|
import lsp.test.provider.fixtures;
|
|
|
|
export namespace lsp::test::provider
|
|
{
|
|
class JsonFlowTests
|
|
{
|
|
public:
|
|
static void Register(TestRunner& runner);
|
|
|
|
private:
|
|
static TestResult TestInitializeCompletionResolveDefinition();
|
|
};
|
|
}
|
|
|
|
namespace lsp::test::provider
|
|
{
|
|
namespace
|
|
{
|
|
struct ProviderEnv
|
|
{
|
|
scheduler::AsyncExecutor scheduler{ 1 };
|
|
manager::ManagerHub hub{};
|
|
core::ExecutionContext context;
|
|
|
|
ProviderEnv()
|
|
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
|
|
{
|
|
hub.Initialize();
|
|
}
|
|
};
|
|
|
|
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::ResponseMessage ParseResponse(const std::string& json)
|
|
{
|
|
auto parsed = codec::Deserialize<protocol::ResponseMessage>(json);
|
|
assertTrue(parsed.has_value(), "Failed to deserialize response");
|
|
return parsed.value();
|
|
}
|
|
|
|
protocol::Position FindPosition(const std::string& content, const std::string& marker, bool after_marker)
|
|
{
|
|
auto pos = content.find(marker);
|
|
assertTrue(pos != std::string::npos, "Marker not found in fixture");
|
|
protocol::Position result{};
|
|
result.line = 0;
|
|
result.character = 0;
|
|
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<std::uint32_t>(marker.size());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::optional<protocol::CompletionItem> FindItem(const std::vector<protocol::CompletionItem>& items,
|
|
const std::string& label)
|
|
{
|
|
auto it = std::find_if(items.begin(), items.end(), [&](const auto& item) {
|
|
return item.label == label;
|
|
});
|
|
if (it == items.end())
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
return *it;
|
|
}
|
|
}
|
|
|
|
void JsonFlowTests::Register(TestRunner& runner)
|
|
{
|
|
runner.addTest("json flow initialize->completion->resolve->definition", TestInitializeCompletionResolveDefinition);
|
|
}
|
|
|
|
TestResult JsonFlowTests::TestInitializeCompletionResolveDefinition()
|
|
{
|
|
TestResult result{ "", true, "ok" };
|
|
ProviderEnv env;
|
|
|
|
auto workspace_uri = ToUri(FixturePath("workspace"));
|
|
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"] = folders;
|
|
|
|
protocol::RequestMessage init_request;
|
|
init_request.id = "init";
|
|
init_request.method = "initialize";
|
|
init_request.params = protocol::LSPAny(init_params);
|
|
|
|
auto init_json = SerializeOrThrow(init_request);
|
|
auto parsed_init = DeserializeOrThrow<protocol::RequestMessage>(init_json);
|
|
::lsp::provider::Initialize init_provider;
|
|
auto init_response_json = init_provider.ProvideResponse(parsed_init, env.context);
|
|
auto init_response = ParseResponse(init_response_json);
|
|
assertTrue(init_response.result.has_value(), "Initialize should return result");
|
|
env.scheduler.WaitAll();
|
|
|
|
auto path = FixturePath("main_unit.tsf");
|
|
auto content = ReadTextFile(path);
|
|
auto uri = ToUri(path);
|
|
|
|
protocol::LSPObject text_doc{
|
|
{ "uri", uri },
|
|
{ "languageId", "tsl" },
|
|
{ "version", 1 },
|
|
{ "text", content }
|
|
};
|
|
protocol::NotificationMessage open_message;
|
|
open_message.method = "textDocument/didOpen";
|
|
open_message.params = protocol::LSPAny(protocol::LSPObject{ { "textDocument", protocol::LSPAny(text_doc) } });
|
|
|
|
auto open_json = SerializeOrThrow(open_message);
|
|
auto parsed_open = DeserializeOrThrow<protocol::NotificationMessage>(open_json);
|
|
::lsp::provider::text_document::DidOpen open_provider;
|
|
open_provider.HandleNotification(parsed_open, env.context);
|
|
|
|
auto completion_pos = FindPosition(content, "new Wid", true);
|
|
protocol::LSPObject completion_params{
|
|
{ "textDocument", protocol::LSPObject{ { "uri", uri } } },
|
|
{ "position", protocol::LSPObject{
|
|
{ "line", completion_pos.line },
|
|
{ "character", completion_pos.character } } }
|
|
};
|
|
protocol::RequestMessage completion_request;
|
|
completion_request.id = "c1";
|
|
completion_request.method = "textDocument/completion";
|
|
completion_request.params = protocol::LSPAny(completion_params);
|
|
|
|
auto completion_json = SerializeOrThrow(completion_request);
|
|
auto parsed_completion = DeserializeOrThrow<protocol::RequestMessage>(completion_json);
|
|
::lsp::provider::text_document::Completion completion_provider;
|
|
auto completion_response_json = completion_provider.ProvideResponse(parsed_completion, env.context);
|
|
auto completion_response = ParseResponse(completion_response_json);
|
|
auto list = codec::FromLSPAny.template operator()<protocol::CompletionList>(completion_response.result.value());
|
|
auto item = FindItem(list.items, "Widget");
|
|
assertTrue(item.has_value(), "Expected Widget completion");
|
|
|
|
protocol::RequestMessage resolve_request;
|
|
resolve_request.id = "r1";
|
|
resolve_request.method = "completionItem/resolve";
|
|
resolve_request.params = codec::ToLSPAny(*item);
|
|
|
|
auto resolve_json = SerializeOrThrow(resolve_request);
|
|
auto parsed_resolve = DeserializeOrThrow<protocol::RequestMessage>(resolve_json);
|
|
::lsp::provider::completion_item::Resolve resolve_provider;
|
|
auto resolve_response_json = resolve_provider.ProvideResponse(parsed_resolve, env.context);
|
|
auto resolve_response = ParseResponse(resolve_response_json);
|
|
auto resolved = codec::FromLSPAny.template operator()<protocol::CompletionItem>(resolve_response.result.value());
|
|
assertTrue(resolved.insertText.has_value(), "Resolved item should have insertText");
|
|
|
|
auto def_pos = FindPosition(content, "UnitFunc(1);", false);
|
|
protocol::LSPObject definition_params{
|
|
{ "textDocument", protocol::LSPObject{ { "uri", uri } } },
|
|
{ "position", protocol::LSPObject{
|
|
{ "line", def_pos.line },
|
|
{ "character", def_pos.character } } }
|
|
};
|
|
protocol::RequestMessage def_request;
|
|
def_request.id = "d1";
|
|
def_request.method = "textDocument/definition";
|
|
def_request.params = protocol::LSPAny(definition_params);
|
|
|
|
auto def_json = SerializeOrThrow(def_request);
|
|
auto parsed_def = DeserializeOrThrow<protocol::RequestMessage>(def_json);
|
|
::lsp::provider::text_document::Definition def_provider;
|
|
auto def_response_json = def_provider.ProvideResponse(parsed_def, env.context);
|
|
auto def_response = ParseResponse(def_response_json);
|
|
auto location = codec::FromLSPAny.template operator()<protocol::Location>(def_response.result.value());
|
|
|
|
auto expected_pos = FindPosition(content, "function UnitFunc", false);
|
|
assertTrue(location.range.start.line == expected_pos.line, "Definition should resolve in document");
|
|
return result;
|
|
}
|
|
}
|