Skip to content

Commit 271e3a2

Browse files
Add option to show .Last.value in the Variables pane (#794)
* include .Last.value * move to update check * make it configurable * impl inspection * better error handling * tests or it didn't happen * prevent tests from relying on global state * code review feedback * make it possible to change setting on the fly * Update crates/ark/src/variables/r_variables.rs Co-authored-by: Davis Vaughan <[email protected]> --------- Co-authored-by: Davis Vaughan <[email protected]>
1 parent 6f22311 commit 271e3a2

File tree

5 files changed

+383
-14
lines changed

5 files changed

+383
-14
lines changed

crates/ark/src/variables/r_variables.rs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// r_variables.rs
33
//
4-
// Copyright (C) 2023-2024 by Posit Software, PBC
4+
// Copyright (C) 2023-2025 by Posit Software, PBC
55
//
66
//
77

@@ -26,6 +26,7 @@ use harp::environment::Environment;
2626
use harp::environment::EnvironmentFilter;
2727
use harp::exec::RFunction;
2828
use harp::exec::RFunctionExt;
29+
use harp::get_option;
2930
use harp::object::RObject;
3031
use harp::utils::r_assert_type;
3132
use harp::vector::CharacterVector;
@@ -42,6 +43,17 @@ use crate::r_task;
4243
use crate::thread::RThreadSafe;
4344
use crate::variables::variable::PositronVariable;
4445

46+
/// Enumeration of treatments for the .Last.value variable
47+
pub enum LastValue {
48+
/// Always show the .Last.value variable in the Variables pane. This is used
49+
/// by tests to show the value without changing the global option.
50+
Always,
51+
52+
/// Use the value of the global option `positron.show_last_value` to
53+
/// determine whether to show the .Last.value variable
54+
UseOption,
55+
}
56+
4557
/**
4658
* The R Variables handler provides the server side of Positron's Variables panel, and is
4759
* responsible for creating and updating the list of variables.
@@ -66,6 +78,14 @@ pub struct RVariables {
6678
/// thread. Tracked in https://github.com/posit-dev/positron/issues/1812
6779
current_bindings: RThreadSafe<Vec<Binding>>,
6880
version: u64,
81+
82+
/// Whether to always show the .Last.value in the Variables pane, regardless
83+
/// of the value of positron.show_last_value
84+
show_last_value: LastValue,
85+
86+
/// Whether we are currently showing the .Last.value variable in the Variables
87+
/// pane.
88+
showing_last_value: bool,
6989
}
7090

7191
impl RVariables {
@@ -76,6 +96,23 @@ impl RVariables {
7696
* - `comm`: A channel used to send messages to the frontend
7797
*/
7898
pub fn start(env: RObject, comm: CommSocket, comm_manager_tx: Sender<CommManagerEvent>) {
99+
// Start with default settings
100+
Self::start_with_config(env, comm, comm_manager_tx, LastValue::UseOption);
101+
}
102+
103+
/**
104+
* Creates a new RVariables instance with specific configuration.
105+
*
106+
* - `env`: An R environment to scan for variables, typically R_GlobalEnv
107+
* - `comm`: A channel used to send messages to the frontend
108+
* - `show_last_value`: Whether to include .Last.value in the variables list
109+
*/
110+
pub fn start_with_config(
111+
env: RObject,
112+
comm: CommSocket,
113+
comm_manager_tx: Sender<CommManagerEvent>,
114+
show_last_value: LastValue,
115+
) {
79116
// Validate that the RObject we were passed is actually an environment
80117
if let Err(err) = r_assert_type(env.sexp, &[ENVSXP]) {
81118
log::warn!(
@@ -99,6 +136,8 @@ impl RVariables {
99136
env,
100137
current_bindings,
101138
version: 0,
139+
show_last_value,
140+
showing_last_value: false,
102141
};
103142
environment.execution_thread();
104143
});
@@ -196,6 +235,14 @@ impl RVariables {
196235
r_task(|| {
197236
self.update_bindings(self.bindings());
198237

238+
// If the special .Last.value variable is enabled, add it to the
239+
// list. This is a special R value that doesn't have its own
240+
// binding.
241+
if let Some(last_value) = self.last_value() {
242+
self.showing_last_value = true;
243+
variables.push(last_value.var());
244+
}
245+
199246
for binding in self.current_bindings.get() {
200247
variables.push(PositronVariable::new(binding).var());
201248
}
@@ -357,6 +404,45 @@ impl RVariables {
357404
}
358405
}
359406

407+
/// Gets the value of the special variable '.Last.value' (the value of the
408+
/// last expression evaluated at the top level), if enabled.
409+
///
410+
/// Returns None in all other cases.
411+
fn last_value(&self) -> Option<PositronVariable> {
412+
// Check the cached value first
413+
let show_last_value = match self.show_last_value {
414+
LastValue::Always => true,
415+
LastValue::UseOption => {
416+
// If we aren't always showing the last value, update from the
417+
// global option
418+
let use_last_value = get_option("positron.show_last_value");
419+
match use_last_value.get_bool(0) {
420+
Ok(Some(true)) => true,
421+
_ => false,
422+
}
423+
},
424+
};
425+
426+
if show_last_value {
427+
match harp::environment::last_value() {
428+
Ok(last_robj) => Some(PositronVariable::from(
429+
String::from(".Last.value"),
430+
String::from(".Last.value"),
431+
last_robj.sexp,
432+
)),
433+
Err(err) => {
434+
// This isn't a critical error but would also be very
435+
// unexpected.
436+
log::error!("Environment: Could not evaluate .Last.value ({err:?})");
437+
None
438+
},
439+
}
440+
} else {
441+
// Last value display is disabled
442+
None
443+
}
444+
}
445+
360446
#[tracing::instrument(level = "trace", skip_all)]
361447
fn update(&mut self, request_id: Option<String>) {
362448
let mut assigned: Vec<Variable> = vec![];
@@ -371,6 +457,18 @@ impl RVariables {
371457
let mut new_iter = new_bindings.get().iter();
372458
let mut new_next = new_iter.next();
373459

460+
// Track the last value if the user has requested it. Treat this
461+
// value as assigned every time we update the Variables list.
462+
if let Some(last_value) = self.last_value() {
463+
self.showing_last_value = true;
464+
assigned.push(last_value.var());
465+
} else if self.showing_last_value {
466+
// If we are no longer showing the last value, remove it from
467+
// the list of assigned variables
468+
self.showing_last_value = false;
469+
removed.push(".Last.value".to_string());
470+
}
471+
374472
loop {
375473
match (old_next, new_next) {
376474
// nothing more to do

crates/ark/src/variables/variable.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// variable.rs
33
//
4-
// Copyright (C) 2023 by Posit Software, PBC
4+
// Copyright (C) 2023-2025 by Posit Software, PBC
55
//
66
//
77

@@ -651,7 +651,7 @@ impl PositronVariable {
651651
/**
652652
* Create a new Variable from an R object
653653
*/
654-
fn from(access_key: String, display_name: String, x: SEXP) -> Self {
654+
pub fn from(access_key: String, display_name: String, x: SEXP) -> Self {
655655
let WorkspaceVariableDisplayValue {
656656
display_value,
657657
is_truncated,
@@ -1069,6 +1069,13 @@ impl PositronVariable {
10691069
// Concrete nodes are objects that are treated as is. Accessing an element from them
10701070
// might result in special node types.
10711071

1072+
// Check for the special access key `.Last.value`; we can only get the
1073+
// value for this object by evaluating it in R
1074+
if access_key == ".Last.value" {
1075+
let last_robj = harp::environment::last_value()?;
1076+
return Ok(EnvironmentVariableNode::Concrete { object: last_robj });
1077+
}
1078+
10721079
// First try to get child using a generic method
10731080
// When building the children list of nodes that use a custom `get_children` method, the access_key is
10741081
// formatted as "custom-{index}-{length(name)}-{name}". If the access_key has this format, we call the custom `get_child_at`,

0 commit comments

Comments
 (0)