🐛 fix(core): enforce strict LSP lifecycle
This commit is contained in:
@@ -6,6 +6,7 @@ import spdlog;
|
|||||||
import std;
|
import std;
|
||||||
|
|
||||||
import lsp.core.server;
|
import lsp.core.server;
|
||||||
|
import lsp.provider.manifest;
|
||||||
import lsp.utils.args_parser;
|
import lsp.utils.args_parser;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -46,11 +47,17 @@ export int Run(int argc, char* argv[])
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int exit_code = 1;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
spdlog::info("TSL-LSP server starting...");
|
spdlog::info("TSL-LSP server starting...");
|
||||||
lsp::core::LspServer server(config.thread_count, config.interpreter_path);
|
lsp::core::LspServer server(
|
||||||
server.Run();
|
std::cin,
|
||||||
|
std::cout,
|
||||||
|
lsp::provider::RegisterAllProviders,
|
||||||
|
config.thread_count,
|
||||||
|
config.interpreter_path);
|
||||||
|
exit_code = server.Run();
|
||||||
}
|
}
|
||||||
catch (const std::exception& error)
|
catch (const std::exception& error)
|
||||||
{
|
{
|
||||||
@@ -67,7 +74,7 @@ export int Run(int argc, char* argv[])
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::info("TSL-LSP server stopped normally");
|
spdlog::info("TSL-LSP server stopped with exit code {}", exit_code);
|
||||||
spdlog::shutdown();
|
spdlog::shutdown();
|
||||||
return 0;
|
return exit_code;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,19 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
export module lsp.core.dispatcher;
|
export module lsp.core.dispatcher;
|
||||||
import spdlog;
|
|
||||||
|
|
||||||
|
import spdlog;
|
||||||
import std;
|
import std;
|
||||||
import lsp.protocol.types;
|
|
||||||
import lsp.codec.facade;
|
import lsp.codec.facade;
|
||||||
import lsp.scheduler.async_executor;
|
|
||||||
import lsp.manager.manager_hub;
|
import lsp.manager.manager_hub;
|
||||||
|
import lsp.protocol.types;
|
||||||
|
import lsp.scheduler.async_executor;
|
||||||
|
|
||||||
namespace transform = lsp::codec;
|
namespace transform = lsp::codec;
|
||||||
|
|
||||||
export namespace lsp::core
|
export namespace lsp::core
|
||||||
{
|
{
|
||||||
enum class ServerLifecycleEvent
|
|
||||||
{
|
|
||||||
kInitializing,
|
|
||||||
kInitialized,
|
|
||||||
kInitializeFailed,
|
|
||||||
kShuttingDown,
|
|
||||||
kShutdown
|
|
||||||
};
|
|
||||||
|
|
||||||
using LifecycleCallback = std::function<void(ServerLifecycleEvent)>;
|
|
||||||
|
|
||||||
class IProvider
|
class IProvider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -32,61 +22,65 @@ export namespace lsp::core
|
|||||||
virtual std::string GetProviderName() const = 0;
|
virtual std::string GetProviderName() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ExecutionContext;
|
||||||
|
|
||||||
class IRequestProvider : public IProvider
|
class IRequestProvider : public IProvider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~IRequestProvider() = default;
|
virtual ~IRequestProvider() = default;
|
||||||
virtual std::string ProvideResponse(const protocol::RequestMessage& request, class ExecutionContext& execution_context) = 0;
|
virtual std::string ProvideResponse(const protocol::RequestMessage& request,
|
||||||
|
ExecutionContext& execution_context) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class INotificationProvider : public IProvider
|
class INotificationProvider : public IProvider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~INotificationProvider() = default;
|
virtual ~INotificationProvider() = default;
|
||||||
virtual void HandleNotification(const protocol::NotificationMessage& notification, class ExecutionContext& execution_context) = 0;
|
virtual void HandleNotification(const protocol::NotificationMessage& notification,
|
||||||
|
ExecutionContext& execution_context) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ExecutionContext
|
class ExecutionContext
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ExecutionContext(LifecycleCallback lifecycle_callback,
|
ExecutionContext(scheduler::async_executor::AsyncExecutor& scheduler,
|
||||||
scheduler::async_executor::AsyncExecutor& scheduler,
|
manager::ManagerHub& manager_hub)
|
||||||
manager::ManagerHub& manager_hub) :
|
: async_executor_(scheduler), manager_hub_(manager_hub)
|
||||||
lifecycle_callback_(lifecycle_callback),
|
|
||||||
async_executor_(scheduler),
|
|
||||||
manager_hub_(manager_hub) {}
|
|
||||||
|
|
||||||
scheduler::async_executor::AsyncExecutor& GetScheduler() const { return async_executor_; }
|
|
||||||
manager::ManagerHub& GetManagerHub() const { return manager_hub_; }
|
|
||||||
|
|
||||||
void TriggerLifecycleEvent(ServerLifecycleEvent event) const
|
|
||||||
{
|
{
|
||||||
if (lifecycle_callback_)
|
}
|
||||||
lifecycle_callback_(event);
|
|
||||||
|
scheduler::async_executor::AsyncExecutor& GetScheduler() const
|
||||||
|
{
|
||||||
|
return async_executor_;
|
||||||
|
}
|
||||||
|
|
||||||
|
manager::ManagerHub& GetManagerHub() const
|
||||||
|
{
|
||||||
|
return manager_hub_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LifecycleCallback lifecycle_callback_;
|
|
||||||
scheduler::async_executor::AsyncExecutor& async_executor_;
|
scheduler::async_executor::AsyncExecutor& async_executor_;
|
||||||
manager::ManagerHub& manager_hub_;
|
manager::ManagerHub& manager_hub_;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string BuildErrorResponseMessage(const protocol::RequestMessage& request, protocol::ErrorCodes code, const std::string& message);
|
std::string BuildErrorResponseMessage(std::optional<protocol::RequestId> id,
|
||||||
|
protocol::ErrorCodes code,
|
||||||
|
std::string_view message);
|
||||||
|
std::string BuildErrorResponseMessage(const protocol::RequestMessage& request,
|
||||||
|
protocol::ErrorCodes code,
|
||||||
|
std::string_view message);
|
||||||
|
|
||||||
class RequestDispatcher
|
class RequestDispatcher
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RequestDispatcher();
|
RequestDispatcher(scheduler::async_executor::AsyncExecutor& scheduler,
|
||||||
|
manager::ManagerHub& manager_hub);
|
||||||
~RequestDispatcher() = default;
|
~RequestDispatcher() = default;
|
||||||
|
|
||||||
void SetRequestScheduler(scheduler::async_executor::AsyncExecutor* scheduler);
|
|
||||||
void SetManagerHub(manager::ManagerHub* manager_hub);
|
|
||||||
|
|
||||||
void RegisterRequestProvider(std::shared_ptr<IRequestProvider> provider);
|
void RegisterRequestProvider(std::shared_ptr<IRequestProvider> provider);
|
||||||
void RegisterNotificationProvider(std::shared_ptr<INotificationProvider> provider);
|
void RegisterNotificationProvider(std::shared_ptr<INotificationProvider> provider);
|
||||||
|
|
||||||
void RegisterLifecycleCallback(LifecycleCallback callback);
|
|
||||||
|
|
||||||
std::string Dispatch(const protocol::RequestMessage& request);
|
std::string Dispatch(const protocol::RequestMessage& request);
|
||||||
void Dispatch(const protocol::NotificationMessage& notification);
|
void Dispatch(const protocol::NotificationMessage& notification);
|
||||||
|
|
||||||
@@ -97,99 +91,71 @@ export namespace lsp::core
|
|||||||
std::vector<std::string> GetAllSupportedMethods() const;
|
std::vector<std::string> GetAllSupportedMethods() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void NotifyAllLifecycleListeners(ServerLifecycleEvent event);
|
|
||||||
std::string HandleUnknownRequest(const protocol::RequestMessage& request);
|
std::string HandleUnknownRequest(const protocol::RequestMessage& request);
|
||||||
void HandleUnknownNotification(const protocol::NotificationMessage& notification);
|
void HandleUnknownNotification(const protocol::NotificationMessage& notification);
|
||||||
|
|
||||||
private:
|
|
||||||
mutable std::shared_mutex providers_mutex_;
|
mutable std::shared_mutex providers_mutex_;
|
||||||
std::unordered_map<std::string, std::shared_ptr<IRequestProvider>> providers_;
|
std::unordered_map<std::string, std::shared_ptr<IRequestProvider>> providers_;
|
||||||
|
|
||||||
mutable std::shared_mutex notification_providers_mutex_;
|
mutable std::shared_mutex notification_providers_mutex_;
|
||||||
std::unordered_map<std::string, std::shared_ptr<INotificationProvider>> notification_providers_;
|
std::unordered_map<std::string, std::shared_ptr<INotificationProvider>>
|
||||||
|
notification_providers_;
|
||||||
|
|
||||||
std::mutex callbacks_mutex_;
|
scheduler::async_executor::AsyncExecutor& async_executor_;
|
||||||
std::vector<LifecycleCallback> lifecycle_callbacks_;
|
manager::ManagerHub& manager_hub_;
|
||||||
|
|
||||||
LifecycleCallback context_lifecycle_callback_;
|
|
||||||
|
|
||||||
scheduler::async_executor::AsyncExecutor* async_executor_ = nullptr;
|
|
||||||
manager::ManagerHub* manager_hub_ = nullptr;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace lsp::core
|
namespace lsp::core
|
||||||
{
|
{
|
||||||
RequestDispatcher::RequestDispatcher()
|
RequestDispatcher::RequestDispatcher(
|
||||||
|
scheduler::async_executor::AsyncExecutor& scheduler,
|
||||||
|
manager::ManagerHub& manager_hub)
|
||||||
|
: async_executor_(scheduler), manager_hub_(manager_hub)
|
||||||
{
|
{
|
||||||
context_lifecycle_callback_ = [this](ServerLifecycleEvent event) {
|
|
||||||
NotifyAllLifecycleListeners(event);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestDispatcher::SetRequestScheduler(scheduler::async_executor::AsyncExecutor* scheduler)
|
void RequestDispatcher::RegisterRequestProvider(
|
||||||
|
std::shared_ptr<IRequestProvider> provider)
|
||||||
{
|
{
|
||||||
async_executor_ = scheduler;
|
std::unique_lock lock(providers_mutex_);
|
||||||
spdlog::debug("Request scheduler set");
|
|
||||||
}
|
|
||||||
|
|
||||||
void RequestDispatcher::SetManagerHub(manager::ManagerHub* manager_hub)
|
|
||||||
{
|
|
||||||
manager_hub_ = manager_hub;
|
|
||||||
spdlog::debug("Manager hub bound to dispatcher");
|
|
||||||
}
|
|
||||||
|
|
||||||
void RequestDispatcher::RegisterRequestProvider(std::shared_ptr<IRequestProvider> provider)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::shared_mutex> lock(providers_mutex_);
|
|
||||||
std::string method = provider->GetMethod();
|
std::string method = provider->GetMethod();
|
||||||
providers_[method] = provider;
|
providers_[method] = std::move(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestDispatcher::RegisterNotificationProvider(std::shared_ptr<INotificationProvider> provider)
|
void RequestDispatcher::RegisterNotificationProvider(
|
||||||
|
std::shared_ptr<INotificationProvider> provider)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::shared_mutex> lock(notification_providers_mutex_);
|
std::unique_lock lock(notification_providers_mutex_);
|
||||||
std::string method = provider->GetMethod();
|
std::string method = provider->GetMethod();
|
||||||
notification_providers_[method] = provider;
|
notification_providers_[method] = std::move(provider);
|
||||||
}
|
|
||||||
|
|
||||||
void RequestDispatcher::RegisterLifecycleCallback(LifecycleCallback callback)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(callbacks_mutex_);
|
|
||||||
lifecycle_callbacks_.push_back(std::move(callback));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string RequestDispatcher::Dispatch(const protocol::RequestMessage& request)
|
std::string RequestDispatcher::Dispatch(const protocol::RequestMessage& request)
|
||||||
{
|
{
|
||||||
std::shared_ptr<IRequestProvider> provider = nullptr;
|
std::shared_ptr<IRequestProvider> provider;
|
||||||
{
|
{
|
||||||
std::shared_lock<std::shared_mutex> lock(providers_mutex_);
|
std::shared_lock lock(providers_mutex_);
|
||||||
auto it = providers_.find(request.method);
|
auto provider_it = providers_.find(request.method);
|
||||||
if (it != providers_.end())
|
if (provider_it != providers_.end())
|
||||||
provider = it->second;
|
provider = provider_it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!provider)
|
if (!provider)
|
||||||
return HandleUnknownRequest(request);
|
return HandleUnknownRequest(request);
|
||||||
|
|
||||||
if (!async_executor_ || !manager_hub_)
|
ExecutionContext context(async_executor_, manager_hub_);
|
||||||
{
|
|
||||||
spdlog::error("RequestDispatcher dependencies not set");
|
|
||||||
return "{}";
|
|
||||||
}
|
|
||||||
|
|
||||||
ExecutionContext context(context_lifecycle_callback_, *async_executor_, *manager_hub_);
|
|
||||||
return provider->ProvideResponse(request, context);
|
return provider->ProvideResponse(request, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestDispatcher::Dispatch(const protocol::NotificationMessage& notification)
|
void RequestDispatcher::Dispatch(const protocol::NotificationMessage& notification)
|
||||||
{
|
{
|
||||||
std::shared_ptr<INotificationProvider> provider = nullptr;
|
std::shared_ptr<INotificationProvider> provider;
|
||||||
{
|
{
|
||||||
std::shared_lock<std::shared_mutex> lock(notification_providers_mutex_);
|
std::shared_lock lock(notification_providers_mutex_);
|
||||||
auto it = notification_providers_.find(notification.method);
|
auto provider_it = notification_providers_.find(notification.method);
|
||||||
if (it != notification_providers_.end())
|
if (provider_it != notification_providers_.end())
|
||||||
provider = it->second;
|
provider = provider_it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!provider)
|
if (!provider)
|
||||||
@@ -198,45 +164,45 @@ namespace lsp::core
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!async_executor_ || !manager_hub_)
|
ExecutionContext context(async_executor_, manager_hub_);
|
||||||
{
|
|
||||||
spdlog::error("NotificationDispatcher dependencies not set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExecutionContext context(context_lifecycle_callback_, *async_executor_, *manager_hub_);
|
|
||||||
provider->HandleNotification(notification, context);
|
provider->HandleNotification(notification, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestDispatcher::SupportsRequest(const std::string& method) const
|
bool RequestDispatcher::SupportsRequest(const std::string& method) const
|
||||||
{
|
{
|
||||||
std::shared_lock<std::shared_mutex> lock(providers_mutex_);
|
std::shared_lock lock(providers_mutex_);
|
||||||
return providers_.contains(method);
|
return providers_.contains(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestDispatcher::SupportsNotification(const std::string& method) const
|
bool RequestDispatcher::SupportsNotification(const std::string& method) const
|
||||||
{
|
{
|
||||||
std::shared_lock<std::shared_mutex> lock(notification_providers_mutex_);
|
std::shared_lock lock(notification_providers_mutex_);
|
||||||
return notification_providers_.contains(method);
|
return notification_providers_.contains(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> RequestDispatcher::GetSupportedRequests() const
|
std::vector<std::string> RequestDispatcher::GetSupportedRequests() const
|
||||||
{
|
{
|
||||||
std::vector<std::string> methods;
|
std::vector<std::string> methods;
|
||||||
std::shared_lock<std::shared_mutex> lock(providers_mutex_);
|
std::shared_lock lock(providers_mutex_);
|
||||||
methods.reserve(providers_.size());
|
methods.reserve(providers_.size());
|
||||||
for (const auto& [method, _] : providers_)
|
for (const auto& [method, provider] : providers_)
|
||||||
|
{
|
||||||
|
static_cast<void>(provider);
|
||||||
methods.push_back(method);
|
methods.push_back(method);
|
||||||
|
}
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> RequestDispatcher::GetSupportedNotifications() const
|
std::vector<std::string> RequestDispatcher::GetSupportedNotifications() const
|
||||||
{
|
{
|
||||||
std::vector<std::string> methods;
|
std::vector<std::string> methods;
|
||||||
std::shared_lock<std::shared_mutex> lock(notification_providers_mutex_);
|
std::shared_lock lock(notification_providers_mutex_);
|
||||||
methods.reserve(notification_providers_.size());
|
methods.reserve(notification_providers_.size());
|
||||||
for (const auto& [method, _] : notification_providers_)
|
for (const auto& [method, provider] : notification_providers_)
|
||||||
|
{
|
||||||
|
static_cast<void>(provider);
|
||||||
methods.push_back(method);
|
methods.push_back(method);
|
||||||
|
}
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,48 +214,40 @@ namespace lsp::core
|
|||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestDispatcher::NotifyAllLifecycleListeners(ServerLifecycleEvent event)
|
std::string RequestDispatcher::HandleUnknownRequest(
|
||||||
{
|
const protocol::RequestMessage& request)
|
||||||
std::lock_guard<std::mutex> lock(callbacks_mutex_);
|
|
||||||
for (const auto& callback : lifecycle_callbacks_)
|
|
||||||
{
|
|
||||||
if (callback)
|
|
||||||
callback(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string RequestDispatcher::HandleUnknownRequest(const protocol::RequestMessage& request)
|
|
||||||
{
|
{
|
||||||
spdlog::warn("No request provider registered for method: {}", request.method);
|
spdlog::warn("No request provider registered for method: {}", request.method);
|
||||||
|
return BuildErrorResponseMessage(
|
||||||
|
request, protocol::ErrorCodes::MethodNotFound, "Method not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestDispatcher::HandleUnknownNotification(
|
||||||
|
const protocol::NotificationMessage& notification)
|
||||||
|
{
|
||||||
|
spdlog::warn("No notification provider registered for method: {}",
|
||||||
|
notification.method);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BuildErrorResponseMessage(std::optional<protocol::RequestId> id,
|
||||||
|
protocol::ErrorCodes code,
|
||||||
|
std::string_view message)
|
||||||
|
{
|
||||||
protocol::ResponseMessage response;
|
protocol::ResponseMessage response;
|
||||||
response.id = request.id;
|
response.id = std::move(id);
|
||||||
response.error = protocol::ResponseError{
|
response.error = protocol::ResponseError{
|
||||||
.jsonrpc = "2.0",
|
.jsonrpc = "2.0",
|
||||||
.code = static_cast<protocol::integer>(protocol::ErrorCodes::MethodNotFound),
|
.code = static_cast<protocol::integer>(code),
|
||||||
.message = "Method not supported",
|
.message = std::string(message),
|
||||||
.data = std::nullopt
|
.data = std::nullopt,
|
||||||
};
|
};
|
||||||
auto json = transform::Serialize(response);
|
return transform::Serialize(response).value();
|
||||||
return json.value_or("{}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestDispatcher::HandleUnknownNotification(const protocol::NotificationMessage& notification)
|
std::string BuildErrorResponseMessage(const protocol::RequestMessage& request,
|
||||||
|
protocol::ErrorCodes code,
|
||||||
|
std::string_view message)
|
||||||
{
|
{
|
||||||
spdlog::warn("No notification provider registered for method: {}", notification.method);
|
return BuildErrorResponseMessage(request.id, code, message);
|
||||||
}
|
|
||||||
|
|
||||||
std::string BuildErrorResponseMessage(const protocol::RequestMessage& request, protocol::ErrorCodes code, const std::string& message)
|
|
||||||
{
|
|
||||||
protocol::ResponseMessage response;
|
|
||||||
response.id = request.id;
|
|
||||||
protocol::ResponseError error;
|
|
||||||
error.code = static_cast<protocol::integer>(code);
|
|
||||||
error.message = message;
|
|
||||||
response.error = error;
|
|
||||||
auto json = transform::Serialize(response);
|
|
||||||
if (json.has_value())
|
|
||||||
return json.value();
|
|
||||||
spdlog::error("Failed to serialize error response.");
|
|
||||||
return R"({"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"Failed to serialize error response"}})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+186
-241
@@ -1,65 +1,59 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
export module lsp.core.server;
|
export module lsp.core.server;
|
||||||
|
|
||||||
import spdlog;
|
import spdlog;
|
||||||
|
import std;
|
||||||
import tree_sitter;
|
import tree_sitter;
|
||||||
|
|
||||||
import std;
|
|
||||||
|
|
||||||
import lsp.bridge.win32_stdio;
|
import lsp.bridge.win32_stdio;
|
||||||
import lsp.core.dispatcher;
|
|
||||||
import lsp.protocol;
|
|
||||||
import lsp.codec.facade;
|
import lsp.codec.facade;
|
||||||
|
import lsp.core.dispatcher;
|
||||||
import lsp.language.ast;
|
import lsp.language.ast;
|
||||||
import lsp.manager.manager_hub;
|
|
||||||
import lsp.manager.bootstrap;
|
import lsp.manager.bootstrap;
|
||||||
import lsp.manager.events;
|
import lsp.manager.events;
|
||||||
import lsp.scheduler.async_executor;
|
import lsp.manager.manager_hub;
|
||||||
import lsp.provider.base.interface;
|
import lsp.protocol;
|
||||||
import lsp.provider.manifest;
|
import lsp.provider.manifest;
|
||||||
|
import lsp.scheduler.async_executor;
|
||||||
|
|
||||||
namespace transform = lsp::codec;
|
namespace transform = lsp::codec;
|
||||||
|
|
||||||
export namespace lsp::core
|
export namespace lsp::core
|
||||||
{
|
{
|
||||||
|
using ProviderRegistrar = std::function<void(RequestDispatcher&)>;
|
||||||
|
|
||||||
|
enum class ServerState
|
||||||
|
{
|
||||||
|
kUninitialized,
|
||||||
|
kRunning,
|
||||||
|
kShutdownRequested,
|
||||||
|
kExiting,
|
||||||
|
};
|
||||||
|
|
||||||
class LspServer
|
class LspServer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit LspServer(std::size_t concurrency = std::thread::hardware_concurrency(),
|
LspServer(std::istream& input,
|
||||||
|
std::ostream& output,
|
||||||
|
ProviderRegistrar registrar,
|
||||||
|
std::size_t concurrency = std::thread::hardware_concurrency(),
|
||||||
std::string interpreter_path = "");
|
std::string interpreter_path = "");
|
||||||
~LspServer();
|
~LspServer() = default;
|
||||||
void Run();
|
|
||||||
|
int Run();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// 读取LSP消息
|
|
||||||
std::optional<std::string> ReadMessage();
|
std::optional<std::string> ReadMessage();
|
||||||
|
|
||||||
// 处理LSP请求 - 返回序列化的响应或空字符串(对于通知)
|
|
||||||
void HandleMessage(const std::string& raw_message);
|
void HandleMessage(const std::string& raw_message);
|
||||||
|
|
||||||
// 发送LSP消息(响应/通知)
|
|
||||||
void SendMessage(const std::string& message);
|
void SendMessage(const std::string& message);
|
||||||
|
|
||||||
// 处理不同类型的消息
|
|
||||||
void HandleRequest(const protocol::RequestMessage& request);
|
void HandleRequest(const protocol::RequestMessage& request);
|
||||||
void HandleNotification(const protocol::NotificationMessage& notification);
|
void HandleNotification(const protocol::NotificationMessage& notification);
|
||||||
void HandleResponse(const protocol::ResponseMessage& response);
|
void HandleResponse(const protocol::ResponseMessage& response);
|
||||||
|
|
||||||
// 生命周期事件处理
|
|
||||||
void OnLifecycleEvent(provider::ServerLifecycleEvent event);
|
|
||||||
|
|
||||||
// 判断是否需要同步处理
|
|
||||||
bool RequiresSyncProcessing(const std::string& method) const;
|
|
||||||
|
|
||||||
// 检查是否可以处理请求
|
|
||||||
bool CanProcessRequest(const std::string& method) const;
|
|
||||||
|
|
||||||
// 处理取消请求
|
|
||||||
void HandleCancelRequest(const protocol::NotificationMessage& notification);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void InitializeManagerHub();
|
void InitializeManagerHub();
|
||||||
void RegisterProviders();
|
void RegisterProviders(ProviderRegistrar registrar);
|
||||||
void RegisterDiagnosticsPublisher();
|
void RegisterDiagnosticsPublisher();
|
||||||
|
|
||||||
void PublishDiagnostics(const protocol::DocumentUri& uri,
|
void PublishDiagnostics(const protocol::DocumentUri& uri,
|
||||||
@@ -68,32 +62,42 @@ export namespace lsp::core
|
|||||||
const protocol::string& content);
|
const protocol::string& content);
|
||||||
void ClearDiagnostics(const protocol::DocumentUri& uri);
|
void ClearDiagnostics(const protocol::DocumentUri& uri);
|
||||||
|
|
||||||
// 错误处理
|
void SendError(const protocol::RequestMessage& request,
|
||||||
void SendError(const protocol::RequestMessage& request, protocol::ErrorCodes code, const std::string& message);
|
protocol::ErrorCodes code,
|
||||||
void SendStateError(const protocol::RequestMessage& request);
|
std::string_view message);
|
||||||
|
|
||||||
private:
|
std::istream& input_;
|
||||||
RequestDispatcher dispatcher_;
|
std::ostream& output_;
|
||||||
manager::ManagerHub manager_hub_;
|
manager::ManagerHub manager_hub_;
|
||||||
scheduler::async_executor::AsyncExecutor async_executor_;
|
scheduler::async_executor::AsyncExecutor async_executor_;
|
||||||
|
RequestDispatcher dispatcher_;
|
||||||
std::string interpreter_path_;
|
std::string interpreter_path_;
|
||||||
|
|
||||||
std::atomic<bool> is_initialized_ = false;
|
ServerState state_ = ServerState::kUninitialized;
|
||||||
std::atomic<bool> is_shutting_down_ = false;
|
int exit_code_ = 1;
|
||||||
|
std::atomic<bool> fatal_io_error_ = false;
|
||||||
std::mutex output_mutex_;
|
std::mutex output_mutex_;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace lsp::core
|
namespace lsp::core
|
||||||
{
|
{
|
||||||
LspServer::LspServer(std::size_t concurrency, std::string interpreter_path) : manager_hub_(),
|
LspServer::LspServer(std::istream& input,
|
||||||
|
std::ostream& output,
|
||||||
|
ProviderRegistrar registrar,
|
||||||
|
std::size_t concurrency,
|
||||||
|
std::string interpreter_path)
|
||||||
|
: input_(input),
|
||||||
|
output_(output),
|
||||||
|
manager_hub_(),
|
||||||
async_executor_(concurrency),
|
async_executor_(concurrency),
|
||||||
|
dispatcher_(async_executor_, manager_hub_),
|
||||||
interpreter_path_(std::move(interpreter_path))
|
interpreter_path_(std::move(interpreter_path))
|
||||||
{
|
{
|
||||||
spdlog::info("Initializing LSP server with {} worker threads", concurrency);
|
spdlog::info("Initializing LSP server with {} worker threads", concurrency);
|
||||||
|
|
||||||
InitializeManagerHub();
|
InitializeManagerHub();
|
||||||
RegisterProviders();
|
RegisterProviders(std::move(registrar));
|
||||||
if (provider::kEnableDiagnosticsPublisher)
|
if (provider::kEnableDiagnosticsPublisher)
|
||||||
{
|
{
|
||||||
RegisterDiagnosticsPublisher();
|
RegisterDiagnosticsPublisher();
|
||||||
@@ -103,48 +107,48 @@ namespace lsp::core
|
|||||||
spdlog::debug("Diagnostics publisher disabled (staged rollout)");
|
spdlog::debug("Diagnostics publisher disabled (staged rollout)");
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::debug("LSP server initialized with {} providers.", dispatcher_.GetAllSupportedMethods().size());
|
spdlog::debug("LSP server initialized with {} providers.",
|
||||||
|
dispatcher_.GetAllSupportedMethods().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
LspServer::~LspServer()
|
int LspServer::Run()
|
||||||
{
|
|
||||||
is_shutting_down_ = true;
|
|
||||||
spdlog::info("LSP server shutting down...");
|
|
||||||
}
|
|
||||||
|
|
||||||
void LspServer::Run()
|
|
||||||
{
|
{
|
||||||
spdlog::info("LSP server starting main loop...");
|
spdlog::info("LSP server starting main loop...");
|
||||||
spdlog::info("Waiting for LSP messages on stdin...");
|
spdlog::info("Waiting for LSP messages on stdin...");
|
||||||
|
|
||||||
bridge::win32_stdio::SetStdioBinaryMode();
|
bridge::win32_stdio::SetStdioBinaryMode();
|
||||||
|
|
||||||
while (!is_shutting_down_)
|
while (state_ != ServerState::kExiting)
|
||||||
{
|
{
|
||||||
|
if (fatal_io_error_)
|
||||||
|
break;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::optional<std::string> message = ReadMessage();
|
auto message = ReadMessage();
|
||||||
if (!message)
|
if (!message)
|
||||||
{
|
|
||||||
if (std::cin.eof())
|
|
||||||
{
|
{
|
||||||
spdlog::info("End of input stream, exiting main loop");
|
spdlog::info("End of input stream, exiting main loop");
|
||||||
break; // EOF
|
break;
|
||||||
}
|
}
|
||||||
spdlog::debug("No message received, continuing...");
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleMessage(*message);
|
HandleMessage(*message);
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& error)
|
||||||
{
|
{
|
||||||
spdlog::error("Error in main loop: {}", e.what());
|
spdlog::error("Fatal error in main loop: {}", error.what());
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
exit_code_ = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
spdlog::error("Unknown fatal error in main loop");
|
||||||
|
exit_code_ = 1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::info("LSP server main loop ended");
|
spdlog::info("LSP server main loop ended");
|
||||||
|
return exit_code_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> LspServer::ReadMessage()
|
std::optional<std::string> LspServer::ReadMessage()
|
||||||
@@ -152,56 +156,32 @@ namespace lsp::core
|
|||||||
std::string line;
|
std::string line;
|
||||||
std::size_t content_length = 0;
|
std::size_t content_length = 0;
|
||||||
|
|
||||||
// 读取 LSP Header
|
while (std::getline(input_, line))
|
||||||
while (std::getline(std::cin, line))
|
|
||||||
{
|
{
|
||||||
// 去掉尾部 \\r
|
|
||||||
if (!line.empty() && line.back() == '\r')
|
if (!line.empty() && line.back() == '\r')
|
||||||
{
|
|
||||||
line.pop_back();
|
line.pop_back();
|
||||||
}
|
|
||||||
|
|
||||||
if (line.empty())
|
if (line.empty())
|
||||||
{
|
break;
|
||||||
break; // 空行表示 header 结束
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.rfind("Content-Length:", 0) == 0)
|
if (line.rfind("Content-Length:", 0) != 0)
|
||||||
{
|
continue;
|
||||||
std::string length_str = line.substr(15); // 跳过 "Content-Length:"
|
|
||||||
std::size_t start = length_str.find_first_not_of(' ');
|
std::string length = line.substr(std::string_view("Content-Length:").size());
|
||||||
if (start != std::string::npos)
|
const auto start = length.find_first_not_of(' ');
|
||||||
{
|
if (start == std::string::npos)
|
||||||
length_str = length_str.substr(start);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
content_length = std::stoul(length_str);
|
|
||||||
spdlog::trace("Content-Length: {}", content_length);
|
|
||||||
}
|
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
|
||||||
spdlog::error("Failed to parse Content-Length: {}", e.what());
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
|
||||||
}
|
content_length = std::stoul(length.substr(start));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content_length == 0)
|
if (content_length == 0)
|
||||||
{
|
|
||||||
spdlog::debug("No Content-Length found in header");
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
|
||||||
|
|
||||||
// 读取内容体
|
|
||||||
std::string body(content_length, '\0');
|
std::string body(content_length, '\0');
|
||||||
std::cin.read(&body[0], content_length);
|
input_.read(body.data(), static_cast<std::streamsize>(content_length));
|
||||||
|
if (input_.gcount() != static_cast<std::streamsize>(content_length))
|
||||||
if (std::cin.gcount() != static_cast<std::streamsize>(content_length))
|
|
||||||
{
|
|
||||||
spdlog::error("Failed to read expected content length: {} bytes, got {} bytes", content_length, std::cin.gcount());
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::trace("Received message: {}", body);
|
spdlog::trace("Received message: {}", body);
|
||||||
return body;
|
return body;
|
||||||
@@ -216,11 +196,11 @@ namespace lsp::core
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& obj = any->Get<protocol::LSPObject>();
|
const auto& object = any->Get<protocol::LSPObject>();
|
||||||
const bool has_id = obj.find("id") != obj.end();
|
const bool has_id = object.contains("id");
|
||||||
const bool has_method = obj.find("method") != obj.end();
|
const bool has_method = object.contains("method");
|
||||||
const bool has_result = obj.find("result") != obj.end();
|
const bool has_result = object.contains("result");
|
||||||
const bool has_error = obj.find("error") != obj.end();
|
const bool has_error = object.contains("error");
|
||||||
|
|
||||||
if (has_method && has_id)
|
if (has_method && has_id)
|
||||||
{
|
{
|
||||||
@@ -233,10 +213,15 @@ namespace lsp::core
|
|||||||
|
|
||||||
if (has_method)
|
if (has_method)
|
||||||
{
|
{
|
||||||
if (auto notification = transform::Deserialize<protocol::NotificationMessage>(raw_message))
|
if (auto notification =
|
||||||
|
transform::Deserialize<protocol::NotificationMessage>(raw_message))
|
||||||
|
{
|
||||||
HandleNotification(*notification);
|
HandleNotification(*notification);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
spdlog::warn("Failed to parse notification message");
|
spdlog::warn("Failed to parse notification message");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,35 +239,84 @@ namespace lsp::core
|
|||||||
|
|
||||||
void LspServer::SendMessage(const std::string& message)
|
void LspServer::SendMessage(const std::string& message)
|
||||||
{
|
{
|
||||||
if (message.empty())
|
std::lock_guard lock(output_mutex_);
|
||||||
return;
|
output_ << "Content-Length: " << message.size() << "\r\n\r\n"
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(output_mutex_);
|
|
||||||
std::cout << "Content-Length: " << message.size() << "\r\n\r\n"
|
|
||||||
<< message << std::flush;
|
<< message << std::flush;
|
||||||
|
if (!output_)
|
||||||
|
{
|
||||||
|
fatal_io_error_ = true;
|
||||||
|
throw std::runtime_error("Failed to write LSP message");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LspServer::HandleRequest(const protocol::RequestMessage& request)
|
void LspServer::HandleRequest(const protocol::RequestMessage& request)
|
||||||
{
|
{
|
||||||
spdlog::debug("Handling request: {}", request.method);
|
spdlog::debug("Handling request: {}", request.method);
|
||||||
|
|
||||||
|
if (request.method == "initialize")
|
||||||
|
{
|
||||||
|
if (state_ != ServerState::kUninitialized)
|
||||||
|
{
|
||||||
|
SendError(request,
|
||||||
|
protocol::ErrorCodes::InvalidRequest,
|
||||||
|
"Server is already initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto response = dispatcher_.Dispatch(request);
|
||||||
|
SendMessage(response);
|
||||||
|
const auto parsed = transform::Deserialize<protocol::ResponseMessage>(response);
|
||||||
|
if (!parsed)
|
||||||
|
throw std::runtime_error("Initialize provider returned an invalid response");
|
||||||
|
if (!parsed->error)
|
||||||
|
state_ = ServerState::kRunning;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (request.method == "shutdown")
|
if (request.method == "shutdown")
|
||||||
{
|
{
|
||||||
auto response = dispatcher_.Dispatch(request);
|
if (state_ == ServerState::kUninitialized)
|
||||||
SendMessage(response);
|
|
||||||
is_shutting_down_ = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 未初始化时的特殊处理
|
|
||||||
if (!is_initialized_ && request.method != "initialize")
|
|
||||||
{
|
{
|
||||||
SendStateError(request);
|
SendError(request,
|
||||||
|
protocol::ErrorCodes::ServerNotInitialized,
|
||||||
|
"Server not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state_ != ServerState::kRunning)
|
||||||
|
{
|
||||||
|
SendError(request,
|
||||||
|
protocol::ErrorCodes::InvalidRequest,
|
||||||
|
"Shutdown already requested");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto response = dispatcher_.Dispatch(request);
|
async_executor_.WaitAll();
|
||||||
SendMessage(response);
|
manager_hub_.Shutdown();
|
||||||
|
|
||||||
|
protocol::ResponseMessage response;
|
||||||
|
response.id = request.id;
|
||||||
|
response.result = protocol::LSPAny(std::nullptr_t{});
|
||||||
|
SendMessage(transform::Serialize(response).value());
|
||||||
|
state_ = ServerState::kShutdownRequested;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ == ServerState::kUninitialized)
|
||||||
|
{
|
||||||
|
SendError(request,
|
||||||
|
protocol::ErrorCodes::ServerNotInitialized,
|
||||||
|
"Server not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state_ == ServerState::kShutdownRequested)
|
||||||
|
{
|
||||||
|
SendError(request,
|
||||||
|
protocol::ErrorCodes::InvalidRequest,
|
||||||
|
"Server is shutting down");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessage(dispatcher_.Dispatch(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LspServer::HandleNotification(const protocol::NotificationMessage& notification)
|
void LspServer::HandleNotification(const protocol::NotificationMessage& notification)
|
||||||
@@ -291,21 +325,17 @@ namespace lsp::core
|
|||||||
|
|
||||||
if (notification.method == "exit")
|
if (notification.method == "exit")
|
||||||
{
|
{
|
||||||
is_shutting_down_ = true;
|
const bool orderly = state_ == ServerState::kShutdownRequested;
|
||||||
|
state_ = ServerState::kExiting;
|
||||||
|
exit_code_ = orderly ? 0 : 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理取消请求
|
if (state_ != ServerState::kRunning)
|
||||||
if (notification.method == "$/cancelRequest")
|
|
||||||
{
|
{
|
||||||
HandleCancelRequest(notification);
|
spdlog::warn("Ignoring notification {} in server state {}",
|
||||||
return;
|
notification.method,
|
||||||
}
|
static_cast<int>(state_));
|
||||||
|
|
||||||
// 未初始化时只接受 initialized/exit
|
|
||||||
if (!is_initialized_ && notification.method != "initialized" && notification.method != "exit")
|
|
||||||
{
|
|
||||||
spdlog::warn("Server not initialized; ignoring notification: {}", notification.method);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,108 +345,37 @@ namespace lsp::core
|
|||||||
void LspServer::HandleResponse(const protocol::ResponseMessage& response)
|
void LspServer::HandleResponse(const protocol::ResponseMessage& response)
|
||||||
{
|
{
|
||||||
std::string id = "<no id>";
|
std::string id = "<no id>";
|
||||||
if (response.id.has_value())
|
if (response.id)
|
||||||
id = transform::debug::GetIdString(response.id.value());
|
id = transform::debug::GetIdString(*response.id);
|
||||||
spdlog::debug("Received response: {}", id);
|
spdlog::debug("Received response: {}", id);
|
||||||
// 当前服务器作为 client 的场景较少,这里暂时不处理
|
|
||||||
}
|
|
||||||
|
|
||||||
void LspServer::OnLifecycleEvent(provider::ServerLifecycleEvent event)
|
|
||||||
{
|
|
||||||
switch (event)
|
|
||||||
{
|
|
||||||
case provider::ServerLifecycleEvent::kInitialized:
|
|
||||||
is_initialized_ = true;
|
|
||||||
spdlog::info("Server initialized");
|
|
||||||
break;
|
|
||||||
case provider::ServerLifecycleEvent::kShutdown:
|
|
||||||
is_shutting_down_ = true;
|
|
||||||
spdlog::info("Server shutting down");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LspServer::RequiresSyncProcessing(const std::string& method) const
|
|
||||||
{
|
|
||||||
static const std::unordered_set<std::string> kSyncMethods = {
|
|
||||||
"initialize",
|
|
||||||
"shutdown",
|
|
||||||
"exit",
|
|
||||||
"$/setTrace",
|
|
||||||
};
|
|
||||||
return kSyncMethods.contains(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LspServer::CanProcessRequest(const std::string& method) const
|
|
||||||
{
|
|
||||||
if (is_shutting_down_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// 初始化前,只处理 initialize 请求
|
|
||||||
if (!is_initialized_ && method != "initialize")
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LspServer::HandleCancelRequest(const protocol::NotificationMessage& notification)
|
|
||||||
{
|
|
||||||
if (!notification.params.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
protocol::CancelParams params =
|
|
||||||
transform::FromLSPAny.template operator()<protocol::CancelParams>(notification.params.value());
|
|
||||||
|
|
||||||
const std::string id_string = std::visit([](const auto& value) -> std::string {
|
|
||||||
if constexpr (std::is_same_v<std::decay_t<decltype(value)>, int>)
|
|
||||||
return std::to_string(value);
|
|
||||||
else
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
params.id);
|
|
||||||
|
|
||||||
spdlog::debug("Cancel request received for id: {}", id_string);
|
|
||||||
// TODO: 实现请求取消逻辑
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LspServer::InitializeManagerHub()
|
void LspServer::InitializeManagerHub()
|
||||||
{
|
{
|
||||||
manager_hub_.Initialize();
|
manager_hub_.Initialize();
|
||||||
|
|
||||||
if (!interpreter_path_.empty())
|
if (interpreter_path_.empty())
|
||||||
{
|
return;
|
||||||
std::filesystem::path base = interpreter_path_;
|
|
||||||
std::filesystem::path funcext_path = base / "funcext";
|
const std::filesystem::path funcext_path =
|
||||||
if (std::filesystem::exists(funcext_path))
|
std::filesystem::path(interpreter_path_) / "funcext";
|
||||||
|
if (!std::filesystem::exists(funcext_path))
|
||||||
{
|
{
|
||||||
|
spdlog::warn("Interpreter funcext path does not exist: {}",
|
||||||
|
funcext_path.string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
manager::bootstrap::InitializeManagerHub(
|
manager::bootstrap::InitializeManagerHub(
|
||||||
manager_hub_,
|
manager_hub_, async_executor_, {funcext_path.string()});
|
||||||
async_executor_,
|
|
||||||
{ funcext_path.string() });
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
void LspServer::RegisterProviders(ProviderRegistrar registrar)
|
||||||
{
|
{
|
||||||
spdlog::warn("Interpreter funcext path does not exist: {}", funcext_path.string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LspServer::RegisterProviders()
|
|
||||||
{
|
|
||||||
dispatcher_.SetRequestScheduler(&async_executor_);
|
|
||||||
dispatcher_.SetManagerHub(&manager_hub_);
|
|
||||||
|
|
||||||
dispatcher_.RegisterLifecycleCallback([this](ServerLifecycleEvent event) {
|
|
||||||
OnLifecycleEvent(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
spdlog::info("Registering LSP providers...");
|
spdlog::info("Registering LSP providers...");
|
||||||
|
registrar(dispatcher_);
|
||||||
provider::RegisterAllProviders(dispatcher_);
|
spdlog::info("Registered {} LSP providers",
|
||||||
|
dispatcher_.GetAllSupportedMethods().size());
|
||||||
spdlog::info("Registered {} LSP providers", dispatcher_.GetAllSupportedMethods().size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LspServer::RegisterDiagnosticsPublisher()
|
void LspServer::RegisterDiagnosticsPublisher()
|
||||||
@@ -425,12 +384,14 @@ namespace lsp::core
|
|||||||
|
|
||||||
event_bus.Subscribe<manager::events::DocumentParsed>(
|
event_bus.Subscribe<manager::events::DocumentParsed>(
|
||||||
[this](const manager::events::DocumentParsed& event) {
|
[this](const manager::events::DocumentParsed& event) {
|
||||||
PublishDiagnostics(event.item.uri, event.item.version, event.tree, event.item.text);
|
PublishDiagnostics(
|
||||||
|
event.item.uri, event.item.version, event.tree, event.item.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
event_bus.Subscribe<manager::events::DocumentReparsed>(
|
event_bus.Subscribe<manager::events::DocumentReparsed>(
|
||||||
[this](const manager::events::DocumentReparsed& event) {
|
[this](const manager::events::DocumentReparsed& event) {
|
||||||
PublishDiagnostics(event.item.uri, event.item.version, event.tree, event.item.text);
|
PublishDiagnostics(
|
||||||
|
event.item.uri, event.item.version, event.tree, event.item.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
event_bus.Subscribe<manager::events::DocumentClosed>(
|
event_bus.Subscribe<manager::events::DocumentClosed>(
|
||||||
@@ -496,7 +457,6 @@ namespace lsp::core
|
|||||||
spdlog::warn("Failed to serialize diagnostics notification for {}", uri);
|
spdlog::warn("Failed to serialize diagnostics notification for {}", uri);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SendMessage(*json);
|
SendMessage(*json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,28 +477,13 @@ namespace lsp::core
|
|||||||
spdlog::warn("Failed to serialize diagnostics clear notification for {}", uri);
|
spdlog::warn("Failed to serialize diagnostics clear notification for {}", uri);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SendMessage(*json);
|
SendMessage(*json);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LspServer::SendError(const protocol::RequestMessage& request, protocol::ErrorCodes code, const std::string& message)
|
void LspServer::SendError(const protocol::RequestMessage& request,
|
||||||
|
protocol::ErrorCodes code,
|
||||||
|
std::string_view message)
|
||||||
{
|
{
|
||||||
protocol::ResponseMessage response;
|
SendMessage(BuildErrorResponseMessage(request, code, message));
|
||||||
response.id = request.id;
|
|
||||||
protocol::ResponseError error;
|
|
||||||
error.code = static_cast<protocol::integer>(code);
|
|
||||||
error.message = message;
|
|
||||||
response.error = error;
|
|
||||||
auto json = transform::Serialize(response);
|
|
||||||
if (json)
|
|
||||||
{
|
|
||||||
SendMessage(*json);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LspServer::SendStateError(const protocol::RequestMessage& request)
|
|
||||||
{
|
|
||||||
SendError(request, protocol::ErrorCodes::ServerNotInitialized, "Server not initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,13 +4,10 @@ export module lsp.provider.base.interface;
|
|||||||
|
|
||||||
import std;
|
import std;
|
||||||
|
|
||||||
// Thin bridge to core dispatcher interfaces and aliases for backward compatibility.
|
|
||||||
export import lsp.core.dispatcher;
|
export import lsp.core.dispatcher;
|
||||||
|
|
||||||
export namespace lsp::provider
|
export namespace lsp::provider
|
||||||
{
|
{
|
||||||
using ServerLifecycleEvent = lsp::core::ServerLifecycleEvent;
|
|
||||||
using LifecycleCallback = lsp::core::LifecycleCallback;
|
|
||||||
using ExecutionContext = lsp::core::ExecutionContext;
|
using ExecutionContext = lsp::core::ExecutionContext;
|
||||||
using IProvider = lsp::core::IProvider;
|
using IProvider = lsp::core::IProvider;
|
||||||
using IRequestProvider = lsp::core::IRequestProvider;
|
using IRequestProvider = lsp::core::IRequestProvider;
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
module;
|
|
||||||
|
|
||||||
|
|
||||||
export module lsp.provider.cancel_request.cancel_request;
|
|
||||||
import spdlog;
|
|
||||||
|
|
||||||
import std;
|
|
||||||
|
|
||||||
import lsp.protocol.types;
|
|
||||||
import lsp.codec.facade;
|
|
||||||
import lsp.provider.base.interface;
|
|
||||||
|
|
||||||
namespace transform = lsp::codec;
|
|
||||||
|
|
||||||
export namespace lsp::provider
|
|
||||||
{
|
|
||||||
class CancelRequest : public AutoRegisterProvider<CancelRequest, INotificationProvider>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static constexpr std::string_view kMethod = "$/cancelRequest";
|
|
||||||
static constexpr std::string_view kProviderName = "CancelRequest";
|
|
||||||
CancelRequest() = default;
|
|
||||||
void HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context) override;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace lsp::provider
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void CancelRequest::HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context)
|
|
||||||
{
|
|
||||||
spdlog::debug("CancelRequestProvider received {}", notification.method);
|
|
||||||
|
|
||||||
auto params = transform::FromLSPAny.template operator()<protocol::CancelParams>(notification.params.value());
|
|
||||||
std::string id_to_cancel = transform::debug::GetIdString(params.id);
|
|
||||||
spdlog::debug("Processing cancel request for ID: {}", id_to_cancel);
|
|
||||||
|
|
||||||
auto& scheduler = context.GetScheduler();
|
|
||||||
bool cancelled = scheduler.Cancel(id_to_cancel);
|
|
||||||
spdlog::debug("Cancel request {} {}", id_to_cancel, cancelled ? "succeeded" : "not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
module;
|
|
||||||
|
|
||||||
|
|
||||||
export module lsp.provider.exit.exit;
|
|
||||||
import spdlog;
|
|
||||||
|
|
||||||
import std;
|
|
||||||
|
|
||||||
import lsp.provider.base.interface;
|
|
||||||
import lsp.protocol.types;
|
|
||||||
|
|
||||||
export namespace lsp::provider
|
|
||||||
{
|
|
||||||
class Exit : public AutoRegisterProvider<Exit, INotificationProvider>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static constexpr std::string_view kMethod = "exit";
|
|
||||||
static constexpr std::string_view kProviderName = "Exit";
|
|
||||||
Exit() = default;
|
|
||||||
|
|
||||||
void HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context) override;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace lsp::provider
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Exit::HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context)
|
|
||||||
{
|
|
||||||
spdlog::debug("Exit notification {}", notification.method);
|
|
||||||
spdlog::info("Exit notification received");
|
|
||||||
|
|
||||||
// 触发生命周期事件
|
|
||||||
context.TriggerLifecycleEvent(ServerLifecycleEvent::kShuttingDown);
|
|
||||||
|
|
||||||
// 给一些时间完成清理
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
||||||
|
|
||||||
context.TriggerLifecycleEvent(ServerLifecycleEvent::kShutdown);
|
|
||||||
|
|
||||||
std::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -60,11 +60,7 @@ namespace lsp::provider
|
|||||||
response.result = transform::ToLSPAny(BuildInitializeResult());
|
response.result = transform::ToLSPAny(BuildInitializeResult());
|
||||||
std::optional<std::string> json = transform::Serialize(response);
|
std::optional<std::string> json = transform::Serialize(response);
|
||||||
if (!json.has_value())
|
if (!json.has_value())
|
||||||
{
|
throw std::runtime_error("Failed to serialize initialize response");
|
||||||
context.TriggerLifecycleEvent(ServerLifecycleEvent::kInitializeFailed);
|
|
||||||
return BuildErrorResponseMessage(request, protocol::ErrorCodes::InternalError, "Internal error");
|
|
||||||
}
|
|
||||||
context.TriggerLifecycleEvent(ServerLifecycleEvent::kInitialized);
|
|
||||||
return json.value();
|
return json.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import lsp.provider.base.registry;
|
|||||||
import lsp.provider.completion_item.resolve;
|
import lsp.provider.completion_item.resolve;
|
||||||
import lsp.provider.initialize.initialize;
|
import lsp.provider.initialize.initialize;
|
||||||
import lsp.provider.initialized.initialized;
|
import lsp.provider.initialized.initialized;
|
||||||
import lsp.provider.shutdown.shutdown;
|
|
||||||
import lsp.provider.text_document.definition;
|
import lsp.provider.text_document.definition;
|
||||||
import lsp.provider.text_document.did_change;
|
import lsp.provider.text_document.did_change;
|
||||||
import lsp.provider.text_document.did_close;
|
import lsp.provider.text_document.did_close;
|
||||||
@@ -20,13 +19,11 @@ import lsp.provider.trace.set_trace;
|
|||||||
// Uncomment when re-enabling additional capabilities.
|
// Uncomment when re-enabling additional capabilities.
|
||||||
// import lsp.provider.call_hierarchy.incoming_calls;
|
// import lsp.provider.call_hierarchy.incoming_calls;
|
||||||
// import lsp.provider.call_hierarchy.outgoing_calls;
|
// import lsp.provider.call_hierarchy.outgoing_calls;
|
||||||
// import lsp.provider.cancel_request.cancel_request;
|
|
||||||
// import lsp.provider.client.register_capability;
|
// import lsp.provider.client.register_capability;
|
||||||
// import lsp.provider.client.unregister_capability;
|
// import lsp.provider.client.unregister_capability;
|
||||||
// import lsp.provider.code_action.resolve;
|
// import lsp.provider.code_action.resolve;
|
||||||
// import lsp.provider.code_lens.resolve;
|
// import lsp.provider.code_lens.resolve;
|
||||||
// import lsp.provider.document_link.resolve;
|
// import lsp.provider.document_link.resolve;
|
||||||
// import lsp.provider.exit.exit;
|
|
||||||
// import lsp.provider.inlay_hint.resolve;
|
// import lsp.provider.inlay_hint.resolve;
|
||||||
// import lsp.provider.telemetry.event;
|
// import lsp.provider.telemetry.event;
|
||||||
// import lsp.provider.text_document.code_action;
|
// import lsp.provider.text_document.code_action;
|
||||||
@@ -94,7 +91,6 @@ export namespace lsp::provider
|
|||||||
completion_item::Resolve,
|
completion_item::Resolve,
|
||||||
Initialize,
|
Initialize,
|
||||||
Initialized,
|
Initialized,
|
||||||
Shutdown,
|
|
||||||
text_document::Completion,
|
text_document::Completion,
|
||||||
text_document::Definition,
|
text_document::Definition,
|
||||||
text_document::DidChange,
|
text_document::DidChange,
|
||||||
@@ -107,18 +103,15 @@ export namespace lsp::provider
|
|||||||
// using AllProviders = ProviderRegistry<
|
// using AllProviders = ProviderRegistry<
|
||||||
// call_hierarchy::IncomingCalls,
|
// call_hierarchy::IncomingCalls,
|
||||||
// call_hierarchy::OutgoingCalls,
|
// call_hierarchy::OutgoingCalls,
|
||||||
// CancelRequest,
|
|
||||||
// client::RegisterCapability,
|
// client::RegisterCapability,
|
||||||
// client::UnregisterCapability,
|
// client::UnregisterCapability,
|
||||||
// code_action::Resolve,
|
// code_action::Resolve,
|
||||||
// code_lens::Resolve,
|
// code_lens::Resolve,
|
||||||
// completion_item::Resolve,
|
// completion_item::Resolve,
|
||||||
// document_link::Resolve,
|
// document_link::Resolve,
|
||||||
// Exit,
|
|
||||||
// Initialize,
|
// Initialize,
|
||||||
// Initialized,
|
// Initialized,
|
||||||
// inlay_hint::Resolve,
|
// inlay_hint::Resolve,
|
||||||
// Shutdown,
|
|
||||||
// telemetry::Event,
|
// telemetry::Event,
|
||||||
// text_document::CodeAction,
|
// text_document::CodeAction,
|
||||||
// text_document::CodeLens,
|
// text_document::CodeLens,
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
module;
|
|
||||||
|
|
||||||
|
|
||||||
export module lsp.provider.shutdown.shutdown;
|
|
||||||
import spdlog;
|
|
||||||
|
|
||||||
import std;
|
|
||||||
|
|
||||||
import lsp.protocol;
|
|
||||||
import lsp.codec.facade;
|
|
||||||
import lsp.provider.base.interface;
|
|
||||||
|
|
||||||
namespace transform = lsp::codec;
|
|
||||||
|
|
||||||
export namespace lsp::provider
|
|
||||||
{
|
|
||||||
class Shutdown : public AutoRegisterProvider<Shutdown, IRequestProvider>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static constexpr std::string_view kMethod = "shutdown";
|
|
||||||
static constexpr std::string_view kProviderName = "Shutdown";
|
|
||||||
Shutdown() = default;
|
|
||||||
|
|
||||||
std::string ProvideResponse(const protocol::RequestMessage& request, ExecutionContext& context) override;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace lsp::provider
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
std::string Shutdown::ProvideResponse(const protocol::RequestMessage& request, ExecutionContext& context)
|
|
||||||
{
|
|
||||||
spdlog::debug("ShutdownProvider: Providing response for method {}", request.method);
|
|
||||||
|
|
||||||
// 触发关闭事件
|
|
||||||
context.TriggerLifecycleEvent(ServerLifecycleEvent::kShuttingDown);
|
|
||||||
|
|
||||||
context.GetManagerHub().Shutdown();
|
|
||||||
|
|
||||||
// 构建响应 - shutdown 返回 null
|
|
||||||
protocol::ResponseMessage response;
|
|
||||||
response.id = request.id;
|
|
||||||
auto json = transform::Serialize(response);
|
|
||||||
if (!json.has_value())
|
|
||||||
return BuildErrorResponseMessage(request, protocol::ErrorCodes::InternalError, "Failed to serialize response");
|
|
||||||
spdlog::info("Shutdown request processed successfully");
|
|
||||||
return json.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -56,6 +56,10 @@ if(BUILD_TESTS)
|
|||||||
COMMAND ${PYTHON3_EXECUTABLE}
|
COMMAND ${PYTHON3_EXECUTABLE}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/test_cli_startup.py
|
${CMAKE_CURRENT_LIST_DIR}/test_cli_startup.py
|
||||||
--server $<TARGET_FILE:tsl-server>)
|
--server $<TARGET_FILE:tsl-server>)
|
||||||
|
add_test(NAME test_core_server
|
||||||
|
COMMAND ${PYTHON3_EXECUTABLE}
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/test_core_server.py
|
||||||
|
--server $<TARGET_FILE:test_provider>)
|
||||||
else()
|
else()
|
||||||
message(WARNING "python3 not found; skipping test_lsp_json and test_cli_startup registration")
|
message(WARNING "python3 not found; skipping test_lsp_json and test_cli_startup registration")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import select
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def frame(message: dict) -> bytes:
|
||||||
|
body = json.dumps(message, separators=(",", ":")).encode("utf-8")
|
||||||
|
return f"Content-Length: {len(body)}\r\n\r\n".encode("ascii") + body
|
||||||
|
|
||||||
|
|
||||||
|
def read_messages(data: bytes) -> list[dict]:
|
||||||
|
messages = []
|
||||||
|
offset = 0
|
||||||
|
while offset < len(data):
|
||||||
|
header_end = data.find(b"\r\n\r\n", offset)
|
||||||
|
if header_end < 0:
|
||||||
|
raise RuntimeError("Incomplete LSP response header")
|
||||||
|
|
||||||
|
header = data[offset:header_end].decode("ascii")
|
||||||
|
fields = dict(line.split(": ", 1) for line in header.split("\r\n"))
|
||||||
|
length = int(fields["Content-Length"])
|
||||||
|
body_start = header_end + 4
|
||||||
|
body_end = body_start + length
|
||||||
|
if body_end > len(data):
|
||||||
|
raise RuntimeError("Incomplete LSP response body")
|
||||||
|
|
||||||
|
messages.append(json.loads(data[body_start:body_end].decode("utf-8")))
|
||||||
|
offset = body_end
|
||||||
|
return messages
|
||||||
|
|
||||||
|
|
||||||
|
def response_by_id(data: bytes, request_id: int | str) -> dict:
|
||||||
|
return next(message for message in read_messages(data)
|
||||||
|
if message.get("id") == request_id)
|
||||||
|
|
||||||
|
|
||||||
|
def run_raw(server: Path, payload: bytes) -> subprocess.CompletedProcess[bytes]:
|
||||||
|
return subprocess.run(
|
||||||
|
[str(server), "--core-server-fixture"],
|
||||||
|
input=payload,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
timeout=5,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_batch(server: Path, messages: list[dict]) -> subprocess.CompletedProcess[bytes]:
|
||||||
|
return run_raw(server, b"".join(frame(message) for message in messages))
|
||||||
|
|
||||||
|
|
||||||
|
class LspClient:
|
||||||
|
def __init__(self, server: Path) -> None:
|
||||||
|
self.proc = subprocess.Popen(
|
||||||
|
[str(server), "--core-server-fixture"],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
self.buffer = b""
|
||||||
|
|
||||||
|
def send(self, message: dict) -> None:
|
||||||
|
if self.proc.stdin is None:
|
||||||
|
raise RuntimeError("Fixture stdin is unavailable")
|
||||||
|
self.proc.stdin.write(frame(message))
|
||||||
|
self.proc.stdin.flush()
|
||||||
|
|
||||||
|
def read(self, timeout: float = 1.0) -> dict:
|
||||||
|
if self.proc.stdout is None:
|
||||||
|
raise RuntimeError("Fixture stdout is unavailable")
|
||||||
|
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
while True:
|
||||||
|
header_end = self.buffer.find(b"\r\n\r\n")
|
||||||
|
if header_end >= 0:
|
||||||
|
header = self.buffer[:header_end].decode("ascii")
|
||||||
|
fields = dict(line.split(": ", 1)
|
||||||
|
for line in header.split("\r\n"))
|
||||||
|
length = int(fields["Content-Length"])
|
||||||
|
body_start = header_end + 4
|
||||||
|
body_end = body_start + length
|
||||||
|
if len(self.buffer) >= body_end:
|
||||||
|
body = self.buffer[body_start:body_end]
|
||||||
|
self.buffer = self.buffer[body_end:]
|
||||||
|
return json.loads(body.decode("utf-8"))
|
||||||
|
|
||||||
|
remaining = deadline - time.monotonic()
|
||||||
|
if remaining <= 0:
|
||||||
|
raise TimeoutError("Timed out waiting for LSP response")
|
||||||
|
|
||||||
|
ready, _, _ = select.select(
|
||||||
|
[self.proc.stdout.fileno()], [], [], remaining)
|
||||||
|
if not ready:
|
||||||
|
continue
|
||||||
|
|
||||||
|
chunk = os.read(self.proc.stdout.fileno(), 4096)
|
||||||
|
if not chunk:
|
||||||
|
raise RuntimeError("Fixture closed stdout before a response")
|
||||||
|
self.buffer += chunk
|
||||||
|
|
||||||
|
def close_input(self) -> int:
|
||||||
|
if self.proc.stdin is not None:
|
||||||
|
self.proc.stdin.close()
|
||||||
|
return self.proc.wait(timeout=5)
|
||||||
|
|
||||||
|
def kill(self) -> None:
|
||||||
|
if self.proc.poll() is None:
|
||||||
|
self.proc.kill()
|
||||||
|
self.proc.wait(timeout=5)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_lifecycle(server: Path) -> None:
|
||||||
|
before_init = run_batch(server, [
|
||||||
|
{"jsonrpc": "2.0", "id": 1, "method": "shutdown"},
|
||||||
|
{"jsonrpc": "2.0", "method": "exit"},
|
||||||
|
])
|
||||||
|
if before_init.returncode != 1:
|
||||||
|
raise RuntimeError("shutdown before initialize should exit with code 1")
|
||||||
|
if response_by_id(before_init.stdout, 1)["error"]["code"] != -32002:
|
||||||
|
raise RuntimeError("shutdown before initialize should return ServerNotInitialized")
|
||||||
|
|
||||||
|
repeated = run_batch(server, [
|
||||||
|
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}},
|
||||||
|
{"jsonrpc": "2.0", "id": 2, "method": "initialize", "params": {}},
|
||||||
|
{"jsonrpc": "2.0", "id": 3, "method": "shutdown"},
|
||||||
|
{"jsonrpc": "2.0", "method": "exit"},
|
||||||
|
])
|
||||||
|
if repeated.returncode != 0:
|
||||||
|
raise RuntimeError("shutdown followed by exit should return code 0")
|
||||||
|
if response_by_id(repeated.stdout, 2)["error"]["code"] != -32600:
|
||||||
|
raise RuntimeError("repeated initialize should return InvalidRequest")
|
||||||
|
if response_by_id(repeated.stdout, 3).get("result", "missing") is not None:
|
||||||
|
raise RuntimeError("shutdown should return a null result")
|
||||||
|
|
||||||
|
direct_exit = run_batch(
|
||||||
|
server, [{"jsonrpc": "2.0", "method": "exit"}])
|
||||||
|
if direct_exit.returncode != 1:
|
||||||
|
raise RuntimeError("exit before shutdown should return code 1")
|
||||||
|
|
||||||
|
client = LspClient(server)
|
||||||
|
try:
|
||||||
|
client.send({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 11,
|
||||||
|
"method": "initialize",
|
||||||
|
"params": {},
|
||||||
|
})
|
||||||
|
if client.read().get("id") != 11:
|
||||||
|
raise RuntimeError("missing initialize response")
|
||||||
|
|
||||||
|
client.send({"jsonrpc": "2.0", "id": 12, "method": "shutdown"})
|
||||||
|
if client.read().get("result", "missing") is not None:
|
||||||
|
raise RuntimeError("shutdown should return a null result")
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
||||||
|
if client.proc.poll() is not None:
|
||||||
|
raise RuntimeError("server exited before receiving exit notification")
|
||||||
|
|
||||||
|
client.send({"jsonrpc": "2.0", "method": "exit"})
|
||||||
|
if client.close_input() != 0:
|
||||||
|
raise RuntimeError("orderly lifecycle should return code 0")
|
||||||
|
finally:
|
||||||
|
client.kill()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--server", type=Path, required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
assert_lifecycle(args.server)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -20,6 +20,7 @@ set(SOURCES
|
|||||||
main.cc
|
main.cc
|
||||||
test_main.cppm
|
test_main.cppm
|
||||||
../test_lsp_any/test_framework.cppm
|
../test_lsp_any/test_framework.cppm
|
||||||
|
core_server_fixture.cppm
|
||||||
fixtures.cppm
|
fixtures.cppm
|
||||||
completion_test.cppm
|
completion_test.cppm
|
||||||
json_flow_test.cppm
|
json_flow_test.cppm
|
||||||
@@ -49,6 +50,7 @@ target_sources(
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
||||||
FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_main.cppm
|
FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_main.cppm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../test_lsp_any/test_framework.cppm
|
${CMAKE_CURRENT_SOURCE_DIR}/../test_lsp_any/test_framework.cppm
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/core_server_fixture.cppm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/fixtures.cppm
|
${CMAKE_CURRENT_SOURCE_DIR}/fixtures.cppm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/completion_test.cppm
|
${CMAKE_CURRENT_SOURCE_DIR}/completion_test.cppm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/json_flow_test.cppm
|
${CMAKE_CURRENT_SOURCE_DIR}/json_flow_test.cppm
|
||||||
@@ -62,10 +64,14 @@ target_sources(
|
|||||||
../../src/bridge/spdlog.cppm
|
../../src/bridge/spdlog.cppm
|
||||||
../../src/bridge/taskflow.cppm
|
../../src/bridge/taskflow.cppm
|
||||||
../../src/bridge/tree_sitter.cppm
|
../../src/bridge/tree_sitter.cppm
|
||||||
|
../../src/bridge/win32_stdio.cppm
|
||||||
|
../../src/utils/args_parser.cppm
|
||||||
../../src/utils/string.cppm
|
../../src/utils/string.cppm
|
||||||
../../src/utils/text_coordinates.cppm
|
../../src/utils/text_coordinates.cppm
|
||||||
../../src/core/dispatcher.cppm
|
../../src/core/dispatcher.cppm
|
||||||
|
../../src/core/server.cppm
|
||||||
../../src/scheduler/async_executor.cppm
|
../../src/scheduler/async_executor.cppm
|
||||||
|
../../src/manager/bootstrap.cppm
|
||||||
../../src/manager/event_bus.cppm
|
../../src/manager/event_bus.cppm
|
||||||
../../src/manager/events.cppm
|
../../src/manager/events.cppm
|
||||||
../../src/manager/detail/text_document.cppm
|
../../src/manager/detail/text_document.cppm
|
||||||
@@ -140,9 +146,6 @@ target_sources(
|
|||||||
../../src/provider/completion_item/resolve.cppm
|
../../src/provider/completion_item/resolve.cppm
|
||||||
../../src/provider/initialize/initialize.cppm
|
../../src/provider/initialize/initialize.cppm
|
||||||
../../src/provider/initialized/initialized.cppm
|
../../src/provider/initialized/initialized.cppm
|
||||||
../../src/provider/shutdown/shutdown.cppm
|
|
||||||
../../src/provider/exit/exit.cppm
|
|
||||||
../../src/provider/cancel_request/cancel_request.cppm
|
|
||||||
../../src/provider/trace/set_trace.cppm
|
../../src/provider/trace/set_trace.cppm
|
||||||
../../src/provider/client/register_capability.cppm
|
../../src/provider/client/register_capability.cppm
|
||||||
../../src/provider/client/unregister_capability.cppm
|
../../src/provider/client/unregister_capability.cppm
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ namespace lsp::test::provider
|
|||||||
core::ExecutionContext context;
|
core::ExecutionContext context;
|
||||||
|
|
||||||
ProviderEnv()
|
ProviderEnv()
|
||||||
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
|
: context(scheduler, hub)
|
||||||
{
|
{
|
||||||
hub.Initialize();
|
hub.Initialize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
export module lsp.test.provider.core_server_fixture;
|
||||||
|
|
||||||
|
import spdlog;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
import lsp.codec.facade;
|
||||||
|
import lsp.core.server;
|
||||||
|
import lsp.protocol;
|
||||||
|
import lsp.provider.base.interface;
|
||||||
|
|
||||||
|
export namespace lsp::test::provider
|
||||||
|
{
|
||||||
|
int RunCoreServerFixture();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace lsp::test::provider
|
||||||
|
{
|
||||||
|
class FixtureInitialize final : public core::IRequestProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string GetMethod() const override
|
||||||
|
{
|
||||||
|
return "initialize";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetProviderName() const override
|
||||||
|
{
|
||||||
|
return "FixtureInitialize";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ProvideResponse(const protocol::RequestMessage& request,
|
||||||
|
core::ExecutionContext&) override
|
||||||
|
{
|
||||||
|
protocol::ResponseMessage response;
|
||||||
|
response.id = request.id;
|
||||||
|
response.result = protocol::LSPAny(protocol::LSPObject{});
|
||||||
|
return codec::Serialize(response).value();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int RunCoreServerFixture()
|
||||||
|
{
|
||||||
|
spdlog::set_level(spdlog::level::off);
|
||||||
|
core::LspServer server(
|
||||||
|
std::cin,
|
||||||
|
std::cout,
|
||||||
|
[](core::RequestDispatcher& dispatcher) {
|
||||||
|
dispatcher.RegisterRequestProvider(std::make_shared<FixtureInitialize>());
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
"");
|
||||||
|
return server.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ namespace lsp::test::provider
|
|||||||
core::ExecutionContext context;
|
core::ExecutionContext context;
|
||||||
|
|
||||||
ProviderEnv()
|
ProviderEnv()
|
||||||
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
|
: context(scheduler, hub)
|
||||||
{
|
{
|
||||||
hub.Initialize();
|
hub.Initialize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace lsp::test::provider
|
|||||||
core::ExecutionContext context;
|
core::ExecutionContext context;
|
||||||
|
|
||||||
ProviderEnv()
|
ProviderEnv()
|
||||||
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
|
: context(scheduler, hub)
|
||||||
{
|
{
|
||||||
hub.Initialize();
|
hub.Initialize();
|
||||||
}
|
}
|
||||||
@@ -207,4 +207,3 @@ namespace lsp::test::provider
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace lsp::test::provider
|
|||||||
core::ExecutionContext context;
|
core::ExecutionContext context;
|
||||||
|
|
||||||
ProviderEnv()
|
ProviderEnv()
|
||||||
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
|
: context(scheduler, hub)
|
||||||
{
|
{
|
||||||
hub.Initialize();
|
hub.Initialize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,13 +51,11 @@ namespace lsp::test::provider
|
|||||||
{
|
{
|
||||||
scheduler::async_executor::AsyncExecutor scheduler{ 1 };
|
scheduler::async_executor::AsyncExecutor scheduler{ 1 };
|
||||||
manager::ManagerHub hub{};
|
manager::ManagerHub hub{};
|
||||||
core::RequestDispatcher dispatcher{};
|
core::RequestDispatcher dispatcher{ scheduler, hub };
|
||||||
|
|
||||||
ProviderEnv()
|
ProviderEnv()
|
||||||
{
|
{
|
||||||
hub.Initialize();
|
hub.Initialize();
|
||||||
dispatcher.SetRequestScheduler(&scheduler);
|
|
||||||
dispatcher.SetManagerHub(&hub);
|
|
||||||
provider::RegisterAllProviders(dispatcher);
|
provider::RegisterAllProviders(dispatcher);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,10 +77,7 @@ import lsp.provider.workspace.will_delete_files;
|
|||||||
import lsp.provider.workspace.will_rename_files;
|
import lsp.provider.workspace.will_rename_files;
|
||||||
import lsp.provider.workspace_symbol.resolve;
|
import lsp.provider.workspace_symbol.resolve;
|
||||||
import lsp.provider.text_document.publish_diagnostics;
|
import lsp.provider.text_document.publish_diagnostics;
|
||||||
import lsp.provider.shutdown.shutdown;
|
|
||||||
import lsp.provider.cancel_request.cancel_request;
|
|
||||||
import lsp.provider.trace.set_trace;
|
import lsp.provider.trace.set_trace;
|
||||||
import lsp.provider.exit.exit;
|
|
||||||
import lsp.core.dispatcher;
|
import lsp.core.dispatcher;
|
||||||
import lsp.manager.manager_hub;
|
import lsp.manager.manager_hub;
|
||||||
import lsp.manager.symbol;
|
import lsp.manager.symbol;
|
||||||
@@ -160,13 +157,8 @@ export namespace lsp::test::provider
|
|||||||
static TestResult TestExecuteCommandProvider();
|
static TestResult TestExecuteCommandProvider();
|
||||||
static TestResult TestWillFileOperationsProviders();
|
static TestResult TestWillFileOperationsProviders();
|
||||||
static TestResult TestWorkspaceSymbolResolveProvider();
|
static TestResult TestWorkspaceSymbolResolveProvider();
|
||||||
static TestResult TestShutdownProvider();
|
|
||||||
static TestResult TestCancelRequestProvider();
|
|
||||||
static TestResult TestSetTraceProvider();
|
static TestResult TestSetTraceProvider();
|
||||||
static TestResult TestExitProvider();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int RunExitProviderChild();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace lsp::test::provider
|
namespace lsp::test::provider
|
||||||
@@ -175,13 +167,12 @@ namespace lsp::test::provider
|
|||||||
{
|
{
|
||||||
struct ProviderEnv
|
struct ProviderEnv
|
||||||
{
|
{
|
||||||
std::vector<core::ServerLifecycleEvent> events;
|
|
||||||
scheduler::async_executor::AsyncExecutor scheduler{ 1 };
|
scheduler::async_executor::AsyncExecutor scheduler{ 1 };
|
||||||
manager::ManagerHub hub{};
|
manager::ManagerHub hub{};
|
||||||
core::ExecutionContext context;
|
core::ExecutionContext context;
|
||||||
|
|
||||||
ProviderEnv()
|
ProviderEnv()
|
||||||
: context([this](core::ServerLifecycleEvent event) { events.push_back(event); }, scheduler, hub)
|
: context(scheduler, hub)
|
||||||
{
|
{
|
||||||
hub.Initialize();
|
hub.Initialize();
|
||||||
}
|
}
|
||||||
@@ -365,10 +356,7 @@ namespace lsp::test::provider
|
|||||||
runner.addTest("workspace executeCommand provider", TestExecuteCommandProvider);
|
runner.addTest("workspace executeCommand provider", TestExecuteCommandProvider);
|
||||||
runner.addTest("workspace will file operations providers", TestWillFileOperationsProviders);
|
runner.addTest("workspace will file operations providers", TestWillFileOperationsProviders);
|
||||||
runner.addTest("workspaceSymbol/resolve provider", TestWorkspaceSymbolResolveProvider);
|
runner.addTest("workspaceSymbol/resolve provider", TestWorkspaceSymbolResolveProvider);
|
||||||
runner.addTest("shutdown provider", TestShutdownProvider);
|
|
||||||
runner.addTest("cancel request provider", TestCancelRequestProvider);
|
|
||||||
runner.addTest("setTrace provider", TestSetTraceProvider);
|
runner.addTest("setTrace provider", TestSetTraceProvider);
|
||||||
runner.addTest("exit provider", TestExitProvider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResult ProviderMiscTests::TestInitializeProvider()
|
TestResult ProviderMiscTests::TestInitializeProvider()
|
||||||
@@ -434,8 +422,6 @@ namespace lsp::test::provider
|
|||||||
});
|
});
|
||||||
assertTrue(found_workspace, "Workspace symbols should be indexed");
|
assertTrue(found_workspace, "Workspace symbols should be indexed");
|
||||||
|
|
||||||
assertTrue(!env.events.empty(), "Initialize should emit lifecycle event");
|
|
||||||
assertTrue(env.events.back() == core::ServerLifecycleEvent::kInitialized, "Initialize should emit initialized");
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2233,7 +2219,6 @@ namespace lsp::test::provider
|
|||||||
const auto& stored_obj = stored.Get<protocol::LSPObject>();
|
const auto& stored_obj = stored.Get<protocol::LSPObject>();
|
||||||
assertTrue(stored_obj.contains("tsl"), "stored settings should include tsl section");
|
assertTrue(stored_obj.contains("tsl"), "stored settings should include tsl section");
|
||||||
|
|
||||||
assertTrue(env.events.empty(), "didChangeConfiguration should not trigger lifecycle events");
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2761,7 +2746,6 @@ namespace lsp::test::provider
|
|||||||
provider.HandleNotification(notification, env.context);
|
provider.HandleNotification(notification, env.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(env.events.empty(), "window message notifications should not trigger lifecycle events");
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2781,7 +2765,6 @@ namespace lsp::test::provider
|
|||||||
::lsp::provider::telemetry::Event provider;
|
::lsp::provider::telemetry::Event provider;
|
||||||
provider.HandleNotification(notification, env.context);
|
provider.HandleNotification(notification, env.context);
|
||||||
|
|
||||||
assertTrue(env.events.empty(), "telemetry/event should not trigger lifecycle events");
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2816,7 +2799,6 @@ namespace lsp::test::provider
|
|||||||
::lsp::provider::text_document::PublishDiagnostics provider;
|
::lsp::provider::text_document::PublishDiagnostics provider;
|
||||||
provider.HandleNotification(notification, env.context);
|
provider.HandleNotification(notification, env.context);
|
||||||
|
|
||||||
assertTrue(env.events.empty(), "publishDiagnostics should not trigger lifecycle events");
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3396,63 +3378,6 @@ namespace lsp::test::provider
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResult ProviderMiscTests::TestShutdownProvider()
|
|
||||||
{
|
|
||||||
TestResult result{ "", true, "ok" };
|
|
||||||
ProviderEnv env;
|
|
||||||
|
|
||||||
auto path = FixturePath("rename_case.tsl");
|
|
||||||
auto content = ReadTextFile(path);
|
|
||||||
auto uri = ToUri(path);
|
|
||||||
OpenDocument(env.hub, uri, content, 1);
|
|
||||||
|
|
||||||
protocol::RequestMessage request;
|
|
||||||
request.id = "shutdown";
|
|
||||||
request.method = "shutdown";
|
|
||||||
|
|
||||||
::lsp::provider::Shutdown provider;
|
|
||||||
auto json = provider.ProvideResponse(request, env.context);
|
|
||||||
auto response = ParseResponse(json);
|
|
||||||
assertTrue(!response.error.has_value(), "Shutdown should not return error");
|
|
||||||
assertTrue(env.events.size() >= 1, "Shutdown should emit lifecycle event");
|
|
||||||
assertTrue(env.events.back() == core::ServerLifecycleEvent::kShuttingDown, "Shutdown should emit shutting down");
|
|
||||||
assertFalse(env.hub.documents().GetContent(uri).has_value(), "Shutdown should clear documents");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TestResult ProviderMiscTests::TestCancelRequestProvider()
|
|
||||||
{
|
|
||||||
TestResult result{ "", true, "ok" };
|
|
||||||
ProviderEnv env;
|
|
||||||
|
|
||||||
std::atomic<bool> started{ false };
|
|
||||||
env.scheduler.Submit("cancel_me", [&started](std::stop_token) -> std::optional<std::string> {
|
|
||||||
started.store(true);
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
|
||||||
return std::string("done");
|
|
||||||
});
|
|
||||||
|
|
||||||
while (!started.load())
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol::CancelParams params;
|
|
||||||
params.id = std::string("cancel_me");
|
|
||||||
protocol::NotificationMessage notification;
|
|
||||||
notification.method = "$/cancelRequest";
|
|
||||||
notification.params = codec::ToLSPAny(params);
|
|
||||||
|
|
||||||
::lsp::provider::CancelRequest provider;
|
|
||||||
provider.HandleNotification(notification, env.context);
|
|
||||||
env.scheduler.WaitAll();
|
|
||||||
|
|
||||||
auto stats = env.scheduler.GetStatistics();
|
|
||||||
assertEqual(std::size_t(1), static_cast<std::size_t>(stats.cancelled),
|
|
||||||
"CancelRequest should mark task cancelled");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TestResult ProviderMiscTests::TestSetTraceProvider()
|
TestResult ProviderMiscTests::TestSetTraceProvider()
|
||||||
{
|
{
|
||||||
TestResult result{ "", true, "ok" };
|
TestResult result{ "", true, "ok" };
|
||||||
@@ -3484,24 +3409,4 @@ namespace lsp::test::provider
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResult ProviderMiscTests::TestExitProvider()
|
|
||||||
{
|
|
||||||
TestResult result{ "", true, "ok" };
|
|
||||||
auto exe = ExecutablePath();
|
|
||||||
assertTrue(!exe.empty(), "ExecutablePath should be set");
|
|
||||||
std::string command = "\"" + exe + "\" --exit-provider";
|
|
||||||
int code = std::system(command.c_str());
|
|
||||||
assertEqual(0, code, "Exit should return code 0");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int RunExitProviderChild()
|
|
||||||
{
|
|
||||||
ProviderEnv env;
|
|
||||||
::lsp::provider::Exit provider;
|
|
||||||
protocol::NotificationMessage notification;
|
|
||||||
notification.method = "exit";
|
|
||||||
provider.HandleNotification(notification, env.context);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,16 +12,13 @@ import lsp.manager.manager_hub;
|
|||||||
import lsp.scheduler.async_executor;
|
import lsp.scheduler.async_executor;
|
||||||
import lsp.test.provider.fixtures;
|
import lsp.test.provider.fixtures;
|
||||||
|
|
||||||
import lsp.provider.cancel_request.cancel_request;
|
|
||||||
import lsp.provider.code_action.resolve;
|
import lsp.provider.code_action.resolve;
|
||||||
import lsp.provider.code_lens.resolve;
|
import lsp.provider.code_lens.resolve;
|
||||||
import lsp.provider.completion_item.resolve;
|
import lsp.provider.completion_item.resolve;
|
||||||
import lsp.provider.document_link.resolve;
|
import lsp.provider.document_link.resolve;
|
||||||
import lsp.provider.exit.exit;
|
|
||||||
import lsp.provider.initialize.initialize;
|
import lsp.provider.initialize.initialize;
|
||||||
import lsp.provider.initialized.initialized;
|
import lsp.provider.initialized.initialized;
|
||||||
import lsp.provider.inlay_hint.resolve;
|
import lsp.provider.inlay_hint.resolve;
|
||||||
import lsp.provider.shutdown.shutdown;
|
|
||||||
import lsp.provider.trace.set_trace;
|
import lsp.provider.trace.set_trace;
|
||||||
import lsp.provider.call_hierarchy.incoming_calls;
|
import lsp.provider.call_hierarchy.incoming_calls;
|
||||||
import lsp.provider.call_hierarchy.outgoing_calls;
|
import lsp.provider.call_hierarchy.outgoing_calls;
|
||||||
@@ -118,7 +115,7 @@ namespace lsp::test::provider
|
|||||||
core::ExecutionContext context;
|
core::ExecutionContext context;
|
||||||
|
|
||||||
ProviderEnv()
|
ProviderEnv()
|
||||||
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
|
: context(scheduler, hub)
|
||||||
{
|
{
|
||||||
hub.Initialize();
|
hub.Initialize();
|
||||||
}
|
}
|
||||||
@@ -174,7 +171,6 @@ namespace lsp::test::provider
|
|||||||
TestResult result{ "", true, "ok" };
|
TestResult result{ "", true, "ok" };
|
||||||
|
|
||||||
CheckProviderMetadata<provider::Initialize>("initialize", "Initialize");
|
CheckProviderMetadata<provider::Initialize>("initialize", "Initialize");
|
||||||
CheckProviderMetadata<provider::Shutdown>("shutdown", "Shutdown");
|
|
||||||
CheckProviderMetadata<provider::completion_item::Resolve>("completionItem/resolve", "CompletionItemResolve");
|
CheckProviderMetadata<provider::completion_item::Resolve>("completionItem/resolve", "CompletionItemResolve");
|
||||||
CheckProviderMetadata<provider::text_document::Completion>("textDocument/completion", "TextDocumentCompletion");
|
CheckProviderMetadata<provider::text_document::Completion>("textDocument/completion", "TextDocumentCompletion");
|
||||||
CheckProviderMetadata<provider::text_document::Definition>("textDocument/definition", "TextDocumentDefinition");
|
CheckProviderMetadata<provider::text_document::Definition>("textDocument/definition", "TextDocumentDefinition");
|
||||||
@@ -273,8 +269,6 @@ namespace lsp::test::provider
|
|||||||
CheckProviderMetadata<provider::workspace::DidDeleteFiles>("workspace/didDeleteFiles", "WorkspaceDidDeleteFiles");
|
CheckProviderMetadata<provider::workspace::DidDeleteFiles>("workspace/didDeleteFiles", "WorkspaceDidDeleteFiles");
|
||||||
CheckProviderMetadata<provider::workspace::DidRenameFiles>("workspace/didRenameFiles", "WorkspaceDidRenameFiles");
|
CheckProviderMetadata<provider::workspace::DidRenameFiles>("workspace/didRenameFiles", "WorkspaceDidRenameFiles");
|
||||||
CheckProviderMetadata<provider::Initialized>("initialized", "Initialized");
|
CheckProviderMetadata<provider::Initialized>("initialized", "Initialized");
|
||||||
CheckProviderMetadata<provider::Exit>("exit", "Exit");
|
|
||||||
CheckProviderMetadata<provider::CancelRequest>("$/cancelRequest", "CancelRequest");
|
|
||||||
CheckProviderMetadata<provider::SetTrace>("$/setTrace", "SetTrace");
|
CheckProviderMetadata<provider::SetTrace>("$/setTrace", "SetTrace");
|
||||||
CheckProviderMetadata<provider::text_document::DidOpen>("textDocument/didOpen", "TextDocumentDidOpen");
|
CheckProviderMetadata<provider::text_document::DidOpen>("textDocument/didOpen", "TextDocumentDidOpen");
|
||||||
CheckProviderMetadata<provider::text_document::DidChange>("textDocument/didChange", "TextDocumentDidChange");
|
CheckProviderMetadata<provider::text_document::DidChange>("textDocument/didChange", "TextDocumentDidChange");
|
||||||
@@ -287,7 +281,6 @@ namespace lsp::test::provider
|
|||||||
{
|
{
|
||||||
TestResult result{ "", true, "ok" };
|
TestResult result{ "", true, "ok" };
|
||||||
|
|
||||||
CheckRequestResponse<provider::Shutdown>();
|
|
||||||
CheckRequestResponse<provider::completion_item::Resolve>();
|
CheckRequestResponse<provider::completion_item::Resolve>();
|
||||||
CheckRequestResponse<provider::text_document::Completion>();
|
CheckRequestResponse<provider::text_document::Completion>();
|
||||||
CheckRequestResponse<provider::text_document::Definition>();
|
CheckRequestResponse<provider::text_document::Definition>();
|
||||||
@@ -357,7 +350,6 @@ namespace lsp::test::provider
|
|||||||
TestResult result{ "", true, "ok" };
|
TestResult result{ "", true, "ok" };
|
||||||
|
|
||||||
CheckNotificationHandler<provider::Initialized>(std::nullopt);
|
CheckNotificationHandler<provider::Initialized>(std::nullopt);
|
||||||
CheckNotificationHandler<provider::Exit>(std::nullopt);
|
|
||||||
CheckNotificationHandler<provider::workspace::DidChangeConfiguration>(std::nullopt);
|
CheckNotificationHandler<provider::workspace::DidChangeConfiguration>(std::nullopt);
|
||||||
CheckNotificationHandler<provider::workspace::DidChangeWatchedFiles>(std::nullopt);
|
CheckNotificationHandler<provider::workspace::DidChangeWatchedFiles>(std::nullopt);
|
||||||
CheckNotificationHandler<provider::workspace::DidChangeWorkspaceFolders>(std::nullopt);
|
CheckNotificationHandler<provider::workspace::DidChangeWorkspaceFolders>(std::nullopt);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import std;
|
|||||||
|
|
||||||
import lsp.test.framework;
|
import lsp.test.framework;
|
||||||
import lsp.test.provider.completion;
|
import lsp.test.provider.completion;
|
||||||
|
import lsp.test.provider.core_server_fixture;
|
||||||
import lsp.test.provider.definitions;
|
import lsp.test.provider.definitions;
|
||||||
import lsp.test.provider.interpreter;
|
import lsp.test.provider.interpreter;
|
||||||
import lsp.test.provider.json_flow;
|
import lsp.test.provider.json_flow;
|
||||||
@@ -26,9 +27,9 @@ export int Run(int argc, char** argv)
|
|||||||
{
|
{
|
||||||
std::string_view arg(argv[i]);
|
std::string_view arg(argv[i]);
|
||||||
|
|
||||||
if (arg == "--exit-provider")
|
if (arg == "--core-server-fixture")
|
||||||
{
|
{
|
||||||
return lsp::test::provider::RunExitProviderChild();
|
return lsp::test::provider::RunCoreServerFixture();
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr std::string_view kInterpreterPrefix = "--interpreter=";
|
constexpr std::string_view kInterpreterPrefix = "--interpreter=";
|
||||||
|
|||||||
Reference in New Issue
Block a user