aboutsummaryrefslogtreecommitdiff
path: root/src/app/input
diff options
context:
space:
mode:
authorakbash <akbash@herdr.dev>2026-08-25 22:02:37 +0300
committerGitHub <noreply@github.com>2026-08-25 22:02:37 +0300
commit9ff467bda4aa0fcb79ccb4a04392b63e37ce7f37 (patch)
tree46b6122009206ab1bf2ffa94b33daeacf0341a10 /src/app/input
parent6e8b138d0f7d7d695530657a6d8dc475bd3fba2b (diff)
fix: route osc 8 file links to plugin handlers (#2942)
refs #2941 Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src/app/input')
-rw-r--r--src/app/input/mod.rs17
-rw-r--r--src/app/input/terminal.rs51
2 files changed, 63 insertions, 5 deletions
diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs
index 597d9296..10edc0c0 100644
--- a/src/app/input/mod.rs
+++ b/src/app/input/mod.rs
@@ -595,14 +595,21 @@ impl App {
return false;
};
- 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) => {}
+ let plugin_handled = match self.invoke_plugin_link_handler_for_url(&url, info.id) {
+ Ok(handled) => handled,
Err(err) => {
tracing::warn!(err = %err, url = %url, "failed to invoke plugin link handler");
+ false
}
+ };
+ if !plugin_handled && crate::app::actions::safe_web_url(&url).is_none() {
+ return false;
+ }
+
+ self.last_pane_click = None;
+ self.pending_url_click_sources.insert(source_id);
+ if plugin_handled {
+ return true;
}
match open_url(&url) {
Ok(Some(child)) => self.detached_process_children.push(child),
diff --git a/src/app/input/terminal.rs b/src/app/input/terminal.rs
index 6b3d3537..0b910621 100644
--- a/src/app/input/terminal.rs
+++ b/src/app/input/terminal.rs
@@ -1044,6 +1044,57 @@ mod tests {
#[cfg(unix)]
#[tokio::test]
+ async fn ctrl_click_osc8_file_url_invokes_plugin_link_handler() {
+ let uri = "file:///tmp/herdr-file-repro.txt";
+ let screen = format!("\x1b]8;;{uri}\x1b\\FILE\x1b]8;;\x1b\\");
+ let (mut app, info) = app_with_screen_bytes(screen.as_bytes());
+ install_test_link_handler(&mut app);
+ app.state
+ .installed_plugins
+ .get_mut("example.links")
+ .expect("test plugin")
+ .link_handlers[0]
+ .pattern = r"^file:///tmp/herdr-file-repro\.txt$".into();
+
+ let handled = app.handle_modified_url_click_with(
+ 41,
+ modified_mouse(
+ MouseEventKind::Down(MouseButton::Left),
+ info.inner_rect.x + 1,
+ info.inner_rect.y,
+ KeyModifiers::CONTROL,
+ ),
+ |_| panic!("matched file link should not use the system URL opener"),
+ );
+
+ assert!(handled);
+ let log = app
+ .state
+ .plugin_command_logs
+ .last()
+ .expect("ctrl-click should start plugin link handler");
+ assert_eq!(log.plugin_id, "example.links");
+ assert_eq!(log.action_id.as_deref(), Some("open"));
+
+ let (mut unmatched_app, unmatched_info) = app_with_screen_bytes(screen.as_bytes());
+ install_test_link_handler(&mut unmatched_app);
+ let unmatched_handled = unmatched_app.handle_modified_url_click_with(
+ 42,
+ modified_mouse(
+ MouseEventKind::Down(MouseButton::Left),
+ unmatched_info.inner_rect.x + 1,
+ unmatched_info.inner_rect.y,
+ KeyModifiers::CONTROL,
+ ),
+ |_| panic!("unmatched file link should not use the system URL opener"),
+ );
+
+ assert!(!unmatched_handled);
+ assert!(unmatched_app.state.plugin_command_logs.is_empty());
+ }
+
+ #[cfg(unix)]
+ #[tokio::test]
async fn ctrl_click_url_invokes_plugin_link_handler_but_super_click_does_not() {
let line = "see https://github.com/herdrdev/herdr/issues/398";
let col = line.find("github").expect("url host") as u16;