Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
818edaf
feat: use jdv as the data viewer backend when it is installed
eitsupi Oct 16, 2026
29ff428
feat: view Arrow data through the jdv transport
eitsupi Oct 16, 2026
06c3891
fix: connect jdv transport tests to the Node-form socket path
eitsupi Oct 17, 2026
a6555a8
refactor: connect jdv to the session transport for every data viewer …
eitsupi Oct 17, 2026
f19ad0d
refactor: serve all jdv views over the session transport
eitsupi Oct 17, 2026
7136f96
feat: follow the variable passed to View() with jdv on R 4.6
eitsupi Oct 17, 2026
c71d741
feat: report failed jdv view updates in the data viewer
eitsupi Oct 17, 2026
426083b
style: wrap long lines in the jdv View() hook and its test
eitsupi Oct 17, 2026
30091e4
feat: add a setting to follow the variable passed to View() in the da…
eitsupi Oct 17, 2026
920cfcc
feat: add a Refresh button to the jdv data viewer and keep tracked up…
eitsupi Oct 17, 2026
d40fa19
chore: bump sess development version to 3.2.9000.9000
eitsupi Oct 17, 2026
6233430
refactor: use jdv_binding() and jdv error classes in the View() hook
eitsupi Oct 17, 2026
687d2f5
feat: follow jdv API v1 in the data viewer
eitsupi Oct 17, 2026
bb69e8b
fix: preserve jdv datetime filter operands and clarify duration units
eitsupi Oct 18, 2026
eb00208
feat: use jdv for explicit View() calls in Interactive
eitsupi Oct 18, 2026
8ab8498
feat: follow the latest jdv protocol in the data viewer
eitsupi Oct 18, 2026
d075f84
refactor: drop notes about older jdv from the data viewer comments
eitsupi Oct 18, 2026
4ed2c80
test: express jdv contract checks without naming retired formats
eitsupi Oct 18, 2026
4112507
feat: open drillable jdv grid cells as child views
eitsupi Oct 18, 2026
195d5b4
feat: back Interactive table and list previews with jdv handles in sess
eitsupi Oct 18, 2026
bbcf33a
feat: serve Interactive inline tables and lists from jdv handles
eitsupi Oct 18, 2026
5599b4f
style: fix hanging indents flagged by lintr in interactive previews
eitsupi Oct 18, 2026
ee8c2e6
fix: stop stalled jdv stream and transport requests
eitsupi Oct 18, 2026
7389ff4
fix: label jdv missing-value filters as NA
eitsupi Oct 18, 2026
478fc5b
fix: fill byte-limited jdv inline table pages
eitsupi Oct 18, 2026
55460e0
fix: preserve raw jdv preview pages until normalization
eitsupi Oct 18, 2026
55799df
fix: open interactive viewer paths through jdv handles
eitsupi Oct 19, 2026
8fcc2b7
fix: preserve Arrow numeric display strings
eitsupi Oct 19, 2026
abb6d04
perf: skip unchanged data viewer header refreshes
eitsupi Oct 19, 2026
fbe86b7
feat: show Arrow stream progress and known row totals
eitsupi Oct 19, 2026
c4a79cb
refactor: simplify Arrow row-count hints
eitsupi Oct 19, 2026
39cb0f4
fix: bound inline previews by the encoded event size
eitsupi Oct 19, 2026
de07a67
style: fix lintr warnings in interactive handle tests
eitsupi Oct 19, 2026
cbf78d5
perf: avoid repeated buffer copies when receiving jdv frames
eitsupi Oct 19, 2026
5062666
perf: fetch only displayed columns in the data viewer
eitsupi Oct 19, 2026
6c73a81
refactor: drop the explicit column fallback
eitsupi Oct 19, 2026
0cf8698
feat: read sparse jdv missing-row positions
eitsupi Oct 19, 2026
470c448
feat: add empty-string filters for jdv text columns
eitsupi Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions R/attach_sess.R
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ vscode_r_attach_sess <- function(endpoint, pkg_path, managed_root, consent_dir,
timeout_seconds = 180,
setup_timeout_seconds = 300,
use_rstudioapi = TRUE,
track_variables = TRUE,
attachment_id = NULL,
auto_activate = TRUE) {
if (!is.null(attachment_id) &&
Expand Down Expand Up @@ -235,6 +236,11 @@ vscode_r_attach_sess <- function(endpoint, pkg_path, managed_root, consent_dir,
connect <- get("connect", envir = ns, inherits = FALSE)
args <- list(endpoint = endpoint, plot_backend = plot_backend,
use_rstudioapi = use_rstudioapi)
# Tracking is on by default, so only a disabled setting is passed. This
# keeps older sess namespaces working.
if (!isTRUE(track_variables)) {
args$track_variables <- FALSE
}
# Preserve compatibility with older sess namespaces and helper stubs
# when the caller uses the legacy, untagged attach path.
if (!is.null(attachment_id)) {
Expand Down
4 changes: 3 additions & 1 deletion R/profile.R
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ invisible(local({
plot_backend <- Sys.getenv("SESS_PLOT_BACKEND", "auto")
connect <- get("connect", envir = ns, inherits = FALSE)
use_rstudioapi <- as.logical(Sys.getenv("SESS_RSTUDIOAPI", "TRUE"))
result <- connect(use_rstudioapi = use_rstudioapi, plot_backend = plot_backend)
track_variables <- as.logical(Sys.getenv("SESS_TRACK_VARIABLES", "TRUE"))
result <- connect(use_rstudioapi = use_rstudioapi, plot_backend = plot_backend,
track_variables = track_variables)
isTRUE(result)
}

Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2108,6 +2108,11 @@
"default": 500,
"markdownDescription": "The maximum number of rows per page in the data viewer. `0` means to disable pagination."
},
"r.session.viewers.trackVariables": {
"type": "boolean",
"default": true,
"markdownDescription": "Update the data viewer when the variable passed to `View()` is changed in R, for example `View(df)` followed by `df <- transform(df, ...)`. Requires R >= 4.6 and the [jdv](https://github.com/eitsupi/jdv) package; otherwise the data viewer shows a fixed snapshot. Applies to R sessions started or attached after the setting is changed. `options(sess.dataviewer.track = TRUE/FALSE)` in R takes precedence over this setting. Requires `#r.sessionWatcher#` to be set to `true`."
},
"r.session.viewers.viewColumn": {
"type": "object",
"markdownDescription": "Which view column should R-related webviews be displayed? Requires `#r.sessionWatcher#` to be set to `true`.",
Expand Down Expand Up @@ -2635,6 +2640,7 @@
"dependencies": {
"@vscode/codicons": "0.0.46-24",
"ag-grid-community": "^36.2.0",
"apache-arrow": "21.2.0",
"cheerio": "1.0.0-rc.12",
"ejs": "^3.1.10",
"highlight.js": "^11.11.1",
Expand Down
36 changes: 36 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion sess/DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: sess
Type: Package
Title: High-Performance IPC Bridge for R Sessions
Version: 3.2.0
Version: 3.2.9000.9000
Authors@R: c(
person(given = "Randy",
family = "Lai",
Expand Down Expand Up @@ -37,6 +37,7 @@ Suggests:
htmlwidgets,
systemfonts,
httpgd,
jdv,
jgd,
svglite,
tinytest
Expand Down
152 changes: 152 additions & 0 deletions sess/R/handlers.R
Original file line number Diff line number Diff line change
Expand Up @@ -1291,3 +1291,155 @@ handle_dataview_dispose <- function(params) {
}
TRUE
}

# jdv data viewer backend (experimental) -----------------------------------
#
# When the jdv package is available, View() registers data frames, matrices,
# lists and Arrow data with jdv instead of the sess data viewer. jdv talks to the
# viewer over its own local transport, whose address VS Code provides per
# session; sess only registers the views. Set `options(sess.dataviewer = "sess")`
# to keep using the sess data viewer.

.jdv_arrow_classes <- c("ArrowTabular", "RecordBatchReader", "nanoarrow_array_stream",
"polars_data_frame")

.jdv_is_arrow <- function(x) {
inherits(x, .jdv_arrow_classes)
}

# Return a row-count hint when an Arrow source exposes dimensions without reading
# its data. Forward-only readers and streams return NULL from nrow().
.jdv_arrow_nrow <- function(x) {
count <- tryCatch(nrow(x), error = function(e) NULL)
if (is.numeric(count) && !is.complex(count) && length(count) == 1L &&
is.finite(count) && count >= 0 && count == floor(count)) {
unname(as.numeric(count))
}
}

# Make sure jdv has a persistent connection to this session's viewer host.
# Returns FALSE, leaving the user's own jdv state untouched, when the sess data
# viewer should be used instead: jdv is missing or opted out, VS Code offers no
# transport, or jdv is already connected elsewhere or owned by an embedding host.
# Every failure, whether a jdv_error (jdv_error_in_use, jdv_error_connect_failed)
# or the transport request itself failing, means the same fallback, so errors
# are not told apart.
.jdv_connect <- function(address = NULL) {
if (identical(getOption("sess.dataviewer", "auto"), "sess") ||
!requireNamespace("jdv", quietly = TRUE)) {
return(FALSE)
}
tryCatch({
connection <- jdv::jdv_connection()
if (identical(connection$state, "connected")) {
return(is.character(.sess_env$jdv_address) &&
identical(connection$address, .sess_env$jdv_address))
}
if (!identical(connection$state, "disconnected")) {
return(FALSE)
}
# Callers that cannot wait for a reply (an inspection being answered) pass it in.
if (is.null(address)) {
address <- request_client("jdv/address")$address
}
if (!is.character(address) || length(address) != 1L || is.na(address) ||
!nzchar(address)) {
return(FALSE)
}
# An explicit address leaves discovery.json and the `jdv.address` option of
# other jdv hosts the user may run alone.
jdv::jdv_connect(address)
.sess_env$jdv_address <- address
TRUE
}, error = function(e) FALSE)
}

# Close the connection that .jdv_connect() opened, with all of its views.
.jdv_release <- function() {
if (is.null(.sess_env$jdv_address)) {
return(invisible(NULL))
}
address <- .sess_env$jdv_address
.sess_env$jdv_address <- NULL
if (isNamespaceLoaded("jdv")) {
try({
if (identical(jdv::jdv_connection()$address, address)) {
jdv::jdv_disconnect()
}
}, silent = TRUE)
}
invisible(NULL)
}

# The variable that View(<expression>) was called with, for jdv to follow.
# jdv::jdv_binding() does the lookup without evaluating anything: it returns
# list(name, env) for a symbol bound in `env` or one of its parents, and NULL
# for other expressions, unreadable bindings (promises, active bindings), unbound
# names and R older than 4.6.0.
# sess only adds the gates: the VS Code setting `r.session.viewers.trackVariables`
# (passed to connect() as `track_variables`, default on), which
# `options(sess.dataviewer.track = TRUE/FALSE)` overrides in both directions.
.jdv_track_binding <- function(expression, env) {
track <- getOption("sess.dataviewer.track", .sess_env$track_variables %||% TRUE)
if (!is.symbol(expression) || !nzchar(as.character(expression)) || !isTRUE(track) ||
!requireNamespace("jdv", quietly = TRUE)) {
return(NULL)
}
tryCatch(jdv::jdv_binding(expression, env), error = function(e) NULL)
}

# Show `x` with jdv. Returns FALSE when jdv cannot be used, so the caller falls
# back to the sess data viewer.
#
# The title is jdv's display slot: View() with the same title replaces the view
# and increments its generation. Grids are paged so that sorting and filtering
# stay in R. When `binding` names the variable that holds `x`, jdv follows it and
# rebuilds the view as the next generation after the variable is reassigned. jdv
# rejects the binding with a jdv_error if it does not hold `x` (for example
# View(x) called through do.call()); then the view is registered without
# tracking, since View() must not fail because of it. Arrow sources are
# sequential and cannot be tracked, so they never get a binding.
#
# jdv checks tracked variables from its own task callback and sends the update
# over the transport itself, so sess needs no task callback of its own for it.
# Only an embedding host that collects jdv_notifications() must call
# jdv_rpc(NULL) first.
.jdv_show <- function(x, title_key, binding = NULL, address = NULL) {
if (.jdv_is_arrow(x) && !requireNamespace("nanoarrow", quietly = TRUE)) {
return(FALSE)
}
row_count <- if (.jdv_is_arrow(x)) .jdv_arrow_nrow(x) else NULL
title <- if (nzchar(title_key)) title_key else NULL
# Only jdv errors about the binding are retried without it; anything else
# ends in FALSE so that View() falls back to the sess data viewer.
.jdv_show_registered(function() {
if (!is.null(binding) && !.jdv_is_arrow(x)) {
registered <- tryCatch({
jdv::jdv_register(x, title = title, mode = "paged", binding = binding)
}, jdv_error = function(e) FALSE)
if (!identical(registered, FALSE)) {
return(registered)
}
}
jdv::jdv_register(x, title = title, mode = "paged")
}, address, row_count)
}

# Connect and register a view, sharing the same failure contract for values and
# Interactive handles. A row-count notification is best-effort display metadata.
.jdv_show_registered <- function(register, address = NULL, row_count = NULL) {
if (!.jdv_connect(address)) return(FALSE)
tryCatch({
registration <- register()
if (!is.null(row_count)) {
# A notification is only a hint; it must not make a successful View fail.
try(notify_client("jdv/rowCount", list(
view = registration$id,
generation = registration$generation,
nrow = row_count,
address = .sess_env$jdv_address
)), silent = TRUE)
}
TRUE
}, error = function(e) FALSE)
}
18 changes: 13 additions & 5 deletions sess/R/hooks.R
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,9 @@ runtime_start <- function(use_rstudioapi = TRUE,
owner <- as.character(expression)
}
force(title)

if (is.null(.sess_env$view_owner) && isTRUE(.sess_env$interactive_connected) &&
.interactive_rich_value(x)) {
return(invisible(NULL))
}
# Resolve the variable behind View(<symbol>) before x is forced, so jdv can
# follow it. The lookup starts at the caller, never at this function's frame.
tracked_binding <- .jdv_track_binding(original_expression, parent.frame())

view_type <- if (dataview_is_table(x)) {
"table"
Expand All @@ -88,6 +86,16 @@ runtime_start <- function(use_rstudioapi = TRUE,
"object"
}
title_key <- paste(as.character(title), collapse = "\n")
if ((view_type %in% c("table", "list") || .jdv_is_arrow(x)) &&
.jdv_show(x, title_key, tracked_binding)) {
return(invisible(NULL))
}
# Explicit View() in Interactive should open the normal data viewer when
# jdv is available. Inline rich output remains the fallback when it is not.
if (is.null(.sess_env$view_owner) && isTRUE(.sess_env$interactive_connected) &&
.interactive_rich_value(x)) {
return(invisible(NULL))
}
owner <- owner %||% title_key
registry_key <- paste0(view_type, ":", owner)
dataview_registry <- .sess_env$dataview_registry
Expand Down
Loading
Loading