diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-24 02:09:22 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-24 02:10:47 +0300 |
| commit | 58e812b238844fa05233eadc537081a1f0d06f36 (patch) | |
| tree | 61d071cf4a6c33b58cf8a35f56aa8174fe084252 /src/app/input | |
| parent | 36de78dd4fa4e2c203ee955f18caf82a6bca7e80 (diff) | |
fix: consume handled url click gestures
refs #1761
Diffstat (limited to 'src/app/input')
| -rw-r--r-- | src/app/input/mod.rs | 34 | ||||
| -rw-r--r-- | src/app/input/terminal.rs | 50 |
2 files changed, 82 insertions, 2 deletions
diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs index 738b7963..16713222 100644 --- a/src/app/input/mod.rs +++ b/src/app/input/mod.rs @@ -256,6 +256,31 @@ impl App { } pub(super) fn handle_mouse(&mut self, mouse: MouseEvent) { + self.handle_mouse_from_input_source(super::LOCAL_INPUT_SOURCE, mouse); + } + + pub(super) fn handle_mouse_from_input_source( + &mut self, + source_id: super::InputSourceId, + mouse: MouseEvent, + ) { + match mouse.kind { + MouseEventKind::Down(MouseButton::Left) => { + self.pending_url_click_sources.remove(&source_id); + } + MouseEventKind::Drag(MouseButton::Left) + if self.pending_url_click_sources.contains(&source_id) => + { + return; + } + MouseEventKind::Up(MouseButton::Left) + if self.pending_url_click_sources.remove(&source_id) => + { + return; + } + _ => {} + } + if self.state.popup_pane.is_some() { self.handle_popup_mouse(mouse); return; @@ -284,7 +309,7 @@ impl App { } } - if self.handle_modified_url_click(mouse) { + if self.handle_modified_url_click(source_id, mouse) { return; } @@ -469,7 +494,11 @@ impl App { self.focus_pane_internal_via_api(ws_idx, pane_id); } - fn handle_modified_url_click(&mut self, mouse: MouseEvent) -> bool { + fn handle_modified_url_click( + &mut self, + source_id: super::InputSourceId, + mouse: MouseEvent, + ) -> bool { if self.state.mode != Mode::Terminal || !matches!(mouse.kind, MouseEventKind::Down(MouseButton::Left)) || !mouse.modifiers.contains(modified_url_click_modifier()) @@ -490,6 +519,7 @@ impl App { }; self.last_pane_click = None; + self.pending_url_click_sources.insert(source_id); match self.invoke_plugin_link_handler_for_url(&url, info.id) { Ok(true) => return true, Ok(false) => {} diff --git a/src/app/input/terminal.rs b/src/app/input/terminal.rs index 61c6bbb3..371803da 100644 --- a/src/app/input/terminal.rs +++ b/src/app/input/terminal.rs @@ -328,6 +328,7 @@ impl App { } pub(crate) fn release_input_source_headless(&mut self, source_id: crate::app::InputSourceId) { + self.pending_url_click_sources.remove(&source_id); for pressed in self.take_pressed_keys_for_source(source_id) { let release = pressed .key @@ -337,6 +338,7 @@ impl App { } pub(crate) async fn release_input_source(&mut self, source_id: crate::app::InputSourceId) { + self.pending_url_click_sources.remove(&source_id); for pressed in self.take_pressed_keys_for_source(source_id) { let release = pressed .key @@ -846,6 +848,54 @@ mod tests { #[cfg(unix)] #[tokio::test] + async fn ctrl_click_url_does_not_forward_release_to_mouse_reporting_pane() { + let line = "see https://github.com/ogulcancelik/herdr/issues/1761"; + let col = line.find("github").expect("url host") as u16; + let (mut app, info) = app_with_screen_bytes(b""); + let pane_id = app.state.workspaces[0].tabs[0].root_pane; + let screen = format!("\x1b[?1049h\x1b[?1000h\x1b[?1006h{line}"); + let (runtime, mut input_rx) = + crate::terminal::TerminalRuntime::test_with_channel_and_scrollback_bytes( + info.inner_rect.width, + info.inner_rect.height, + 0, + screen.as_bytes(), + 4, + ); + app.state.insert_test_runtime(pane_id, runtime); + install_test_link_handler(&mut app); + let mut send_mouse = |source_id, kind, column, modifiers| { + app.handle_mouse_from_input_source( + source_id, + modified_mouse(kind, column, info.inner_rect.y, modifiers), + ); + }; + let down = MouseEventKind::Down(MouseButton::Left); + let up = MouseEventKind::Up(MouseButton::Left); + let url_x = info.inner_rect.x + col; + + send_mouse(41, down, url_x, KeyModifiers::CONTROL); + send_mouse(42, down, info.inner_rect.x, KeyModifiers::empty()); + send_mouse(42, up, info.inner_rect.x, KeyModifiers::empty()); + send_mouse(41, up, url_x, KeyModifiers::empty()); + + assert_eq!(app.state.plugin_command_logs.len(), 1); + assert_eq!( + input_rx.try_recv().expect("other source mouse down"), + Bytes::from_static(b"\x1b[<0;1;1M") + ); + assert_eq!( + input_rx.try_recv().expect("other source mouse up"), + Bytes::from_static(b"\x1b[<0;1;1m") + ); + assert!( + input_rx.try_recv().is_err(), + "handled URL click must not leave an unmatched release for the pane" + ); + } + + #[cfg(unix)] + #[tokio::test] async fn ctrl_click_url_invokes_plugin_link_handler_but_super_click_does_not() { let line = "see https://github.com/ogulcancelik/herdr/issues/398"; let col = line.find("github").expect("url host") as u16; |
