✨ 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;
|
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 BuildSignature(const std::vector<language::symbol::Parameter>& params, const std::optional<std::string>& return_type)
|
||||||
{
|
{
|
||||||
std::string detail = "(";
|
std::string detail = "(";
|
||||||
|
|
@ -208,6 +242,27 @@ namespace lsp::provider::completion_item
|
||||||
return snippet;
|
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,
|
std::string BuildCreateObjectSnippet(const std::string& class_name,
|
||||||
const language::symbol::Method* ctor,
|
const language::symbol::Method* ctor,
|
||||||
bool has_open_quote,
|
bool has_open_quote,
|
||||||
|
|
@ -235,6 +290,85 @@ namespace lsp::provider::completion_item
|
||||||
snippet += "$0";
|
snippet += "$0";
|
||||||
return snippet;
|
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>();
|
const auto& obj = item.data->Get<protocol::LSPObject>();
|
||||||
auto ctx = GetStringField(obj, "ctx");
|
auto ctx = GetStringField(obj, "ctx");
|
||||||
auto class_name = GetStringField(obj, "class");
|
|
||||||
auto unit_name = GetStringField(obj, "unit");
|
|
||||||
auto uri = GetStringField(obj, "uri");
|
auto uri = GetStringField(obj, "uri");
|
||||||
|
|
||||||
if (ctx && class_name && !class_name->empty() && uri)
|
if (ctx && uri)
|
||||||
{
|
{
|
||||||
auto& hub = execution_context.GetManagerHub();
|
auto& hub = execution_context.GetManagerHub();
|
||||||
|
|
||||||
|
|
@ -270,6 +402,17 @@ namespace lsp::provider::completion_item
|
||||||
auto workspace_tables = hub.symbols().GetWorkspaceSymbolTables();
|
auto workspace_tables = hub.symbols().GetWorkspaceSymbolTables();
|
||||||
auto system_tables = hub.symbols().GetSystemSymbolTables();
|
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*> {
|
auto try_find = [&](const language::symbol::SymbolTable& table) -> std::optional<const language::symbol::SymbolTable*> {
|
||||||
if (unit_name && !unit_name->empty())
|
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;
|
protocol::ResponseMessage response;
|
||||||
response.id = request.id;
|
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)
|
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())
|
for (const auto& wrapper : table.all_definitions())
|
||||||
{
|
{
|
||||||
const auto& symbol = wrapper.get();
|
const auto& symbol = wrapper.get();
|
||||||
|
|
@ -431,6 +432,17 @@ namespace lsp::provider::text_document
|
||||||
auto detail = BuildSignature(func->parameters, func->return_type);
|
auto detail = BuildSignature(func->parameters, func->return_type);
|
||||||
item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = detail, .description = SourceTag(source) };
|
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 });
|
out.push_back({ std::move(item), source });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -467,6 +479,13 @@ namespace lsp::provider::text_document
|
||||||
{
|
{
|
||||||
auto detail = BuildSignature(func->parameters, func->return_type);
|
auto detail = BuildSignature(func->parameters, func->return_type);
|
||||||
item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = detail, .description = SourceTag(source) };
|
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
|
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)
|
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);
|
auto scope_id = FindScopeOwnedBy(table, language::symbol::ScopeKind::kClass, cls.id);
|
||||||
if (!scope_id)
|
if (!scope_id)
|
||||||
{
|
{
|
||||||
|
|
@ -857,6 +877,19 @@ namespace lsp::provider::text_document
|
||||||
.detail = BuildSignature(method->parameters, method->return_type),
|
.detail = BuildSignature(method->parameters, method->return_type),
|
||||||
.description = SourceTag(source)
|
.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 });
|
out.push_back({ std::move(item), source });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -932,6 +965,13 @@ namespace lsp::provider::text_document
|
||||||
CompletionSource source,
|
CompletionSource source,
|
||||||
std::vector<SourcedCompletionItem>& out)
|
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);
|
auto scope_id = FindScopeOwnedBy(table, language::symbol::ScopeKind::kClass, class_id);
|
||||||
if (!scope_id)
|
if (!scope_id)
|
||||||
{
|
{
|
||||||
|
|
@ -979,6 +1019,22 @@ namespace lsp::provider::text_document
|
||||||
.detail = BuildSignature(method->parameters, method->return_type),
|
.detail = BuildSignature(method->parameters, method->return_type),
|
||||||
.description = SourceTag(source)
|
.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 });
|
out.push_back({ std::move(item), source });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,10 @@ export namespace lsp::test::provider
|
||||||
static TestResult TestCompletionResolveCreateObjectSnippet();
|
static TestResult TestCompletionResolveCreateObjectSnippet();
|
||||||
static TestResult TestCompletionResolveCreateObjectUnquotedSnippet();
|
static TestResult TestCompletionResolveCreateObjectUnquotedSnippet();
|
||||||
static TestResult TestCompletionResolveCreateObjectSingleQuoteSnippet();
|
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 snippet", TestCompletionResolveCreateObjectSnippet);
|
||||||
runner.addTest("completion resolve createobject unquoted snippet", TestCompletionResolveCreateObjectUnquotedSnippet);
|
runner.addTest("completion resolve createobject unquoted snippet", TestCompletionResolveCreateObjectUnquotedSnippet);
|
||||||
runner.addTest("completion resolve createobject single quote snippet", TestCompletionResolveCreateObjectSingleQuoteSnippet);
|
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()
|
TestResult CompletionTests::TestClassMethodCompletion()
|
||||||
|
|
@ -681,4 +689,124 @@ namespace lsp::test::provider
|
||||||
assertTrue(snippet.contains("${1:a}"), "Resolved createobject snippet should include param placeholders");
|
assertTrue(snippet.contains("${1:a}"), "Resolved createobject snippet should include param placeholders");
|
||||||
return result;
|
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