📦 deps(lsp_server): update conan dependencies
lsp-server ci / build-and-test (push) Failing after 0s

This commit is contained in:
csh
2026-05-27 09:54:39 +08:00
parent 7838f3ec3d
commit f4f629c232
9 changed files with 380 additions and 16 deletions
+6 -1
View File
@@ -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)
+2 -2
View File
@@ -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
View File
@@ -1,6 +1,7 @@
module;
// Global module fragment: pull in third-party headers
#include <bit>
#include <taskflow/taskflow.hpp>
export module taskflow;
+18
View File
@@ -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;
}
}
+31 -10
View File
@@ -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