module; #include export module lsp.provider.text_document.completion; import spdlog; import std; import lsp.protocol; import lsp.codec.facade; import lsp.provider.base.interface; import lsp.language.ast; import lsp.language.symbol; import lsp.language.semantic; import lsp.language.keyword; import lsp.utils.string; import lsp.utils.text_coordinates; namespace transform = lsp::codec; export namespace lsp::provider::text_document { class Completion : public AutoRegisterProvider { public: static constexpr std::string_view kMethod = "textDocument/completion"; static constexpr std::string_view kProviderName = "TextDocumentCompletion"; std::string ProvideResponse(const protocol::RequestMessage& request, ExecutionContext& execution_context) override; }; } namespace lsp::provider::text_document { namespace { enum class CompletionSource { kEditing, kWorkspace, kSystem, kKeyword }; struct CompletionContext { std::string uri; protocol::Position position{}; std::string line_content; std::string prefix; std::string class_name; bool is_new_context = false; bool is_createobject_context = false; bool is_unit_context = false; bool is_class_context = false; bool is_class_method_context = false; bool is_unit_member_context = false; bool is_object_member_context = false; bool is_unit_scoped_new = false; std::string unit_name; std::string member_prefix; std::string object_name; bool createobject_has_open_quote = false; char createobject_quote = '"'; }; struct SourcedCompletionItem { protocol::CompletionItem item; CompletionSource source; }; std::string SourceTag(CompletionSource source) { switch (source) { case CompletionSource::kEditing: return "[E]"; case CompletionSource::kWorkspace: return "[W]"; case CompletionSource::kSystem: return "[S]"; case CompletionSource::kKeyword: default: return "[K]"; } } protocol::CompletionItemKind ToCompletionItemKind(protocol::SymbolKind kind) { switch (kind) { case protocol::SymbolKind::Function: return protocol::CompletionItemKind::Function; case protocol::SymbolKind::Method: return protocol::CompletionItemKind::Method; case protocol::SymbolKind::Constructor: return protocol::CompletionItemKind::Constructor; case protocol::SymbolKind::Class: return protocol::CompletionItemKind::Class; case protocol::SymbolKind::Property: return protocol::CompletionItemKind::Property; case protocol::SymbolKind::Field: return protocol::CompletionItemKind::Field; case protocol::SymbolKind::Variable: case protocol::SymbolKind::TypeParameter: return protocol::CompletionItemKind::Variable; case protocol::SymbolKind::Constant: return protocol::CompletionItemKind::Constant; case protocol::SymbolKind::Module: return protocol::CompletionItemKind::Module; default: return protocol::CompletionItemKind::Text; } } 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; } bool IsNewContext(const std::string& line) { std::string line_lower = utils::ToLower(line); std::size_t new_pos = line_lower.rfind("new "); if (new_pos == std::string::npos) return false; std::string after_new = utils::Trim(line.substr(new_pos + 4)); return after_new.find('(') == std::string::npos; } struct CreateObjectContextInfo { std::string prefix; bool has_open_quote = false; char quote = '"'; }; bool IsCreateObjectContext(const std::string& line, CreateObjectContextInfo& out) { std::string line_lower = utils::ToLower(line); std::size_t pos = line_lower.rfind("createobject("); if (pos == std::string::npos) { return false; } std::string after = line.substr(pos + std::string("createobject(").size()); std::size_t i = 0; while (i < after.size() && std::isspace(static_cast(after[i]))) { ++i; } after = after.substr(i); if (after.empty()) { out = CreateObjectContextInfo{}; return true; } if (after.find(',') != std::string::npos) { return false; } char first = after.front(); if (first == '"' || first == '\'') { out.has_open_quote = true; out.quote = first; out.prefix = after.substr(1); if (out.prefix.find(first) != std::string::npos) { return false; } return true; } out.prefix = after; out.has_open_quote = false; out.quote = '"'; return true; } bool IsClassMethodContext(const std::string& line, std::string& out_class_name, std::string& out_prefix) { std::string line_lower = utils::ToLower(line); std::size_t class_pos = line_lower.rfind("class("); if (class_pos == std::string::npos) return false; std::string after_class = line.substr(class_pos + 6); std::size_t close_paren = after_class.find(')'); if (close_paren == std::string::npos) return false; std::size_t dot_pos = after_class.find('.', close_paren); if (dot_pos == std::string::npos) return false; out_class_name = utils::Trim(after_class.substr(0, close_paren)); if (out_class_name.empty()) return false; out_prefix = utils::Trim(after_class.substr(dot_pos + 1)); return true; } bool IsUnitContext(const std::string& line) { std::string line_lower = utils::ToLower(line); std::size_t unit_pos = line_lower.rfind("unit("); if (unit_pos == std::string::npos) return false; std::string after = line.substr(unit_pos + 5); return after.find(')') == std::string::npos; } bool IsClassContext(const std::string& line) { std::string line_lower = utils::ToLower(line); std::size_t class_pos = line_lower.rfind("class("); if (class_pos == std::string::npos) return false; std::string after = line.substr(class_pos + 6); return after.find(')') == std::string::npos; } std::string ExtractPrefixByScan(std::size_t cursor_pos, std::size_t line_start, const std::string& content) { std::size_t prefix_start = cursor_pos; while (prefix_start > line_start) { char ch = content[prefix_start - 1]; if (!std::isalnum(static_cast(ch)) && ch != '_') break; --prefix_start; } return content.substr(prefix_start, cursor_pos - prefix_start); } CompletionContext AnalyzeContext(const protocol::CompletionParams& params, const std::optional& document_content) { CompletionContext result; result.uri = params.textDocument.uri; result.position = params.position; if (!document_content) return result; const std::string& content = *document_content; std::size_t line_start = 0; std::size_t current_line = 0; for (std::size_t i = 0; i < content.length(); ++i) { if (current_line == result.position.line) { line_start = i; break; } if (content[i] == '\n') ++current_line; } std::size_t cursor_pos = std::min(line_start + result.position.character, content.length()); result.line_content = content.substr(line_start, cursor_pos - line_start); std::string class_name; std::string method_prefix; result.is_class_method_context = IsClassMethodContext(result.line_content, class_name, method_prefix); if (result.is_class_method_context) { result.class_name = class_name; result.prefix = method_prefix; } else { CreateObjectContextInfo create_info; result.is_createobject_context = IsCreateObjectContext(result.line_content, create_info); if (result.is_createobject_context) { result.prefix = utils::Trim(create_info.prefix); result.createobject_has_open_quote = create_info.has_open_quote; result.createobject_quote = create_info.quote; } else { result.is_new_context = IsNewContext(result.line_content); result.is_unit_context = IsUnitContext(result.line_content); result.is_class_context = IsClassContext(result.line_content); if (result.is_new_context) result.prefix = utils::Trim(result.line_content.substr(result.line_content.rfind("new ") + 4)); else if (result.is_unit_context) result.prefix = utils::Trim(result.line_content.substr(result.line_content.rfind("unit(") + 5)); else if (result.is_class_context) result.prefix = utils::Trim(result.line_content.substr(result.line_content.rfind("class(") + 6)); else result.prefix = ExtractPrefixByScan(cursor_pos, line_start, content); } } return result; } 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::vector CollectVisibleUnits(const language::symbol::SymbolTable* table, const protocol::Position& position, const std::optional& content_opt) { if (!table) return {}; std::unordered_set units; auto add_imports = [&units](const auto& imports) { for (const auto& imp : imports) units.insert(utils::ToLower(imp.unit_name)); }; // Always include current module name std::string module_name = GetModuleName(*table); if (!module_name.empty()) units.insert(utils::ToLower(module_name)); // Gather imports from current symbol scope if (content_opt) { std::uint32_t offset = utils::text_coordinates::ToOffset(position, *content_opt); language::ast::Location loc{}; loc.start_line = loc.end_line = position.line; loc.start_column = loc.end_column = position.character; loc.start_offset = loc.end_offset = offset; if (auto id_opt = table->FindSymbolAt(loc)) { if (const auto* sym = table->definition(*id_opt)) { if (const auto* func = sym->As()) add_imports(func->imports); else if (const auto* method = sym->As()) add_imports(method->imports); else if (const auto* cls = sym->As()) add_imports(cls->imports); else if (const auto* unit = sym->As()) { add_imports(unit->interface_imports); add_imports(unit->implementation_imports); } } } } // Unit level imports for (const auto& wrapper : table->all_definitions()) { const auto& sym = wrapper.get(); if (sym.kind() == protocol::SymbolKind::Module) { const auto* unit = sym.As(); if (unit) { add_imports(unit->interface_imports); add_imports(unit->implementation_imports); } } } std::vector result; result.reserve(units.size()); for (const auto& name : units) result.push_back(name); return result; } bool IsUnitVisible(const std::unordered_set& visible_units, const std::string& unit_name) { return visible_units.find(utils::ToLower(unit_name)) != visible_units.end(); } void AppendKeywordItems(const std::string& prefix, std::vector& out) { auto keywords = language::keyword::Repo::Instance().FindByPrefix(utils::ToLower(prefix)); for (const auto& keyword : keywords) { protocol::CompletionItem item; item.label = keyword.keyword; item.kind = protocol::CompletionItemKind::Keyword; item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = "", .description = SourceTag(CompletionSource::kKeyword) }; out.push_back({ std::move(item), CompletionSource::kKeyword }); } } void AppendFunctions(const language::symbol::SymbolTable& table, const std::string& prefix, CompletionSource source, std::vector& out) { for (const auto& wrapper : table.all_definitions()) { const auto& symbol = wrapper.get(); if (symbol.kind() != protocol::SymbolKind::Function) continue; if (!prefix.empty() && !utils::IStartsWith(symbol.name(), prefix)) continue; const auto* func = symbol.As(); protocol::CompletionItem item; item.label = symbol.name(); item.kind = ToCompletionItemKind(symbol.kind()); if (func) { auto detail = BuildSignature(func->parameters, func->return_type); item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = detail, .description = SourceTag(source) }; } out.push_back({ std::move(item), source }); } } void AppendUnitMembers(const language::symbol::SymbolTable& table, const std::string& unit_name, const std::string& member_prefix, CompletionSource source, std::vector& out) { auto module_name = GetModuleName(table); if (module_name.empty() || !utils::IEquals(module_name, unit_name)) return; for (const auto& wrapper : table.all_definitions()) { const auto& symbol = wrapper.get(); if (symbol.kind() != protocol::SymbolKind::Function && symbol.kind() != protocol::SymbolKind::Variable && symbol.kind() != protocol::SymbolKind::Constant && symbol.kind() != protocol::SymbolKind::Class) { continue; } if (!member_prefix.empty() && !utils::IStartsWith(symbol.name(), member_prefix)) continue; protocol::CompletionItem item; item.label = symbol.name(); item.kind = ToCompletionItemKind(symbol.kind()); if (const auto* func = symbol.As()) { auto detail = BuildSignature(func->parameters, func->return_type); item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = detail, .description = SourceTag(source) }; } else { item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = "", .description = SourceTag(source) }; } out.push_back({ std::move(item), source }); } } bool HasStaticMethod(const language::symbol::Class& cls, const language::symbol::SymbolTable& table) { const auto& scopes = table.scopes().all_scopes(); for (const auto& [_, scope] : scopes) { if (scope.kind != language::symbol::ScopeKind::kClass || !scope.owner || *scope.owner != cls.id) { continue; } 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->is_static) return true; } } } return false; } 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; } void AppendClasses(const language::symbol::SymbolTable& table, const std::string& prefix, CompletionSource source, std::vector& out, bool mark_new_context, const std::string& unit_filter = "") { auto module_name = GetModuleName(table); if (!unit_filter.empty()) { if (module_name.empty() || !utils::IEquals(module_name, unit_filter)) return; } for (const auto& wrapper : table.all_definitions()) { const auto& symbol = wrapper.get(); if (symbol.kind() != protocol::SymbolKind::Class) continue; if (!prefix.empty() && !utils::IStartsWith(symbol.name(), prefix)) continue; const auto* cls = symbol.As(); if (!cls) continue; if (!mark_new_context && !HasStaticMethod(*cls, table)) continue; protocol::CompletionItem item; item.label = symbol.name(); item.kind = mark_new_context ? protocol::CompletionItemKind::Constructor : ToCompletionItemKind(symbol.kind()); item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = "", .description = SourceTag(source) }; protocol::LSPObject data; data["ctx"] = mark_new_context ? "new" : "class"; data["class"] = symbol.name(); data["unit"] = module_name; item.data = std::move(data); out.push_back({ std::move(item), source }); } } void AppendUnits(const language::symbol::SymbolTable& table, const std::string& prefix, CompletionSource source, std::vector& out, const std::unordered_set* allowed_units = nullptr) { auto module_name = GetModuleName(table); if (!module_name.empty() && allowed_units && !IsUnitVisible(*allowed_units, module_name)) return; for (const auto& wrapper : table.all_definitions()) { const auto& symbol = wrapper.get(); if (symbol.kind() != protocol::SymbolKind::Module) continue; if (!prefix.empty() && !utils::IStartsWith(symbol.name(), prefix)) continue; protocol::CompletionItem item; item.label = symbol.name(); item.kind = ToCompletionItemKind(symbol.kind()); item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = "", .description = SourceTag(source) }; item.data = protocol::LSPAny("unit_context"); out.push_back({ std::move(item), source }); } } 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; } struct DotAccessContext { bool is_unit_call = false; std::string base_name; std::string member_prefix; }; struct QualifiedPrefix { std::string unit_name; std::string member_prefix; }; std::optional ParseUnitQualifiedPrefix(const std::string& text) { std::string trimmed = utils::Trim(text); if (trimmed.empty()) { return std::nullopt; } std::string lower = utils::ToLower(trimmed); // unit(UnitA).Prefix if (lower.starts_with("unit(")) { std::string after_unit = trimmed.substr(5); std::size_t close_paren = after_unit.find(')'); if (close_paren == std::string::npos) { return std::nullopt; } std::string unit_name = utils::Trim(after_unit.substr(0, close_paren)); std::size_t dot_pos = after_unit.find('.', close_paren); std::string member_prefix; if (dot_pos != std::string::npos && dot_pos + 1 <= after_unit.size()) { member_prefix = utils::Trim(after_unit.substr(dot_pos + 1)); } if (unit_name.empty()) { return std::nullopt; } return QualifiedPrefix{ .unit_name = std::move(unit_name), .member_prefix = std::move(member_prefix), }; } // UnitA.Prefix std::size_t dot_pos = trimmed.find_last_of('.'); if (dot_pos != std::string::npos) { std::string unit_name = utils::Trim(trimmed.substr(0, dot_pos)); std::string member_prefix; if (dot_pos + 1 <= trimmed.size()) { member_prefix = utils::Trim(trimmed.substr(dot_pos + 1)); } if (unit_name.empty()) { return std::nullopt; } return QualifiedPrefix{ .unit_name = std::move(unit_name), .member_prefix = std::move(member_prefix), }; } return std::nullopt; } std::optional DetectDotAccessContext(const std::string& line) { // unit(xxx).prefix { std::string line_lower = utils::ToLower(line); std::size_t unit_pos = line_lower.rfind("unit("); if (unit_pos != std::string::npos) { std::string after_unit = line.substr(unit_pos + 5); std::size_t close_paren = after_unit.find(')'); if (close_paren == std::string::npos) { return std::nullopt; } std::size_t dot_pos = after_unit.find('.', close_paren); if (dot_pos != std::string::npos && dot_pos > close_paren) { DotAccessContext ctx; ctx.is_unit_call = true; ctx.base_name = utils::Trim(after_unit.substr(0, close_paren)); ctx.member_prefix = utils::Trim(after_unit.substr(dot_pos + 1)); if (!ctx.base_name.empty()) { return ctx; } } } } // ident.prefix std::size_t dot_pos = line.rfind('.'); if (dot_pos == std::string::npos) { return std::nullopt; } std::size_t start = dot_pos; while (start > 0) { char ch = line[start - 1]; if (!std::isalnum(static_cast(ch)) && ch != '_') break; --start; } std::string base = utils::Trim(line.substr(start, dot_pos - start)); if (base.empty()) { return std::nullopt; } DotAccessContext ctx; ctx.base_name = std::move(base); ctx.member_prefix = utils::Trim(line.substr(dot_pos + 1)); return ctx; } std::vector CollectUnitSearchOrder(const language::symbol::SymbolTable& table, const protocol::Position& position, const std::string& content) { std::vector imports; std::uint32_t offset = utils::text_coordinates::ToOffset(position, content); language::ast::Location loc{}; loc.start_line = loc.end_line = position.line; loc.start_column = loc.end_column = position.character; loc.start_offset = loc.end_offset = offset; auto add_imports = [&imports](const auto& vec) { imports.reserve(imports.size() + vec.size()); for (const auto& imp : vec) { imports.push_back(imp.unit_name); } }; if (auto id_opt = table.FindSymbolAt(loc)) { if (const auto* sym = table.definition(*id_opt)) { if (const auto* func = sym->As()) add_imports(func->imports); else if (const auto* method = sym->As()) add_imports(method->imports); else if (const auto* cls = sym->As()) add_imports(cls->imports); else if (const auto* unit = sym->As()) { add_imports(unit->interface_imports); add_imports(unit->implementation_imports); } } } if (imports.empty()) { for (const auto& wrapper : table.all_definitions()) { const auto& sym = wrapper.get(); if (sym.kind() == protocol::SymbolKind::Module) { if (const auto* unit = sym.As()) { add_imports(unit->interface_imports); add_imports(unit->implementation_imports); } break; } } } std::vector order; std::unordered_set seen; std::string module_name = GetModuleName(table); if (!module_name.empty()) { order.push_back(module_name); seen.insert(module_name); } for (auto it = imports.rbegin(); it != imports.rend(); ++it) { if (it->empty()) { continue; } if (seen.find(*it) != seen.end()) { continue; } order.push_back(*it); seen.insert(*it); } return order; } void AppendClassMethods(const language::symbol::SymbolTable& table, const language::symbol::Class& cls, const std::string& prefix, CompletionSource source, std::vector& out) { auto scope_id = FindScopeOwnedBy(table, language::symbol::ScopeKind::kClass, cls.id); if (!scope_id) { return; } const auto* scope = table.scopes().scope(*scope_id); if (!scope) { return; } for (const auto& [_, ids] : scope->symbols) { for (auto id : ids) { const auto* member_symbol = table.definition(id); if (!member_symbol || member_symbol->kind() != protocol::SymbolKind::Method) continue; const auto* method = member_symbol->As(); if (!method) continue; // 仅补全类方法(静态) if (!method->is_static) continue; if (!prefix.empty() && !utils::IStartsWith(method->name, prefix)) continue; protocol::CompletionItem item; item.label = method->name; item.kind = protocol::CompletionItemKind::Method; item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = BuildSignature(method->parameters, method->return_type), .description = SourceTag(source) }; out.push_back({ std::move(item), source }); } } } std::optional ResolveTypeNameInScope( const language::symbol::SymbolTable& table, const language::semantic::SemanticModel* semantic, const protocol::Position& position, const std::string& content, const std::string& name) { if (name.empty()) { return std::nullopt; } std::uint32_t offset = utils::text_coordinates::ToOffset(position, content); language::ast::Location loc{}; loc.start_line = loc.end_line = position.line; loc.start_column = loc.end_column = position.character; loc.start_offset = loc.end_offset = offset; auto scope_id = table.scopes().FindScopeAt(loc); if (!scope_id) { return std::nullopt; } auto symbol_id = table.scopes().FindSymbolInScopeChain(*scope_id, name); if (!symbol_id) { return std::nullopt; } const auto* sym = table.definition(*symbol_id); if (!sym) { return std::nullopt; } if (semantic) { auto type = semantic->type_system().GetSymbolType(*symbol_id); if (type && type->kind() == language::semantic::TypeKind::kClass) { if (const auto* class_type = type->As()) { if (const auto* class_symbol = table.definition(class_type->class_id())) { return class_symbol->name(); } } } } if (const auto* var = sym->As()) { return var->type; } if (const auto* field = sym->As()) { return field->type; } return std::nullopt; } void AppendInstanceMembers(const language::symbol::SymbolTable& table, language::symbol::SymbolId class_id, const std::string& prefix, CompletionSource source, std::vector& out) { auto scope_id = FindScopeOwnedBy(table, language::symbol::ScopeKind::kClass, class_id); if (!scope_id) { return; } const auto* scope = table.scopes().scope(*scope_id); if (!scope) { return; } for (const auto& [_, ids] : scope->symbols) { for (auto id : ids) { const auto* member = table.definition(id); if (!member) { continue; } if (!prefix.empty() && !utils::IStartsWith(member->name(), prefix)) { continue; } if (member->kind() == protocol::SymbolKind::Method) { const auto* method = member->As(); if (!method || method->is_static) { continue; } if (method->method_kind == language::ast::MethodKind::kConstructor || method->method_kind == language::ast::MethodKind::kDestructor) { continue; } protocol::CompletionItem item; item.label = method->name; item.kind = protocol::CompletionItemKind::Method; item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = BuildSignature(method->parameters, method->return_type), .description = SourceTag(source) }; out.push_back({ std::move(item), source }); continue; } if (member->kind() == protocol::SymbolKind::Property) { protocol::CompletionItem item; item.label = member->name(); item.kind = protocol::CompletionItemKind::Property; item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = "", .description = SourceTag(source) }; out.push_back({ std::move(item), source }); continue; } if (member->kind() == protocol::SymbolKind::Field) { const auto* field = member->As(); if (!field || field->is_static) { continue; } protocol::CompletionItem item; item.label = field->name; item.kind = protocol::CompletionItemKind::Field; item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = "", .description = SourceTag(source) }; out.push_back({ std::move(item), source }); continue; } } } } void AppendCreateObjectClasses(const language::symbol::SymbolTable& table, const std::string& prefix, CompletionSource source, std::vector& out, bool has_open_quote, char quote_char, const std::string& unit_filter = "") { auto module_name = GetModuleName(table); if (!unit_filter.empty()) { if (module_name.empty() || !utils::IEquals(module_name, unit_filter)) return; } for (const auto& wrapper : table.all_definitions()) { const auto& symbol = wrapper.get(); if (symbol.kind() != protocol::SymbolKind::Class) continue; if (!prefix.empty() && !utils::IStartsWith(symbol.name(), prefix)) continue; protocol::CompletionItem item; item.label = symbol.name(); item.kind = protocol::CompletionItemKind::Constructor; item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = "", .description = SourceTag(source) }; protocol::LSPObject data; data["ctx"] = "createobject"; data["class"] = symbol.name(); data["unit"] = module_name; data["has_open_quote"] = has_open_quote; data["quote"] = std::string(1, quote_char); item.data = std::move(data); out.push_back({ std::move(item), source }); } } std::vector FilterAndSort(const std::vector& items, const std::string& prefix) { std::vector unique_items; std::set seen_labels; for (const auto& item : items) { if (seen_labels.count(item.item.label) > 0) continue; seen_labels.insert(item.item.label); if (!prefix.empty() && !utils::IStartsWith(item.item.label, prefix)) continue; unique_items.push_back(&item); } std::sort( unique_items.begin(), unique_items.end(), [&prefix](const SourcedCompletionItem* a, const SourcedCompletionItem* b) { std::string a_lower = utils::ToLower(a->item.label); std::string b_lower = utils::ToLower(b->item.label); std::string prefix_lower = utils::ToLower(prefix); bool a_exact = (a_lower == prefix_lower); bool b_exact = (b_lower == prefix_lower); if (a_exact != b_exact) return a_exact; if (!prefix.empty()) { int a_score = static_cast(prefix.length()) * 100 / static_cast(a->item.label.length()); int b_score = static_cast(prefix.length()) * 100 / static_cast(b->item.label.length()); if (a_score != b_score) return a_score > b_score; } if (a->source != b->source) { auto priority = [](CompletionSource src) -> int { switch (src) { case CompletionSource::kEditing: return 0; case CompletionSource::kWorkspace: return 1; case CompletionSource::kSystem: return 2; case CompletionSource::kKeyword: default: return 3; } }; return priority(a->source) < priority(b->source); } return a_lower < b_lower; }); std::vector result; result.reserve(unique_items.size()); for (const auto* item : unique_items) result.push_back(*item); return result; } protocol::CompletionList BuildCompletionList(const protocol::CompletionParams& params, ExecutionContext& execution_context) { protocol::CompletionList list; list.isIncomplete = false; auto& manager_hub = execution_context.GetManagerHub(); const auto content = manager_hub.documents().GetContent(params.textDocument.uri); auto context = AnalyzeContext(params, content); std::vector collected; // 当前文档符号表 const auto* editing_table = manager_hub.symbols().GetSymbolTable(params.textDocument.uri); const auto* editing_semantic = manager_hub.symbols().GetSemanticModel(params.textDocument.uri); // 工作区/系统符号表 auto workspace_tables = manager_hub.symbols().GetWorkspaceSymbolTables(); auto system_tables = manager_hub.symbols().GetSystemSymbolTables(); std::unordered_set visible_units_set; if (editing_table) { auto visible_units = CollectVisibleUnits(editing_table, params.position, content); for (auto& u : visible_units) visible_units_set.insert(u); } // Member access context (unit/obj) if (!context.is_class_method_context && !context.is_new_context && !context.is_unit_context && !context.is_class_context && !context.is_createobject_context) { if (auto dot_ctx = DetectDotAccessContext(context.line_content)) { context.prefix = dot_ctx->member_prefix; context.member_prefix = dot_ctx->member_prefix; if (dot_ctx->is_unit_call) { if (IsUnitVisible(visible_units_set, dot_ctx->base_name)) { context.is_unit_member_context = true; context.unit_name = dot_ctx->base_name; } } else { if (IsUnitVisible(visible_units_set, dot_ctx->base_name)) { context.is_unit_member_context = true; context.unit_name = dot_ctx->base_name; } else { context.is_object_member_context = true; context.object_name = dot_ctx->base_name; } } } } // 处理 new unit(...) 或 alias.class if (context.is_new_context && !context.prefix.empty()) { std::string lower_prefix = utils::ToLower(context.prefix); if (lower_prefix.starts_with("unit(")) { auto after = context.prefix.substr(5); auto close = after.find(')'); auto dot = after.find('.'); std::size_t name_end = std::min( close == std::string::npos ? after.size() : close, dot == std::string::npos ? after.size() : dot); context.unit_name = utils::Trim(after.substr(0, name_end)); if (!context.unit_name.empty()) { context.is_unit_scoped_new = true; if (dot != std::string::npos && dot + 1 < after.size()) context.prefix = utils::Trim(after.substr(dot + 1)); else context.prefix = ""; } } else { auto dot = context.prefix.find_last_of('.'); if (dot != std::string::npos) { auto alias = utils::Trim(context.prefix.substr(0, dot)); if (!alias.empty()) { context.is_unit_scoped_new = true; context.unit_name = alias; context.prefix = utils::Trim(context.prefix.substr(dot + 1)); } } } } if (context.is_class_method_context) { bool found = false; if (editing_table) { if (auto cls_opt = FindClassSymbol(*editing_table, context.class_name)) { const auto* cls = cls_opt.value()->As(); if (cls) { AppendClassMethods(*editing_table, *cls, context.prefix, CompletionSource::kEditing, collected); found = true; } } } if (!found) { for (const auto* table : workspace_tables) { if (auto cls_opt = FindClassSymbol(*table, context.class_name)) { const auto* cls = cls_opt.value()->As(); if (cls) AppendClassMethods(*table, *cls, context.prefix, CompletionSource::kWorkspace, collected); found = true; break; } } } if (!found) { for (const auto* table : system_tables) { if (auto cls_opt = FindClassSymbol(*table, context.class_name)) { const auto* cls = cls_opt.value()->As(); if (cls) AppendClassMethods(*table, *cls, context.prefix, CompletionSource::kSystem, collected); break; } } } } else if (context.is_unit_member_context) { if (context.unit_name.empty() || !IsUnitVisible(visible_units_set, context.unit_name)) { // not visible -> no unit member suggestions } else { if (editing_table) AppendUnitMembers(*editing_table, context.unit_name, context.member_prefix, CompletionSource::kEditing, collected); for (const auto* table : workspace_tables) AppendUnitMembers(*table, context.unit_name, context.member_prefix, CompletionSource::kWorkspace, collected); for (const auto* table : system_tables) AppendUnitMembers(*table, context.unit_name, context.member_prefix, CompletionSource::kSystem, collected); } } else if (context.is_object_member_context) { if (editing_table && content) { if (auto type_name = ResolveTypeNameInScope(*editing_table, editing_semantic, params.position, *content, context.object_name)) { std::string type_text = utils::Trim(*type_name); std::string class_name = type_text; std::vector unit_candidates; if (auto qualified = ParseUnitQualifiedPrefix(type_text)) { if (!qualified->unit_name.empty() && !qualified->member_prefix.empty()) { unit_candidates.push_back(qualified->unit_name); class_name = qualified->member_prefix; } } if (!class_name.empty()) { if (unit_candidates.empty()) { unit_candidates = CollectUnitSearchOrder(*editing_table, params.position, *content); } bool found_class = false; for (const auto& unit_name : unit_candidates) { if (found_class) break; auto try_table = [&](const language::symbol::SymbolTable& table, CompletionSource source) { if (!utils::IEquals(GetModuleName(table), unit_name)) { return; } auto cls_opt = FindClassSymbol(table, class_name); if (!cls_opt) { return; } AppendInstanceMembers(table, (*cls_opt)->id(), context.member_prefix, source, collected); found_class = true; }; try_table(*editing_table, CompletionSource::kEditing); for (const auto* table : workspace_tables) try_table(*table, CompletionSource::kWorkspace); for (const auto* table : system_tables) try_table(*table, CompletionSource::kSystem); } (void)found_class; } } } } else if (context.is_new_context) { if (context.is_unit_scoped_new && !context.unit_name.empty()) { if (editing_table) AppendClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, true, context.unit_name); for (const auto* table : workspace_tables) AppendClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, true, context.unit_name); for (const auto* table : system_tables) AppendClasses(*table, context.prefix, CompletionSource::kSystem, collected, true, context.unit_name); } else { if (editing_table && content) { auto unit_order = CollectUnitSearchOrder(*editing_table, params.position, *content); if (!unit_order.empty()) { for (const auto& unit_name : unit_order) { AppendClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, true, unit_name); for (const auto* table : workspace_tables) AppendClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, true, unit_name); for (const auto* table : system_tables) AppendClasses(*table, context.prefix, CompletionSource::kSystem, collected, true, unit_name); } } else { AppendClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, true); for (const auto* table : workspace_tables) AppendClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, true); for (const auto* table : system_tables) AppendClasses(*table, context.prefix, CompletionSource::kSystem, collected, true); } } else { if (editing_table) AppendClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, true); for (const auto* table : workspace_tables) AppendClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, true); for (const auto* table : system_tables) AppendClasses(*table, context.prefix, CompletionSource::kSystem, collected, true); } } } else if (context.is_createobject_context) { if (auto qualified = ParseUnitQualifiedPrefix(context.prefix)) { if (editing_table) AppendCreateObjectClasses(*editing_table, qualified->member_prefix, CompletionSource::kEditing, collected, context.createobject_has_open_quote, context.createobject_quote, qualified->unit_name); for (const auto* table : workspace_tables) AppendCreateObjectClasses(*table, qualified->member_prefix, CompletionSource::kWorkspace, collected, context.createobject_has_open_quote, context.createobject_quote, qualified->unit_name); for (const auto* table : system_tables) AppendCreateObjectClasses(*table, qualified->member_prefix, CompletionSource::kSystem, collected, context.createobject_has_open_quote, context.createobject_quote, qualified->unit_name); } else if (editing_table && content) { auto unit_order = CollectUnitSearchOrder(*editing_table, params.position, *content); if (!unit_order.empty()) { for (const auto& unit_name : unit_order) { AppendCreateObjectClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, context.createobject_has_open_quote, context.createobject_quote, unit_name); for (const auto* table : workspace_tables) AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, context.createobject_has_open_quote, context.createobject_quote, unit_name); for (const auto* table : system_tables) AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kSystem, collected, context.createobject_has_open_quote, context.createobject_quote, unit_name); } } else { AppendCreateObjectClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, context.createobject_has_open_quote, context.createobject_quote); for (const auto* table : workspace_tables) AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, context.createobject_has_open_quote, context.createobject_quote); for (const auto* table : system_tables) AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kSystem, collected, context.createobject_has_open_quote, context.createobject_quote); } } else { if (editing_table) AppendCreateObjectClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, context.createobject_has_open_quote, context.createobject_quote); for (const auto* table : workspace_tables) AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, context.createobject_has_open_quote, context.createobject_quote); for (const auto* table : system_tables) AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kSystem, collected, context.createobject_has_open_quote, context.createobject_quote); } } else if (context.is_unit_context) { if (!visible_units_set.empty()) { if (editing_table) AppendUnits(*editing_table, context.prefix, CompletionSource::kEditing, collected, &visible_units_set); for (const auto* table : workspace_tables) AppendUnits(*table, context.prefix, CompletionSource::kWorkspace, collected, &visible_units_set); for (const auto* table : system_tables) AppendUnits(*table, context.prefix, CompletionSource::kSystem, collected, &visible_units_set); } } else if (context.is_class_context) { if (editing_table) AppendClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, false); for (const auto* table : workspace_tables) AppendClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, false); for (const auto* table : system_tables) AppendClasses(*table, context.prefix, CompletionSource::kSystem, collected, false); } else { AppendKeywordItems(context.prefix, collected); if (editing_table) AppendFunctions(*editing_table, context.prefix, CompletionSource::kEditing, collected); for (const auto* table : workspace_tables) AppendFunctions(*table, context.prefix, CompletionSource::kWorkspace, collected); for (const auto* table : system_tables) AppendFunctions(*table, context.prefix, CompletionSource::kSystem, collected); } for (auto& entry : collected) { if (!entry.item.data) { continue; } if (!entry.item.data->Is()) { continue; } auto& obj = entry.item.data->Get(); auto ctx_it = obj.find("ctx"); if (ctx_it == obj.end()) { continue; } if (!ctx_it->second.Is()) { continue; } obj["uri"] = params.textDocument.uri; } std::string filter_prefix = context.prefix; if (context.is_createobject_context) { if (auto qualified = ParseUnitQualifiedPrefix(context.prefix)) { filter_prefix = qualified->member_prefix; } } auto filtered = FilterAndSort(collected, filter_prefix); list.items.reserve(filtered.size()); for (auto& item : filtered) list.items.push_back(std::move(item.item)); return list; } } std::string Completion::ProvideResponse(const protocol::RequestMessage& request, ExecutionContext& execution_context) { spdlog::debug("{}: Processing completion request", GetProviderName()); if (!request.params.has_value()) { spdlog::warn("{}: Missing params in request", GetProviderName()); return BuildErrorResponseMessage(request, protocol::ErrorCodes::InvalidParams, "Missing params"); } const auto params = transform::FromLSPAny.template operator()(request.params.value()); auto completion_list = BuildCompletionList(params, execution_context); protocol::ResponseMessage response; response.id = request.id; response.result = transform::ToLSPAny(completion_list); auto json = transform::Serialize(response); if (!json) return BuildErrorResponseMessage(request, protocol::ErrorCodes::InternalError, "Failed to serialize response"); return *json; } }