charmbracelet / charmbracelet/vhs

On-demand frame capture

Open
#417 0 comments 1 reaction 0 assignees View on GitHub
enhancement
Dominant language
Go
Stars
20.9k
Forks
474
Avg merge
4d 1h
Merged PRs (30d)
2

Description

**Is your feature request related to a problem? Please describe.**

The recording strategy (for text files) works on a [per-command basis](https://github.com/charmbracelet/vhs/blob/d98c77a8793adcd9b9944c629280007ec3999eba/command.go#L120), i.e. record a frame when a command is executed. This can generate inconsistencies when dealing with external programs that can have variant response times, thus affecting golden file stability. Right now, the only way to avoid this is by doing a `Hide`/Run the program and wait a while for it to return/`Show` dance that can make the tape file a bit annoying to navigate.

**Describe the solution you'd like**

It would be great to have a command `Set CaptureMode` which could either be `OnCommand` (the default one) or `OnDemand`. Setting the capture mode to `OnDemand` would disable the tool from recording an output at every command and only record the output when executing a specific command (e.g. `Capture`).

**Describe alternatives you've considered**

This could also be achieved by adding a configuration flag to the binary, such as `-capturemode=`. It's up to you guys to decide which one would be the best approach.

**Additional context**
Here's a simple use case with a variant response time executable:
```Go
// Quits after finding a multiple of 10 or after 1 second.
func main() {
found := make(chan struct{})
go func() {
for {
if rand.Int()%10 != 0 {
time.Sleep(200 * time.Millisecond)
continue
}
close(found)
break
}
}()
select {
case <-found:
fmt.Println("Found :)")
case <-time.After(time.Second):
fmt.Println("Timed out :(")
}
}
```
Running the above with a simple tape file:
```tape
Output simple.txt
Type "./stopWhenFound"
Enter
Sleep 1s
```
We can have this (when the number is found immediately):
```text
> ./stopWhenFound

────────────────────────────────────────────────────────────────────────────────
> ./stopWhenFound
Found :)
>

────────────────────────────────────────────────────────────────────────────────
> ./stopWhenFound
Found :)
>

────────────────────────────────────────────────────────────────────────────────
```
Or this (when the number is found after a while):
```text
> ./stopWhenFound

────────────────────────────────────────────────────────────────────────────────
> ./stopWhenFound

────────────────────────────────────────────────────────────────────────────────
> ./stopWhenFound
Found :)
>

────────────────────────────────────────────────────────────────────────────────
```

When using `Hide`/`Sleep`:
```tape
Output hiding.txt
Hide
Type "./stopWhenFound"
Enter
Sleep 1s
Show
# The final sleep necessary when using hide/sleep, as vhs must have a .gif output and
# will fail with a "no frames" error if it can't generate it, even though the above should be
# enough for the text output.
# This can be avoided if https://github.com/charmbracelet/vhs/issues/363 gets addressed.
Sleep 100ms
```
We can have a single frame with the output (the last frame is due to the "required" sleep in the end):
```text
> ./stopWhenFound
Found :)
>

────────────────────────────────────────────────────────────────────────────────
> ./stopWhenFound
Found :)
>

────────────────────────────────────────────────────────────────────────────────
```
On simple files, the hide/sleep isn't that bad, but when dealing with more complex interactions it can escalate fast. This could be avoided by the suggestion listed in this issue, since (instead of hiding/showing) we could use a `Capture` line that would record the current terminal frame.

Contributor guide

Open the contributing guide

Research direction

Start with the recording strategy in command.go around the linked per-command capture logic, then trace how Set commands and tape commands are parsed. Compare the OnCommand and proposed OnDemand behavior in the examples, including the Capture command. Done means on-demand tapes capture only at explicit Capture points while the default behavior remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
cli
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.