From c13798457988fee0247b0bbc71a67a7d31c5547a Mon Sep 17 00:00:00 2001
From: atusy <30277794+atusy@users.noreply.github.com>
Date: Wed, 3 Apr 2024 00:49:05 +0900
Subject: [PATCH 01/11] feat(fuzzyhelp): prefer startDynamicHelp to Rd2HTML
---
R/fuzzyhelp.R | 64 +++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 55 insertions(+), 9 deletions(-)
diff --git a/R/fuzzyhelp.R b/R/fuzzyhelp.R
index 6a4de2a..0bf3f27 100644
--- a/R/fuzzyhelp.R
+++ b/R/fuzzyhelp.R
@@ -16,7 +16,13 @@ get_content <- function(x, i) {
topic <- x$Topic[i]
package <- x$Package[i]
if (type == "help") {
- return(get_help(topic, package))
+ p <- startDynamicHelp()
+ if (is.null(p)) {
+ return(get_help(topic, package))
+ }
+ h <- help((topic), (package), help_type = "html")
+ u <- sprintf("http://127.0.0.1:%d/library/%s/html/%s.html", p, package, basename(h))
+ return(u)
}
if (type == "vignette") {
return(get_vignette(topic, package))
@@ -318,12 +324,14 @@ create_server <- function(method = c("fzf", "lv")) {
reactiveToc() # avoids noisy refresh
reactable::getReactableState("tocViewer", "selected")
})
- reactiveHelp <- shiny::reactive(
- htmltools::tags$iframe(
- srcdoc = get_content(reactiveToc(), reactiveSelection()),
- style = "width: 100%; height: 100%;",
- id = "helpViewer",
- onload = "(function(){
+ reactiveHelp <- shiny::reactive({
+ arguments <- list(style = "width: 100%; height: 100%;", id = "helpViewer")
+ content <- get_content(reactiveToc(), reactiveSelection())
+ if (grepl("^http://", content)) {
+ arguments$src <- content
+ } else {
+ arguments$srcdoc <- content
+ arguments$onload <- "(function(){
// replace anchors to avoid nesting shiny widgets
const pattern = document.baseURI + '#';
const iframe = document.querySelector('#helpViewer iframe');
@@ -338,8 +346,9 @@ create_server <- function(method = c("fzf", "lv")) {
}
});
})();"
- )
- )
+ }
+ do.call(htmltools::tags$iframe, arguments)
+ })
output$tocViewer <- reactable::renderReactable(reactiveTocViewer())
output$helpViewer <- shiny::renderUI(reactiveHelp())
@@ -364,6 +373,43 @@ create_server <- function(method = c("fzf", "lv")) {
.env <- new.env()
+startDynamicHelp <- function() {
+ if (
+ !is.null(.env$helpProcess) &&
+ is.null(.env$helpProcess$get_exit_status()) &&
+ is.integer(.env$helpPort)
+ ) {
+ return(.env$helpPort)
+ }
+
+ tf <- tempfile()
+ writeLines("", tf)
+
+ .env$helpProcess <- callr::r_bg(function(output) {
+ port <- tools::startDynamicHelp(NA)
+ writeLines(as.character(port), output)
+
+ # keep the process running
+ while (TRUE) {
+ Sys.sleep(60 * 60 * 24) # without sleep, dynamic help surver stop responding
+ }
+ }, list(output = tf))
+
+ # Wait up to 1 second until the server is ready
+ for (. in seq(10)) {
+ port <- readLines(tf)
+ if (port != "") {
+ port <- as.integer(port)
+ .env$helpPort <- port
+ return(port)
+ }
+ Sys.sleep(0.1)
+ }
+
+ # If server is unavailable, return NULL and fallback to using Rd2HTML
+ return(NULL)
+}
+
#' Fuzzily Search Help and View the Selection
#'
#' Users no more have to afraid of exact name of the object they need help.
From 769ef19dbdbdda7a62e2366515bb59a5f8bb9fff Mon Sep 17 00:00:00 2001
From: atusy <30277794+atusy@users.noreply.github.com>
Date: Wed, 3 Apr 2024 08:45:11 +0900
Subject: [PATCH 02/11] fix(fuzzyhelp): hide some features unavailable in
background mode
---
R/fuzzyhelp.R | 62 ++++++++++++++++++++++++++++++---------------------
1 file changed, 37 insertions(+), 25 deletions(-)
diff --git a/R/fuzzyhelp.R b/R/fuzzyhelp.R
index 0bf3f27..f81223a 100644
--- a/R/fuzzyhelp.R
+++ b/R/fuzzyhelp.R
@@ -4,7 +4,7 @@ NULL
#' Get preview content for Shiny UI
#' @noRd
-get_content <- function(x, i) {
+get_content <- function(x, i, background) {
if (NROW(x) == 0L || length(i) == 0L) {
return("")
}
@@ -28,7 +28,11 @@ get_content <- function(x, i) {
return(get_vignette(topic, package))
}
if (type == "demo") {
- return('Press "Done" to see demo.')
+ return(if (background) {
+ sprintf('Call demo("%s")
to see demo', topic)
+ } else {
+ 'Press "Done" to see demo.'
+ })
}
paste("Viewer not available for the type:", type)
}
@@ -242,9 +246,13 @@ search_toc <- function(df, queries, ...) {
dplyr::select(!"SCORE")
}
-create_ui <- function(query = "") {
+create_ui <- function(query = "", background = FALSE) {
miniUI::miniPage(
- miniUI::gadgetTitleBar("Fuzzy Help Search"),
+ if (background) {
+ miniUI::gadgetTitleBar("Fuzzy Help Search", NULL, NULL)
+ } else {
+ miniUI::gadgetTitleBar("Fuzzy Help Search")
+ },
miniUI::miniContentPanel(
shiny::textInput(
"query",
@@ -294,7 +302,7 @@ parse_query <- function(string) {
queries[queries != ""]
}
-create_server <- function(method = c("fzf", "lv")) {
+create_server <- function(method = c("fzf", "lv"), background = FALSE) {
method <- match.arg(method)
function(input, output) {
toc <- create_toc()
@@ -326,7 +334,7 @@ create_server <- function(method = c("fzf", "lv")) {
})
reactiveHelp <- shiny::reactive({
arguments <- list(style = "width: 100%; height: 100%;", id = "helpViewer")
- content <- get_content(reactiveToc(), reactiveSelection())
+ content <- get_content(reactiveToc(), reactiveSelection(), background)
if (grepl("^http://", content)) {
arguments$src <- content
} else {
@@ -353,21 +361,23 @@ create_server <- function(method = c("fzf", "lv")) {
output$tocViewer <- reactable::renderReactable(reactiveTocViewer())
output$helpViewer <- shiny::renderUI(reactiveHelp())
- shiny::observeEvent(input$done, {
- shiny::stopApp()
- selection <- reactiveToc()[reactiveSelection(), ]
- type <- selection$Type[1L]
- topic <- selection$Topic[1L]
- package <- selection$Package[1L]
- if (rstudioapi::isAvailable()) {
- rstudioapi::sendToConsole(
- sprintf('%s("%s", package = "%s")', type, topic, package),
- execute = TRUE
- )
- } else {
- getNamespace("utils")[[type]]((topic), (package))
- }
- })
+ if (!background) {
+ shiny::observeEvent(input$done, {
+ shiny::stopApp()
+ selection <- reactiveToc()[reactiveSelection(), ]
+ type <- selection$Type[1L]
+ topic <- selection$Topic[1L]
+ package <- selection$Package[1L]
+ if (rstudioapi::isAvailable()) {
+ rstudioapi::sendToConsole(
+ sprintf('%s("%s", package = "%s")', type, topic, package),
+ execute = TRUE
+ )
+ } else {
+ getNamespace("utils")[[type]]((topic), (package))
+ }
+ })
+ }
}
}
@@ -412,11 +422,13 @@ startDynamicHelp <- function() {
#' Fuzzily Search Help and View the Selection
#'
-#' Users no more have to afraid of exact name of the object they need help.
+#' Users no longer have to remember the exact name to find help, vignettes,
+#' and demo.
#' A shiny gadget helps you to find a topic fuzzily.
#' Click radio buttons to switch preview contents.
#' Click "Done" or "Cancel" to close the widget.
-#' The "Done" button will also hook `help` function on the selection.
+#' When `background = FALSE`, the "Done" button will also hook `help`,
+#' `vignette`, or `demo`, accordingly.
#'
#' @param query An initial query to search for the help system.
#' @param method A fuzzy match method to use. Choices are "fzf" and "lv"
@@ -448,8 +460,8 @@ fuzzyhelp <- function(
method = getOption("fuzzyhelp.method", "fzf"),
background = getOption("fuzzyhelp.background", TRUE),
viewer = shiny::paneViewer()) {
- app <- create_ui(query)
- server <- create_server(method)
+ app <- create_ui(query, background)
+ server <- create_server(method, background)
# Create new gadget on foreground
if (!background) {
From a7f8f8b7d2a58e2d95050c8641c55cd1f4eb34e0 Mon Sep 17 00:00:00 2001
From: atusy <30277794+atusy@users.noreply.github.com>
Date: Wed, 3 Apr 2024 08:49:21 +0900
Subject: [PATCH 03/11] refactor(fuzzyhelp): order of if statements
---
R/fuzzyhelp.R | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/R/fuzzyhelp.R b/R/fuzzyhelp.R
index f81223a..ef770b2 100644
--- a/R/fuzzyhelp.R
+++ b/R/fuzzyhelp.R
@@ -15,6 +15,13 @@ get_content <- function(x, i, background) {
type <- x$Type[i]
topic <- x$Topic[i]
package <- x$Package[i]
+ if (type == "demo") {
+ return(if (background) {
+ sprintf('Call demo("%s")
to see demo', topic)
+ } else {
+ 'Press "Done" to see demo.'
+ })
+ }
if (type == "help") {
p <- startDynamicHelp()
if (is.null(p)) {
@@ -27,13 +34,6 @@ get_content <- function(x, i, background) {
if (type == "vignette") {
return(get_vignette(topic, package))
}
- if (type == "demo") {
- return(if (background) {
- sprintf('Call demo("%s")
to see demo', topic)
- } else {
- 'Press "Done" to see demo.'
- })
- }
paste("Viewer not available for the type:", type)
}
From 7a0ed72b042adf237816c18be8e733038dd42e87 Mon Sep 17 00:00:00 2001
From: atusy <30277794+atusy@users.noreply.github.com>
Date: Wed, 3 Apr 2024 09:01:14 +0900
Subject: [PATCH 04/11] fix(fuzzyhelp): suggesting demo() should include
package argument
---
R/fuzzyhelp.R | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/R/fuzzyhelp.R b/R/fuzzyhelp.R
index ef770b2..cd6b350 100644
--- a/R/fuzzyhelp.R
+++ b/R/fuzzyhelp.R
@@ -17,7 +17,7 @@ get_content <- function(x, i, background) {
package <- x$Package[i]
if (type == "demo") {
return(if (background) {
- sprintf('Call demo("%s")
to see demo', topic)
+ sprintf('Call demo("%s", "%s")
to see demo', topic, package)
} else {
'Press "Done" to see demo.'
})
From a95f1027fbf6398ebc112f58b115f90ef0d53104 Mon Sep 17 00:00:00 2001
From: atusy <30277794+atusy@users.noreply.github.com>
Date: Wed, 3 Apr 2024 09:21:15 +0900
Subject: [PATCH 05/11] feat(fuzzyhelp): also use native help server for
vignette
---
R/fuzzyhelp.R | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/R/fuzzyhelp.R b/R/fuzzyhelp.R
index cd6b350..3a55090 100644
--- a/R/fuzzyhelp.R
+++ b/R/fuzzyhelp.R
@@ -22,18 +22,26 @@ get_content <- function(x, i, background) {
'Press "Done" to see demo.'
})
}
+
+ helpPort <- startDynamicHelp()
+ helpUrl <- "http://127.0.0.1:%d/library/%s/%s/%s%s"
+
if (type == "help") {
- p <- startDynamicHelp()
- if (is.null(p)) {
+ if (is.null(helpPort)) {
return(get_help(topic, package))
}
h <- help((topic), (package), help_type = "html")
- u <- sprintf("http://127.0.0.1:%d/library/%s/html/%s.html", p, package, basename(h))
- return(u)
+ return(sprintf(helpUrl, helpPort, package, "html", basename(h), ".html"))
}
+
if (type == "vignette") {
- return(get_vignette(topic, package))
+ if (is.null(helpPort)) {
+ return(get_vignette(topic, package))
+ }
+ v <- vignette(topic, package)
+ return(sprintf(helpUrl, helpPort, basename(v$Dir), "doc", v$PDF, ""))
}
+
paste("Viewer not available for the type:", type)
}
From 3adc717f7dc18a67a69ad00871a3f283a8a6c4b1 Mon Sep 17 00:00:00 2001
From: atusy <30277794+atusy@users.noreply.github.com>
Date: Wed, 3 Apr 2024 09:39:22 +0900
Subject: [PATCH 06/11] docs(NEWS): fuzzyhelp changes
---
NEWS.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/NEWS.md b/NEWS.md
index 1ccdc69..9a61bc9 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,8 @@
+# felp 0.5.0
+
+- On`fuzzyhelp()`, help/vignette contents gain syntax highlights and links. It formerly used `Rd2HTML()` to generate HTML contents. Now the contents inherit from a help server with `startDynamicHelp()`, which means they are exactly same as the contents of `help()` or `vignette()`.
+- On `fuzzyhelp(background = FALSE)`, "Done" and "Cancel" buttons are removed because the feature of the "Done" requires the Shiny app run on the main thread.
+
# felp 0.4.0
- Support `fuzzyhelp` to run Shiny App in background without blocking user terminal.
From 5330662e0fd88aac63b2794108a25cc64dfb49ef Mon Sep 17 00:00:00 2001
From: atusy <30277794+atusy@users.noreply.github.com>
Date: Wed, 3 Apr 2024 09:39:45 +0900
Subject: [PATCH 07/11] chore(docs): devtools::document()
---
man/fuzzyhelp.Rd | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/man/fuzzyhelp.Rd b/man/fuzzyhelp.Rd
index 0f21c8d..21857aa 100644
--- a/man/fuzzyhelp.Rd
+++ b/man/fuzzyhelp.Rd
@@ -31,11 +31,13 @@ If the \code{background} argument is \code{TRUE}, then the return value inherits
\code{callr::r_bg()}. Otherwise, \code{NULL} is returned.
}
\description{
-Users no more have to afraid of exact name of the object they need help.
+Users no longer have to remember the exact name to find help, vignettes,
+and demo.
A shiny gadget helps you to find a topic fuzzily.
Click radio buttons to switch preview contents.
Click "Done" or "Cancel" to close the widget.
-The "Done" button will also hook \code{help} function on the selection.
+When \code{background = FALSE}, the "Done" button will also hook \code{help},
+\code{vignette}, or \code{demo}, accordingly.
}
\note{
The default fuzzy match algorithm is a simplified version of
From 00773e4bd747cd920c51f68ab961c3f596e2447f Mon Sep 17 00:00:00 2001
From: atusy <30277794+atusy@users.noreply.github.com>
Date: Wed, 3 Apr 2024 09:40:38 +0900
Subject: [PATCH 08/11] chore(docs): pkgdown::build_site()
---
docs/articles/felp.html | 2 +-
docs/news/index.html | 7 ++++++-
docs/pkgdown.yml | 2 +-
docs/reference/fuzzyhelp.html | 12 ++++++++----
4 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/docs/articles/felp.html b/docs/articles/felp.html
index 91b15ab..c4ef0e0 100644
--- a/docs/articles/felp.html
+++ b/docs/articles/felp.html
@@ -323,7 +323,7 @@
fuzzyhelp()
, help/vignette contents gain syntax highlights and links. It formerly used Rd2HTML()
to generate HTML contents. Now the contents inherit from a help server with startDynamicHelp()
, which means they are exactly same as the contents of help()
or vignette()
.fuzzyhelp(background = FALSE)
, “Done” and “Cancel” buttons are removed because the feature of the “Done” requires the Shiny app run on the main thread.fuzzyhelp
to run Shiny App in background without blocking user terminal. The new behavior is enabled by default and can be disabled by passing FALSE
to the background
argument or to the fuzzyhelp.background
option (#18, #20).fuzzyhelp
function. A click on a anchor should not cause nesting of the UI when href of the anchor is an ID. Instead, the click should scroll the window to show the element with the corresponding ID (#17).Users no more have to afraid of exact name of the object they need help. +
Users no longer have to remember the exact name to find help, vignettes,
+and demo.
A shiny gadget helps you to find a topic fuzzily.
Click radio buttons to switch preview contents.
Click "Done" or "Cancel" to close the widget.
-The "Done" button will also hook help
function on the selection.
background = FALSE
, the "Done" button will also hook help
,
+vignette
, or demo
, accordingly.
demo("%s", "%s")
to see demo', topic, package)
- } else {
- 'Press "Done" to see demo.'
- })
- }
-
helpPort <- startDynamicHelp()
- helpUrl <- "http://127.0.0.1:%d/library/%s/%s/%s%s"
+ helpUrl <- "http://127.0.0.1:%d/%s/%s/%s/%s%s"
if (type == "help") {
if (is.null(helpPort)) {
return(get_help(topic, package))
}
h <- help((topic), (package), help_type = "html")
- return(sprintf(helpUrl, helpPort, package, "html", basename(h), ".html"))
+ return(sprintf(helpUrl, helpPort, "library", package, "html", basename(h), ".html"))
}
if (type == "vignette") {
@@ -39,7 +31,14 @@ get_content <- function(x, i, background) {
return(get_vignette(topic, package))
}
v <- vignette(topic, package)
- return(sprintf(helpUrl, helpPort, basename(v$Dir), "doc", v$PDF, ""))
+ return(sprintf(helpUrl, helpPort, "library", basename(v$Dir), "doc", v$PDF, ""))
+ }
+
+ if (type == "demo") {
+ if (is.null(helpPort)) {
+ return(sprintf('Call demo("%s", "%s")
to see demo', topic, package))
+ }
+ return(sprintf(helpUrl, helpPort, "library", package, "Demo", topic, ""))
}
paste("Viewer not available for the type:", type)
@@ -342,7 +341,7 @@ create_server <- function(method = c("fzf", "lv"), background = FALSE) {
})
reactiveHelp <- shiny::reactive({
arguments <- list(style = "width: 100%; height: 100%;", id = "helpViewer")
- content <- get_content(reactiveToc(), reactiveSelection(), background)
+ content <- get_content(reactiveToc(), reactiveSelection())
if (grepl("^http://", content)) {
arguments$src <- content
} else {
From c8e2baf259db29f94ad6859970dc2003f44dab6a Mon Sep 17 00:00:00 2001
From: atusy <30277794+atusy@users.noreply.github.com>
Date: Fri, 5 Apr 2024 23:10:24 +0900
Subject: [PATCH 10/11] docs(NEWS): fuzzyhelp() support previewing demo
---
NEWS.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/NEWS.md b/NEWS.md
index 9a61bc9..881a970 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,7 @@
# felp 0.5.0
- On`fuzzyhelp()`, help/vignette contents gain syntax highlights and links. It formerly used `Rd2HTML()` to generate HTML contents. Now the contents inherit from a help server with `startDynamicHelp()`, which means they are exactly same as the contents of `help()` or `vignette()`.
+- On `fuzzyhelp()`, demo contents can be previewed.
- On `fuzzyhelp(background = FALSE)`, "Done" and "Cancel" buttons are removed because the feature of the "Done" requires the Shiny app run on the main thread.
# felp 0.4.0
From 2609f18686d9a80882567f13b717842183c94d1d Mon Sep 17 00:00:00 2001
From: atusy <30277794+atusy@users.noreply.github.com>
Date: Fri, 5 Apr 2024 23:13:50 +0900
Subject: [PATCH 11/11] chore(pkgdown): build
---
docs/articles/felp.html | 2 +-
docs/news/index.html | 1 +
docs/pkgdown.yml | 2 +-
3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/articles/felp.html b/docs/articles/felp.html
index c4ef0e0..2a299f9 100644
--- a/docs/articles/felp.html
+++ b/docs/articles/felp.html
@@ -323,7 +323,7 @@ fuzzyhelp()
, help/vignette contents gain syntax highlights and links. It formerly used Rd2HTML()
to generate HTML contents. Now the contents inherit from a help server with startDynamicHelp()
, which means they are exactly same as the contents of help()
or vignette()
.fuzzyhelp()
, demo contents can be previewed.fuzzyhelp(background = FALSE)
, “Done” and “Cancel” buttons are removed because the feature of the “Done” requires the Shiny app run on the main thread.