docs/internal/INPUT_LAYOUT_RENDERING_SUMMARY.md
This document summarizes the architecture for input processing, layout, and rendering in fresh2, covering the main buffer flow, split/composite views, Settings UI, plugin system, and specific UI elements.
The input system follows a hierarchical, bubbling model defined primarily in crates/fresh-editor/src/input/handler.rs.
InputHandler trait defines the contract for any component that handles input.
handle_key_event(&mut self, event: &KeyEvent, ctx: &mut InputContext) -> InputResult: Handles the event at the current level.focused_child_mut(&mut self) -> Option<&mut dyn InputHandler>: Returns the currently focused child component.dispatch_input(...) -> InputResult: The main entry point. It implements the Leaf-first, bubble-up logic.dispatch_input recursively calls itself on focused_child_mut().InputResult::Ignored, the parent attempts to handle the event via handle_key_event.InputResult::Consumed, propagation stops immediately.is_modal() to return true. A modal handler forces dispatch_input to return Consumed for all events, even those it didn't explicitly handle, preventing input leakage to parents (used in Settings and Prompts).InputContext is threaded through the call stack. It allows handlers to queue DeferredActions (e.g., CloseSettings, PasteToSettings, ExecuteAction) to be performed after the immutable borrow of the input loop is released.CompositeInputRouter (crates/fresh-editor/src/input/composite_router.rs) handles input for split buffers.
focused_pane in CompositeViewState.Tab to switch panes, h/j/k/l for movement) and routes editing keys to the active source buffer if editable.SettingsState (crates/fresh-editor/src/view/settings/state.rs) implements InputHandler.
Categories, Settings, Footer).handle_text_editing_input, handle_dropdown_input, etc., depending on the active control state.pkg.ts) define custom modes (e.g., "pkg-manager") via editor.defineMode.
globalThis.pkg_nav_up).Rendering uses ratatui for TUI primitives and a custom viewport system for buffer content.
Viewport (crates/fresh-editor/src/view/viewport.rs) manages the visible window into a TextBuffer.
top_byte (source of truth for vertical scroll), left_column, width, height, and wrapping state.ensure_visible ensures the cursor stays in view, calculating line positions. It handles both wrapped and unwrapped lines.prepare_viewport pre-loads chunks of data from TextBuffer (which uses a PieceTree) before rendering to ensure performance with large files.TextBuffer (crates/fresh-editor/src/model/buffer.rs) is the data model.
PieceTree for efficient edits.iter_pieces_in_range) for rendering.CompositeBuffer (crates/fresh-editor/src/model/composite_buffer.rs) aggregates multiple SourcePanes.
LineAlignment to map display rows to source lines (e.g., for side-by-side diffs). Rows can be Context, Addition, Deletion, etc.CompositeViewState (crates/fresh-editor/src/view/composite_view.rs) manages the presentation.
pane_widths based on configured ratios (e.g., 50/50) and computes Rects for each pane via compute_pane_rects.scroll_row for synchronized scrolling of all panes.pane_cursors for each pane.render_settings (crates/fresh-editor/src/view/settings/render.rs) draws the UI.
SettingsLayout tracks screen areas (Rects) for items and controls to support mouse interaction (SettingsHit).Toggle, Number, Dropdown, TextList, Map, etc., handle the visual state of each setting.TabsRenderer (crates/fresh-editor/src/view/ui/tabs.rs) renders the tab bar.
compute_tab_scroll_offset calculates horizontal scrolling to keep the active tab visible.StatusBarRenderer (crates/fresh-editor/src/view/ui/status_bar.rs) handles the bottom bar.
MenuRenderer (crates/fresh-editor/src/view/ui/menu.rs) renders the top menu.
MenuState tracks active menu, highlighted item, and nested submenu path.render_scrollbar (crates/fresh-editor/src/view/ui/scrollbar.rs) is a reusable widget.
thumb_geometry). Supports click-to-scroll on the track (click_to_offset).crates/fresh-editor/src/view/ui/scroll_panel.rs) A generic widget for scrolling variable-height items.
ScrollState with row offsets (not item index offsets) to handle items of different heights.ensure_focused_visible handles scrolling to keep focused items (and sub-regions within items) in view.render_button (crates/fresh-editor/src/view/settings/render.rs) renders footer buttons (Save, Cancel, etc.).
createVirtualBufferInExistingSplit.[ Install ] or [ Update ].▸ for selection.padEnd(LIST_WIDTH)).applyPkgManagerHighlighting applies colors using editor.addOverlay.
[ ] brackets) or uses the entries structure to apply specific styles (colors) to ranges of bytes.pkg_nav_up, pkg_nav_down) mapped to keys update the internal selection state index.pkg_activate checks the current focus state (list, filter button, action button) and executes the corresponding logic.This section details how responsibilities (State, Render, Input, Mouse) are distributed for each component category.
Architecture: Integrated Pipeline
EditorState and TextBuffer (content) + Viewport (scroll position).view_pipeline.rs): A strict pipeline converts TextBuffer → ViewTokenWire (tokens) → ViewLine (display lines). This preserves semantic info (like source byte offsets) through the entire process.Layout in view_pipeline.rs): The pipeline produces a Layout object containing ViewLines and byte_to_line mappings. This object acts as the "hit test" database.handler.rs): Dispatches keys to the active buffer.Layout object produced during rendering to map screen coordinates (x, y) back to source byte offsets in O(1) or O(log n) time. This decouples rendering logic from hit-testing logic while keeping them consistent.Architecture: Separated Modules with "Retained Mode" Hit Testing
state.rs): SettingsState is the single source of truth for navigation, pending changes, and active pages.render.rs): Pure function render_settings. Crucially, it returns a SettingsLayout object.layout.rs): The SettingsLayout contains computed Rects for every interactive element (buttons, checkboxes, items). It persists until the next render.mouse.rs): Queries the cached SettingsLayout to determine what was clicked (SettingsHit). It modifies SettingsState directly.input.rs): A dedicated InputHandler implementation for SettingsState that routes keys based on the FocusPanel (Categories vs Settings vs Footer).Architecture: Immediate Mode Rendering + Hit Area Return
tabs.rs): TabsRenderer::render_for_split draws tabs and immediately returns a Vec of hit areas (buffer_id, start_col, end_col, close_col). The editor stores this ephemeral layout for the next mouse click.status_bar.rs): StatusBarRenderer::render_status_bar returns a StatusBarLayout struct containing coordinates for clickable indicators (LSP, Line Ending, Language).menu.rs): MenuRenderer draws the menu. Unlike Settings, it doesn't return a full layout object. Instead, MenuState has logic (get_menu_at_position) to re-calculate hit testing on-the-fly based on string widths.scrollbar.rs): ScrollbarState holds the logic. render_scrollbar draws it. Input handling uses ScrollbarState::click_to_offset to map clicks back to scroll positions mathmatically, without needing a cached layout.Architecture: Monolithic Script
pkgState object in TypeScript holds everything (search query, selection index, installed list).updatePkgManagerView rebuilds the entire virtual buffer content (strings) from pkgState every time something changes. It manually reapplies overlays.pkg_nav_up) modify pkgState and call updatePkgManagerView.A review of the implementation reveals several areas where abstractions are "leaky" or where logic is manually duplicated rather than using a shared framework.
There is a significant inconsistency in how interactive areas are tracked:
SettingsLayout object produced by the renderer. This is a clean, retained-mode approach to hit testing.MenuState::get_menu_at_position. It re-calculates widths based on label strings on-the-fly when a click occurs. This means the logic for "how a menu is drawn" is duplicated between MenuRenderer and MenuState.The Prompt system (crates/fresh-editor/src/view/prompt.rs) is designed to be a generic input mechanism. However:
StatusBarRenderer contains a specialized render_file_open_prompt method.FileOpenState and performs specialized path truncation that only exists for this one prompt type.Virtual buffers (used by pkg.ts and others) are a low-level abstraction that places a heavy burden on the creator:
pkg.ts manually calculates padding (e.g., padEnd(LIST_WIDTH)) and joins strings to create a "grid".editor.addOverlay.crates/fresh-editor/src/view/ui/split_rendering.rs is a massive module (~5.5k lines) that handles:
CompositeInputRouter is a giant, manual match statement for keys. It has to decide whether to scroll the composite view, switch panes, or forward the key to a source buffer. This "routing logic" is hardcoded and doesn't easily allow for plugin-defined behaviors in composite views without modifying the core Rust code.