From 797a35263d7ff94aeb2068778e3a877c841ffcaf Mon Sep 17 00:00:00 2001 From: Randy Lai Date: Sat, 2 May 2026 16:59:46 -0700 Subject: [PATCH 1/2] chore: remove obsolete R/session directory --- R/session/init.R | 104 ---- R/session/profile.R | 33 -- R/session/rstudioapi.R | 315 ------------ R/session/rstudioapi_util.R | 258 ---------- R/session/vsc.R | 941 ------------------------------------ 5 files changed, 1651 deletions(-) delete mode 100644 R/session/init.R delete mode 100644 R/session/profile.R delete mode 100644 R/session/rstudioapi.R delete mode 100644 R/session/rstudioapi_util.R delete mode 100644 R/session/vsc.R diff --git a/R/session/init.R b/R/session/init.R deleted file mode 100644 index 410263c2..00000000 --- a/R/session/init.R +++ /dev/null @@ -1,104 +0,0 @@ -# This file is executed with its containing directory as wd - -# Remember the working directory (should be extension subfolder that contains this script) -dir_init <- getwd() - - -# This function is run at the beginning of R's startup sequence -# Code that is meant to be run at the end of the startup should go in `init_last` -init_first <- function() { - # return early if not a vscode term session - if ( - !interactive() - || Sys.getenv("RSTUDIO") != "" - || Sys.getenv("TERM_PROGRAM") != "vscode" - ) { - return() - } - - # check required packages - required_packages <- c("jsonlite", "rlang") - missing_packages <- required_packages[ - !vapply(required_packages, requireNamespace, - logical(1L), quietly = TRUE - ) - ] - - if (length(missing_packages)) { - message( - "VSCode R Session Watcher requires ", - toString(missing_packages), ". ", - "Please install manually in order to use VSCode-R." - ) - } else { - # Initialize vsc utils after loading other default packages - assign(".First.sys", init_last, envir = globalenv()) - } -} - -old.First.sys <- .First.sys - -# Overwrite for `.First.sys` -# Is used to make sure that all default packages are loaded first -# Will be assigned to and called from the global environment, -# Will be run with wd being the user's working directory (!) -init_last <- function() { - old.First.sys() - - # cleanup previous version - removeTaskCallback("vscode-R") - options(vscodeR = NULL) - .vsc.name <- "tools:vscode" - if (.vsc.name %in% search()) { - detach(.vsc.name, character.only = TRUE) - } - - # Source vsc utils in new environmeent - .vsc <- new.env() - source(file.path(dir_init, "vsc.R"), local = .vsc) - - # attach functions that are meant to be called by the user/vscode - exports <- local({ - .vsc <- .vsc - .vsc.attach <- .vsc$attach - .vsc.view <- .vsc$show_dataview - .vsc.browser <- .vsc$show_browser - .vsc.viewer <- .vsc$show_viewer - .vsc.page_viewer <- .vsc$show_page_viewer - View <- .vsc.view - environment() - }) - attach(exports, name = .vsc.name, warn.conflicts = FALSE) - - # overwrite S3 bindings from other packages - suppressWarnings({ - if (!identical(getOption("vsc.helpPanel", "Two"), FALSE)) { - # Overwrite print function for results of `?` - .vsc$.S3method( - "print", - "help_files_with_topic", - .vsc$print.help_files_with_topic - ) - # Overwrite print function for results of `??` - .vsc$.S3method( - "print", - "hsearch", - .vsc$print.hsearch - ) - } - # Further S3 overwrites can go here - # ... - }) - - # remove this function from globalenv() - suppressWarnings( - rm(".First.sys", envir = globalenv()) - ) - - # Attach to vscode - exports$.vsc.attach() - - invisible() -} - -init_first() diff --git a/R/session/profile.R b/R/session/profile.R deleted file mode 100644 index dafdf298..00000000 --- a/R/session/profile.R +++ /dev/null @@ -1,33 +0,0 @@ -# Source the original .Rprofile -local({ - try_source <- function(file) { - if (file.exists(file)) { - source(file) - TRUE - } else { - FALSE - } - } - - r_profile <- Sys.getenv("R_PROFILE_USER_OLD") - Sys.setenv( - R_PROFILE_USER_OLD = "", - R_PROFILE_USER = r_profile - ) - - if (nzchar(r_profile)) { - try_source(r_profile) - } else { - try_source(".Rprofile") || try_source(file.path("~", ".Rprofile")) - } - - invisible() -}) - -# Run vscode initializer -local({ - init_file <- Sys.getenv("VSCODE_INIT_R") - if (nzchar(init_file)) { - source(init_file, chdir = TRUE, local = TRUE) - } -}) diff --git a/R/session/rstudioapi.R b/R/session/rstudioapi.R deleted file mode 100644 index 60ea4358..00000000 --- a/R/session/rstudioapi.R +++ /dev/null @@ -1,315 +0,0 @@ -getActiveDocumentContext <- function() { - # In RStudio this returns either a document context for either the active - # source editor or active console. - # In VSCode this only ever returns the active (or last active) text editor. - # This is because it is currently not possible to tell in VSCode whether - # a text editor or terminal has focus. The concept of active is different. - # It means currently using or most recently used, and applies to text - # editors and terminals separately. - # This shoudln't be much of a limitation as the only context returned for - # the console was the current selection, so it is not very useful. - editor_context <- rstudioapi_call("active_editor_context") - - make_rs_document_context(editor_context) -} - -getSourceEditorContext <- getActiveDocumentContext - -verifyAvailable <- function(version_needed = NULL) { - if (is.null(version_needed)) TRUE else FALSE -} - -isAvailable <- function(version_needed = NULL, child_ok) { - verifyAvailable(version_needed) -} - -insertText <- function(location, text, id = NULL) { - - ## insertText also supports insertText("text"), insertText(text = "text"), - ## allowing the location parameter to be used for the text when - ## text itself is null. - ## This is dispatched as a separate request type - if (missing(text) && is.character(location) && length(location) == 1) { - ## handling insertText("text") - return(invisible( - rstudioapi_call("replace_text_in_current_selection", - text = location, - id = id - ) - )) - } else if (missing(location)) { - ## handling insertText(text = "text") - return(invisible(rstudioapi_call( - "replace_text_in_current_selection", - text = text, - id = id - ))) - } else if (is.null(location) && missing(text)) { - ## handling insertText(NULL) - return(invisible(NULL)) - } - - ## ensure normalised_location is a list containing a possible mix of - ## document_position and document_range objects - normalised_location <- normalise_pos_or_range_arg(location) - normalised_text <- normalise_text_arg(text, length(normalised_location)) - ## Having normalised we are guaranteed these are the same length. - ## Package up all the edits in a query to send to VSCode in an object - ## This is done so the edits can be applied in a single edit object, which - ## is hopefull closest to RStudio behaviour. - query <- - mapply(function(location, text) { - list( - operation = if (rstudioapi::is.document_range(location)) { - "modifyRange" - } else { - "insertText" - }, - location = location, - text = text - ) - }, - normalised_location, - normalised_text, - SIMPLIFY = FALSE - ) - - invisible( - rstudioapi_call("insert_or_modify_text", query = query, id = id) - ) -} - -modifyRange <- insertText - -readPreference <- function(name, default) { - ## in future we could map some rstudio preferences to vscode settings. - ## since the caller must provide a default this should work. - default -} - -readRStudioPreference <- readPreference - -.vsc_rstudioapi_env <- environment() - -hasFun <- function(name, version_needed = NULL, ...) { - if (!is.null(version_needed)) { - return(FALSE) - } - - obj <- .vsc_rstudioapi_env[[name]] - is.function(obj) && !identical(obj, .vsc_not_yet_implemented) -} - -findFun <- function(name, version_needed = NULL, ...) { - if (!is.null(version_needed)) { - stop("VSCode does not support used of 'version_needed'.") - } - - if (hasFun(name, version_needed = version_needed, ...)) { - .vsc_rstudioapi_env[[name]] - } else { - stop("Cannot find function '", name, "'") - } -} - -showDialog <- function(title, message, url = "") { - message <- sprintf("%s: %s \n%s", title, message, url) - invisible( - rstudioapi_call("show_dialog", message = message) - ) -} - -navigateToFile <- function(file, line = -1L, column = -1L) { - # normalise path since relative paths don't work as URIs in VSC - invisible( - rstudioapi_call( - "navigate_to_file", - file = normalizePath(file), - line = line, - column = column - ) - ) -} - -setSelectionRanges <- function(ranges, id = NULL) { - ranges_or_positions <- normalise_pos_or_range_arg(ranges) - - ranges <- lapply(ranges_or_positions, function(location) { - if (rstudioapi::is.document_position(location)) { - rstudioapi::document_range(location, location) - } else { - location - } - }) - - invisible( - rstudioapi_call("set_selection_ranges", ranges = ranges, id = id) - ) -} - -setCursorPosition <- setSelectionRanges - -documentSave <- function(id = NULL) { - invisible( - rstudioapi_call("document_save", id = id) - ) -} - -getActiveProject <- function() { - path_object <- rstudioapi_call("get_project_path") - if (is.null(path_object$path)) { - stop( - "No folder for active document. ", - "Is it unsaved? Try saving and run addin again." - ) - } - path_object$path -} - -.vsc_document_context <- function(id = NULL) { - doc_context <- rstudioapi_call("document_context", id = id) - doc_context -} - -documentId <- function(allowConsole = TRUE) document_context()$id$external - -documentPath <- function(id = NULL) document_context(id)$id$path - -documentSaveAll <- function() { - invisible( - rstudioapi_call("document_save_all") - ) -} - -documentNew <- function(text, - type = c("r", "rmarkdown", "sql"), - position = rstudioapi::document_position(0, 0), - execute = FALSE) { - if (!rstudioapi::is.document_position((position))) { - stop("DocumentNew requires a document_position object") - } - if (length(text) != 1 || !is.character(text)) { - stop("text for DocumentNew must be a length one character vector.") - } - if (execute) { - message( - "VSCode {rstudioapi} emulation does not support ", - " executing documents upon creation" - ) - } - - invisible( - rstudioapi_call( - "document_new", - text = text, - type = type, - position = position - ) - ) -} - -setDocumentContents <- function(text, id = NULL) { - whole_document_range <- - rstudioapi::document_range( - rstudioapi::document_position(0, 0), - rstudioapi::document_position(Inf, Inf) - ) - insertText(whole_document_range, text, id) -} - -restartSession <- function() { - invisible( - rstudioapi_call("restart_r") - ) -} - -viewer <- function(url, height = NULL) { - # cant bind to this directly because it's not created when the binding is - # made. - .vsc.viewer(url) -} - -getVersion <- function() { - numeric_version("0") -} - -versionInfo <- function() { - list( - citation = "", - mode = "vscode", - version = numeric_version("0"), - release_name = "vscode" - ) -} - -sendToConsole <- function(code, execute = TRUE, echo = TRUE, focus = FALSE) { - if (!echo) { - stop("rstudioapi::sendToConsole only supports echo = TRUE in VSCode.") - } - - code_to_run <- paste0(code, collapse = "\n") - invisible( - rstudioapi_call("send_to_console", code = code_to_run, execute = execute, focus = focus) - ) -} - - -# Unimplemented API calls that will error if called. - -.vsc_not_yet_implemented <- function(...) { - stop("This {rstudioapi} function is not currently implemented for VSCode.") -} - - -getConsoleEditorContext <- .vsc_not_yet_implemented -sourceMarkers <- .vsc_not_yet_implemented -documentClose <- .vsc_not_yet_implemented -showPrompt <- .vsc_not_yet_implemented -showQuestion <- .vsc_not_yet_implemented -updateDialog <- .vsc_not_yet_implemented -openProject <- .vsc_not_yet_implemented -initializeProject <- .vsc_not_yet_implemented -addTheme <- .vsc_not_yet_implemented -applyTheme <- .vsc_not_yet_implemented -convertTheme <- .vsc_not_yet_implemented -getThemeInfo <- .vsc_not_yet_implemented -getThemes <- .vsc_not_yet_implemented -removeTheme <- .vsc_not_yet_implemented -jobAdd <- .vsc_not_yet_implemented -jobAddOutput <- .vsc_not_yet_implemented -jobAddProgress <- .vsc_not_yet_implemented -jobRemove <- .vsc_not_yet_implemented -jobRunScript <- .vsc_not_yet_implemented -jobSetProgress <- .vsc_not_yet_implemented -jobSetState <- .vsc_not_yet_implemented -jobSetStatus <- .vsc_not_yet_implemented -launcherGetInfo <- .vsc_not_yet_implemented -launcherAvailable <- .vsc_not_yet_implemented -launcherGetJobs <- .vsc_not_yet_implemented -launcherConfig <- .vsc_not_yet_implemented -launcherContainer <- .vsc_not_yet_implemented -launcherControlJob <- .vsc_not_yet_implemented -launcherGetJob <- .vsc_not_yet_implemented -launcherHostMount <- .vsc_not_yet_implemented -launcherNfsMount <- .vsc_not_yet_implemented -launcherPlacementConstraint <- .vsc_not_yet_implemented -launcherResourceLimit <- .vsc_not_yet_implemented -launcherSubmitJob <- .vsc_not_yet_implemented -launcherSubmitR <- .vsc_not_yet_implemented -previewRd <- .vsc_not_yet_implemented -previewSql <- .vsc_not_yet_implemented -writePreference <- .vsc_not_yet_implemented -writeRStudioPreference <- .vsc_not_yet_implemented -getPersistentValue <- .vsc_not_yet_implemented -setPersistentValue <- .vsc_not_yet_implemented -savePlotAsImage <- .vsc_not_yet_implemented -createProjectTemplate <- .vsc_not_yet_implemented -hasColourConsole <- .vsc_not_yet_implemented -bugReport <- .vsc_not_yet_implemented -buildToolsCheck <- .vsc_not_yet_implemented -buildToolsInstall <- .vsc_not_yet_implemented -buildToolsExec <- .vsc_not_yet_implemented -dictionariesPath <- .vsc_not_yet_implemented -userDictionariesPath <- .vsc_not_yet_implemented -executeCommand <- .vsc_not_yet_implemented -translateLocalUrl <- .vsc_not_yet_implemented diff --git a/R/session/rstudioapi_util.R b/R/session/rstudioapi_util.R deleted file mode 100644 index da5fe0ff..00000000 --- a/R/session/rstudioapi_util.R +++ /dev/null @@ -1,258 +0,0 @@ -rstudioapi_call <- function(action, ...) { - request_response("rstudioapi", action = action, args = list(...)) -} - -rstudioapi_patch_hook <- function(api_env) { - patch_rstudioapi_fn <- - function(old, new) { - if (namespace_has(old, "rstudioapi")) { - assignInNamespace( - x = old, - value = new, - ns = "rstudioapi" - ) - } - } - ## make assignments to functions found in api_env namespace - ## that have function with the same name in {rstudioapi} namespace - api_list <- as.list(api_env) - mapply( - patch_rstudioapi_fn, - names(api_list), - api_list - ) -} - -make_rs_range <- function(vsc_selection) { - # vscode positions are zero indexed - # rstudioapi is one indexed - rstudioapi::document_range( - start = rstudioapi::document_position( - row = vsc_selection$start$line + 1, - column = vsc_selection$start$character + 1 - ), - end = rstudioapi::document_position( - row = vsc_selection$end$line + 1, - column = vsc_selection$end$character + 1 - ) - ) -} - -extract_document_ranges <- function(vsc_selections) { - lapply(vsc_selections, make_rs_range) -} - -to_content_lines <- function(contents, ranges) { - content_lines <- strsplit(contents, "\n|\r\n|\r$")[[1]] - - - # edge case handling: The cursor is at the start of a new empty line, - # and that line is the final line. - range_end_row <- unlist(lapply(ranges, function(range) range$end["row"])) - last_row <- max(range_end_row) - if (last_row == length(content_lines) + 1) { - content_lines <- c(content_lines, "") - } - - content_lines -} - - -extract_range_text <- function(range, content_lines) { - if (!range_has_text(range)) { - return("") - } - - content_rows <- - content_lines[(range$start["row"]):(range$end["row"])] - content_rows[length(content_rows)] <- - substring( - content_rows[length(content_rows)], - 1, - range$end["column"] - 1 - # it's a minus 1 here because the selection end point is the number - # of the first unselected column. I.e. range 1 - 2 is all of - # columns >= 1 and < 2, which is column 1. - ) - content_rows[1] <- - substring( - content_rows[1], - range$start["column"] - ) - - paste0(content_rows, collapse = "\n") -} - -range_has_text <- function(range) { - (range$end["row"] - range$start["row"]) + - (range$end["column"] - range$start["column"]) > 0 -} - -make_rs_document_selection <- function(ranges, range_texts) { - selection_data <- - mapply( - function(range, text) { - list( - range = range, - text = text - ) - }, - ranges, - range_texts, - SIMPLIFY = FALSE - ) - structure(selection_data, - class = "document_selection" - ) -} - -make_rs_document_context <- - function(vsc_editor_context) { - document_ranges <- - extract_document_ranges(vsc_editor_context$selection) - content_lines <- - to_content_lines(vsc_editor_context$contents, document_ranges) - document_range_texts <- - lapply( - document_ranges, - extract_range_text, - content_lines - ) - document_selection <- - make_rs_document_selection( - document_ranges, - document_range_texts - ) - - structure(list( - id = vsc_editor_context$id$external, - path = vsc_editor_context$path, - contents = content_lines, - selections = document_selection - ), - class = "document_context" - ) - } - -is_positionable <- function(p) is.numeric(p) && length(p) == 2 - -is_rangable <- function(r) is.numeric(r) && length(r) == 4 - -normalise_pos_or_range_arg <- function(location) { - # This is necessary due to the loose constraints of the location argument - # in rstudioapi::insertText and rstudioapi::modifyRange. These - # functions can take single vectors coerable to postition or range OR a - # list where each element may be a location, range, or a vector coercable - # to such. I prefer to normalise the argument to a list of either formal - # positions or ranges. - if (rstudioapi::is.document_position(location)) { - list(location) - } else if (is_positionable(location)) { - list(rstudioapi::as.document_position(location)) - } else if (rstudioapi::is.document_range(location)) { - list(location) - } else if (is_rangable(location)) { - list(rstudioapi::as.document_range(location)) - } else if (is.list(location)) { - lapply( - location, - function(a_location) { - if (rstudioapi::is.document_position(a_location) || rstudioapi::is.document_range(a_location)) { - a_location - } else if (is_positionable(a_location)) { - rstudioapi::as.document_position(a_location) - } else if (is_rangable((a_location))) { - rstudioapi::as.document_range(a_location) - } else { - stop( - "object in location list was not a", - " document_position or document_range" - ) - } - } - ) - } else { - stop("location object was not a document_position or document_range") - } -} - -normalise_text_arg <- function(text, location_length) { - if (length(text) == location_length) { - text - } else if (length(text) == 1 && location_length > 1) { - rep(text, location_length) - } else { - stop( - "text vector needs to be of length 1 or", - " the same length as location list" - ) - } -} - -update_addin_registry <- function(addin_registry) { - pkgs <- .packages(all.available = TRUE) - addin_files <- vapply(pkgs, function(pkg) { - system.file("rstudio/addins.dcf", package = pkg) - }, character(1L)) - addin_files <- addin_files[file.exists(addin_files)] - addin_descriptions <- - mapply( - function(package, package_dcf) { - addin_description_names <- - c( - "name", - "description", - "binding", - "interactive", - "package" - ) - description_result <- - tryCatch({ - addin_description <- - as.data.frame(read.dcf(package_dcf), - stringsAsFactors = FALSE - ) - - if (ncol(addin_description) < 4) { - NULL - } - ## if less than 4 columns it's malformed - ## a NULL will be ignored in the rbind - - addin_description$package <- package - names(addin_description) <- addin_description_names - - addin_description[, addin_description_names] - ## this filters out any extra columns - }, - error = function(cond) { - message( - "addins.dcf file for ", package, - " could not be read from R library. ", - "The RStudio addin picker will not ", - "contain it's addins" - ) - - NULL - } - ) - - description_result - }, - names(addin_files), - addin_files, - SIMPLIFY = FALSE - ) - addin_descriptions_flat <- - do.call( - function(...) rbind(..., make.row.names = FALSE), - addin_descriptions - ) - - jsonlite::write_json(addin_descriptions_flat, addin_registry, pretty = TRUE) -} - -namespace_has <- function(obj, namespace) { - attempt <- try(getFromNamespace(obj, namespace), silent = TRUE) - !inherits(attempt, "try-error") -} diff --git a/R/session/vsc.R b/R/session/vsc.R deleted file mode 100644 index 5ab46139..00000000 --- a/R/session/vsc.R +++ /dev/null @@ -1,941 +0,0 @@ -pid <- Sys.getpid() -wd <- getwd() -tempdir <- tempdir() -homedir <- Sys.getenv( - if (.Platform$OS.type == "windows") "USERPROFILE" else "HOME" -) -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") -user_options <- names(options()) - -logger <- if (getOption("vsc.debug", FALSE)) { - function(...) cat(..., "\n", sep = "") -} else { - function(...) invisible() -} - -load_settings <- function() { - if (!file.exists(settings_file)) { - return(FALSE) - } - - setting <- function(x, ...) { - switch(EXPR = x, ..., x) - } - - mapping <- quote(list( - vsc.use_webserver = session$useWebServer, - vsc.use_httpgd = plot$useHttpgd, - vsc.show_object_size = workspaceViewer$showObjectSize, - vsc.rstudioapi = session$emulateRStudioAPI, - vsc.str.max.level = setting(session$levelOfObjectDetail, Minimal = 0, Normal = 1, Detailed = 2), - vsc.object_length_limit = session$objectLengthLimit, - vsc.object_timeout = session$objectTimeout, - vsc.globalenv = session$watchGlobalEnvironment, - vsc.plot = setting(session$viewers$viewColumn$plot, Disable = FALSE), - vsc.dev.args = plot$devArgs, - vsc.browser = setting(session$viewers$viewColumn$browser, Disable = FALSE), - vsc.viewer = setting(session$viewers$viewColumn$viewer, Disable = FALSE), - vsc.page_viewer = setting(session$viewers$viewColumn$pageViewer, Disable = FALSE), - vsc.row_limit = session$data$rowLimit, - vsc.view = setting(session$viewers$viewColumn$view, Disable = FALSE), - vsc.helpPanel = setting(session$viewers$viewColumn$helpPanel, Disable = FALSE) - )) - - vsc_settings <- tryCatch(jsonlite::read_json(settings_file), error = function(e) { - message("Error occurs when reading VS Code settings: ", conditionMessage(e)) - }) - - if (is.null(vsc_settings)) { - return(FALSE) - } - - ops <- eval(mapping, vsc_settings) - - # exclude options set by user on startup - r_options <- ops[!(names(ops) %in% user_options)] - - options(r_options) -} - -load_settings() - -if (is.null(getOption("help_type"))) { - options(help_type = "html") -} - -use_webserver <- isTRUE(getOption("vsc.use_webserver", FALSE)) -if (use_webserver) { - if (requireNamespace("httpuv", quietly = TRUE)) { - request_handlers <- list( - hover = function(expr, ...) { - tryCatch({ - expr <- parse(text = expr, keep.source = FALSE)[[1]] - obj <- eval(expr, .GlobalEnv) - list(str = capture_str(obj)) - }, error = function(e) NULL) - }, - - complete = function(expr, trigger, ...) { - obj <- tryCatch({ - expr <- parse(text = expr, keep.source = FALSE)[[1]] - eval(expr, .GlobalEnv) - }, error = function(e) NULL) - - if (is.null(obj)) { - return(NULL) - } - - if (trigger == "$") { - names <- if (is.object(obj)) { - .DollarNames(obj, pattern = "") - } else if (is.recursive(obj)) { - names(obj) - } else { - NULL - } - - result <- lapply(names, function(name) { - item <- obj[[name]] - list( - name = name, - type = typeof(item), - str = try_capture_str(item) - ) - }) - return(result) - } - - if (trigger == "@" && isS4(obj)) { - names <- slotNames(obj) - result <- lapply(names, function(name) { - item <- slot(obj, name) - list( - name = name, - type = typeof(item), - str = try_capture_str(item) - ) - }) - return(result) - } - } - ) - - server <- getOption("vsc.server") - if (!is.null(server) && server$isRunning()) { - host <- server$getHost() - port <- server$getPort() - token <- attr(server, "token") - } else { - host <- "127.0.0.1" - port <- httpuv::randomPort() - token <- sprintf("%d:%d:%.6f", pid, port, Sys.time()) - server <- httpuv::startServer(host, port, - list( - onHeaders = function(req) { - logger("http request ", - req[["REMOTE_ADDR"]], ":", - req[["REMOTE_PORT"]], " ", - req[["REQUEST_METHOD"]], " ", - req[["HTTP_USER_AGENT"]] - ) - - if (!nzchar(req[["REMOTE_ADDR"]]) || identical(req[["REMOTE_PORT"]], "0")) { - return(NULL) - } - - if (!identical(req[["HTTP_AUTHORIZATION"]], token)) { - return(list( - status = 401L, - headers = list( - "Content-Type" = "text/plain" - ), - body = "Unauthorized" - )) - } - - if (!identical(req[["HTTP_CONTENT_TYPE"]], "application/json")) { - return(list( - status = 400L, - headers = list( - "Content-Type" = "text/plain" - ), - body = "Bad request" - )) - } - }, - call = function(req) { - content <- req$rook.input$read_lines() - request <- jsonlite::fromJSON(content, simplifyVector = FALSE) - handler <- request_handlers[[request$type]] - response <- if (is.function(handler)) do.call(handler, request) - - list( - status = 200L, - headers = list( - "Content-Type" = "application/json" - ), - body = jsonlite::toJSON(response, auto_unbox = TRUE, force = TRUE) - ) - } - ) - ) - attr(server, "token") <- token - options(vsc.server = server) - } - } else { - message("{httpuv} is required to use WebServer from the session watcher.") - use_webserver <- FALSE - } -} - -get_timestamp <- function() { - sprintf("%.6f", Sys.time()) -} - -scalar <- function(x) { - class(x) <- c("scalar", class(x)) - x -} - -request <- function(command, ...) { - obj <- list( - time = Sys.time(), - pid = pid, - wd = wd, - command = command, - ... - ) - jsonlite::write_json(obj, request_file, - auto_unbox = TRUE, null = "null", force = TRUE - ) - cat(get_timestamp(), file = request_lock_file) -} - -try_catch_timeout <- function(expr, timeout = Inf, ...) { - expr <- substitute(expr) - envir <- parent.frame() - setTimeLimit(timeout, transient = TRUE) - on.exit(setTimeLimit()) - tryCatch(eval(expr, envir), ...) -} - -capture_str <- function(object, max.level = getOption("vsc.str.max.level", 0)) { - paste0(utils::capture.output( - utils::str(object, - max.level = max.level, - give.attr = FALSE, - vec.len = 1 - ) - ), collapse = "\n") -} - -try_capture_str <- function(object, max.level = getOption("vsc.str.max.level", 0)) { - tryCatch( - capture_str(object, max.level = max.level), - error = function(e) { - paste0(class(object), collapse = ", ") - } - ) -} - -rebind <- function(sym, value, ns) { - if (is.character(ns)) { - Recall(sym, value, getNamespace(ns)) - pkg <- paste0("package:", ns) - if (pkg %in% search()) { - Recall(sym, value, as.environment(pkg)) - } - } else if (is.environment(ns)) { - if (bindingIsLocked(sym, ns)) { - unlockBinding(sym, ns) - on.exit(lockBinding(sym, ns)) - } - assign(sym, value, ns) - } else { - stop("ns must be a string or environment") - } -} - -address <- function(x) { - info <- utils::capture.output(.Internal(inspect(x, 0L, 0L))) - sub("@([a-z0-9]+)\\s+.+", "\\1", info[[1]]) -} - -globalenv_cache <- new.env(parent = emptyenv()) - -inspect_env <- function(env, cache) { - all_names <- ls(env) - rm(list = setdiff(names(globalenv_cache), all_names), envir = cache) - is_active <- vapply(all_names, bindingIsActive, logical(1), USE.NAMES = TRUE, env) - is_promise <- rlang::env_binding_are_lazy(env, all_names[!is_active]) - show_object_size <- getOption("vsc.show_object_size", FALSE) - object_length_limit <- getOption("vsc.object_length_limit", 2000) - object_timeout <- getOption("vsc.object_timeout", 50) / 1000 - str_max_level <- getOption("vsc.str.max.level", 0) - objs <- lapply(all_names, function(name) { - if (isTRUE(is_promise[name])) { - info <- list( - class = "promise", - type = scalar("promise"), - length = scalar(0L), - str = scalar("(promise)") - ) - } else if (isTRUE(is_active[name])) { - info <- list( - class = "active_binding", - type = scalar("active_binding"), - length = scalar(0L), - str = scalar("(active-binding)") - ) - } else { - obj <- env[[name]] - - info <- list( - class = class(obj), - type = scalar(typeof(obj)), - length = scalar(length(obj)) - ) - - if (show_object_size) { - addr <- address(obj) - cobj <- cache[[name]] - if (is.null(cobj) || cobj$address != addr || cobj$length != info$length) { - cache[[name]] <- cobj <- list( - address = addr, - length = length(obj), - size = unclass(object.size(obj)) - ) - } - info$size <- scalar(cobj$size) - } - - if (length(obj) > object_length_limit) { - info$str <- scalar(trimws(try_capture_str(obj, 0))) - } else { - info_str <- NULL - if (str_max_level > 0) { - info_str <- try_catch_timeout( - capture_str(obj, str_max_level), - timeout = object_timeout, - error = function(e) NULL - ) - } - if (is.null(info_str)) { - info_str <- try_capture_str(obj, 0) - } - info$str <- scalar(trimws(info_str)) - obj_names <- if (is.object(obj)) { - .DollarNames(obj, pattern = "") - } else if (is.recursive(obj)) { - names(obj) - } else { - NULL - } - - if (length(obj_names)) { - info$names <- obj_names - } - } - - if (isS4(obj)) { - info$slots <- slotNames(obj) - } - - if (!is.null(dim(obj))) { - info$dim <- dim(obj) - } - } - info - }) - names(objs) <- all_names - objs -} - -dir_session <- file.path(tempdir, "vscode-R") -dir.create(dir_session, showWarnings = FALSE, recursive = TRUE) - -removeTaskCallback("vsc.workspace") -show_globalenv <- isTRUE(getOption("vsc.globalenv", TRUE)) -workspace_file <- file.path(dir_session, "workspace.json") -workspace_lock_file <- file.path(dir_session, "workspace.lock") -file.create(workspace_lock_file, showWarnings = FALSE) - -update_workspace <- function(...) { - tryCatch({ - data <- list( - search = search()[-1], - 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) - }, error = message) - TRUE -} -update_workspace() -addTaskCallback(update_workspace, name = "vsc.workspace") - -removeTaskCallback("vsc.plot") -use_httpgd <- identical(getOption("vsc.use_httpgd", FALSE), TRUE) -show_plot <- !identical(getOption("vsc.plot", "Two"), FALSE) -if (use_httpgd && "httpgd" %in% .packages(all.available = TRUE)) { - options(device = function(...) { - httpgd::hgd( - silent = TRUE - ) - .vsc$request("httpgd", url = httpgd::hgd_url()) - }) -} else if (use_httpgd) { - message("Install package `httpgd` to use vscode-R with httpgd!") -} else if (show_plot) { - plot_file <- file.path(dir_session, "plot.png") - plot_lock_file <- file.path(dir_session, "plot.lock") - file.create(plot_file, plot_lock_file, showWarnings = FALSE) - - plot_updated <- FALSE - null_dev_id <- c(pdf = 2L) - null_dev_size <- c(7 + pi, 7 + pi) - - check_null_dev <- function() { - identical(dev.cur(), null_dev_id) && - identical(dev.size(), null_dev_size) - } - - new_plot <- function() { - if (check_null_dev()) { - plot_updated <<- TRUE - } - } - - options( - device = function(...) { - pdf(NULL, - width = null_dev_size[[1L]], - height = null_dev_size[[2L]], - bg = "white") - dev.control(displaylist = "enable") - } - ) - - update_plot <- function(...) { - tryCatch({ - if (plot_updated && check_null_dev()) { - plot_updated <<- FALSE - record <- recordPlot() - if (length(record[[1L]])) { - dev_args <- getOption("vsc.dev.args") - do.call(png, c(list(filename = plot_file), dev_args)) - on.exit({ - dev.off() - cat(get_timestamp(), file = plot_lock_file) - }) - replayPlot(record) - } - } - }, error = message) - TRUE - } - - setHook("plot.new", new_plot, "replace") - setHook("grid.newpage", new_plot, "replace") - - rebind(".External.graphics", function(...) { - out <- .Primitive(".External.graphics")(...) - if (check_null_dev()) { - plot_updated <<- TRUE - } - out - }, "base") - - update_plot() - addTaskCallback(update_plot, name = "vsc.plot") -} - -show_view <- !identical(getOption("vsc.view", "Two"), FALSE) -if (show_view) { - get_column_def <- function(name, field, value) { - filter <- TRUE - tooltip <- sprintf( - "%s, class: [%s], type: %s", - name, - toString(class(value)), - typeof(value) - ) - if (is.numeric(value)) { - type <- "numericColumn" - if (is.null(attr(value, "class"))) { - filter <- "agNumberColumnFilter" - } - } else if (inherits(value, "Date")) { - type <- "dateColumn" - filter <- "agDateColumnFilter" - } else { - type <- "textColumn" - filter <- "agTextColumnFilter" - } - list( - headerName = name, - headerTooltip = tooltip, - field = field, - type = type, - filter = filter - ) - } - - dataview_table <- function(data) { - if (is.matrix(data)) { - data <- as.data.frame.matrix(data) - } - - if (is.data.frame(data)) { - .nrow <- nrow(data) - .colnames <- colnames(data) - if (is.null(.colnames)) { - .colnames <- sprintf("V%d", seq_len(ncol(data))) - } else { - .colnames <- trimws(.colnames) - } - if (.row_names_info(data) > 0L) { - rownames <- rownames(data) - rownames(data) <- NULL - } else { - rownames <- seq_len(.nrow) - } - .colnames <- c("(row)", .colnames) - fields <- sprintf("x%d", seq_along(.colnames)) - data <- c(list(" " = rownames), .subset(data)) - names(data) <- fields - class(data) <- "data.frame" - attr(data, "row.names") <- .set_row_names(.nrow) - columns <- .mapply(get_column_def, - list(.colnames, fields, data), - NULL - ) - list( - columns = columns, - data = data - ) - } else { - stop("data must be a data.frame or a matrix") - } - } - - show_dataview <- function(x, title, uuid = NULL, - viewer = getOption("vsc.view", "Two"), - row_limit = abs(getOption("vsc.row_limit", 0))) { - as_truncated_data <- function(.data) { - .nrow <- nrow(.data) - if (row_limit != 0 && row_limit < .nrow) { - title <<- sprintf("%s (limited to %d/%d)", title, row_limit, .nrow) - .data <- utils::head(.data, n = row_limit) - } - return(.data) - } - - if (missing(title)) { - sub <- substitute(x) - title <- deparse(sub, nlines = 1) - } - if (inherits(x, "ArrowTabular")) { - x <- as_truncated_data(x) - x <- as.data.frame(x) - } - if (is.environment(x)) { - all_names <- ls(x) - is_active <- vapply(all_names, bindingIsActive, logical(1), USE.NAMES = TRUE, x) - is_promise <- rlang::env_binding_are_lazy(x, all_names[!is_active]) - x <- lapply(all_names, function(name) { - if (isTRUE(is_promise[name])) { - data.frame( - class = "promise", - type = "promise", - length = 0L, - size = 0L, - value = "(promise)", - stringsAsFactors = FALSE, - check.names = FALSE - ) - } else if (isTRUE(is_active[name])) { - data.frame( - class = "active_binding", - type = "active_binding", - length = 0L, - size = 0L, - value = "(active-binding)", - stringsAsFactors = FALSE, - check.names = FALSE - ) - } else { - obj <- x[[name]] - data.frame( - class = paste0(class(obj), collapse = ", "), - type = typeof(obj), - length = length(obj), - size = as.integer(object.size(obj)), - value = trimws(try_capture_str(obj, 0)), - stringsAsFactors = FALSE, - check.names = FALSE - ) - } - }) - names(x) <- all_names - if (length(x)) { - x <- do.call(rbind, x) - } else { - x <- data.frame( - class = character(), - type = character(), - length = integer(), - size = integer(), - value = character(), - stringsAsFactors = FALSE, - check.names = FALSE - ) - } - } - 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 - ) - } 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 - ) - }, 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 - ) - }) - } 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 - ) - } - } - - rebind("View", show_dataview, "utils") -} - -attach <- function() { - load_settings() - if (rstudioapi_enabled()) { - rstudioapi_util_env$update_addin_registry(addin_registry) - } - request("attach", - version = sprintf("%s.%s", R.version$major, R.version$minor), - tempdir = tempdir, - info = list( - command = commandArgs()[[1L]], - version = R.version.string, - start_time = format(file.info(tempdir)$ctime) - ), - plot_url = if (identical(names(dev.cur()), "httpgd")) httpgd::hgd_url(), - server = if (use_webserver) list( - host = host, - port = port, - token = token - ) else NULL - ) -} - -path_to_uri <- function(path) { - if (length(path) == 0) { - return(character()) - } - path <- path.expand(path) - if (.Platform$OS.type == "windows") { - prefix <- "file:///" - path <- gsub("\\", "/", path, fixed = TRUE) - } else { - prefix <- "file://" - } - paste0(prefix, utils::URLencode(path)) -} - -request_browser <- function(url, title, ..., viewer) { - # Printing URL with specific port triggers - # auto port-forwarding under remote development - message("Browsing ", url) - request("browser", url = url, title = title, ..., viewer = viewer) -} - -show_browser <- function(url, title = url, ..., - viewer = getOption("vsc.browser", "Active")) { - proxy_uri <- Sys.getenv("VSCODE_PROXY_URI") - if (nzchar(proxy_uri)) { - is_base_path <- grepl("\\:\\d+$", url) - url <- sub("^https?\\://(127\\.0\\.0\\.1|localhost)(\\:)?", - sub("\\{\\{?port\\}\\}?/?", "", proxy_uri), url - ) - if (is_base_path) { - url <- paste0(url, "/") - } - } - if (grepl("^https?\\://(127\\.0\\.0\\.1|localhost)(\\:\\d+)?", url)) { - request_browser(url = url, title = title, ..., viewer = viewer) - } else if (grepl("^https?\\://", url)) { - message( - if (nzchar(proxy_uri)) { - "VSCode is not running on localhost but on a remote server.\n" - } else { - "VSCode WebView only supports showing local http content.\n" - }, - "Opening in external browser..." - ) - request_browser(url = url, title = title, ..., viewer = FALSE) - } else { - path <- sub("^file\\://", "", url) - if (file.exists(path)) { - path <- normalizePath(path, "/", mustWork = TRUE) - if (grepl("\\.html?$", path, ignore.case = TRUE)) { - message( - "VSCode WebView has restricted access to local file.\n", - "Opening in external browser..." - ) - request_browser(url = path_to_uri(path), - title = title, ..., viewer = FALSE - ) - } else { - request("dataview", source = "object", type = "txt", - title = title, file = path, viewer = viewer - ) - } - } else { - stop("File not exists") - } - } -} - -show_webview <- function(url, title, ..., viewer) { - if (!is.character(url)) { - real_url <- NULL - temp_viewer <- function(url, ...) { - real_url <<- url - } - op <- options(viewer = temp_viewer, page_viewer = temp_viewer) - on.exit(options(op)) - print(url) - if (is.character(real_url)) { - url <- real_url - } else { - stop("Invalid object") - } - } - proxy_uri <- Sys.getenv("VSCODE_PROXY_URI") - if (nzchar(proxy_uri)) { - is_base_path <- grepl("\\:\\d+$", url) - url <- sub("^https?\\://(127\\.0\\.0\\.1|localhost)(\\:)?", - sub("\\{\\{?port\\}\\}?/?", "", proxy_uri), url - ) - if (is_base_path) { - url <- paste0(url, "/") - } - } - if (grepl("^https?\\://(127\\.0\\.0\\.1|localhost)(\\:\\d+)?", url)) { - request_browser(url = url, title = title, ..., viewer = viewer) - } else if (grepl("^https?\\://", url)) { - message( - if (nzchar(proxy_uri)) { - "VSCode is not running on localhost but on a remote server.\n" - } else { - "VSCode WebView only supports showing local http content.\n" - }, - "Opening in external browser..." - ) - request_browser(url = url, title = title, ..., viewer = FALSE) - } else if (file.exists(url)) { - file <- normalizePath(url, "/", mustWork = TRUE) - request("webview", file = file, title = title, viewer = viewer, ...) - } else { - stop("File not exists") - } -} - -show_viewer <- function(url, title = NULL, ..., - viewer = getOption("vsc.viewer", "Two")) { - if (is.null(title)) { - expr <- substitute(url) - if (is.character(url)) { - title <- "Viewer" - } else { - title <- deparse(expr, nlines = 1) - } - } - show_webview(url = url, title = title, ..., viewer = viewer) -} - -show_page_viewer <- function(url, title = NULL, ..., - viewer = getOption("vsc.page_viewer", "Active")) { - if (is.null(title)) { - expr <- substitute(url) - if (is.character(url)) { - title <- "Page Viewer" - } else { - title <- deparse(expr, nlines = 1) - } - } - show_webview(url = url, title = title, ..., viewer = viewer) -} - -options( - browser = show_browser, - viewer = show_viewer, - page_viewer = show_page_viewer -) - -# rstudioapi -rstudioapi_enabled <- function() { - isTRUE(getOption("vsc.rstudioapi", TRUE)) -} - -if (rstudioapi_enabled()) { - response_timeout <- 5 - response_lock_file <- file.path(dir_session, "response.lock") - response_file <- file.path(dir_session, "response.log") - file.create(response_lock_file, showWarnings = FALSE) - file.create(response_file, showWarnings = FALSE) - addin_registry <- file.path(dir_session, "addins.json") - # This is created in attach() - - get_response_timestamp <- function() { - readLines(response_lock_file) - } - # initialise the reponse timestamp to empty string - response_time_stamp <- "" - - get_response_lock <- function() { - lock_time_stamp <- get_response_timestamp() - if (isTRUE(lock_time_stamp != response_time_stamp)) { - response_time_stamp <<- lock_time_stamp - TRUE - } else { - FALSE - } - } - - request_response <- function(command, ...) { - request(command, ..., sd = dir_session) - wait_start <- Sys.time() - while (!get_response_lock()) { - if ((Sys.time() - wait_start) > response_timeout) { - stop( - "Did not receive a response from VSCode-R API within ", - response_timeout, " seconds." - ) - } - Sys.sleep(0.1) - } - jsonlite::read_json(response_file) - } - - rstudioapi_util_env <- new.env() - rstudioapi_env <- new.env(parent = rstudioapi_util_env) - source(file.path(dir_init, "rstudioapi_util.R"), local = rstudioapi_util_env) - source(file.path(dir_init, "rstudioapi.R"), local = rstudioapi_env) - setHook( - packageEvent("rstudioapi", "onLoad"), - function(...) { - rstudioapi_util_env$rstudioapi_patch_hook(rstudioapi_env) - } - ) - if ("rstudioapi" %in% loadedNamespaces()) { - # if the rstudioapi is already loaded, for example via a call to - # library(tidyverse) in the user's profile, we need to shim it now. - # There's no harm in having also registered the hook in this case. It can - # work in the event that the namespace is unloaded and reloaded. - rstudioapi_util_env$rstudioapi_patch_hook(rstudioapi_env) - } - -} - -print.help_files_with_topic <- function(h, ...) { - viewer <- getOption("vsc.helpPanel", "Two") - if (!identical(FALSE, viewer) && length(h) >= 1 && is.character(h)) { - file <- h[1] - path <- dirname(file) - dirpath <- dirname(path) - pkgname <- basename(dirpath) - requestPath <- paste0( - "/library/", - pkgname, - "/html/", - basename(file), - ".html" - ) - request(command = "help", requestPath = requestPath, viewer = viewer) - } else { - utils:::print.help_files_with_topic(h, ...) - } - invisible(h) -} - -print.hsearch <- function(x, ...) { - viewer <- getOption("vsc.helpPanel", "Two") - if (!identical(FALSE, viewer) && length(x) >= 1) { - requestPath <- paste0( - "/doc/html/Search?pattern=", - tools:::escapeAmpersand(x$pattern), - paste0("&fields.", x$fields, "=1", - collapse = "" - ), - if (!is.null(x$agrep)) paste0("&agrep=", x$agrep), - if (!x$ignore.case) "&ignore.case=0", - if (!identical( - x$types, - getOption("help.search.types") - )) { - paste0("&types.", x$types, "=1", - collapse = "" - ) - }, - if (!is.null(x$package)) { - paste0( - "&package=", - paste(x$package, collapse = ";") - ) - }, - if (!identical(x$lib.loc, .libPaths())) { - paste0( - "&lib.loc=", - paste(x$lib.loc, collapse = ";") - ) - } - ) - request(command = "help", requestPath = requestPath, viewer = viewer) - } else { - utils:::print.hsearch(x, ...) - } - invisible(x) -} - -# a copy of .S3method(), since this function is new in R 4.0 -.S3method <- function(generic, class, method) { - if (missing(method)) { - method <- paste(generic, class, sep = ".") - } - method <- match.fun(method) - registerS3method(generic, class, method, envir = parent.frame()) - invisible(NULL) -} - -reg.finalizer(.GlobalEnv, function(e) .vsc$request("detach"), onexit = TRUE) From 77b0c2c4db1eee20edc3d9421a503f850297f00f Mon Sep 17 00:00:00 2001 From: toscm Date: Sat, 2 May 2026 17:16:09 -0700 Subject: [PATCH 2/2] fix(lsp): ignore virtual and deleted files for diagnostics --- src/languageService.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/languageService.ts b/src/languageService.ts index 18a99b6e..c7776ecc 100644 --- a/src/languageService.ts +++ b/src/languageService.ts @@ -4,6 +4,7 @@ import * as os from 'os'; import { dirname } from 'path'; import * as net from 'net'; import { URL } from 'url'; +import * as fs from 'fs'; import { LanguageClient, LanguageClientOptions, StreamInfo, DocumentFilter, ErrorAction, CloseAction, RevealOutputChannelOn } from 'vscode-languageclient/node'; import { Disposable, workspace, Uri, TextDocument, WorkspaceConfiguration, OutputChannel, window, WorkspaceFolder } from 'vscode'; import { DisposableProcess, getRLibPaths, getRpath, promptToInstallRPackage, spawn, substituteVariables } from './util'; @@ -137,6 +138,23 @@ export class LanguageService implements Disposable { configurationSection: 'r.lsp', fileEvents: workspace.createFileSystemWatcher('**/*.{R,r}'), }, + middleware: { + handleDiagnostics: (uri, diagnostics, next) => { + const supportedSchemes = ['file', 'untitled', 'vscode-notebook-cell']; + + // Drop diagnostics for unsupported schemes (like git://) + if (!supportedSchemes.includes(uri.scheme)) { + return next(uri, []); + } + + // Drop diagnostics for files that no longer exist on disk + if (uri.scheme === 'file' && !fs.existsSync(uri.fsPath)) { + return next(uri, []); + } + + return next(uri, diagnostics); + } + }, revealOutputChannelOn: RevealOutputChannelOn.Never, errorHandler: { error: () => { @@ -324,8 +342,11 @@ export class LanguageService implements Disposable { this.startMultiLanguageService(); } else { const documentSelector: DocumentFilter[] = [ - { language: 'r' }, - { language: 'rmd' }, + { scheme: 'file', language: 'r' }, + { scheme: 'file', language: 'rmd' }, + { scheme: 'untitled', language: 'r' }, + { scheme: 'untitled', language: 'rmd' }, + { scheme: 'vscode-notebook-cell', language: 'r' }, ]; const workspaceFolder = workspace.workspaceFolders?.[0];