Skip to content

Commit f2135eb

Browse files
authored
Merge pull request #848 from posit-dev/feature/view-function
Support functions in `View()`
2 parents 6d50751 + 8d2c19c commit f2135eb

23 files changed

+670
-128
lines changed

crates/amalthea/src/comm/variables_comm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ pub enum VariablesBackendReply {
295295
ClipboardFormatReply(FormattedVariable),
296296

297297
/// The ID of the viewer that was opened.
298-
ViewReply(String),
298+
ViewReply(Option<String>),
299299

300300
}
301301

crates/ark/src/interface.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2004,7 +2004,12 @@ impl RMain {
20042004
}
20052005
}
20062006

2007-
pub fn did_open_virtual_document(&mut self, uri: String, contents: String) {
2007+
pub fn insert_virtual_document(&mut self, uri: String, contents: String) {
2008+
log::trace!("Inserting vdoc for `{uri}`");
2009+
2010+
// Strip scheme if any. We're only storing the path.
2011+
let uri = uri.strip_prefix("ark:").unwrap_or(&uri).to_string();
2012+
20082013
// Save our own copy of the virtual document. If the LSP is currently closed
20092014
// or restarts, we can notify it of all virtual documents it should know about
20102015
// in the LSP channel setup step. It is common for the kernel to create the
@@ -2017,6 +2022,11 @@ impl RMain {
20172022
))
20182023
}
20192024

2025+
pub fn has_virtual_document(&self, uri: &String) -> bool {
2026+
let uri = uri.strip_prefix("ark:").unwrap_or(&uri).to_string();
2027+
self.lsp_virtual_documents.contains_key(&uri)
2028+
}
2029+
20202030
pub fn call_frontend_method(&self, request: UiFrontendRequest) -> anyhow::Result<RObject> {
20212031
log::trace!("Calling frontend method {request:?}");
20222032

@@ -2310,3 +2320,17 @@ fn is_auto_printing() -> bool {
23102320
car == show_fun.sexp
23112321
}
23122322
}
2323+
2324+
#[harp::register]
2325+
unsafe extern "C-unwind" fn ps_insert_virtual_document(
2326+
uri: SEXP,
2327+
contents: SEXP,
2328+
) -> anyhow::Result<SEXP> {
2329+
let uri: String = RObject::view(uri).try_into()?;
2330+
let contents: String = RObject::view(contents).try_into()?;
2331+
2332+
let main = RMain::get_mut();
2333+
main.insert_virtual_document(uri, contents);
2334+
2335+
Ok(RObject::null().sexp)
2336+
}

crates/ark/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub mod treesitter;
4343
pub mod ui;
4444
pub mod variables;
4545
pub mod version;
46+
pub mod view;
4647
pub mod viewer;
4748

4849
pub(crate) use r_task::r_task;

crates/ark/src/modules/positron/debug.R

Lines changed: 8 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -247,41 +247,21 @@ frame_info_from_srcref <- function(
247247
srcref,
248248
environment
249249
) {
250-
srcfile <- attr(srcref, "srcfile")
251-
if (is.null(srcfile)) {
250+
info <- srcref_info(srcref)
251+
if (is.null(info)) {
252252
return(NULL)
253253
}
254254

255-
# If the file name is missing but there is a `srcref`, then we can try to use
256-
# the `lines` to reconstruct a fake source file that `srcref` can point into.
257-
# This is used when debugging user functions that are entered directly into the console,
258-
# and for functions parsed with `parse(text = <text>, keep.source = TRUE)`.
259-
file <- srcfile$filename
260-
lines <- srcfile$lines
261-
262-
if (!identical(file, "") && !identical(file, "<text>")) {
263-
# TODO: Handle absolute paths by using `wd`
264-
file <- normalizePath(file, mustWork = FALSE)
265-
content <- NULL
266-
} else if (!is.null(lines)) {
267-
file <- NULL
268-
content <- paste0(lines, collapse = "\n")
269-
} else {
270-
return(NULL)
271-
}
272-
273-
range <- srcref_to_range(srcref)
274-
275255
new_frame_info(
276256
source_name = source_name,
277257
frame_name = frame_name,
278-
file = file,
279-
contents = content,
258+
file = info$file,
259+
contents = info$content,
280260
environment = environment,
281-
start_line = range$start_line,
282-
start_column = range$start_column,
283-
end_line = range$end_line,
284-
end_column = range$end_column
261+
start_line = info$range$start_line,
262+
start_column = info$range$start_column,
263+
end_line = info$range$end_line,
264+
end_column = info$range$end_column
285265
)
286266
}
287267

@@ -364,35 +344,6 @@ frame_info_unknown_range <- function(
364344
)
365345
}
366346

367-
srcref_to_range <- function(x) {
368-
n <- length(x)
369-
370-
# The first and third fields are sensitive to #line directives if they exist,
371-
# which we want to honour in order to jump to original files
372-
# rather than generated files.
373-
loc_start_line <- 1L
374-
loc_end_line <- 3L
375-
376-
# We need the `column` value rather than the `byte` value, so we
377-
# can index into a character. However the srcref documentation
378-
# allows a 4 elements vector when the bytes and column values are
379-
# the same. We account for this here.
380-
if (n >= 6) {
381-
loc_start_column <- 5L
382-
loc_end_column <- 6L
383-
} else {
384-
loc_start_column <- 2L
385-
loc_end_column <- 4L
386-
}
387-
388-
list(
389-
start_line = x[[loc_start_line]],
390-
start_column = x[[loc_start_column]],
391-
end_line = x[[loc_end_line]],
392-
end_column = x[[loc_end_column]]
393-
)
394-
}
395-
396347
new_frame_info <- function(
397348
source_name,
398349
frame_name,

crates/ark/src/modules/positron/frontend-methods.R

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,22 @@
3535
#' @export
3636
.ps.ui.navigateToFile <- function(
3737
file = character(0),
38-
line = -1L,
39-
column = -1L
38+
line = 0L,
39+
column = 0L
4040
) {
41-
file <- normalizePath(file)
41+
# Don't normalize if there's a scheme, e.g. an `ark:` URI
42+
if (!grepl("^[a-zA-Z][a-zA-Z0-9+.-]*:", file)) {
43+
file <- normalizePath(file)
44+
}
45+
46+
# Convert `-1L` for compatibility with RStudioAPI
47+
if (line < 0L) {
48+
line <- 0L
49+
}
50+
if (column < 0L) {
51+
column <- 0L
52+
}
53+
4254
.ps.Call("ps_ui_navigate_to_file", file, line, column)
4355
}
4456

crates/ark/src/modules/positron/hooks.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#
77

88
register_hooks <- function() {
9-
rebind("utils", "View", .ps.view_data_frame, namespace = TRUE)
9+
rebind("utils", "View", view, namespace = TRUE)
1010
rebind("base", "debug", new_ark_debug(base::debug), namespace = TRUE)
1111
rebind(
1212
"base",

crates/ark/src/modules/positron/r_data_explorer.R

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,15 @@
55
#
66
#
77

8-
#' @export
9-
.ps.view_data_frame <- function(x, title) {
10-
# Derive the name of the object from the expression passed to View()
11-
object_name <- .ps.as_label(substitute(x))
12-
13-
# Create a title from the name of the object if one is not provided
14-
if (missing(title)) {
15-
title <- object_name
16-
}
17-
18-
stopifnot(
19-
is.data.frame(x) || is.matrix(x),
20-
is.character(title) && length(title) == 1L && !is.na(title)
21-
)
22-
23-
# If the variable is defined in the parent frame using the same name as was
24-
# passed to View(), we can watch it for updates.
25-
#
26-
# Note that this means that (for example) View(foo) will watch the variable
27-
# foo in the parent frame, but Viewing temporary variables like
28-
# View(cbind(foo, bar)) does not create something that can be watched.
29-
var <- ""
30-
env <- NULL
31-
if (isTRUE(exists(object_name, envir = parent.frame(), inherits = FALSE))) {
32-
var <- object_name
33-
env <- parent.frame()
34-
}
35-
8+
view_data_frame <- function(x, title, var, env) {
9+
stopifnot(is_viewable_data_frame(x))
3610
invisible(.ps.Call("ps_view_data_frame", x, title, var, env))
3711
}
3812

13+
is_viewable_data_frame <- function(x) {
14+
is.data.frame(x) || is.matrix(x)
15+
}
16+
3917
.ps.null_count <- function(column) {
4018
sum(is.na(column))
4119
}

crates/ark/src/modules/positron/srcref.R

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
1-
# For debugging from R
1+
# Populate source references right there and then instead of at idle time. Used
2+
# for `View()` when called from top-level. Also useful for debugging from R. Be
3+
# careful with this one because it produces session-wide side effects that
4+
# mutate source references for all functions in the given namespace. This could
5+
# invalidate reasonable assumptions made by currently running code.
26
ns_populate_srcref <- function(ns_name) {
37
loadNamespace(ns_name)
48
.ps.Call("ps_ns_populate_srcref", ns_name)
59
}
610

11+
ns_populate_srcref_without_vdoc_insertion <- function(ns_name) {
12+
loadNamespace(ns_name)
13+
.ps.Call("ps_ns_populate_srcref_without_vdoc_insertion", ns_name)
14+
}
15+
16+
fn_populate_srcref_without_vdoc_insertion <- function(fn) {
17+
fn_env <- topenv(environment(fn))
18+
if (!isNamespace(fn_env)) {
19+
return(NULL)
20+
}
21+
22+
pkg <- getNamespaceName(fn_env)
23+
ns_populate_srcref_without_vdoc_insertion(pkg)
24+
}
25+
26+
727
# Called from Rust
828
reparse_with_srcref <- function(x, name, uri, line) {
929
if (!is.function(x)) {
@@ -83,3 +103,65 @@ do_resource_namespaces <- function(default) {
83103
resource_namespaces <- function(pkgs) {
84104
.ps.Call("ps_resource_namespaces", pkgs)
85105
}
106+
107+
srcref_info <- function(srcref) {
108+
srcfile <- attr(srcref, "srcfile")
109+
if (is.null(srcfile)) {
110+
return(NULL)
111+
}
112+
113+
# If the file name is missing but there is a `srcref`, then we can try to use
114+
# the `lines` to reconstruct a fake source file that `srcref` can point into.
115+
# This is used when debugging user functions that are entered directly into the console,
116+
# and for functions parsed with `parse(text = <text>, keep.source = TRUE)`.
117+
file <- srcfile$filename
118+
lines <- srcfile$lines
119+
120+
if (!identical(file, "") && !identical(file, "<text>")) {
121+
# TODO: Handle absolute paths by using `wd`
122+
file <- normalizePath(file, mustWork = FALSE)
123+
content <- NULL
124+
} else if (!is.null(lines)) {
125+
file <- NULL
126+
content <- paste0(lines, collapse = "\n")
127+
} else {
128+
return(NULL)
129+
}
130+
131+
range <- srcref_to_range(srcref)
132+
133+
list(
134+
file = file,
135+
content = content,
136+
range = range
137+
)
138+
}
139+
140+
srcref_to_range <- function(x) {
141+
n <- length(x)
142+
143+
# The first and third fields are sensitive to #line directives if they exist,
144+
# which we want to honour in order to jump to original files
145+
# rather than generated files.
146+
loc_start_line <- 1L
147+
loc_end_line <- 3L
148+
149+
# We need the `column` value rather than the `byte` value, so we
150+
# can index into a character. However the srcref documentation
151+
# allows a 4 elements vector when the bytes and column values are
152+
# the same. We account for this here.
153+
if (n >= 6) {
154+
loc_start_column <- 5L
155+
loc_end_column <- 6L
156+
} else {
157+
loc_start_column <- 2L
158+
loc_end_column <- 4L
159+
}
160+
161+
list(
162+
start_line = x[[loc_start_line]],
163+
start_column = x[[loc_start_column]],
164+
end_line = x[[loc_end_line]],
165+
end_column = x[[loc_end_column]]
166+
)
167+
}

crates/ark/src/modules/positron/tools.R

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,6 @@ push_rds <- function(x, path = NULL, context = "") {
127127
xs
128128
}
129129

130-
is_string <- function(x) {
131-
is.character(x) && length(x) == 1 && !is.na(x)
132-
}
133-
134130
local_options <- function(..., .frame = parent.frame()) {
135131
options <- list(...)
136132
old <- options(options)

crates/ark/src/modules/positron/utils.R

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@
6363
}
6464

6565
# Extracts a character label from a syntactically valid quoted R expression
66-
#' @export
67-
.ps.as_label <- function(expr) {
66+
as_label <- function(expr) {
6867
paste(deparse(expr, backtick = TRUE), collapse = "")
6968
}
7069

@@ -145,3 +144,11 @@ is_string <- function(x) {
145144
is_http_url <- function(x) {
146145
is_string(x) && grepl("^https?://", x)
147146
}
147+
148+
obj_address <- function(x) {
149+
.ps.Call("ps_obj_address", x)
150+
}
151+
152+
paste_line <- function(x) {
153+
paste(x, collapse = "\n")
154+
}

0 commit comments

Comments
 (0)