├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── dependabot-sync.yml │ ├── goreleaser.yml │ ├── lint-sync.yml │ ├── lint.yml │ └── nightly.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── THEMES.md ├── bar.png ├── command.go ├── command_test.go ├── draw.go ├── embed.go ├── error.go ├── evaluator.go ├── examples ├── README.md ├── bubbletea │ ├── altscreen-toggle.tape │ ├── chat.tape │ ├── composable-views.tape │ ├── credit-card-form.tape │ ├── debounce.tape │ ├── exec.tape │ ├── fullscreen.tape │ ├── glamour.tape │ ├── help.tape │ ├── http.tape │ ├── list-default.tape │ ├── list-fancy.tape │ ├── list-simple.tape │ ├── package-manager.tape │ ├── pager.tape │ ├── paginator.tape │ ├── pipe.tape │ ├── progress-animated.tape │ ├── progress-static.tape │ ├── realtime.tape │ ├── result.tape │ ├── send-msg.tape │ ├── sequence.tape │ ├── simple.tape │ ├── spinner.tape │ ├── spinners.tape │ ├── split-editors.tape │ ├── stopwatch.tape │ ├── table.tape │ ├── tabs.tape │ ├── textarea.tape │ ├── textinput.tape │ ├── textinputs.tape │ ├── timer.tape │ ├── tui-daemon-combo.tape │ └── views.tape ├── cli-ui │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── format.gif │ ├── format.tape │ ├── interactive-prompt.gif │ ├── interactive-prompt.tape │ ├── nested-frames.gif │ ├── nested-frames.tape │ ├── progress.gif │ ├── progress.tape │ ├── spinner.gif │ ├── spinner.tape │ ├── status-widget.gif │ ├── status-widget.tape │ ├── symbols.gif │ ├── symbols.tape │ ├── text-prompt.ascii │ ├── text-prompt.gif │ └── text-prompt.tape ├── commands │ ├── README.md │ ├── alt.tape │ ├── arrow.gif │ ├── arrow.tape │ ├── backspace.gif │ ├── backspace.tape │ ├── clipboard.gif │ ├── clipboard.tape │ ├── comment.tape │ ├── ctrl.gif │ ├── ctrl.tape │ ├── enter.gif │ ├── enter.tape │ ├── hide.gif │ ├── hide.tape │ ├── show.tape │ ├── space.gif │ ├── space.tape │ ├── tab.gif │ ├── tab.tape │ ├── type.gif │ └── type.tape ├── decorations │ ├── decorations.gif │ └── decorations.tape ├── demo.gif ├── demo.mp4 ├── demo.png ├── demo.tape ├── demo.webm ├── env │ ├── env.gif │ └── env.tape ├── errors │ ├── dimensions.tape │ ├── parser.tape │ └── require.tape ├── examples │ └── screenshot.gif ├── fixtures │ └── all.tape ├── gh-cli │ ├── README.md │ ├── gh-issue.gif │ ├── gh-issue.tape │ ├── gh-pr.gif │ └── gh-pr.tape ├── glow │ ├── CarrotCake.md │ ├── NiHao.md │ ├── README.md │ ├── StewedPeaches.md │ ├── glow-edit.gif │ ├── glow-edit.tape │ ├── glow-simple.ascii │ ├── glow-simple.gif │ ├── glow-simple.tape │ ├── glow.ascii │ ├── glow.tape │ ├── notes │ │ ├── Currywurst.md │ │ ├── Kasewurst.md │ │ ├── Spatzle.md │ │ └── Weibwurst.md │ ├── to-do │ │ ├── Okonomiyaki.md │ │ └── Takoyaki.md │ └── vhs-glow.gif ├── gum │ ├── README.md │ ├── file.gif │ ├── file.tape │ ├── pager.gif │ ├── pager.tape │ ├── src │ │ ├── id_rsa │ │ ├── id_rsa.pub │ │ ├── lipgloss │ │ │ ├── README.md │ │ │ ├── align.go │ │ │ ├── borders.go │ │ │ ├── colors.go │ │ │ ├── join.go │ │ │ └── style.go │ │ └── super_secret_message.txt │ ├── superhero.csv │ ├── table.gif │ └── table.tape ├── jqp │ ├── README.md │ ├── jqp.gif │ └── jqp.tape ├── meta.gif ├── meta.tape ├── neofetch │ ├── README.md │ ├── colorize-ascii.go │ ├── neofetch.gif │ ├── neofetch.mp4 │ ├── neofetch.tape │ ├── neofetch.webm │ ├── vhs-color.ascii │ ├── vhs.ascii │ └── vhs.conf ├── publish │ ├── cassette.tape │ ├── out.gif │ ├── output.gif │ ├── publish.gif │ └── publish.tape ├── screenshot.gif ├── screenshot.tape ├── settings │ ├── README.md │ ├── height.gif │ ├── height.tape │ ├── set-border-radius.gif │ ├── set-border-radius.tape │ ├── set-cursor-blink.gif │ ├── set-cursor-blink.tape │ ├── set-font-family.gif │ ├── set-font-family.tape │ ├── set-font-size-10.gif │ ├── set-font-size-10.tape │ ├── set-font-size-20.gif │ ├── set-font-size-20.tape │ ├── set-font-size-40.gif │ ├── set-font-size-40.tape │ ├── set-letter-spacing.gif │ ├── set-letter-spacing.tape │ ├── set-line-height.gif │ ├── set-line-height.tape │ ├── set-loop-offset.gif │ ├── set-loop-offset.tape │ ├── set-margin.gif │ ├── set-margin.tape │ ├── set-padding.gif │ ├── set-padding.tape │ ├── set-shell-bash.gif │ ├── set-shell-bash.tape │ ├── set-shell-cmd.gif │ ├── set-shell-cmd.tape │ ├── set-shell-custom.gif │ ├── set-shell-custom.tape │ ├── set-shell-fish.gif │ ├── set-shell-fish.tape │ ├── set-shell-nu.gif │ ├── set-shell-nu.tape │ ├── set-shell-osh.gif │ ├── set-shell-osh.tape │ ├── set-shell-pwsh.gif │ ├── set-shell-pwsh.tape │ ├── set-shell-xonsh.gif │ ├── set-shell-xonsh.tape │ ├── set-shell-zsh.gif │ ├── set-shell-zsh.tape │ ├── set-theme-name.gif │ ├── set-theme-name.tape │ ├── set-theme.gif │ ├── set-theme.tape │ ├── set-typing-speed.gif │ ├── set-typing-speed.tape │ ├── set-window-bar.gif │ ├── set-window-bar.tape │ ├── width.gif │ └── width.tape ├── slides │ ├── README.md │ ├── slides.gif │ └── slides.tape ├── split │ ├── split.gif │ └── split.tape ├── vhs-promo.mp4 ├── vhs-promo.webm ├── welcome.gif └── welcome.tape ├── ffmpeg.go ├── go.mod ├── go.sum ├── keys.go ├── lexer ├── lexer.go └── lexer_test.go ├── main.go ├── man.go ├── mask.png ├── parser ├── parser.go └── parser_test.go ├── publish.go ├── record.go ├── record_test.go ├── screenshot.go ├── screenshot_test.go ├── scripts └── download_theme.sh ├── serve.go ├── serve_unix.go ├── serve_windows.go ├── shell.go ├── style.go ├── syntax.go ├── testing.go ├── themes.go ├── themes.json ├── themes_test.go ├── token ├── token.go └── token_test.go ├── tty.go ├── tty_unix.go ├── tty_windows.go ├── vhs.go └── video.go /.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.gif filter=lfs diff=lfs merge=lfs -text 2 | **/*.mp4 filter=lfs diff=lfs merge=lfs -text 3 | **/*.webm filter=lfs diff=lfs merge=lfs -text 4 | **/*.png filter=lfs diff=lfs merge=lfs -text 5 | *.tape linguist-language=elixir 6 | themes*.json linguist-generated 7 | THEMES.md linguist-generated 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @charmbracelet/everyone 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Setup** 14 | Please complete the following information along with version numbers, if applicable. 15 | - OS [e.g. Ubuntu, macOS] 16 | - Shell [e.g. zsh, fish] 17 | - Terminal Emulator [e.g. kitty, iterm] 18 | - Terminal Multiplexer [e.g. tmux] 19 | 20 | **To Reproduce** 21 | Steps to reproduce the behavior: 22 | 1. Go to '...' 23 | 2. Click on '....' 24 | 3. Scroll down to '....' 25 | 4. See error 26 | 27 | **Source Code** 28 | Please include source code if needed to reproduce the behavior. 29 | 30 | **Expected behavior** 31 | A clear and concise description of what you expected to happen. 32 | 33 | **Screenshots** 34 | Add screenshots to help explain your problem. 35 | 36 | **Additional context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Discord 4 | url: https://charm.sh/discord 5 | about: Chat on our Discord. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "gomod" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | day: "monday" 9 | time: "05:00" 10 | timezone: "America/New_York" 11 | labels: 12 | - "dependencies" 13 | commit-message: 14 | prefix: "chore" 15 | include: "scope" 16 | 17 | - package-ecosystem: "github-actions" 18 | directory: "/" 19 | schedule: 20 | interval: "weekly" 21 | day: "monday" 22 | time: "05:00" 23 | timezone: "America/New_York" 24 | labels: 25 | - "dependencies" 26 | commit-message: 27 | prefix: "chore" 28 | include: "scope" 29 | 30 | - package-ecosystem: "docker" 31 | directory: "/" 32 | schedule: 33 | interval: "weekly" 34 | day: "monday" 35 | time: "05:00" 36 | timezone: "America/New_York" 37 | labels: 38 | - "dependencies" 39 | commit-message: 40 | prefix: "chore" 41 | include: "scope" 42 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | uses: charmbracelet/meta/.github/workflows/build.yml@main 8 | 9 | snapshot: 10 | uses: charmbracelet/meta/.github/workflows/snapshot.yml@main 11 | secrets: 12 | goreleaser_key: ${{ secrets.GORELEASER_KEY }} 13 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-sync.yml: -------------------------------------------------------------------------------- 1 | name: dependabot-sync 2 | on: 3 | schedule: 4 | - cron: "0 0 * * 0" # every Sunday at midnight 5 | workflow_dispatch: # allows manual triggering 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | dependabot-sync: 13 | uses: charmbracelet/meta/.github/workflows/dependabot-sync.yml@main 14 | with: 15 | repo_name: ${{ github.event.repository.name }} 16 | secrets: 17 | gh_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 2 | 3 | name: goreleaser 4 | 5 | on: 6 | push: 7 | tags: 8 | - v*.*.* 9 | 10 | concurrency: 11 | group: goreleaser 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | goreleaser: 16 | uses: charmbracelet/meta/.github/workflows/goreleaser.yml@main 17 | secrets: 18 | docker_username: ${{ secrets.DOCKERHUB_USERNAME }} 19 | docker_token: ${{ secrets.DOCKERHUB_TOKEN }} 20 | gh_pat: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 21 | goreleaser_key: ${{ secrets.GORELEASER_KEY }} 22 | aur_key: ${{ secrets.AUR_KEY }} 23 | fury_token: ${{ secrets.FURY_TOKEN }} 24 | nfpm_gpg_key: ${{ secrets.NFPM_GPG_KEY }} 25 | nfpm_passphrase: ${{ secrets.NFPM_PASSPHRASE }} 26 | macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }} 27 | macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }} 28 | macos_notary_issuer_id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} 29 | macos_notary_key_id: ${{ secrets.MACOS_NOTARY_KEY_ID }} 30 | macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }} 31 | -------------------------------------------------------------------------------- /.github/workflows/lint-sync.yml: -------------------------------------------------------------------------------- 1 | name: lint-sync 2 | on: 3 | schedule: 4 | # every Sunday at midnight 5 | - cron: "0 0 * * 0" 6 | workflow_dispatch: # allows manual triggering 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | 12 | jobs: 13 | lint: 14 | uses: charmbracelet/meta/.github/workflows/lint-sync.yml@main 15 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | lint: 8 | uses: charmbracelet/meta/.github/workflows/lint.yml@main 9 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: nightly 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | nightly: 10 | uses: charmbracelet/meta/.github/workflows/nightly.yml@main 11 | secrets: 12 | docker_username: ${{ secrets.DOCKERHUB_USERNAME }} 13 | docker_token: ${{ secrets.DOCKERHUB_TOKEN }} 14 | goreleaser_key: ${{ secrets.GORELEASER_KEY }} 15 | macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }} 16 | macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }} 17 | macos_notary_issuer_id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} 18 | macos_notary_key_id: ${{ secrets.MACOS_NOTARY_KEY_ID }} 19 | macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .ssh/ 3 | captures/ 4 | tmp/ 5 | frames/ 6 | manpages 7 | completions 8 | dist 9 | vhs 10 | .idea/ 11 | .vscode/ 12 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | tests: false 4 | linters: 5 | enable: 6 | - bodyclose 7 | - exhaustive 8 | - goconst 9 | - godot 10 | - gomoddirectives 11 | - goprintffuncname 12 | - gosec 13 | - misspell 14 | - nakedret 15 | - nestif 16 | - nilerr 17 | - noctx 18 | - nolintlint 19 | - prealloc 20 | - revive 21 | - rowserrcheck 22 | - sqlclosecheck 23 | - tparallel 24 | - unconvert 25 | - unparam 26 | - whitespace 27 | - wrapcheck 28 | exclusions: 29 | generated: lax 30 | presets: 31 | - common-false-positives 32 | issues: 33 | max-issues-per-linter: 0 34 | max-same-issues: 0 35 | formatters: 36 | enable: 37 | - gofumpt 38 | - goimports 39 | exclusions: 40 | generated: lax 41 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | includes: 2 | - from_url: 3 | url: charmbracelet/meta/main/goreleaser-vhs.yaml 4 | 5 | variables: 6 | main: "." 7 | description: "A tool for recording terminal GIFs" 8 | github_url: "https://github.com/charmbracelet/vhs" 9 | maintainer: "Maas Lalani " 10 | brew_commit_author_name: "Maas Lalani" 11 | brew_commit_author_email: "maas@charm.sh" 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tsl0922/ttyd:alpine as ttyd 2 | FROM alpine:latest as fontcollector 3 | 4 | # Install Fonts 5 | RUN apk add --no-cache \ 6 | --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main \ 7 | --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community \ 8 | --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing \ 9 | font-adobe-source-code-pro font-source-code-pro-nerd \ 10 | font-dejavu font-dejavu-sans-mono-nerd \ 11 | font-fira-code font-fira-code-nerd \ 12 | font-hack font-hack-nerd \ 13 | font-ibm-plex-mono-nerd \ 14 | font-inconsolata font-inconsolata-nerd \ 15 | font-jetbrains-mono font-jetbrains-mono-nerd \ 16 | font-liberation font-liberation-mono-nerd \ 17 | font-noto \ 18 | font-roboto-mono \ 19 | font-ubuntu font-ubuntu-mono-nerd \ 20 | font-noto-emoji 21 | 22 | FROM debian:stable-slim 23 | 24 | RUN apt-get update 25 | 26 | # Add fonts 27 | COPY --from=fontcollector /usr/share/fonts/ /usr/share/fonts 28 | 29 | # Install latest ttyd 30 | COPY --from=ttyd /usr/bin/ttyd /usr/bin/ttyd 31 | 32 | # Expose port 33 | EXPOSE 1976 34 | 35 | # Create volume 36 | VOLUME /vhs 37 | WORKDIR /vhs 38 | 39 | # Install Dependencies 40 | RUN apt-get -y install ffmpeg chromium bash 41 | 42 | # Create user 43 | RUN useradd -u 1976 -U -s /bin/false vhs 44 | # Mimic alpine default color option 45 | RUN echo 'alias ls="ls --color"' >> ~/.bashrc 46 | # Install 47 | COPY vhs /usr/bin/ 48 | 49 | ENV VHS_PORT "1976" 50 | ENV VHS_HOST "0.0.0.0" 51 | ENV VHS_GID "1976" 52 | ENV VHS_UID "1976" 53 | ENV VHS_KEY_PATH "/vhs/vhs" 54 | ENV VHS_AUTHORIZED_KEYS_PATH "" 55 | ENV VHS_NO_SANDBOX "true" 56 | 57 | ENTRYPOINT ["/usr/bin/vhs"] 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 Charmbracelet, Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | themes.json: 2 | # this is the url used by https://windowsterminalthemes.dev/ 3 | # See https://github.com/atomcorp/themes/blob/master/app/src/App.tsx#L18 4 | @./scripts/download_theme.sh \ 5 | https://2zrysvpla9.execute-api.eu-west-2.amazonaws.com/prod/themes \ 6 | themes 7 | 8 | THEMES.md: 9 | @go run . themes --markdown 2> THEMES.md 10 | 11 | all: themes.json THEMES.md 12 | @echo "Running all" 13 | 14 | refresh: 15 | @rm -rf themes.json themes.json THEMES.md 16 | @$(MAKE) all 17 | 18 | -------------------------------------------------------------------------------- /bar.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:69d190b64fc60876593adfb2c99f233ef019971b0530e64c7e5d1d3007d8cd50 3 | size 3631 4 | -------------------------------------------------------------------------------- /command_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/charmbracelet/vhs/parser" 8 | ) 9 | 10 | func TestCommand(t *testing.T) { 11 | const numberOfCommands = 29 12 | if len(parser.CommandTypes) != numberOfCommands { 13 | t.Errorf("Expected %d commands, got %d", numberOfCommands, len(parser.CommandTypes)) 14 | } 15 | 16 | const numberOfCommandFuncs = 29 17 | if len(CommandFuncs) != numberOfCommandFuncs { 18 | t.Errorf("Expected %d commands, got %d", numberOfCommandFuncs, len(CommandFuncs)) 19 | } 20 | } 21 | 22 | func TestExecuteSetTheme(t *testing.T) { 23 | t.Run("empty", func(t *testing.T) { 24 | theme, err := getTheme(" ") 25 | requireNoErr(t, err) 26 | requireDefaultTheme(t, theme) 27 | }) 28 | t.Run("named", func(t *testing.T) { 29 | theme, err := getTheme("Andromeda") 30 | requireNoErr(t, err) 31 | requireNotDefaultTheme(t, theme) 32 | }) 33 | t.Run("json", func(t *testing.T) { 34 | theme, err := getTheme(`{"background": "#29283b"}`) 35 | requireNoErr(t, err) 36 | requireNotDefaultTheme(t, theme) 37 | if "#29283b" != theme.Background { 38 | t.Errorf("wrong background, expected %q, got %q", "#29283b", theme.Background) 39 | } 40 | }) 41 | t.Run("suggestion", func(t *testing.T) { 42 | theme, err := getTheme("cattppuccin latt") 43 | requireEqualErr(t, err, "invalid `Set Theme \"cattppuccin latt\"`: did you mean \"Catppuccin Latte\"") 44 | requireDefaultTheme(t, theme) 45 | }) 46 | t.Run("invalid json", func(t *testing.T) { 47 | theme, err := getTheme(`{"background`) 48 | requireErr(t, err) 49 | requireDefaultTheme(t, theme) 50 | }) 51 | t.Run("unknown theme", func(t *testing.T) { 52 | theme, err := getTheme("foobar") 53 | requireErr(t, err) 54 | requireDefaultTheme(t, theme) 55 | }) 56 | } 57 | 58 | func requireErr(tb testing.TB, err error) { 59 | tb.Helper() 60 | if err == nil { 61 | tb.Fatalf("expected an error, got nil") 62 | } 63 | } 64 | 65 | func requireEqualErr(tb testing.TB, err1 error, err2 string) { 66 | tb.Helper() 67 | if err1 == nil { 68 | tb.Fatalf("expected an error, got nil") 69 | } 70 | if err1.Error() != err2 { 71 | tb.Fatalf("errors do not match: %q != %q", err1.Error(), err2) 72 | } 73 | } 74 | 75 | func requireNoErr(tb testing.TB, err error) { 76 | tb.Helper() 77 | if err != nil { 78 | tb.Fatalf("expected no error, got: %v", err) 79 | } 80 | } 81 | 82 | func requireDefaultTheme(tb testing.TB, theme Theme) { 83 | tb.Helper() 84 | if !reflect.DeepEqual(DefaultTheme, theme) { 85 | tb.Fatalf("expected theme to be the default theme, got something else: %+v", theme) 86 | } 87 | } 88 | 89 | func requireNotDefaultTheme(tb testing.TB, theme Theme) { 90 | tb.Helper() 91 | if reflect.DeepEqual(DefaultTheme, theme) { 92 | tb.Fatalf("expected theme to be different from the default theme, got the default instead") 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /embed.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import _ "embed" 4 | 5 | //go:embed examples/demo.tape 6 | var demoTape []byte 7 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | 8 | "github.com/charmbracelet/vhs/parser" 9 | ) 10 | 11 | // InvalidSyntaxError is returned when the parser encounters one or more errors. 12 | type InvalidSyntaxError struct { 13 | Errors []parser.Error 14 | } 15 | 16 | func (e InvalidSyntaxError) Error() string { 17 | return fmt.Sprintf("parser: %d error(s)", len(e.Errors)) 18 | } 19 | 20 | // ErrorColumnOffset is the number of columns that an error should be printed 21 | // to the left to account for the line number. 22 | const ErrorColumnOffset = 5 23 | 24 | // Underline returns a string of ^ characters which helps underline the problematic token 25 | // in a parser.Error. 26 | func Underline(n int) string { 27 | return ErrorStyle.Render(strings.Repeat("^", n)) 28 | } 29 | 30 | // LineNumber returns a formatted version of the given line number. 31 | func LineNumber(line int) string { 32 | return LineNumberStyle.Render(fmt.Sprintf(" %2d │ ", line)) 33 | } 34 | 35 | func printError(out io.Writer, tape string, err parser.Error) { 36 | lines := strings.Split(tape, "\n") 37 | 38 | _, _ = fmt.Fprint(out, LineNumber(err.Token.Line)) 39 | _, _ = fmt.Fprintln(out, lines[err.Token.Line-1]) 40 | _, _ = fmt.Fprint(out, strings.Repeat(" ", err.Token.Column+ErrorColumnOffset)) 41 | _, _ = fmt.Fprintln(out, Underline(len(err.Token.Literal)), err.Msg) 42 | _, _ = fmt.Fprintln(out) 43 | } 44 | 45 | func printErrors(out io.Writer, tape string, errs []error) { 46 | for _, err := range errs { 47 | switch err := err.(type) { 48 | case InvalidSyntaxError: 49 | for _, v := range err.Errors { 50 | printError(out, tape, v) 51 | } 52 | _, _ = fmt.Fprintln(out, ErrorStyle.Render(err.Error())) 53 | 54 | default: 55 | _, _ = fmt.Fprintln(out, ErrorStyle.Render(err.Error())) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /evaluator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | 10 | "github.com/charmbracelet/vhs/lexer" 11 | "github.com/charmbracelet/vhs/parser" 12 | "github.com/charmbracelet/vhs/token" 13 | "github.com/go-rod/rod" 14 | ) 15 | 16 | // EvaluatorOption is a function that can be used to modify the VHS instance. 17 | type EvaluatorOption func(*VHS) 18 | 19 | // Evaluate takes as input a tape string, an output writer, and an output file 20 | // and evaluates all the commands within the tape string and produces a GIF. 21 | func Evaluate(ctx context.Context, tape string, out io.Writer, opts ...EvaluatorOption) []error { 22 | l := lexer.New(tape) 23 | p := parser.New(l) 24 | 25 | cmds := p.Parse() 26 | errs := p.Errors() 27 | if len(errs) != 0 || len(cmds) == 0 { 28 | return []error{InvalidSyntaxError{errs}} 29 | } 30 | 31 | v := New() 32 | for _, cmd := range cmds { 33 | if cmd.Type == token.SET && cmd.Options == "Shell" || cmd.Type == token.ENV { 34 | err := Execute(cmd, &v) 35 | if err != nil { 36 | return []error{err} 37 | } 38 | } 39 | } 40 | 41 | // Start things up 42 | if err := v.Start(); err != nil { 43 | return []error{err} 44 | } 45 | defer func() { _ = v.close() }() 46 | 47 | // Let's wait until we can access the window.term variable. 48 | // 49 | // This is necessary because some SET commands modify the terminal. 50 | err := v.Page.Wait(rod.Eval("() => window.term != undefined")) 51 | if err != nil { 52 | return []error{err} 53 | } 54 | 55 | var offset int 56 | for i, cmd := range cmds { 57 | if cmd.Type == token.SET || cmd.Type == token.OUTPUT || cmd.Type == token.REQUIRE { 58 | _, _ = fmt.Fprintln(out, Highlight(cmd, false)) 59 | if cmd.Options != "Shell" { 60 | err := Execute(cmd, &v) 61 | if err != nil { 62 | return []error{err} 63 | } 64 | } 65 | } else { 66 | offset = i 67 | break 68 | } 69 | } 70 | 71 | // Make sure image is big enough to fit padding, bar, and margins 72 | video := v.Options.Video 73 | minWidth := double(video.Style.Padding) + double(video.Style.Margin) 74 | minHeight := double(video.Style.Padding) + double(video.Style.Margin) 75 | if video.Style.WindowBar != "" { 76 | minHeight += video.Style.WindowBarSize 77 | } 78 | if video.Style.Height < minHeight || video.Style.Width < minWidth { 79 | v.Errors = append( 80 | v.Errors, 81 | fmt.Errorf( 82 | "Dimensions must be at least %d x %d", 83 | minWidth, minHeight, 84 | ), 85 | ) 86 | } 87 | 88 | if len(v.Errors) > 0 { 89 | return v.Errors 90 | } 91 | 92 | // Setup the terminal session so we can start executing commands. 93 | v.Setup() 94 | 95 | // If the first command (after Settings and Outputs) is a Hide command, we can 96 | // begin executing the commands before we start recording to avoid capturing 97 | // any unwanted frames. 98 | if cmds[offset].Type == token.HIDE { 99 | for i, cmd := range cmds[offset:] { 100 | if cmd.Type == token.SHOW { 101 | offset += i 102 | break 103 | } 104 | _, _ = fmt.Fprintln(out, Highlight(cmd, true)) 105 | err := Execute(cmd, &v) 106 | if err != nil { 107 | return []error{err} 108 | } 109 | } 110 | } 111 | 112 | // Begin recording frames as we are now in a recording state. 113 | ctx, cancel := context.WithCancel(ctx) 114 | ch := v.Record(ctx) 115 | 116 | // Clean up temporary files at the end. 117 | defer func() { 118 | if v.Options.Video.Output.Frames != "" { 119 | // Move the frames to the output directory. 120 | _ = os.Rename(v.Options.Video.Input, v.Options.Video.Output.Frames) 121 | } 122 | 123 | _ = v.Cleanup() 124 | }() 125 | 126 | teardown := func() { 127 | // Stop recording frames. 128 | cancel() 129 | // Read from channel to ensure recorder is done. 130 | <-ch 131 | } 132 | 133 | // Log errors from the recording process. 134 | go func() { 135 | for err := range ch { 136 | log.Print(err.Error()) 137 | } 138 | }() 139 | 140 | for _, cmd := range cmds[offset:] { 141 | if ctx.Err() != nil { 142 | teardown() 143 | return []error{ctx.Err()} 144 | } 145 | 146 | // When changing the FontFamily, FontSize, LineHeight, Padding 147 | // The xterm.js canvas changes dimensions and causes FFMPEG to not work 148 | // correctly (specifically) with palettegen. 149 | // It will be possible to change settings on the fly in the future, but 150 | // it is currently not as it does not result in a proper render of the 151 | // GIF as the frame sequence will change dimensions. This is fixable. 152 | // 153 | // We should remove if isSetting statement. 154 | isSetting := cmd.Type == token.SET && cmd.Options != "TypingSpeed" 155 | 156 | if isSetting { 157 | fmt.Println(ErrorStyle.Render(fmt.Sprintf("WARN: 'Set %s %s' has been ignored. Move the directive to the top of the file.\nLearn more: https://github.com/charmbracelet/vhs#settings", cmd.Options, cmd.Args))) 158 | } 159 | if isSetting || cmd.Type == token.REQUIRE { 160 | _, _ = fmt.Fprintln(out, Highlight(cmd, true)) 161 | continue 162 | } 163 | _, _ = fmt.Fprintln(out, Highlight(cmd, !v.recording || cmd.Type == token.SHOW || cmd.Type == token.HIDE || isSetting)) 164 | err := Execute(cmd, &v) 165 | if err != nil { 166 | teardown() 167 | return []error{err} 168 | } 169 | } 170 | 171 | // If running as an SSH server, the output file is a temporary file 172 | // to use for the output. 173 | // 174 | // We need to set the GIF file path before it is created but after all of 175 | // the settings and commands are executed. This is done in `serve.go`. 176 | // 177 | // Since the GIF creation is deferred, setting the output file here will 178 | // achieve what we want. 179 | for _, opt := range opts { 180 | opt(&v) 181 | } 182 | 183 | teardown() 184 | if err := v.Render(); err != nil { 185 | return []error{err} 186 | } 187 | return nil 188 | } 189 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ### Gum 4 | 5 | Example of recording a demo of [Gum](https://github.com/charmbracelet/gum) 6 | with VHS. 7 | 8 | #### Gum File 9 | 10 | gum file demo with VHS 11 | 12 | ``` 13 | Output file.gif 14 | 15 | Type "gum file ./src" 16 | Sleep 1s 17 | Enter 18 | Sleep 2s 19 | Down@500ms 4 20 | Up@500ms 1 21 | Sleep 1s 22 | Enter 23 | Sleep 1s 24 | Down@500ms 4 25 | Sleep 1s 26 | Up@500ms 2 27 | Sleep 2s 28 | ``` 29 | 30 | #### Gum Pager 31 | 32 | gum pager demo with VHS 33 | 34 | ``` 35 | Output pager.gif 36 | 37 | Set Padding 20 38 | Set FontSize 16 39 | Set Height 600 40 | 41 | Type "gum pager < ~/src/gum/README.md --border normal" 42 | Sleep 1s 43 | Enter 44 | Sleep 2s 45 | Down@25ms 40 46 | Sleep 1s 47 | Up@25ms 30 48 | Sleep 1s 49 | Down@25ms 20 50 | Sleep 3s 51 | ``` 52 | 53 | #### Gum Table 54 | 55 | gum table demo with VHS 56 | 57 | ``` 58 | Output table.gif 59 | 60 | Type "gum table < superhero.csv -w 2,12,5,6,6,8,4,20 --height 10" 61 | Enter 62 | Sleep 1s 63 | Down@200ms 10 64 | Sleep 1s 65 | Down@200ms 10 66 | Sleep 1s 67 | Up@200ms 10 68 | Sleep 1s 69 | Enter 70 | Sleep 3s 71 | ``` 72 | 73 | ### GitHub CLI 74 | 75 | Examples recorded with VHS for the GitHub CLI (`gh`): 76 | 77 | #### Issues 78 | 79 | Simple gh issue demo 80 | 81 | 82 | ``` 83 | Output gh-issue.gif 84 | 85 | Type "gh issue list" 86 | Sleep 500ms 87 | Enter 1 88 | Sleep 4s 89 | 90 | Ctrl+L 91 | Sleep 500ms 92 | 93 | Type "gh issue view 19" 94 | Sleep 500ms 95 | Enter 96 | 97 | Sleep 5s 98 | ``` 99 | 100 | #### Pull Requests 101 | 102 | Simple gh pr demo 103 | 104 | ``` 105 | Output gh-pr.gif 106 | 107 | Type "gh pr list --state all" 108 | Sleep 500ms 109 | Enter 110 | 111 | Sleep 5s 112 | ``` 113 | 114 | ### Bubble Tea 115 | 116 | Examples recorded with VHS for Bubble Tea. 117 | 118 | * [GIFS Renders](https://github.com/charmbracelet/bubbletea/tree/master/examples) 119 | * [Tape Files](./bubbletea) 120 | 121 | ### jqp 122 | 123 | Example of recording a demo of [`jqp`](https://github.com/noahgorstein/jqp) 124 | with VHS. 125 | 126 | Simple jqp demo with VHS 127 | 128 | ### Glow 129 | 130 | Example of recording a demo of [Glow](https://github.com/charmbracelet/glow) 131 | with VHS. 132 | 133 | #### Glow Simple 134 | 135 | Simple glow demo with VHS 136 | 137 | ``` 138 | Output glow-simple.ascii 139 | Output glow-simple.gif 140 | 141 | Set Width 1000 142 | Set Height 1000 143 | 144 | Type "glow" 145 | Enter 146 | Sleep 1s 147 | Enter 148 | Sleep 1s 149 | Escape 150 | Sleep 1s 151 | Type "q" 152 | Sleep 1s 153 | ``` 154 | 155 | #### Glow 156 | 157 | Glow demo with VHS 158 | 159 | ``` 160 | Output vhs-glow.gif 161 | Output glow.ascii 162 | 163 | Set Width 1600 164 | Set Height 1040 165 | 166 | Sleep 1s 167 | 168 | Type "glow" 169 | 170 | Sleep 100ms 171 | 172 | Enter 173 | 174 | Sleep 1s 175 | 176 | Hide 177 | Tab 178 | Type "/artichoke" 179 | Enter 180 | Down 2 181 | Show 182 | 183 | Sleep 0.5s 184 | 185 | Down 20 186 | 187 | Hide 188 | Escape 189 | Type "l" 190 | Down 5 191 | Show 192 | 193 | Sleep 1s 194 | Up@400ms 5 195 | 196 | Hide 197 | Type "/ulysses" 198 | Enter 199 | Show 200 | 201 | Sleep 0.5s 202 | 203 | Down@200ms 20 204 | 205 | Hide 206 | Escape 207 | Type "/" 208 | Show 209 | 210 | Sleep 0.5s 211 | 212 | Type@500ms "todo" 213 | Sleep 1 214 | 215 | Hide 216 | Escape 217 | Type "/ulysses" 218 | Enter 219 | Show 220 | 221 | Sleep 0.5s 222 | 223 | Type@750ms "????" 224 | 225 | Hide 226 | Escape 227 | Type "/artichoke" 228 | Enter 229 | Type "m" 230 | Ctrl+A 231 | Right 4 232 | Show 233 | 234 | Sleep 1s 235 | Type@250ms "Tasty " 236 | Sleep 1s 237 | 238 | Hide 239 | Escape 240 | Down 5 241 | Type "m" 242 | Ctrl+U 243 | Show 244 | 245 | Sleep 1s 246 | Type@150ms "Your new internet thing" 247 | Sleep 3s 248 | 249 | Hide 250 | Ctrl+C 251 | Show 252 | ``` 253 | -------------------------------------------------------------------------------- /examples/bubbletea/altscreen-toggle.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/altscreen-toggle.gif 2 | 3 | Hide 4 | Type "go build -o altscreen-toggle ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./altscreen-toggle" 11 | Enter 12 | Sleep 0.5 13 | Space@0.5 4 14 | 15 | Type "q" 16 | Sleep 1 17 | 18 | Hide 19 | Type "rm ./altscreen-toggle" 20 | Enter 21 | -------------------------------------------------------------------------------- /examples/bubbletea/chat.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/chat.gif 2 | 3 | Hide 4 | Type "go build -o chat ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./chat" 11 | Enter 12 | Sleep 0.5 13 | Type "Hello, Chat Room" Sleep .25 14 | Enter 15 | Sleep 0.5 16 | Type "!!!" Sleep .25 17 | Enter 18 | Sleep 1 19 | 20 | Hide 21 | Ctrl+C 22 | Type "rm chat" 23 | Enter 24 | -------------------------------------------------------------------------------- /examples/bubbletea/composable-views.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/composable-views.gif 2 | 3 | Hide 4 | Type "go build -o views ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./views" 11 | Enter 12 | Sleep 0.5 13 | Tab@1 4 14 | Sleep 0.5 15 | Type "n" 16 | Tab 17 | Sleep 0.5 18 | Type@250ms "nnnn" 19 | Sleep 1 20 | 21 | Hide 22 | Ctrl+C 23 | Type "rm views" 24 | Enter 25 | -------------------------------------------------------------------------------- /examples/bubbletea/credit-card-form.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/credit-card-form.gif 2 | 3 | Hide 4 | Type "go build -o credit-card ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./credit-card" 11 | Enter 12 | Sleep 0.5 13 | 14 | Type "1234 5678 9012 3456" 15 | Sleep .3 Tab Sleep .3 16 | Type "12/34" 17 | Sleep .3 Tab Sleep .3 18 | Type "123" 19 | Sleep 1 20 | 21 | Hide 22 | Ctrl+C 23 | Type "rm credit-card" 24 | Enter 25 | -------------------------------------------------------------------------------- /examples/bubbletea/debounce.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/debounce.gif 2 | 3 | Hide 4 | Type "go build -o debounce ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./debounce" 11 | Enter 12 | Sleep 0.5 13 | 14 | Space@250ms 10 15 | Sleep 1 16 | 17 | Hide 18 | Ctrl+C 19 | Type "rm debounce" 20 | Enter 21 | -------------------------------------------------------------------------------- /examples/bubbletea/exec.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/exec.gif 2 | 3 | Hide 4 | Type "go build -o exec ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "EDITOR=nano ./exec" 11 | Enter 12 | Sleep 0.5 13 | 14 | Type@0.5 "aaaa" 15 | Sleep 1 16 | 17 | Type@0.5 "e" 18 | Type "Hello, EDITOR!" 19 | Sleep 1 20 | Ctrl+X 21 | Sleep 0.5 22 | Type "n" 23 | Sleep 1 24 | 25 | Hide 26 | Type "q" 27 | Type "rm exec" 28 | Enter 29 | -------------------------------------------------------------------------------- /examples/bubbletea/fullscreen.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/fullscreen.gif 2 | 3 | Hide 4 | Type "go build -o fullscreen ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./fullscreen" 11 | Sleep 0.5 12 | Enter 13 | 14 | Sleep 4 15 | 16 | Hide 17 | Type "rm fullscreen" 18 | Enter 19 | -------------------------------------------------------------------------------- /examples/bubbletea/glamour.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/glamour.gif 2 | Set Height 750 3 | Set FontSize 16 4 | 5 | Hide 6 | Type "go build -o glamour ." 7 | Enter 8 | Type "clear" 9 | Enter 10 | Show 11 | 12 | Type "./glamour" 13 | Enter 14 | Sleep 1 15 | 16 | Down@10ms 25 17 | 18 | Sleep 1 19 | 20 | Hide 21 | Type "q" 22 | Type "rm glamour" 23 | Enter 24 | Show 25 | -------------------------------------------------------------------------------- /examples/bubbletea/help.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/help.gif 2 | 3 | Hide 4 | Type "go build -o help ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./help" 11 | Enter 12 | Sleep 0.5 13 | 14 | Type@1 "?" 15 | 16 | Up@0.5 17 | Down@0.5 18 | Left@0.5 19 | Right@0.5 20 | 21 | Type@1 "?" 22 | 23 | Type "q" 24 | 25 | Sleep 1 26 | 27 | Hide 28 | Type "rm help" 29 | Enter 30 | -------------------------------------------------------------------------------- /examples/bubbletea/http.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/http.gif 2 | 3 | Hide 4 | Type "go build -o http ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./http" 11 | Enter 12 | Sleep 2 13 | 14 | Hide 15 | Ctrl+C 16 | Type "rm http" 17 | Enter 18 | -------------------------------------------------------------------------------- /examples/bubbletea/list-default.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/list-default.gif 2 | Set FontSize 16 3 | 4 | Hide 5 | Type "go build -o list-default ." 6 | Enter 7 | Type "clear" 8 | Enter 9 | Show 10 | 11 | Type "./list-default" 12 | Enter 13 | Sleep 0.5 14 | 15 | Down@250ms 3 16 | Right@250ms 3 17 | 18 | Type "/" 19 | Sleep 0.5 20 | 21 | Type "nutel" 22 | Enter 23 | Sleep 0.5 24 | Type "?" 25 | Sleep 0.5 26 | Type "?" 27 | Sleep 0.5 28 | Escape@250ms 2 29 | 30 | Hide 31 | Ctrl+C 32 | Type "rm list-default" 33 | Enter 34 | -------------------------------------------------------------------------------- /examples/bubbletea/list-fancy.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/list-fancy.gif 2 | Set FontSize 14 3 | 4 | Hide 5 | Type "go build -o list-fancy ." 6 | Enter 7 | Type "clear" 8 | Enter 9 | Show 10 | 11 | Type "./list-fancy" 12 | Enter 13 | Sleep 0.5 14 | 15 | Down@250ms 3 16 | Enter 17 | Sleep 0.5 18 | Type "x" 19 | Sleep 0.5 20 | Type "?" 21 | Sleep 0.5 22 | Type@150ms "aaaa" 23 | Type@150ms "xxxx" 24 | 25 | Hide 26 | Ctrl+C 27 | Type "rm list-fancy" 28 | Enter 29 | -------------------------------------------------------------------------------- /examples/bubbletea/list-simple.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/list-simple.gif 2 | Set FontSize 18 3 | 4 | Hide 5 | Type "go build -o list-simple ." 6 | Enter 7 | Type "clear" 8 | Enter 9 | Show 10 | 11 | Type "./list-simple" 12 | Enter 13 | Sleep 0.5 14 | 15 | Down@250ms 3 16 | Enter 17 | Sleep 1 18 | 19 | Hide 20 | Ctrl+C 21 | Type "rm list-simple" 22 | Enter 23 | -------------------------------------------------------------------------------- /examples/bubbletea/package-manager.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/package-manager.gif 2 | 3 | Hide 4 | Type "go build -o package-manager ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./package-manager" 11 | Enter 12 | Sleep 0.5 13 | 14 | Sleep 10 15 | 16 | Hide 17 | Ctrl+C 18 | Type "rm package-manager" 19 | Enter 20 | Show 21 | -------------------------------------------------------------------------------- /examples/bubbletea/pager.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/pager.gif 2 | Set FontSize 16 3 | 4 | Hide 5 | Type "go build -o pager ." 6 | Enter 7 | Type "clear" 8 | Enter 9 | Show 10 | 11 | Type "./pager" 12 | Enter 13 | Sleep 0.5 14 | 15 | Down@25ms 20 16 | 17 | Hide 18 | Type "q" 19 | Type "rm pager" 20 | Enter 21 | -------------------------------------------------------------------------------- /examples/bubbletea/paginator.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/paginator.gif 2 | Set FontSize 12 3 | Set Padding 36 4 | 5 | Hide 6 | Type "go build -o paginator ." 7 | Enter 8 | Type "clear" 9 | Enter 10 | Show 11 | 12 | Type "./paginator" 13 | Enter 14 | Sleep 1 15 | 16 | Right@250ms 10 17 | Sleep 1 18 | Left@250ms 10 19 | Sleep 1 20 | 21 | Hide 22 | Ctrl+C 23 | Type "rm paginator" 24 | Enter 25 | -------------------------------------------------------------------------------- /examples/bubbletea/pipe.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/pipe.gif 2 | 3 | Hide 4 | Type "go build -o pipe . && clear" 5 | Enter 6 | Show 7 | 8 | Type "echo 'Hello' | ./pipe" 9 | Enter 10 | Sleep 1 11 | 12 | Type ", world!" 13 | Sleep 0.5 14 | Enter 15 | Sleep 1 16 | 17 | Hide 18 | Ctrl+C 19 | Type "rm pipe" 20 | Enter 21 | -------------------------------------------------------------------------------- /examples/bubbletea/progress-animated.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/progress-animated.gif 2 | 3 | Hide 4 | Type "go build -o progress-animated ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./progress-animated" 11 | Enter 12 | Sleep 1 13 | 14 | Sleep 5 15 | 16 | Hide 17 | Ctrl+C 18 | Type "rm progress-animated" 19 | Enter 20 | Show 21 | -------------------------------------------------------------------------------- /examples/bubbletea/progress-static.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/progress-static.gif 2 | 3 | Hide 4 | Type "go build -o progress-static ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./progress-static" 11 | Enter 12 | 13 | Sleep 5 14 | 15 | Hide 16 | Ctrl+C 17 | Type "rm progress-static" 18 | Enter 19 | -------------------------------------------------------------------------------- /examples/bubbletea/realtime.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/realtime.gif 2 | 3 | Hide 4 | Type "go build -o realtime ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./realtime" 11 | Enter 12 | Sleep 0.5 13 | 14 | Sleep 2 15 | Space # Exit 16 | Sleep 1 17 | 18 | Hide 19 | Ctrl+C 20 | Type "rm realtime" 21 | Enter 22 | -------------------------------------------------------------------------------- /examples/bubbletea/result.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/result.gif 2 | 3 | Hide 4 | Type "go build -o result ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./result" 11 | Enter 12 | Sleep 0.5 13 | 14 | Down@250ms 2 15 | Up@250ms 2 16 | Enter 17 | Sleep 1 18 | 19 | Hide 20 | Ctrl+C 21 | Type "rm result" 22 | Enter 23 | -------------------------------------------------------------------------------- /examples/bubbletea/send-msg.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/send-msg.gif 2 | 3 | Hide 4 | Type "go build -o send-msg ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./send-msg" 11 | Enter 12 | Sleep 3 13 | Space 14 | Sleep 1 15 | 16 | Hide 17 | Ctrl+C 18 | Type "rm send-msg" 19 | Enter 20 | -------------------------------------------------------------------------------- /examples/bubbletea/sequence.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/sequence.gif 2 | 3 | Hide 4 | Type "go build -o sequence ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./sequence" 11 | Enter 12 | 13 | Sleep 3 14 | 15 | Hide 16 | Ctrl+C 17 | Type "rm sequence" 18 | Enter 19 | -------------------------------------------------------------------------------- /examples/bubbletea/simple.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/simple.gif 2 | 3 | Hide 4 | Type "go build -o simple ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./simple" 11 | Enter 12 | Sleep 0.5 13 | 14 | Sleep 3 15 | 16 | Hide 17 | Ctrl+C 18 | Type "rm simple" 19 | Enter 20 | -------------------------------------------------------------------------------- /examples/bubbletea/spinner.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/spinner.gif 2 | 3 | Hide 4 | Type "go build -o spinner ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./spinner" 11 | Enter 12 | Sleep 0.5 13 | 14 | Sleep 3 15 | Type "q" 16 | Sleep 0.5 17 | 18 | Hide 19 | Ctrl+C 20 | Type "rm spinner" 21 | Enter 22 | -------------------------------------------------------------------------------- /examples/bubbletea/spinners.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/spinners.gif 2 | 3 | Hide 4 | Type "go build -o spinners ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./spinners" 11 | Enter 12 | Sleep 3 13 | Right@1 5 14 | Sleep 1 15 | 16 | Hide 17 | Ctrl+C 18 | Type "rm spinners" 19 | Enter 20 | -------------------------------------------------------------------------------- /examples/bubbletea/split-editors.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/split-editors.gif 2 | Set FontSize 16 3 | 4 | Hide 5 | Type "go build -o split-editors ." 6 | Enter 7 | Type "clear" 8 | Enter 9 | Show 10 | 11 | Type "./split-editors" 12 | Enter 13 | Sleep 1 14 | 15 | Type "Hello, there!" 16 | Sleep 0.5 17 | Tab 18 | Sleep 0.5 19 | Type "Hi!" 20 | Sleep 0.5 21 | Enter 22 | Type "How are you?" 23 | Sleep 0.5 24 | Tab 25 | Sleep 0.5 26 | Ctrl+U 27 | Sleep 0.5 28 | Type "I'm good! Thanks for asking :)" 29 | Sleep .5 30 | Enter 31 | Sleep 0.5 32 | Ctrl+N 33 | Sleep 0.5 34 | Tab@250ms 2 35 | Type "Hello, world!" 36 | Sleep 1 37 | 38 | Hide 39 | Escape 40 | Type "rm split-editors" 41 | Enter 42 | -------------------------------------------------------------------------------- /examples/bubbletea/stopwatch.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/stopwatch.gif 2 | 3 | Hide 4 | Type "go build -o stopwatch ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./stopwatch" 11 | Enter 12 | Sleep 0.5 13 | 14 | Sleep 1 15 | Type "s" 16 | Sleep 0.5 17 | Type "r" 18 | Sleep 0.5 19 | Type "s" 20 | Sleep 2 21 | Type "q" 22 | Sleep 0.5 23 | 24 | Hide 25 | Ctrl+C 26 | Type "rm stopwatch" 27 | Enter 28 | Show 29 | -------------------------------------------------------------------------------- /examples/bubbletea/table.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/table.gif 2 | 3 | Hide 4 | Type "go build -o table ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./table" 11 | Enter 12 | Sleep 0.5 13 | 14 | Down@250ms 4 15 | Sleep 1 16 | Enter 17 | Sleep 1 18 | 19 | Hide 20 | Ctrl+C 21 | Type "rm table" 22 | Enter 23 | -------------------------------------------------------------------------------- /examples/bubbletea/tabs.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/tabs.gif 2 | 3 | Hide 4 | Type "go build -o tabs ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./tabs" 11 | Enter 12 | Sleep 0.5 13 | 14 | Right@0.5 5 15 | Left@0.5 5 16 | Sleep 1 17 | 18 | Hide 19 | Ctrl+C 20 | Type "rm tabs" 21 | Enter 22 | -------------------------------------------------------------------------------- /examples/bubbletea/textarea.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/textarea.gif 2 | 3 | Hide 4 | Type "go build -o textarea ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./textarea" 11 | Enter 12 | Sleep 0.5 13 | 14 | Type "Makin' my way downtown" 15 | Sleep 250ms 16 | Enter 17 | Type "Walking fast, faces pass" 18 | Sleep 250ms 19 | Enter 20 | Type "And I'm homebound" 21 | Sleep 1 22 | Ctrl+C 23 | Sleep 1 24 | 25 | Hide 26 | Type "rm textarea" 27 | Enter 28 | -------------------------------------------------------------------------------- /examples/bubbletea/textinput.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/textinput.gif 2 | 3 | Hide 4 | Type "go build -o textinput ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./textinput" 11 | Enter 12 | Sleep 0.5 13 | 14 | Type "Ponyta" 15 | Sleep 0.5 16 | Left@100ms 2 17 | Type "(lalala)" 18 | Sleep 1 19 | 20 | Hide 21 | Ctrl+C 22 | Type "rm textinput" 23 | Enter 24 | -------------------------------------------------------------------------------- /examples/bubbletea/textinputs.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/textinputs.gif 2 | 3 | Hide 4 | Type "go build -o textinputs ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./textinputs" 11 | Enter 12 | Sleep 0.5 13 | 14 | Type "qt314" 15 | Sleep 0.5 16 | Tab 17 | Sleep 0.5 18 | Ctrl+R 19 | Sleep 0.5 20 | Type "pi@cute.com" 21 | Sleep 0.5 22 | Ctrl+R 23 | Tab 24 | Sleep 0.5 25 | Type "password" 26 | 27 | Hide 28 | Ctrl+C 29 | Type "rm textinputs" 30 | Enter 31 | -------------------------------------------------------------------------------- /examples/bubbletea/timer.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/timer.gif 2 | 3 | Hide 4 | Type "go build -o timer ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./timer" 11 | Sleep 0.5 12 | Enter 13 | Sleep 0.5 14 | Type "s" 15 | Sleep 0.5 16 | Type "s" 17 | Sleep 2 18 | 19 | Hide 20 | Ctrl+C 21 | Type "rm timer" 22 | Enter 23 | -------------------------------------------------------------------------------- /examples/bubbletea/tui-daemon-combo.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/tui-daemon-combo.gif 2 | 3 | Hide 4 | Type "go build -o tui-daemon-combo ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./tui-daemon-combo" 11 | Enter 12 | Sleep 0.5 13 | 14 | Sleep 3 15 | Space 16 | Sleep 1 17 | 18 | Hide 19 | Ctrl+C 20 | Type "rm tui-daemon-combo" 21 | Enter 22 | -------------------------------------------------------------------------------- /examples/bubbletea/views.tape: -------------------------------------------------------------------------------- 1 | Output examples/bubbletea/views.gif 2 | 3 | Hide 4 | Type "go build -o views ." 5 | Enter 6 | Type "clear" 7 | Enter 8 | Show 9 | 10 | Type "./views" 11 | Enter 12 | Sleep 1 13 | 14 | Down@0.5 2 15 | Sleep 0.5 16 | Enter 17 | Sleep 3 18 | 19 | Hide 20 | Ctrl+C 21 | Type "rm views" 22 | Enter 23 | -------------------------------------------------------------------------------- /examples/cli-ui/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gem 'cli-ui' 3 | -------------------------------------------------------------------------------- /examples/cli-ui/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | cli-ui (1.5.1) 5 | 6 | PLATFORMS 7 | arm64-darwin-21 8 | 9 | DEPENDENCIES 10 | cli-ui 11 | 12 | BUNDLED WITH 13 | 2.3.7 14 | -------------------------------------------------------------------------------- /examples/cli-ui/README.md: -------------------------------------------------------------------------------- 1 | # CLI UI 2 | 3 | ### Format 4 | 5 | 6 | 7 | ``` 8 | Output examples/cli-ui/format.gif 9 | 10 | Set FontSize 32 11 | 12 | Set Width 2200 13 | Set Height 400 14 | 15 | Hide 16 | Type "irb --noautocomplete" 17 | Enter 18 | Type "require 'cli/ui'" 19 | Enter 20 | Type "CLI::UI::StdoutRouter.enable" 21 | Enter 22 | Ctrl+L 23 | Show 24 | 25 | Type 'puts CLI::UI.fmt "{{red:Red}} {{green:Green}}"' 26 | Sleep .5 27 | Enter 28 | Sleep 5 29 | ``` 30 | 31 | ### Nested Frames 32 | 33 | 34 | 35 | ``` 36 | Output examples/cli-ui/nested-frames.gif 37 | 38 | Set FontSize 32 39 | 40 | Set Width 2000 41 | Set Height 750 42 | 43 | Hide 44 | Type "irb --noautocomplete" 45 | Enter 46 | Type "require 'cli/ui'" 47 | Enter 48 | Type "CLI::UI::StdoutRouter.enable" 49 | Enter 50 | Ctrl+L 51 | Show 52 | 53 | Type "CLI::UI::Frame.open('Frame 1') do" 54 | Enter 55 | Type " CLI::UI::Frame.open('Frame 2') { puts 'inside frame 2' }" 56 | Enter 57 | Type " puts 'inside frame 1'" 58 | Enter 59 | Type "end" 60 | 61 | Sleep 1 62 | 63 | Enter 64 | 65 | Sleep 3 66 | ``` 67 | 68 | ### Progress 69 | 70 | 71 | ``` 72 | Output examples/cli-ui/progress.gif 73 | 74 | Set FontSize 32 75 | 76 | Set Width 2200 77 | Set Height 400 78 | 79 | Hide 80 | Type "irb --noautocomplete" 81 | Enter 82 | Type "require 'cli/ui'" 83 | Enter 84 | Type "CLI::UI::StdoutRouter.enable" 85 | Enter 86 | Ctrl+L 87 | Show 88 | 89 | Type "CLI::UI::Progress.progress { |bar| 100.times { sleep 0.02; bar.tick } }" 90 | Sleep .5 91 | Enter 92 | Sleep 5 93 | ``` 94 | 95 | ### Spinner 96 | 97 | 98 | ``` 99 | Output examples/cli-ui/spinner.gif 100 | 101 | Set FontSize 32 102 | 103 | Set Width 2200 104 | Set Height 400 105 | 106 | Hide 107 | Type "irb --noautocomplete" 108 | Enter 109 | Type "require 'cli/ui'" 110 | Enter 111 | Type "CLI::UI::StdoutRouter.enable" 112 | Enter 113 | Ctrl+L 114 | Show 115 | 116 | Type "CLI::UI::Spinner.spin('Spinning...') { sleep 3 }" 117 | Sleep .5 118 | Enter 119 | Sleep 5 120 | ``` 121 | 122 | ### Status Widget 123 | 124 | 125 | ``` 126 | Output examples/cli-ui/status-widget.gif 127 | 128 | Set FontSize 32 129 | 130 | Set Width 2200 131 | Set Height 400 132 | 133 | Hide 134 | Type "irb --noautocomplete" 135 | Enter 136 | Type "require 'cli/ui'" 137 | Enter 138 | Type "CLI::UI::StdoutRouter.enable" 139 | Enter 140 | Ctrl+L 141 | Show 142 | 143 | Type 'CLI::UI::Spinner.spin("Building: {{@widget/status:1:2:3:4}}") { |spinner| sleep 3 }' 144 | Sleep .5 145 | Enter 146 | Sleep 5 147 | ``` 148 | 149 | ### Symbols 150 | 151 | 152 | ``` 153 | Output examples/cli-ui/symbols.gif 154 | 155 | Set FontSize 32 156 | 157 | Set Width 2200 158 | Set Height 400 159 | 160 | Hide 161 | Type "irb --noautocomplete" 162 | Enter 163 | Type "require 'cli/ui'" 164 | Enter 165 | Type "CLI::UI::StdoutRouter.enable" 166 | Enter 167 | Ctrl+L 168 | Show 169 | 170 | Type 'puts CLI::UI.fmt "{{*}} {{v}} {{?}} {{x}}"' 171 | Sleep .5 172 | Enter 173 | Sleep 5 174 | ``` 175 | 176 | ### Text Prompt 177 | 178 | 179 | ``` 180 | Output examples/cli-ui/text-prompt.ascii 181 | Output examples/cli-ui/text-prompt.gif 182 | 183 | Set FontSize 32 184 | 185 | Set Width 2200 186 | Set Height 500 187 | 188 | Hide 189 | Type "irb --noautocomplete" 190 | Enter 191 | Type "require 'cli/ui'" 192 | Enter 193 | Type "CLI::UI::StdoutRouter.enable" 194 | Enter 195 | Ctrl+L 196 | Show 197 | 198 | Type "CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')" 199 | Sleep .5 200 | Enter 201 | Sleep 1 202 | Type "I love it!" 203 | Sleep 1 204 | Enter 205 | Sleep 3 206 | ``` 207 | -------------------------------------------------------------------------------- /examples/cli-ui/format.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:df1c4e11cecfed1720183583cb56e97346621575b8cd8bf5a43aae1f98fe9702 3 | size 65749 4 | -------------------------------------------------------------------------------- /examples/cli-ui/format.tape: -------------------------------------------------------------------------------- 1 | Output examples/cli-ui/format.gif 2 | 3 | Set FontSize 32 4 | 5 | Set Width 2200 6 | Set Height 400 7 | 8 | Hide 9 | Type "irb --noautocomplete" 10 | Enter 11 | Type "require 'cli/ui'" 12 | Enter 13 | Type "CLI::UI::StdoutRouter.enable" 14 | Enter 15 | Ctrl+L 16 | Show 17 | 18 | Type 'puts CLI::UI.fmt "{{red:Red}} {{green:Green}}"' 19 | Sleep .5 20 | Enter 21 | Sleep 5 22 | -------------------------------------------------------------------------------- /examples/cli-ui/interactive-prompt.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:81f10376198fea0a21afe6a187013eb324009e694efd985bd76e57127ddcb392 3 | size 129696 4 | -------------------------------------------------------------------------------- /examples/cli-ui/interactive-prompt.tape: -------------------------------------------------------------------------------- 1 | Output examples/cli-ui/interactive-prompt.gif 2 | 3 | Set FontSize 32 4 | 5 | Set Width 2200 6 | Set Height 500 7 | 8 | Hide 9 | Type "irb --noautocomplete" 10 | Enter 11 | Type "require 'cli/ui'" 12 | Enter 13 | Type "CLI::UI::StdoutRouter.enable" 14 | Enter 15 | Ctrl+L 16 | Show 17 | 18 | Type "CLI::UI.ask('What do you use?', options: %w(rails go ruby python))" 19 | Sleep .5 20 | Enter 21 | Sleep 1 22 | 23 | Down@.5 3 24 | Sleep 1 25 | Up@.5 2 26 | Sleep 1 27 | 28 | Enter 29 | 30 | Sleep 3 31 | -------------------------------------------------------------------------------- /examples/cli-ui/nested-frames.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1c4037b7bc34c3ad836e9a8f4df65ea4620de21ed08372e4a1e639140023bb84 3 | size 166625 4 | -------------------------------------------------------------------------------- /examples/cli-ui/nested-frames.tape: -------------------------------------------------------------------------------- 1 | Output examples/cli-ui/nested-frames.gif 2 | 3 | Set FontSize 32 4 | 5 | Set Width 2000 6 | Set Height 750 7 | 8 | Hide 9 | Type "irb --noautocomplete" 10 | Enter 11 | Type "require 'cli/ui'" 12 | Enter 13 | Type "CLI::UI::StdoutRouter.enable" 14 | Enter 15 | Ctrl+L 16 | Show 17 | 18 | Type "CLI::UI::Frame.open('Frame 1') do" 19 | Enter 20 | Type " CLI::UI::Frame.open('Frame 2') { puts 'inside frame 2' }" 21 | Enter 22 | Type " puts 'inside frame 1'" 23 | Enter 24 | Type "end" 25 | 26 | Sleep 1 27 | 28 | Enter 29 | 30 | Sleep 3 31 | -------------------------------------------------------------------------------- /examples/cli-ui/progress.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ccbbb2aaaeef8ab06234c6681dbda1cab03be884d1c3a2be63acfb404e3969e5 3 | size 167860 4 | -------------------------------------------------------------------------------- /examples/cli-ui/progress.tape: -------------------------------------------------------------------------------- 1 | Output examples/cli-ui/progress.gif 2 | 3 | Set FontSize 32 4 | 5 | Set Width 2200 6 | Set Height 400 7 | 8 | Hide 9 | Type "irb --noautocomplete" 10 | Enter 11 | Type "require 'cli/ui'" 12 | Enter 13 | Type "CLI::UI::StdoutRouter.enable" 14 | Enter 15 | Ctrl+L 16 | Show 17 | 18 | Type "CLI::UI::Progress.progress { |bar| 100.times { sleep 0.02; bar.tick } }" 19 | Sleep .5 20 | Enter 21 | Sleep 5 22 | -------------------------------------------------------------------------------- /examples/cli-ui/spinner.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:49e8f39ef8573ac25b9b025a2465caaf607159c00e8adaa83a45c1b8d69e6a7f 3 | size 67734 4 | -------------------------------------------------------------------------------- /examples/cli-ui/spinner.tape: -------------------------------------------------------------------------------- 1 | Output examples/cli-ui/spinner.gif 2 | 3 | Set FontSize 32 4 | 5 | Set Width 2200 6 | Set Height 400 7 | 8 | Hide 9 | Type "irb --noautocomplete" 10 | Enter 11 | Type "require 'cli/ui'" 12 | Enter 13 | Type "CLI::UI::StdoutRouter.enable" 14 | Enter 15 | Ctrl+L 16 | Show 17 | 18 | Type "CLI::UI::Spinner.spin('Spinning...') { sleep 3 }" 19 | Sleep .5 20 | Enter 21 | Sleep 5 22 | -------------------------------------------------------------------------------- /examples/cli-ui/status-widget.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3c2f6609f862ec2568eaf4b5fafea77994e2fa717f3a691657a0ace6572f3589 3 | size 102494 4 | -------------------------------------------------------------------------------- /examples/cli-ui/status-widget.tape: -------------------------------------------------------------------------------- 1 | Output examples/cli-ui/status-widget.gif 2 | 3 | Set FontSize 32 4 | 5 | Set Width 2200 6 | Set Height 400 7 | 8 | Hide 9 | Type "irb --noautocomplete" 10 | Enter 11 | Type "require 'cli/ui'" 12 | Enter 13 | Type "CLI::UI::StdoutRouter.enable" 14 | Enter 15 | Ctrl+L 16 | Show 17 | 18 | Type 'CLI::UI::Spinner.spin("Building: {{@widget/status:1:2:3:4}}") { |spinner| sleep 3 }' 19 | Sleep .5 20 | Enter 21 | Sleep 5 22 | -------------------------------------------------------------------------------- /examples/cli-ui/symbols.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7dad62eb2fc634d4ae85de98e60c9c9a91504aedac923c9c785acfb5ae1a061f 3 | size 53043 4 | -------------------------------------------------------------------------------- /examples/cli-ui/symbols.tape: -------------------------------------------------------------------------------- 1 | Output examples/cli-ui/symbols.gif 2 | 3 | Set FontSize 32 4 | 5 | Set Width 2200 6 | Set Height 400 7 | 8 | Hide 9 | Type "irb --noautocomplete" 10 | Enter 11 | Type "require 'cli/ui'" 12 | Enter 13 | Type "CLI::UI::StdoutRouter.enable" 14 | Enter 15 | Ctrl+L 16 | Show 17 | 18 | Type 'puts CLI::UI.fmt "{{*}} {{v}} {{?}} {{x}}"' 19 | Sleep .5 20 | Enter 21 | Sleep 5 22 | -------------------------------------------------------------------------------- /examples/cli-ui/text-prompt.ascii: -------------------------------------------------------------------------------- 1 | irb(main):003:0> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ──────────────────────────────────────────────────────────────────────────────── 10 | irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!') 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ──────────────────────────────────────────────────────────────────────────────── 19 | irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!') 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ──────────────────────────────────────────────────────────────────────────────── 28 | irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!') 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ──────────────────────────────────────────────────────────────────────────────── 37 | irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!') 38 | ? Is CLI UI Awesome? (empty = It is great!) 39 | > 40 | 41 | 42 | 43 | 44 | 45 | ──────────────────────────────────────────────────────────────────────────────── 46 | irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!') 47 | ? Is CLI UI Awesome? (empty = It is great!) 48 | > I love it! 49 | 50 | 51 | 52 | 53 | 54 | ──────────────────────────────────────────────────────────────────────────────── 55 | irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!') 56 | ? Is CLI UI Awesome? (empty = It is great!) 57 | > I love it! 58 | 59 | 60 | 61 | 62 | 63 | ──────────────────────────────────────────────────────────────────────────────── 64 | irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!') 65 | ? Is CLI UI Awesome? (empty = It is great!) 66 | > I love it! 67 | => "I love it!" 68 | 69 | 70 | 71 | 72 | ──────────────────────────────────────────────────────────────────────────────── 73 | irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!') 74 | ? Is CLI UI Awesome? (empty = It is great!) 75 | > I love it! 76 | => "I love it!" 77 | irb(main):004:0> 78 | 79 | 80 | 81 | ──────────────────────────────────────────────────────────────────────────────── 82 | -------------------------------------------------------------------------------- /examples/cli-ui/text-prompt.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:81f8cf235f60981450234e3d46e4af6e3e9a9e09135d88491123ebb2a5206190 3 | size 80920 4 | -------------------------------------------------------------------------------- /examples/cli-ui/text-prompt.tape: -------------------------------------------------------------------------------- 1 | Output examples/cli-ui/text-prompt.ascii 2 | Output examples/cli-ui/text-prompt.gif 3 | 4 | Set FontSize 32 5 | 6 | Set Width 2200 7 | Set Height 500 8 | 9 | Hide 10 | Type "irb --noautocomplete" 11 | Enter 12 | Type "require 'cli/ui'" 13 | Enter 14 | Type "CLI::UI::StdoutRouter.enable" 15 | Enter 16 | Ctrl+L 17 | Show 18 | 19 | Type "CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')" 20 | Sleep .5 21 | Enter 22 | Sleep 1 23 | Type "I love it!" 24 | Sleep 1 25 | Enter 26 | Sleep 3 27 | -------------------------------------------------------------------------------- /examples/commands/README.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | ### Arrow 4 | 5 | 6 | 7 | ``` 8 | Output examples/commands/arrow.gif 9 | Set FontSize 42 10 | Set Height 225 11 | 12 | Type "Navigate around" 13 | Sleep .25 14 | Left 10 15 | Sleep 1 16 | Right@50ms 10 17 | Sleep 1 18 | ``` 19 | 20 | ### Backspace 21 | 22 | 23 | 24 | ``` 25 | Output examples/commands/backspace.gif 26 | Set FontSize 42 27 | Set Height 225 28 | 29 | Type@50ms "Delete anything..." 30 | Backspace 18 31 | Sleep 1 32 | ``` 33 | 34 | ### Comment 35 | 36 | 37 | 38 | ``` 39 | Output examples/commands/comment.gif 40 | Set Height 500 41 | Set Width 1000 42 | 43 | # This is a comment. 44 | # These are ignored by the parser so you can write whatever you want! 45 | 46 | # Quickly comment out a command you don't need. 47 | # Type "Hello, world!" 48 | 49 | Sleep 1 50 | ``` 51 | 52 | ### Ctrl 53 | 54 | 55 | 56 | ``` 57 | Output examples/commands/ctrl.gif 58 | Set FontSize 42 59 | Set Height 225 60 | 61 | Sleep 1 62 | Ctrl+R 63 | Sleep 1 64 | ``` 65 | 66 | ### Enter 67 | 68 | 69 | 70 | ``` 71 | Output examples/commands/enter.gif 72 | Set FontSize 42 73 | Set Height 350 74 | 75 | Sleep 1 76 | Enter@.5 2 77 | Sleep 1 78 | ``` 79 | 80 | ### Hide 81 | 82 | 83 | 84 | ``` 85 | Output examples/commands/hide.gif 86 | 87 | Set FontSize 42 88 | Set Height 300 89 | 90 | Hide 91 | Type "You won't see this being typed." Ctrl+C 92 | Show 93 | Type "You will see this being typed." 94 | 95 | Sleep 2 96 | ``` 97 | 98 | ### Show 99 | 100 | 101 | 102 | ``` 103 | Output examples/commands/show.gif 104 | 105 | Hide 106 | Type "export HIDDEN=wow" 107 | Enter 108 | Ctrl+L 109 | Show 110 | 111 | Type "echo $HIDDEN" 112 | Enter 113 | Sleep 1 114 | ``` 115 | 116 | ### Space 117 | 118 | 119 | 120 | ``` 121 | Output examples/commands/space.gif 122 | Set FontSize 42 123 | Set Height 225 124 | 125 | Sleep .25 126 | Space 10 127 | Sleep 1 128 | ``` 129 | 130 | ### Tab 131 | 132 | 133 | 134 | ``` 135 | Output examples/commands/tab.gif 136 | Set FontSize 42 137 | Set Height 300 138 | 139 | Type "cd ." 140 | Sleep 0.5s 141 | Tab@0.5s 2 142 | Sleep 1s 143 | ``` 144 | 145 | ### Type 146 | 147 | 148 | 149 | ``` 150 | Output examples/commands/type.gif 151 | Set FontSize 42 152 | Set Height 225 153 | 154 | Sleep 1 155 | 156 | # Type something 157 | Type "Whatever you want" 158 | 159 | Sleep 1 Ctrl+U Sleep 1 160 | 161 | # Type something really slowly! 162 | Type@500ms "Slow down there, partner." 163 | 164 | Sleep 1 165 | ``` 166 | 167 | -------------------------------------------------------------------------------- /examples/commands/alt.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/alt.gif 2 | Set FontSize 42 3 | Set Height 225 4 | 5 | Sleep 1 6 | 7 | Alt+. 8 | 9 | Sleep 1 10 | -------------------------------------------------------------------------------- /examples/commands/arrow.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2394db57de49437b3fb95104af4184b32ffa6898e202debad86417291abcf3fa 3 | size 38102 4 | -------------------------------------------------------------------------------- /examples/commands/arrow.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/arrow.gif 2 | Set FontSize 42 3 | Set Height 225 4 | 5 | Type "Navigate around" 6 | Sleep .25 7 | Left 10 8 | Sleep 1 9 | Right@50ms 10 10 | Sleep 1 11 | -------------------------------------------------------------------------------- /examples/commands/backspace.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:17c62b33ba2677c8cd497d324aee4c55acbb8f3a8088fb1699a8ac5c7ad50b3c 3 | size 24533 4 | -------------------------------------------------------------------------------- /examples/commands/backspace.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/backspace.gif 2 | Set FontSize 42 3 | Set Height 225 4 | 5 | Type@50ms "Delete anything..." 6 | Backspace 18 7 | Sleep 1 8 | -------------------------------------------------------------------------------- /examples/commands/clipboard.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c2cbbaf7fd904e0f9868b899ecc836801f0642fda1a0c08e71913afe4c433fca 3 | size 22729 4 | -------------------------------------------------------------------------------- /examples/commands/clipboard.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/clipboard.gif 2 | 3 | Set FontSize 42 4 | Set Height 225 5 | 6 | Copy "https://github.com/charmbracelet" 7 | Type "open " 8 | Sleep 500ms 9 | Paste 10 | Sleep 3s 11 | -------------------------------------------------------------------------------- /examples/commands/comment.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/comment.gif 2 | Set Height 500 3 | Set Width 1000 4 | 5 | # This is a comment. 6 | # These are ignored by the parser so you can write whatever you want! 7 | 8 | # Quickly comment out a command you don't need. 9 | # Type "Hello, world!" 10 | 11 | Sleep 1 12 | -------------------------------------------------------------------------------- /examples/commands/ctrl.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a2ac402a5a303109e2ece6de18c7274736c8eb1dd6888e834f3d9369114b399f 3 | size 13756 4 | -------------------------------------------------------------------------------- /examples/commands/ctrl.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/ctrl.gif 2 | Set FontSize 42 3 | Set Height 225 4 | 5 | Sleep 1 6 | 7 | Ctrl+R 8 | 9 | Sleep 1 10 | -------------------------------------------------------------------------------- /examples/commands/enter.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e108c46c58fdd509a050766dc30b232137955b9aa67db98901a3f1fc34f85759 3 | size 9435 4 | -------------------------------------------------------------------------------- /examples/commands/enter.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/enter.gif 2 | Set FontSize 42 3 | Set Height 350 4 | 5 | Sleep 1 6 | Enter@.5 2 7 | Sleep 1 8 | -------------------------------------------------------------------------------- /examples/commands/hide.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f3f833269b2734860b09fcb6e4941cf8656b64a70d8390affc1b2c773804cbfe 3 | size 40437 4 | -------------------------------------------------------------------------------- /examples/commands/hide.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/hide.gif 2 | 3 | Set FontSize 42 4 | Set Height 300 5 | 6 | Hide 7 | Type "You won't see this being typed." Ctrl+C 8 | Show 9 | Type "You will see this being typed." 10 | 11 | Sleep 2 12 | -------------------------------------------------------------------------------- /examples/commands/show.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/show.gif 2 | 3 | Hide 4 | Type "export HIDDEN=wow" 5 | Enter 6 | Ctrl+L 7 | Show 8 | 9 | Type "echo $HIDDEN" 10 | Enter 11 | Sleep 1 12 | -------------------------------------------------------------------------------- /examples/commands/space.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5445a91e8a81776fc3c4e7ba868c6bf68c527eef13590b67449e22aad60086aa 3 | size 8581 4 | -------------------------------------------------------------------------------- /examples/commands/space.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/space.gif 2 | Set FontSize 42 3 | Set Height 225 4 | 5 | Sleep .25 6 | Space 10 7 | Sleep 1 8 | -------------------------------------------------------------------------------- /examples/commands/tab.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:53972ff61b6b6153f5c348afd89571d2af4dae679dcb20716eef83f419594667 3 | size 22905 4 | -------------------------------------------------------------------------------- /examples/commands/tab.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/tab.gif 2 | Set FontSize 42 3 | Set Height 300 4 | 5 | Type "cd ." 6 | Sleep 0.5s 7 | Tab@0.5s 2 8 | Shift+Tab 9 | Sleep 1s 10 | -------------------------------------------------------------------------------- /examples/commands/type.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f53f41ae3db24430f4b49e295f1450afd632e2fe701d7ca8dbd043a5cc28e889 3 | size 54548 4 | -------------------------------------------------------------------------------- /examples/commands/type.tape: -------------------------------------------------------------------------------- 1 | Output examples/commands/type.gif 2 | Set FontSize 42 3 | Set Height 225 4 | 5 | Sleep 1 6 | 7 | # Type something 8 | Type "Whatever you want" 9 | 10 | Sleep 1 Ctrl+U Sleep 1 11 | 12 | # Type something really slowly! 13 | Type@500ms "Slow down there, partner." 14 | 15 | Sleep 1 16 | -------------------------------------------------------------------------------- /examples/decorations/decorations.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e6d9b1622c4690d736db3bd8fc408f6c5872823ebc6073e76dfac7c3fbf13bba 3 | size 22902 4 | -------------------------------------------------------------------------------- /examples/decorations/decorations.tape: -------------------------------------------------------------------------------- 1 | Output examples/decorations/decorations.gif 2 | 3 | Set FontSize 28 4 | Set Width 1200 5 | Set Height 800 6 | Set Padding 30 7 | 8 | Set Margin 80 9 | Set MarginFill "#674EFF" 10 | Set WindowBar Colorful 11 | Set WindowBarSize 40 12 | Set BorderRadius 8 13 | 14 | Type "I can't believe it's not butter." 15 | Sleep 2s -------------------------------------------------------------------------------- /examples/demo.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:80bae084569563bb4c280062f0f64903b7b45c187cd7a6f2d56d3be62360c47f 3 | size 25403 4 | -------------------------------------------------------------------------------- /examples/demo.mp4: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:fa2342eb4d9faaed26122eb8bf55c014ee35de3ef8730193e08ae89be1080cd9 3 | size 16531 4 | -------------------------------------------------------------------------------- /examples/demo.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:dba734975fb549ff46ed4329bae2e551b2560a708dce6273b654ffc9da4266f5 3 | size 97185 4 | -------------------------------------------------------------------------------- /examples/demo.tape: -------------------------------------------------------------------------------- 1 | # VHS documentation 2 | # 3 | # Output: 4 | # Output .gif Create a GIF output at the given 5 | # Output .mp4 Create an MP4 output at the given 6 | # Output .webm Create a WebM output at the given 7 | # 8 | # Require: 9 | # Require Ensure a program is on the $PATH to proceed 10 | # 11 | # Settings: 12 | # Set FontSize Set the font size of the terminal 13 | # Set FontFamily Set the font family of the terminal 14 | # Set Height Set the height of the terminal 15 | # Set Width Set the width of the terminal 16 | # Set LetterSpacing Set the font letter spacing (tracking) 17 | # Set LineHeight Set the font line height 18 | # Set LoopOffset % Set the starting frame offset for the GIF loop 19 | # Set Theme Set the theme of the terminal 20 | # Set Padding Set the padding of the terminal 21 | # Set Framerate Set the framerate of the recording 22 | # Set PlaybackSpeed Set the playback speed of the recording 23 | # Set MarginFill Set the file or color the margin will be filled with. 24 | # Set Margin Set the size of the margin. Has no effect if MarginFill isn't set. 25 | # Set BorderRadius Set terminal border radius, in pixels. 26 | # Set WindowBar Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight) 27 | # Set WindowBarSize Set window bar size, in pixels. Default is 40. 28 | # Set TypingSpeed