module; export module lsp.provider.completion_item.resolve; import spdlog; import std; import lsp.protocol; import lsp.codec.facade; import lsp.provider.base.interface; import lsp.language.symbol; import lsp.language.ast; import lsp.utils.string; namespace transform = lsp::codec; export namespace lsp::provider::completion_item { class Resolve : public AutoRegisterProvider { public: static constexpr std::string_view kMethod = "completionItem/resolve"; static constexpr std::string_view kProviderName = "CompletionItemResolve"; Resolve() = default; std::string ProvideResponse(const protocol::RequestMessage& request, ExecutionContext& execution_context) override; }; } namespace lsp::provider::completion_item { namespace { std::optional GetStringField(const protocol::LSPObject& obj, const std::string& key) { auto it = obj.find(key); if (it == obj.end() || !it->second.Is()) { return std::nullopt; } const auto& s = it->second.Get(); return s; } std::optional GetBoolField(const protocol::LSPObject& obj, const std::string& key) { auto it = obj.find(key); if (it == obj.end() || !it->second.Is()) { return std::nullopt; } return it->second.Get(); } std::string GetModuleName(const language::symbol::SymbolTable& table) { for (const auto& wrapper : table.all_definitions()) { const auto& symbol = wrapper.get(); if (symbol.kind() == protocol::SymbolKind::Module) { return symbol.name(); } } return ""; } std::optional FindClassSymbol( const language::symbol::SymbolTable& table, const std::string& class_name) { auto ids = table.FindSymbolsByName(class_name); for (auto id : ids) { const auto* symbol = table.definition(id); if (symbol && symbol->kind() == protocol::SymbolKind::Class) { return symbol; } } return std::nullopt; } std::optional FindScopeOwnedBy( const language::symbol::SymbolTable& table, language::symbol::ScopeKind kind, language::symbol::SymbolId owner_id) { for (const auto& [scope_id, scope] : table.scopes().all_scopes()) { if (scope.kind == kind && scope.owner && *scope.owner == owner_id) { return scope_id; } } return std::nullopt; } std::vector CollectConstructors( const language::symbol::SymbolTable& table, language::symbol::SymbolId class_id) { std::vector 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* member = table.definition(id); if (!member || member->kind() != protocol::SymbolKind::Method) { continue; } const auto* method = member->As(); if (!method || method->method_kind != language::ast::MethodKind::kConstructor) { continue; } result.push_back(method); } } return result; } const language::symbol::Method* PickBestConstructor(const std::vector& ctors) { const language::symbol::Method* best = nullptr; std::size_t best_required = std::numeric_limits::max(); std::size_t best_total = std::numeric_limits::max(); for (const auto* ctor : ctors) { if (!ctor) { continue; } std::size_t required = 0; for (const auto& p : ctor->parameters) { if (!p.default_value.has_value()) { ++required; } } if (required < best_required || (required == best_required && ctor->parameters.size() < best_total)) { best = ctor; best_required = required; best_total = ctor->parameters.size(); } } return best; } std::string BuildSignature(const std::vector& params, const std::optional& return_type) { std::string detail = "("; for (std::size_t i = 0; i < params.size(); ++i) { if (i > 0) detail += ", "; detail += params[i].name; if (params[i].type && !params[i].type->empty()) detail += ": " + *params[i].type; } detail += ")"; if (return_type && !return_type->empty()) detail += ": " + *return_type; return detail; } std::string BuildNewSnippet(const std::string& class_name, const language::symbol::Method* ctor) { std::string snippet = class_name; snippet += "("; if (ctor && !ctor->parameters.empty()) { for (std::size_t i = 0; i < ctor->parameters.size(); ++i) { if (i > 0) { snippet += ", "; } const auto& p = ctor->parameters[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, char quote_char) { std::string snippet; if (!has_open_quote) { snippet.push_back(quote_char); } snippet += class_name; snippet.push_back(quote_char); if (ctor && !ctor->parameters.empty()) { for (std::size_t i = 0; i < ctor->parameters.size(); ++i) { snippet += ", "; const auto& p = ctor->parameters[i]; snippet += "${" + std::to_string(i + 1) + ":" + p.name + "}"; } } snippet += "$0"; return snippet; } } std::string Resolve::ProvideResponse(const protocol::RequestMessage& request, ExecutionContext& execution_context) { if (!request.params.has_value()) { spdlog::warn("{}: Missing params in request", GetProviderName()); return BuildErrorResponseMessage(request, protocol::ErrorCodes::InvalidParams, "Missing params"); } protocol::CompletionItem item = transform::FromLSPAny.template operator()(request.params.value()); if (item.data && item.data->Is()) { const auto& obj = item.data->Get(); 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) { auto& hub = execution_context.GetManagerHub(); const language::symbol::SymbolTable* editing_table = hub.symbols().GetSymbolTable(*uri); auto workspace_tables = hub.symbols().GetWorkspaceSymbolTables(); auto system_tables = hub.symbols().GetSystemSymbolTables(); auto try_find = [&](const language::symbol::SymbolTable& table) -> std::optional { if (unit_name && !unit_name->empty()) { auto module = GetModuleName(table); if (module.empty() || !utils::IEquals(module, *unit_name)) { 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 t = try_find(*editing_table)) table_for_class = *t; } 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; } } } const language::symbol::Method* best_ctor = nullptr; if (table_for_class) { if (auto cls_sym = FindClassSymbol(*table_for_class, *class_name)) { auto ctors = CollectConstructors(*table_for_class, (*cls_sym)->id()); best_ctor = PickBestConstructor(ctors); if (!item.labelDetails) { item.labelDetails = protocol::CompletionItemLabelDetails{}; } item.labelDetails->detail = best_ctor ? BuildSignature(best_ctor->parameters, best_ctor->return_type) : ""; } } if (*ctx == "new") { item.insertText = BuildNewSnippet(*class_name, best_ctor); item.insertTextFormat = protocol::InsertTextFormat::Snippet; item.kind = protocol::CompletionItemKind::Constructor; } else if (*ctx == "createobject") { bool has_open_quote = GetBoolField(obj, "has_open_quote").value_or(false); char quote_char = '"'; if (auto quote = GetStringField(obj, "quote"); quote && !quote->empty()) { quote_char = (*quote)[0]; } item.insertText = BuildCreateObjectSnippet(*class_name, best_ctor, has_open_quote, quote_char); item.insertTextFormat = protocol::InsertTextFormat::Snippet; item.kind = protocol::CompletionItemKind::Constructor; } } } protocol::ResponseMessage response; response.id = request.id; response.result = transform::ToLSPAny(item); if (auto json = transform::Serialize(response)) return *json; return BuildErrorResponseMessage(request, protocol::ErrorCodes::InternalError, "Failed to serialize completion resolve response"); } }