vhs icon indicating copy to clipboard operation
vhs copied to clipboard

Wait for command to finish before continuing

Open jacobtomlinson opened this issue 3 years ago • 38 comments

Is it possible to wait for a command to finish before you continue typing.

I know I can use Sleep to wait but I might not know how long the command will take and it could be different every time.

demo

jacobtomlinson avatar Oct 28 '22 11:10 jacobtomlinson

this was discussed a bit in https://github.com/charmbracelet/vhs/issues/14, but I agree we should do something about it

caarlos0 avatar Oct 28 '22 13:10 caarlos0

Yep! Thanks for this @jacobtomlinson, we'll definitely introduce something for this since it's probably the most common usecase. It will probably look something like one of these but other suggestions are welcome.

Run "sleep 2 && echo 'First!'"
Exec "sleep 2 && echo 'First!'"
Execute "sleep 2 && echo 'First!'"

maaslalani avatar Oct 28 '22 14:10 maaslalani

I'm working on a PR for this. Will this support shell scripts? E.g.,

Exec "./script.sh"

or

Exec@file "./script.sh"

cc: @caarlos0 @maaslalani

abhijit-hota avatar Oct 28 '22 16:10 abhijit-hota

IMHO, exec should:

  • type the command
  • press enter
  • wait for the command to finish

so, IMHO there's no need to handle executing files, its just a Type ./file.sh; Enter

That said, not sure if we should or not support the Type options here too (like speed), but I guess we should

caarlos0 avatar Oct 28 '22 16:10 caarlos0

Another alternative is to have a Wait command that way we don't need to add all the options for typing speed and enter delays.

Type@100ms "./file" Sleep 0.5 Enter Wait

maaslalani avatar Oct 28 '22 17:10 maaslalani

I tried implementing this but I am afraid I couldn't find a way to wait. We're using ttyd and AFAICT, there's no way of knowing the state of a process that is running inside ttyd.

Pointers are appreciated.

P.S., any reason we're using ttyd over GoTTY?

abhijit-hota avatar Oct 28 '22 18:10 abhijit-hota

@abhijit-hota I had a similar concern. Is this possible with GoTTY? We can switch over to that.

I used ttyd since it looked more active and maintained.

maaslalani avatar Oct 28 '22 18:10 maaslalani

Is this possible with GoTTY?

Doesn't seem so from the documentation.

I used ttyd since it looked more active and maintained.

Fair enough. Just checked that.


I have opened a support issue here: https://github.com/tsl0922/ttyd/issues/1009

abhijit-hota avatar Oct 28 '22 18:10 abhijit-hota

My plan is to support both ttyd and GoTTY so we can use whichever is installed on the machine.

maaslalani avatar Oct 28 '22 18:10 maaslalani

As per https://github.com/tsl0922/ttyd/issues/1009#issuecomment-1295718599, ttyd cannot tell us that.

We need to get the pid of the process started inside ttyd when pressing Enter to check the state.

abhijit-hota avatar Oct 29 '22 04:10 abhijit-hota

I think we can solve this with pgrep:

If someone wants to Exec "sleep 5" we can pgrep "sleep 5" every few milliseconds (100ms?) to wait for it to finish.

image

maaslalani avatar Oct 29 '22 06:10 maaslalani

What do you think about the method suggested here: https://github.com/tsl0922/ttyd/issues/1009#issuecomment-1295741037

abhijit-hota avatar Oct 29 '22 07:10 abhijit-hota

Not sure we can use $! because we can't really type into the ttyd session since we are recording it. pgrep will let us look at the running processes outside the ttyd session which I think is desirable.

maaslalani avatar Oct 30 '22 11:10 maaslalani

Makes sense. There is a chance of conflict between command when using pgrep.

abhijit-hota avatar Oct 30 '22 12:10 abhijit-hota

The other thing I was thinking is could we somehow set how long the command appears to take in the GIF, regardless of how long it actually took.

Some commands vary a lot in execution time, I just did a helm install which took ~30 seconds, but if the cache conditions are right it only takes ~5 seconds.

It would be great to tell vcr to wait until the command has finished, but also perhaps only show a fixed 2 second delay and snip out the rest.

jacobtomlinson avatar Oct 31 '22 10:10 jacobtomlinson

Another alternative would be specifying some condition that is polled until it is true, and until then further execution is blocked. But for "wait for command to finish" this is a bit of a complicated way, and might not always be possible (or at least easily).

Airblader avatar Nov 02 '22 10:11 Airblader

Some ideas:

  • Wait for the shell prompt to be re-displayed. This would require a setting so that VCR knows what to wait for. This is similar to expect(1). There could be a default value, or override for specific prompt (eg from a subprocess)
  • Use the shell's prompt command to send a signal to VCR to continue (this doesn't work for prompts within a subprocess)

normanr avatar Nov 14 '22 00:11 normanr

Can't this be tracked via the process tree? When Enter is executed a new child process will be created, VCR could just poll until that process has finished.

jacobtomlinson avatar Nov 14 '22 10:11 jacobtomlinson

Would that also work for things like command &?

Airblader avatar Nov 14 '22 10:11 Airblader

Would you want it to? Adding the & usually means you want to continue without waiting.

jacobtomlinson avatar Nov 14 '22 10:11 jacobtomlinson

Exactly, but the process would still exist, so wouldn't VHS just keep blocking?

Airblader avatar Nov 14 '22 10:11 Airblader

Only if the user calls Wait.

I'm suggesting syntax like Type@100ms "./file" Sleep 0.5 Enter Wait that @maaslalani proposed. If you don't want to wait you just don't add the Wait.

jacobtomlinson avatar Nov 14 '22 10:11 jacobtomlinson

Ah, I see; you would still make it an explicit Wait action. Which of course makes sense, since there are blocking processes where you definitely do want to continue doing things (like opening vim).

Airblader avatar Nov 14 '22 10:11 Airblader

I played around with VHS a bit trying to automatically generate small demo videos in CI for our documentation site that are always up to date. Without a way to wait for commands to finish that is not possible, as sleeping for a given time interval is not reliable enough when you try to create a video automatically. So :+1: from my side for this feature request.

dominiklohmann avatar Nov 16 '22 13:11 dominiklohmann

I've tried to implement this feature in my implementation using PROMPT_COMMAND env variable to send signal from shell back to the controller, and it works nicely. Maybe it could be used in VHS too?

kotborealis avatar Nov 19 '22 19:11 kotborealis

I think the expect approach @normanr suggested would be the most universal.

It would solve things like:

  • controlling an Arduino or CNC controller over serial
  • logging into an ssh server
  • answering interactive prompts when creating a demo for a tool like terraform

The most significant difficulty I see is determining when to start waiting for the output. Waiting for a prompt would be intuitive, but it may not be evident that you'd need to expect all the previous "prompt> " lines to get where you want to; so maybe a ResetExpect or StartExpect command too?


I propose something like this:

Set ExpectTimeout 3s # timeout Expect after 3 seconds
Set ExpectTimeout 0  # disable Expect timeout (default)

StartExpect # Signals the start of where we should look for the output for Expect

Expect[@timeout] <string> # wait for a string in output since the most recent StartExpect or Expect (or beginning of tape)

Care should be taken so that Expect is interruptable (i.e., Ctrl+C) in case things go wrong :D.

mastercactapus avatar Nov 26 '22 15:11 mastercactapus

I would throw in that it isn't entirely universal to wait for some output. You might well want to wait for a process to exit without output or for some other state to have been reached. The latter can be considered out of scope, but the former does seem important to me.

Airblader avatar Nov 26 '22 16:11 Airblader

@Airblader hmm, that's a good point. Though when working with a shell, you could wait for the prompt to re-appear. Since it is likely the most common case, maybe we can handle it with a default?

Something like:

Set ExpectPrompt ">" # set's the default expect string (usually the prompt string)

Expect[@timeout] [string]

Though a naked Expect doesn't read as well.

Set ExpectPrompt "$"

StartExpect
Type "sleep 1" Enter
Expect

Maybe it's okay, but here's some other ideas:

Set WaitForDefault "$"

StartWaitFor
Type "sleep 1" Enter
WaitFor
Set WaitOutputDefault "$"

StartWaitOutput
Type "sleep 1" Enter
WaitOutput

Type "echo hi" Enter
WaitOutput "hi"

mastercactapus avatar Nov 26 '22 16:11 mastercactapus

IMHO: Most universal would be to wait for an exit code.

till avatar Nov 26 '22 18:11 till

I can't entirely agree that an exit code is universal since commands can be interactive. For example, SSH may prompt for a password without giving an exit code before the input is required. But an exit code would work for most use cases.

That said, it's also a little challenging to go the exit code route because vhs isn't running the commands; instead, it's typing keys to a browser, which gets funneled to ttyd, and records the rendered canvas image it gets back. In other words, it's emulating a keyboard and screen rather than being a shell interpreter.

Something like what @kotborealis suggested could work but may be fragile, as it would only work if the shell is running on the same machine and only if we are not making a tape for a command line tool that's interactive.


I've been experimenting with this all day, and I'm trying to settle on an API that makes sense. I currently have it as Prompt but I'm thinking of changing it to Match and MatchAny. As I've been testing it, there are just too many edge cases so I went with a regex approach.

Match would allow you to specify a regular expression to match against the current line of the terminal (e.g., ^> or ^password:) and MatchAny would match against any string on the screen, allowing you to work with interactive TUI applications.

mastercactapus avatar Nov 26 '22 20:11 mastercactapus