diff options
| author | akbash <akbash@herdr.dev> | 2026-08-18 00:11:20 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-18 00:11:20 +0300 |
| commit | 76211757ecce73cba981bdf6704954b25a100a8c (patch) | |
| tree | 8686202ba5d6bb32d13e9d911b3aa4236bb30a6c /src/app/input/terminal.rs | |
| parent | 19022d03c902de586009b1d156bc20b01ce48135 (diff) | |
fix: reap ctrl-click url openers (#2906)
* fix: reap ctrl-click url openers
refs #2903
* fix: scope linux opener test import
refs #2903
* test: isolate url opener regression
refs #2903
---------
Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src/app/input/terminal.rs')
| -rw-r--r-- | src/app/input/terminal.rs | 61 |
1 files changed, 61 insertions, 0 deletions
diff --git a/src/app/input/terminal.rs b/src/app/input/terminal.rs index 12d09e6a..e5b40a74 100644 --- a/src/app/input/terminal.rs +++ b/src/app/input/terminal.rs @@ -411,6 +411,8 @@ mod tests { use crossterm::event::{KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind}; use ratatui::layout::Rect; + #[cfg(target_os = "linux")] + use super::super::wait_for_detached_process_reap; use super::super::{app_for_mouse_test, mouse, numbered_lines_bytes}; #[cfg(unix)] use super::super::{unique_temp_path, wait_for_file}; @@ -885,6 +887,65 @@ mod tests { assert!(app.selection_highlight_clear_deadline.is_none()); } + #[cfg(target_os = "linux")] + #[tokio::test] + async fn ctrl_click_url_reaps_failed_opener() { + let opener_dir = unique_temp_path("url-opener"); + let record_path = opener_dir.join("record"); + let opener_path = opener_dir.join("xdg-open"); + std::fs::create_dir_all(&opener_dir).expect("fake opener directory"); + std::fs::write( + &opener_path, + "#!/bin/sh\nprintf '%s\\n%s\\n' \"$$\" \"$1\" > \"$2\"\nexit 3\n", + ) + .expect("fake opener script"); + + let url = "https://example.com/akbash-2903"; + let line = format!("see {url}"); + let (mut app, info) = app_with_screen_bytes(line.as_bytes()); + let col = info.inner_rect.x + line.find("example").expect("url host") as u16; + let handled = app.handle_modified_url_click_with( + 41, + modified_mouse( + MouseEventKind::Down(MouseButton::Left), + col, + info.inner_rect.y, + KeyModifiers::CONTROL, + ), + |clicked_url| { + std::process::Command::new("/bin/sh") + .arg(&opener_path) + .arg(clicked_url) + .arg(&record_path) + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn() + .map(Some) + }, + ); + assert!(handled); + + let record = wait_for_file(&record_path); + let mut lines = record.lines(); + let pid = lines + .next() + .expect("opener pid") + .parse::<u32>() + .expect("numeric opener pid"); + assert_eq!(lines.next(), Some(url)); + + let reaped = wait_for_detached_process_reap(&mut app, pid).await; + if !reaped { + unsafe { + libc::waitpid(pid as libc::pid_t, std::ptr::null_mut(), 0); + } + } + + let _ = std::fs::remove_dir_all(&opener_dir); + assert!(reaped, "failed URL opener child {pid} was not reaped"); + } + #[cfg(unix)] #[tokio::test] async fn ctrl_click_url_does_not_forward_release_to_mouse_reporting_pane() { |
