Skip to content

Commit 105d35c

Browse files
authored
Add back correct S4 support to the variables pane (#658)
* Add back correct S4 support * match with `starts_with()`
1 parent 68de61c commit 105d35c

File tree

3 files changed

+84
-0
lines changed

3 files changed

+84
-0
lines changed

crates/ark/src/variables/variable.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use harp::utils::pairlist_size;
3030
use harp::utils::r_altrep_class;
3131
use harp::utils::r_assert_type;
3232
use harp::utils::r_classes;
33+
use harp::utils::r_format_s4;
3334
use harp::utils::r_inherits;
3435
use harp::utils::r_is_altrep;
3536
use harp::utils::r_is_data_frame;
@@ -100,6 +101,7 @@ impl WorkspaceVariableDisplayValue {
100101
LANGSXP => Self::from_language(value),
101102
_ if r_is_matrix(value) => Self::from_matrix(value)?,
102103
RAWSXP | LGLSXP | INTSXP | REALSXP | STRSXP | CPLXSXP => Self::from_default(value)?,
104+
_ if r_is_s4(value) => Self::from_s4(value)?,
103105
_ => Self::from_error(Error::Anyhow(anyhow!(
104106
"Unexpected type {}",
105107
r_type2char(r_typeof(value))
@@ -336,6 +338,20 @@ impl WorkspaceVariableDisplayValue {
336338
Ok(Self::new(display_value, false))
337339
}
338340

341+
fn from_s4(value: SEXP) -> anyhow::Result<Self> {
342+
let result: Vec<String> = RObject::from(r_format_s4(value)?).try_into()?;
343+
let mut display_value = String::from("");
344+
for val in result.iter() {
345+
for char in val.chars() {
346+
if display_value.len() >= MAX_DISPLAY_VALUE_LENGTH {
347+
return Ok(Self::new(display_value, true));
348+
}
349+
display_value.push(char);
350+
}
351+
}
352+
Ok(Self::new(display_value, false))
353+
}
354+
339355
fn from_default(value: SEXP) -> anyhow::Result<Self> {
340356
let formatted = FormattedVector::new(RObject::from(value))?;
341357

@@ -1886,6 +1902,16 @@ mod tests {
18861902
)
18871903
.unwrap();
18881904

1905+
let path = vec![];
1906+
let vars = PositronVariable::inspect(env.clone(), &path).unwrap();
1907+
1908+
assert_eq!(vars.len(), 1);
1909+
// Matching equality is not nice because the default `format` method for S4 objects
1910+
// uses different quoting characters on Windows vs Unix.
1911+
// Unix: <S4 class ‘ddiMatrix’ [package “Matrix”] with 4 slots>
1912+
// Windows: <S4 class 'ddiMatrix' [package "Matrix"] with 4 slots>
1913+
assert!(vars[0].display_value.starts_with("<S4 class"));
1914+
18891915
// Inspect the S4 object
18901916
let path = vec![String::from("x")];
18911917
let fields = PositronVariable::inspect(env.clone(), &path).unwrap();
@@ -2032,4 +2058,20 @@ mod tests {
20322058
assert_eq!(vars[0].display_value, "[]");
20332059
});
20342060
}
2061+
2062+
#[test]
2063+
fn test_s4_with_different_length() {
2064+
r_task(|| {
2065+
let env = Environment::new_empty().unwrap();
2066+
// Matrix::Matrix objects have length != 1, but their format() method returns a length 1 character
2067+
// describing their class.
2068+
let value = harp::parse_eval_base("Matrix::Matrix(0, nrow= 10, ncol = 10)").unwrap();
2069+
env.bind("x".into(), &value);
2070+
2071+
let path = vec![];
2072+
let vars = PositronVariable::inspect(env.into(), &path).unwrap();
2073+
assert_eq!(vars.len(), 1);
2074+
assert!(vars[0].display_value.starts_with("<S4 class"),);
2075+
})
2076+
}
20352077
}

crates/harp/src/modules/format.R

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,34 @@ init_test_format <- function() {
8686

8787
environment()
8888
}
89+
90+
harp_format_s4 <- function(x, ...) {
91+
# For S4 values we assume that the formatted value is a character vector of length 1,
92+
# even if the value has length > 1. This is because the formatted value is typically
93+
# the class of the object, which is a single string.
94+
# If for some reason the formatted value is not a character vector of length 1, we
95+
# check if the result is a character vector the same length as `value`.
96+
out <- base::format(x, ...)
97+
98+
if (length(out) != 1 && length(out) != length(x)) {
99+
log_trace(sprintf(
100+
"`format()` method for <%s> should return a character vector of length 1 or the same length as the object.",
101+
class_collapsed(x)
102+
))
103+
return(format_fallback_s4(x))
104+
}
105+
106+
if (!is.character(out)) {
107+
log_trace(sprintf(
108+
"`format()` method for <%s> should return a character vector.",
109+
class_collapsed(x)
110+
))
111+
return(format_fallback_s4(x))
112+
}
113+
114+
out
115+
}
116+
117+
format_fallback_s4 <- function(x) {
118+
paste0("<S4 class '", class_collapsed(x), "'>")
119+
}

crates/harp/src/utils.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,17 @@ pub fn r_format_vec(x: SEXP) -> Result<SEXP> {
735735
}
736736
}
737737

738+
pub fn r_format_s4(x: SEXP) -> Result<SEXP> {
739+
if !r_is_s4(x) {
740+
return Err(Error::UnexpectedType(r_typeof(x), vec![S4SXP]));
741+
}
742+
743+
let out = RFunction::new("", "harp_format_s4")
744+
.add(x)
745+
.call_in(unsafe { HARP_ENV.unwrap() })?;
746+
Ok(out.sexp)
747+
}
748+
738749
pub fn r_subset_vec(x: SEXP, indices: Vec<i64>) -> Result<SEXP> {
739750
let env = unsafe { HARP_ENV.unwrap() };
740751
let indices: Vec<i64> = indices.into_iter().map(|i| i + 1).collect();

0 commit comments

Comments
 (0)