diff --git a/crates/ark/src/modules/positron/errors.R b/crates/ark/src/modules/positron/errors.R index 99b2fb023a..b3e6d69a5f 100644 --- a/crates/ark/src/modules/positron/errors.R +++ b/crates/ark/src/modules/positron/errors.R @@ -129,6 +129,17 @@ globalInterruptHandler <- function(cnd) { format_traceback(traceback) } +# Sources `file` with ark's global condition handlers re-registered. +# Intended to be called from a `top_level_exec()` context (which clears the +# handler stack), so that `globalCallingHandlers()` succeeds. This restores +# message/error/warning rendering to match interactive behavior during +# `.Rprofile` sourcing without putting calling-handlers on the stack that +# would block user `globalCallingHandlers()` calls. +.ps.errors.source_with_handlers <- function(file) { + initialize_errors() + sys.source(file, envir = globalenv()) +} + # If a sink is active (either on output or on messages) messages # are always streamed to `stderr`. This follows rlang behaviour # and ensures messages can be sinked from stderr consistently. @@ -278,10 +289,21 @@ initialize_errors <- function() { # Unregister all handlers and hold onto them handlers <- globalCallingHandlers(NULL) + existing_ps_handler <- vapply( + handlers, + function(h) { + isTRUE(all.equal(h, .ps.errors.globalErrorHandler)) || + isTRUE(all.equal(h, .ps.errors.globalWarningHandler)) || + isTRUE(all.equal(h, .ps.errors.globalMessageHandler)) || + isTRUE(all.equal(h, globalInterruptHandler)) + }, + logical(1) + ) + # Inject our global error handler at the end. # This allows other existing error handlers to run ahead of us. handlers <- c( - handlers, + handlers[!existing_ps_handler], list( error = .ps.errors.globalErrorHandler, warning = .ps.errors.globalWarningHandler, diff --git a/crates/ark/src/startup.rs b/crates/ark/src/startup.rs index 291ef3833d..835c3508fb 100644 --- a/crates/ark/src/startup.rs +++ b/crates/ark/src/startup.rs @@ -12,7 +12,6 @@ use std::str::FromStr; use amalthea::socket::iopub::IOPubMessage; use amalthea::wire::stream::Stream; use amalthea::wire::stream::StreamOutput; -use harp::environment::R_ENVS; use harp::exec::RFunction; use harp::exec::RFunctionExt; use libr::Rf_eval; @@ -73,14 +72,16 @@ fn source_r_profile(path: &Path) { // `globalCallingHandlers()` from being called within `.Rprofile`s (can't // call it when there are handlers on the stack). That is a common place to // register global calling handlers, including in Gabor's prompt package. - // Source in the global env to mimic R. + // Source in the global env to mimic R. We do still register our handlers + // using globalCallingHandlers so that messages, warnings and errors appear + // as expected + let positron_ns = crate::modules::ARK_ENVS.positron_ns; let result = unsafe { - let call = RFunction::new("base", "sys.source") + let call = RFunction::from(".ps.errors.source_with_handlers") .param("file", path) - .param("envir", R_ENVS.global) .call .build(); - harp::top_level_exec(|| Rf_eval(call.sexp, R_ENVS.global)) + harp::top_level_exec(|| Rf_eval(call.sexp, positron_ns)) }; let Err(err) = result else {