Skip to content

Commit 3f80fb7

Browse files
authored
Simplify .Last.value handling (#1062)
* Simplify `.Last.value` handling * Move `execute_request_invisibly()` to ark_test This magically works because the ark_test side has the `self.recv_iopub_busy()` and `self.recv_iopub_idle()` overrides that buffer Variables messages * Move all execution related helpers to ark_test So they all get proper buffering * Move `is_installed()` to ark_test It runs R code, so is very R specific
1 parent b32cd7a commit 3f80fb7

File tree

5 files changed

+215
-475
lines changed

5 files changed

+215
-475
lines changed

crates/amalthea/src/fixtures/dummy_frontend.rs

Lines changed: 0 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use crate::wire::comm_msg::CommWireMsg;
1717
use crate::wire::execute_input::ExecuteInput;
1818
use crate::wire::execute_request::ExecuteRequest;
1919
use crate::wire::execute_request::ExecuteRequestPositron;
20-
use crate::wire::execute_request::JupyterPositronLocation;
2120
use crate::wire::handshake_reply::HandshakeReply;
2221
use crate::wire::input_reply::InputReply;
2322
use crate::wire::jupyter_message::JupyterMessage;
@@ -241,112 +240,6 @@ impl DummyFrontend {
241240
})
242241
}
243242

244-
/// Sends an execute request and handles the standard message flow:
245-
/// busy -> execute_input -> idle -> execute_reply.
246-
/// Asserts that the input code matches and returns the execution count.
247-
#[track_caller]
248-
pub fn execute_request_invisibly(&self, code: &str) -> u32 {
249-
self.send_execute_request(code, ExecuteRequestOptions::default());
250-
self.recv_iopub_busy();
251-
252-
let input = self.recv_iopub_execute_input();
253-
assert_eq!(input.code, code);
254-
255-
self.recv_iopub_idle();
256-
257-
let execution_count = self.recv_shell_execute_reply();
258-
assert_eq!(execution_count, input.execution_count);
259-
260-
execution_count
261-
}
262-
263-
/// Sends an execute request and handles the standard message flow with a result:
264-
/// busy -> execute_input -> execute_result -> idle -> execute_reply.
265-
/// Asserts that the input code matches and passes the result to the callback.
266-
/// Returns the execution count.
267-
#[track_caller]
268-
pub fn execute_request<F>(&self, code: &str, result_check: F) -> u32
269-
where
270-
F: FnOnce(String),
271-
{
272-
self.execute_request_with_options(code, result_check, Default::default())
273-
}
274-
275-
#[track_caller]
276-
pub fn execute_request_with_location<F>(
277-
&self,
278-
code: &str,
279-
result_check: F,
280-
code_location: JupyterPositronLocation,
281-
) -> u32
282-
where
283-
F: FnOnce(String),
284-
{
285-
self.execute_request_with_options(
286-
code,
287-
result_check,
288-
ExecuteRequestOptions {
289-
positron: Some(ExecuteRequestPositron {
290-
code_location: Some(code_location),
291-
}),
292-
..Default::default()
293-
},
294-
)
295-
}
296-
297-
#[track_caller]
298-
pub fn execute_request_with_options<F>(
299-
&self,
300-
code: &str,
301-
result_check: F,
302-
options: ExecuteRequestOptions,
303-
) -> u32
304-
where
305-
F: FnOnce(String),
306-
{
307-
self.send_execute_request(code, options);
308-
self.recv_iopub_busy();
309-
310-
let input = self.recv_iopub_execute_input();
311-
assert_eq!(input.code, code);
312-
313-
let result = self.recv_iopub_execute_result();
314-
result_check(result);
315-
316-
self.recv_iopub_idle();
317-
318-
let execution_count = self.recv_shell_execute_reply();
319-
assert_eq!(execution_count, input.execution_count);
320-
321-
execution_count
322-
}
323-
324-
/// Sends an execute request that produces an error and handles the standard message flow:
325-
/// busy -> execute_input -> execute_error -> idle -> execute_reply_exception.
326-
/// Passes the error message to the callback for custom assertions.
327-
/// Returns the execution count.
328-
#[track_caller]
329-
pub fn execute_request_error<F>(&self, code: &str, error_check: F) -> u32
330-
where
331-
F: FnOnce(String),
332-
{
333-
self.send_execute_request(code, ExecuteRequestOptions::default());
334-
self.recv_iopub_busy();
335-
336-
let input = self.recv_iopub_execute_input();
337-
assert_eq!(input.code, code);
338-
339-
let error_msg = self.recv_iopub_execute_error();
340-
error_check(error_msg);
341-
342-
self.recv_iopub_idle();
343-
344-
let execution_count = self.recv_shell_execute_reply_exception();
345-
assert_eq!(execution_count, input.execution_count);
346-
347-
execution_count
348-
}
349-
350243
/// Sends a Jupyter message on the Stdin socket
351244
pub fn send_stdin<T: ProtocolMessage>(&self, msg: T) {
352245
Self::send(&self.stdin_socket, &self.session, msg);
@@ -713,31 +606,6 @@ impl DummyFrontend {
713606
(data.content.comm_id, data.content.target_name, data.content.data)
714607
})
715608
}
716-
717-
pub fn is_installed(&self, package: &str) -> bool {
718-
let code = format!(".ps.is_installed('{package}')");
719-
self.send_execute_request(&code, ExecuteRequestOptions::default());
720-
self.recv_iopub_busy();
721-
722-
let input = self.recv_iopub_execute_input();
723-
assert_eq!(input.code, code);
724-
725-
let result = self.recv_iopub_execute_result();
726-
727-
let out = if result == "[1] TRUE" {
728-
true
729-
} else if result == "[1] FALSE" {
730-
false
731-
} else {
732-
panic!("Expected `TRUE` or `FALSE`, got '{result}'.");
733-
};
734-
735-
self.recv_iopub_idle();
736-
737-
assert_eq!(self.recv_shell_execute_reply(), input.execution_count);
738-
739-
out
740-
}
741609
}
742610

743611
impl Default for ExecuteRequestOptions {

crates/ark/src/variables/r_variables.rs

Lines changed: 20 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ use harp::utils::r_assert_type;
3535
use harp::utils::r_is_function;
3636
use harp::vector::CharacterVector;
3737
use harp::vector::Vector;
38+
use harp::RSymbol;
39+
use harp::R_ENVS;
3840
use libr::R_GlobalEnv;
3941
use libr::Rf_ScalarLogical;
4042
use libr::ENVSXP;
@@ -53,17 +55,6 @@ use crate::variables::variable::try_dispatch_view;
5355
use crate::variables::variable::PositronVariable;
5456
use crate::view::view;
5557

56-
/// Enumeration of treatments for the .Last.value variable
57-
pub enum LastValue {
58-
/// Always show the .Last.value variable in the Variables pane. This is used
59-
/// by tests to show the value without changing the global option.
60-
Always,
61-
62-
/// Use the value of the global option `positron.show_last_value` to
63-
/// determine whether to show the .Last.value variable
64-
UseOption,
65-
}
66-
6758
/**
6859
* The R Variables handler provides the server side of Positron's Variables panel, and is
6960
* responsible for creating and updating the list of variables.
@@ -90,14 +81,6 @@ pub struct RVariables {
9081
current_bindings: RThreadSafe<Vec<Binding>>,
9182
version: u64,
9283

93-
/// Whether to always show the .Last.value in the Variables pane, regardless
94-
/// of the value of positron.show_last_value
95-
show_last_value: LastValue,
96-
97-
/// Whether we are currently showing the .Last.value variable in the Variables
98-
/// pane.
99-
showing_last_value: bool,
100-
10184
/// Whether we need to send an initial `Refresh` event to the frontend,
10285
/// which is required for frontend initialization. Set on construction,
10386
/// cleared after the first `update()` call.
@@ -116,24 +99,6 @@ impl RVariables {
11699
comm: CommSocket,
117100
comm_event_tx: Sender<CommEvent>,
118101
iopub_tx: Sender<IOPubMessage>,
119-
) {
120-
// Start with default settings
121-
Self::start_with_config(env, comm, comm_event_tx, iopub_tx, LastValue::UseOption);
122-
}
123-
124-
/**
125-
* Creates a new RVariables instance with specific configuration.
126-
*
127-
* - `env`: An R environment to scan for variables, typically R_GlobalEnv
128-
* - `comm`: A channel used to send messages to the frontend
129-
* - `show_last_value`: Whether to include .Last.value in the variables list
130-
*/
131-
pub fn start_with_config(
132-
env: RObject,
133-
comm: CommSocket,
134-
comm_event_tx: Sender<CommEvent>,
135-
iopub_tx: Sender<IOPubMessage>,
136-
show_last_value: LastValue,
137102
) {
138103
// Validate that the RObject we were passed is actually an environment
139104
if let Err(err) = r_assert_type(env.sexp, &[ENVSXP]) {
@@ -159,8 +124,6 @@ impl RVariables {
159124
env,
160125
current_bindings,
161126
version: 0,
162-
show_last_value,
163-
showing_last_value: false,
164127
needs_initial_refresh: true,
165128
};
166129
environment.execution_thread();
@@ -256,10 +219,6 @@ impl RVariables {
256219
fn list_variables(&mut self) -> Vec<Variable> {
257220
let mut variables: Vec<Variable> = vec![];
258221
r_task(|| {
259-
if let Some(last_value) = self.last_value() {
260-
variables.push(last_value.var());
261-
}
262-
263222
for binding in self.current_bindings.get() {
264223
variables.push(PositronVariable::new(binding).var());
265224
}
@@ -526,45 +485,21 @@ impl RVariables {
526485
}
527486
}
528487

529-
/// Gets the value of the special variable '.Last.value' (the value of the
530-
/// last expression evaluated at the top level), if enabled.
531-
///
532-
/// Returns None in all other cases.
533-
fn last_value(&self) -> Option<PositronVariable> {
534-
// Check the cached value first
535-
let show_last_value = match self.show_last_value {
536-
LastValue::Always => true,
537-
LastValue::UseOption => {
538-
// If we aren't always showing the last value, update from the
539-
// global option
540-
let use_last_value = get_option("positron.show_last_value");
541-
match use_last_value.get_bool(0) {
542-
Ok(Some(true)) => true,
543-
_ => false,
544-
}
545-
},
546-
};
547-
548-
if show_last_value {
549-
match harp::environment::last_value() {
550-
Ok(last_robj) => Some(PositronVariable::from(
551-
String::from(".Last.value"),
552-
String::from(".Last.value"),
553-
last_robj.sexp,
554-
)),
555-
Err(err) => {
556-
// This isn't a critical error but would also be very
557-
// unexpected.
558-
log::error!("Variables: Could not evaluate .Last.value ({err:?})");
559-
None
560-
},
561-
}
562-
} else {
563-
// Last value display is disabled
564-
None
488+
/// Determines whether or not we should be showing the special `.Last.value` object
489+
fn show_last_value(&self) -> bool {
490+
match get_option("positron.show_last_value").get_bool(0) {
491+
Ok(Some(true)) => true,
492+
_ => false,
565493
}
566494
}
567495

496+
fn last_value() -> harp::Result<Binding> {
497+
Binding::new(
498+
&Environment::view(R_ENVS.base),
499+
RSymbol::from(".Last.value"),
500+
)
501+
}
502+
568503
#[tracing::instrument(level = "trace", skip_all)]
569504
fn update(&mut self) {
570505
r_task(|| {
@@ -580,10 +515,6 @@ impl RVariables {
580515

581516
let mut variables: Vec<Variable> = vec![];
582517

583-
if let (Some(var), _) = self.track_last_value() {
584-
variables.push(var);
585-
}
586-
587518
for binding in self.current_bindings.get() {
588519
variables.push(PositronVariable::new(binding).var());
589520
}
@@ -602,12 +533,6 @@ impl RVariables {
602533
let mut assigned: Vec<Variable> = vec![];
603534
let mut removed: Vec<String> = vec![];
604535

605-
match self.track_last_value() {
606-
(Some(var), _) => assigned.push(var),
607-
(None, true) => removed.push(".Last.value".to_string()),
608-
(None, false) => {},
609-
}
610-
611536
let new_bindings = self.bindings();
612537

613538
let mut old_iter = self.current_bindings.get().iter();
@@ -684,20 +609,6 @@ impl RVariables {
684609

685610
// SAFETY: The following methods must be called in an `r_task()`
686611

687-
/// Updates `self.showing_last_value` and returns the last value variable
688-
/// if it should be shown, along with whether it was previously shown.
689-
fn track_last_value(&mut self) -> (Option<Variable>, bool) {
690-
let was_showing = self.showing_last_value;
691-
692-
if let Some(last_value) = self.last_value() {
693-
self.showing_last_value = true;
694-
(Some(last_value.var()), was_showing)
695-
} else {
696-
self.showing_last_value = false;
697-
(None, was_showing)
698-
}
699-
}
700-
701612
/// Updates `self.env` to the current effective environment if it changed
702613
/// (e.g. entering or exiting debug mode). Returns `true` if switched.
703614
fn update_env(&mut self) -> bool {
@@ -721,6 +632,12 @@ impl RVariables {
721632

722633
let mut bindings: Vec<Binding> = env.iter().filter_map(|b| b.ok()).collect();
723634

635+
if self.show_last_value() {
636+
if let Some(last_value) = Self::last_value().log_err() {
637+
bindings.push(last_value);
638+
}
639+
}
640+
724641
bindings.sort_by(|a, b| a.name.cmp(&b.name));
725642

726643
RThreadSafe::new(bindings)

0 commit comments

Comments
 (0)