From aeb17f7f56fa32a73caff3abb2c41796029f64cf Mon Sep 17 00:00:00 2001 From: Brice Stacey Date: Wed, 22 Apr 2026 15:40:33 -0400 Subject: [PATCH 1/2] Return description and author for installed packages Extend .ps.rpc.pkg_list to return `description` and `author` alongside the existing fields so Positron's Packages pane card view can render them. Both are normalized for display: descriptions collapse whitespace so multi-line text fits on one line, and author strips trailing `` markers from Maintainer values. All three methods (pak / base / renv) populate the new fields. base and renv share a helper that wraps utils::installed.packages() so the lib lookup only differs by `lib.loc`. See posit-dev/positron#12925 --- .../ark/src/modules/positron/packages_pane.R | 71 +++++++++++++------ 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/crates/ark/src/modules/positron/packages_pane.R b/crates/ark/src/modules/positron/packages_pane.R index 38dd3ce48..2deef3173 100644 --- a/crates/ark/src/modules/positron/packages_pane.R +++ b/crates/ark/src/modules/positron/packages_pane.R @@ -8,6 +8,49 @@ # This file contains RPC functions for the packages pane. # These functions are called via callMethod from the Positron R extension. +# Normalize a DESCRIPTION field value for display: collapse whitespace/newlines +# into single spaces so a multi-line Description fits on one line in the UI. +.ps.pkg_description_text <- function(value) { + if (is.null(value) || is.na(value)) { + return("") + } + value <- as.character(value) + value <- gsub("\\s+", " ", value, perl = TRUE) + trimws(value) +} + +# Normalize a Maintainer/Author value for display: collapse whitespace and strip +# trailing email addresses in angle brackets (e.g., "Hadley Wickham " +# becomes "Hadley Wickham"). +.ps.pkg_author_text <- function(value) { + if (is.null(value) || is.na(value)) { + return("") + } + value <- as.character(value) + value <- gsub("\\s+", " ", value, perl = TRUE) + value <- gsub("\\s*<[^>]+>", "", value, perl = TRUE) + trimws(value) +} + +# Build the package list via utils::installed.packages(), optionally scoped to a +# specific library path (used by the renv method). +.ps.pkg_list_installed <- function(lib.loc = NULL) { + ip <- utils::installed.packages( + lib.loc = lib.loc, + fields = c("Description", "Maintainer") + ) + lapply(seq_len(nrow(ip)), function(i) { + list( + id = paste0(ip[i, "Package"], "-", ip[i, "Version"]), + name = ip[i, "Package"], + displayName = ip[i, "Package"], + version = ip[i, "Version"], + description = .ps.pkg_description_text(ip[i, "Description"]), + author = .ps.pkg_author_text(ip[i, "Maintainer"]) + ) + }) +} + # Return a list of installed packages #' @export .ps.rpc.pkg_list <- function(method = c("pak", "base", "renv")) { @@ -23,32 +66,14 @@ id = paste0(pkgs$package[[i]], "-", pkgs$version[[i]]), name = pkgs$package[[i]], displayName = pkgs$package[[i]], - version = as.character(pkgs$version[[i]]) + version = as.character(pkgs$version[[i]]), + description = .ps.pkg_description_text(pkgs$description[[i]]), + author = .ps.pkg_author_text(pkgs$maintainer[[i]]) ) }) }, - base = { - ip <- utils::installed.packages() - lapply(seq_len(nrow(ip)), function(i) { - list( - id = paste0(ip[i, "Package"], "-", ip[i, "Version"]), - name = ip[i, "Package"], - displayName = ip[i, "Package"], - version = ip[i, "Version"] - ) - }) - }, - renv = { - ip <- utils::installed.packages(lib.loc = renv::paths$library()) - lapply(seq_len(nrow(ip)), function(i) { - list( - id = paste0(ip[i, "Package"], "-", ip[i, "Version"]), - name = ip[i, "Package"], - displayName = ip[i, "Package"], - version = ip[i, "Version"] - ) - }) - } + base = .ps.pkg_list_installed(), + renv = .ps.pkg_list_installed(lib.loc = renv::paths$library()) ) } From cd9f03e5821d0ad71b99179e93231d3b3a8a8b03 Mon Sep 17 00:00:00 2001 From: Brice Stacey Date: Mon, 27 Apr 2026 11:16:40 -0400 Subject: [PATCH 2/2] Vectorize installed package listing Address review feedback: collapse pak/base/renv into a single .ps.pkg_list_installed() that always uses utils::installed.packages(), scoped by lib.loc for the renv method. The pak::lib_status() branch went away since pak's value is in install/update, not listing. Replace the per-row lapply with vectorized whitespace/email scrubbing and a single Map() to assemble the per-package lists. --- .../ark/src/modules/positron/packages_pane.R | 95 ++++++++----------- 1 file changed, 38 insertions(+), 57 deletions(-) diff --git a/crates/ark/src/modules/positron/packages_pane.R b/crates/ark/src/modules/positron/packages_pane.R index 2deef3173..f8c4ede9a 100644 --- a/crates/ark/src/modules/positron/packages_pane.R +++ b/crates/ark/src/modules/positron/packages_pane.R @@ -8,73 +8,54 @@ # This file contains RPC functions for the packages pane. # These functions are called via callMethod from the Positron R extension. -# Normalize a DESCRIPTION field value for display: collapse whitespace/newlines -# into single spaces so a multi-line Description fits on one line in the UI. -.ps.pkg_description_text <- function(value) { - if (is.null(value) || is.na(value)) { - return("") - } - value <- as.character(value) - value <- gsub("\\s+", " ", value, perl = TRUE) - trimws(value) -} - -# Normalize a Maintainer/Author value for display: collapse whitespace and strip -# trailing email addresses in angle brackets (e.g., "Hadley Wickham " -# becomes "Hadley Wickham"). -.ps.pkg_author_text <- function(value) { - if (is.null(value) || is.na(value)) { - return("") - } - value <- as.character(value) - value <- gsub("\\s+", " ", value, perl = TRUE) - value <- gsub("\\s*<[^>]+>", "", value, perl = TRUE) - trimws(value) -} - -# Build the package list via utils::installed.packages(), optionally scoped to a -# specific library path (used by the renv method). .ps.pkg_list_installed <- function(lib.loc = NULL) { ip <- utils::installed.packages( lib.loc = lib.loc, fields = c("Description", "Maintainer") ) - lapply(seq_len(nrow(ip)), function(i) { - list( - id = paste0(ip[i, "Package"], "-", ip[i, "Version"]), - name = ip[i, "Package"], - displayName = ip[i, "Package"], - version = ip[i, "Version"], - description = .ps.pkg_description_text(ip[i, "Description"]), - author = .ps.pkg_author_text(ip[i, "Maintainer"]) - ) - }) + + name <- ip[, "Package"] + version <- ip[, "Version"] + id <- paste0(name, "-", version) + # Collapse whitespace so a multi-line Description fits on one line in the UI. + description <- trimws(gsub( + "\\s+", + " ", + ifelse(is.na(ip[, "Description"]), "", ip[, "Description"]), + perl = TRUE + )) + # Strip "" markers so "Hadley Wickham " becomes "Hadley Wickham". + author <- trimws(gsub( + "\\s*<[^>]+>", + "", + gsub( + "\\s+", + " ", + ifelse(is.na(ip[, "Maintainer"]), "", ip[, "Maintainer"]), + perl = TRUE + ), + perl = TRUE + )) + + unname(Map( + list, + id = id, + name = name, + displayName = name, + version = version, + description = description, + author = author + )) } -# Return a list of installed packages +# Return a list of installed packages. The pak/base/renv methods exist for +# parity with install/update operations; for listing we always use +# utils::installed.packages(), scoped to the renv library when requested. #' @export .ps.rpc.pkg_list <- function(method = c("pak", "base", "renv")) { method <- match.arg(method) - switch( - method, - pak = { - old_opt <- options(pak.no_extra_messages = TRUE) - on.exit(options(old_opt), add = TRUE) - pkgs <- pak::lib_status() - lapply(seq_len(nrow(pkgs)), function(i) { - list( - id = paste0(pkgs$package[[i]], "-", pkgs$version[[i]]), - name = pkgs$package[[i]], - displayName = pkgs$package[[i]], - version = as.character(pkgs$version[[i]]), - description = .ps.pkg_description_text(pkgs$description[[i]]), - author = .ps.pkg_author_text(pkgs$maintainer[[i]]) - ) - }) - }, - base = .ps.pkg_list_installed(), - renv = .ps.pkg_list_installed(lib.loc = renv::paths$library()) - ) + lib.loc <- if (method == "renv") renv::paths$library() else NULL + .ps.pkg_list_installed(lib.loc = lib.loc) }