From 57813909d30f1b23e1faf638e386ed6c08000e71 Mon Sep 17 00:00:00 2001 From: Kevin Ushey Date: Tue, 27 Jan 2026 16:57:10 -0800 Subject: [PATCH 1/2] add API for edit suggestions --- NAMESPACE | 1 + NEWS.md | 4 +++ R/document-api.R | 76 +++++++++++++++++++++++++++++++++++++++ man/showEditSuggestion.Rd | 62 ++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 man/showEditSuggestion.Rd diff --git a/NAMESPACE b/NAMESPACE index e53a4df..2fd5ac9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -113,6 +113,7 @@ export(setGhostText) export(setPersistentValue) export(setSelectionRanges) export(showDialog) +export(showEditSuggestion) export(showPrompt) export(showQuestion) export(sourceMarkers) diff --git a/NEWS.md b/NEWS.md index 99fb1bb..c087a78 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # rstudioapi (development version) +* Added `showEditSuggestion()` for displaying edit suggestions in the RStudio + editor. The function takes a document range and suggested replacement text, + allowing RStudio to present a visual diff that users can accept or dismiss. + # rstudioapi 0.18.0 * `rstudioapi::documentNew()` now accepts arbitrary document types. (#316) diff --git a/R/document-api.R b/R/document-api.R index 98d53f5..fe4e0b9 100644 --- a/R/document-api.R +++ b/R/document-api.R @@ -181,6 +181,82 @@ documentSaveAll <- function() { callFun("documentSaveAll") } +#' Show an Edit Suggestion in RStudio +#' +#' Displays an edit suggestion for a specified range in the active document. +#' The suggestion shows what text would replace the given range. RStudio +#' computes and displays the necessary insertions and deletions. +#' +#' @param range A document range (or object coercible to a range) indicating +#' where the suggestion applies. Can be a \code{\link{document_range}} object or +#' a numeric vector of the form \code{c(start_row, start_col, end_row, end_col)}. +#' +#' @param text Character string containing the suggested replacement text for +#' the specified range. +#' +#' @param id The document id. When \code{NULL} or blank, the suggestion will +#' be shown in the currently open, or last focused, RStudio document. +#' +#' @return Invisibly returns \code{TRUE} if the suggestion was successfully shown, +#' \code{FALSE} otherwise. +#' +#' @details +#' The edit suggestion appears in the RStudio editor, showing what would change +#' if the suggestion were accepted. Users can accept or dismiss the suggestion +#' through RStudio's UI. +#' +#' The \code{range} parameter specifies the region of text that would be replaced. +#' RStudio automatically computes the minimal diff between the current text in +#' that range and the suggested \code{text}. +#' +#' @note The \code{showEditSuggestion} function was added in version 2026.01.0 +#' of RStudio. +#' +#' @seealso \code{\link{setGhostText}} for inline completion suggestions, +#' \code{\link{insertText}} for direct text insertion, +#' \code{\link{modifyRange}} for programmatic range modification. +#' +#' @examples +#' \dontrun{ +#' # Suggest replacing a word +#' range <- document_range(c(5, 1), c(5, 10)) +#' showEditSuggestion(range, "corrected_text") +#' +#' # Using vector notation +#' showEditSuggestion(c(5, 1, 5, 10), "corrected_text") +#' +#' # Suggest for current selection +#' context <- getActiveDocumentContext() +#' selection <- primary_selection(context) +#' showEditSuggestion(selection$range, "improved code") +#' } +#' +#' @export +showEditSuggestion <- function(range, text, id = NULL) { + + # Check if function is available in this RStudio version + if (!hasFun("showEditSuggestion")) { + warning("showEditSuggestion requires RStudio 2026.01.0 or newer") + return(invisible(FALSE)) + } + + # Validate and coerce range parameter + range <- as.document_range(range) + + # Validate text parameter + if (!is.character(text) || length(text) != 1) { + stop("'text' must be a single character string") + } + + # Call the RStudio API + callFun("showEditSuggestion", + range = range, + text = text, + id = id) + + invisible(TRUE) +} + #' Retrieve Information about an RStudio Editor #' #' Returns information about an RStudio editor. diff --git a/man/showEditSuggestion.Rd b/man/showEditSuggestion.Rd new file mode 100644 index 0000000..f370bb1 --- /dev/null +++ b/man/showEditSuggestion.Rd @@ -0,0 +1,62 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/document-api.R +\name{showEditSuggestion} +\alias{showEditSuggestion} +\title{Show an Edit Suggestion in RStudio} +\usage{ +showEditSuggestion(range, text, id = NULL) +} +\arguments{ +\item{range}{A document range (or object coercible to a range) indicating +where the suggestion applies. Can be a \code{\link{document_range}} object or +a numeric vector of the form \code{c(start_row, start_col, end_row, end_col)}.} + +\item{text}{Character string containing the suggested replacement text for +the specified range.} + +\item{id}{The document id. When \code{NULL} or blank, the suggestion will +be shown in the currently open, or last focused, RStudio document.} +} +\value{ +Invisibly returns \code{TRUE} if the suggestion was successfully shown, +\code{FALSE} otherwise. +} +\description{ +Displays an edit suggestion for a specified range in the active document. +The suggestion shows what text would replace the given range. RStudio +computes and displays the necessary insertions and deletions. +} +\details{ +The edit suggestion appears in the RStudio editor, showing what would change +if the suggestion were accepted. Users can accept or dismiss the suggestion +through RStudio's UI. + +The \code{range} parameter specifies the region of text that would be replaced. +RStudio automatically computes the minimal diff between the current text in +that range and the suggested \code{text}. +} +\note{ +The \code{showEditSuggestion} function was added in version 2026.01.0 +of RStudio. +} +\examples{ +\dontrun{ +# Suggest replacing a word +range <- document_range(c(5, 1), c(5, 10)) +showEditSuggestion(range, "corrected_text") + +# Using vector notation +showEditSuggestion(c(5, 1, 5, 10), "corrected_text") + +# Suggest for current selection +context <- getActiveDocumentContext() +selection <- primary_selection(context) +showEditSuggestion(selection$range, "improved code") +} + +} +\seealso{ +\code{\link{setGhostText}} for inline completion suggestions, +\code{\link{insertText}} for direct text insertion, +\code{\link{modifyRange}} for programmatic range modification. +} From fdee30034261057ca952efca178ca22e83c87aac Mon Sep 17 00:00:00 2001 From: Kevin Ushey Date: Thu, 28 May 2026 14:04:50 -0700 Subject: [PATCH 2/2] fix getMode() fallback for old rstudio versions On very old versions of RStudio that lack the internal .rs.isDesktop() helper, the getMode() fallback path threw 'attempt to apply non-function'. Guard the .rs.isDesktop() call and fall back to versionInfo()$mode (available since RStudio 0.97.124) when it is absent. The .rs.isDesktop() check is retained as the preferred fallback for performance reasons (#280): versionInfo() is slow because it reads the citation file. Fixes #326. --- NEWS.md | 4 ++++ R/code.R | 14 ++++++++++++-- tests/testthat/test-code.R | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/testthat/test-code.R diff --git a/NEWS.md b/NEWS.md index c087a78..3d39b0c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # rstudioapi (development version) +* `getMode()` no longer fails on very old versions of RStudio that lack the + internal `.rs.isDesktop()` helper. In that case, it now falls back to + `versionInfo()$mode`, which has been available since RStudio 0.97.124. (#326) + * Added `showEditSuggestion()` for displaying edit suggestions in the RStudio editor. The function takes a document range and suggested replacement text, allowing RStudio to present a visual diff that users can accept or dismiss. diff --git a/R/code.R b/R/code.R index f10e58c..3d1aee1 100644 --- a/R/code.R +++ b/R/code.R @@ -89,8 +89,18 @@ getMode <- function() { # use fallback if not verifyAvailable() - rstudio <- as.environment("tools:rstudio") - if (rstudio$.rs.isDesktop()) "desktop" else "server" + + # prefer the internal '.rs.isDesktop()' helper when available; this is + # fast, and lets us avoid the relatively slow 'versionInfo()' call, which + # reads the RStudio citation file (#280) + rstudio <- toolsEnv() + if (is.function(rstudio$.rs.isDesktop)) + return(if (rstudio$.rs.isDesktop()) "desktop" else "server") + + # otherwise, fall back to 'versionInfo()'; its '$mode' field has been + # available since RStudio 0.97.124, and so works even with very old + # versions of RStudio that lack the '.rs.isDesktop()' helper (#326) + versionInfo()$mode } diff --git a/tests/testthat/test-code.R b/tests/testthat/test-code.R new file mode 100644 index 0000000..ac58ff1 --- /dev/null +++ b/tests/testthat/test-code.R @@ -0,0 +1,34 @@ +test_that("getMode() prefers the fast '.rs.isDesktop()' helper when available", { + local_edition(3) + + rstudio <- new.env(parent = emptyenv()) + rstudio$.rs.isDesktop <- function() TRUE + + local_mocked_bindings( + hasFun = function(...) FALSE, + verifyAvailable = function(...) invisible(TRUE), + toolsEnv = function() rstudio, + versionInfo = function() stop("versionInfo() should not be called") + ) + + expect_identical(getMode(), "desktop") + + rstudio$.rs.isDesktop <- function() FALSE + expect_identical(getMode(), "server") +}) + +test_that("getMode() falls back to versionInfo() when '.rs.isDesktop()' is unavailable (#326)", { + local_edition(3) + + # an old RStudio that lacks the '.rs.isDesktop()' helper + rstudio <- new.env(parent = emptyenv()) + + local_mocked_bindings( + hasFun = function(...) FALSE, + verifyAvailable = function(...) invisible(TRUE), + toolsEnv = function() rstudio, + versionInfo = function() list(mode = "server") + ) + + expect_identical(getMode(), "server") +})