📦 deps(lsp_server): update conan dependencies
lsp-server ci / build-and-test (push) Failing after 0s
lsp-server ci / build-and-test (push) Failing after 0s
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
cmake_minimum_required(VERSION 4.2)
|
||||
|
||||
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
|
||||
# CMake 4.3 rotated the experimental gate UUID for `import std`.
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.3")
|
||||
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "451f2fe2-a8a2-47c3-bc32-94786d8fc91b")
|
||||
else()
|
||||
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
|
||||
endif()
|
||||
|
||||
project(tsl-server LANGUAGES C CXX)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[requires]
|
||||
glaze/7.0.2
|
||||
glaze/7.4.0
|
||||
spdlog/1.17.0
|
||||
fmt/12.1.0
|
||||
taskflow/3.10.0
|
||||
taskflow/4.0.0
|
||||
tree-sitter/0.25.9
|
||||
|
||||
[generators]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module;
|
||||
|
||||
// Global module fragment: pull in third-party headers
|
||||
#include <bit>
|
||||
#include <taskflow/taskflow.hpp>
|
||||
|
||||
export module taskflow;
|
||||
|
||||
@@ -29,6 +29,9 @@ export namespace lsp::codec
|
||||
|
||||
template<typename T>
|
||||
static protocol::LSPAny SerializeViaJson(const T& obj);
|
||||
|
||||
template<typename T>
|
||||
static T ConvertEnum(const protocol::LSPAny& any);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -165,6 +168,14 @@ namespace lsp::codec
|
||||
return std::nullopt;
|
||||
return FromLSPAny<typename Type::value_type>(any);
|
||||
}
|
||||
else if constexpr (std::is_enum_v<Type>)
|
||||
{
|
||||
return ConvertEnum<Type>(any);
|
||||
}
|
||||
else if constexpr (requires { from_lsp_any_custom(std::type_identity<Type>{}, any); })
|
||||
{
|
||||
return from_lsp_any_custom(std::type_identity<Type>{}, any);
|
||||
}
|
||||
else if constexpr (is_user_struct_v<Type>)
|
||||
{
|
||||
return ConvertViaJson<Type>(any);
|
||||
@@ -198,6 +209,13 @@ namespace lsp::codec
|
||||
throw ConversionError("LSPAny does not contain a compatible numeric type");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T LSPAnyConverter::ConvertEnum(const protocol::LSPAny& any)
|
||||
{
|
||||
using Underlying = std::underlying_type_t<T>;
|
||||
return static_cast<T>(ExtractNumber<Underlying>(any));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T LSPAnyConverter::ConvertViaJson(const protocol::LSPAny& any)
|
||||
{
|
||||
|
||||
@@ -241,3 +241,167 @@ export namespace lsp::protocol
|
||||
std::vector<InlineCompletionItem> items;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace lsp::protocol
|
||||
{
|
||||
namespace completion_conversion_detail
|
||||
{
|
||||
inline const LSPObject& RequireObject(const LSPAny& any, std::string_view context)
|
||||
{
|
||||
if (!any.Is<LSPObject>())
|
||||
throw std::runtime_error(std::string(context) + " must be an object");
|
||||
return any.Get<LSPObject>();
|
||||
}
|
||||
|
||||
inline const LSPArray& RequireArray(const LSPAny& any, std::string_view context)
|
||||
{
|
||||
if (!any.Is<LSPArray>())
|
||||
throw std::runtime_error(std::string(context) + " must be an array");
|
||||
return any.Get<LSPArray>();
|
||||
}
|
||||
|
||||
inline const LSPAny* FindField(const LSPObject& obj, std::string_view key)
|
||||
{
|
||||
auto it = obj.find(string(key));
|
||||
if (it == obj.end() || it->second.Is<std::nullptr_t>())
|
||||
return nullptr;
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
inline string ReadString(const LSPAny& any, std::string_view context)
|
||||
{
|
||||
if (!any.Is<string>())
|
||||
throw std::runtime_error(std::string(context) + " must be a string");
|
||||
return any.Get<string>();
|
||||
}
|
||||
|
||||
inline boolean ReadBoolean(const LSPAny& any, std::string_view context)
|
||||
{
|
||||
if (!any.Is<boolean>())
|
||||
throw std::runtime_error(std::string(context) + " must be a boolean");
|
||||
return any.Get<boolean>();
|
||||
}
|
||||
|
||||
inline std::int64_t ReadInteger(const LSPAny& any, std::string_view context)
|
||||
{
|
||||
if (any.Is<integer>())
|
||||
return any.Get<integer>();
|
||||
if (any.Is<uinteger>())
|
||||
return any.Get<uinteger>();
|
||||
throw std::runtime_error(std::string(context) + " must be an integer");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<string> OptionalString(const LSPObject& obj, T key)
|
||||
{
|
||||
if (const auto* field = FindField(obj, key))
|
||||
return ReadString(*field, key);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<boolean> OptionalBoolean(const LSPObject& obj, T key)
|
||||
{
|
||||
if (const auto* field = FindField(obj, key))
|
||||
return ReadBoolean(*field, key);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename Enum, typename T>
|
||||
std::optional<Enum> OptionalEnum(const LSPObject& obj, T key)
|
||||
{
|
||||
if (const auto* field = FindField(obj, key))
|
||||
return static_cast<Enum>(ReadInteger(*field, key));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<std::vector<string>> OptionalStringVector(const LSPObject& obj, T key)
|
||||
{
|
||||
const auto* field = FindField(obj, key);
|
||||
if (field == nullptr)
|
||||
return std::nullopt;
|
||||
|
||||
const auto& arr = RequireArray(*field, key);
|
||||
std::vector<string> values;
|
||||
values.reserve(arr.size());
|
||||
for (const auto& item : arr)
|
||||
values.push_back(ReadString(item, key));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
inline CompletionItemLabelDetails from_lsp_any_custom(std::type_identity<CompletionItemLabelDetails>,
|
||||
const LSPAny& any)
|
||||
{
|
||||
const auto& obj = completion_conversion_detail::RequireObject(any, "CompletionItemLabelDetails");
|
||||
|
||||
CompletionItemLabelDetails details;
|
||||
details.detail = completion_conversion_detail::OptionalString(obj, "detail");
|
||||
details.description = completion_conversion_detail::OptionalString(obj, "description");
|
||||
return details;
|
||||
}
|
||||
|
||||
inline CompletionItem from_lsp_any_custom(std::type_identity<CompletionItem>, const LSPAny& any)
|
||||
{
|
||||
const auto& obj = completion_conversion_detail::RequireObject(any, "CompletionItem");
|
||||
|
||||
CompletionItem item;
|
||||
const auto* label = completion_conversion_detail::FindField(obj, "label");
|
||||
if (label == nullptr)
|
||||
throw std::runtime_error("Missing required field: label");
|
||||
|
||||
item.label = completion_conversion_detail::ReadString(*label, "label");
|
||||
if (const auto* label_details = completion_conversion_detail::FindField(obj, "labelDetails"))
|
||||
item.labelDetails = from_lsp_any_custom(std::type_identity<CompletionItemLabelDetails>{}, *label_details);
|
||||
item.kind = completion_conversion_detail::OptionalEnum<CompletionItemKind>(obj, "kind");
|
||||
item.detail = completion_conversion_detail::OptionalString(obj, "detail");
|
||||
item.preselect = completion_conversion_detail::OptionalBoolean(obj, "preselect");
|
||||
item.sortText = completion_conversion_detail::OptionalString(obj, "sortText");
|
||||
item.filterText = completion_conversion_detail::OptionalString(obj, "filterText");
|
||||
item.insertText = completion_conversion_detail::OptionalString(obj, "insertText");
|
||||
item.insertTextFormat = completion_conversion_detail::OptionalEnum<InsertTextFormat>(obj, "insertTextFormat");
|
||||
item.insertTextMode = completion_conversion_detail::OptionalEnum<InsertTextMode>(obj, "insertTextMode");
|
||||
item.textEditText = completion_conversion_detail::OptionalString(obj, "textEditText");
|
||||
item.commitCharacters = completion_conversion_detail::OptionalStringVector(obj, "commitCharacters");
|
||||
|
||||
if (const auto* data = completion_conversion_detail::FindField(obj, "data"))
|
||||
item.data = *data;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
inline CompletionList from_lsp_any_custom(std::type_identity<CompletionList>, const LSPAny& any)
|
||||
{
|
||||
const auto& obj = completion_conversion_detail::RequireObject(any, "CompletionList");
|
||||
|
||||
CompletionList list;
|
||||
const auto* is_incomplete = completion_conversion_detail::FindField(obj, "isIncomplete");
|
||||
if (is_incomplete == nullptr)
|
||||
throw std::runtime_error("Missing required field: isIncomplete");
|
||||
list.isIncomplete = completion_conversion_detail::ReadBoolean(*is_incomplete, "isIncomplete");
|
||||
|
||||
if (const auto* items = completion_conversion_detail::FindField(obj, "items"))
|
||||
{
|
||||
const auto& arr = completion_conversion_detail::RequireArray(*items, "items");
|
||||
list.items.reserve(arr.size());
|
||||
for (const auto& item : arr)
|
||||
list.items.push_back(from_lsp_any_custom(std::type_identity<CompletionItem>{}, item));
|
||||
}
|
||||
|
||||
if (const auto* item_defaults = completion_conversion_detail::FindField(obj, "itemDefaults"))
|
||||
{
|
||||
const auto& defaults_obj = completion_conversion_detail::RequireObject(*item_defaults, "itemDefaults");
|
||||
list.itemDefaults.commitCharacters =
|
||||
completion_conversion_detail::OptionalStringVector(defaults_obj, "commitCharacters");
|
||||
list.itemDefaults.insertTextFormat =
|
||||
completion_conversion_detail::OptionalEnum<InsertTextFormat>(defaults_obj, "insertTextFormat");
|
||||
list.itemDefaults.insertTextMode =
|
||||
completion_conversion_detail::OptionalEnum<InsertTextMode>(defaults_obj, "insertTextMode");
|
||||
if (const auto* data = completion_conversion_detail::FindField(defaults_obj, "data"))
|
||||
list.itemDefaults.data = *data;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export namespace lsp::scheduler
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool completed = false;
|
||||
bool callback_completed = false;
|
||||
std::optional<std::string> result;
|
||||
std::exception_ptr error;
|
||||
std::chrono::steady_clock::time_point start_time{};
|
||||
@@ -141,7 +142,7 @@ namespace lsp::scheduler
|
||||
if (!state)
|
||||
return false;
|
||||
std::unique_lock<std::mutex> lk(state->mutex);
|
||||
state->cv.wait(lk, [state]() { return state->completed; });
|
||||
state->cv.wait(lk, [state]() { return state->completed && state->callback_completed; });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ namespace lsp::scheduler
|
||||
if (!state)
|
||||
return std::nullopt;
|
||||
std::unique_lock<std::mutex> lk(state->mutex);
|
||||
if (!state->completed || state->error)
|
||||
if (!state->completed || !state->callback_completed || state->error)
|
||||
return std::nullopt;
|
||||
return state->result;
|
||||
}
|
||||
@@ -213,7 +214,7 @@ namespace lsp::scheduler
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lk(state->mutex);
|
||||
state->cv.wait(lk, [state]() { return state->completed; });
|
||||
state->cv.wait(lk, [state]() { return state->completed && state->callback_completed; });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -231,7 +232,7 @@ namespace lsp::scheduler
|
||||
for (const auto& state : tasks)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(state->mutex);
|
||||
state->cv.wait(lk, [state]() { return state->completed; });
|
||||
state->cv.wait(lk, [state]() { return state->completed && state->callback_completed; });
|
||||
}
|
||||
|
||||
// Ensure the underlying executor finishes any tasks that may have been
|
||||
@@ -334,22 +335,42 @@ namespace lsp::scheduler
|
||||
state->error = std::make_exception_ptr(std::runtime_error("Task failed"));
|
||||
}
|
||||
|
||||
state->cv.notify_all();
|
||||
|
||||
UnregisterTask(task_id, state);
|
||||
|
||||
auto elapsed = GetElapsedTime(start_time);
|
||||
if (failed)
|
||||
bool callback_failed = false;
|
||||
if (callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(result, is_cancelled);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
callback_failed = true;
|
||||
std::unique_lock<std::mutex> lk(state->mutex);
|
||||
state->error = std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
const bool has_failed = failed || callback_failed;
|
||||
if (has_failed)
|
||||
++failed_;
|
||||
else if (is_cancelled)
|
||||
++cancelled_;
|
||||
else
|
||||
++completed_;
|
||||
|
||||
if (callback)
|
||||
callback(result, is_cancelled);
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(state->mutex);
|
||||
state->callback_completed = true;
|
||||
}
|
||||
state->cv.notify_all();
|
||||
|
||||
spdlog::info("[{}] Task completed. cancelled={}, failed={}, elapsed={}", task_id, is_cancelled, failed, FormatDuration(elapsed));
|
||||
if (callback_failed)
|
||||
spdlog::error("[{}] Task callback threw exception", task_id);
|
||||
|
||||
spdlog::info("[{}] Task completed. cancelled={}, failed={}, elapsed={}", task_id, is_cancelled, has_failed, FormatDuration(elapsed));
|
||||
}
|
||||
|
||||
bool AsyncExecutor::RegisterTask(const std::string& task_id, const detail::ActiveEntry& ctx)
|
||||
|
||||
@@ -6,7 +6,10 @@ export module lsp.test.lsp_any.transformer;
|
||||
|
||||
import lsp.test.framework;
|
||||
import lsp.protocol.common.basic_types;
|
||||
import lsp.protocol.common.message;
|
||||
import lsp.protocol.text_document.completion;
|
||||
import lsp.codec.common;
|
||||
import lsp.codec.facade;
|
||||
import lsp.codec.transformer;
|
||||
|
||||
export namespace lsp::test
|
||||
@@ -69,6 +72,7 @@ export namespace lsp::test
|
||||
static TestResult testNestedVector();
|
||||
static TestResult testNestedLSPObject();
|
||||
static TestResult testMixedTypeNesting();
|
||||
static TestResult testCompletionListWithDataObject();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,6 +128,7 @@ namespace lsp::test
|
||||
runner.addTest("Transformer - 嵌套Vector", testNestedVector);
|
||||
runner.addTest("Transformer - 嵌套LSPObject", testNestedLSPObject);
|
||||
runner.addTest("Transformer - 混合类型嵌套", testMixedTypeNesting);
|
||||
runner.addTest("Transformer - CompletionList data对象", testCompletionListWithDataObject);
|
||||
}
|
||||
|
||||
// ==================== ToLSPAny 基本类型测试 ====================
|
||||
@@ -717,4 +722,32 @@ namespace lsp::test
|
||||
return result;
|
||||
}
|
||||
|
||||
TestResult TransformerTests::testCompletionListWithDataObject()
|
||||
{
|
||||
TestResult result;
|
||||
result.passed = true;
|
||||
|
||||
const auto response_json = std::string{
|
||||
R"json({"jsonrpc":"2.0","id":"c1","result":{"isIncomplete":false,"itemDefaults":{},"items":[{"data":{"class":"Widget","ctx":"call","is_static":true,"kind":"method","name":"StaticFoo","unit":"MainUnit","uri":"file:///home/csh/windows_share/tinysoft/tsl-devkit/lsp-server/test/test_provider/fixtures/main_unit.tsf"},"kind":2,"label":"StaticFoo","labelDetails":{"description":"[E]","detail":"(y: integer)"}}]}})json"
|
||||
};
|
||||
auto response = transform::Deserialize<protocol::ResponseMessage>(response_json);
|
||||
|
||||
assertTrue(response.has_value(), "应该能反序列化 ResponseMessage");
|
||||
assertTrue(response->result.has_value(), "ResponseMessage.result 应该有值");
|
||||
|
||||
auto completion_list = transform::FromLSPAny.template operator()<protocol::CompletionList>(response->result.value());
|
||||
|
||||
assertEqual(size_t(1), completion_list.items.size(), "应该保留一个补全项");
|
||||
assertTrue(completion_list.items[0].data.has_value(), "CompletionItem.data 应该有值");
|
||||
assertTrue(completion_list.items[0].data->Is<protocol::LSPObject>(), "CompletionItem.data 应该是对象");
|
||||
|
||||
const auto& result_data = completion_list.items[0].data->Get<protocol::LSPObject>();
|
||||
assertEqual(std::string("Widget"), result_data.at("class").Get<protocol::string>(), "data.class 应该保留");
|
||||
assertEqual(std::string("call"), result_data.at("ctx").Get<protocol::string>(), "data.ctx 应该保留");
|
||||
assertEqual(true, result_data.at("is_static").Get<protocol::boolean>(), "data.is_static 应该保留");
|
||||
|
||||
result.message = "成功";
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace lsp::test
|
||||
|
||||
Reference in New Issue
Block a user