From 89bced25d696bf0337282919c498cfd14b3444d0 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 9 Jul 2025 13:22:29 -0700 Subject: [PATCH 1/4] use_air() also turns on "format on save" for qmd + other updates --- .Rbuildignore | 2 +- .vscode/settings.json | 4 +++ R/air.R | 48 ++++++++++++++++++++---------------- man/use_air.Rd | 15 ++++++----- tests/testthat/_snaps/air.md | 6 ++++- tests/testthat/test-air.R | 22 +++++++++++++---- 6 files changed, 63 insertions(+), 34 deletions(-) diff --git a/.Rbuildignore b/.Rbuildignore index ee7ab9907..1a6446795 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -24,5 +24,5 @@ ^internal$ ^revdep-cloud$ ^CRAN-SUBMISSION$ -^[\.]?air\.toml$ ^\.vscode$ +^[.]?air[.]toml$ diff --git a/.vscode/settings.json b/.vscode/settings.json index f2d0b79d6..a9f69fe41 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,9 @@ "[r]": { "editor.formatOnSave": true, "editor.defaultFormatter": "Posit.air-vscode" + }, + "[quarto]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "quarto.quarto" } } diff --git a/R/air.R b/R/air.R index aad1e2247..a38088dda 100644 --- a/R/air.R +++ b/R/air.R @@ -13,11 +13,12 @@ #' used by the Air extension installed through either VS Code or Positron, see #' the Installation section for more details. Specifically it: #' -#' - Sets `editor.formatOnSave = true` for R files to enable formatting on -#' every save. +#' - Sets `editor.formatOnSave = true` for R and Quarto files to enable +#' formatting on every save. #' #' - Sets `editor.defaultFormatter` to Air for R files to ensure that Air is -#' always selected as the formatter for this project. +#' always selected as the formatter for this project. Likewise, sets the +#' default formatter for Quarto. #' #' - Sets the Air extension as a "recommended" extension for this project, #' which triggers a notification for contributors coming to this project @@ -31,20 +32,21 @@ #' may prefer another editor. #' #' Note that `use_air()` does not actually invoke Air, it just configures your -#' project with the recommended settings. Consult the [editors +#' project with the recommended settings. Consult [Air's editors #' guide](https://posit-dev.github.io/air/editors.html) to learn how to invoke #' Air in your preferred editor. #' #' ## Installation #' -#' Note that this setup does not install an Air binary, so there is an +#' Note that this setup does not install an Air binary, so there may be an #' additional manual step you must take before using Air for the first time: #' #' - For RStudio, follow the [installation #' guide](https://posit-dev.github.io/air/editor-rstudio.html). #' -#' - For Positron, install the [OpenVSX -#' Extension](https://open-vsx.org/extension/posit/air-vscode). +#' - For Positron, the [Air extension](https://open-vsx.org/extension/posit/air-vscode) +#' is installed by default and that already includes the Air binary. A typical +#' Positron user does not need to do anything about installing Air. #' #' - For VS Code, install the [VS Code #' Extension](https://marketplace.visualstudio.com/items?itemName=Posit.air-vscode). @@ -118,7 +120,7 @@ create_air_toml <- function(ignore = FALSE) { air_toml_regex <- function() { # Pre-escaped regex allowing both `air.toml` and `.air.toml` - "^[\\.]?air\\.toml$" + "^[.]?air[.]toml$" } create_vscode_json_file <- function(name) { @@ -131,7 +133,7 @@ create_vscode_json_file <- function(name) { ui_bullets(c("v" = "Creating {.path {pth(path)}}.")) } - # Tools like jsonlite fail to read empty json files, + # Tools like jsonlite fails to read empty json files, # so if we've just created it, write in `{}`. The easiest # way to do that is to write an empty named list. if (is_file_empty(path)) { @@ -143,20 +145,24 @@ create_vscode_json_file <- function(name) { write_air_vscode_settings_json <- function(path) { settings <- jsonlite::read_json(path) - settings_r <- settings[["[r]"]] - - if (is.null(settings_r)) { - # Mock it - settings_r <- set_names(list()) + language_formatters <- list( + `[r]` = "Posit.air-vscode", + `[quarto]` = "quarto.quarto" + ) + + for (id in names(language_formatters)) { + language_settings <- settings[[id]] %||% set_names(list()) + + # Set these regardless of their previous values. Assume that calling + # `use_air()` is an explicit request to opt in to these settings. + # Version control should also give folks the ability to see the proposed + # changes and tweak to their liking. + language_settings[["editor.formatOnSave"]] <- TRUE + language_settings[["editor.defaultFormatter"]] <- language_formatters[[id]] + + settings[[id]] <- language_settings } - # Set these regardless of their previous values. Assume that calling - # `use_air()` is an explicit request to opt in to these settings. - settings_r[["editor.formatOnSave"]] <- TRUE - settings_r[["editor.defaultFormatter"]] <- "Posit.air-vscode" - - settings[["[r]"]] <- settings_r - write_vscode_json(x = settings, path = path) } diff --git a/man/use_air.Rd b/man/use_air.Rd index 14667c73f..b3774cc90 100644 --- a/man/use_air.Rd +++ b/man/use_air.Rd @@ -26,10 +26,11 @@ R package, \code{.Rbuildignore} is updated to ignore this file. used by the Air extension installed through either VS Code or Positron, see the Installation section for more details. Specifically it: \itemize{ -\item Sets \code{editor.formatOnSave = true} for R files to enable formatting on -every save. +\item Sets \code{editor.formatOnSave = true} for R and Quarto files to enable +formatting on every save. \item Sets \code{editor.defaultFormatter} to Air for R files to ensure that Air is -always selected as the formatter for this project. +always selected as the formatter for this project. Likewise, sets the +default formatter for Quarto. \item Sets the Air extension as a "recommended" extension for this project, which triggers a notification for contributors coming to this project that don't yet have the Air extension installed. @@ -43,15 +44,17 @@ may prefer another editor. } Note that \code{use_air()} does not actually invoke Air, it just configures your -project with the recommended settings. Consult the \href{https://posit-dev.github.io/air/editors.html}{editors guide} to learn how to invoke +project with the recommended settings. Consult \href{https://posit-dev.github.io/air/editors.html}{Air's editors guide} to learn how to invoke Air in your preferred editor. \subsection{Installation}{ -Note that this setup does not install an Air binary, so there is an +Note that this setup does not install an Air binary, so there may be an additional manual step you must take before using Air for the first time: \itemize{ \item For RStudio, follow the \href{https://posit-dev.github.io/air/editor-rstudio.html}{installation guide}. -\item For Positron, install the \href{https://open-vsx.org/extension/posit/air-vscode}{OpenVSX Extension}. +\item For Positron, the \href{https://open-vsx.org/extension/posit/air-vscode}{Air extension} +is installed by default and that already includes the Air binary. A typical +Positron user does not need to do anything about installing Air. \item For VS Code, install the \href{https://marketplace.visualstudio.com/items?itemName=Posit.air-vscode}{VS Code Extension}. \item For other editors, check to \href{https://posit-dev.github.io/air/editors.html}{see if that editor is supported} by Air. } diff --git a/tests/testthat/_snaps/air.md b/tests/testthat/_snaps/air.md index 0d0cb2b24..460f506d9 100644 --- a/tests/testthat/_snaps/air.md +++ b/tests/testthat/_snaps/air.md @@ -4,7 +4,7 @@ use_air() Message v Creating 'air.toml'. - v Adding "^[\\.]?air\\.toml$" to '.Rbuildignore'. + v Adding "^[.]?air[.]toml$" to '.Rbuildignore'. v Creating '.vscode/'. v Adding "^\\.vscode$" to '.Rbuildignore'. v Creating '.vscode/settings.json'. @@ -21,6 +21,10 @@ "[r]": { "editor.formatOnSave": true, "editor.defaultFormatter": "Posit.air-vscode" + }, + "[quarto]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "quarto.quarto" } } diff --git a/tests/testthat/test-air.R b/tests/testthat/test-air.R index df476c214..567392447 100644 --- a/tests/testthat/test-air.R +++ b/tests/testthat/test-air.R @@ -17,6 +17,11 @@ test_that("creates correct default package files", { settings[["[r]"]][["editor.defaultFormatter"]], "Posit.air-vscode" ) + expect_true(settings[["[quarto]"]][["editor.formatOnSave"]]) + expect_identical( + settings[["[quarto]"]][["editor.defaultFormatter"]], + "quarto.quarto" + ) settings <- jsonlite::read_json(proj_path(".vscode", "extensions.json")) recommendations <- settings[["recommendations"]] @@ -48,6 +53,11 @@ test_that("creates correct default project files", { settings[["[r]"]][["editor.defaultFormatter"]], "Posit.air-vscode" ) + expect_true(settings[["[quarto]"]][["editor.formatOnSave"]]) + expect_identical( + settings[["[quarto]"]][["editor.defaultFormatter"]], + "quarto.quarto" + ) settings <- jsonlite::read_json(proj_path(".vscode", "extensions.json")) recommendations <- settings[["recommendations"]] @@ -68,6 +78,9 @@ test_that("respects existing `settings.json`, but overwrites settings we own", { ), "[rust]" = list( "editor.formatOnSave" = FALSE + ), + "[quarto]" = list( + "editor.wordWrap" = "wordWrapColumn" ) ) @@ -76,11 +89,10 @@ test_that("respects existing `settings.json`, but overwrites settings we own", { use_air() # Here is all that should change - settings[["[r]"]] <- list( - # Overwrite both of these to Air's recommendations - "editor.formatOnSave" = TRUE, - "editor.defaultFormatter" = "Posit.air-vscode" - ) + settings[["[r]"]][["editor.formatOnSave"]] <- TRUE + settings[["[r]"]][["editor.defaultFormatter"]] <- "Posit.air-vscode" + settings[["[quarto]"]][["editor.formatOnSave"]] <- TRUE + settings[["[quarto]"]][["editor.defaultFormatter"]] <- "quarto.quarto" actual_settings <- jsonlite::read_json(path) From 96c34708f1a5d55a1d03984856125985478301a7 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 9 Jul 2025 14:30:49 -0700 Subject: [PATCH 2/4] Use modifyList() --- R/air.R | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/R/air.R b/R/air.R index a38088dda..e39e14d15 100644 --- a/R/air.R +++ b/R/air.R @@ -145,23 +145,18 @@ create_vscode_json_file <- function(name) { write_air_vscode_settings_json <- function(path) { settings <- jsonlite::read_json(path) - language_formatters <- list( - `[r]` = "Posit.air-vscode", - `[quarto]` = "quarto.quarto" - ) - - for (id in names(language_formatters)) { - language_settings <- settings[[id]] %||% set_names(list()) - - # Set these regardless of their previous values. Assume that calling - # `use_air()` is an explicit request to opt in to these settings. - # Version control should also give folks the ability to see the proposed - # changes and tweak to their liking. - language_settings[["editor.formatOnSave"]] <- TRUE - language_settings[["editor.defaultFormatter"]] <- language_formatters[[id]] - settings[[id]] <- language_settings - } + patch <- list( + `[r]` = list( + "editor.formatOnSave" = TRUE, + "editor.defaultFormatter" = "Posit.air-vscode" + ), + `[quarto]` = list( + "editor.formatOnSave" = TRUE, + "editor.defaultFormatter" = "quarto.quarto" + ) + ) + settings <- utils::modifyList(settings, patch) write_vscode_json(x = settings, path = path) } From 62098c416dafccd87cfecc8eafbdd7ab78c2dd34 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 9 Jul 2025 14:38:45 -0700 Subject: [PATCH 3/4] Be a bit more robust --- R/air.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/air.R b/R/air.R index e39e14d15..60c119517 100644 --- a/R/air.R +++ b/R/air.R @@ -133,7 +133,7 @@ create_vscode_json_file <- function(name) { ui_bullets(c("v" = "Creating {.path {pth(path)}}.")) } - # Tools like jsonlite fails to read empty json files, + # Tools like jsonlite fail to read empty json files, # so if we've just created it, write in `{}`. The easiest # way to do that is to write an empty named list. if (is_file_empty(path)) { @@ -144,7 +144,7 @@ create_vscode_json_file <- function(name) { } write_air_vscode_settings_json <- function(path) { - settings <- jsonlite::read_json(path) + settings <- jsonlite::read_json(path) %||% set_names(list()) patch <- list( `[r]` = list( From b5b2b472c2f15172c2b4715a60bb586634fcbc6b Mon Sep 17 00:00:00 2001 From: "Jennifer (Jenny) Bryan" Date: Thu, 10 Jul 2025 09:31:01 -0700 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Davis Vaughan --- R/air.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/air.R b/R/air.R index 60c119517..2555eae04 100644 --- a/R/air.R +++ b/R/air.R @@ -46,7 +46,7 @@ #' #' - For Positron, the [Air extension](https://open-vsx.org/extension/posit/air-vscode) #' is installed by default and that already includes the Air binary. A typical -#' Positron user does not need to do anything about installing Air. +#' Positron user does not need to do anything to install Air. #' #' - For VS Code, install the [VS Code #' Extension](https://marketplace.visualstudio.com/items?itemName=Posit.air-vscode).