|
6 | 6 | "io"
|
7 | 7 | "os"
|
8 | 8 | "os/exec"
|
| 9 | + "regexp" |
9 | 10 | "strconv"
|
10 | 11 | "strings"
|
11 | 12 | "time"
|
@@ -76,6 +77,7 @@ var CommandFuncs = map[parser.CommandType]CommandFunc{
|
76 | 77 | token.COPY: ExecuteCopy,
|
77 | 78 | token.PASTE: ExecutePaste,
|
78 | 79 | token.ENV: ExecuteEnv,
|
| 80 | + token.WAIT: ExecuteWait, |
79 | 81 | }
|
80 | 82 |
|
81 | 83 | // ExecuteNoop is a no-op command that does nothing.
|
@@ -111,6 +113,71 @@ func ExecuteKey(k input.Key) CommandFunc {
|
111 | 113 | }
|
112 | 114 | }
|
113 | 115 |
|
| 116 | +// WaitTick is the amount of time to wait between checking for a match. |
| 117 | +const WaitTick = 10 * time.Millisecond |
| 118 | + |
| 119 | +// ExecuteWait is a CommandFunc that waits for a regex match for the given amount of time. |
| 120 | +func ExecuteWait(c parser.Command, v *VHS) error { |
| 121 | + scope, rxStr, ok := strings.Cut(c.Args, " ") |
| 122 | + rx := v.Options.WaitPattern |
| 123 | + if ok { |
| 124 | + // This is validated on parse so using MustCompile reduces noise. |
| 125 | + rx = regexp.MustCompile(rxStr) |
| 126 | + } |
| 127 | + |
| 128 | + timeout := v.Options.WaitTimeout |
| 129 | + if c.Options != "" { |
| 130 | + t, err := time.ParseDuration(c.Options) |
| 131 | + if err != nil { |
| 132 | + // Shouldn't be possible due to parse validation. |
| 133 | + return fmt.Errorf("failed to parse duration: %w", err) |
| 134 | + } |
| 135 | + timeout = t |
| 136 | + } |
| 137 | + |
| 138 | + checkT := time.NewTicker(WaitTick) |
| 139 | + defer checkT.Stop() |
| 140 | + timeoutT := time.NewTimer(timeout) |
| 141 | + defer timeoutT.Stop() |
| 142 | + |
| 143 | + for { |
| 144 | + var last string |
| 145 | + switch scope { |
| 146 | + case "Line": |
| 147 | + line, err := v.CurrentLine() |
| 148 | + if err != nil { |
| 149 | + return fmt.Errorf("failed to get current line: %w", err) |
| 150 | + } |
| 151 | + last = line |
| 152 | + |
| 153 | + if rx.MatchString(line) { |
| 154 | + return nil |
| 155 | + } |
| 156 | + case "Screen": |
| 157 | + lines, err := v.Buffer() |
| 158 | + if err != nil { |
| 159 | + return fmt.Errorf("failed to get buffer: %w", err) |
| 160 | + } |
| 161 | + last = strings.Join(lines, "\n") |
| 162 | + |
| 163 | + if rx.MatchString(last) { |
| 164 | + return nil |
| 165 | + } |
| 166 | + default: |
| 167 | + // Should be impossible due to parse validation, but we don't want to |
| 168 | + // hang if it does happen due to a bug. |
| 169 | + return fmt.Errorf("invalid scope %q", scope) |
| 170 | + } |
| 171 | + |
| 172 | + select { |
| 173 | + case <-checkT.C: |
| 174 | + continue |
| 175 | + case <-timeoutT.C: |
| 176 | + return fmt.Errorf("timeout waiting for %q to match %s; last value was: %s", c.Args, rx.String(), last) |
| 177 | + } |
| 178 | + } |
| 179 | +} |
| 180 | + |
114 | 181 | // ExecuteCtrl is a CommandFunc that presses the argument keys and/or modifiers
|
115 | 182 | // with the ctrl key held down on the running instance of vhs.
|
116 | 183 | func ExecuteCtrl(c parser.Command, v *VHS) error {
|
@@ -371,6 +438,8 @@ var Settings = map[string]CommandFunc{
|
371 | 438 | "WindowBar": ExecuteSetWindowBar,
|
372 | 439 | "WindowBarSize": ExecuteSetWindowBarSize,
|
373 | 440 | "BorderRadius": ExecuteSetBorderRadius,
|
| 441 | + "WaitPattern": ExecuteSetWaitPattern, |
| 442 | + "WaitTimeout": ExecuteSetWaitTimeout, |
374 | 443 | "CursorBlink": ExecuteSetCursorBlink,
|
375 | 444 | }
|
376 | 445 |
|
@@ -521,6 +590,26 @@ func ExecuteSetTypingSpeed(c parser.Command, v *VHS) error {
|
521 | 590 | return nil
|
522 | 591 | }
|
523 | 592 |
|
| 593 | +// ExecuteSetWaitTimeout applies the default wait timeout on the vhs. |
| 594 | +func ExecuteSetWaitTimeout(c parser.Command, v *VHS) error { |
| 595 | + waitTimeout, err := time.ParseDuration(c.Args) |
| 596 | + if err != nil { |
| 597 | + return fmt.Errorf("failed to parse wait timeout: %w", err) |
| 598 | + } |
| 599 | + v.Options.WaitTimeout = waitTimeout |
| 600 | + return nil |
| 601 | +} |
| 602 | + |
| 603 | +// ExecuteSetWaitPattern applies the default wait pattern on the vhs. |
| 604 | +func ExecuteSetWaitPattern(c parser.Command, v *VHS) error { |
| 605 | + rx, err := regexp.Compile(c.Args) |
| 606 | + if err != nil { |
| 607 | + return fmt.Errorf("failed to compile regexp: %w", err) |
| 608 | + } |
| 609 | + v.Options.WaitPattern = rx |
| 610 | + return nil |
| 611 | +} |
| 612 | + |
524 | 613 | // ExecuteSetPadding applies the padding on the vhs.
|
525 | 614 | func ExecuteSetPadding(c parser.Command, v *VHS) error {
|
526 | 615 | padding, err := strconv.Atoi(c.Args)
|
|
0 commit comments