Step 135: library-aware completion

This commit is contained in:
Bill
2026-02-09 17:21:26 -07:00
parent 4b5304240a
commit b7bd54d5da
6 changed files with 143 additions and 4 deletions

View File

@@ -0,0 +1,49 @@
#pragma once
#include "LSPClient.h"
#include "PrimitivesRegistry.h"
#include <unordered_set>
#include <algorithm>
struct CompletionBuildResult {
std::vector<LSPClient::CompletionItem> items;
std::unordered_set<std::string> preferredNames;
};
static inline bool matchesPrefix(const std::string& text, const std::string& prefix) {
if (prefix.empty()) return true;
return text.rfind(prefix, 0) == 0;
}
static inline CompletionBuildResult buildLibraryAwareCompletions(
const std::vector<LSPClient::CompletionItem>& lspItems,
const std::vector<PrimitiveSymbol>& primitives,
const std::string& prefix) {
CompletionBuildResult out;
out.items = lspItems;
std::unordered_set<std::string> existing;
for (const auto& item : lspItems) {
existing.insert(item.label);
}
for (const auto& prim : primitives) {
out.preferredNames.insert(prim.name);
if (!matchesPrefix(prim.name, prefix)) continue;
if (existing.insert(prim.name).second) {
LSPClient::CompletionItem ci;
ci.label = prim.name;
ci.insertText = prim.name;
ci.detail = prim.source;
out.items.push_back(std::move(ci));
}
}
std::stable_sort(out.items.begin(), out.items.end(),
[&](const LSPClient::CompletionItem& a, const LSPClient::CompletionItem& b) {
bool aPref = out.preferredNames.count(a.label) > 0;
bool bPref = out.preferredNames.count(b.label) > 0;
if (aPref != bPref) return aPref > bPref;
return a.label < b.label;
});
return out;
}

View File

@@ -14,6 +14,7 @@
#include "EditorState.h"
#include "EditorUtils.h"
#include "CompletionUtils.h"
// ---------------------------------------------------------------------------
// Main
@@ -1315,9 +1316,25 @@ int main(int, char**) {
auto items = state.lsp->getCompletionItems();
int cursor = state.active()->widget.getCursor();
std::string prefix = EditorState::wordPrefixAt(state.active()->editBuf, cursor);
std::string nodeId;
if (state.activeAST()) {
ASTNode* scopeNode = findNodeAtPosition(state.activeAST(),
std::max(0, state.active()->cursorLine - 1),
std::max(0, state.active()->cursorCol - 1));
if (scopeNode) nodeId = scopeNode->id;
}
std::vector<PrimitiveSymbol> primitives;
auto funcs = state.primitives.getAvailableFunctions(nodeId);
auto types = state.primitives.getAvailableTypes(nodeId);
auto consts = state.primitives.getAvailableConstants(nodeId);
primitives.insert(primitives.end(), funcs.begin(), funcs.end());
primitives.insert(primitives.end(), types.begin(), types.end());
primitives.insert(primitives.end(), consts.begin(), consts.end());
auto built = buildLibraryAwareCompletions(items, primitives, prefix);
std::vector<LSPClient::CompletionItem> filtered;
filtered.reserve(items.size());
for (const auto& item : items) {
filtered.reserve(built.items.size());
for (const auto& item : built.items) {
const std::string& key = item.filterText.empty() ? item.label : item.filterText;
if (prefix.empty() || key.rfind(prefix, 0) == 0) filtered.push_back(item);
}
@@ -1342,20 +1359,44 @@ int main(int, char**) {
state.completionVisible = false;
}
auto deriveLibrary = [](const std::string& name) {
auto pos = name.find("::");
if (pos != std::string::npos) return name.substr(0, pos);
pos = name.find('.');
if (pos != std::string::npos) return name.substr(0, pos);
return std::string();
};
for (int i = 0; i < (int)filtered.size(); ++i) {
std::string itemLabel = filtered[i].label;
if (filtered[i].kind != 0) {
itemLabel = "[" + std::to_string(filtered[i].kind) + "] " + itemLabel;
}
bool selected = (i == state.completionSelected);
bool preferred = built.preferredNames.count(filtered[i].label) > 0;
std::string libHint = deriveLibrary(filtered[i].label);
if (!preferred && !libHint.empty()) itemLabel += " (auto-import)";
if (!preferred) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f));
}
if (ImGui::Selectable(itemLabel.c_str(), selected)) {
state.completionSelected = i;
accept = true;
}
if (!preferred) {
ImGui::PopStyleColor();
}
}
if (accept && !filtered.empty()) {
const auto& item = filtered[state.completionSelected];
bool preferred = built.preferredNames.count(item.label) > 0;
if (!preferred) {
std::string libHint = deriveLibrary(item.label);
if (!libHint.empty()) {
state.ensureImportForSymbol(libHint, item.label);
}
}
int start = EditorState::wordStartAt(state.active()->editBuf, cursor);
state.applyCompletion(state.active(), item.insertText, start, cursor);
state.lsp->clearCompletionItems();