Symptom
When the LSP editor hover popup is open and tall enough to need its own scrollbar, the popup's scrollbar is painted via Cairo on the editor DrawingArea. When the popup is wide enough that its right edge overlaps the editor's vertical scrollbar (i.e., when the window is narrow), clicks on the popup scrollbar go to the editor scrollbar instead of operating the popup. The popup scrollbar appears non-interactive in that region, while the editor scrolls under it.
With a wider editor (no overlap), popup scrollbar works correctly.
Root cause
The editor's vertical scrollbar is a native gtk4::Scrollbar widget added via overlay.add_overlay(&vertical):
src/gtk/mod.rs:5214 — let vertical = gtk4::Scrollbar::new(gtk4::Orientation::Vertical, Some(&v_adj));
src/gtk/mod.rs:5241 — overlay.add_overlay(&vertical);
The popup is Cairo-painted on the editor DrawingArea, but in the GTK widget tree the gtk4::Scrollbar overlay sits on top of the DA. GTK widget hit-testing routes clicks to the topmost widget at the click position. So clicks landing on the gtk4::Scrollbar widget (regardless of what the DA paints there visually) are intercepted by the scrollbar widget, never reach the DA, and never reach handle_mouse_click_msg.
vimcode's modal stack (reconcile_editor_hover_modal + dispatch_click with editor_hover registered) cannot arbitrate this because the click event never enters the modal-stack dispatch path. The modal stack only sees clicks the DA receives.
Why this is pre-existing, not caused by #469
The bug exists identically on the legacy quadraui_gtk::draw_rich_text_popup shim path and on the migrated Surface::RichTextPopup path. Both produce identical paint geometry and identical popup_layout.link_hit_regions / popup_layout.scrollbar. The migration in #469 was incorrectly suspected of causing this; user confirmed the same symptom reproduces on `develop` (with the shim) when the scrollbars overlap.
Surfaced during #469 investigation. Filed as separate issue so #469 can close.
Repro
- Open a file in vimcode.
- Make the editor window narrow enough that the popup scrollbar would overlap the editor vertical scrollbar (typically <100ch of viewport width).
- Hover an LSP symbol with rich documentation (markdown body long enough to need scrolling, e.g. `String` in Rust).
- Click on the popup's right-edge scrollbar where it visually overlaps the editor scrollbar.
- Observed: editor scrolls, popup scrollbar doesn't move.
- Expected: popup scrolls.
Fix options
Each is per-backend GTK wiring; the underlying primitive geometry is already shared.
Option A: Hide the native scrollbar when editor_hover is visible (cheap)
In `sync_window_scrollbars` (or similar per-frame sync), when `engine.editor_hover.is_some()`, set `scrollbars.vertical.set_can_target(false)` for the active window. When the hover dismisses, restore. Pros: ~5 lines. Cons: scrollbar still paints but is unclickable; visually confusing. Disables scrolling on the rest of the editor while hovering (probably fine — hover dismisses on any outside click anyway).
Option B: Hide the native scrollbar entirely when editor_hover overlaps it (more precise)
Same as A but only set `can_target(false)` if the hover popup_rect overlaps the scrollbar's allocation. Requires inspecting `scrollbars.vertical.allocation()` against `editor_hover_popup_rect`. ~15 lines.
Option C: Replace the native gtk4::Scrollbar with a Cairo-painted Surface::Scrollbar (right long-term answer)
Mirror what the horizontal scrollbar does (`draw_h_scrollbars` at `src/gtk/draw.rs:990` — uses `Surface::Scrollbar` + `engine.scroll_surfaces.push(ScrollSurface)` so `dispatch_click` arbitrates against the modal stack). Drop the native widget. Pros: unifies vertical + horizontal scrollbar code paths, eliminates GTK widget-tree z-order issues for every modal that overlaps the scrollbar (not just editor_hover). Cons: requires drag handling via `DragState::ScrollbarY` instead of the native widget's drag, requires cursor-indicator repaint logic to move into Cairo too. ~100–150 lines.
Recommended path
Option C in the long run (cleans up the entire per-backend scrollbar story and works for any future popup), but Option A as an immediate stopgap is reasonable if a release is imminent.
Related
Trace data confirming root cause
From #469 user smoke test, click data with editor_hover modal correctly registered:
```
[#469] reconcile_editor_hover_modal: visible=true rect=Some((199.0, 244.0, 880.0, 462.0)) links=2 sb=true
[#469] dispatch_click @(326,379) -> 1 events; modal_top=Some("editor_hover") surfaces=0
[#469] event: MouseDown { widget: Some(WidgetId("editor_hover")), ... }
[#469] reached editor_hover check
[#469] on_popup=true rect=Some((199.0, 244.0, 880.0, 462.0))
[#469] link_hit=Some("command:definition")
```
When clicks reach the DA, the modal-stack arbitration works. The overlap-bug clicks never appear in this log at all — they get consumed by the gtk4::Scrollbar widget before the DA's mouse-click controller fires.
Symptom
When the LSP editor hover popup is open and tall enough to need its own scrollbar, the popup's scrollbar is painted via Cairo on the editor DrawingArea. When the popup is wide enough that its right edge overlaps the editor's vertical scrollbar (i.e., when the window is narrow), clicks on the popup scrollbar go to the editor scrollbar instead of operating the popup. The popup scrollbar appears non-interactive in that region, while the editor scrolls under it.
With a wider editor (no overlap), popup scrollbar works correctly.
Root cause
The editor's vertical scrollbar is a native
gtk4::Scrollbarwidget added viaoverlay.add_overlay(&vertical):src/gtk/mod.rs:5214—let vertical = gtk4::Scrollbar::new(gtk4::Orientation::Vertical, Some(&v_adj));src/gtk/mod.rs:5241—overlay.add_overlay(&vertical);The popup is Cairo-painted on the editor
DrawingArea, but in the GTK widget tree thegtk4::Scrollbaroverlay sits on top of the DA. GTK widget hit-testing routes clicks to the topmost widget at the click position. So clicks landing on the gtk4::Scrollbar widget (regardless of what the DA paints there visually) are intercepted by the scrollbar widget, never reach the DA, and never reachhandle_mouse_click_msg.vimcode's modal stack (
reconcile_editor_hover_modal+dispatch_clickwitheditor_hoverregistered) cannot arbitrate this because the click event never enters the modal-stack dispatch path. The modal stack only sees clicks the DA receives.Why this is pre-existing, not caused by #469
The bug exists identically on the legacy
quadraui_gtk::draw_rich_text_popupshim path and on the migratedSurface::RichTextPopuppath. Both produce identical paint geometry and identicalpopup_layout.link_hit_regions/popup_layout.scrollbar. The migration in #469 was incorrectly suspected of causing this; user confirmed the same symptom reproduces on `develop` (with the shim) when the scrollbars overlap.Surfaced during #469 investigation. Filed as separate issue so #469 can close.
Repro
Fix options
Each is per-backend GTK wiring; the underlying primitive geometry is already shared.
Option A: Hide the native scrollbar when editor_hover is visible (cheap)
In `sync_window_scrollbars` (or similar per-frame sync), when `engine.editor_hover.is_some()`, set `scrollbars.vertical.set_can_target(false)` for the active window. When the hover dismisses, restore. Pros: ~5 lines. Cons: scrollbar still paints but is unclickable; visually confusing. Disables scrolling on the rest of the editor while hovering (probably fine — hover dismisses on any outside click anyway).
Option B: Hide the native scrollbar entirely when editor_hover overlaps it (more precise)
Same as A but only set `can_target(false)` if the hover popup_rect overlaps the scrollbar's allocation. Requires inspecting `scrollbars.vertical.allocation()` against `editor_hover_popup_rect`. ~15 lines.
Option C: Replace the native gtk4::Scrollbar with a Cairo-painted Surface::Scrollbar (right long-term answer)
Mirror what the horizontal scrollbar does (`draw_h_scrollbars` at `src/gtk/draw.rs:990` — uses `Surface::Scrollbar` + `engine.scroll_surfaces.push(ScrollSurface)` so `dispatch_click` arbitrates against the modal stack). Drop the native widget. Pros: unifies vertical + horizontal scrollbar code paths, eliminates GTK widget-tree z-order issues for every modal that overlaps the scrollbar (not just editor_hover). Cons: requires drag handling via `DragState::ScrollbarY` instead of the native widget's drag, requires cursor-indicator repaint logic to move into Cairo too. ~100–150 lines.
Recommended path
Option C in the long run (cleans up the entire per-backend scrollbar story and works for any future popup), but Option A as an immediate stopgap is reasonable if a release is imminent.
Related
Trace data confirming root cause
From #469 user smoke test, click data with editor_hover modal correctly registered:
```
[#469] reconcile_editor_hover_modal: visible=true rect=Some((199.0, 244.0, 880.0, 462.0)) links=2 sb=true
[#469] dispatch_click @(326,379) -> 1 events; modal_top=Some("editor_hover") surfaces=0
[#469] event: MouseDown { widget: Some(WidgetId("editor_hover")), ... }
[#469] reached editor_hover check
[#469] on_popup=true rect=Some((199.0, 244.0, 880.0, 462.0))
[#469] link_hit=Some("command:definition")
```
When clicks reach the DA, the modal-stack arbitration works. The overlap-bug clicks never appear in this log at all — they get consumed by the gtk4::Scrollbar widget before the DA's mouse-click controller fires.