diff --git a/editor/src/CompletionUtils.h b/editor/src/CompletionUtils.h index b7f9593..1abab15 100644 --- a/editor/src/CompletionUtils.h +++ b/editor/src/CompletionUtils.h @@ -3,6 +3,12 @@ #include "PrimitivesRegistry.h" #include #include +#include + +enum class CompletionContext { + General, + CppInclude +}; struct CompletionBuildResult { std::vector items; @@ -14,26 +20,104 @@ static inline bool matchesPrefix(const std::string& text, const std::string& pre return text.rfind(prefix, 0) == 0; } +static inline std::string trimLeft(const std::string& in) { + size_t i = 0; + while (i < in.size() && std::isspace(static_cast(in[i]))) ++i; + return in.substr(i); +} + +static inline std::string currentLinePrefix(const std::string& text, int cursor) { + if (cursor < 0) cursor = 0; + if (cursor > (int)text.size()) cursor = (int)text.size(); + int lineStart = cursor; + while (lineStart > 0 && text[lineStart - 1] != '\n') --lineStart; + return text.substr(lineStart, cursor - lineStart); +} + +static inline bool isCppLikeLanguage(const std::string& language) { + return language == "cpp" || language == "c"; +} + +static inline CompletionContext detectCompletionContext(const std::string& text, + int cursor, + const std::string& language) { + if (!isCppLikeLanguage(language)) return CompletionContext::General; + std::string line = trimLeft(currentLinePrefix(text, cursor)); + if (line.rfind("#inc", 0) == 0 || line.rfind("#include", 0) == 0) { + return CompletionContext::CppInclude; + } + return CompletionContext::General; +} + +static inline bool looksLikeIncludeCandidate(const LSPClient::CompletionItem& item) { + const std::string& label = item.label; + const std::string& filter = item.filterText.empty() ? item.label : item.filterText; + const std::string& insert = item.insertText.empty() ? item.label : item.insertText; + if (label.rfind("<", 0) == 0 || label.rfind("\"", 0) == 0) return true; + if (filter.rfind("<", 0) == 0 || filter.rfind("\"", 0) == 0) return true; + if (insert.rfind("<", 0) == 0 || insert.rfind("\"", 0) == 0) return true; + if (label.find(".h") != std::string::npos || filter.find(".h") != std::string::npos) return true; + if (label.find("/") != std::string::npos || filter.find("/") != std::string::npos) return true; + if (label == "include" || filter == "include") return true; + return false; +} + +static inline std::vector includeSnippetCompletions() { + std::vector out; + auto add = [&](const std::string& label, const std::string& insert, const std::string& detail) { + LSPClient::CompletionItem ci; + ci.label = label; + ci.insertText = insert; + ci.filterText = label; + ci.detail = detail; + out.push_back(std::move(ci)); + }; + add("include", "include ", "preprocessor directive"); + add("", "", "C++ header"); + add("", "", "C++ header"); + add("", "", "C++ header"); + add("", "", "C++ header"); + return out; +} + static inline CompletionBuildResult buildLibraryAwareCompletions( const std::vector& lspItems, const std::vector& primitives, - const std::string& prefix) { + const std::string& prefix, + CompletionContext context = CompletionContext::General) { CompletionBuildResult out; - out.items = lspItems; + if (context == CompletionContext::CppInclude) { + for (const auto& item : lspItems) { + if (looksLikeIncludeCandidate(item)) out.items.push_back(item); + } + } else { + out.items = lspItems; + } + std::unordered_set existing; - for (const auto& item : lspItems) { + for (const auto& item : out.items) { 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)); + if (context == CompletionContext::CppInclude) { + for (const auto& ci : includeSnippetCompletions()) { + if (!matchesPrefix(ci.label, prefix)) continue; + if (existing.insert(ci.label).second) { + out.preferredNames.insert(ci.label); + out.items.push_back(ci); + } + } + } else { + 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)); + } } } diff --git a/editor/src/panels/EditorPanel.h b/editor/src/panels/EditorPanel.h index bb0efaa..2ffbaf5 100644 --- a/editor/src/panels/EditorPanel.h +++ b/editor/src/panels/EditorPanel.h @@ -443,7 +443,12 @@ static void renderEditorPanel(EditorState& state) { primitives.insert(primitives.end(), types.begin(), types.end()); primitives.insert(primitives.end(), consts.begin(), consts.end()); - auto built = buildLibraryAwareCompletions(items, primitives, prefix); + CompletionContext completionContext = + detectCompletionContext(state.active()->editBuf, + cursor, + state.active()->language); + auto built = buildLibraryAwareCompletions(items, primitives, prefix, + completionContext); std::vector filtered; filtered.reserve(built.items.size()); for (const auto& item : built.items) { diff --git a/progress.md b/progress.md index c8010cc..cb5190b 100644 --- a/progress.md +++ b/progress.md @@ -5337,6 +5337,25 @@ enabled by the user. - `step54_test` — PASS (10/10) regression coverage - `step437_test` — PASS (8/8) regression coverage +### Hotfix F: context-aware include completion filtering +**Status:** PASS (relevance improved for `#include` workflows) + +Improved completion relevance in C/C++ preprocessor include contexts to avoid +noisy unrelated suggestions (for example `std::*` symbols when typing `#inc`). + +**Files modified:** +- `editor/src/CompletionUtils.h`: + - added completion context detection (`General`, `CppInclude`) + - added include-context filtering for LSP items + - added include-focused snippet suggestions (`include`, ``, ``, ``, ``) +- `editor/src/panels/EditorPanel.h`: + - passed runtime completion context into completion builder + +**Verification run:** +- `cmake --build editor/build-native --target whetstone_editor` — PASS +- `step54_test` — PASS (10/10) regression coverage +- `step437_test` — PASS (8/8) regression coverage + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)