1540 lines
63 KiB
C++
1540 lines
63 KiB
C++
module;
|
|
|
|
#include <cstdint>
|
|
|
|
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<Completion, IRequestProvider>
|
|
{
|
|
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<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;
|
|
}
|
|
|
|
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<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);
|
|
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<unsigned char>(ch)) && ch != '_')
|
|
break;
|
|
--prefix_start;
|
|
}
|
|
return content.substr(prefix_start, cursor_pos - prefix_start);
|
|
}
|
|
|
|
CompletionContext AnalyzeContext(const protocol::CompletionParams& params, const std::optional<std::string>& 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<std::string> CollectVisibleUnits(const language::symbol::SymbolTable* table, const protocol::Position& position, const std::optional<std::string>& content_opt)
|
|
{
|
|
if (!table)
|
|
return {};
|
|
|
|
std::unordered_set<std::string, utils::IHasher, utils::IEqualTo> 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<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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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<language::symbol::Unit>();
|
|
if (unit)
|
|
{
|
|
add_imports(unit->interface_imports);
|
|
add_imports(unit->implementation_imports);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> result;
|
|
result.reserve(units.size());
|
|
for (const auto& name : units)
|
|
result.push_back(name);
|
|
return result;
|
|
}
|
|
|
|
bool IsUnitVisible(const std::unordered_set<std::string, utils::IHasher, utils::IEqualTo>& 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<SourcedCompletionItem>& 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<SourcedCompletionItem>& 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<language::symbol::Function>();
|
|
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<SourcedCompletionItem>& 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<language::symbol::Function>())
|
|
{
|
|
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<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,
|
|
std::vector<SourcedCompletionItem>& 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<language::symbol::Class>();
|
|
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<SourcedCompletionItem>& out,
|
|
const std::unordered_set<std::string, utils::IHasher, utils::IEqualTo>* 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<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;
|
|
}
|
|
|
|
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<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
|
|
{
|
|
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;
|
|
}
|
|
|
|
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);
|
|
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<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 = 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<SourcedCompletionItem> FilterAndSort(const std::vector<SourcedCompletionItem>& items, const std::string& prefix)
|
|
{
|
|
std::vector<const SourcedCompletionItem*> unique_items;
|
|
std::set<std::string> 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<int>(prefix.length()) * 100 / static_cast<int>(a->item.label.length());
|
|
int b_score = static_cast<int>(prefix.length()) * 100 / static_cast<int>(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<SourcedCompletionItem> 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<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();
|
|
|
|
std::unordered_set<std::string, utils::IHasher, utils::IEqualTo> 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<language::symbol::Class>();
|
|
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<language::symbol::Class>();
|
|
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<language::symbol::Class>();
|
|
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<std::string> 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<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;
|
|
}
|
|
|
|
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()<protocol::CompletionParams>(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;
|
|
}
|
|
} |