The catchup package provides a way to “catch up” your R environment to a specified point in a computational notebook containing R chunks. It currently works with both Quarto and R Markdown notebooks.
Note: The lack of activity in this repo is not a sign that the project is inactive or abandoned. It’s just that it does the thing that I built it to do exactly as I intended, so until there’s a major syntax change in the Quarto/RMarkdown world, there’s unlikely to be much action around here.
The reason I haven’t yet moved the repo status from WIP to Active is that I don’t have enough reports of how well the package is holding up out in the wild. I’ve been using {catchup} regularly since the beginning and it’s working swimmingly for my own workflow and needs, but I’m sure that other people’s mileage will vary. I’d appreciate it if you try the package and let me know if you run into any problems.
Package goals
The package was born of my own frustration as someone who frequently works with Quarto notebooks interactively over multiple sessions. Typically, I’ll open a Quarto notebook with lots of different sections and want to jump to a given point in the notebook and pick up where I left off, catching my R session’s state up to reflect that position. Manually going through each chunk and sending it to the console is a pain, especially when some chunks are meant to be evaluated and others aren’t.
If using an IDE developed by the same company behind Quarto, you’ll have the convenient option of placing your cursor somewhere in the document and then hitting a button called “Run all chunks above” or similar, which is smart enough to ignore chunks with the eval option set to false. I wanted to reproduce this behaviour in the form of an R package so that the user isn’t dependent on any particular development environment to get the benefits.
My goal was to do this using only base R, so that the package is lightweight and dependency-free. To this end, it’s all just plain-text parsing and regular expressions under the hood.
Installation
You can install the latest version of catchup using your preferred method of installing R packages from git repo URLs, e.g.:
# Using the `pak` package:
pak::pkg_install("git::https://codeberg.org/pjphd/catchup.git")
# Using the `remotes` package:
remotes::install_git(url = "https://codeberg.org/pjphd/catchup.git")Usage
The whole functionality of the package is in the catchup() function. You call this function and pass it the path to a Quarto or RMarkdown file, and it will find the R chunks and run them in the R session from which it is called.
For example:
qmd_test_file <- testthat::test_path("test_data", "quarto_test.qmd")
catchup(doc_path = qmd_test_file)
#>
#> Evaluating R expressions from quarto_test.qmd :
#> [catchup]: for_example <- "I'm a variable"
#> [catchup]: i_should_exist <- 0
#> [catchup]: i_should_exist <- i_should_exist + 1
#> [catchup]: i_should_exist <- 5
#>
#> {catchup} found and evaluated 4 R chunks. The environment is now caught up to the specified point in the notebook.The test file we just used to “catch up” our session creates a variable called i_should_exist and modifies its value a couple of times. Because we just did the equivalent of “run all chunks” in the file, it’s now in our environment:
i_should_exist
#> [1] 5By default, catchup will parse the whole file and won’t evaluate any chunks where the eval option is set to FALSE.
You can customise the behaviour a bit via some optional arguments:
- Defining the document range (the lines in the document that
catchupwill consider when looking for code chunks to execute):-
from_label/to_label: If you provide a character string corresponding to a Quarto/R Markdown chunk label,catchupwill use it to define either the start (from_label) or end (to_label) of the document range. E.g. to run all chunks in a Quarto notebook upto and including the chunk with option#| label: "read-in-data", use argumentto_label = "read-in-data". -
from_line/to_line: If you specify a line number to either of these arguments,catchupwill use those line numbers to define the document range. E.g. to run all chunks up to and including line 99, use argumentto_line = 99. - You can mix and match
from_labelwithto_lineand vice versa, but if you provide bothfrom_labelandfrom_lineor bothto_labelandto_line, the_labelargument will be used to define the range and the_lineargument will be ignored.
-
-
force_eval: If you wantcatchupto ignore theevalchunk option and execute all R chunks it encounters, set this argument toTRUE.
More examples:
catchup(qmd_test_file, to_line = 33)
#>
#> Evaluating R expressions from quarto_test.qmd :
#> [catchup]: for_example <- "I'm a variable"
#> [catchup]: i_should_exist <- 0
#>
#> {catchup} found and evaluated 2 R chunks. The environment is now caught up to the specified point in the notebook.Because we only caught up to line 33, rather than the whole file, the variable i_should_exist won’t be incremented as highly now.
i_should_exist
#> [1] 0The test file we read in also creates a variable called i_should_not_exist, but it only appears in chunks with the eval option set to FALSE. As a result, it’s not in our environment at this point:
try(
print(i_should_not_exist)
)
#> Error in eval(expr, envir) : object 'i_should_not_exist' not foundWe can force those chunks to be evaluated anyway:
catchup(qmd_test_file, force_eval = TRUE)
#>
#> Evaluating R expressions from quarto_test.qmd :
#> [catchup]: for_example <- "I'm a variable"
#> [catchup]: i_should_exist <- 0
#> [catchup]: i_should_not_exist <- 0
#> [catchup]: i_should_exist <- i_should_exist + 1
#> [catchup]: i_should_not_exist <- i_should_not_exist + 1
#> [catchup]: i_should_exist <- 5
#>
#> {catchup} found and evaluated 6 R chunks. The environment is now caught up to the specified point in the notebook.
i_should_not_exist
#> [1] 1A note of caution
Normally, it’s bad practice to modify a user’s environment from within a package. However, this package is developed for the very specific use case that you want to evaluate embedded code in order to modify your environment. It does this by parsing the text as R expressions and feeding them to R’s source(). The code is actually executed, as it would be if you sourced in an R script.
It’s therefore particularly important that you verify that the code in the notebook you use to catch up is safe and that you want it executed in your environment. If you’re not sure, it would be better to go through and run the chunks manually instead of using catchup.
Controlling the environment in which chunks are run
By default, the code in the notebook’s R chunks will be evaluated in the context from which the catchup() function is called (which will normally be the global environment). However, the catchup() function does allow you to specify (via the exec_env argument) an environment that will be passed to source(), and the expressions will be evaluated there.
This is useful, for example, for testing the package - {testthat} tests are each run in their own temporary environment, but if catchup evaluates the code chunks in the global environment, testthat can’t access the results when cleaning up after itself and subsequent tests will be compromised. By setting exec_env to the value of environment() inside the test functions, it ensures that the global environment is never changed, and the objects created as a result of catchup() are in a place that testthat can clean up.
Get help
For more on using the package, see the documentation:
?catchupFor any other questions, contact me directly or raise an issue.
Package checks
Note that the following checks are run in R on my local machine when the README is built and their results embedded here via rmarkdown/knitr.
Cloud computing has real environmental costs and using CI for simple checks like these on small packages is a waste of resources in my opinion. Instead, I’m just rebuilding the README before each push to keep the results of the checks up-to-date.
Code coverage:
covr::package_coverage()
#> catchup Coverage: 75.36%
#> R/helpers.R: 64.91%
#> R/catchup.R: 82.72%R CMD CHECK results:
devtools::check(
env_vars = c("NOT_CRAN" = "true", "_R_CHECK_SYSTEM_CLOCK_" = 0),
quiet = TRUE
)
#> ℹ Loading catchup
#> ── R CMD check results ────────────────────────────────────── catchup 0.0.2 ────
#> Duration: 8.3s
#>
#> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔These checks were run in the following environment:
cbind("Session information" = sessioninfo::platform_info()[c(
"version", "system", "os", "date"
)])
#> Session information
#> version "R version 4.5.3 (2026-03-11)"
#> system "x86_64, linux-gnu"
#> os "Fedora Linux 43 (Toolbx Container Image)"
#> date "2026-09-12"