From 76b102703cc7a65c8f7f8302acba7c01c2e0c1d9 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Wed, 21 Jun 2023 12:21:08 +0200 Subject: [PATCH] Use `nframe()` to detect prompts like `readline()` --- crates/ark/src/interface.rs | 26 +++++++++++++++++++------- crates/harp/src/lib.rs | 3 ++- crates/harp/src/session.rs | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 crates/harp/src/session.rs diff --git a/crates/ark/src/interface.rs b/crates/ark/src/interface.rs index 5e7f58885..de6282589 100644 --- a/crates/ark/src/interface.rs +++ b/crates/ark/src/interface.rs @@ -309,17 +309,29 @@ pub struct PromptInfo { } fn prompt_info(prompt_c: *const c_char) -> PromptInfo { + let n_frame = unwrap!(harp::session::r_n_frame(), Err(err) => { + warn!("`r_n_frame()` failed: {}", err); + 0 + }); + trace!("prompt_info(): n_frame = '{}'", n_frame); + let prompt_slice = unsafe { CStr::from_ptr(prompt_c) }; let prompt = prompt_slice.to_string_lossy().into_owned(); - // The request is incomplete if we see the continue prompt - let continue_prompt = unsafe { r_get_option::("continue").unwrap() }; - let incomplete = prompt == continue_prompt; + // TODO: Detect with `env_is_browsed(sys.frame(sys.nframe()))` + let browser = false; - // If the current prompt doesn't match the default prompt, assume that - // we're reading use input, e.g. via 'readline()'. - let default_prompt = unsafe { r_get_option::("prompt").unwrap() }; - let user_request = !incomplete && prompt != default_prompt; + // If there are frames on the stack and we're not in a browser prompt, + // this means some user code is requesting input, e.g. via `readline()` + let user_request = !browser && n_frame > 0; + + // The request is incomplete if we see the continue prompt, except if + // we're in a user request, e.g. `readline("+ ")` + let continue_prompt = unwrap!(unsafe { r_get_option::("continue") }, Err(err) => { + warn!("`r_get_option()` failed: {}", err); + String::from("+ ") + }); + let incomplete = !user_request && prompt == continue_prompt; if incomplete { trace!("Got R prompt '{}', marking request incomplete", prompt); diff --git a/crates/harp/src/lib.rs b/crates/harp/src/lib.rs index 3439a34a5..3b27953bd 100644 --- a/crates/harp/src/lib.rs +++ b/crates/harp/src/lib.rs @@ -14,6 +14,7 @@ pub mod lock; pub mod object; pub mod protect; pub mod routines; +pub mod session; pub mod string; pub mod symbol; pub mod test; @@ -162,7 +163,7 @@ macro_rules! r_lang { ($($tts:tt)*) => {{ let value = $crate::r_pairlist!($($tts)*); - libR_sys::SET_TYPEOF(value, LISTSXP as i32); + libR_sys::SET_TYPEOF(value, LANGSXP as i32); value }} diff --git a/crates/harp/src/session.rs b/crates/harp/src/session.rs new file mode 100644 index 000000000..e3c7a65bf --- /dev/null +++ b/crates/harp/src/session.rs @@ -0,0 +1,36 @@ +// +// session.rs +// +// Copyright (C) 2023 Posit Software, PBC. All rights reserved. +// +// + +use std::sync::Once; + +use libR_sys::*; + +use crate::utils::r_try_eval_silent; +use crate::vector::integer_vector::IntegerVector; +use crate::vector::Vector; + +pub fn r_n_frame() -> crate::error::Result { + SESSION_INIT.call_once(init_interface); + + unsafe { + let ffi = r_try_eval_silent(NFRAME_CALL as SEXP, R_BaseEnv)?; + let n_frame = IntegerVector::new(ffi)?; + Ok(n_frame.get_unchecked_elt(0)) + } +} + +// Globals +static SESSION_INIT: Once = Once::new(); +static mut NFRAME_CALL: usize = 0; + +fn init_interface() { + unsafe { + let nframe_call = crate::r_lang!(crate::r_symbol!("sys.nframe")); + R_PreserveObject(nframe_call); + NFRAME_CALL = nframe_call as usize; + } +}