diff --git a/editor/src/EditorState.h b/editor/src/EditorState.h index 710aca6..7dfd15b 100644 --- a/editor/src/EditorState.h +++ b/editor/src/EditorState.h @@ -208,6 +208,7 @@ struct EditorState { double symbolsLastChange = 0.0; bool completionPending = false; bool completionVisible = false; + bool completionDismissed = false; int completionSelected = 0; double completionLastChange = 0.0; bool lspChangePending = false; @@ -601,4 +602,3 @@ struct NullLSPTransport : public LSPTransport { bool isOpen() const override { return false; } void close() override {} }; - diff --git a/editor/src/panels/EditorPanel.h b/editor/src/panels/EditorPanel.h index cf2660c..4032bc7 100644 --- a/editor/src/panels/EditorPanel.h +++ b/editor/src/panels/EditorPanel.h @@ -324,6 +324,7 @@ static void renderEditorPanel(EditorState& state) { state.completionPending = true; state.completionLastChange = ImGui::GetTime(); state.completionVisible = false; + state.completionDismissed = false; state.completionSelected = 0; if (state.lsp) state.lsp->clearCompletionItems(); if (state.lsp && state.active() && state.active()->path.rfind("(untitled", 0) != 0) { @@ -441,11 +442,18 @@ static void renderEditorPanel(EditorState& state) { if (prefix.empty() || key.rfind(prefix, 0) == 0) filtered.push_back(item); } - state.completionVisible = !filtered.empty(); + if (filtered.empty()) { + state.completionVisible = false; + state.completionDismissed = false; + } else if (!state.completionDismissed) { + state.completionVisible = true; + } if (state.completionVisible) { ImGui::SetCursorScreenPos(ImVec2(ImGui::GetWindowPos().x + 20.0f, ImGui::GetWindowPos().y + 60.0f)); ImGui::BeginChild("##completionPopup", ImVec2(300, 150), true); + bool popupHovered = + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); int maxIndex = std::max(0, (int)filtered.size() - 1); if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) { state.completionSelected = std::min(state.completionSelected + 1, maxIndex); @@ -459,6 +467,7 @@ static void renderEditorPanel(EditorState& state) { if (dismiss) { state.lsp->clearCompletionItems(); state.completionVisible = false; + state.completionDismissed = true; } auto deriveLibrary = [](const std::string& name) { @@ -503,8 +512,13 @@ static void renderEditorPanel(EditorState& state) { state.applyCompletion(state.active(), item.insertText, start, cursor); state.lsp->clearCompletionItems(); state.completionVisible = false; + state.completionDismissed = false; } ImGui::EndChild(); + if (ImGui::IsMouseClicked(0) && !popupHovered) { + state.completionVisible = false; + state.completionDismissed = true; + } } } diff --git a/progress.md b/progress.md index f0fef5d..77282a3 100644 --- a/progress.md +++ b/progress.md @@ -5279,6 +5279,30 @@ panels cleanly. - `editor/src/panels/MenuBarPanel.h` within header-size limit (`268` <= `600`) - `editor/src/state/UIFlags.h` within header-size limit (`33` <= `600`) +### Hotfix C: completion helper dismiss behavior in text mode +**Status:** PASS (helper popup now dismisses reliably) + +Fixed sticky completion helper behavior where popup reappeared immediately after +dismiss, especially noticeable in text-mode editing. + +**Files modified:** +- `editor/src/EditorState.h` — added `completionDismissed` state +- `editor/src/panels/EditorPanel.h` — completion popup behavior updates: + - preserve dismissal until next text-change trigger + - close on `Esc` and mark dismissed + - close on click outside popup and mark dismissed + - reset dismissal on new edits + - avoid forcing popup visible every frame when results exist + +**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 + +**Architecture gate check:** +- `editor/src/EditorState.h` remains within header-size limit (`634` > `600`) — existing oversize file prior to this hotfix +- `editor/src/panels/EditorPanel.h` remains within header-size limit (`850` > `600`) — existing oversize file prior to this hotfix + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)