refactor(console): phase 4 — extract ConsoleSelectionController from the god-class

Pull the text-selection concern out of ConsoleTab into a focused, ImGui-free
ConsoleSelectionController (console_selection_controller.{h,cpp}). It owns the
anchor/caret state and all the fiddly logic around it: the mouse-drag lifecycle
(beginDrag/updateDrag/endDrag), select-all, range ordering, text extraction over
the visible model, and the index shift applied when the buffer cap evicts lines
from the top.

ConsoleTab now delegates: the four scattered selection fields (is_selecting_,
has_selection_, sel_anchor_, sel_end_) and five helper methods (selectionStart/
End, isPosBeforeOrEqual, getSelectedText, clearSelection) collapse to a single
selection_ member plus a thin selectedText() wrapper. screenToTextPos (still
view-side — it needs the frame's layout) now returns the shared ConsoleTextPos,
so ConsoleTab::TextPos is retired.

The byte-offset / eviction math was previously only reachable through the live
ImGui renderer; it now has direct unit coverage (testConsoleSelectionController:
ordering, drag lifecycle, upward drag, select-all, partial/full/no-op eviction
shift, and filtered extraction). Full-node + lite build clean; ctest green;
source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:46:06 -05:00
parent 779c9682d4
commit 0cdbbd024e
6 changed files with 290 additions and 108 deletions

View File

@@ -6,6 +6,7 @@
#include "console_channel.h"
#include "console_model.h"
#include "console_selection_controller.h"
#include "console_text_layout.h"
#include "../layout.h"
#include "../../daemon/embedded_daemon.h"
@@ -103,20 +104,13 @@ private:
void renderInput(ConsoleCommandExecutor& exec);
void renderCommandsPopup();
// Selection helpers
std::string getSelectedText() const;
void clearSelection();
// Convert screen position to line/column
struct TextPos {
int line = -1;
int col = 0;
};
TextPos screenToTextPos(ImVec2 screen_pos) const;
bool isPosBeforeOrEqual(const TextPos& a, const TextPos& b) const;
TextPos selectionStart() const; // Returns the earlier of sel_anchor_ and sel_end_
TextPos selectionEnd() const; // Returns the later of sel_anchor_ and sel_end_
// Map a screen point to a raw {line, col} position via this frame's wrapped layout.
// (View-side because it needs layout_ + output_origin_; feeds the selection controller.)
ConsoleTextPos screenToTextPos(ImVec2 screen_pos) const;
// The current selection's text (via selection_ over the visible model). "" if inactive.
std::string selectedText() const;
// Thread-safe line store: producers on any thread call model_.ingest(); the main
// thread drains it once per frame in render(). The visible deque is main-thread-only.
ConsoleModel model_;
@@ -128,11 +122,8 @@ private:
int new_lines_since_scroll_ = 0; // new lines while scrolled up (for indicator)
// (log-ingestion cursors + result queue moved to the ConsoleCommandExecutor)
// Text selection state
bool is_selecting_ = false;
bool has_selection_ = false;
TextPos sel_anchor_; // Where the mouse was first pressed
TextPos sel_end_; // Where the mouse currently is / was released
// Text-selection state + logic (anchor/caret, ordering, extraction, eviction shift).
ConsoleSelectionController selection_;
float output_scroll_y_ = 0.0f; // Track scroll position for selection
ImVec2 output_origin_ = {0, 0}; // Top-left of output area
float output_line_height_ = 0.0f; // Text line height (for scanline alignment)