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
19 changes: 15 additions & 4 deletions sess/R/server.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
#' 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
#' string clears it, and an omitted field leaves it unchanged.
#' 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) {
# Invalidate poll callbacks and restore a previous runtime before reconnecting.
Expand Down Expand Up @@ -54,8 +55,12 @@ connect <- function(endpoint = NULL, use_rstudioapi = TRUE, use_httpgd = TRUE, u
}
}

# An automatic reconnect prints from later while the prompt is already shown,
# and the console does not redraw it. Startup and console calls to connect()
# are followed by the frontend's own prompt.
print_async_msg <- function(msg) {
prompt <- if (interactive()) getOption("prompt") else ""
redraw <- isTRUE(.sess_env$reconnecting) && interactive()
prompt <- if (redraw) getOption("prompt") else ""
cat(sprintf("\r%s\n\n%s", msg, prompt))
}

Expand All @@ -76,7 +81,9 @@ connect <- function(endpoint = NULL, use_rstudioapi = TRUE, use_httpgd = TRUE, u

if (is.null(.sess_env$con)) return(FALSE)

print_async_msg("[sess] Connected to VS Code")
if (!isTRUE(getOption("sess.quiet"))) {
print_async_msg("[sess] Connected to VS Code")
}

# Start the polling loop
poll_connection(.sess_env$transport_generation)
Expand Down Expand Up @@ -272,9 +279,13 @@ connect <- function(endpoint = NULL, use_rstudioapi = TRUE, use_httpgd = TRUE, u
# 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)
.sess_env$reconnecting <- TRUE
tryCatch(
do.call(connect, c(list(endpoint = endpoint), settings$options)),
error = function(e) message("[sess] Reconnection failed: ", conditionMessage(e))
error = function(e) message("[sess] Reconnection failed: ", conditionMessage(e)),
finally = {
.sess_env$reconnecting <- NULL
}
)
if (!is.null(.sess_env$con)) {
settings$endpoint <- endpoint
Expand Down
1 change: 1 addition & 0 deletions sess/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ In VS Code, this is controlled by the `r.plot.backend` setting.
| `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. |
| `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. |

## Protocol reference

Expand Down
84 changes: 84 additions & 0 deletions sess/inst/tinytest/test-connection-message.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Only automatic reconnects redraw the prompt, and sess.quiet suppresses only success.
local({
if (!requireNamespace("processx", quietly = TRUE) || .Platform$OS.type == "windows") {
return(invisible(NULL))
}

old_options <- options()[c("sess.quiet", "prompt")]
on.exit({
options(old_options)
sess:::.transport_disconnect(silent = TRUE)
}, add = TRUE)

# Tests run non-interactively, so present an interactive console to connect().
# .schedule_reconnect() is rebound too so that its retries call this copy.
env <- new.env(parent = asNamespace("sess"))
for (name in c("connect", ".schedule_reconnect")) {
fun <- get(name, asNamespace("sess"))
environment(fun) <- env
assign(name, fun, env)
}
env$interactive <- function() TRUE
opts <- list(use_rstudioapi = FALSE, use_httpgd = FALSE, use_jgd = FALSE)

connect_once <- function(quiet) {
path <- tempfile(fileext = ".sock")
server <- processx::conn_create_unix_socket(path, encoding = "")
on.exit({
sess:::.transport_disconnect(silent = TRUE)
try(close(server), silent = TRUE)
unlink(path)
}, add = TRUE)

options(sess.quiet = quiet)
capture.output(do.call(env$connect, c(list(endpoint = path), opts)))
}

options(prompt = "custom prompt> ")
visible <- connect_once(FALSE)
expect_true(any(grepl("\\[sess\\] Connected to VS Code", visible)))
expect_false(any(grepl("custom prompt>", visible, fixed = TRUE)))

quiet <- connect_once(TRUE)
expect_length(quiet, 0L)

missing <- tempfile(fileext = ".sock")
failure <- capture.output(do.call(env$connect, c(list(endpoint = missing), opts)))
expect_true(any(grepl("\\[sess\\] Failed to connect", failure)))
expect_false(any(grepl("custom prompt>", failure, fixed = TRUE)))

# Automatic reconnects print at an idle prompt, so each message redraws it.
callbacks <- list()
schedule <- function(callback, delay) {
callbacks[[length(callbacks) + 1L]] <<- callback
}
tick <- function() {
callback <- callbacks[[1L]]
callbacks <<- callbacks[-1L]
capture.output(callback())
}
replacement <- tempfile(fileext = ".sock")
discovery <- tempfile(fileext = ".json")
writeLines(jsonlite::toJSON(list(version = 1L, endpoint = replacement),
auto_unbox = TRUE), discovery)
server <- NULL
on.exit({
if (!is.null(server)) try(close(server), silent = TRUE)
unlink(c(replacement, discovery))
}, add = TRUE)

options(sess.quiet = FALSE)
env$.schedule_reconnect(list(path = discovery, endpoint = "old", options = opts),
schedule = schedule)
not_listening <- tick()
expect_true(any(grepl("\\[sess\\] Failed to connect", not_listening)))
expect_equal(tail(not_listening, 1L), "custom prompt> ")

server <- processx::conn_create_unix_socket(replacement, encoding = "")
reconnected <- tick()
expect_true(any(grepl("\\[sess\\] Connected to VS Code", reconnected)))
expect_equal(tail(reconnected, 1L), "custom prompt> ")

# Once the reconnect finishes, a manual connect returns to a fresh prompt again.
expect_false(any(grepl("custom prompt>", connect_once(FALSE), fixed = TRUE)))
})
3 changes: 2 additions & 1 deletion sess/man/connect.Rd

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

Loading