feat(lsp_completion): support unit-qualified constructors

- Support `new UnitA.ClassName` and `new unit(UnitA).ClassName` completion.
- Support `createobject("UnitA.ClassName"` and `createobject("unit(UnitA).ClassName"` completion.
- Apply `uses` ordering when resolving ambiguous ClassName (last wins).
- Improve `obj.` member completion using qualified type strings.
This commit is contained in:
csh
2025-12-14 10:33:12 +08:00
parent 3106ab9ce4
commit 171d5e2064
3 changed files with 538 additions and 35 deletions
@@ -610,6 +610,76 @@ namespace lsp::provider::text_document
std::string member_prefix;
};
struct QualifiedPrefix
{
std::string unit_name;
std::string member_prefix;
};
std::optional<QualifiedPrefix> 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<DotAccessContext> DetectDotAccessContext(const std::string& line)
{
// unit(xxx).prefix
@@ -667,6 +737,88 @@ namespace lsp::provider::text_document
return ctx;
}
std::vector<std::string> CollectUnitSearchOrder(const language::symbol::SymbolTable& table,
const protocol::Position& position,
const std::string& content)
{
std::vector<std::string> 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<language::symbol::Function>())
add_imports(func->imports);
else if (const auto* method = sym->As<language::symbol::Method>())
add_imports(method->imports);
else if (const auto* cls = sym->As<language::symbol::Class>())
add_imports(cls->imports);
else if (const auto* unit = sym->As<language::symbol::Unit>())
{
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<language::symbol::Unit>())
{
add_imports(unit->interface_imports);
add_imports(unit->implementation_imports);
}
break;
}
}
}
std::vector<std::string> order;
std::unordered_set<std::string, utils::IHasher, utils::IEqualTo> 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<SourcedCompletionItem>& out)
{
auto scope_id = FindScopeOwnedBy(table, language::symbol::ScopeKind::kClass, cls.id);
@@ -1044,7 +1196,7 @@ namespace lsp::provider::text_document
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() && IsUnitVisible(visible_units_set, context.unit_name))
if (!context.unit_name.empty())
{
context.is_unit_scoped_new = true;
if (dot != std::string::npos && dot + 1 < after.size())
@@ -1055,11 +1207,11 @@ namespace lsp::provider::text_document
}
else
{
auto dot = context.prefix.find('.');
auto dot = context.prefix.find_last_of('.');
if (dot != std::string::npos)
{
auto alias = utils::Trim(context.prefix.substr(0, dot));
if (!alias.empty() && IsUnitVisible(visible_units_set, alias))
if (!alias.empty())
{
context.is_unit_scoped_new = true;
context.unit_name = alias;
@@ -1136,25 +1288,57 @@ namespace lsp::provider::text_document
{
if (auto type_name = ResolveTypeNameInScope(*editing_table, editing_semantic, params.position, *content, context.object_name))
{
bool found_class = false;
std::string type_text = utils::Trim(*type_name);
std::string class_name = type_text;
std::vector<std::string> unit_candidates;
auto try_append = [&](const language::symbol::SymbolTable& table, CompletionSource source) {
auto cls_opt = FindClassSymbol(table, *type_name);
if (!cls_opt)
if (auto qualified = ParseUnitQualifiedPrefix(type_text))
{
if (!qualified->unit_name.empty() && !qualified->member_prefix.empty())
{
return;
unit_candidates.push_back(qualified->unit_name);
class_name = qualified->member_prefix;
}
AppendInstanceMembers(table, (*cls_opt)->id(), context.member_prefix, source, collected);
found_class = true;
};
}
try_append(*editing_table, CompletionSource::kEditing);
for (const auto* table : workspace_tables)
try_append(*table, CompletionSource::kWorkspace);
for (const auto* table : system_tables)
try_append(*table, CompletionSource::kSystem);
if (!class_name.empty())
{
if (unit_candidates.empty())
{
unit_candidates = CollectUnitSearchOrder(*editing_table, params.position, *content);
}
(void)found_class;
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;
}
}
}
}
@@ -1171,22 +1355,83 @@ namespace lsp::provider::text_document
}
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);
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 (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);
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)
{