From 2c3c4a9ac39983d271c016b320056744a324cf8a Mon Sep 17 00:00:00 2001 From: Ricardo Rodrigo Basa Date: Thu, 15 Aug 2024 13:45:44 +0800 Subject: [PATCH] Convert to package (#8) --- .github/workflows/test-and-lint.yaml | 7 + DESCRIPTION | 10 +- NAMESPACE | 3 +- NEWS.md | 6 +- R/Rprofile.R | 70 --- R/box_lsp.R | 106 ++-- R/helper-utils.R | 494 ------------------ R/utils.R | 25 + README.md | 93 +++- inst/Rprofile.R | 5 + man/box_use_parser.Rd | 19 + man/use_box_lsp.Rd | 17 + tests/testthat/helper-utils.R | 24 +- .../test-lsp_completion_package_three_dots.R | 2 + 14 files changed, 267 insertions(+), 614 deletions(-) delete mode 100644 R/Rprofile.R delete mode 100644 R/helper-utils.R create mode 100644 R/utils.R create mode 100644 inst/Rprofile.R create mode 100644 man/box_use_parser.Rd create mode 100644 man/use_box_lsp.Rd diff --git a/.github/workflows/test-and-lint.yaml b/.github/workflows/test-and-lint.yaml index 801985b..f16332b 100644 --- a/.github/workflows/test-and-lint.yaml +++ b/.github/workflows/test-and-lint.yaml @@ -45,6 +45,13 @@ jobs: extra-packages: any::rcmdcheck needs: check + - name: R CMD CHECK + if: always() + uses: r-lib/actions/check-r-package@v2 + with: + error-on: '"note"' + args: 'c("--no-tests", "--no-manual", "--as-cran")' + - name: Lint if: always() shell: Rscript {0} diff --git a/DESCRIPTION b/DESCRIPTION index 3facdf4..e865a54 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: box.lsp Title: What the Package Does (One Line, Title Case) -Version: 0.0.0.9002 +Version: 0.0.0.9003 Authors@R: c( person("Ricardo Rodrigo", "Basa", role = c("aut", "cre"), email = "opensource+rodrigo@appsilon.com"), @@ -12,16 +12,18 @@ Description: What the package does (one paragraph). License: LGPL (>= 3) Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 Imports: box, - purrr + cli, + fs Suggests: - fs, languageserver, lintr, magrittr, mockery, + purrr, + rprojroot, stringi, stringr, testthat (>= 3.0.0), diff --git a/NAMESPACE b/NAMESPACE index 65a82e1..f88bc7a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,4 +1,5 @@ # Generated by roxygen2: do not edit by hand +export(box_use_parser) +export(use_box_lsp) import(box) -import(purrr) diff --git a/NEWS.md b/NEWS.md index 901bb81..5b23691 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,8 @@ -* box.lsp 0.0.0.9002 +# box.lsp 0.0.0.9003 + +* steps towards package + +# box.lsp 0.0.0.9002 * support for pkg[attach_list] * support for pkg[alias = attached_function] diff --git a/R/Rprofile.R b/R/Rprofile.R deleted file mode 100644 index 1be2423..0000000 --- a/R/Rprofile.R +++ /dev/null @@ -1,70 +0,0 @@ -box_use_parser <- function(expr, action) { - call <- match.call(box::use, expr) - packages <- unlist(lapply(call[-1], function(x) { - # this is for the last blank in box::use(something, ) - if (x == "") { - return() - } - - # this is for a whole package attached box::use(dplyr) - if (typeof(x) == "symbol") { - # attempts to get package$func to work - # nolint start - # action$assign(symbol = paste0(as.character(x), "$", getNamespaceExports(as.character(x))), value = NULL) - # action$assign(symbol = getNamespaceExports(as.character(x)), value = NULL) - # return() - # nolint end - return(as.character(x)) - } - - # this is for a whole module attached box::use(app/logic/utils) - if (as.character(x[[1]]) == "/") { - y <- x[[length(x)]] - - # this case is for app/logic/module_one - if (length(y) == 1) { - action$assign(symbol = as.character(y), value = NULL) - } - - # this case is for app/logic/module_two[...] - if (length(y) == 3 && y[[3]] == "...") { - # import box module, iterate over its namespace and assign - } - - # this case is for app/logic/module_three[a, b, c] - lapply(y[-c(1, 2)], function(z) { - action$assign(symbol = as.character(z), value = NULL) - }) - - return() - } - - # for box::use(dplyr[]) - if (x[[1]] == "[") { - # for box::use(dplyr[alias = a]) - # Does not seem to be needed - # nolint start - # action$assign(symbol = names(x) %>% purrr::keep(~. != ""), value = NULL) - # nolint end - - # for box::use(dplyr[a, b, c]) - lapply(as.character(x)[-c(1, 2)], function(y) { - if (y != "...") { - action$assign(symbol = as.character(y), value = NULL) - } - }) - - # for box::use(dplyr[a, b, ...]) - if (x[[length(x)]] == "...") { - as.character(x[[2]]) - } - } - })) - action$update(packages = packages) -} - -options( - languageserver.parser_hooks = list( - "box::use" = box_use_parser - ) -) diff --git a/R/box_lsp.R b/R/box_lsp.R index 21c3892..8bc0e59 100644 --- a/R/box_lsp.R +++ b/R/box_lsp.R @@ -1,38 +1,76 @@ #' @import box NULL -#' @import purrr -NULL +#' Box::use Document Parser +#' +#' Custom \{languageserver\} parser hook for \{box\} modules. +#' +#' @param expr An R expression to evaluate +#' @param action A list of action functions from `languageserver:::parse_expr()`. +#' @returns Used for side-effects provided by the `action` list of functions. +#' +#' @export +box_use_parser <- function(expr, action) { + call <- match.call(box::use, expr) + packages <- unlist(lapply(call[-1], function(x) { + # this is for the last blank in box::use(something, ) + if (x == "") { + return() + } + + # this is for a whole package attached box::use(dplyr) + if (typeof(x) == "symbol") { + # attempts to get package$func to work + # nolint start + # action$assign(symbol = paste0(as.character(x), "$", getNamespaceExports(as.character(x))), value = NULL) + # action$assign(symbol = getNamespaceExports(as.character(x)), value = NULL) + # return() + # nolint end + return(as.character(x)) + } + + # this is for a whole module attached box::use(app/logic/utils) + if (as.character(x[[1]]) == "/") { + y <- x[[length(x)]] + + # this case is for app/logic/module_one + if (length(y) == 1) { + action$assign(symbol = as.character(y), value = NULL) + } + + # this case is for app/logic/module_two[...] + if (length(y) == 3 && y[[3]] == "...") { + # import box module, iterate over its namespace and assign + } + + # this case is for app/logic/module_three[a, b, c] + lapply(y[-c(1, 2)], function(z) { + action$assign(symbol = as.character(z), value = NULL) + }) + + return() + } + + # for box::use(dplyr[]) + if (x[[1]] == "[") { + # for box::use(dplyr[alias = a]) + # Does not seem to be needed + # nolint start + # action$assign(symbol = names(x) %>% purrr::keep(~. != ""), value = NULL) + # nolint end + + # for box::use(dplyr[a, b, c]) + lapply(as.character(x)[-c(1, 2)], function(y) { + if (y != "...") { + action$assign(symbol = as.character(y), value = NULL) + } + }) -# stash -# nolint start -# box_use_parser <- function(expr, action) { -# fun <- if (requireNamespace("box", quietly = TRUE)) box::use else -# function(...) NULL -# call <- match.call(fun, expr, expand.dots = FALSE) -# -# if(!isTRUE(call$character.only)) { -# packages <- vapply( -# call[["..."]], -# function(item) { -# item <- as.character(item) -# if (item[1] == "[" && grepl("\\.\\.\\.", item[3])) { -# return(item[2]) -# } else { -# return("") -# } -# }, -# character(1L) -# ) -# packages <- purrr::keep(packages, ~. != "") -# action$update(packages = packages) -# -# test_value <- "function(x, y) { \"A \" }" -# action$assign(symbol = "module$fun", value = "function(x, y) { \"A \" }") -# action$parse(test_value) -# } -# } -# nolint end - -options(languageserver.trace = TRUE) -options(languageserver.debug = TRUE) + # for box::use(dplyr[a, b, ...]) + if (x[[length(x)]] == "...") { + as.character(x[[2]]) + } + } + })) + action$update(packages = packages) +} diff --git a/R/helper-utils.R b/R/helper-utils.R deleted file mode 100644 index 00b51fd..0000000 --- a/R/helper-utils.R +++ /dev/null @@ -1,494 +0,0 @@ -### From REditors/languageserver/tests/testthat/helper-utils.R -# nolint start -suppressPackageStartupMessages({ - library(magrittr) - library(mockery) - library(purrr) - library(fs) -}) - -# a hack to make withr::defer_parent to work, see https://github.com/r-lib/withr/issues/123 -defer <- withr::defer - -expect_equivalent <- function(x, y) { - expect_equal(x, y, ignore_attr = TRUE) -} - -language_client <- function(working_dir = getwd(), diagnostics = FALSE, capabilities = NULL) { - withr::local_dir(working_dir) - withr::local_file(".Rprofile", { - rprofile <- readLines(fs::path(rprojroot::find_rstudio_root_file(), "R", "Rprofile.R")) - - writeLines(rprofile, ".Rprofile") - readLines(".Rprofile") - }) - - if (nzchar(Sys.getenv("R_LANGSVR_LOG"))) { - script <- sprintf( - "languageserver::run(debug = '%s')", - normalizePath(Sys.getenv("R_LANGSVR_LOG"), "/", mustWork = FALSE)) - } else { - script <- "languageserver::run()" - } - - client <- languageserver:::LanguageClient$new( - file.path(R.home("bin"), "R"), c("--slave", "-e", script)) - - client$notification_handlers <- list( - `textDocument/publishDiagnostics` = function(self, params) { - uri <- params$uri - diagnostics <- params$diagnostics - self$diagnostics$set(uri, diagnostics) - } - ) - - client$start(working_dir = working_dir, capabilities = capabilities) - client$catch_callback_error <- FALSE - # initialize request - data <- client$fetch(blocking = TRUE) - client$handle_raw(data) - client %>% notify("initialized") - client %>% notify( - "workspace/didChangeConfiguration", list(settings = list(diagnostics = diagnostics))) - withr::defer_parent({ - # it is sometimes necessary to shutdown the server probably - # we skip this for other times for speed - if (Sys.getenv("R_LANGSVR_TEST_FAST", "YES") == "NO") { - client %>% respond("shutdown", NULL, retry = FALSE) - client$process$wait(10 * 1000) # 10 sec - if (client$process$is_alive()) { - cat("server did not shutdown peacefully\n") - client$process$kill_tree() - } - } else { - client$process$kill_tree() - } - }) - client -} - - -notify <- function(client, method, params = NULL) { - client$deliver(languageserver:::Notification$new(method, params)) - invisible(client) -} - - -did_open <- function(client, path, uri = languageserver:::path_to_uri(path), text = NULL, languageId = NULL) { - if (is.null(text)) { - text <- stringi::stri_read_lines(path) - } - text <- paste0(text, collapse = "\n") - - if (is.null(languageId)) { - languageId <- if (is_rmarkdown(uri)) "rmd" else "r" - } - - notify( - client, - "textDocument/didOpen", - list( - textDocument = list( - uri = uri, - languageId = languageId, - version = 1, - text = text - ) - ) - ) - invisible(client) -} - - -did_save <- function(client, path, uri = languageserver:::path_to_uri(path), text = NULL) { - includeText <- tryCatch( - client$ServerCapabilities$textDocumentSync$save$includeText, - error = function(e) FALSE - ) - if (includeText) { - if (is.null(text)) { - text <- stringi::stri_read_lines(path) - } - text <- paste0(text, collapse = "\n") - params <- list(textDocument = list(uri = uri), text = text) - } else { - params <- list(textDocument = list(uri = uri)) - } - notify( - client, - "textDocument/didSave", - params) - Sys.sleep(0.5) - invisible(client) -} - - -respond <- function(client, method, params, timeout, allow_error = FALSE, - retry = TRUE, retry_when = function(result) length(result) == 0) { - if (missing(timeout)) { - if (Sys.getenv("R_COVR", "") == "true") { - # we give more time to covr - timeout <- 30 - } else { - timeout <- 10 - } - } - storage <- new.env(parent = .GlobalEnv) - cb <- function(self, result, error = NULL) { - if (is.null(error)) { - storage$done <- TRUE - storage$result <- result - } else if (allow_error) { - storage$done <- TRUE - storage$result <- error - } - } - - start_time <- Sys.time() - remaining <- timeout - client$deliver(client$request(method, params), callback = cb) - if (method == "shutdown") { - # do not expect the server returns anything - return(NULL) - } - while (!isTRUE(storage$done)) { - if (remaining < 0) { - fail("timeout when obtaining response") - return(NULL) - } - data <- client$fetch(blocking = TRUE, timeout = remaining) - if (!is.null(data)) client$handle_raw(data) - remaining <- (start_time + timeout) - Sys.time() - } - result <- storage$result - if (retry && retry_when(result)) { - remaining <- (start_time + timeout) - Sys.time() - if (remaining < 0) { - fail("timeout when obtaining desired response") - return(NULL) - } - Sys.sleep(0.2) - return(Recall(client, method, params, remaining, allow_error, retry, retry_when)) - } - return(result) -} - - -respond_completion <- function(client, path, pos, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/completion", - list( - textDocument = list(uri = uri), - position = list(line = pos[1], character = pos[2]) - ), - ... - ) -} - -respond_completion_item_resolve <- function(client, params, ...) { - respond( - client, - "completionItem/resolve", - params, - ... - ) -} - -respond_signature <- function(client, path, pos, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/signatureHelp", - list( - textDocument = list(uri = uri), - position = list(line = pos[1], character = pos[2]) - ), - ... - ) -} - -respond_hover <- function(client, path, pos, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/hover", - list( - textDocument = list(uri = uri), - position = list(line = pos[1], character = pos[2]) - ), - ... - ) -} - -respond_definition <- function(client, path, pos, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/definition", - list( - textDocument = list(uri = uri), - position = list(line = pos[1], character = pos[2]) - ), - ... - ) -} - -respond_references <- function(client, path, pos, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/references", - list( - textDocument = list(uri = uri), - position = list(line = pos[1], character = pos[2]) - ), - ... - ) -} - -respond_rename <- function(client, path, pos, newName, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/rename", - list( - textDocument = list(uri = uri), - position = list(line = pos[1], character = pos[2]), - newName = newName - ), - ... - ) -} - -respond_prepare_rename <- function(client, path, pos, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/prepareRename", - list( - textDocument = list(uri = uri), - position = list(line = pos[1], character = pos[2]) - ), - ... - ) -} - - -respond_formatting <- function(client, path, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/formatting", - list( - textDocument = list(uri = uri), - options = list(tabSize = 4, insertSpaces = TRUE) - ), - ... - ) -} - -respond_range_formatting <- function(client, path, start_pos, end_pos, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/rangeFormatting", - list( - textDocument = list(uri = uri), - range = range( - start = position(start_pos[1], start_pos[2]), - end = position(end_pos[1], end_pos[2]) - ), - options = list(tabSize = 4, insertSpaces = TRUE) - ), - ... - ) -} - -respond_folding_range <- function(client, path, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/foldingRange", - list( - textDocument = list(uri = uri)), - ... - ) -} - -respond_selection_range <- function(client, path, positions, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/selectionRange", - list( - textDocument = list(uri = uri), - positions = positions), - ... - ) -} - -respond_on_type_formatting <- function(client, path, pos, ch, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/onTypeFormatting", - list( - textDocument = list(uri = uri), - position = position(pos[1], pos[2]), - ch = ch, - options = list(tabSize = 4, insertSpaces = TRUE) - ), - ... - ) -} - - -respond_document_highlight <- function(client, path, pos, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/documentHighlight", - list( - textDocument = list(uri = uri), - position = list(line = pos[1], character = pos[2]) - ), - ... - ) -} - -respond_document_symbol <- function(client, path, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/documentSymbol", - list( - textDocument = list(uri = uri) - ), - ... - ) -} - -respond_workspace_symbol <- function(client, query, ...) { - respond( - client, - "workspace/symbol", - list( - query = query - ), - ... - ) -} - -respond_document_link <- function(client, path, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/documentLink", - list( - textDocument = list(uri = uri) - ), - ... - ) -} - -respond_document_link_resolve <- function(client, params, ...) { - respond( - client, - "documentLink/resolve", - params, - ... - ) -} - -respond_document_color <- function(client, path, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/documentColor", - list( - textDocument = list(uri = uri) - ), - ... - ) -} - -respond_document_folding_range <- function(client, path, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/foldingRange", - list( - textDocument = list(uri = uri) - ), - ... - ) -} - -respond_prepare_call_hierarchy <- function(client, path, pos, ..., uri = languageserver:::path_to_uri(path)) { - respond( - client, - "textDocument/prepareCallHierarchy", - list( - textDocument = list(uri = uri), - position = list(line = pos[1], character = pos[2]) - ), - ... - ) -} - -respond_call_hierarchy_incoming_calls <- function(client, item, ...) { - respond( - client, - "callHierarchy/incomingCalls", - list( - item = item - ), - ... - ) -} - -respond_call_hierarchy_outgoing_calls <- function(client, item, ...) { - respond( - client, - "callHierarchy/outgoingCalls", - list( - item = item - ), - ... - ) -} - -respond_code_action <- function(client, path, start_pos, end_pos, ..., uri = languageserver:::path_to_uri(path)) { - diagnostics <- client$diagnostics$get(uri) - range <- range( - start = position(start_pos[1], start_pos[2]), - end = position(end_pos[1], end_pos[2]) - ) - respond( - client, - "textDocument/codeAction", - list( - textDocument = list(uri = uri), - range = range, - context = list( - diagnostics = Filter(function(item) { - range_overlap(item$range, range) - }, diagnostics) - ) - ), - ... - ) -} - -wait_for <- function(client, method, timeout = 30) { - storage <- new.env(parent = .GlobalEnv) - start_time <- Sys.time() - remaining <- timeout - - original_handler <- client$notification_handlers[[method]] - on.exit({ - client$notification_handlers[[method]] <- original_handler - }) - client$notification_handlers[[method]] <- function(self, params) { - storage$params <- params - original_handler(self, params) - } - - while (remaining > 0) { - data <- client$fetch(blocking = TRUE, timeout = remaining) - if (!is.null(data)) { - client$handle_raw(data) - if (hasName(storage, "params")) { - return(storage$params) - } - } - remaining <- (start_time + timeout) - Sys.time() - } - NULL -} -# nolint end diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..6e93d79 --- /dev/null +++ b/R/utils.R @@ -0,0 +1,25 @@ +#' Configures a project to use \{box.lsp\} +#' +#' @param file_path File name to append `{box.lsp}` configuration lines. +#' @returns Writes configuration lines to `file_path`. +#' +#' @export +use_box_lsp <- function(file_path = ".Rprofile") { + rprofile <- fs::path_package("box.lsp", "Rprofile.R") + to_write <- readLines(rprofile) + + tryCatch({ + if (file.exists(file_path)) { + cli::cli_alert_info( + "{file_path} exists. Will append {{box.lsp}} configuration lines to the file." + ) + write("", file = file_path, append = TRUE) + } + write(to_write, file = file_path, append = TRUE) + cli::cli_alert_success( + "{{box.lsp}} configuration written to {file_path}. Please check the changes made." + ) + }, error = function(e) { + cli::cli_abort(e) + }) +} diff --git a/README.md b/README.md index b35f868..42cc0d8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# box_lsp -Custom language server parser for box modules +# box.lsp +*Experimental* custom language server parser hook for `{box}` modules -This repository is intended for *development purposes only*. It is structured as an R package, but, as of this time, there is *no intention to publish* it on CRAN. The R package restructure is merely being used for the development tools (`{devtools}`, `{usethis}`, `{testthat}`, et al). It may later on become part of `{rhino}` or `{languageserver}` itself. +This package is an attempt to provide `{box}`-compatibility for `{languageserver}` used in VS Code. The code is based on initial work by [Pavel Demin](https://github.com/Gotfrid). @@ -21,17 +21,17 @@ The code is based on initial work by [Pavel Demin](https://github.com/Gotfrid). ## How to use -1. Copy the contents of `inst/Rprofile.R` to your project's `.Rprofile`. +1. `box.lsp::use_box_lsp()` to configure your project's `.Rprofile` file. 2. Restart the R session to load `.Rprofile`. ## How to develop 1. Ensure all `Imports` and `Suggests` packages are installed. 2. Set `R_LANGSVR_LOG=./lsp.log` in `.Renviron` to start logging -3. Restart R session to load `.Rprofile` and `.Renviron` +3. Restart R session to load `.Rprofile` and `.Renviron`. 4. `devtools::load_all()` to load all development functions. -### Dev work on `box_use_parser()` +### Development work on `box_use_parser()` ```R action <- list( @@ -50,9 +50,10 @@ box_use_parser(expr, action) ### Dev work on completion -Lines count from zero. +Lines and characters are index zero. ```R +source("./tests/testthat/helper-utils.R") client <- language_client() temp_file <- withr::local_tempfile(fileext = ".R") @@ -68,5 +69,81 @@ writeLines( client %>% did_save(temp_file) client %>% respond_completion( - temp_file, c(1, 8)) + temp_file, c(1, 5)) ``` + +### Development and Debugging using TCP LSP Client + +#### Install `tcplspclient` + +An interactive [client](https://github.com/MilesMcBain/tcplspclient) for `{languageserver}`. + +```R +pak::pkg_install("milesmcbain/tcplspclient") +devtools::install_github("milesmcbain/tcplspclient") +``` + +#### On Instance A +```R +library(tcplspclient) +client <- TCPLanguageClient$new(host = "localhost", port = 8888) +``` + +#### On Instance B with `{languageserver}` package repo open + +Copy + +```R +source(".Rprofile") # custom parsers, custom lsp config +# Add `browser()` statements inside `{languageserver}` functions, or +# Add debug breakpoints in the RStudio editor. +devtools::load_all() +# Run `debugonce()`/`debug()` if needed. +run(port = 8888) +``` + +#### On Instance A + +```R +# tcp_test.R +box::use(stringr[...]) + +str_c + +some_list <- list( + aaa = "A", + bbb = "B" +) + +some_list$a +``` + +```R +# Check connection +client$handshake() + +doc_path <- "tcp_test.R" + +# With every change made to the test document: +client$send_notification( + method = "textDocument/didSave", + params = list( + textDocument = list(uri = languageserver:::path_to_uri(doc_path)), + text = paste0(stringi::stri_read_lines(doc_path), collapse = "\n") + ) +) + +# To trigger a completion request: +# line and character are index 0 +client$send_message( + method = "textDocument/completion", + params = list( + textDocument = list( + uri = languageserver:::path_to_uri(doc_path) + ), + position = list(line = 2, character = 5) + ) +) +``` + +The interactive debugger runs in Instance B. diff --git a/inst/Rprofile.R b/inst/Rprofile.R new file mode 100644 index 0000000..591cb4b --- /dev/null +++ b/inst/Rprofile.R @@ -0,0 +1,5 @@ +options( + languageserver.parser_hooks = list( + "box::use" = box.lsp::box_use_parser + ) +) diff --git a/man/box_use_parser.Rd b/man/box_use_parser.Rd new file mode 100644 index 0000000..0bf1229 --- /dev/null +++ b/man/box_use_parser.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/box_lsp.R +\name{box_use_parser} +\alias{box_use_parser} +\title{Box::use Document Parser} +\usage{ +box_use_parser(expr, action) +} +\arguments{ +\item{expr}{An R expression to evaluate} + +\item{action}{A list of action functions from \code{languageserver:::parse_expr()}.} +} +\value{ +Used for side-effects provided by the \code{action} list of functions. +} +\description{ +Custom \{languageserver\} parser hook for \{box\} modules. +} diff --git a/man/use_box_lsp.Rd b/man/use_box_lsp.Rd new file mode 100644 index 0000000..42dcc65 --- /dev/null +++ b/man/use_box_lsp.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{use_box_lsp} +\alias{use_box_lsp} +\title{Configures a project to use \{box.lsp\}} +\usage{ +use_box_lsp(file_path = ".Rprofile") +} +\arguments{ +\item{file_path}{File name to append \code{{box.lsp}} configuration lines.} +} +\value{ +Writes configuration lines to \code{file_path}. +} +\description{ +Configures a project to use \{box.lsp\} +} diff --git a/tests/testthat/helper-utils.R b/tests/testthat/helper-utils.R index 00b51fd..5ed1bc0 100644 --- a/tests/testthat/helper-utils.R +++ b/tests/testthat/helper-utils.R @@ -7,6 +7,8 @@ suppressPackageStartupMessages({ library(fs) }) + + # a hack to make withr::defer_parent to work, see https://github.com/r-lib/withr/issues/123 defer <- withr::defer @@ -17,9 +19,27 @@ expect_equivalent <- function(x, y) { language_client <- function(working_dir = getwd(), diagnostics = FALSE, capabilities = NULL) { withr::local_dir(working_dir) withr::local_file(".Rprofile", { - rprofile <- readLines(fs::path(rprojroot::find_rstudio_root_file(), "R", "Rprofile.R")) + if (testthat::is_checking()) { + parser_code <- c( + "box_use_parser <-", + deparse(box.lsp::box_use_parser) + ) + rprofile <- readLines(fs::path_package("box.lsp", "Rprofile.R")) + + } else { + source(fs::path(rprojroot::find_package_root_file(), "R", "box_lsp.R"), local = TRUE) + parser_code <- c( + "box_use_parser <-", + deparse(box_use_parser) + ) + + rprofile <- readLines(fs::path(rprojroot::find_package_root_file(), "inst", "Rprofile.R")) + rprofile <- sub("box.lsp::", "", rprofile) + } + + write(parser_code, ".Rprofile", append = TRUE) + write(rprofile, ".Rprofile", append = TRUE) - writeLines(rprofile, ".Rprofile") readLines(".Rprofile") }) diff --git a/tests/testthat/test-lsp_completion_package_three_dots.R b/tests/testthat/test-lsp_completion_package_three_dots.R index 737f21d..4b9c879 100644 --- a/tests/testthat/test-lsp_completion_package_three_dots.R +++ b/tests/testthat/test-lsp_completion_package_three_dots.R @@ -1,4 +1,6 @@ test_that("completion of package attached three dots works", { + testthat::skip_on_cran() + testthat::skip_if(testthat::is_checking()) client <- language_client() temp_file <- withr::local_tempfile(fileext = ".R")