From ed1f608ed7a3980d489fd752cea8a65d2da2059e Mon Sep 17 00:00:00 2001 From: kuddev Date: Sat, 18 Jul 2026 22:46:44 +0800 Subject: [PATCH] fix: route paste to clicked split pane --- nebula_app/src/window_context.rs | 23 ++++++++++++++--------- nebula_app/src/window_context/split.rs | 13 +++++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/nebula_app/src/window_context.rs b/nebula_app/src/window_context.rs index f2ae752..da9aa41 100644 --- a/nebula_app/src/window_context.rs +++ b/nebula_app/src/window_context.rs @@ -60,6 +60,12 @@ mod split; /// terminal's event tag. Panes live in [`WindowContext::panes`]. type PaneId = u64; +/// Mouse buttons whose press is an interaction with terminal content and must +/// therefore update split focus before the event is routed to a pane. +fn pane_focus_button(button: &MouseButton) -> bool { + matches!(button, MouseButton::Left | MouseButton::Middle | MouseButton::Right) +} + /// Sentinel pane id for document-viewer tabs. A doc tab owns no pane and no /// PTY: its `Layout::Leaf(DOC_PANE_ID)` deliberately resolves to `None` in /// every `pane()` lookup, which is exactly the degraded behaviour those call @@ -2194,7 +2200,10 @@ impl WindowContext { } } - // In a split, a left click moves keyboard focus to the clicked pane. + // In a split, a terminal-content mouse press moves keyboard focus to + // the clicked pane. Right-click paste and middle-click selection paste + // must target the pane under the pointer as well; otherwise they use + // the previous keyboard focus and write into a neighbouring terminal. // Resolve focus from the click position before routing this batch so the // click lands on the pane the user aimed at. if !matches!(self.active_layout(), Layout::Leaf(_)) { @@ -2218,17 +2227,13 @@ impl WindowContext { matches!( e, WinitEvent::WindowEvent { - event: WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Left, - .. - }, + event: WindowEvent::MouseInput { state: ElementState::Pressed, button, .. }, .. - } + } if pane_focus_button(button) ) }); - // A left click always refocuses the clicked pane; focus-follows-mouse - // also refocuses on plain pointer motion. + // A terminal mouse press always refocuses the clicked pane; + // focus-follows-mouse also refocuses on plain pointer motion. let target = if clicked { latest_pos.or(Some((self.mouse.x as f32, self.mouse.y as f32))) } else if ffm { diff --git a/nebula_app/src/window_context/split.rs b/nebula_app/src/window_context/split.rs index d37a597..43ec2b7 100644 --- a/nebula_app/src/window_context/split.rs +++ b/nebula_app/src/window_context/split.rs @@ -852,10 +852,23 @@ impl WindowContext { #[cfg(test)] mod resize_layout_tests { + use winit::event::MouseButton; + use nebula_terminal::grid::Dimensions; use super::{Layout, collect_layout, pane_viewport_contains, terminal_content_rect}; use crate::display::{SizeInfo, SplitDirection}; + use crate::window_context::pane_focus_button; + + #[test] + fn all_terminal_mouse_buttons_move_split_focus_before_routing() { + assert!(pane_focus_button(&MouseButton::Left)); + assert!(pane_focus_button(&MouseButton::Middle)); + assert!(pane_focus_button(&MouseButton::Right)); + assert!(!pane_focus_button(&MouseButton::Back)); + assert!(!pane_focus_button(&MouseButton::Forward)); + assert!(!pane_focus_button(&MouseButton::Other(8))); + } #[test] fn pane_layout_keeps_message_bar_rows_out_of_the_pty_viewport() {