Skip to content
Open
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
120 changes: 2 additions & 118 deletions package-lock.json

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

4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2000,7 +2000,6 @@
"@types/sinon": "^10.0.20",
"@types/vscode": "^1.75.0",
"@types/winreg": "^1.2.36",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vscode/test-cli": "^0.0.12",
Expand All @@ -2026,8 +2025,7 @@
"js-yaml": "^4.1.1",
"node-fetch": "^2.7.0",
"vscode-languageclient": "^9.0.1",
"winreg": "^1.2.5",
"ws": "^8.19.0"
"winreg": "^1.2.5"
},
"extensionDependencies": [
"REditorSupport.r-syntax"
Expand Down
8 changes: 5 additions & 3 deletions sess/DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ Title: Modern R IPC Server
Version: 3.0.0
Author: Gemini
Maintainer: Gemini <gemini@example.com>
Description: Implements a high-performance HTTP and WebSocket server for R Inter-Process Communication, replacing legacy file-system watchers. This package provides a generic protocol for IDEs and other clients to communicate with an R session.
Description: Implements a high-performance IPC client for R sessions using Unix domain sockets and Windows named pipes. Replaces legacy file-system watcher based workflows while keeping JSON-RPC communication semantics for IDE/editor integration.
License: MIT
Encoding: UTF-8
LazyData: true
Imports:
websocket,
Imports:
processx (>= 3.5.0),
later,
jsonlite,
utils,
methods,
rstudioapi,
svglite
Suggests:
testthat (>= 3.0.0)
Config/roxygen2/version: 8.0.0
79 changes: 44 additions & 35 deletions sess/R/dispatch.R
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
#' Send a message to the client via WebSocket (JSON-RPC 2.0)
#'
#' This is the internal workhorse for both Notifications and Requests.
#' Write a JSON object to the IPC pipe as a NDJSON line (internal)
#' @keywords internal
ipc_write <- function(data) {
con <- .sess_env$con
if (is.null(con)) return(invisible(FALSE))

line <- paste0(jsonlite::toJSON(data, auto_unbox = TRUE, null = "null", force = TRUE), "\n")
tryCatch(
{
remainder <- processx::conn_write(con, line)

# processx::conn_write() may perform a partial write and return
# remaining bytes; keep writing until all data is flushed.
while (is.raw(remainder) && length(remainder) > 0) {
remainder <- processx::conn_write(con, remainder)
}

invisible(TRUE)
},
error = function(e) {
warning("[sess] Failed to send IPC message: ", e$message)
invisible(FALSE)
}
)
}

#' Send a message to the client via IPC pipe (JSON-RPC 2.0)
#'
#' @param method String. The JSON-RPC method.
#' @param params List. The parameters for the method.
#' @param request Logical. If TRUE, sends a Request and waits for a Response.
#' @return The result of the request if request=TRUE, otherwise TRUE if sent.
#' @keywords internal
rpc_send <- function(method, params = list(), request = FALSE) {
if (is.null(.sess_env$ws)) {
if (is.null(.sess_env$con)) {
return(invisible(FALSE))
}

Expand All @@ -24,45 +48,30 @@ rpc_send <- function(method, params = list(), request = FALSE) {
msg$id <- req_id
}

# Push over the websocket
payload <- jsonlite::toJSON(msg, auto_unbox = TRUE, null = "null", force = TRUE)
tryCatch(
{
.sess_env$ws$send(payload)
},
error = function(e) {
warning("Failed to send IPC message: ", e$message)
invisible(FALSE)
}
)
ipc_write(msg)

if (!request) {
return(invisible(TRUE))
}
invisible(TRUE)
} else {
# NON-BLOCKING WAIT:
# Run later callbacks (which include poll_connection) while waiting for a response.
while (is.null(.sess_env$pending_responses[[req_id]])) {
later::run_now()
Sys.sleep(0.01)
}

# NON-BLOCKING WAIT:
# Process HTTP/WS events in the background while blocking the R console execution
# This prevents the R event loop from locking up.
while (is.null(.sess_env$pending_responses[[req_id]])) {
later::run_now()
Sys.sleep(0.01)
}
response <- .sess_env$pending_responses[[req_id]]
.sess_env$pending_responses[[req_id]] <- NULL

# Retrieve and clean up response
response <- .sess_env$pending_responses[[req_id]]
.sess_env$pending_responses[[req_id]] <- NULL
if (inherits(response, "json_rpc_error")) {
stop(sprintf("JSON-RPC Error [%d]: %s", response$code, response$message))
}

# Handle JSON-RPC Errors if any
if (inherits(response, "json_rpc_error")) {
stop(sprintf("JSON-RPC Error [%d]: %s", response$code, response$message))
response
}

response
}

#' Notify the client via WebSocket (JSON-RPC 2.0 Notification)
#'
#' Pushes an event instantly to the client extension via the active WebSocket connection.
#' Notify the client via IPC pipe (JSON-RPC 2.0 Notification)
#'
#' @param method A string representing the action (e.g., "dataview", "plot_updated")
#' @param params A list containing the arguments for the command
Expand Down
Loading
Loading