✨ feat(lsp_completion): complete object creation and members
- Add class-name completion for `new` and `createobject("..."`.
- Generate constructor parameter snippets in completionItem/resolve.
- Support `obj.` instance member completion (methods/properties/fields).
- Infer `new`/`createobject` result types in SemanticModel TypeSystem.
This commit is contained in:
parent
476e83beb8
commit
3106ab9ce4
|
|
@ -1158,6 +1158,25 @@ namespace lsp::language::semantic
|
|||
{
|
||||
if (auto* ident = dynamic_cast<ast::Identifier*>(call->callee.get()))
|
||||
{
|
||||
if (utils::IEquals(ident->name, "createobject"))
|
||||
{
|
||||
if (!call->arguments.empty() && call->arguments.front().value)
|
||||
{
|
||||
if (auto* lit = dynamic_cast<ast::Literal*>(call->arguments.front().value.get()))
|
||||
{
|
||||
if (lit->literal_kind == ast::LiteralKind::kString)
|
||||
{
|
||||
auto class_id = ResolveClassSymbol(lit->value, lit->location);
|
||||
if (class_id)
|
||||
{
|
||||
return type_system.CreateClassType(*class_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return type_system.GetUnknownType();
|
||||
}
|
||||
|
||||
auto callee_id = ResolveIdentifier(ident->name, ident->location);
|
||||
if (callee_id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,9 +7,140 @@ module lsp.language.symbol:internal.builder;
|
|||
import :types;
|
||||
import lsp.language.ast;
|
||||
import lsp.protocol.types;
|
||||
import lsp.utils.string;
|
||||
|
||||
namespace lsp::language::symbol
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::optional<std::string> UnquoteStringLiteral(std::string value)
|
||||
{
|
||||
if (value.size() < 2)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
char first = value.front();
|
||||
char last = value.back();
|
||||
if ((first == '"' && last == '"') || (first == '\'' && last == '\''))
|
||||
{
|
||||
value.erase(value.begin());
|
||||
value.pop_back();
|
||||
return value;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> ExtractClassNameFromExpression(const ast::Expression* expr)
|
||||
{
|
||||
if (!expr)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (const auto* ident = dynamic_cast<const ast::Identifier*>(expr))
|
||||
{
|
||||
if (!ident->name.empty())
|
||||
{
|
||||
return ident->name;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (const auto* attr = dynamic_cast<const ast::AttributeExpression*>(expr))
|
||||
{
|
||||
// Handle `unit(x).ClassName` by taking last attribute segment.
|
||||
return ExtractClassNameFromExpression(attr->attribute.get());
|
||||
}
|
||||
|
||||
if (const auto* call = dynamic_cast<const ast::CallExpression*>(expr))
|
||||
{
|
||||
return ExtractClassNameFromExpression(call->callee.get());
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> InferTypeFromExpression(const ast::Expression* expr)
|
||||
{
|
||||
if (!expr)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (const auto* new_expr = dynamic_cast<const ast::NewExpression*>(expr))
|
||||
{
|
||||
return ExtractClassNameFromExpression(new_expr->target.get());
|
||||
}
|
||||
|
||||
if (const auto* call = dynamic_cast<const ast::CallExpression*>(expr))
|
||||
{
|
||||
const auto* callee_ident = dynamic_cast<const ast::Identifier*>(call->callee.get());
|
||||
if (!callee_ident)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!utils::IEquals(utils::ToLower(callee_ident->name), "createobject"))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (call->arguments.empty() || !call->arguments[0].value)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto* literal = dynamic_cast<const ast::Literal*>(call->arguments[0].value.get());
|
||||
if (!literal || literal->literal_kind != ast::LiteralKind::kString)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (auto unquoted = UnquoteStringLiteral(literal->value))
|
||||
{
|
||||
if (!unquoted->empty())
|
||||
{
|
||||
return *unquoted;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void MaybeUpdateSymbolType(SymbolTable& table, ScopeId scope_id, const std::string& name, const std::string& type_name)
|
||||
{
|
||||
if (name.empty() || type_name.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto id_opt = table.scopes().FindSymbolInScopeChain(scope_id, name);
|
||||
if (!id_opt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Symbol* symbol = const_cast<Symbol*>(table.definition(*id_opt));
|
||||
if (!symbol)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto* var = symbol->As<Variable>())
|
||||
{
|
||||
var->type = type_name;
|
||||
}
|
||||
else if (auto* field = symbol->As<Field>())
|
||||
{
|
||||
field->type = type_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Builder::Builder(SymbolTable& table) : table_(table),
|
||||
current_scope_id_(kInvalidScopeId),
|
||||
in_interface_section_(false),
|
||||
|
|
@ -544,7 +675,13 @@ namespace lsp::language::symbol
|
|||
|
||||
void Builder::VisitVarDeclaration(ast::VarDeclaration& node)
|
||||
{
|
||||
CreateSymbol(node.name, protocol::SymbolKind::Variable, node.location, ExtractTypeName(node.type));
|
||||
auto type_hint = ExtractTypeName(node.type);
|
||||
if (!type_hint && node.initializer && *node.initializer)
|
||||
{
|
||||
type_hint = InferTypeFromExpression(node.initializer->get());
|
||||
}
|
||||
|
||||
CreateSymbol(node.name, protocol::SymbolKind::Variable, node.location, type_hint);
|
||||
|
||||
if (node.initializer && *node.initializer)
|
||||
{
|
||||
|
|
@ -702,11 +839,40 @@ namespace lsp::language::symbol
|
|||
|
||||
void Builder::VisitAssignmentExpression(ast::AssignmentExpression& node)
|
||||
{
|
||||
std::optional<std::string> inferred_type;
|
||||
if (node.right)
|
||||
{
|
||||
inferred_type = InferTypeFromExpression(node.right.get());
|
||||
}
|
||||
|
||||
if (const auto* ident = std::get_if<std::unique_ptr<ast::Identifier>>(&node.left))
|
||||
{
|
||||
if (*ident)
|
||||
{
|
||||
auto existing = table_.scopes().FindSymbolInScopeChain(current_scope_id_, (*ident)->name);
|
||||
if (!existing)
|
||||
{
|
||||
CreateSymbol((*ident)->name, protocol::SymbolKind::Variable, (*ident)->location, inferred_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProcessLValue(node.left, true);
|
||||
|
||||
if (node.right)
|
||||
{
|
||||
VisitExpression(*node.right);
|
||||
|
||||
if (inferred_type)
|
||||
{
|
||||
if (const auto* ident = std::get_if<std::unique_ptr<ast::Identifier>>(&node.left))
|
||||
{
|
||||
if (*ident)
|
||||
{
|
||||
MaybeUpdateSymbolType(table_, current_scope_id_, (*ident)->name, *inferred_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
module;
|
||||
|
||||
|
||||
export module lsp.manager.symbol;
|
||||
import tree_sitter;
|
||||
import spdlog;
|
||||
|
|
@ -497,6 +496,11 @@ namespace lsp::manager
|
|||
|
||||
analysis.semantic_model = std::make_unique<language::semantic::SemanticModel>(*analysis.symbol_table);
|
||||
|
||||
{
|
||||
language::semantic::Analyzer analyzer(*analysis.symbol_table, *analysis.semantic_model);
|
||||
analyzer.Analyze(*analysis.ast);
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
editing_symbols_[event.item.uri] = std::move(analysis);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
module;
|
||||
|
||||
|
||||
export module lsp.provider.completion_item.resolve;
|
||||
import spdlog;
|
||||
|
||||
|
|
@ -9,6 +8,9 @@ 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;
|
||||
|
||||
|
|
@ -27,6 +29,214 @@ export namespace lsp::provider::completion_item
|
|||
|
||||
namespace lsp::provider::completion_item
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::optional<std::string> GetStringField(const protocol::LSPObject& obj, const std::string& key)
|
||||
{
|
||||
auto it = obj.find(key);
|
||||
if (it == obj.end() || !it->second.Is<protocol::string>())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto& s = it->second.Get<protocol::string>();
|
||||
return s;
|
||||
}
|
||||
|
||||
std::optional<bool> GetBoolField(const protocol::LSPObject& obj, const std::string& key)
|
||||
{
|
||||
auto it = obj.find(key);
|
||||
if (it == obj.end() || !it->second.Is<protocol::boolean>())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
return it->second.Get<protocol::boolean>();
|
||||
}
|
||||
|
||||
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<const language::symbol::Symbol*> 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<language::symbol::ScopeId> 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<const language::symbol::Method*> CollectConstructors(
|
||||
const language::symbol::SymbolTable& table,
|
||||
language::symbol::SymbolId class_id)
|
||||
{
|
||||
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* member = table.definition(id);
|
||||
if (!member || member->kind() != protocol::SymbolKind::Method)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* method = member->As<language::symbol::Method>();
|
||||
if (!method || method->method_kind != language::ast::MethodKind::kConstructor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_back(method);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const language::symbol::Method* PickBestConstructor(const std::vector<const language::symbol::Method*>& ctors)
|
||||
{
|
||||
const language::symbol::Method* 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* 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<language::symbol::Parameter>& params, const std::optional<std::string>& 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::GetMethod() const
|
||||
{
|
||||
return "completionItem/resolve";
|
||||
|
|
@ -40,8 +250,6 @@ namespace lsp::provider::completion_item
|
|||
std::string Resolve::ProvideResponse(const protocol::RequestMessage& request,
|
||||
ExecutionContext& execution_context)
|
||||
{
|
||||
static_cast<void>(execution_context);
|
||||
|
||||
if (!request.params.has_value())
|
||||
{
|
||||
spdlog::warn("{}: Missing params in request", GetProviderName());
|
||||
|
|
@ -52,7 +260,109 @@ namespace lsp::provider::completion_item
|
|||
|
||||
protocol::CompletionItem item = transform::FromLSPAny.template operator()<protocol::CompletionItem>(request.params.value());
|
||||
|
||||
// 暂未迁移 resolve 逻辑,保持原样返回
|
||||
if (item.data && item.data->Is<protocol::LSPObject>())
|
||||
{
|
||||
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)
|
||||
{
|
||||
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<const language::symbol::SymbolTable*> {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ 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;
|
||||
|
|
@ -49,13 +50,18 @@ namespace lsp::provider::text_document
|
|||
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
|
||||
|
|
@ -85,14 +91,17 @@ namespace lsp::provider::text_document
|
|||
switch (kind)
|
||||
{
|
||||
case protocol::SymbolKind::Function:
|
||||
case protocol::SymbolKind::Method:
|
||||
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;
|
||||
|
|
@ -133,6 +142,60 @@ namespace lsp::provider::text_document
|
|||
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<unsigned char>(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);
|
||||
|
|
@ -179,57 +242,6 @@ namespace lsp::provider::text_document
|
|||
return after.find(')') == std::string::npos;
|
||||
}
|
||||
|
||||
std::optional<CompletionContext> DetectUnitMemberContext(const std::string& line)
|
||||
{
|
||||
CompletionContext ctx;
|
||||
// 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(')');
|
||||
std::size_t dot_pos = after_unit.find('.');
|
||||
if (dot_pos != std::string::npos && dot_pos > 0)
|
||||
{
|
||||
std::size_t name_end = close_paren != std::string::npos && close_paren < dot_pos ? close_paren : dot_pos;
|
||||
ctx.unit_name = utils::Trim(after_unit.substr(0, name_end));
|
||||
ctx.member_prefix = utils::Trim(after_unit.substr(dot_pos + 1));
|
||||
ctx.is_unit_member_context = !ctx.unit_name.empty();
|
||||
if (ctx.is_unit_member_context)
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// alias.prefix
|
||||
std::size_t dot_pos = line.rfind('.');
|
||||
if (dot_pos != std::string::npos)
|
||||
{
|
||||
// find token before dot
|
||||
std::size_t start = dot_pos;
|
||||
while (start > 0)
|
||||
{
|
||||
char ch = line[start - 1];
|
||||
if (!std::isalnum(static_cast<unsigned char>(ch)) && ch != '_')
|
||||
break;
|
||||
--start;
|
||||
}
|
||||
std::string alias = utils::Trim(line.substr(start, dot_pos - start));
|
||||
std::string prefix = utils::Trim(line.substr(dot_pos + 1));
|
||||
if (!alias.empty())
|
||||
{
|
||||
ctx.is_unit_member_context = true;
|
||||
ctx.unit_name = alias;
|
||||
ctx.member_prefix = prefix;
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string ExtractPrefixByScan(std::size_t cursor_pos, std::size_t line_start, const std::string& content)
|
||||
{
|
||||
std::size_t prefix_start = cursor_pos;
|
||||
|
|
@ -279,18 +291,29 @@ namespace lsp::provider::text_document
|
|||
}
|
||||
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));
|
||||
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.prefix = ExtractPrefixByScan(cursor_pos, line_start, content);
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
|
@ -458,18 +481,45 @@ namespace lsp::provider::text_document
|
|||
|
||||
bool HasStaticMethod(const language::symbol::Class& cls, const language::symbol::SymbolTable& table)
|
||||
{
|
||||
for (auto member_id : cls.members)
|
||||
const auto& scopes = table.scopes().all_scopes();
|
||||
for (const auto& [_, scope] : scopes)
|
||||
{
|
||||
const auto* member = table.definition(member_id);
|
||||
if (!member || member->kind() != protocol::SymbolKind::Method)
|
||||
if (scope.kind != language::symbol::ScopeKind::kClass || !scope.owner || *scope.owner != cls.id)
|
||||
{
|
||||
continue;
|
||||
const auto* method = member->As<language::symbol::Method>();
|
||||
if (method && method->is_static)
|
||||
return true;
|
||||
}
|
||||
|
||||
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<language::symbol::Method>();
|
||||
if (method && method->is_static)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<language::symbol::ScopeId> 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,
|
||||
|
|
@ -477,9 +527,9 @@ namespace lsp::provider::text_document
|
|||
bool mark_new_context,
|
||||
const std::string& unit_filter = "")
|
||||
{
|
||||
auto module_name = GetModuleName(table);
|
||||
if (!unit_filter.empty())
|
||||
{
|
||||
auto module_name = GetModuleName(table);
|
||||
if (module_name.empty() || !utils::IEquals(module_name, unit_filter))
|
||||
return;
|
||||
}
|
||||
|
|
@ -493,14 +543,23 @@ namespace lsp::provider::text_document
|
|||
continue;
|
||||
|
||||
const auto* cls = symbol.As<language::symbol::Class>();
|
||||
if (!cls || !HasStaticMethod(*cls, table))
|
||||
if (!cls)
|
||||
continue;
|
||||
|
||||
if (!mark_new_context && !HasStaticMethod(*cls, table))
|
||||
continue;
|
||||
|
||||
protocol::CompletionItem item;
|
||||
item.label = symbol.name();
|
||||
item.kind = ToCompletionItemKind(symbol.kind());
|
||||
item.kind = mark_new_context ? protocol::CompletionItemKind::Constructor : ToCompletionItemKind(symbol.kind());
|
||||
item.labelDetails = protocol::CompletionItemLabelDetails{ .detail = "", .description = SourceTag(source) };
|
||||
item.data = protocol::LSPAny(mark_new_context ? "new_context" : "class_context");
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
|
@ -544,32 +603,301 @@ namespace lsp::provider::text_document
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct DotAccessContext
|
||||
{
|
||||
bool is_unit_call = false;
|
||||
std::string base_name;
|
||||
std::string member_prefix;
|
||||
};
|
||||
|
||||
std::optional<DotAccessContext> 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<unsigned char>(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;
|
||||
}
|
||||
|
||||
void AppendClassMethods(const language::symbol::SymbolTable& table, const language::symbol::Class& cls, const std::string& prefix, CompletionSource source, std::vector<SourcedCompletionItem>& out)
|
||||
{
|
||||
for (auto member_id : cls.members)
|
||||
auto scope_id = FindScopeOwnedBy(table, language::symbol::ScopeKind::kClass, cls.id);
|
||||
if (!scope_id)
|
||||
{
|
||||
const auto* member_symbol = table.definition(member_id);
|
||||
if (!member_symbol || member_symbol->kind() != protocol::SymbolKind::Method)
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* method = member_symbol->As<language::symbol::Method>();
|
||||
if (!method)
|
||||
continue;
|
||||
const auto* scope = table.scopes().scope(*scope_id);
|
||||
if (!scope)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 仅补全类方法(静态)
|
||||
if (!method->is_static)
|
||||
continue;
|
||||
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;
|
||||
|
||||
if (!prefix.empty() && !utils::IStartsWith(method->name, prefix))
|
||||
const auto* method = member_symbol->As<language::symbol::Method>();
|
||||
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<std::string> 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<language::semantic::ClassType>())
|
||||
{
|
||||
if (const auto* class_symbol = table.definition(class_type->class_id()))
|
||||
{
|
||||
return class_symbol->name();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto* var = sym->As<language::symbol::Variable>())
|
||||
{
|
||||
return var->type;
|
||||
}
|
||||
|
||||
if (const auto* field = sym->As<language::symbol::Field>())
|
||||
{
|
||||
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<SourcedCompletionItem>& 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<language::symbol::Method>();
|
||||
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<language::symbol::Field>();
|
||||
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<SourcedCompletionItem>& 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 = method->name;
|
||||
item.kind = protocol::CompletionItemKind::Function;
|
||||
item.labelDetails = protocol::CompletionItemLabelDetails{
|
||||
.detail = BuildSignature(method->parameters, method->return_type),
|
||||
.description = SourceTag(source)
|
||||
};
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
|
@ -650,21 +978,11 @@ namespace lsp::provider::text_document
|
|||
const auto content = manager_hub.documents().GetContent(params.textDocument.uri);
|
||||
auto context = AnalyzeContext(params, content);
|
||||
|
||||
if (!context.is_class_method_context)
|
||||
{
|
||||
if (auto unit_ctx = DetectUnitMemberContext(context.line_content))
|
||||
{
|
||||
context.is_unit_member_context = unit_ctx->is_unit_member_context;
|
||||
context.unit_name = unit_ctx->unit_name;
|
||||
context.member_prefix = unit_ctx->member_prefix;
|
||||
context.prefix = unit_ctx->member_prefix;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SourcedCompletionItem> 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();
|
||||
|
|
@ -677,6 +995,42 @@ namespace lsp::provider::text_document
|
|||
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())
|
||||
{
|
||||
|
|
@ -776,6 +1130,34 @@ namespace lsp::provider::text_document
|
|||
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))
|
||||
{
|
||||
bool found_class = false;
|
||||
|
||||
auto try_append = [&](const language::symbol::SymbolTable& table, CompletionSource source) {
|
||||
auto cls_opt = FindClassSymbol(table, *type_name);
|
||||
if (!cls_opt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
(void)found_class;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (context.is_new_context)
|
||||
{
|
||||
if (context.is_unit_scoped_new && !context.unit_name.empty())
|
||||
|
|
@ -797,6 +1179,15 @@ namespace lsp::provider::text_document
|
|||
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);
|
||||
}
|
||||
else if (context.is_unit_context)
|
||||
{
|
||||
if (!visible_units_set.empty())
|
||||
|
|
@ -830,6 +1221,33 @@ namespace lsp::provider::text_document
|
|||
AppendFunctions(*table, context.prefix, CompletionSource::kSystem, collected);
|
||||
}
|
||||
|
||||
for (auto& entry : collected)
|
||||
{
|
||||
if (!entry.item.data)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry.item.data->Is<protocol::LSPObject>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& obj = entry.item.data->Get<protocol::LSPObject>();
|
||||
auto ctx_it = obj.find("ctx");
|
||||
if (ctx_it == obj.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ctx_it->second.Is<protocol::string>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
obj["uri"] = params.textDocument.uri;
|
||||
}
|
||||
|
||||
auto filtered = FilterAndSort(collected, context.prefix);
|
||||
list.items.reserve(filtered.size());
|
||||
for (auto& item : filtered)
|
||||
|
|
|
|||
Loading…
Reference in New Issue