record_demo() and friends cover the typical case — type
a block of R code into a real REPL, get a GIF. When you outgrow that,
the underlying charmbracelet/vhs tape
syntax is more general than record_demo() exposes. This
vignette walks through the primitives you’ll want, the corners that
bite, and how to hand a tape file or string to
vhsr_run_tape().
record_demo() behindrecord_demo() assumes you want to type R into a
freshly-spawned R REPL. Reach for hand-written tape when:
Backspace corrections, arrow-key navigation,
Wait /regex/ synchronisation against output.Screenshot
directives in a single recording for a flipbook.For everything else, record_demo() saves typing.
The full vhs tape reference lives at the charmbracelet/vhs README; this is a working subset.
Set Shell "bash"
Set FontSize 22
Set Width 1200
Set Height 600
Set Theme "Catppuccin Mocha"
Set TypingSpeed 60ms
Set directives must come before any input
(Type, Enter, …). record_demo()
always emits them at the top of the tape.
Type "echo Hello"
Type@<duration> overrides the global
TypingSpeed for one line:
Type@30ms "this types at 30ms per char"
vhsR uses this for typing_speed_jitter.
Enter
Enter 3 # press Enter three times
Backspace 5
Tab
Space
Up / Down / Left / Right
Ctrl+C
Sleep 800ms
Sleep 2s
Wait /regex/ blocks until the on-screen content
matches:
Type "long-running-command"
Enter
Wait /Done\.$/ # waits up to 15s by default
Useful when the next typed input would be too early — for instance, waiting for an R prompt to redraw before continuing.
Hide
Type "stuff that shouldn't appear in the recording"
Enter
Show
Hide does not pause execution. The
shell still runs typed input; vhs just stops adding frames to the
output. record_demo() uses this to hide the
R --quiet --no-save (or arf, or
radian) startup.
Source "common-setup.tape" # include another tape file
Env DEMO_VAR "true" # env var for the spawned shell
Screenshot "frame.png" # save a PNG of the current frame
Screenshot is what record_demo_screenshot()
calls through to.
Three corners of vhs tape syntax that vhsR papers over internally; you’ll want to copy the pattern when writing tape by hand.
Slashes in unquoted paths look like a Wait
regex literal. vhs parses /something/ as a regex.
So this fails:
Output /tmp/demo.gif
Screenshot /tmp/frame.png
Quote the paths:
Output "/tmp/demo.gif"
Screenshot "/tmp/frame.png"Strings with double quotes inside
Type need backtick fallback:
Type "x <- \"hello\"" # broken — vhs doesn't handle the escape
Type `x <- "hello"` # works — vhs treats backticks as quotes
vhsR:::quote_for_tape() picks the right form per
line.
Screenshot is occasionally racy.
vhs takes the screenshot the moment the directive runs in the tape, but
the headless renderer sometimes hasn’t repainted yet. Leave a
Sleep 200ms (or longer) immediately before each
Screenshot to be safe.
Pass a file path:
…or a multi-line string:
vhsr_run_tape('
Output "demo.gif"
Set Shell "bash"
Set FontSize 22
Type "echo Hello from a hand-written tape!"
Enter
Sleep 800ms
Type `ls -1 *.R | head -3`
Enter
Sleep 1s
')Both code paths go through the same vhs binary as
record_demo(), with the same vhsr_check() gate
and the same path resolution (vhsr_vhs_path() etc.).
Anything you can express in tape syntax, this function can run.
The example above produces:
vhsR’s vhsr_install() pins to a specific upstream vhs
version (DEFAULT_VHS_VERSION in R/install.R).
If you’ve written tape that relies on a newer vhs feature, install that
version explicitly:
…or point at a system-installed vhs by setting
vhsR.vhs_path / VHSR_VHS. See
?vhsr_vhs_path for the resolution chain.
?vhsr_run_tape, ?vhsr_install,
?vhsr_vhs_path.vignette("recording-for-readme", package = "vhsR") and
vignette("pacing-and-feel", package = "vhsR") for the
typical-case workflows.