211 lines
7.7 KiB
C++
211 lines
7.7 KiB
C++
module;
|
|
|
|
export module lsp.test.provider.interpreter;
|
|
|
|
import std;
|
|
|
|
import lsp.codec.facade;
|
|
import lsp.core.dispatcher;
|
|
import lsp.manager.manager_hub;
|
|
import lsp.protocol;
|
|
import lsp.provider.completion_item.resolve;
|
|
import lsp.provider.text_document.completion;
|
|
import lsp.scheduler.async_executor;
|
|
import lsp.test.framework;
|
|
import lsp.test.provider.fixtures;
|
|
|
|
export namespace lsp::test::provider
|
|
{
|
|
class InterpreterTests
|
|
{
|
|
public:
|
|
static void Register(TestRunner& runner);
|
|
|
|
private:
|
|
static TestResult TestInterpreterSystemLibraryCompletionResolve();
|
|
};
|
|
}
|
|
|
|
namespace lsp::test::provider
|
|
{
|
|
namespace
|
|
{
|
|
namespace codec = lsp::codec;
|
|
|
|
struct ProviderEnv
|
|
{
|
|
scheduler::AsyncExecutor scheduler{ 4 };
|
|
manager::ManagerHub hub{};
|
|
core::ExecutionContext context;
|
|
|
|
ProviderEnv()
|
|
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
|
|
{
|
|
hub.Initialize();
|
|
}
|
|
};
|
|
|
|
protocol::ResponseMessage ParseResponse(const std::string& json)
|
|
{
|
|
auto parsed = codec::Deserialize<protocol::ResponseMessage>(json);
|
|
assertTrue(parsed.has_value(), "Failed to deserialize response JSON");
|
|
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 test content");
|
|
|
|
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;
|
|
}
|
|
|
|
std::filesystem::path ExpandUserPath(const std::string& value)
|
|
{
|
|
std::filesystem::path path = value;
|
|
if (!value.empty() && value[0] == '~')
|
|
{
|
|
const char* home = std::getenv("HOME");
|
|
assertTrue(home != nullptr, "HOME is not set; cannot expand '~'");
|
|
if (value.size() == 1)
|
|
{
|
|
path = home;
|
|
}
|
|
else if (value[1] == '/')
|
|
{
|
|
path = std::filesystem::path(home) / value.substr(2);
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
std::size_t CountTsfFiles(const std::filesystem::path& root)
|
|
{
|
|
std::size_t count = 0;
|
|
auto options = std::filesystem::directory_options::follow_directory_symlink |
|
|
std::filesystem::directory_options::skip_permission_denied;
|
|
|
|
for (const auto& entry : std::filesystem::recursive_directory_iterator(root, options))
|
|
{
|
|
if (!entry.is_regular_file())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto ext = entry.path().extension().string();
|
|
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char ch) {
|
|
return static_cast<char>(std::tolower(ch));
|
|
});
|
|
|
|
if (ext == ".tsf")
|
|
{
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
}
|
|
|
|
void InterpreterTests::Register(TestRunner& runner)
|
|
{
|
|
runner.addTest("interpreter system library completion/resolve", TestInterpreterSystemLibraryCompletionResolve);
|
|
}
|
|
|
|
TestResult InterpreterTests::TestInterpreterSystemLibraryCompletionResolve()
|
|
{
|
|
TestResult result{ "", true, "ok" };
|
|
|
|
const auto& interpreter_root = InterpreterPath();
|
|
if (interpreter_root.empty())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
auto interpreter_path = ExpandUserPath(interpreter_root);
|
|
auto funcext_path = interpreter_path / "funcext";
|
|
|
|
assertTrue(std::filesystem::exists(funcext_path), "Interpreter funcext path does not exist: " + funcext_path.string());
|
|
assertTrue(std::filesystem::is_directory(funcext_path), "Interpreter funcext path is not a directory: " + funcext_path.string());
|
|
|
|
const auto expected_tsf_count = CountTsfFiles(funcext_path);
|
|
|
|
ProviderEnv env;
|
|
env.hub.symbols().LoadSystemLibrary(funcext_path.string());
|
|
|
|
const auto system_tables = env.hub.symbols().GetSystemSymbolTables();
|
|
assertEqual(expected_tsf_count, system_tables.size(), "System library should index all .tsf files under funcext");
|
|
|
|
auto nonce = std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
|
|
auto doc_path = std::filesystem::temp_directory_path() / ("tsl_interpreter_completion_" + nonce + ".tsl");
|
|
auto uri = ToUri(doc_path);
|
|
|
|
std::string content = "unit InterpreterCompletion;\n"
|
|
"begin\n"
|
|
" m := new PageHel\n"
|
|
"end;\n";
|
|
|
|
protocol::DidOpenTextDocumentParams open_params;
|
|
open_params.textDocument.uri = uri;
|
|
open_params.textDocument.languageId = "tsl";
|
|
open_params.textDocument.version = 1;
|
|
open_params.textDocument.text = content;
|
|
env.hub.documents().OpenDocument(open_params);
|
|
|
|
protocol::CompletionParams completion_params;
|
|
completion_params.textDocument.uri = uri;
|
|
completion_params.position = FindPosition(content, "new PageHel", true);
|
|
|
|
protocol::RequestMessage completion_request;
|
|
completion_request.id = "c1";
|
|
completion_request.method = "textDocument/completion";
|
|
completion_request.params = codec::ToLSPAny(completion_params);
|
|
|
|
::lsp::provider::text_document::Completion completion_provider;
|
|
auto completion_response_json = completion_provider.ProvideResponse(completion_request, env.context);
|
|
auto completion_response = ParseResponse(completion_response_json);
|
|
assertTrue(completion_response.result.has_value(), "Completion should return result");
|
|
auto list = codec::FromLSPAny.template operator()<protocol::CompletionList>(*completion_response.result);
|
|
|
|
auto item_it = std::find_if(list.items.begin(), list.items.end(), [](const auto& item) {
|
|
return item.label == "PageHelperFreeMutex";
|
|
});
|
|
assertTrue(item_it != list.items.end(), "Completion should include PageHelperFreeMutex from system library");
|
|
|
|
protocol::RequestMessage resolve_request;
|
|
resolve_request.id = "r1";
|
|
resolve_request.method = "completionItem/resolve";
|
|
resolve_request.params = codec::ToLSPAny(*item_it);
|
|
|
|
::lsp::provider::completion_item::Resolve resolve_provider;
|
|
auto resolve_response_json = resolve_provider.ProvideResponse(resolve_request, env.context);
|
|
auto resolve_response = ParseResponse(resolve_response_json);
|
|
assertTrue(resolve_response.result.has_value(), "Resolve should return result");
|
|
auto resolved = codec::FromLSPAny.template operator()<protocol::CompletionItem>(*resolve_response.result);
|
|
|
|
assertTrue(resolved.insertText.has_value(), "Resolved completion item should include insertText snippet");
|
|
assertTrue(resolved.insertText->contains("PageHelperFreeMutex("), "Resolved snippet should include class name");
|
|
assertTrue(resolved.insertText->contains("${1:"), "Resolved snippet should include parameter placeholder");
|
|
return result;
|
|
}
|
|
}
|
|
|