diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-15 17:29:45 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-15 17:45:44 +0300 |
| commit | 0099c10881fd05ddecd3b535700d41eb3f42b633 (patch) | |
| tree | 0c05211953394414b35fe6930b5b45594294833a /src | |
| parent | 07261e09be60ac0c145685fd11f94351cc0e5ba6 (diff) | |
fix: use ctrl-click for captured pane links on macos
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/input/mod.rs | 12 | ||||
| -rw-r--r-- | src/app/input/terminal.rs | 74 | ||||
| -rw-r--r-- | src/raw_input.rs | 21 |
3 files changed, 96 insertions, 11 deletions
diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs index 339f7293..cb4b0d46 100644 --- a/src/app/input/mod.rs +++ b/src/app/input/mod.rs @@ -23,23 +23,13 @@ enum WheelRouting { const WORKSPACE_DRAG_THRESHOLD: u16 = 1; const TAB_DRAG_THRESHOLD: u16 = 1; -#[cfg(target_os = "macos")] -fn modified_url_click_modifier() -> KeyModifiers { - KeyModifiers::SUPER -} - -#[cfg(not(target_os = "macos"))] fn modified_url_click_modifier() -> KeyModifiers { KeyModifiers::CONTROL } #[cfg(test)] #[test] -fn modified_url_click_modifier_matches_platform_primary_modifier() { - #[cfg(target_os = "macos")] - assert_eq!(modified_url_click_modifier(), KeyModifiers::SUPER); - - #[cfg(not(target_os = "macos"))] +fn modified_url_click_modifier_matches_terminal_mouse_reporting() { assert_eq!(modified_url_click_modifier(), KeyModifiers::CONTROL); } diff --git a/src/app/input/terminal.rs b/src/app/input/terminal.rs index 60f2d4cc..e13412a2 100644 --- a/src/app/input/terminal.rs +++ b/src/app/input/terminal.rs @@ -261,6 +261,45 @@ mod tests { .is_some_and(crate::selection::Selection::is_visible)); } + #[cfg(unix)] + fn install_test_link_handler(app: &mut App) { + let plugin_root = std::env::temp_dir(); + app.state.installed_plugins = std::collections::HashMap::from([( + "example.links".to_string(), + crate::api::schema::InstalledPluginInfo { + plugin_id: "example.links".into(), + name: "Links".into(), + version: "0.1.0".into(), + min_herdr_version: "0.6.10".into(), + description: None, + manifest_path: plugin_root.join("herdr-plugin.toml").display().to_string(), + plugin_root: plugin_root.display().to_string(), + enabled: true, + platforms: None, + build: Vec::new(), + actions: vec![crate::api::schema::PluginManifestAction { + id: "open".into(), + title: "Open link".into(), + description: None, + contexts: Vec::new(), + platforms: None, + command: vec!["sh".into(), "-c".into(), ":".into()], + }], + events: Vec::new(), + panes: Vec::new(), + link_handlers: vec![crate::api::schema::PluginManifestLinkHandler { + id: "github-issue".into(), + title: "Open GitHub issue".into(), + pattern: "^https://github\\.com/[^/]+/[^/]+/(issues|pull)/[0-9]+$".into(), + action: "open".into(), + platforms: None, + }], + source: crate::api::schema::PluginSourceInfo::default(), + warnings: Vec::new(), + }, + )]); + } + #[tokio::test] async fn dragging_selection_above_pane_autoscrolls_and_extends_into_scrollback() { let mut app = app_for_mouse_test(); @@ -465,6 +504,41 @@ mod tests { assert!(app.selection_highlight_clear_deadline.is_none()); } + #[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; + + let (mut ctrl_app, ctrl_info) = app_with_screen_bytes(line.as_bytes()); + install_test_link_handler(&mut ctrl_app); + ctrl_app.handle_mouse(modified_mouse( + MouseEventKind::Down(MouseButton::Left), + ctrl_info.inner_rect.x + col, + ctrl_info.inner_rect.y, + KeyModifiers::CONTROL, + )); + + let ctrl_log = ctrl_app + .state + .plugin_command_logs + .last() + .expect("ctrl-click should start plugin link handler"); + assert_eq!(ctrl_log.plugin_id, "example.links"); + assert_eq!(ctrl_log.action_id.as_deref(), Some("open")); + + let (mut super_app, super_info) = app_with_screen_bytes(line.as_bytes()); + install_test_link_handler(&mut super_app); + super_app.handle_mouse(modified_mouse( + MouseEventKind::Down(MouseButton::Left), + super_info.inner_rect.x + col, + super_info.inner_rect.y, + KeyModifiers::SUPER, + )); + + assert!(super_app.state.plugin_command_logs.is_empty()); + } + #[tokio::test] async fn pane_cell_url_resolver_finds_visible_url() { let line = "see https://example.com/pr/307."; diff --git a/src/raw_input.rs b/src/raw_input.rs index fddcb3fd..346da476 100644 --- a/src/raw_input.rs +++ b/src/raw_input.rs @@ -818,6 +818,27 @@ mod tests { assert_eq!(mouse.kind, MouseEventKind::Down(MouseButton::Left)); assert_eq!(mouse.column, 19); assert_eq!(mouse.row, 9); + assert_eq!(mouse.modifiers, KeyModifiers::empty()); + } + + #[test] + fn parses_sgr_mouse_observable_modifiers() { + let cases = [ + (b"\x1b[<8;20;10M".as_slice(), KeyModifiers::ALT), + (b"\x1b[<16;20;10M".as_slice(), KeyModifiers::CONTROL), + ( + b"\x1b[<24;20;10M".as_slice(), + KeyModifiers::ALT | KeyModifiers::CONTROL, + ), + ]; + + for (input, expected) in cases { + let (RawInputEvent::Mouse(mouse), _) = extract_one_event(input).unwrap() else { + panic!("expected mouse"); + }; + assert_eq!(mouse.modifiers, expected); + assert!(!mouse.modifiers.contains(KeyModifiers::SUPER)); + } } #[test] |
