fix(input): keep pending url clicks across host focus loss (#2291)
Opening a URL raises the browser, which takes focus away from the host terminal before the mouse release arrives. release_input_source(_headless) cleared pending_url_click_sources on that focus loss, so the release was forwarded to the pane and the agent opened the same URL again. Clear the set in clear_input_source instead, whose only production caller is remove_client. refs #2290 Co-authored-by: kataokatsuki <kataokatsuki@users.noreply.github.com>
This commit is contained in:
parent
cc9fa47540
commit
1997b88b3f
|
|
@ -350,7 +350,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);
|
||||
// Pending URL clicks survive this call; see clear_input_source.
|
||||
for pressed in self.take_pressed_keys_for_source(source_id) {
|
||||
let release = pressed
|
||||
.key
|
||||
|
|
@ -360,7 +360,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);
|
||||
// Pending URL clicks survive this call; see clear_input_source.
|
||||
for pressed in self.take_pressed_keys_for_source(source_id) {
|
||||
let release = pressed
|
||||
.key
|
||||
|
|
@ -924,6 +924,61 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
async fn outer_focus_loss_does_not_forward_pending_url_click_release_to_pane() {
|
||||
let line = "see https://github.com/herdrdev/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 url_x = info.inner_rect.x + col;
|
||||
|
||||
app.handle_mouse_from_input_source(
|
||||
41,
|
||||
modified_mouse(
|
||||
MouseEventKind::Down(MouseButton::Left),
|
||||
url_x,
|
||||
info.inner_rect.y,
|
||||
KeyModifiers::CONTROL,
|
||||
),
|
||||
);
|
||||
assert_eq!(app.state.plugin_command_logs.len(), 1);
|
||||
|
||||
// Opening the URL raises the browser, so the host terminal loses focus
|
||||
// while the button is still down.
|
||||
app.route_client_events_from(
|
||||
41,
|
||||
vec![crate::raw_input::RawInputEvent::OuterFocusLost],
|
||||
false,
|
||||
);
|
||||
|
||||
app.handle_mouse_from_input_source(
|
||||
41,
|
||||
modified_mouse(
|
||||
MouseEventKind::Up(MouseButton::Left),
|
||||
url_x,
|
||||
info.inner_rect.y,
|
||||
KeyModifiers::empty(),
|
||||
),
|
||||
);
|
||||
|
||||
assert!(
|
||||
input_rx.try_recv().is_err(),
|
||||
"focus loss must not clear a pending URL click, so its release must stay out of the pane"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
async fn ctrl_click_url_invokes_plugin_link_handler_but_super_click_does_not() {
|
||||
|
|
|
|||
|
|
@ -1778,6 +1778,11 @@ impl App {
|
|||
}
|
||||
|
||||
pub(crate) fn clear_input_source(&mut self, source_id: InputSourceId) {
|
||||
// Call this only when the input source is gone for good. A pending URL
|
||||
// click has to outlive a plain focus change, because opening the URL
|
||||
// raises the browser and costs the host terminal its focus before the
|
||||
// mouse release arrives.
|
||||
self.pending_url_click_sources.remove(&source_id);
|
||||
self.release_input_source_headless(source_id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2710,7 +2710,8 @@ impl HeadlessServer {
|
|||
.iter()
|
||||
.any(|event| matches!(event, crate::raw_input::RawInputEvent::OuterFocusLost))
|
||||
{
|
||||
self.app.clear_input_source(client_id);
|
||||
// Focus loss is not a teardown, so the pending URL click stays.
|
||||
self.app.release_input_source_headless(client_id);
|
||||
}
|
||||
}
|
||||
let events = events_for_app_routing(events, source_was_foreground, source_is_full_app);
|
||||
|
|
|
|||
Loading…
Reference in New Issue