From f46da7438d4810bee099bff2ba618774df30d539 Mon Sep 17 00:00:00 2001 From: Tal Hadad Date: Mon, 17 Jul 2023 20:34:28 +0300 Subject: [PATCH 01/28] wip: start working on connection to the requests by a tcp server. Fixes #1359 --- R/session/init.R | 2 +- R/session/vsc.R | 83 ++++++--- package.json | 1 + src/extension.ts | 5 + src/liveShare/index.ts | 1 - src/liveShare/shareSession.ts | 21 ++- src/session.ts | 278 +++++++++++++++++++---------- src/{liveShare => }/virtualDocs.ts | 0 yarn.lock | 39 ++++ 9 files changed, 305 insertions(+), 125 deletions(-) rename src/{liveShare => }/virtualDocs.ts (100%) diff --git a/R/session/init.R b/R/session/init.R index b44cc6c96..ffe71a5e2 100644 --- a/R/session/init.R +++ b/R/session/init.R @@ -95,7 +95,7 @@ init_last <- function() { ) # Attach to vscode - exports$.vsc.attach() + #exports$.vsc.attach() invisible() } diff --git a/R/session/vsc.R b/R/session/vsc.R index 5a13f993b..27e9a94bb 100644 --- a/R/session/vsc.R +++ b/R/session/vsc.R @@ -8,6 +8,7 @@ dir_watcher <- Sys.getenv("VSCODE_WATCHER_DIR", file.path(homedir, ".vscode-R")) request_file <- file.path(dir_watcher, "request.log") request_lock_file <- file.path(dir_watcher, "request.lock") settings_file <- file.path(dir_watcher, "settings.json") +request_tcp_connection <- NA user_options <- names(options()) logger <- if (getOption("vsc.debug", FALSE)) { @@ -208,9 +209,12 @@ request <- function(command, ...) { command = command, ... ) - jsonlite::write_json(obj, request_file, - auto_unbox = TRUE, null = "null", force = TRUE) - cat(get_timestamp(), file = request_lock_file) + request_target <- if (is.na(request_tcp_connection)) request_file else request_tcp_connection + jsonlite::write_json(obj, request_target, + auto_unbox = TRUE, na = "string", null = "null", force = TRUE) + if (is.na(request_tcp_connection)) { + cat(get_timestamp(), file = request_lock_file) + } } try_catch_timeout <- function(expr, timeout = Inf, ...) { @@ -369,8 +373,12 @@ update_workspace <- function(...) { loaded_namespaces = loadedNamespaces(), globalenv = if (show_globalenv) inspect_env(.GlobalEnv, globalenv_cache) else NULL ) - jsonlite::write_json(data, workspace_file, force = TRUE, pretty = FALSE) - cat(get_timestamp(), file = workspace_lock_file) + if (is.na(request_tcp_connection)) { + jsonlite::write_json(data, workspace_file, force = TRUE, pretty = FALSE) + cat(get_timestamp(), file = workspace_lock_file) + } else { + request("updateWorkspace", workspaceData = data) + } }, error = message) TRUE } @@ -419,6 +427,7 @@ if (use_httpgd && "httpgd" %in% .packages(all.available = TRUE)) { } ) + # TODO: Make this be available by TCP only as well update_plot <- function(...) { tryCatch({ if (plot_updated && check_null_dev()) { @@ -595,47 +604,64 @@ if (show_view) { ) } } + send_data_request <- function(data, source, type, ...) { + if (is.na(request_tcp_connection)) { + file <- tempfile(tmpdir = tempdir, ...) + if (type == "json") { + jsonlite::write_json(data, file, na = "string", null = "null", auto_unbox = TRUE, force = TRUE) + } else { + writeLines(code, file) + } + request("dataview", source = source, type = type, + title = title, file = file, viewer = viewer, uuid = uuid) + } else { + request("dataview", source = source, type = type, + title = title, data = data, viewer = viewer, uuid = uuid) + } + } if (is.data.frame(x) || is.matrix(x)) { x <- as_truncated_data(x) data <- dataview_table(x) - file <- tempfile(tmpdir = tempdir, fileext = ".json") - jsonlite::write_json(data, file, na = "string", null = "null", auto_unbox = TRUE, force = TRUE) - request("dataview", source = "table", type = "json", - title = title, file = file, viewer = viewer, uuid = uuid) + send_data_request(data, source = "table", type = "json", fileext = ".json") } else if (is.list(x)) { tryCatch({ - file <- tempfile(tmpdir = tempdir, fileext = ".json") - jsonlite::write_json(x, file, na = "string", null = "null", auto_unbox = TRUE, force = TRUE) - request("dataview", source = "list", type = "json", - title = title, file = file, viewer = viewer, uuid = uuid) + send_data_request(x, source = "list", type = "json", fileext = ".json") }, error = function(e) { - file <- file.path(tempdir, paste0(make.names(title), ".txt")) text <- utils::capture.output(print(x)) - writeLines(text, file) - request("dataview", source = "object", type = "txt", - title = title, file = file, viewer = viewer, uuid = uuid) + send_data_request(text, source = "object", type = "txt", paste0(make.names(title)), fileext = ".txt") }) } else { - file <- file.path(tempdir, paste0(make.names(title), ".R")) if (is.primitive(x)) { code <- utils::capture.output(print(x)) } else { code <- deparse(x) } - writeLines(code, file) - request("dataview", source = "object", type = "R", - title = title, file = file, viewer = viewer, uuid = uuid) + send_data_request(code, source = "object", type = "R", paste0(make.names(title)), fileext = ".R") } } rebind("View", show_dataview, "utils") } -attach <- function() { +attach <- function(host = "127.0.0.1", port = NA) { load_settings() if (rstudioapi_enabled()) { rstudioapi_util_env$update_addin_registry(addin_registry) } + if (!is.na(request_tcp_connection)) { + close(request_tcp_connection) + request_tcp_connection <<- NA + } + if (!is.na(port)) { + request_tcp_connection <<- socketConnection( + host = host, + port = port, + blocking = TRUE, + server = FALSE, + open = "a+" + ) + } + parent <- parent.env(environment()) request("attach", version = sprintf("%s.%s", R.version$major, R.version$minor), tempdir = tempdir, @@ -646,9 +672,9 @@ attach <- function() { ), plot_url = if (identical(names(dev.cur()), "httpgd")) httpgd::hgd_url(), server = if (use_webserver) list( - host = host, - port = port, - token = token + host = parent$host, + port = parent$port, + token = parent$token ) else NULL ) } @@ -929,4 +955,9 @@ print.hsearch <- function(x, ...) { invisible(NULL) } -reg.finalizer(.GlobalEnv, function(e) .vsc$request("detach"), onexit = TRUE) +reg.finalizer(.GlobalEnv, function(e) { + .vsc$request("detach") + if (!is.na(request_tcp_connection)) { + close(request_tcp_connection) + } +}, onexit = TRUE) diff --git a/package.json b/package.json index 23c830459..3f914e6b7 100644 --- a/package.json +++ b/package.json @@ -2157,6 +2157,7 @@ "jquery.json-viewer": "^1.5.0", "js-yaml": "^4.1.0", "node-fetch": "^2.6.7", + "promise-socket": "^7.0.0", "vscode-languageclient": "^8.1.0", "vsls": "^1.0.4753", "winreg": "^1.2.4" diff --git a/src/extension.ts b/src/extension.ts index c994d9a46..fe40910c8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,6 +24,7 @@ import * as rShare from './liveShare'; import * as httpgdViewer from './plotViewer'; import * as languageService from './languageService'; import { RTaskProvider } from './tasks'; +import { docProvider, docScheme } from './virtualDocs'; // global objects used in other files @@ -45,6 +46,10 @@ export async function activate(context: vscode.ExtensionContext): Promise { void updateRequest(sessionStatusBarItem); }); + + void startIncomingRequestServer('0.0.0.0', sessionStatusBarItem);// TODO: Make it more configurable + console.info('[startRequestWatcher] Done'); } @@ -334,8 +341,21 @@ export async function showWebView(file: string, title: string, viewer: string | console.info('[showWebView] Done'); } -export async function showDataView(source: string, type: string, title: string, file: string, viewer: string): Promise { +export async function showDataView(source: string, type: string, title: string, file: string | undefined, data: string | object | undefined, viewer: string): Promise { console.info(`[showDataView] source: ${source}, type: ${type}, title: ${title}, file: ${file}, viewer: ${viewer}`); + console.debug(`data: ${JSON.stringify(data)}`); + + const getDataContent = async () : Promise => { + if (file === undefined) { + return typeof data == "string" ? data : JSON.stringify(data); + } else { + const fileContent = await readContent(file, 'utf8'); + if (fileContent == null) { + console.error("Error: File wasn't found!"); + return undefined; + } + } + }; if (isGuestSession) { resDir = guestResDir; @@ -353,7 +373,8 @@ export async function showDataView(source: string, type: string, title: string, retainContextWhenHidden: true, localResourceRoots: [Uri.file(resDir)], }); - const content = await getTableHtml(panel.webview, file); + const fileContent = await getDataContent(); + const content = await getTableHtml(panel.webview, fileContent!); panel.iconPath = new UriIcon('open-preview'); panel.webview.html = content; } else if (source === 'list') { @@ -368,14 +389,15 @@ export async function showDataView(source: string, type: string, title: string, retainContextWhenHidden: true, localResourceRoots: [Uri.file(resDir)], }); - const content = await getListHtml(panel.webview, file); + const fileContent = await getDataContent(); + const content = await getListHtml(panel.webview, fileContent!); panel.iconPath = new UriIcon('open-preview'); panel.webview.html = content; } else { - if (isGuestSession) { - const fileContent = await rGuestService?.requestFileContent(file, 'utf8'); + if (isGuestSession || file === undefined) { + const fileContent = file === undefined ? data as string : await rGuestService?.requestFileContent(file, 'utf8'); if (fileContent) { - await openVirtualDoc(file, fileContent, true, true, ViewColumn[viewer as keyof typeof ViewColumn]); + await openVirtualDoc(file ?? "R View", fileContent, true, true, ViewColumn[viewer as keyof typeof ViewColumn]); } } else { await commands.executeCommand('vscode.open', Uri.file(file), { @@ -388,10 +410,9 @@ export async function showDataView(source: string, type: string, title: string, console.info('[showDataView] Done'); } -export async function getTableHtml(webview: Webview, file: string): Promise { +export async function getTableHtml(webview: Webview, content: string): Promise { resDir = isGuestSession ? guestResDir : resDir; const pageSize = config().get('session.data.pageSize', 500); - const content = await readContent(file, 'utf8'); return ` @@ -512,7 +533,7 @@ export async function getTableHtml(webview: Webview, file: string): Promise { +export async function getListHtml(webview: Webview, content: string): Promise { resDir = isGuestSession ? guestResDir : resDir; - const content = await readContent(file, 'utf8'); return ` @@ -629,7 +649,7 @@ export async function getListHtml(webview: Webview, file: string): Promise