diff --git a/crates/amalthea/src/comm/ui_comm.rs b/crates/amalthea/src/comm/ui_comm.rs index 0aa195569..01a1256b9 100644 --- a/crates/amalthea/src/comm/ui_comm.rs +++ b/crates/amalthea/src/comm/ui_comm.rs @@ -292,6 +292,13 @@ pub struct ShowHtmlFileParams { pub height: i64, } +/// Parameters for the OpenWithSystem method. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct OpenWithSystemParams { + /// The file path to open with the system default application + pub path: String, +} + /** * Backend RPC request types for the ui comm */ @@ -502,6 +509,10 @@ pub enum UiFrontendEvent { #[serde(rename = "show_html_file")] ShowHtmlFile(ShowHtmlFileParams), + /// Open a file or folder with the system default application + #[serde(rename = "open_with_system")] + OpenWithSystem(OpenWithSystemParams), + /// This event is used to signal that the stored messages the front-end /// replays when constructing multi-output plots should be reset. This /// happens for things like a holoviews extension being changed. diff --git a/crates/ark/src/browser.rs b/crates/ark/src/browser.rs index 37cd7f521..f9d6a630f 100644 --- a/crates/ark/src/browser.rs +++ b/crates/ark/src/browser.rs @@ -5,15 +5,16 @@ // // -use amalthea::comm::ui_comm::ShowUrlParams; -use amalthea::comm::ui_comm::UiFrontendEvent; use harp::object::RObject; +use harp::utils::r_normalize_path; use libr::Rf_ScalarLogical; use libr::SEXP; use crate::help::message::HelpEvent; use crate::help::message::ShowHelpUrlParams; use crate::interface::RMain; +use crate::ui::events::send_open_with_system_event; +use crate::ui::events::send_show_url_event; #[harp::register] pub unsafe extern "C-unwind" fn ps_browse_url(url: SEXP) -> anyhow::Result { @@ -35,33 +36,32 @@ fn handle_help_url(url: String) -> anyhow::Result<()> { } unsafe fn ps_browse_url_impl(url: SEXP) -> anyhow::Result { - // Extract URL. - let url = RObject::view(url).to::()?; - let _span = tracing::trace_span!("browseURL", url = %url).entered(); + // Extract URL string for analysis + let url_string = RObject::view(url).to::()?; + let _span = tracing::trace_span!("browseURL", url = %url_string).entered(); // Handle help server requests. - if is_help_url(&url) { + if is_help_url(&url_string) { log::trace!("Help is handling URL"); - handle_help_url(url)?; + handle_help_url(url_string)?; return Ok(Rf_ScalarLogical(1)); - } else { - log::trace!("Help is not handling URL"); } - // TODO: What is the right thing to do outside of Positron when - // `options(browser =)` is called? Right now we error. - - // For all other URLs, create a ShowUrl event and send it to the main - // thread; Positron will handle it. - let params = ShowUrlParams { url }; - let event = UiFrontendEvent::ShowUrl(params); - - let main = RMain::get(); - let ui_comm_tx = main - .get_ui_comm_tx() - .ok_or_else(|| anyhow::anyhow!("UI comm not connected."))?; - - ui_comm_tx.send_event(event); + // Handle web URLs + if is_web_url(&url_string) { + log::trace!("Handling web URL"); + send_show_url_event(&url_string)?; + return Ok(Rf_ScalarLogical(1)); + } + // This is probably a file path? Send to the front end and ask for system + // default opener. + log::trace!("Treating as file path and asking system to open"); + let path = r_normalize_path(url.into())?; + send_open_with_system_event(&path)?; Ok(Rf_ScalarLogical(1)) } + +fn is_web_url(url: &str) -> bool { + url.starts_with("http://") || url.starts_with("https://") +} diff --git a/crates/ark/src/ui/events.rs b/crates/ark/src/ui/events.rs index ee67f1bc3..97efee245 100644 --- a/crates/ark/src/ui/events.rs +++ b/crates/ark/src/ui/events.rs @@ -6,6 +6,7 @@ // use amalthea::comm::ui_comm::OpenEditorParams; +use amalthea::comm::ui_comm::OpenWithSystemParams; use amalthea::comm::ui_comm::OpenWorkspaceParams; use amalthea::comm::ui_comm::Position; use amalthea::comm::ui_comm::Range; @@ -96,23 +97,43 @@ pub unsafe extern "C-unwind" fn ps_ui_set_selection_ranges(ranges: SEXP) -> anyh Ok(R_NilValue) } -#[harp::register] -pub unsafe extern "C-unwind" fn ps_ui_show_url(url: SEXP) -> anyhow::Result { +pub fn send_show_url_event(url: &str) -> anyhow::Result<()> { let params = ShowUrlParams { - url: RObject::view(url).try_into()?, + url: url.to_string(), }; - let event = UiFrontendEvent::ShowUrl(params); let main = RMain::get(); let ui_comm_tx = main .get_ui_comm_tx() - .ok_or_else(|| ui_comm_not_connected("ui_show_url"))?; + .ok_or_else(|| ui_comm_not_connected("show_url"))?; ui_comm_tx.send_event(event); + Ok(()) +} + +#[harp::register] +pub unsafe extern "C-unwind" fn ps_ui_show_url(url: SEXP) -> anyhow::Result { + let url_string = RObject::view(url).to::()?; + send_show_url_event(&url_string)?; Ok(R_NilValue) } +pub fn send_open_with_system_event(path: &str) -> anyhow::Result<()> { + let params = OpenWithSystemParams { + path: path.to_string(), + }; + let event = UiFrontendEvent::OpenWithSystem(params); + + let main = RMain::get(); + let ui_comm_tx = main + .get_ui_comm_tx() + .ok_or_else(|| ui_comm_not_connected("open_with_system"))?; + ui_comm_tx.send_event(event); + + Ok(()) +} + pub fn ps_ui_robj_as_ranges(ranges: SEXP) -> anyhow::Result> { let ranges_as_r_objects: Vec = RObject::view(ranges).try_into()?; let ranges_as_result: Result>, _> = ranges_as_r_objects