From f440712ae6f0546dd8e0354074fb33113cd57318 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Wed, 23 Nov 2022 21:06:51 -0600 Subject: [PATCH] Add labels to the default progress bar and allow users to provide a custom progress bar (#2196) close #1880; close #2035 ; close #2077 --- NEWS.md | 6 ++++++ R/output.R | 7 ++++--- R/parser.R | 1 - R/utils.R | 27 +++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index eae6084748..889b2e7ad5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # CHANGES IN knitr VERSION 1.42 +## NEW FEATURES + +- Users can specify a custom progress bar for `knit()` now. The default is still a text progress bar created from `utils::txtProgressBar()`. To specify a custom progress bar, set `options(knitr.progress.fun = function(total, labels) {})`. This function should take arguments `total` (the total number of chunks) and `labels` (the vector of chunk labels), create a progress bar, and return a list of two methods: `list(update = function(i) {}, done = function() {})`. The `update()` method takes `i` (index of the current chunk) as the input and updates the progress bar. The `done()` method closes the progress bar. See https://yihui.org/knitr/options/#global-r-options for documentation and examples. + +- The default text progress bar is still written to `stdout()` by default, but can also be written to other connections or `stderr()` now. To do so, set `options(knitr.progress.output = )` to a connection or `stderr()`. + ## MAJOR CHANGES - `knit()` no longer prints out chunk options beneath the text progress bar while knitting a document (thanks, @hadley, #1880). diff --git a/R/output.R b/R/output.R index 395bd4449f..7df2bf16a9 100644 --- a/R/output.R +++ b/R/output.R @@ -289,8 +289,9 @@ process_file = function(text, output) { labels = unlist(lapply(groups, function(g) { if (is.list(g$params)) g[[c('params', 'label')]] else '' })) - pb = txtProgressBar(0, n, char = '.', style = 3) - on.exit(close(pb), add = TRUE) + pb_fun = getOption('knitr.progress.fun', txt_pb) + pb = if (is.function(pb_fun)) pb_fun(n, labels) + on.exit(if (!is.null(pb)) pb$done(), add = TRUE) } wd = getwd() for (i in 1:n) { @@ -302,7 +303,7 @@ process_file = function(text, output) { } break # must have called knit_exit(), so exit early } - if (progress) setTxtProgressBar(pb, i) + if (progress && !is.null(pb)) pb$update(i) group = groups[[i]] res[i] = withCallingHandlers( if (tangle) process_tangle(group) else process_group(group), diff --git a/R/parser.R b/R/parser.R index bd6cbfaba8..4ad330e442 100644 --- a/R/parser.R +++ b/R/parser.R @@ -333,7 +333,6 @@ get_option_comment = function(engine) { print.block = function(x, ...) { params = x$params - cat(' chunk:', params$label, '\n') if (opts_knit$get('verbose')) { code = knit_code$get(params$label) if (length(code) && !is_blank(code)) { diff --git a/R/utils.R b/R/utils.R index a8d0047430..70ccd7e185 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1085,3 +1085,30 @@ str_split = function(x, split, ...) { y[x == ''] = list('') y } + +# default progress bar function in knitr: create a text progress bar, and return +# methods to update/close it +txt_pb = function(total, labels) { + s = ifelse(labels == '', '', sprintf(' (%s)', labels)) # chunk labels in () + w = nchar(s) # widths of labels + n = max(w) + # right-pad spaces for same width of all labels so a wider label of the + # progress bar in a previous step could be completely wiped (by spaces) + s = paste0(s, strrep(' ', n - w)) + w2 = getOption('width') + con = getOption('knitr.progress.output', '') + pb = txtProgressBar( + 0, total, 0, '.', width = max(w2 - 10 - n, 10), style = 3, file = con + ) + list( + update = function(i) { + setTxtProgressBar(pb, i) + cat(s[i], file = con) # append chunk label to the progress bar + }, + done = function() { + # wipe the progress bar + cat(paste0('\r', strrep(' ', max(w2, 10) + 10 + n)), file = con) + close(pb) + } + ) +}