Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions R/profile.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ if (requireNamespace("sess", quietly = TRUE)) {
plot_backend <- Sys.getenv("SESS_PLOT_BACKEND", "auto")
sess::connect(
use_rstudioapi = as.logical(Sys.getenv("SESS_RSTUDIOAPI", "TRUE")),
use_httpgd = (plot_backend %in% c("auto", "httpgd")),
use_jgd = (plot_backend %in% c("auto", "jgd"))
plot_backend = plot_backend
)
})
}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ For example, assuming that you have installed `arf` and `jgd`, your user `settin

Please consult the relevant installation wiki pages for your OS ([Windows](https://github.com/REditorSupport/vscode-R/wiki/Installation:-Windows) | [macOS](https://github.com/REditorSupport/vscode-R/wiki/Installation:-macOS) | [Linux](https://github.com/REditorSupport/vscode-R/wiki/Installation:-Linux)) for more detailed instructions.

Set `r.plot.backend` to `"native"` to use R's configured graphics device without
opening the VS Code plot viewer automatically. The `"standard"` backend continues
to display static plots in VS Code.

## Features

* Snippets for R and R Markdown.
Expand Down
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1860,26 +1860,32 @@
"r.plot.useHttpgd": {
"type": "boolean",
"default": false,
"scope": "window",
"ignoreSync": true,
"markdownDescription": "Legacy compatibility setting for selecting the httpgd plot viewer. When `#r.plot.backend#` is `auto`, setting this to `true` selects the `httpgd` backend. Workspace settings take precedence over user settings. Within the same scope, an explicit `#r.plot.backend#` value takes precedence.\n\nRequires the `httpgd` R package version 1.2.0 or later.",
"markdownDeprecationMessage": "Deprecated: use `#r.plot.backend#` instead. This setting is retained for compatibility with existing configurations but will be removed in a future release.",
"deprecationMessage": "Deprecated: use r.plot.backend instead. This setting is retained for compatibility with existing configurations but will be removed in a future release."
},
"r.plot.backend": {
"type": "string",
"default": "auto",
"scope": "window",
"ignoreSync": true,
"enum": [
"auto",
"standard",
"httpgd",
"jgd"
"jgd",
"native"
],
"markdownEnumDescriptions": [
"Automatic: tries JGD first (if installed), then httpgd, then standard. Existing `#r.plot.useHttpgd#` configurations remain supported.",
"Standard static plot viewer (PNG/SVG)",
"httpgd-based interactive plot viewer (requires `httpgd` R package)",
"JGD-based interactive plot viewer (requires `jgd` R package)"
"JGD-based interactive plot viewer (requires `jgd` R package)",
"Use R's configured graphics device without plot integration or an automatic VS Code plot viewer"
],
"markdownDescription": "The canonical setting for selecting the plot backend.\n\nWhen set to `auto`, the best available backend is used (JGD if installed, then httpgd, then standard). For backwards compatibility, setting `#r.plot.useHttpgd#` to `true` selects httpgd while this setting remains `auto`; workspace settings take precedence over user settings, with an explicit backend value preferred within the same scope."
"markdownDescription": "The canonical setting for selecting the plot backend. `standard` uses the VS Code static plot viewer; `native` leaves R's configured graphics device untouched.\n\nWhen set to `auto`, the best available backend is used (JGD if installed, then httpgd, then standard). For backwards compatibility, setting `#r.plot.useHttpgd#` to `true` selects httpgd while this setting remains `auto`; workspace settings take precedence over user settings, with an explicit backend value preferred within the same scope."
},
"r.plot.jgd.historyLimit": {
"type": "number",
Expand Down
2 changes: 1 addition & 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.0.1
Version: 3.0.9000.9000
Authors@R: c(
person(given = "Randy",
family = "Lai",
Expand Down
78 changes: 64 additions & 14 deletions sess/R/hooks.R
Original file line number Diff line number Diff line change
@@ -1,11 +1,54 @@
#' Register VS Code runtime integrations
#'
#' @param use_rstudioapi Logical. Enable rstudioapi emulation.
#' @param use_httpgd Logical. Enable httpgd plot device if available.
#' @param use_jgd Logical. Enable jgd plot device if available.
#' @param use_httpgd Deprecated. Logical. Enable httpgd plot device if available.
#' NULL means unspecified; legacy calls default to TRUE. Use `plot_backend` instead.
#' @param use_jgd Deprecated. Logical. Enable jgd plot device if available.
#' NULL means unspecified; legacy calls default to FALSE. Use `plot_backend` instead.
#' @param plot_backend Plot backend: `auto`, `jgd`, `httpgd`, `standard`, or
#' `native`. NULL also selects `auto`. Deprecated flags select the backend
#' only when this argument is omitted.
#' @export
register_hooks <- function(use_rstudioapi = TRUE, use_httpgd = TRUE, use_jgd = FALSE) {
runtime_start(use_rstudioapi, use_httpgd, use_jgd)
register_hooks <- function(use_rstudioapi = TRUE, use_httpgd = NULL, use_jgd = NULL,
plot_backend = c("auto", "jgd", "httpgd", "standard", "native")) {
has_httpgd <- !is.null(use_httpgd)
has_jgd <- !is.null(use_jgd)
.warn_deprecated_plot_args(has_httpgd, has_jgd)
backend <- if (missing(plot_backend) && (has_httpgd || has_jgd)) {
.legacy_plot_backend(use_httpgd, use_jgd)
} else {
.resolve_plot_backend(plot_backend)
}
runtime_start(use_rstudioapi, backend)
}

.warn_deprecated_plot_args <- function(has_httpgd, has_jgd) {
old_args <- c(if (has_httpgd) "use_httpgd", if (has_jgd) "use_jgd")
if (length(old_args)) {
warning("[sess] ", paste(old_args, collapse = " and "),
if (length(old_args) == 1L) " is deprecated; use plot_backend instead." else
" are deprecated; use plot_backend instead.", call. = FALSE)
}
invisible(NULL)
}

.resolve_plot_backend <- function(plot_backend) {
if (is.null(plot_backend)) return("auto")
match.arg(plot_backend, c("auto", "jgd", "httpgd", "standard", "native"))
}

.legacy_plot_backend <- function(use_httpgd, use_jgd) {
if (is.null(use_httpgd) || is.na(use_httpgd)) use_httpgd <- TRUE
if (is.null(use_jgd) || is.na(use_jgd)) use_jgd <- FALSE
if (use_jgd && use_httpgd) "auto" else if (use_jgd) "jgd" else
if (use_httpgd) "httpgd" else "standard"
}

.select_plot_backend <- function(plot_backend, has_httpgd, has_jgd) {
if (plot_backend == "native") return("native")
if (plot_backend %in% c("auto", "jgd") && has_jgd) return("jgd")
if (plot_backend %in% c("auto", "httpgd") && has_httpgd) return("httpgd")
"standard"
}

# Send runtime notifications after task callbacks return, so transport failure
Expand All @@ -31,7 +74,9 @@ register_hooks <- function(use_rstudioapi = TRUE, use_httpgd = TRUE, use_jgd = F
#' Start the VS Code runtime integration (internal)
#'
#' @keywords internal
runtime_start <- function(use_rstudioapi = TRUE, use_httpgd = TRUE, use_jgd = FALSE) {
runtime_start <- function(use_rstudioapi = TRUE,
plot_backend = c("auto", "jgd", "httpgd", "standard", "native")) {
plot_backend <- match.arg(plot_backend)
.sess_env$runtime_start_phase <- "initialize"
state <- .runtime_state()
if (isTRUE(state$active)) {
Expand Down Expand Up @@ -168,9 +213,14 @@ runtime_start <- function(use_rstudioapi = TRUE, use_httpgd = TRUE, use_jgd = FA
}
invisible(x)
}
# 4. Plot device: JGD > httpgd > Standard
# 4. Plot device: JGD > httpgd > Standard, or no plot integration for native
.sess_env$runtime_start_phase <- "plot"
if (use_jgd && nzchar(Sys.getenv("JGD_SOCKET")) && requireNamespace("jgd", quietly = TRUE)) {
has_jgd <- plot_backend %in% c("auto", "jgd") &&
nzchar(Sys.getenv("JGD_SOCKET")) && requireNamespace("jgd", quietly = TRUE)
has_httpgd <- plot_backend %in% c("auto", "httpgd") &&
requireNamespace("httpgd", quietly = TRUE)
selected_backend <- .select_plot_backend(plot_backend, has_httpgd, has_jgd)
if (selected_backend == "jgd") {
.runtime_set_option("device", function(...) {
jgd::jgd()
.runtime_track_device()
Expand Down Expand Up @@ -202,26 +252,26 @@ runtime_start <- function(use_rstudioapi = TRUE, use_httpgd = TRUE, use_jgd = FA
invisible(TRUE)
}
reconnect_jgd_device()
} else if (use_httpgd && requireNamespace("httpgd", quietly = TRUE)) {
} else if (selected_backend == "httpgd") {
.runtime_set_option("device", function(...) {
httpgd::hgd(silent = TRUE)
.runtime_track_device()
notify_client("httpgd", list(url = httpgd::hgd_url()))
})
} else {
} else if (selected_backend == "standard") {
# If a specific interactive backend was explicitly requested but is
# unavailable, warn before silently degrading to the standard viewer.
# (use_jgd && use_httpgd means "auto", which is meant to degrade quietly.)
if (xor(use_jgd, use_httpgd)) {
if (use_jgd && !requireNamespace("jgd", quietly = TRUE)) {
# Auto is meant to degrade quietly.
if (plot_backend %in% c("jgd", "httpgd")) {
if (plot_backend == "jgd" && !requireNamespace("jgd", quietly = TRUE)) {
warning("[sess] Plot backend \"jgd\" was requested but the jgd package ",
"is not installed. Falling back to the standard plot viewer. ",
"Install jgd, or change the r.plot.backend setting.", call. = FALSE)
} else if (use_jgd) {
} else if (plot_backend == "jgd") {
warning("[sess] Plot backend \"jgd\" was requested but no renderer ",
"connection is available. Falling back to the standard plot ",
"viewer.", call. = FALSE)
} else if (use_httpgd) {
} else if (plot_backend == "httpgd") {
warning("[sess] Plot backend \"httpgd\" was requested but the httpgd ",
"package is not installed. Falling back to the standard plot ",
"viewer. Install httpgd, or change the r.plot.backend setting.",
Expand Down
42 changes: 27 additions & 15 deletions sess/R/server.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,32 @@
#' @param endpoint Character. Local named pipe / Unix domain socket endpoint.
#' If NULL, uses SESS_ENDPOINT, then SESS_DISCOVERY_FILE.
#' @param use_rstudioapi Logical. Enable rstudioapi emulation. Defaults to TRUE.
#' @param use_httpgd Logical. Use httpgd for plotting if available. Defaults to TRUE.
#' @param use_jgd Logical. Use jgd for plotting if available. Defaults to FALSE.
#' @param use_httpgd Deprecated. Logical. Use httpgd for plotting if available.
#' NULL means unspecified; legacy calls default to TRUE. Use `plot_backend` instead.
#' @param use_jgd Deprecated. Logical. Use jgd for plotting if available.
#' NULL means unspecified; legacy calls default to FALSE. Use `plot_backend` instead.
#' @param plot_backend Plot backend: `auto`, `jgd`, `httpgd`, `standard`, or
#' `native`. NULL also selects `auto`. Deprecated flags select the backend
#' only when this argument is omitted.
#' @details When SESS_DISCOVERY_FILE describes the connected endpoint, an
#' unexpected disconnect waits for a replacement endpoint in that file and
#' reconnects with the same runtime options and session identity. The optional
#' discovery jgdSocket string updates JGD_SOCKET when use_jgd is TRUE; an empty
#' discovery jgdSocket string updates JGD_SOCKET when the resolved backend
#' includes jgd; an empty
#' string clears it, and an omitted field leaves it unchanged. Set
#' `options(sess.quiet = TRUE)` to suppress the successful connection message.
#' @export
connect <- function(endpoint = NULL, use_rstudioapi = TRUE, use_httpgd = TRUE, use_jgd = FALSE) {
connect <- function(endpoint = NULL, use_rstudioapi = TRUE, use_httpgd = NULL,
use_jgd = NULL,
plot_backend = c("auto", "jgd", "httpgd", "standard", "native")) {
has_httpgd <- !is.null(use_httpgd)
has_jgd <- !is.null(use_jgd)
.warn_deprecated_plot_args(has_httpgd, has_jgd)
plot_backend <- if (missing(plot_backend) && (has_httpgd || has_jgd)) {
.legacy_plot_backend(use_httpgd, use_jgd)
} else {
.resolve_plot_backend(plot_backend)
}
# Invalidate poll callbacks and restore a previous runtime before reconnecting.
.transport_disconnect(silent = TRUE)
.sess_env$con <- NULL
Expand All @@ -39,11 +55,10 @@ connect <- function(endpoint = NULL, use_rstudioapi = TRUE, use_httpgd = TRUE, u
# endpoints from the generated attach script. Never redirect an unrelated client.
discovery <- if (nzchar(discovery_file)) .read_discovery(discovery_file) else NULL
if (!is.null(discovery) && identical(discovery$endpoint, endpoint)) {
.configure_discovery_jgd(discovery, use_jgd)
.configure_discovery_jgd(discovery, plot_backend %in% c("auto", "jgd"))
.sess_env$reconnect <- list(
path = discovery_file, endpoint = endpoint,
options = list(use_rstudioapi = use_rstudioapi,
use_httpgd = use_httpgd, use_jgd = use_jgd)
options = list(use_rstudioapi = use_rstudioapi, plot_backend = plot_backend)
)
}

Expand Down Expand Up @@ -93,13 +108,9 @@ connect <- function(endpoint = NULL, use_rstudioapi = TRUE, use_httpgd = TRUE, u
connected <- do_connect()

if (is.na(use_rstudioapi)) use_rstudioapi <- TRUE
if (is.na(use_httpgd)) use_httpgd <- TRUE
if (is.na(use_jgd)) use_jgd <- FALSE
if (isTRUE(connected) && !is.null(.sess_env$con)) {
tryCatch(
runtime_start(use_rstudioapi = use_rstudioapi,
use_httpgd = use_httpgd,
use_jgd = use_jgd),
runtime_start(use_rstudioapi = use_rstudioapi, plot_backend = plot_backend),
error = function(e) {
phase <- .sess_env$runtime_start_phase
error_call <- conditionCall(e)
Expand Down Expand Up @@ -231,8 +242,8 @@ connect <- function(endpoint = NULL, use_rstudioapi = TRUE, use_httpgd = TRUE, u
}

# Discovery configures only the optional JGD renderer, never arbitrary R state.
.configure_discovery_jgd <- function(discovery, use_jgd) {
if (!isTRUE(use_jgd) || is.null(discovery$jgdSocket)) return(invisible(NULL))
.configure_discovery_jgd <- function(discovery, jgd_enabled) {
if (!isTRUE(jgd_enabled) || is.null(discovery$jgdSocket)) return(invisible(NULL))
if (nzchar(discovery$jgdSocket)) {
Sys.setenv(JGD_SOCKET = discovery$jgdSocket)
} else {
Expand Down Expand Up @@ -278,7 +289,8 @@ connect <- function(endpoint = NULL, use_rstudioapi = TRUE, use_httpgd = TRUE, u
!identical(endpoint, settings$endpoint)) {
# Read endpoint and renderer from one snapshot, even if the file is
# replaced again while connect() runs.
.configure_discovery_jgd(discovery, settings$options$use_jgd)
.configure_discovery_jgd(discovery,
settings$options$plot_backend %in% c("auto", "jgd"))
.sess_env$reconnecting <- TRUE
tryCatch(
do.call(connect, c(list(endpoint = endpoint), settings$options)),
Expand Down
35 changes: 24 additions & 11 deletions sess/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,17 @@ When you start an R terminal from VS Code, the extension's R profile calls
sess::connect(
endpoint = NULL, # socket/pipe endpoint; see below
use_rstudioapi = TRUE, # emulate rstudioapi functions
use_httpgd = TRUE, # allow httpgd as the plot device
use_jgd = FALSE # allow jgd as the plot device
plot_backend = "auto" # or "standard", "httpgd", "jgd", "native"
)
```

If `plot_backend` is omitted, `sess::connect()` uses `auto`. The VS Code extension
passes its configured backend explicitly. Calls that supply the deprecated
`use_httpgd` or `use_jgd` arguments still use those values when `plot_backend`
is omitted. These deprecated arguments default to `NULL`, meaning unspecified.
When either is non-NULL, the unspecified flag uses its legacy default
(`use_httpgd = TRUE`, `use_jgd = FALSE`). If both are NULL, the backend is `auto`.

If `endpoint` is omitted, `connect()` resolves it in this order:

1. The `SESS_ENDPOINT` environment variable.
Expand Down Expand Up @@ -81,11 +87,11 @@ a new schema version. This discovery schema version is separate from the IPC
`protocol_version`.

The optional `jgdSocket` string describes the JGD renderer belonging to that
endpoint. When `use_jgd = TRUE`, `sess` applies it before runtime initialization,
endpoint. When `plot_backend` resolves to `auto` or `jgd`, `sess` applies it before runtime initialization,
including automatic reconnect: a nonempty string sets `JGD_SOCKET`, an empty
string unsets it (renderer unavailable), and an omitted field leaves it untouched.
A present value of another type is invalid. It does not enable JGD or override
`use_jgd`; no arbitrary environment variables or R code are accepted. VS Code
the selected backend; no arbitrary environment variables or R code are accepted. VS Code
publishes endpoint and renderer together in one atomic file replacement, with
an empty `jgdSocket` when its current backend does not provide JGD.

Expand All @@ -103,7 +109,7 @@ R's interactive features to the client:
| `View()` | Data frames, matrices, Arrow tables and polars data frames open in a paged, sortable, filterable data viewer. Lists open as JSON; other objects as R code. |
| `browseURL()`, `viewer`, `page_viewer` | URLs and local HTML files (e.g. htmlwidgets) open in the editor. |
| `?topic`, `help.search()` | Help pages open in the editor's help panel, in the column configured by `r.session.viewers.viewColumn.helpPanel`. |
| Graphics device | Plots appear in the editor's plot viewer (see below). |
| Graphics device | Plots appear in the editor's plot viewer unless `plot_backend = "native"`. |
| `rstudioapi` | Editor functions such as `getActiveDocumentContext()` and `insertText()` are emulated when `use_rstudioapi = TRUE`. |
| Top-level task callback | The client is notified after each command so it can refresh the workspace view. |

Expand All @@ -116,26 +122,33 @@ stacking hooks.

### Graphics devices

For displaying R plots, `sess` chooses a graphics device in this order:
For displaying R plots, `sess` chooses a graphics device in this order when
`plot_backend = "auto"`:

1. **jgd**, if `use_jgd = TRUE`, the `JGD_SOCKET` environment variable is set,
and the [jgd](https://cran.r-project.org/package=jgd) package is installed.
2. **httpgd**, if `use_httpgd = TRUE` and the
[httpgd](https://cran.r-project.org/package=httpgd) package is installed.
1. **jgd**, if `JGD_SOCKET` is set and the
[jgd](https://cran.r-project.org/package=jgd) package is installed.
2. **httpgd**, if the [httpgd](https://cran.r-project.org/package=httpgd)
package is installed.
3. **Standard**: plots are recorded on a null device and re-rendered by the
client on demand at the viewer's size (as SVG via
[svglite](https://cran.r-project.org/package=svglite) if installed,
otherwise PNG).

In VS Code, this is controlled by the `r.plot.backend` setting.
`plot_backend = "native"` leaves the existing R graphics device option, plot
hooks, plot task callbacks, and devices untouched. The `standard` backend continues
to use the static plot viewer. When `plot_backend` is omitted, the existing
`use_httpgd`/`use_jgd` arguments keep their previous meanings, but are
deprecated and warn when supplied with non-NULL values, including `FALSE`.
Use `plot_backend` for new code.

### Options and environment variables

| Name | Type | Purpose |
|---|---|---|
| `SESS_ENDPOINT` | env var | Socket/pipe path used by `connect()`. |
| `SESS_RSTUDIOAPI` | env var | `TRUE`/`FALSE`; passed as `use_rstudioapi` by the extension's R profile. |
| `SESS_PLOT_BACKEND` | env var | `auto`, `standard`, `httpgd` or `jgd`; sets `use_httpgd`/`use_jgd` in the extension's R profile. |
| `SESS_PLOT_BACKEND` | env var | `auto`, `standard`, `httpgd`, `jgd` or `native`; passed as `plot_backend` by the extension's R profile. |
| `JGD_SOCKET` | env var | Socket used by the jgd device; set by the extension. |
| `sess.quiet` | R option | Set to `TRUE` to suppress the successful connection message. Connection failures remain visible. |

Expand Down
Loading
Loading