Skip to content

Commit 4e1ca8b

Browse files
committed
feat(sess): migrate session watcher to WebSockets/JSON-RPC 2.0
Introduce the 'sess' R package for WebSocket IPC, implement terminal orchestration, and modernize the session watcher architecture for improved reliability and performance.
1 parent 1bd81cc commit 4e1ca8b

37 files changed

Lines changed: 4055 additions & 1281 deletions

‎package.json‎

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "r",
33
"displayName": "R",
44
"description": "R Extension for Visual Studio Code",
5-
"version": "2.8.8",
5+
"version": "3.0.0-rc.0",
66
"author": "REditorSupport",
77
"license": "SEE LICENSE IN LICENSE",
88
"publisher": "REditorSupport",
@@ -26,7 +26,7 @@
2626
"R Markdown"
2727
],
2828
"engines": {
29-
"vscode": "^1.75.0"
29+
"vscode": "^1.110.0"
3030
},
3131
"activationEvents": [
3232
"workspaceContains:**/*.{rproj,Rproj,r,R,rd,Rd,rmd,Rmd}",
@@ -393,9 +393,14 @@
393393
"command": "r.generateCCppProperties"
394394
},
395395
{
396-
"title": "Attach Active Terminal",
396+
"title": "Activate R Session",
397397
"category": "R",
398-
"command": "r.attachActive"
398+
"command": "r.activateRSession"
399+
},
400+
{
401+
"title": "Connect to R Session",
402+
"category": "R",
403+
"command": "r.connectToSession"
399404
},
400405
{
401406
"title": "Run Command With Selection or Word in Terminal",
@@ -1615,26 +1620,26 @@
16151620
"r.session.useWebServer": {
16161621
"type": "boolean",
16171622
"default": false,
1618-
"markdownDescription": "Enable experimental use of web server in the R session to handle session requests from the extension. Changes the option `vsc.use_webserver` in R. Requires `#r.sessionWatcher#` to be set to `true`. Requires the `httpuv` R package."
1623+
"markdownDescription": "Enable experimental use of web server in the R session to handle session requests from the extension. Requires `#r.sessionWatcher#` to be set to `true`. Requires the `httpuv` R package."
16191624
},
16201625
"r.session.watchGlobalEnvironment": {
16211626
"type": "boolean",
16221627
"default": true,
1623-
"markdownDescription": "Watch the global environment to provide hover, autocompletions, and workspace viewer information. Changes the option `vsc.globalenv` in R. Requires `#r.sessionWatcher#` to be set to `true`."
1628+
"markdownDescription": "Watch the global environment to provide hover, autocompletions, and workspace viewer information. Requires `#r.sessionWatcher#` to be set to `true`."
16241629
},
16251630
"r.session.objectLengthLimit": {
16261631
"type": "integer",
16271632
"default": 2000,
1628-
"markdownDescription": "The upper limit of object length to show object details in workspace viewer and provide session symbol completion. Decrease this value if you experience significant delay after executing R commands caused by large global objects with many elements. Changes the option `vsc.object_length_limit` in R. Requires `#r.sessionWatcher#` to be set to `true`."
1633+
"markdownDescription": "The upper limit of object length to show object details in workspace viewer and provide session symbol completion. Decrease this value if you experience significant delay after executing R commands caused by large global objects with many elements. Requires `#r.sessionWatcher#` to be set to `true`."
16291634
},
16301635
"r.session.objectTimeout": {
16311636
"type": "integer",
16321637
"default": 50,
1633-
"markdownDescription": "The maximum number of milliseconds to get information of a single object in the global environment. Decrease this value if you experience significant delay after executing R commands caused by large global objects with many elements. Changes the option `vsc.object_timeout` in R. Requires `#r.sessionWatcher#` to be set to `true`."
1638+
"markdownDescription": "The maximum number of milliseconds to get information of a single object in the global environment. Decrease this value if you experience significant delay after executing R commands caused by large global objects with many elements. Requires `#r.sessionWatcher#` to be set to `true`."
16341639
},
16351640
"r.session.levelOfObjectDetail": {
16361641
"type": "string",
1637-
"markdownDescription": "How much of the object to show on hover, autocompletion, and in the workspace viewer? Changes the option `vsc.str.max.level` in R. Requires `#r.sessionWatcher#` to be set to `true`.",
1642+
"markdownDescription": "How much of the object to show on hover, autocompletion, and in the workspace viewer? Requires `#r.sessionWatcher#` to be set to `true`.",
16381643
"default": "Minimal",
16391644
"enum": [
16401645
"Minimal",
@@ -1650,12 +1655,12 @@
16501655
"r.session.emulateRStudioAPI": {
16511656
"type": "boolean",
16521657
"default": true,
1653-
"markdownDescription": "Emulate the RStudio API for addin support and other {rstudioapi} calls. Changes the option `vsc.rstudioapi` in R. Requires `#r.sessionWatcher#` to be set to `true`."
1658+
"markdownDescription": "Emulate the RStudio API for addin support and other {rstudioapi} calls. Requires `#r.sessionWatcher#` to be set to `true`."
16541659
},
16551660
"r.session.data.rowLimit": {
16561661
"type": "integer",
16571662
"default": 0,
1658-
"markdownDescription": "The maximum number of rows to be displayed in the data viewer. `0` means no limit. Changes the option `vsc.row_limit` in R. Requires `#r.sessionWatcher#` to be set to `true`."
1663+
"markdownDescription": "The maximum number of rows to be displayed in the data viewer. `0` means no limit. Changes the option `sess.row_limit` in R. Requires `#r.sessionWatcher#` to be set to `true`."
16591664
},
16601665
"r.session.data.pageSize": {
16611666
"type": "integer",
@@ -1676,7 +1681,7 @@
16761681
"properties": {
16771682
"plot": {
16781683
"type": "string",
1679-
"description": "Which view column to show the plot file on graphics update? \n\nChanges the option 'vsc.plot' in R.",
1684+
"description": "Which view column to show the plot viewer on graphics update?",
16801685
"enum": [
16811686
"Two",
16821687
"Active",
@@ -1693,7 +1698,7 @@
16931698
},
16941699
"browser": {
16951700
"type": "string",
1696-
"description": "Which view column to show the WebView triggered by browser (e.g. shiny apps)? \n\nChanges the option 'vsc.browser' in R.",
1701+
"description": "Which view column to show the WebView triggered by browser (e.g. shiny apps)?",
16971702
"enum": [
16981703
"Two",
16991704
"Active",
@@ -1710,7 +1715,7 @@
17101715
},
17111716
"viewer": {
17121717
"type": "string",
1713-
"description": "Which view column to show the WebView triggered by viewer (e.g. htmlwidgets)? \n\nChanges the option 'vsc.viewer' in R.",
1718+
"description": "Which view column to show the WebView triggered by viewer (e.g. htmlwidgets)?",
17141719
"enum": [
17151720
"Two",
17161721
"Active",
@@ -1727,7 +1732,7 @@
17271732
},
17281733
"pageViewer": {
17291734
"type": "string",
1730-
"description": "Which view column to show the WebView triggered by the page viewer (e.g. profvis)? \n\nChanges the option 'vsc.page_viewer' in R.",
1735+
"description": "Which view column to show the WebView triggered by the page viewer (e.g. profvis)?",
17311736
"enum": [
17321737
"Two",
17331738
"Active",
@@ -1744,7 +1749,7 @@
17441749
},
17451750
"view": {
17461751
"type": "string",
1747-
"description": "Which view column to show the WebView triggered by View()? \n\nChanges the option 'vsc.view' in R.",
1752+
"description": "Which view column to show the WebView triggered by View()? \n\nChanges the option 'sess.dataview' in R.",
17481753
"enum": [
17491754
"Two",
17501755
"Active",
@@ -1761,7 +1766,7 @@
17611766
},
17621767
"helpPanel": {
17631768
"type": "string",
1764-
"description": "Which view column to show the WebView triggered by the help panel? \n\nChanges the option 'vsc.help_panel' in R.",
1769+
"description": "Which view column to show the WebView triggered by the help panel? \n\nChanges the option 'sess.helpPanel' in R.",
17651770
"enum": [
17661771
"Two",
17671772
"Active",
@@ -1787,7 +1792,7 @@
17871792
"r.workspaceViewer.showObjectSize": {
17881793
"type": "boolean",
17891794
"default": false,
1790-
"markdownDescription": "Show object size when hovering over a workspace viewer item. Changes the option `vsc.show_object_size` in R."
1795+
"markdownDescription": "Show object size when hovering over a workspace viewer item."
17911796
},
17921797
"r.workspaceViewer.removeHiddenItems": {
17931798
"type": "boolean",
@@ -1821,20 +1826,20 @@
18211826
},
18221827
"r.plot.devArgs": {
18231828
"type": "object",
1824-
"markdownDescription": "The arguments for the png device to replay user graphics to show in VSCode. Requires `#r.plot.useHttpgd#` to be set to `false`. \n\nChanges the option `vsc.dev.args` in R.",
1829+
"markdownDescription": "The supplementary arguments for the rendering device (e.g., `png()` or `svglite()`) used by the standard plot viewer. Note that width and height are now handled dynamically by the responsive viewer and will be overridden. Requires `#r.plot.useHttpgd#` to be set to `false`. \n\nChanges the option `sess.devArgs` in R.",
18251830
"default": {
18261831
"width": 800,
18271832
"height": 1200
18281833
},
18291834
"properties": {
18301835
"width": {
18311836
"type": "number",
1832-
"description": "Width of the graphic device.",
1837+
"description": "Width of the graphic device (Note: This is now handled dynamically by the responsive viewer).",
18331838
"default": 480
18341839
},
18351840
"height": {
18361841
"type": "number",
1837-
"description": "Height of the graphic device.",
1842+
"description": "Height of the graphic device (Note: This is now handled dynamically by the responsive viewer).",
18381843
"default": 480
18391844
},
18401845
"units": {
@@ -1855,7 +1860,16 @@
18551860
"r.plot.useHttpgd": {
18561861
"type": "boolean",
18571862
"default": false,
1858-
"markdownDescription": "Use the httpgd-based plot viewer instead of the base VSCode-R plot viewer. Changes the option `vsc.use_httpgd` in R.\n\nRequires the `httpgd` R package version 1.2.0 or later."
1863+
"markdownDescription": "Use the httpgd-based plot viewer instead of the base VSCode-R plot viewer.\n\nRequires the `httpgd` R package version 1.2.0 or later."
1864+
},
1865+
"r.plot.format": {
1866+
"type": "string",
1867+
"default": "svglite",
1868+
"enum": [
1869+
"png",
1870+
"svglite"
1871+
],
1872+
"description": "The graphics format to use for the standard plot viewer. Requires `#r.plot.useHttpgd#` to be set to `false`."
18591873
},
18601874
"r.plot.defaults.colorTheme": {
18611875
"type": "string",
@@ -1983,56 +1997,57 @@
19831997
]
19841998
},
19851999
"scripts": {
1986-
"vscode:prepublish": "tsc -p ./html/help && tsc -p ./html/httpgd && node esbuild.js --production",
2000+
"vscode:prepublish": "node esbuild.js --production",
19872001
"changelog": "npx git-cliff v2.8.5.. -o",
1988-
"build": "tsc -p ./html/help && tsc -p ./html/httpgd && node esbuild.js",
2002+
"build": "node esbuild.js && Rscript -e \"remotes::install_local('sess', force=TRUE)\"",
19892003
"watch": "node esbuild.js --watch",
1990-
"watchHelp": "tsc -p ./html/help --watch",
1991-
"watchHttpgd": "tsc -p ./html/httpgd --watch",
1992-
"pretest": "tsc -p ./",
1993-
"test": "node ./out/test/runTest.js",
1994-
"lint": "eslint src --ext ts"
2004+
"clean": "rimraf out",
2005+
"pretest": "npm run clean && tsc -p ./",
2006+
"test": "vscode-test",
2007+
"lint": "eslint src --ext ts && Rscript -e \"lintr::lint_package('sess')\""
19952008
},
19962009
"devDependencies": {
1997-
"@types/cheerio": "^0.22.29",
1998-
"@types/ejs": "^3.0.6",
1999-
"@types/express": "^4.17.12",
2000-
"@types/fs-extra": "^9.0.11",
2001-
"@types/glob": "^8.0.0",
2010+
"@types/cheerio": "^0.22.35",
2011+
"@types/ejs": "^3.1.5",
2012+
"@types/express": "^4.17.25",
2013+
"@types/fs-extra": "^9.0.13",
20022014
"@types/highlight.js": "^10.1.0",
2003-
"@types/js-yaml": "^4.0.2",
2004-
"@types/mocha": "^8.2.2",
2005-
"@types/node": "^18.17.1",
2006-
"@types/node-fetch": "^2.5.10",
2007-
"@types/sinon": "^10.0.13",
2015+
"@types/js-yaml": "^4.0.9",
2016+
"@types/mocha": "^8.2.3",
2017+
"@types/node": "^18.19.130",
2018+
"@types/node-fetch": "^2.6.13",
2019+
"@types/sinon": "^10.0.20",
20082020
"@types/vscode": "^1.75.0",
2009-
"@types/winreg": "^1.2.31",
2010-
"@typescript-eslint/eslint-plugin": "^5.30.0",
2011-
"@typescript-eslint/parser": "^5.30.0",
2012-
"@vscode/test-electron": "^2.2.3",
2013-
"esbuild": "^0.27.3",
2014-
"eslint": "^7.28.0",
2015-
"eslint-plugin-jsdoc": "^35.1.3",
2021+
"@types/winreg": "^1.2.36",
2022+
"@types/ws": "^8.18.1",
2023+
"@typescript-eslint/eslint-plugin": "^5.62.0",
2024+
"@typescript-eslint/parser": "^5.62.0",
2025+
"@vscode/test-cli": "^0.0.12",
2026+
"@vscode/test-electron": "^2.5.2",
2027+
"esbuild": "^0.27.4",
2028+
"eslint": "^7.32.0",
2029+
"eslint-plugin-jsdoc": "^35.5.1",
20162030
"git-cliff": "^2.12.0",
2017-
"mocha": "^11.1.0",
2018-
"sinon": "^15.0.1",
2019-
"typescript": "^4.7.2"
2031+
"mocha": "^11.7.5",
2032+
"sinon": "^15.2.0",
2033+
"typescript": "^4.9.5"
20202034
},
20212035
"dependencies": {
2022-
"ag-grid-community": "^31.3.2",
2036+
"ag-grid-community": "^31.3.4",
20232037
"cheerio": "1.0.0-rc.12",
20242038
"crypto": "^1.0.1",
20252039
"ejs": "^3.1.10",
2026-
"fs-extra": "^10.0.0",
2027-
"highlight.js": "^11.9.0",
2040+
"fs-extra": "^10.1.0",
2041+
"highlight.js": "^11.11.1",
20282042
"httpgd": "0.1.6",
20292043
"jquery": "^3.7.1",
20302044
"jquery.json-viewer": "^1.5.0",
2031-
"js-yaml": "^4.1.0",
2032-
"node-fetch": "^2.6.7",
2045+
"js-yaml": "^4.1.1",
2046+
"node-fetch": "^2.7.0",
20332047
"vscode-languageclient": "^9.0.1",
20342048
"vsls": "^1.0.4753",
2035-
"winreg": "^1.2.4"
2049+
"winreg": "^1.2.5",
2050+
"ws": "^8.19.0"
20362051
},
20372052
"extensionDependencies": [
20382053
"REditorSupport.r-syntax"

‎sess/.lintr‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
linters: linters_with_defaults(
2+
line_length_linter = line_length_linter(100),
3+
object_usage_linter = NULL,
4+
object_length_linter = NULL,
5+
object_name_linter = NULL,
6+
commented_code_linter = NULL,
7+
pipe_continuation_linter = NULL)

‎sess/DESCRIPTION‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Package: sess
2+
Type: Package
3+
Title: Modern R IPC Server
4+
Version: 0.1.0
5+
Author: Gemini
6+
Maintainer: Gemini <[email protected]>
7+
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.
8+
License: MIT
9+
Encoding: UTF-8
10+
LazyData: true
11+
Imports:
12+
websocket,
13+
later,
14+
jsonlite,
15+
utils,
16+
methods,
17+
rstudioapi,
18+
svglite
19+
RoxygenNote: 7.3.3

‎sess/NAMESPACE‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Generated by roxygen2: do not edit by hand
2+
3+
export(notify_client)
4+
export(register_hooks)
5+
export(request_client)
6+
export(sess_app)

‎sess/R/dispatch.R‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#' Send a message to the client via WebSocket (JSON-RPC 2.0)
2+
#'
3+
#' This is the internal workhorse for both Notifications and Requests.
4+
#'
5+
#' @param method String. The JSON-RPC method.
6+
#' @param params List. The parameters for the method.
7+
#' @param request Logical. If TRUE, sends a Request and waits for a Response.
8+
#' @return The result of the request if request=TRUE, otherwise TRUE if sent.
9+
#' @keywords internal
10+
rpc_send <- function(method, params = list(), request = FALSE) {
11+
if (is.null(.sess_env$ws)) {
12+
return(invisible(FALSE))
13+
}
14+
15+
msg <- list(
16+
jsonrpc = "2.0",
17+
method = method,
18+
params = params
19+
)
20+
21+
req_id <- NULL
22+
if (request) {
23+
req_id <- basename(tempfile("req_", tmpdir = .sess_env$tempdir))
24+
msg$id <- req_id
25+
}
26+
27+
# Push over the websocket
28+
payload <- jsonlite::toJSON(msg, auto_unbox = TRUE, null = "null", force = TRUE)
29+
tryCatch(
30+
{
31+
.sess_env$ws$send(payload)
32+
},
33+
error = function(e) {
34+
warning("Failed to send IPC message: ", e$message)
35+
invisible(FALSE)
36+
}
37+
)
38+
39+
if (!request) {
40+
return(invisible(TRUE))
41+
}
42+
43+
# NON-BLOCKING WAIT:
44+
# Process HTTP/WS events in the background while blocking the R console execution
45+
# This prevents the R event loop from locking up.
46+
while (is.null(.sess_env$pending_responses[[req_id]])) {
47+
later::run_now()
48+
Sys.sleep(0.01)
49+
}
50+
51+
# Retrieve and clean up response
52+
response <- .sess_env$pending_responses[[req_id]]
53+
.sess_env$pending_responses[[req_id]] <- NULL
54+
55+
# Handle JSON-RPC Errors if any
56+
if (inherits(response, "json_rpc_error")) {
57+
stop(sprintf("JSON-RPC Error [%d]: %s", response$code, response$message))
58+
}
59+
60+
response
61+
}
62+
63+
#' Notify the client via WebSocket (JSON-RPC 2.0 Notification)
64+
#'
65+
#' Pushes an event instantly to the client extension via the active WebSocket connection.
66+
#'
67+
#' @param method A string representing the action (e.g., "dataview", "plot_updated")
68+
#' @param params A list containing the arguments for the command
69+
#' @export
70+
notify_client <- function(method, params = list()) {
71+
rpc_send(method, params, request = FALSE)
72+
}
73+
74+
#' Emulate rstudioapi (or any client action) synchronously but without blocking the R Event Loop
75+
#'
76+
#' @param action String of the action name
77+
#' @param args List of arguments
78+
#' @export
79+
request_client <- function(action, args = list()) {
80+
rpc_send(action, args, request = TRUE)
81+
}

0 commit comments

Comments
 (0)