✨ feat(lsp_server): add call snippets to completion resolve
This commit is contained in:
parent
baaf2ee688
commit
b2a1bb6685
|
|
@ -168,6 +168,40 @@ namespace lsp::provider::completion_item
|
|||
return best;
|
||||
}
|
||||
|
||||
template<typename Callable>
|
||||
const Callable* PickBestCallable(const std::vector<const Callable*>& candidates)
|
||||
{
|
||||
const Callable* best = nullptr;
|
||||
std::size_t best_required = std::numeric_limits<std::size_t>::max();
|
||||
std::size_t best_total = std::numeric_limits<std::size_t>::max();
|
||||
|
||||
for (const auto* candidate : candidates)
|
||||
{
|
||||
if (!candidate)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::size_t required = 0;
|
||||
for (const auto& p : candidate->parameters)
|
||||
{
|
||||
if (!p.default_value.has_value())
|
||||
{
|
||||
++required;
|
||||
}
|
||||
}
|
||||
|
||||
if (required < best_required || (required == best_required && candidate->parameters.size() < best_total))
|
||||
{
|
||||
best = candidate;
|
||||
best_required = required;
|
||||
best_total = candidate->parameters.size();
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
std::string BuildSignature(const std::vector<language::symbol::Parameter>& params, const std::optional<std::string>& return_type)
|
||||
{
|
||||
std::string detail = "(";
|
||||
|
|
@ -208,6 +242,27 @@ namespace lsp::provider::completion_item
|
|||
return snippet;
|
||||
}
|
||||
|
||||
std::string BuildCallSnippet(const std::string& name, const std::vector<language::symbol::Parameter>& params)
|
||||
{
|
||||
std::string snippet = name;
|
||||
snippet += "(";
|
||||
|
||||
for (std::size_t i = 0; i < params.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
snippet += ", ";
|
||||
}
|
||||
|
||||
const auto& p = params[i];
|
||||
snippet += "${" + std::to_string(i + 1) + ":" + p.name + "}";
|
||||
}
|
||||
|
||||
snippet += ")";
|
||||
snippet += "$0";
|
||||
return snippet;
|
||||
}
|
||||
|
||||
std::string BuildCreateObjectSnippet(const std::string& class_name,
|
||||
const language::symbol::Method* ctor,
|
||||
bool has_open_quote,
|
||||
|
|
@ -235,6 +290,85 @@ namespace lsp::provider::completion_item
|
|||
snippet += "$0";
|
||||
return snippet;
|
||||
}
|
||||
|
||||
std::vector<const language::symbol::Function*> CollectFunctions(
|
||||
const language::symbol::SymbolTable& table,
|
||||
const std::string& function_name)
|
||||
{
|
||||
std::vector<const language::symbol::Function*> result;
|
||||
for (auto id : table.FindSymbolsByName(function_name))
|
||||
{
|
||||
const auto* symbol = table.definition(id);
|
||||
if (!symbol || symbol->kind() != protocol::SymbolKind::Function)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto* fn = symbol->As<language::symbol::Function>())
|
||||
{
|
||||
result.push_back(fn);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<const language::symbol::Method*> CollectMethods(
|
||||
const language::symbol::SymbolTable& table,
|
||||
language::symbol::SymbolId class_id,
|
||||
const std::string& method_name,
|
||||
std::optional<bool> is_static_filter)
|
||||
{
|
||||
std::vector<const language::symbol::Method*> result;
|
||||
auto scope_id = FindScopeOwnedBy(table, language::symbol::ScopeKind::kClass, class_id);
|
||||
if (!scope_id)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* scope = table.scopes().scope(*scope_id);
|
||||
if (!scope)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const auto& [_, ids] : scope->symbols)
|
||||
{
|
||||
for (auto id : ids)
|
||||
{
|
||||
const auto* symbol = table.definition(id);
|
||||
if (!symbol || symbol->kind() != protocol::SymbolKind::Method)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* method = symbol->As<language::symbol::Method>();
|
||||
if (!method)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method->method_kind == language::ast::MethodKind::kConstructor ||
|
||||
method->method_kind == language::ast::MethodKind::kDestructor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!utils::IEquals(method->name, method_name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_static_filter.has_value() && method->is_static != *is_static_filter)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_back(method);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -258,11 +392,9 @@ namespace lsp::provider::completion_item
|
|||
{
|
||||
const auto& obj = item.data->Get<protocol::LSPObject>();
|
||||
auto ctx = GetStringField(obj, "ctx");
|
||||
auto class_name = GetStringField(obj, "class");
|
||||
auto unit_name = GetStringField(obj, "unit");
|
||||
auto uri = GetStringField(obj, "uri");
|
||||
|
||||
if (ctx && class_name && !class_name->empty() && uri)
|
||||
if (ctx && uri)
|
||||
{
|
||||
auto& hub = execution_context.GetManagerHub();
|
||||
|
||||
|
|
@ -270,6 +402,17 @@ namespace lsp::provider::completion_item
|
|||
auto workspace_tables = hub.symbols().GetWorkspaceSymbolTables();
|
||||
auto system_tables = hub.symbols().GetSystemSymbolTables();
|
||||
|
||||
if (*ctx == "new" || *ctx == "createobject")
|
||||
{
|
||||
auto class_name = GetStringField(obj, "class");
|
||||
auto unit_name = GetStringField(obj, "unit");
|
||||
|
||||
if (!class_name || class_name->empty())
|
||||
{
|
||||
// Nothing to resolve.
|
||||
}
|
||||
else
|
||||
{
|
||||
auto try_find = [&](const language::symbol::SymbolTable& table) -> std::optional<const language::symbol::SymbolTable*> {
|
||||
if (unit_name && !unit_name->empty())
|
||||
{
|
||||
|
|
@ -356,6 +499,183 @@ namespace lsp::provider::completion_item
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (*ctx == "call")
|
||||
{
|
||||
auto callable_name = GetStringField(obj, "name");
|
||||
if (!callable_name || callable_name->empty())
|
||||
{
|
||||
callable_name = item.label;
|
||||
}
|
||||
|
||||
auto unit_name = GetStringField(obj, "unit");
|
||||
auto class_name = GetStringField(obj, "class");
|
||||
auto is_static = GetBoolField(obj, "is_static");
|
||||
|
||||
auto matches_unit = [&](const language::symbol::SymbolTable& table) -> bool {
|
||||
if (!unit_name || unit_name->empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto module = GetModuleName(table);
|
||||
return !module.empty() && utils::IEquals(module, *unit_name);
|
||||
};
|
||||
|
||||
if (class_name && !class_name->empty())
|
||||
{
|
||||
auto try_find = [&](const language::symbol::SymbolTable& table) -> std::optional<const language::symbol::SymbolTable*> {
|
||||
if (!matches_unit(table))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
if (FindClassSymbol(table, *class_name))
|
||||
{
|
||||
return &table;
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
const language::symbol::SymbolTable* table_for_class = nullptr;
|
||||
if (editing_table)
|
||||
{
|
||||
if (auto found = try_find(*editing_table))
|
||||
{
|
||||
table_for_class = *found;
|
||||
}
|
||||
}
|
||||
|
||||
if (!table_for_class)
|
||||
{
|
||||
for (const auto* t : workspace_tables)
|
||||
{
|
||||
if (!t)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (auto found = try_find(*t))
|
||||
{
|
||||
table_for_class = *found;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!table_for_class)
|
||||
{
|
||||
for (const auto* t : system_tables)
|
||||
{
|
||||
if (!t)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (auto found = try_find(*t))
|
||||
{
|
||||
table_for_class = *found;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (table_for_class)
|
||||
{
|
||||
if (auto cls_sym = FindClassSymbol(*table_for_class, *class_name))
|
||||
{
|
||||
auto methods = CollectMethods(*table_for_class, (*cls_sym)->id(), *callable_name, is_static);
|
||||
const auto* best = PickBestCallable(methods);
|
||||
if (best)
|
||||
{
|
||||
if (!item.labelDetails)
|
||||
{
|
||||
item.labelDetails = protocol::CompletionItemLabelDetails{};
|
||||
}
|
||||
if (!item.labelDetails->detail || item.labelDetails->detail->empty())
|
||||
{
|
||||
item.labelDetails->detail = BuildSignature(best->parameters, best->return_type);
|
||||
}
|
||||
|
||||
item.insertText = BuildCallSnippet(best->name, best->parameters);
|
||||
item.insertTextFormat = protocol::InsertTextFormat::Snippet;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto try_find = [&](const language::symbol::SymbolTable& table) -> std::optional<const language::symbol::Function*> {
|
||||
if (!matches_unit(table))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto candidates = CollectFunctions(table, *callable_name);
|
||||
if (candidates.empty())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (auto best = PickBestCallable(candidates))
|
||||
{
|
||||
return best;
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
const language::symbol::Function* best = nullptr;
|
||||
if (editing_table)
|
||||
{
|
||||
if (auto found = try_find(*editing_table))
|
||||
{
|
||||
best = *found;
|
||||
}
|
||||
}
|
||||
if (!best)
|
||||
{
|
||||
for (const auto* t : workspace_tables)
|
||||
{
|
||||
if (!t)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (auto found = try_find(*t))
|
||||
{
|
||||
best = *found;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!best)
|
||||
{
|
||||
for (const auto* t : system_tables)
|
||||
{
|
||||
if (!t)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (auto found = try_find(*t))
|
||||
{
|
||||
best = *found;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best)
|
||||
{
|
||||
if (!item.labelDetails)
|
||||
{
|
||||
item.labelDetails = protocol::CompletionItemLabelDetails{};
|
||||
}
|
||||
if (!item.labelDetails->detail || item.labelDetails->detail->empty())
|
||||
{
|
||||
item.labelDetails->detail = BuildSignature(best->parameters, best->return_type);
|
||||
}
|
||||
|
||||
item.insertText = BuildCallSnippet(best->name, best->parameters);
|
||||
item.insertTextFormat = protocol::InsertTextFormat::Snippet;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol::ResponseMessage response;
|
||||
response.id = request.id;
|
||||
|
|
|
|||
|
|
@ -414,6 +414,7 @@ namespace lsp::provider::text_document
|
|||
|
||||
void AppendFunctions(const language::symbol::SymbolTable& table, const std::string& prefix, CompletionSource source, std::vector<SourcedCompletionItem>& out)
|
||||
{
|
||||
const auto module_name = GetModuleName(table);
|
||||
for (const auto& wrapper : table.all_definitions())
|
||||
{
|
||||
const auto& symbol = wrapper.get();
|
||||
|
|
@ -431,6 +432,17 @@ namespace lsp::provider::text_document
|
|||
auto detail = BuildSignature(func->parameters, func->return_type);
|
||||
item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = detail, .description = SourceTag(source) };
|
||||
}
|
||||
|
||||
protocol::LSPObject data;
|
||||
data["ctx"] = "call";
|
||||
data["kind"] = "function";
|
||||
data["name"] = symbol.name();
|
||||
if (!module_name.empty())
|
||||
{
|
||||
data["unit"] = module_name;
|
||||
}
|
||||
item.data = std::move(data);
|
||||
|
||||
out.push_back({ std::move(item), source });
|
||||
}
|
||||
}
|
||||
|
|
@ -467,6 +479,13 @@ namespace lsp::provider::text_document
|
|||
{
|
||||
auto detail = BuildSignature(func->parameters, func->return_type);
|
||||
item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = detail, .description = SourceTag(source) };
|
||||
|
||||
protocol::LSPObject data;
|
||||
data["ctx"] = "call";
|
||||
data["kind"] = "function";
|
||||
data["name"] = symbol.name();
|
||||
data["unit"] = module_name;
|
||||
item.data = std::move(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -819,6 +838,7 @@ namespace lsp::provider::text_document
|
|||
|
||||
void AppendClassMethods(const language::symbol::SymbolTable& table, const language::symbol::Class& cls, const std::string& prefix, CompletionSource source, std::vector<SourcedCompletionItem>& out)
|
||||
{
|
||||
const auto module_name = GetModuleName(table);
|
||||
auto scope_id = FindScopeOwnedBy(table, language::symbol::ScopeKind::kClass, cls.id);
|
||||
if (!scope_id)
|
||||
{
|
||||
|
|
@ -857,6 +877,19 @@ namespace lsp::provider::text_document
|
|||
.detail = BuildSignature(method->parameters, method->return_type),
|
||||
.description = SourceTag(source)
|
||||
};
|
||||
|
||||
protocol::LSPObject data;
|
||||
data["ctx"] = "call";
|
||||
data["kind"] = "method";
|
||||
data["name"] = method->name;
|
||||
data["class"] = cls.name;
|
||||
if (!module_name.empty())
|
||||
{
|
||||
data["unit"] = module_name;
|
||||
}
|
||||
data["is_static"] = method->is_static;
|
||||
item.data = std::move(data);
|
||||
|
||||
out.push_back({ std::move(item), source });
|
||||
}
|
||||
}
|
||||
|
|
@ -932,6 +965,13 @@ namespace lsp::provider::text_document
|
|||
CompletionSource source,
|
||||
std::vector<SourcedCompletionItem>& out)
|
||||
{
|
||||
const auto module_name = GetModuleName(table);
|
||||
std::string class_name;
|
||||
if (const auto* class_symbol = table.definition(class_id))
|
||||
{
|
||||
class_name = class_symbol->name();
|
||||
}
|
||||
|
||||
auto scope_id = FindScopeOwnedBy(table, language::symbol::ScopeKind::kClass, class_id);
|
||||
if (!scope_id)
|
||||
{
|
||||
|
|
@ -979,6 +1019,22 @@ namespace lsp::provider::text_document
|
|||
.detail = BuildSignature(method->parameters, method->return_type),
|
||||
.description = SourceTag(source)
|
||||
};
|
||||
|
||||
protocol::LSPObject data;
|
||||
data["ctx"] = "call";
|
||||
data["kind"] = "method";
|
||||
data["name"] = method->name;
|
||||
if (!class_name.empty())
|
||||
{
|
||||
data["class"] = class_name;
|
||||
}
|
||||
if (!module_name.empty())
|
||||
{
|
||||
data["unit"] = module_name;
|
||||
}
|
||||
data["is_static"] = method->is_static;
|
||||
item.data = std::move(data);
|
||||
|
||||
out.push_back({ std::move(item), source });
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ export namespace lsp::test::provider
|
|||
static TestResult TestCompletionResolveCreateObjectSnippet();
|
||||
static TestResult TestCompletionResolveCreateObjectUnquotedSnippet();
|
||||
static TestResult TestCompletionResolveCreateObjectSingleQuoteSnippet();
|
||||
static TestResult TestCompletionResolveFunctionSnippet();
|
||||
static TestResult TestCompletionResolveUnitMemberFunctionSnippet();
|
||||
static TestResult TestCompletionResolveInstanceMethodSnippet();
|
||||
static TestResult TestCompletionResolveStaticMethodSnippet();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +195,10 @@ namespace lsp::test::provider
|
|||
runner.addTest("completion resolve createobject snippet", TestCompletionResolveCreateObjectSnippet);
|
||||
runner.addTest("completion resolve createobject unquoted snippet", TestCompletionResolveCreateObjectUnquotedSnippet);
|
||||
runner.addTest("completion resolve createobject single quote snippet", TestCompletionResolveCreateObjectSingleQuoteSnippet);
|
||||
runner.addTest("completion resolve function snippet", TestCompletionResolveFunctionSnippet);
|
||||
runner.addTest("completion resolve unit member function snippet", TestCompletionResolveUnitMemberFunctionSnippet);
|
||||
runner.addTest("completion resolve instance method snippet", TestCompletionResolveInstanceMethodSnippet);
|
||||
runner.addTest("completion resolve static method snippet", TestCompletionResolveStaticMethodSnippet);
|
||||
}
|
||||
|
||||
TestResult CompletionTests::TestClassMethodCompletion()
|
||||
|
|
@ -681,4 +689,124 @@ namespace lsp::test::provider
|
|||
assertTrue(snippet.contains("${1:a}"), "Resolved createobject snippet should include param placeholders");
|
||||
return result;
|
||||
}
|
||||
|
||||
TestResult CompletionTests::TestCompletionResolveFunctionSnippet()
|
||||
{
|
||||
TestResult result{ "", true, "ok" };
|
||||
ProviderEnv env;
|
||||
auto path = FixturePath("main_unit.tsf");
|
||||
auto content = ReadTextFile(path);
|
||||
auto uri = ToUri(path);
|
||||
OpenDocument(env.hub, uri, content, 1);
|
||||
|
||||
auto items = RequestCompletion(env, uri, content, "UnitF");
|
||||
auto item = FindItem(items, "UnitFunc");
|
||||
assertTrue(item.has_value(), "Expected UnitFunc completion");
|
||||
|
||||
protocol::RequestMessage request;
|
||||
request.id = "r5";
|
||||
request.method = "completionItem/resolve";
|
||||
request.params = codec::ToLSPAny(*item);
|
||||
|
||||
::lsp::provider::completion_item::Resolve resolver;
|
||||
auto json = resolver.ProvideResponse(request, env.context);
|
||||
auto response = ParseResponse(json);
|
||||
auto resolved = codec::FromLSPAny.template operator()<protocol::CompletionItem>(response.result.value());
|
||||
|
||||
assertTrue(resolved.insertText.has_value(), "Resolved function item should have insertText");
|
||||
auto snippet = resolved.insertText.value();
|
||||
assertTrue(snippet.starts_with("UnitFunc("), "Resolved function snippet should start with UnitFunc(");
|
||||
assertTrue(snippet.contains("${1:a}"), "Resolved function snippet should include param placeholders");
|
||||
return result;
|
||||
}
|
||||
|
||||
TestResult CompletionTests::TestCompletionResolveUnitMemberFunctionSnippet()
|
||||
{
|
||||
TestResult result{ "", true, "ok" };
|
||||
ProviderEnv env;
|
||||
auto path = FixturePath("main_unit.tsf");
|
||||
auto content = ReadTextFile(path);
|
||||
auto uri = ToUri(path);
|
||||
OpenDocument(env.hub, uri, content, 1);
|
||||
|
||||
auto items = RequestCompletion(env, uri, content, "MainUnit.Uni");
|
||||
auto item = FindItem(items, "UnitFunc");
|
||||
assertTrue(item.has_value(), "Expected UnitFunc completion from unit member context");
|
||||
|
||||
protocol::RequestMessage request;
|
||||
request.id = "r6";
|
||||
request.method = "completionItem/resolve";
|
||||
request.params = codec::ToLSPAny(*item);
|
||||
|
||||
::lsp::provider::completion_item::Resolve resolver;
|
||||
auto json = resolver.ProvideResponse(request, env.context);
|
||||
auto response = ParseResponse(json);
|
||||
auto resolved = codec::FromLSPAny.template operator()<protocol::CompletionItem>(response.result.value());
|
||||
|
||||
assertTrue(resolved.insertText.has_value(), "Resolved unit member function should have insertText");
|
||||
auto snippet = resolved.insertText.value();
|
||||
assertTrue(snippet.starts_with("UnitFunc("), "Resolved unit member function snippet should start with UnitFunc(");
|
||||
assertTrue(snippet.contains("${1:a}"), "Resolved unit member function snippet should include param placeholders");
|
||||
return result;
|
||||
}
|
||||
|
||||
TestResult CompletionTests::TestCompletionResolveInstanceMethodSnippet()
|
||||
{
|
||||
TestResult result{ "", true, "ok" };
|
||||
ProviderEnv env;
|
||||
auto path = FixturePath("main_unit.tsf");
|
||||
auto content = ReadTextFile(path);
|
||||
auto uri = ToUri(path);
|
||||
OpenDocument(env.hub, uri, content, 1);
|
||||
|
||||
auto items = RequestCompletion(env, uri, content, "obj.Fo");
|
||||
auto item = FindItem(items, "Foo");
|
||||
assertTrue(item.has_value(), "Expected Foo completion from object member context");
|
||||
|
||||
protocol::RequestMessage request;
|
||||
request.id = "r7";
|
||||
request.method = "completionItem/resolve";
|
||||
request.params = codec::ToLSPAny(*item);
|
||||
|
||||
::lsp::provider::completion_item::Resolve resolver;
|
||||
auto json = resolver.ProvideResponse(request, env.context);
|
||||
auto response = ParseResponse(json);
|
||||
auto resolved = codec::FromLSPAny.template operator()<protocol::CompletionItem>(response.result.value());
|
||||
|
||||
assertTrue(resolved.insertText.has_value(), "Resolved method item should have insertText");
|
||||
auto snippet = resolved.insertText.value();
|
||||
assertTrue(snippet.starts_with("Foo("), "Resolved method snippet should start with Foo(");
|
||||
assertTrue(snippet.contains("${1:x}"), "Resolved method snippet should include param placeholders");
|
||||
return result;
|
||||
}
|
||||
|
||||
TestResult CompletionTests::TestCompletionResolveStaticMethodSnippet()
|
||||
{
|
||||
TestResult result{ "", true, "ok" };
|
||||
ProviderEnv env;
|
||||
auto path = FixturePath("main_unit.tsf");
|
||||
auto content = ReadTextFile(path);
|
||||
auto uri = ToUri(path);
|
||||
OpenDocument(env.hub, uri, content, 1);
|
||||
|
||||
auto items = RequestCompletion(env, uri, content, "class(Widget).Sta");
|
||||
auto item = FindItem(items, "StaticFoo");
|
||||
assertTrue(item.has_value(), "Expected StaticFoo completion from class method context");
|
||||
|
||||
protocol::RequestMessage request;
|
||||
request.id = "r8";
|
||||
request.method = "completionItem/resolve";
|
||||
request.params = codec::ToLSPAny(*item);
|
||||
|
||||
::lsp::provider::completion_item::Resolve resolver;
|
||||
auto json = resolver.ProvideResponse(request, env.context);
|
||||
auto response = ParseResponse(json);
|
||||
auto resolved = codec::FromLSPAny.template operator()<protocol::CompletionItem>(response.result.value());
|
||||
|
||||
assertTrue(resolved.insertText.has_value(), "Resolved static method should have insertText");
|
||||
auto snippet = resolved.insertText.value();
|
||||
assertTrue(snippet.starts_with("StaticFoo("), "Resolved static method snippet should start with StaticFoo(");
|
||||
assertTrue(snippet.contains("${1:y}"), "Resolved static method snippet should include param placeholders");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue