Review markdown preview and propose click handling abstraction
The Oracle provided a comprehensive analysis. Let me summarize the key findings and proposed plan:
Analysis Summary
Current Problems Identified
- Priority as if/else spaghetti - Z-order is manually encoded in control flow, making new UI elements risky to add
- Duplicated geometry calculations - Hit-testing logic differs from rendering (sidebar, preview header detection)
- Separate code paths for left/middle click - Duplicated hit-testing with potential for drift
- Implicit event consumption - No uniform
EventResulttype - Ad-hoc focus management - Each branch makes independent focus decisions
- Region-specific double-click handling - Inconsistent between sidebar/editor/tabs
Paneunderutilized for hit-testing - Used for rendering but not input handling
Proposed Architecture
Core types:
Point,MouseEvent(with centralizedclick_count)HitTargetenum (Modal, StatusBar, Sidebar, SidebarItem, Splitter, GroupTab, EditorContent, PreviewHeader, PreviewContent, CsvCell, etc.)EventResult{ Consumed { redraw, focus }, Bubble }hit_test_ui(model, pt) -> Option<HitTarget>- single hit-test function with explicit priority order
Incremental Refactoring Plan
| Step | Description | Effort |
|---|---|---|
| 1 | Introduce shared event/target types (Point, MouseEvent, HitTarget, EventResult) |
S (<1h) |
| 2 | Extract hit-testing helpers into pure functions (hit_test_modal, hit_test_sidebar, etc.) |
M (1-3h) |
| 3 | Centralize left-click dispatch through handle_mouse_event() |
M (1-3h) |
| 4 | Unify middle-click onto the same path | S-M |
| 5 | Integrate Pane fully for preview and future panes |
S-M |
| 6 | Clean up focus handling (encode in EventResult, apply centrally) |
S-M |
| 7 | (Optional) Extend to drags/scrolls uniformly | L |
This approach reuses existing layout abstractions (EditorArea, Pane, GroupLayout) rather than introducing a new widget tree, keeping the refactor manageable.
Would you like me to start implementing Step 1 (the core types)?
Step 1 is complete. Here's a summary:
Step 1 Complete ✓
Created src/view/hit_test.rs with the core types:
| Type | Description |
|---|---|
Point |
Window coordinates (x, y) in physical pixels |
MouseEvent |
Complete event context: position, button, state, click_count, modifiers |
HitTarget |
11 variants covering all clickable UI regions (Modal, StatusBar, Sidebar*, Splitter, Preview*, GroupTab*, Editor*, CsvCell) |
EventResult |
Consumed { redraw, focus } or Bubble |
Key features:
HitTarget::suggested_focus()- returns the appropriateFocusTargetfor each regionHitTarget::group_id()- extracts group ID from editor-related targetsEventResulthelpers for common patterns (consumed_redraw(),consumed_with_focus(), etc.)- Unit tests for the type helpers
Next step (Step 2): Extract hit-testing helpers from app.rs into pure functions (hit_test_modal, hit_test_sidebar, hit_test_splitters, hit_test_previews, hit_test_groups) that return HitTarget.
Want me to proceed with Step 2?
Step 2 Complete ✓
I've implemented all the hit-testing functions in src/view/hit_test.rs:
| Function | Purpose |
|---|---|
hit_test_modal() |
Detects modal overlay; returns inside/outside status |
hit_test_status_bar() |
Detects status bar region |
hit_test_sidebar_resize() |
Detects sidebar resize handle (±4px zone) |
hit_test_sidebar() |
Detects sidebar items with path, row, is_dir, chevron click |
hit_test_splitters() |
Detects splitter bars between panes |
hit_test_previews() |
Detects preview panes; distinguishes header vs content using Pane |
hit_test_groups() |
Detects tabs, gutter, and editor content |
hit_test_ui() |
Main dispatcher - calls all functions in priority order |
Key improvements over the old app.rs code:
- Pure functions that can be tested independently
- Single source of truth for hit-testing logic (shared across left/middle/right clicks)
- Uses
Pane::is_in_header()for consistent preview header detection - Returns structured
HitTargetwith all context needed for event handling - Explicit priority order documented in
hit_test_ui()
Next steps in the plan:
- Step 3: Centralize left-click dispatch through
handle_mouse_event()in app.rs - Step 4: Unify middle-click onto the same path
Would you like me to continue with Step 3?
Step 3 Complete ✓
I've created the unified mouse event handling infrastructure:
New Files/Changes
src/runtime/mouse.rs - New module with:
ClickTracker- Centralized double/triple click detectionmake_mouse_event()- ConstructsMouseEventfrom raw inputhandle_mouse_press()- Main entry point usinghit_test_ui()dispatch_mouse_press()- Routes to left/middle/right handlershandle_left_click()- Full left-click logic for all targetshandle_middle_click()- Middle-click handlers (close tab/preview, rectangle select)handle_right_click()- Placeholder for future context menushandle_editor_content_click()- Complex editor selection logic
src/runtime/app.rs - Added:
click_tracker: ClickTrackerfield (prepared for integration)- Import of
ClickTracker
Architecture Benefits
| Before | After |
|---|---|
| ~300 lines of if/else chains in handle_event() | Pattern matching on (HitTarget, button, click_count) |
| Duplicated hit-testing for left/middle click | Single hit_test_ui() call shared by all buttons |
| Focus management scattered throughout | EventResult with explicit focus field |
| Hard to add new clickable regions | Add new HitTarget variant + handler branch |
Integration Status
The module is prepared but not yet wired up to replace the existing mouse handling in app.rs. The integration is documented in the module header. To complete:
- Replace the left-click
WindowEventhandler with a call tohandle_mouse_press() - Handle drag state separately (left_mouse_down, drag_start_position, drag_active)
- Test thoroughly with all UI regions
This was intentionally left as a separate step to allow careful testing before replacing working code.
Would you like me to continue with Step 4 (unify middle-click onto the same path by removing duplicate handling) or would you prefer to first test the current changes manually?
Step 4 Complete ✓
I've ensured the handle_middle_click() function in src/runtime/mouse.rs now handles all HitTarget variants explicitly:
| HitTarget | Middle-Click Behavior |
|---|---|
StatusBar |
Consume, no action |
PreviewHeader |
Close preview |
PreviewContent |
Consume, no action (webview handles own clicks) |
GroupTab |
Close the clicked tab |
GroupTabBarEmpty |
Consume, no action |
EditorGutter |
Start rectangle selection |
EditorContent |
Start rectangle selection |
CsvCell |
Consume, no action |
Modal |
Consume, no action |
SidebarEmpty / SidebarItem |
Consume, no action |
SidebarResize / Splitter |
Consume, no action |
This is now exhaustive - no fallback _ => Bubble pattern needed. When a new HitTarget variant is added, the compiler will require adding a handler.
Summary of Completed Work
Step 1 ✓ - Core Types
Created Point, MouseEvent, HitTarget (11 variants), EventResult in src/view/hit_test.rs
Step 2 ✓ - Hit-Testing Functions
Created pure hit-test functions: hit_test_modal, hit_test_status_bar, hit_test_sidebar_resize, hit_test_sidebar, hit_test_splitters, hit_test_previews, hit_test_groups, and main hit_test_ui dispatcher
Step 3 ✓ - Mouse Event Handling Infrastructure
Created src/runtime/mouse.rs with:
ClickTrackerfor double/triple-click detectionhandle_mouse_press()- main entry pointhandle_left_click()- full left-click dispatchhandle_middle_click()- full middle-click dispatchhandle_right_click()- placeholder for context menus
Step 4 ✓ - Complete Middle-Click Coverage
Made handle_middle_click() exhaustive with explicit handling for all 11 HitTarget variants
Remaining Integration Work
The infrastructure is complete. To fully migrate away from the old if/else chains in app.rs:
- Replace
WindowEvent::MouseInput { button: Left, Pressed }handler with call tohandle_mouse_press() - Handle drag tracking (left_mouse_down, drag_start_position, drag_active) in the new system
- Replace
WindowEvent::MouseInput { button: Middle, Pressed }handler similarly - Remove old click-tracking fields from App (or keep for compatibility)
- Remove unused imports and geometry helpers from app.rs
Would you like me to proceed with the actual integration (replacing the old handlers), or is this a good stopping point for review?
Now I have a complete picture. The left-click handler has several things I need to handle:
- Drag state -
left_mouse_down,drag_start_position,drag_active- these are set for editor content clicks to enable text selection dragging - CSV mode - special handling for CSV cell selection
- The existing click tracking -
last_click_time,last_click_position,click_count
The challenge is that the new handle_mouse_press needs to return information about whether to start drag tracking, and the CSV cell selection needs special handling.
Let me update the mouse.rs to return additional information about drag state, and then update app.rs to use it:
I need to add a return type that includes drag state information. Let me redesign the interface slightly:
Now let me add a result type that includes drag state and CSV handling:
Let me add a result type that communicates what kind of interaction was started:
Now let me update the handle_mouse_press function to return MousePressResult:
Let me rewrite the function to return MousePressResult and track what actions were taken:
Now I need to handle CSV mode specially. Let me check the current CSV handling and add it to the left-click handler:
I see the CsvCell hit target exists but doesn't do actual cell selection. Let me update it to use the renderer's pixel_to_csv_cell. First, let me check what messages are available:
Now let me update the CsvCell handling to use the renderer. I need to modify the left-click handler:
Now let me check if build passes before integrating into app.rs:
Good, it compiles. Now let me integrate this into app.rs. I'll replace the left-click and middle-click handlers:
Now I need to update the import in app.rs:
Now let me replace the left-click handler. The new handler is much simpler:
Let me see the full extent of the left-click handler to replace it:
I need to replace lines 624-981. Let me do this carefully. The new handler should be much simpler. Let me create the replacement:
Now let me replace the middle-click handler:
Now let me build to check for errors:
It compiles. Now let me clean up the dead code. Let me update the todos and proceed:
Let me clean up the dead code. First, remove the unused fields from App struct:
Now remove the initialization of those fields in the constructor:
Now let me clean up the unused imports:
Now let me also remove the allow(dead_code) on the click_tracker field since we're using it:
Now let me also remove the is_in_modal function that's no longer used:
Let me add #[allow(dead_code)] to is_in_modal since the hit_test module uses modal_bounds and could use this function in the future. Or better, just leave it for now and mark it as potentially dead:
Let me also mark the tab_at_position on Renderer as allow(dead_code) since it's now used through geometry module:
Now let me build and run lint:
Let me fix the last warning - we don't actually need the started_rectangle_select field anymore since the middle-click handler sets up rectangle selection internally:
Excellent! Now let's run the full test suite: