[
  {
    "path": ".github/CODEOWNERS",
    "content": "*  @charmbracelet/everyone\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\n\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"05:00\"\n      timezone: \"America/New_York\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    groups:\n      all:\n        patterns:\n          - \"*\"\n    ignore:\n      - dependency-name: github.com/charmbracelet/bubbletea/v2\n        versions:\n          - v2.0.0-beta1\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"05:00\"\n      timezone: \"America/New_York\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    groups:\n      all:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: \"docker\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"05:00\"\n      timezone: \"America/New_York\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    groups:\n      all:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "Fixes #...\n\n### Changes\n- \n- \n-\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  build:\n    uses: charmbracelet/meta/.github/workflows/build.yml@main\n    secrets:\n      gh_pat: ${{ secrets.PERSONAL_ACCESS_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/dependabot-sync.yml",
    "content": "name: dependabot-sync\non:\n  schedule:\n    - cron: \"0 0 * * 0\" # every Sunday at midnight\n  workflow_dispatch: # allows manual triggering\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  dependabot-sync:\n    uses: charmbracelet/meta/.github/workflows/dependabot-sync.yml@main\n    with:\n      repo_name: ${{ github.event.repository.name }}\n    secrets:\n      gh_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/goreleaser.yml",
    "content": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n\nname: goreleaser\n\non:\n  push:\n    tags:\n      - v*.*.*\n\nconcurrency:\n  group: goreleaser\n  cancel-in-progress: true\n\njobs:\n  goreleaser:\n    uses: charmbracelet/meta/.github/workflows/goreleaser.yml@main\n    secrets:\n      docker_username: ${{ secrets.DOCKERHUB_USERNAME }}\n      docker_token: ${{ secrets.DOCKERHUB_TOKEN }}\n      gh_pat: ${{ secrets.PERSONAL_ACCESS_TOKEN }}\n      goreleaser_key: ${{ secrets.GORELEASER_KEY }}\n      fury_token: ${{ secrets.FURY_TOKEN }}\n      nfpm_gpg_key: ${{ secrets.NFPM_GPG_KEY }}\n      nfpm_passphrase: ${{ secrets.NFPM_PASSPHRASE }}\n      macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }}\n      macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }}\n      macos_notary_issuer_id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}\n      macos_notary_key_id: ${{ secrets.MACOS_NOTARY_KEY_ID }}\n      macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }}\n"
  },
  {
    "path": ".github/workflows/lint-sync.yml",
    "content": "name: lint-sync\non:\n  schedule:\n    # every Sunday at midnight\n    - cron: \"0 0 * * 0\"\n  workflow_dispatch: # allows manual triggering\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  lint:\n    uses: charmbracelet/meta/.github/workflows/lint-sync.yml@main\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: lint\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    uses: charmbracelet/meta/.github/workflows/lint.yml@main\n"
  },
  {
    "path": ".github/workflows/nightly.yml",
    "content": "name: nightly\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  nightly:\n    uses: charmbracelet/meta/.github/workflows/nightly.yml@main\n    secrets:\n      docker_username: ${{ secrets.DOCKERHUB_USERNAME }}\n      docker_token: ${{ secrets.DOCKERHUB_TOKEN }}\n      goreleaser_key: ${{ secrets.GORELEASER_KEY }}\n      macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }}\n      macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }}\n      macos_notary_issuer_id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}\n      macos_notary_key_id: ${{ secrets.MACOS_NOTARY_KEY_ID }}\n      macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Files\ntest\n.DS_Store\n\n# Binaries\ngum\ndist\ntestdata\n\n# Folders\ncompletions/\nmanpages/\n\n# nix\nresult\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  tests: false\nlinters:\n  enable:\n    - bodyclose\n    - exhaustive\n    - goconst\n    - godot\n    - gomoddirectives\n    - goprintffuncname\n    - gosec\n    - misspell\n    - nakedret\n    - nestif\n    - nilerr\n    - noctx\n    - nolintlint\n    - prealloc\n    - revive\n    - rowserrcheck\n    - sqlclosecheck\n    - tparallel\n    - unconvert\n    - unparam\n    - whitespace\n    - wrapcheck\n  exclusions:\n    rules:\n      - text: '(slog|log)\\.\\w+'\n        linters:\n          - noctx\n    generated: lax\n    presets:\n      - common-false-positives\n  settings:\n    exhaustive:\n      default-signifies-exhaustive: true\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\nformatters:\n  enable:\n    - gofumpt\n    - goimports\n  exclusions:\n    generated: lax\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json\n\nversion: 2\n\nincludes:\n  - from_url:\n      url: charmbracelet/meta/main/goreleaser-full.yaml\n\nvariables:\n  main: \".\"\n  scoop_name: charm-gum\n  description: \"A tool for glamorous shell scripts\"\n  github_url: \"https://github.com/charmbracelet/gum\"\n  maintainer: \"Maas Lalani <maas@charm.sh>\"\n  brew_commit_author_name: \"Maas Lalani\"\n  brew_commit_author_email: \"maas@charm.sh\"\n\nmilestones:\n  - close: true\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM gcr.io/distroless/static\nCOPY gum /usr/local/bin/gum\nENTRYPOINT [ \"/usr/local/bin/gum\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022-2024 Charmbracelet, Inc\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Gum\n\n<p>\n    <a href=\"https://stuff.charm.sh/gum/nutritional-information.png\" target=\"_blank\"><img src=\"https://stuff.charm.sh/gum/gum.png\" alt=\"Gum Image\" width=\"450\" /></a>\n    <br><br>\n    <a href=\"https://github.com/charmbracelet/gum/releases\"><img src=\"https://img.shields.io/github/release/charmbracelet/gum.svg\" alt=\"Latest Release\"></a>\n    <a href=\"https://pkg.go.dev/github.com/charmbracelet/gum?tab=doc\"><img src=\"https://godoc.org/github.com/golang/gddo?status.svg\" alt=\"Go Docs\"></a>\n    <a href=\"https://github.com/charmbracelet/gum/actions\"><img src=\"https://github.com/charmbracelet/gum/workflows/build/badge.svg\" alt=\"Build Status\"></a>\n</p>\n\nA tool for glamorous shell scripts. Leverage the power of\n[Bubbles](https://github.com/charmbracelet/bubbles) and [Lip\nGloss](https://github.com/charmbracelet/lipgloss) in your scripts and aliases\nwithout writing any Go code!\n\n<img alt=\"Shell running the ./demo.sh script\" width=\"600\" src=\"https://vhs.charm.sh/vhs-1qY57RrQlXCuydsEgDp68G.gif\">\n\nThe above example is running from a single shell script ([source](./examples/demo.sh)).\n\n## Tutorial\n\nGum provides highly configurable, ready-to-use utilities to help you write\nuseful shell scripts and dotfile aliases with just a few lines of code.\nLet's build a simple script to help you write\n[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary)\nfor your dotfiles.\n\nAsk for the commit type with gum choose:\n\n```bash\ngum choose \"fix\" \"feat\" \"docs\" \"style\" \"refactor\" \"test\" \"chore\" \"revert\"\n```\n\n> [!NOTE]\n> This command itself will print to stdout which is not all that useful. To make use of the command later on you can save the stdout to a `$VARIABLE` or `file.txt`.\n\nPrompt for the scope of these changes:\n\n```bash\ngum input --placeholder \"scope\"\n```\n\nPrompt for the summary and description of changes:\n\n```bash\ngum input --value \"$TYPE$SCOPE: \" --placeholder \"Summary of this change\"\ngum write --placeholder \"Details of this change\"\n```\n\nConfirm before committing:\n\n```bash\ngum confirm \"Commit changes?\" && git commit -m \"$SUMMARY\" -m \"$DESCRIPTION\"\n```\n\nCheck out the [complete example](https://github.com/charmbracelet/gum/blob/main/examples/commit.sh) for combining these commands in a single script.\n\n<img alt=\"Running the ./examples/commit.sh script to commit to git\" width=\"600\" src=\"https://vhs.charm.sh/vhs-7rRq3LsEuJVwhwr0xf6Er7.gif\">\n\n## Installation\n\nUse a package manager:\n\n```bash\n# macOS or Linux\nbrew install gum\n\n# Arch Linux (btw)\npacman -S gum\n\n# Fedora or EPEL 10\ndnf install gum\n\n# Nix\nnix-env -iA nixpkgs.gum\n\n# Flox\nflox install gum\n\n# Windows (via WinGet or Scoop)\nwinget install charmbracelet.gum\nscoop install charm-gum\n```\n\n<details>\n<summary>Debian/Ubuntu</summary>\n\n```bash\nsudo mkdir -p /etc/apt/keyrings\ncurl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg\necho \"deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *\" | sudo tee /etc/apt/sources.list.d/charm.list\nsudo apt update && sudo apt install gum\n```\n\n</details>\n\n<details>\n<summary>Fedora/RHEL/OpenSuse</summary>\n\n```bash\necho '[charm]\nname=Charm\nbaseurl=https://repo.charm.sh/yum/\nenabled=1\ngpgcheck=1\ngpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo\nsudo rpm --import https://repo.charm.sh/yum/gpg.key\n\n# yum\nsudo yum install gum\n\n# zypper\nsudo zypper refresh\nsudo zypper install gum\n```\n\n</details>\n\n<details>\n<summary>FreeBSD</summary>\n\n```bash\n# packages\nsudo pkg install gum\n\n# ports\ncd /usr/ports/devel/gum && sudo make install clean\n```\n\n</details>\n\nOr download it:\n\n- [Packages][releases] are available in Debian, RPM, and Alpine formats\n- [Binaries][releases] are available for Linux, macOS, Windows, FreeBSD, OpenBSD, and NetBSD\n\nOr just install it with `go`:\n\n```bash\ngo install github.com/charmbracelet/gum@latest\n```\n\n[releases]: https://github.com/charmbracelet/gum/releases\n\n## Commands\n\n- [`choose`](#choose): Choose an option from a list of choices\n- [`confirm`](#confirm): Ask a user to confirm an action\n- [`file`](#file): Pick a file from a folder\n- [`filter`](#filter): Filter items from a list\n- [`format`](#format): Format a string using a template\n- [`input`](#input): Prompt for some input\n- [`join`](#join): Join text vertically or horizontally\n- [`pager`](#pager): Scroll through a file\n- [`spin`](#spin): Display spinner while running a command\n- [`style`](#style): Apply coloring, borders, spacing to text\n- [`table`](#table): Render a table of data\n- [`write`](#write): Prompt for long-form text\n- [`log`](#log): Log messages to output\n\n## Customization\n\nYou can customize `gum` options and styles with `--flags` and `$ENVIRONMENT_VARIABLES`.\nSee `gum <command> --help` for a full view of each command's customization and configuration options.\n\nCustomize with `--flags`:\n\n```bash\n\ngum input --cursor.foreground \"#FF0\" \\\n          --prompt.foreground \"#0FF\" \\\n          --placeholder \"What's up?\" \\\n          --prompt \"* \" \\\n          --width 80 \\\n          --value \"Not much, hby?\"\n```\n\nCustomize with `ENVIRONMENT_VARIABLES`:\n\n```bash\nexport GUM_INPUT_CURSOR_FOREGROUND=\"#FF0\"\nexport GUM_INPUT_PROMPT_FOREGROUND=\"#0FF\"\nexport GUM_INPUT_PLACEHOLDER=\"What's up?\"\nexport GUM_INPUT_PROMPT=\"* \"\nexport GUM_INPUT_WIDTH=80\n\n# --flags can override values set with environment\ngum input\n```\n\n<img alt=\"Gum input displaying most customization options\" width=\"600\" src=\"https://vhs.charm.sh/vhs-5zb9DlQYA70aL9ZpYLTwKv.gif\">\n\n## Input\n\nPrompt for input with a simple command.\n\n```bash\ngum input > answer.txt\ngum input --password > password.txt\n```\n\n<img src=\"https://vhs.charm.sh/vhs-1nScrStFI3BMlCp5yrLtyg.gif\" width=\"600\" alt=\"Shell running gum input typing Not much, you?\" />\n\n## Write\n\nPrompt for some multi-line text (`ctrl+d` to complete text entry).\n\n```bash\ngum write > story.txt\n```\n\n<img src=\"https://vhs.charm.sh/vhs-7abdKKrUEukgx9aJj8O5GX.gif\" width=\"600\" alt=\"Shell running gum write typing a story\" />\n\n## Filter\n\nFilter a list of values with fuzzy matching:\n\n```bash\necho Strawberry >> flavors.txt\necho Banana >> flavors.txt\necho Cherry >> flavors.txt\ngum filter < flavors.txt > selection.txt\n```\n\n<img src=\"https://vhs.charm.sh/vhs-61euOQtKPtQVD7nDpHQhzr.gif\" width=\"600\" alt=\"Shell running gum filter on different bubble gum flavors\" />\n\nSelect multiple options with the `--limit` flag or `--no-limit` flag. Use `tab` or `ctrl+space` to select, `enter` to confirm.\n\n```bash\ncat flavors.txt | gum filter --limit 2\ncat flavors.txt | gum filter --no-limit\n```\n\n## Choose\n\nChoose an option from a list of choices.\n\n```bash\necho \"Pick a card, any card...\"\nCARD=$(gum choose --height 15 {{A,K,Q,J},{10..2}}\" \"{♠,♥,♣,♦})\necho \"Was your card the $CARD?\"\n```\n\nYou can also select multiple items with the `--limit` or `--no-limit` flag, which determines\nthe maximum of items that can be chosen.\n\n```bash\ncat songs.txt | gum choose --limit 5\ncat foods.txt | gum choose --no-limit --header \"Grocery Shopping\"\n```\n\n<img src=\"https://vhs.charm.sh/vhs-3zV1LvofA6Cbn5vBu1NHHl.gif\" width=\"600\" alt=\"Shell running gum choose with numbers and gum flavors\" />\n\n## Confirm\n\nConfirm whether to perform an action. Exits with code `0` (affirmative) or `1`\n(negative) depending on selection.\n\n```bash\ngum confirm && rm file.txt || echo \"File not removed\"\n```\n\n<img src=\"https://vhs.charm.sh/vhs-3xRFvbeQ4lqGerbHY7y3q2.gif\" width=\"600\" alt=\"Shell running gum confirm\" />\n\n## File\n\nPrompt the user to select a file from the file tree.\n\n```bash\n$EDITOR $(gum file $HOME)\n```\n\n<img src=\"https://vhs.charm.sh/vhs-2RMRqmnOPneneIgVJJ3mI1.gif\" width=\"600\" alt=\"Shell running gum file\" />\n\n## Pager\n\nScroll through a long document with line numbers and a fully customizable viewport.\n\n```bash\ngum pager < README.md\n```\n\n<img src=\"https://vhs.charm.sh/vhs-3iMDpgOLmbYr0jrYEGbk7p.gif\" width=\"600\" alt=\"Shell running gum pager\" />\n\n## Spin\n\nDisplay a spinner while running a script or command. The spinner will\nautomatically stop after the given command exits.\n\nTo view or pipe the command's output, use the `--show-output` flag.\n\n```bash\ngum spin --spinner dot --title \"Buying Bubble Gum...\" -- sleep 5\n```\n\n<img src=\"https://vhs.charm.sh/vhs-3YFswCmoY4o3Q7MyzWl6sS.gif\" width=\"600\" alt=\"Shell running gum spin while sleeping for 5 seconds\" />\n\nAvailable spinner types include: `line`, `dot`, `minidot`, `jump`, `pulse`, `points`, `globe`, `moon`, `monkey`, `meter`, `hamburger`.\n\n## Table\n\nSelect a row from some tabular data.\n\n```bash\ngum table < flavors.csv | cut -d ',' -f 1\n```\n\n<!-- <img src=\"https://stuff.charm.sh/gum/table.gif\" width=\"600\" alt=\"Shell running gum table\" /> -->\n\n## Style\n\nPretty print any string with any layout with one command.\n\n```bash\ngum style \\\n\t--foreground 212 --border-foreground 212 --border double \\\n\t--align center --width 50 --margin \"1 2\" --padding \"2 4\" \\\n\t'Bubble Gum (1¢)' 'So sweet and so fresh!'\n```\n\n<img src=\"https://github.com/charmbracelet/gum/assets/42545625/67468acf-b3e0-4e78-bd89-360739eb44fa\" width=\"600\" alt=\"Bubble Gum, So sweet and so fresh!\" />\n\n## Join\n\nCombine text vertically or horizontally. Use this command with `gum style` to\nbuild layouts and pretty output.\n\nTip: Always wrap the output of `gum style` in quotes to preserve newlines\n(`\\n`) when using it as an argument in the `join` command.\n\n```bash\nI=$(gum style --padding \"1 5\" --border double --border-foreground 212 \"I\")\nLOVE=$(gum style --padding \"1 4\" --border double --border-foreground 57 \"LOVE\")\nBUBBLE=$(gum style --padding \"1 8\" --border double --border-foreground 255 \"Bubble\")\nGUM=$(gum style --padding \"1 5\" --border double --border-foreground 240 \"Gum\")\n\nI_LOVE=$(gum join \"$I\" \"$LOVE\")\nBUBBLE_GUM=$(gum join \"$BUBBLE\" \"$GUM\")\ngum join --align center --vertical \"$I_LOVE\" \"$BUBBLE_GUM\"\n```\n\n<img src=\"https://github.com/charmbracelet/gum/assets/42545625/68f7a25d-b495-48dd-982a-cee0c8ea5786\" width=\"600\" alt=\"I LOVE Bubble Gum written out in four boxes with double borders around them.\" />\n\n## Format\n\n`format` processes and formats bodies of text. `gum format` can parse markdown,\ntemplate strings, and named emojis.\n\n```bash\n# Format some markdown\ngum format -- \"# Gum Formats\" \"- Markdown\" \"- Code\" \"- Template\" \"- Emoji\"\necho \"# Gum Formats\\n- Markdown\\n- Code\\n- Template\\n- Emoji\" | gum format\n\n# Syntax highlight some code\ncat main.go | gum format -t code\n\n# Render text any way you want with templates\necho '{{ Bold \"Tasty\" }} {{ Italic \"Bubble\" }} {{ Color \"99\" \"0\" \" Gum \" }}' \\\n    | gum format -t template\n\n# Display your favorite emojis!\necho 'I :heart: Bubble Gum :candy:' | gum format -t emoji\n```\n\nFor more information on template helpers, see the [Termenv\ndocs](https://github.com/muesli/termenv#template-helpers). For a full list of\nnamed emojis see the [GitHub API](https://api.github.com/emojis).\n\n<img src=\"https://github.com/charmbracelet/gum/assets/42545625/5cfbb0c8-0022-460d-841b-fec37527ca66\" width=\"300\" alt=\"Running gum format for different types of formats\" />\n\n## Log\n\n`log` logs messages to the terminal at using different levels and styling using\nthe [`charmbracelet/log`](https://github.com/charmbracelet/log) library.\n\n```bash\n# Log some debug information.\ngum log --structured --level debug \"Creating file...\" name file.txt\n# DEBUG Unable to create file. name=temp.txt\n\n# Log some error.\ngum log --structured --level error \"Unable to create file.\" name file.txt\n# ERROR Unable to create file. name=temp.txt\n\n# Include a timestamp.\ngum log --time rfc822 --level error \"Unable to create file.\"\n```\n\nSee the Go [`time` package](https://pkg.go.dev/time#pkg-constants) for acceptable `--time` formats.\n\nSee [`charmbracelet/log`](https://github.com/charmbracelet/log) for more usage.\n\n<img src=\"https://vhs.charm.sh/vhs-6jupuFM0s2fXiUrBE0I1vU.gif\" width=\"600\" alt=\"Running gum log with debug and error levels\" />\n\n## Examples\n\nHow to use `gum` in your daily workflows:\n\nSee the [examples](./examples/) directory for more real world use cases.\n\n- Write a commit message:\n\n```bash\ngit commit -m \"$(gum input --width 50 --placeholder \"Summary of changes\")\" \\\n           -m \"$(gum write --width 80 --placeholder \"Details of changes\")\"\n```\n\n- Open files in your `$EDITOR`\n\n```bash\n$EDITOR $(gum filter)\n```\n\n- Connect to a `tmux` session\n\n```bash\nSESSION=$(tmux list-sessions -F \\#S | gum filter --placeholder \"Pick session...\")\ntmux switch-client -t \"$SESSION\" || tmux attach -t \"$SESSION\"\n```\n\n- Pick a commit hash from `git` history\n\n```bash\ngit log --oneline | gum filter | cut -d' ' -f1 # | copy\n```\n\n- Simple [`skate`](https://github.com/charmbracelet/skate) password selector.\n\n```\nskate list -k | gum filter | xargs skate get\n```\n\n- Uninstall packages\n\n```bash\nbrew list | gum choose --no-limit | xargs brew uninstall\n```\n\n- Clean up `git` branches\n\n```bash\ngit branch | cut -c 3- | gum choose --no-limit | xargs git branch -D\n```\n\n- Checkout GitHub pull requests with [`gh`](https://cli.github.com/)\n\n```bash\ngh pr list | cut -f1,2 | gum choose | cut -f1 | xargs gh pr checkout\n```\n\n- Copy command from shell history\n\n```bash\ngum filter < $HISTFILE --height 20\n```\n\n- `sudo` replacement\n\n```bash\nalias please=\"gum input --password | sudo -nS\"\n```\n\n## Contributing\n\nSee [contributing][contribute].\n\n[contribute]: https://github.com/charmbracelet/gum/contribute\n\n## Feedback\n\nWe’d love to hear your thoughts on this project. Feel free to drop us a note!\n\n- [Twitter](https://twitter.com/charmcli)\n- [The Fediverse](https://mastodon.social/@charmcli)\n- [Discord](https://charm.sh/chat)\n\n## License\n\n[MIT](https://github.com/charmbracelet/gum/raw/main/LICENSE)\n\n---\n\nPart of [Charm](https://charm.sh).\n\n<a href=\"https://charm.sh/\"><img alt=\"The Charm logo\" src=\"https://stuff.charm.sh/charm-badge.jpg\" width=\"400\" /></a>\n\nCharm热爱开源 • Charm loves open source\n"
  },
  {
    "path": "choose/choose.go",
    "content": "// Package choose provides an interface to choose one option from a given list\n// of options. The options can be provided as (new-line separated) stdin or a\n// list of arguments.\n//\n// It is different from the filter command as it does not provide a fuzzy\n// finding input, so it is best used for smaller lists of options.\n//\n// Let's pick from a list of gum flavors:\n//\n// $ gum choose \"Strawberry\" \"Banana\" \"Cherry\"\npackage choose\n\nimport (\n\t\"strings\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/charmbracelet/bubbles/paginator\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/charmbracelet/x/exp/ordered\"\n)\n\nfunc defaultKeymap() keymap {\n\treturn keymap{\n\t\tDown: key.NewBinding(\n\t\t\tkey.WithKeys(\"down\", \"j\", \"ctrl+j\", \"ctrl+n\"),\n\t\t),\n\t\tUp: key.NewBinding(\n\t\t\tkey.WithKeys(\"up\", \"k\", \"ctrl+k\", \"ctrl+p\"),\n\t\t),\n\t\tRight: key.NewBinding(\n\t\t\tkey.WithKeys(\"right\", \"l\", \"ctrl+f\"),\n\t\t),\n\t\tLeft: key.NewBinding(\n\t\t\tkey.WithKeys(\"left\", \"h\", \"ctrl+b\"),\n\t\t),\n\t\tHome: key.NewBinding(\n\t\t\tkey.WithKeys(\"g\", \"home\"),\n\t\t),\n\t\tEnd: key.NewBinding(\n\t\t\tkey.WithKeys(\"G\", \"end\"),\n\t\t),\n\t\tToggleAll: key.NewBinding(\n\t\t\tkey.WithKeys(\"a\", \"A\", \"ctrl+a\"),\n\t\t\tkey.WithHelp(\"ctrl+a\", \"select all\"),\n\t\t\tkey.WithDisabled(),\n\t\t),\n\t\tToggle: key.NewBinding(\n\t\t\tkey.WithKeys(\" \", \"tab\", \"x\", \"ctrl+@\"),\n\t\t\tkey.WithHelp(\"x\", \"toggle\"),\n\t\t\tkey.WithDisabled(),\n\t\t),\n\t\tAbort: key.NewBinding(\n\t\t\tkey.WithKeys(\"ctrl+c\"),\n\t\t\tkey.WithHelp(\"ctrl+c\", \"abort\"),\n\t\t),\n\t\tQuit: key.NewBinding(\n\t\t\tkey.WithKeys(\"esc\"),\n\t\t\tkey.WithHelp(\"esc\", \"quit\"),\n\t\t),\n\t\tSubmit: key.NewBinding(\n\t\t\tkey.WithKeys(\"enter\", \"ctrl+q\"),\n\t\t\tkey.WithHelp(\"enter\", \"submit\"),\n\t\t),\n\t}\n}\n\ntype keymap struct {\n\tDown,\n\tUp,\n\tRight,\n\tLeft,\n\tHome,\n\tEnd,\n\tToggleAll,\n\tToggle,\n\tAbort,\n\tQuit,\n\tSubmit key.Binding\n}\n\n// FullHelp implements help.KeyMap.\nfunc (k keymap) FullHelp() [][]key.Binding { return nil }\n\n// ShortHelp implements help.KeyMap.\nfunc (k keymap) ShortHelp() []key.Binding {\n\treturn []key.Binding{\n\t\tk.Toggle,\n\t\tkey.NewBinding(\n\t\t\tkey.WithKeys(\"up\", \"down\", \"right\", \"left\"),\n\t\t\tkey.WithHelp(\"←↓↑→\", \"navigate\"),\n\t\t),\n\t\tk.Submit,\n\t\tk.ToggleAll,\n\t}\n}\n\ntype model struct {\n\theight           int\n\tpadding          []int\n\tcursor           string\n\tselectedPrefix   string\n\tunselectedPrefix string\n\tcursorPrefix     string\n\theader           string\n\titems            []item\n\tquitting         bool\n\tsubmitted        bool\n\tindex            int\n\tlimit            int\n\tnumSelected      int\n\tcurrentOrder     int\n\tpaginator        paginator.Model\n\tshowHelp         bool\n\thelp             help.Model\n\tkeymap           keymap\n\n\t// styles\n\tcursorStyle       lipgloss.Style\n\theaderStyle       lipgloss.Style\n\titemStyle         lipgloss.Style\n\tselectedItemStyle lipgloss.Style\n}\n\ntype item struct {\n\ttext     string\n\tselected bool\n\torder    int\n}\n\nfunc (m model) Init() tea.Cmd { return nil }\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\treturn m, nil\n\n\tcase tea.KeyMsg:\n\t\tstart, end := m.paginator.GetSliceBounds(len(m.items))\n\t\tkm := m.keymap\n\t\tswitch {\n\t\tcase key.Matches(msg, km.Down):\n\t\t\tm.index++\n\t\t\tif m.index >= len(m.items) {\n\t\t\t\tm.index = 0\n\t\t\t\tm.paginator.Page = 0\n\t\t\t}\n\t\t\tif m.index >= end {\n\t\t\t\tm.paginator.NextPage()\n\t\t\t}\n\t\tcase key.Matches(msg, km.Up):\n\t\t\tm.index--\n\t\t\tif m.index < 0 {\n\t\t\t\tm.index = len(m.items) - 1\n\t\t\t\tm.paginator.Page = m.paginator.TotalPages - 1\n\t\t\t}\n\t\t\tif m.index < start {\n\t\t\t\tm.paginator.PrevPage()\n\t\t\t}\n\t\tcase key.Matches(msg, km.Right):\n\t\t\tm.index = ordered.Clamp(m.index+m.height, 0, len(m.items)-1)\n\t\t\tm.paginator.NextPage()\n\t\tcase key.Matches(msg, km.Left):\n\t\t\tm.index = ordered.Clamp(m.index-m.height, 0, len(m.items)-1)\n\t\t\tm.paginator.PrevPage()\n\t\tcase key.Matches(msg, km.End):\n\t\t\tm.index = len(m.items) - 1\n\t\t\tm.paginator.Page = m.paginator.TotalPages - 1\n\t\tcase key.Matches(msg, km.Home):\n\t\t\tm.index = 0\n\t\t\tm.paginator.Page = 0\n\t\tcase key.Matches(msg, km.ToggleAll):\n\t\t\tif m.limit <= 1 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif m.numSelected < len(m.items) && m.numSelected < m.limit {\n\t\t\t\tm = m.selectAll()\n\t\t\t} else {\n\t\t\t\tm = m.deselectAll()\n\t\t\t}\n\t\tcase key.Matches(msg, km.Quit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, km.Abort):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Interrupt\n\t\tcase key.Matches(msg, km.Toggle):\n\t\t\tif m.limit == 1 {\n\t\t\t\tbreak // no op\n\t\t\t}\n\n\t\t\tif m.items[m.index].selected {\n\t\t\t\tm.items[m.index].selected = false\n\t\t\t\tm.numSelected--\n\t\t\t} else if m.numSelected < m.limit {\n\t\t\t\tm.items[m.index].selected = true\n\t\t\t\tm.items[m.index].order = m.currentOrder\n\t\t\t\tm.numSelected++\n\t\t\t\tm.currentOrder++\n\t\t\t}\n\t\tcase key.Matches(msg, km.Submit):\n\t\t\tm.quitting = true\n\t\t\tif m.limit <= 1 && m.numSelected < 1 {\n\t\t\t\tm.items[m.index].selected = true\n\t\t\t}\n\t\t\tm.submitted = true\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\tvar cmd tea.Cmd\n\tm.paginator, cmd = m.paginator.Update(msg)\n\treturn m, cmd\n}\n\nfunc (m model) selectAll() model {\n\tfor i := range m.items {\n\t\tif m.numSelected >= m.limit {\n\t\t\tbreak // do not exceed given limit\n\t\t}\n\t\tif m.items[i].selected {\n\t\t\tcontinue\n\t\t}\n\t\tm.items[i].selected = true\n\t\tm.items[i].order = m.currentOrder\n\t\tm.numSelected++\n\t\tm.currentOrder++\n\t}\n\treturn m\n}\n\nfunc (m model) deselectAll() model {\n\tfor i := range m.items {\n\t\tm.items[i].selected = false\n\t\tm.items[i].order = 0\n\t}\n\tm.numSelected = 0\n\tm.currentOrder = 0\n\treturn m\n}\n\nfunc (m model) View() string {\n\tif m.quitting {\n\t\treturn \"\"\n\t}\n\n\tvar s strings.Builder\n\n\tstart, end := m.paginator.GetSliceBounds(len(m.items))\n\tfor i, item := range m.items[start:end] {\n\t\tif i == m.index%m.height {\n\t\t\ts.WriteString(m.cursorStyle.Render(m.cursor))\n\t\t} else {\n\t\t\ts.WriteString(strings.Repeat(\" \", lipgloss.Width(m.cursor)))\n\t\t}\n\n\t\tif item.selected {\n\t\t\ts.WriteString(m.selectedItemStyle.Render(m.selectedPrefix + item.text))\n\t\t} else if i == m.index%m.height {\n\t\t\ts.WriteString(m.cursorStyle.Render(m.cursorPrefix + item.text))\n\t\t} else {\n\t\t\ts.WriteString(m.itemStyle.Render(m.unselectedPrefix + item.text))\n\t\t}\n\t\tif i != m.height {\n\t\t\ts.WriteRune('\\n')\n\t\t}\n\t}\n\n\tif m.paginator.TotalPages > 1 {\n\t\ts.WriteString(strings.Repeat(\"\\n\", m.height-m.paginator.ItemsOnPage(len(m.items))+1))\n\t\ts.WriteString(\"  \" + m.paginator.View())\n\t}\n\n\tvar parts []string\n\n\tif m.header != \"\" {\n\t\tparts = append(parts, m.headerStyle.Render(m.header))\n\t}\n\tparts = append(parts, s.String())\n\tif m.showHelp {\n\t\tparts = append(parts, \"\", m.help.View(m.keymap))\n\t}\n\n\tview := lipgloss.JoinVertical(lipgloss.Left, parts...)\n\treturn lipgloss.NewStyle().\n\t\tPadding(m.padding...).\n\t\tRender(view)\n}\n"
  },
  {
    "path": "choose/command.go",
    "content": "package choose\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/paginator\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/gum/internal/stdin\"\n\t\"github.com/charmbracelet/gum/internal/timeout\"\n\t\"github.com/charmbracelet/gum/internal/tty\"\n\t\"github.com/charmbracelet/gum/style\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\n// Run provides a shell script interface for choosing between different through\n// options.\nfunc (o Options) Run() error {\n\tvar (\n\t\tsubduedStyle     = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: \"#847A85\", Dark: \"#979797\"})\n\t\tverySubduedStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: \"#DDDADA\", Dark: \"#3C3C3C\"})\n\t)\n\n\tinput, _ := stdin.Read(stdin.StripANSI(o.StripANSI))\n\tif len(o.Options) > 0 && len(o.Selected) == 0 {\n\t\to.Selected = strings.Split(input, o.InputDelimiter)\n\t} else if len(o.Options) == 0 {\n\t\tif input == \"\" {\n\t\t\treturn errors.New(\"no options provided, see `gum choose --help`\")\n\t\t}\n\t\to.Options = strings.Split(input, o.InputDelimiter)\n\t}\n\n\t// normalize options into a map\n\toptions := map[string]string{}\n\t// keep the labels in the user-provided order\n\tvar labels []string //nolint:prealloc\n\tfor _, opt := range o.Options {\n\t\tif o.LabelDelimiter == \"\" {\n\t\t\toptions[opt] = opt\n\t\t\tcontinue\n\t\t}\n\t\tlabel, value, ok := strings.Cut(opt, o.LabelDelimiter)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"invalid option format: %q\", opt)\n\t\t}\n\t\tlabels = append(labels, label)\n\t\toptions[label] = value\n\t}\n\tif o.LabelDelimiter != \"\" {\n\t\to.Options = labels\n\t}\n\n\tif o.SelectIfOne && len(o.Options) == 1 {\n\t\tfmt.Println(options[o.Options[0]])\n\t\treturn nil\n\t}\n\n\t// We don't need to display prefixes if we are only picking one option.\n\t// Simply displaying the cursor is enough.\n\tif o.Limit == 1 && !o.NoLimit {\n\t\to.SelectedPrefix = \"\"\n\t\to.UnselectedPrefix = \"\"\n\t\to.CursorPrefix = \"\"\n\t}\n\n\tif o.NoLimit {\n\t\to.Limit = len(o.Options) + 1\n\t}\n\n\tif o.Ordered {\n\t\tslices.SortFunc(o.Options, strings.Compare)\n\t}\n\n\tisSelectAll := len(o.Selected) == 1 && o.Selected[0] == \"*\"\n\n\t// Keep track of the selected items.\n\tcurrentSelected := 0\n\t// Check if selected items should be used.\n\thasSelectedItems := len(o.Selected) > 0\n\tstartingIndex := 0\n\tcurrentOrder := 0\n\titems := make([]item, len(o.Options))\n\tfor i, option := range o.Options {\n\t\tvar order int\n\t\t// Check if the option should be selected.\n\t\tisSelected := hasSelectedItems && currentSelected < o.Limit && (isSelectAll || slices.Contains(o.Selected, option))\n\t\t// If the option is selected then increment the current selected count.\n\t\tif isSelected {\n\t\t\tif o.Limit == 1 {\n\t\t\t\t// When the user can choose only one option don't select the option but\n\t\t\t\t// start with the cursor hovering over it.\n\t\t\t\tstartingIndex = i\n\t\t\t\tisSelected = false\n\t\t\t} else {\n\t\t\t\tcurrentSelected++\n\t\t\t\torder = currentOrder\n\t\t\t\tcurrentOrder++\n\t\t\t}\n\t\t}\n\t\titems[i] = item{text: option, selected: isSelected, order: order}\n\t}\n\n\t// Use the pagination model to display the current and total number of\n\t// pages.\n\ttop, right, bottom, left := style.ParsePadding(o.Padding)\n\tpager := paginator.New()\n\tpager.SetTotalPages((len(items) + o.Height - 1) / o.Height)\n\tpager.PerPage = o.Height\n\tpager.Type = paginator.Dots\n\tpager.ActiveDot = subduedStyle.Render(\"•\")\n\tpager.InactiveDot = verySubduedStyle.Render(\"•\")\n\tpager.KeyMap = paginator.KeyMap{}\n\tpager.Page = startingIndex / o.Height\n\n\tkm := defaultKeymap()\n\tif o.NoLimit || o.Limit > 1 {\n\t\tkm.Toggle.SetEnabled(true)\n\t}\n\tif o.NoLimit {\n\t\tkm.ToggleAll.SetEnabled(true)\n\t}\n\n\tm := model{\n\t\tindex:             startingIndex,\n\t\tcurrentOrder:      currentOrder,\n\t\theight:            o.Height,\n\t\tpadding:           []int{top, right, bottom, left},\n\t\tcursor:            o.Cursor,\n\t\theader:            o.Header,\n\t\tselectedPrefix:    o.SelectedPrefix,\n\t\tunselectedPrefix:  o.UnselectedPrefix,\n\t\tcursorPrefix:      o.CursorPrefix,\n\t\titems:             items,\n\t\tlimit:             o.Limit,\n\t\tpaginator:         pager,\n\t\tcursorStyle:       o.CursorStyle.ToLipgloss(),\n\t\theaderStyle:       o.HeaderStyle.ToLipgloss(),\n\t\titemStyle:         o.ItemStyle.ToLipgloss(),\n\t\tselectedItemStyle: o.SelectedItemStyle.ToLipgloss(),\n\t\tnumSelected:       currentSelected,\n\t\tshowHelp:          o.ShowHelp,\n\t\thelp:              help.New(),\n\t\tkeymap:            km,\n\t}\n\n\tctx, cancel := timeout.Context(o.Timeout)\n\tdefer cancel()\n\n\t// Disable Keybindings since we will control it ourselves.\n\ttm, err := tea.NewProgram(\n\t\tm,\n\t\ttea.WithOutput(os.Stderr),\n\t\ttea.WithContext(ctx),\n\t).Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to pick selection: %w\", err)\n\t}\n\tm = tm.(model)\n\tif !m.submitted {\n\t\treturn errors.New(\"nothing selected\")\n\t}\n\tif o.Ordered && o.Limit > 1 {\n\t\tsort.Slice(m.items, func(i, j int) bool {\n\t\t\treturn m.items[i].order < m.items[j].order\n\t\t})\n\t}\n\n\tvar out []string\n\tfor _, item := range m.items {\n\t\tif item.selected {\n\t\t\tout = append(out, options[item.text])\n\t\t}\n\t}\n\ttty.Println(strings.Join(out, o.OutputDelimiter))\n\treturn nil\n}\n"
  },
  {
    "path": "choose/options.go",
    "content": "package choose\n\nimport (\n\t\"time\"\n\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Options is the customization options for the choose command.\ntype Options struct {\n\tOptions          []string      `arg:\"\" optional:\"\" help:\"Options to choose from.\"`\n\tLimit            int           `help:\"Maximum number of options to pick\" default:\"1\" group:\"Selection\"`\n\tNoLimit          bool          `help:\"Pick unlimited number of options (ignores limit)\" group:\"Selection\"`\n\tOrdered          bool          `help:\"Maintain the order of the selected options\" env:\"GUM_CHOOSE_ORDERED\"`\n\tHeight           int           `help:\"Height of the list\" default:\"10\" env:\"GUM_CHOOSE_HEIGHT\"`\n\tCursor           string        `help:\"Prefix to show on item that corresponds to the cursor position\" default:\"> \" env:\"GUM_CHOOSE_CURSOR\"`\n\tShowHelp         bool          `help:\"Show help keybinds\" default:\"true\" negatable:\"\" env:\"GUM_CHOOSE_SHOW_HELP\"`\n\tTimeout          time.Duration `help:\"Timeout until choose returns selected element\" default:\"0s\" env:\"GUM_CHOOSE_TIMEOUT\"` // including timeout command options [Timeout,...]\n\tHeader           string        `help:\"Header value\" default:\"Choose:\" env:\"GUM_CHOOSE_HEADER\"`\n\tCursorPrefix     string        `help:\"Prefix to show on the cursor item (hidden if limit is 1)\" default:\"• \" env:\"GUM_CHOOSE_CURSOR_PREFIX\"`\n\tSelectedPrefix   string        `help:\"Prefix to show on selected items (hidden if limit is 1)\" default:\"✓ \" env:\"GUM_CHOOSE_SELECTED_PREFIX\"`\n\tUnselectedPrefix string        `help:\"Prefix to show on unselected items (hidden if limit is 1)\" default:\"• \" env:\"GUM_CHOOSE_UNSELECTED_PREFIX\"`\n\tSelected         []string      `help:\"Options that should start as selected (selects all if given *)\" default:\"\" env:\"GUM_CHOOSE_SELECTED\"`\n\tSelectIfOne      bool          `help:\"Select the given option if there is only one\" group:\"Selection\"`\n\tInputDelimiter   string        `help:\"Option delimiter when reading from STDIN\" default:\"\\n\" env:\"GUM_CHOOSE_INPUT_DELIMITER\"`\n\tOutputDelimiter  string        `help:\"Option delimiter when writing to STDOUT\" default:\"\\n\" env:\"GUM_CHOOSE_OUTPUT_DELIMITER\"`\n\tLabelDelimiter   string        `help:\"Allows to set a delimiter, so options can be set as label:value\" default:\"\" env:\"GUM_CHOOSE_LABEL_DELIMITER\"`\n\tStripANSI        bool          `help:\"Strip ANSI sequences when reading from STDIN\" default:\"true\" negatable:\"\" env:\"GUM_CHOOSE_STRIP_ANSI\"`\n\tPadding          string        `help:\"Padding\" default:\"${defaultPadding}\" group:\"Style Flags\" env:\"GUM_CHOOSE_PADDING\"`\n\n\tCursorStyle       style.Styles `embed:\"\" prefix:\"cursor.\" set:\"defaultForeground=212\" envprefix:\"GUM_CHOOSE_CURSOR_\"`\n\tHeaderStyle       style.Styles `embed:\"\" prefix:\"header.\" set:\"defaultForeground=99\" envprefix:\"GUM_CHOOSE_HEADER_\"`\n\tItemStyle         style.Styles `embed:\"\" prefix:\"item.\" hidden:\"\" envprefix:\"GUM_CHOOSE_ITEM_\"`\n\tSelectedItemStyle style.Styles `embed:\"\" prefix:\"selected.\" set:\"defaultForeground=212\" envprefix:\"GUM_CHOOSE_SELECTED_\"`\n}\n"
  },
  {
    "path": "completion/bash.go",
    "content": "// Package completion provides a bash completion generator for Kong\n// applications.\npackage completion\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/alecthomas/kong\"\n)\n\n// Bash is a bash completion generator.\ntype Bash struct{}\n\n// Run generates bash completion script.\nfunc (b Bash) Run(ctx *kong.Context) error {\n\tbuf := new(bytes.Buffer)\n\twritePreamble(buf, ctx.Model.Name)\n\tb.gen(buf, ctx.Model.Node)\n\twritePostscript(buf, ctx.Model.Name)\n\n\t_, err := fmt.Fprint(ctx.Stdout, buf.String())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to generate bash completion: %v\", err)\n\t}\n\treturn nil\n}\n\n// ShellCompDirective is a bit map representing the different behaviors the shell\n// can be instructed to have once completions have been provided.\ntype ShellCompDirective int\n\nconst (\n\t// ShellCompDirectiveError indicates an error occurred and completions should be ignored.\n\tShellCompDirectiveError ShellCompDirective = 1 << iota\n\n\t// ShellCompDirectiveNoSpace indicates that the shell should not add a space\n\t// after the completion even if there is a single completion provided.\n\tShellCompDirectiveNoSpace\n\n\t// ShellCompDirectiveNoFileComp indicates that the shell should not provide\n\t// file completion even when no completion is provided.\n\tShellCompDirectiveNoFileComp\n\n\t// ShellCompDirectiveFilterFileExt indicates that the provided completions\n\t// should be used as file extension filters.\n\t// For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()\n\t// is a shortcut to using this directive explicitly.  The BashCompFilenameExt\n\t// annotation can also be used to obtain the same behavior for flags.\n\tShellCompDirectiveFilterFileExt\n\n\t// ShellCompDirectiveFilterDirs indicates that only directory names should\n\t// be provided in file completion.  To request directory names within another\n\t// directory, the returned completions should specify the directory within\n\t// which to search.  The BashCompSubdirsInDir annotation can be used to\n\t// obtain the same behavior but only for flags.\n\tShellCompDirectiveFilterDirs\n\n\t// ===========================================================================\n\t//\n\t// All directives using iota should be above this one.\n\t// For internal use.\n\tshellCompDirectiveMaxValue //nolint:deadcode,unused,varcheck\n\n\t// ShellCompDirectiveDefault indicates to let the shell perform its default\n\t// behavior after completions have been provided.\n\t// This one must be last to avoid messing up the iota count.\n\tShellCompDirectiveDefault ShellCompDirective = 0\n)\n\n// Annotations for Bash completion.\nconst (\n\t// ShellCompNoDescRequestCmd is the name of the hidden command that is used to request\n\t// completion results without their description.  It is used by the shell completion scripts.\n\tShellCompNoDescRequestCmd = \"completion completeNoDesc\"\n\tBashCompFilenameExt       = \"kong_annotation_bash_completion_filename_extensions\"\n\tBashCompCustom            = \"kong_annotation_bash_completion_custom\"\n\tBashCompOneRequiredFlag   = \"kong_annotation_bash_completion_one_required_flag\"\n\tBashCompSubdirsInDir      = \"kong_annotation_bash_completion_subdirs_in_dir\"\n\n\tactiveHelpEnvVarSuffix = \"_ACTIVE_HELP\"\n)\n\n// activeHelpEnvVar returns the name of the program-specific ActiveHelp environment\n// variable.  It has the format <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the\n// root command in upper case, with all - replaced by _.\nfunc activeHelpEnvVar(name string) string {\n\t// This format should not be changed: users will be using it explicitly.\n\tactiveHelpEnvVar := strings.ToUpper(fmt.Sprintf(\"%s%s\", name, activeHelpEnvVarSuffix))\n\treturn strings.ReplaceAll(activeHelpEnvVar, \"-\", \"_\")\n}\n\nfunc writePreamble(buf io.StringWriter, name string) {\n\twriteString(buf, fmt.Sprintf(\"# bash completion for %-36s -*- shell-script -*-\\n\", name))\n\twriteString(buf, fmt.Sprintf(`\n__%[1]s_debug()\n{\n    if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then\n        echo \"$*\" >> \"${BASH_COMP_DEBUG_FILE}\"\n    fi\n}\n\n# Homebrew on Macs have version 1.3 of bash-completion which doesn't include\n# _init_completion. This is a very minimal version of that function.\n__%[1]s_init_completion()\n{\n    COMPREPLY=()\n    _get_comp_words_by_ref \"$@\" cur prev words cword\n}\n\n__%[1]s_index_of_word()\n{\n    local w word=$1\n    shift\n    index=0\n    for w in \"$@\"; do\n        [[ $w = \"$word\" ]] && return\n        index=$((index+1))\n    done\n    index=-1\n}\n\n__%[1]s_contains_word()\n{\n    local w word=$1; shift\n    for w in \"$@\"; do\n        [[ $w = \"$word\" ]] && return\n    done\n    return 1\n}\n\n__%[1]s_handle_go_custom_completion()\n{\n    __%[1]s_debug \"${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}\"\n\n    local shellCompDirectiveError=%[3]d\n    local shellCompDirectiveNoSpace=%[4]d\n    local shellCompDirectiveNoFileComp=%[5]d\n    local shellCompDirectiveFilterFileExt=%[6]d\n    local shellCompDirectiveFilterDirs=%[7]d\n\n    local out requestComp lastParam lastChar comp directive args\n\n    # Prepare the command to request completions for the program.\n    # Calling ${words[0]} instead of directly %[1]s allows to handle aliases\n    args=(\"${words[@]:1}\")\n    # Disable ActiveHelp which is not supported for bash completion v1\n    requestComp=\"%[8]s=0 ${words[0]} %[2]s ${args[*]}\"\n\n    lastParam=${words[$((${#words[@]}-1))]}\n    lastChar=${lastParam:$((${#lastParam}-1)):1}\n    __%[1]s_debug \"${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}\"\n\n    if [ -z \"${cur}\" ] && [ \"${lastChar}\" != \"=\" ]; then\n        # If the last parameter is complete (there is a space following it)\n        # We add an extra empty parameter so we can indicate this to the go method.\n        __%[1]s_debug \"${FUNCNAME[0]}: Adding extra empty parameter\"\n        requestComp=\"${requestComp} \\\"\\\"\"\n    fi\n\n    __%[1]s_debug \"${FUNCNAME[0]}: calling ${requestComp}\"\n    # Use eval to handle any environment variables and such\n    out=$(eval \"${requestComp}\" 2>/dev/null)\n\n    # Extract the directive integer at the very end of the output following a colon (:)\n    directive=${out##*:}\n    # Remove the directive\n    out=${out%%:*}\n    if [ \"${directive}\" = \"${out}\" ]; then\n        # There is not directive specified\n        directive=0\n    fi\n    __%[1]s_debug \"${FUNCNAME[0]}: the completion directive is: ${directive}\"\n    __%[1]s_debug \"${FUNCNAME[0]}: the completions are: ${out}\"\n\n    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then\n        # Error code.  No completion.\n        __%[1]s_debug \"${FUNCNAME[0]}: received error from custom completion go code\"\n        return\n    else\n        if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then\n            if [[ $(type -t compopt) = \"builtin\" ]]; then\n                __%[1]s_debug \"${FUNCNAME[0]}: activating no space\"\n                compopt -o nospace\n            fi\n        fi\n        if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then\n            if [[ $(type -t compopt) = \"builtin\" ]]; then\n                __%[1]s_debug \"${FUNCNAME[0]}: activating no file completion\"\n                compopt +o default\n            fi\n        fi\n    fi\n\n    if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then\n        # File extension filtering\n        local fullFilter filter filteringCmd\n        # Do not use quotes around the $out variable or else newline\n        # characters will be kept.\n        for filter in ${out}; do\n            fullFilter+=\"$filter|\"\n        done\n\n        filteringCmd=\"_filedir $fullFilter\"\n        __%[1]s_debug \"File filtering command: $filteringCmd\"\n        $filteringCmd\n    elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then\n        # File completion for directories only\n        local subdir\n        # Use printf to strip any trailing newline\n        subdir=$(printf \"%%s\" \"${out}\")\n        if [ -n \"$subdir\" ]; then\n            __%[1]s_debug \"Listing directories in $subdir\"\n            __%[1]s_handle_subdirs_in_dir_flag \"$subdir\"\n        else\n            __%[1]s_debug \"Listing directories in .\"\n            _filedir -d\n        fi\n    else\n        while IFS='' read -r comp; do\n            COMPREPLY+=(\"$comp\")\n        done < <(compgen -W \"${out}\" -- \"$cur\")\n    fi\n}\n\n__%[1]s_handle_reply()\n{\n    __%[1]s_debug \"${FUNCNAME[0]}\"\n    local comp\n    case $cur in\n        -*)\n            if [[ $(type -t compopt) = \"builtin\" ]]; then\n                compopt -o nospace\n            fi\n            local allflags\n            if [ ${#must_have_one_flag[@]} -ne 0 ]; then\n                allflags=(\"${must_have_one_flag[@]}\")\n            else\n                allflags=(\"${flags[*]} ${two_word_flags[*]}\")\n            fi\n            while IFS='' read -r comp; do\n                COMPREPLY+=(\"$comp\")\n            done < <(compgen -W \"${allflags[*]}\" -- \"$cur\")\n            if [[ $(type -t compopt) = \"builtin\" ]]; then\n                [[ \"${COMPREPLY[0]}\" == *= ]] || compopt +o nospace\n            fi\n\n            # complete after --flag=abc\n            if [[ $cur == *=* ]]; then\n                if [[ $(type -t compopt) = \"builtin\" ]]; then\n                    compopt +o nospace\n                fi\n\n                local index flag\n                flag=\"${cur%%=*}\"\n                __%[1]s_index_of_word \"${flag}\" \"${flags_with_completion[@]}\"\n                COMPREPLY=()\n                if [[ ${index} -ge 0 ]]; then\n                    PREFIX=\"\"\n                    cur=\"${cur#*=}\"\n                    ${flags_completion[${index}]}\n                    if [ -n \"${ZSH_VERSION:-}\" ]; then\n                        # zsh completion needs --flag= prefix\n                        eval \"COMPREPLY=( \\\"\\${COMPREPLY[@]/#/${flag}=}\\\" )\"\n                    fi\n                fi\n            fi\n\n            if [[ -z \"${flag_parsing_disabled}\" ]]; then\n                # If flag parsing is enabled, we have completed the flags and can return.\n                # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough\n                # to possibly call handle_go_custom_completion.\n                return 0;\n            fi\n            ;;\n    esac\n\n    # check if we are handling a flag with special work handling\n    local index\n    __%[1]s_index_of_word \"${prev}\" \"${flags_with_completion[@]}\"\n    if [[ ${index} -ge 0 ]]; then\n        ${flags_completion[${index}]}\n        return\n    fi\n\n    # we are parsing a flag and don't have a special handler, no completion\n    if [[ ${cur} != \"${words[cword]}\" ]]; then\n        return\n    fi\n\n    local completions\n    completions=(\"${commands[@]}\")\n    if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then\n        completions+=(\"${must_have_one_noun[@]}\")\n    elif [[ -n \"${has_completion_function}\" ]]; then\n        # if a go completion function is provided, defer to that function\n        __%[1]s_handle_go_custom_completion\n    fi\n    if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then\n        completions+=(\"${must_have_one_flag[@]}\")\n    fi\n    while IFS='' read -r comp; do\n        COMPREPLY+=(\"$comp\")\n    done < <(compgen -W \"${completions[*]}\" -- \"$cur\")\n\n    if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then\n        while IFS='' read -r comp; do\n            COMPREPLY+=(\"$comp\")\n        done < <(compgen -W \"${noun_aliases[*]}\" -- \"$cur\")\n    fi\n\n    if [[ ${#COMPREPLY[@]} -eq 0 ]]; then\n        if declare -F __%[1]s_custom_func >/dev/null; then\n            # try command name qualified custom func\n            __%[1]s_custom_func\n        else\n            # otherwise fall back to unqualified for compatibility\n            declare -F __custom_func >/dev/null && __custom_func\n        fi\n    fi\n\n    # available in bash-completion >= 2, not always present on macOS\n    if declare -F __ltrim_colon_completions >/dev/null; then\n        __ltrim_colon_completions \"$cur\"\n    fi\n\n    # If there is only 1 completion and it is a flag with an = it will be completed\n    # but we don't want a space after the =\n    if [[ \"${#COMPREPLY[@]}\" -eq \"1\" ]] && [[ $(type -t compopt) = \"builtin\" ]] && [[ \"${COMPREPLY[0]}\" == --*= ]]; then\n       compopt -o nospace\n    fi\n}\n\n# The arguments should be in the form \"ext1|ext2|extn\"\n__%[1]s_handle_filename_extension_flag()\n{\n    local ext=\"$1\"\n    _filedir \"@(${ext})\"\n}\n\n__%[1]s_handle_subdirs_in_dir_flag()\n{\n    local dir=\"$1\"\n    pushd \"${dir}\" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return\n}\n\n__%[1]s_handle_flag()\n{\n    __%[1]s_debug \"${FUNCNAME[0]}: c is $c words[c] is ${words[c]}\"\n\n    # if a command required a flag, and we found it, unset must_have_one_flag()\n    local flagname=${words[c]}\n    local flagvalue=\"\"\n    # if the word contained an =\n    if [[ ${words[c]} == *\"=\"* ]]; then\n        flagvalue=${flagname#*=} # take in as flagvalue after the =\n        flagname=${flagname%%=*} # strip everything after the =\n        flagname=\"${flagname}=\" # but put the = back\n    fi\n    __%[1]s_debug \"${FUNCNAME[0]}: looking for ${flagname}\"\n    if __%[1]s_contains_word \"${flagname}\" \"${must_have_one_flag[@]}\"; then\n        must_have_one_flag=()\n    fi\n\n    # if you set a flag which only applies to this command, don't show subcommands\n    if __%[1]s_contains_word \"${flagname}\" \"${local_nonpersistent_flags[@]}\"; then\n      commands=()\n    fi\n\n    # keep flag value with flagname as flaghash\n    # flaghash variable is an associative array which is only supported in bash > 3.\n    if [[ -z \"${BASH_VERSION:-}\" || \"${BASH_VERSINFO[0]:-}\" -gt 3 ]]; then\n        if [ -n \"${flagvalue}\" ] ; then\n            flaghash[${flagname}]=${flagvalue}\n        elif [ -n \"${words[ $((c+1)) ]}\" ] ; then\n            flaghash[${flagname}]=${words[ $((c+1)) ]}\n        else\n            flaghash[${flagname}]=\"true\" # pad \"true\" for bool flag\n        fi\n    fi\n\n    # skip the argument to a two word flag\n    if [[ ${words[c]} != *\"=\"* ]] && __%[1]s_contains_word \"${words[c]}\" \"${two_word_flags[@]}\"; then\n        __%[1]s_debug \"${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument\"\n        c=$((c+1))\n        # if we are looking for a flags value, don't show commands\n        if [[ $c -eq $cword ]]; then\n            commands=()\n        fi\n    fi\n\n    c=$((c+1))\n\n}\n\n__%[1]s_handle_noun()\n{\n    __%[1]s_debug \"${FUNCNAME[0]}: c is $c words[c] is ${words[c]}\"\n\n    if __%[1]s_contains_word \"${words[c]}\" \"${must_have_one_noun[@]}\"; then\n        must_have_one_noun=()\n    elif __%[1]s_contains_word \"${words[c]}\" \"${noun_aliases[@]}\"; then\n        must_have_one_noun=()\n    fi\n\n    nouns+=(\"${words[c]}\")\n    c=$((c+1))\n}\n\n__%[1]s_handle_command()\n{\n    __%[1]s_debug \"${FUNCNAME[0]}: c is $c words[c] is ${words[c]}\"\n\n    local next_command\n    if [[ -n ${last_command} ]]; then\n        next_command=\"_${last_command}_${words[c]//:/__}\"\n    else\n        if [[ $c -eq 0 ]]; then\n            next_command=\"_%[1]s_root_command\"\n        else\n            next_command=\"_${words[c]//:/__}\"\n        fi\n    fi\n    c=$((c+1))\n    __%[1]s_debug \"${FUNCNAME[0]}: looking for ${next_command}\"\n    declare -F \"$next_command\" >/dev/null && $next_command\n}\n\n__%[1]s_handle_word()\n{\n    if [[ $c -ge $cword ]]; then\n        __%[1]s_handle_reply\n        return\n    fi\n    __%[1]s_debug \"${FUNCNAME[0]}: c is $c words[c] is ${words[c]}\"\n    if [[ \"${words[c]}\" == -* ]]; then\n        __%[1]s_handle_flag\n    elif __%[1]s_contains_word \"${words[c]}\" \"${commands[@]}\"; then\n        __%[1]s_handle_command\n    elif [[ $c -eq 0 ]]; then\n        __%[1]s_handle_command\n    elif __%[1]s_contains_word \"${words[c]}\" \"${command_aliases[@]}\"; then\n        # aliashash variable is an associative array which is only supported in bash > 3.\n        if [[ -z \"${BASH_VERSION:-}\" || \"${BASH_VERSINFO[0]:-}\" -gt 3 ]]; then\n            words[c]=${aliashash[${words[c]}]}\n            __%[1]s_handle_command\n        else\n            __%[1]s_handle_noun\n        fi\n    else\n        __%[1]s_handle_noun\n    fi\n    __%[1]s_handle_word\n}\n\n`, name, ShellCompNoDescRequestCmd,\n\t\tShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,\n\t\tShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name)))\n}\n\nfunc writePostscript(buf io.StringWriter, name string) {\n\tname = strings.ReplaceAll(name, \":\", \"__\")\n\twriteString(buf, fmt.Sprintf(\"__start_%s()\\n\", name))\n\twriteString(buf, fmt.Sprintf(`{\n    local cur prev words cword split\n    declare -A flaghash 2>/dev/null || :\n    declare -A aliashash 2>/dev/null || :\n    if declare -F _init_completion >/dev/null 2>&1; then\n        _init_completion -s || return\n    else\n        __%[1]s_init_completion -n \"=\" || return\n    fi\n\n    local c=0\n    local flag_parsing_disabled=\n    local flags=()\n    local two_word_flags=()\n    local local_nonpersistent_flags=()\n    local flags_with_completion=()\n    local flags_completion=()\n    local commands=(\"%[1]s\")\n    local command_aliases=()\n    local must_have_one_flag=()\n    local must_have_one_noun=()\n    local has_completion_function=\"\"\n    local last_command=\"\"\n    local nouns=()\n    local noun_aliases=()\n\n    __%[1]s_handle_word\n}\n\n`, name))\n\twriteString(buf, fmt.Sprintf(`if [[ $(type -t compopt) = \"builtin\" ]]; then\n    complete -o default -F __start_%s %s\nelse\n    complete -o default -o nospace -F __start_%s %s\nfi\n\n`, name, name, name, name))\n\twriteString(buf, \"# ex: ts=4 sw=4 et filetype=sh\\n\")\n}\n\nfunc writeCommands(buf io.StringWriter, cmd *kong.Node) {\n\twriteString(buf, \"    commands=()\\n\")\n\tfor _, c := range cmd.Children {\n\t\tif c == nil || c.Hidden {\n\t\t\tcontinue\n\t\t}\n\t\twriteString(buf, fmt.Sprintf(\"    commands+=(%q)\\n\", c.Name))\n\t\twriteCmdAliases(buf, c)\n\t}\n\twriteString(buf, \"\\n\")\n}\n\nfunc writeFlagHandler(buf io.StringWriter, name string, annotations map[string][]string, cmd *kong.Node) {\n\tfor key, value := range annotations {\n\t\tswitch key {\n\t\tcase BashCompFilenameExt:\n\t\t\twriteString(buf, fmt.Sprintf(\"    flags_with_completion+=(%q)\\n\", name))\n\n\t\t\tvar ext string\n\t\t\tif len(value) > 0 {\n\t\t\t\text = fmt.Sprintf(\"__%s_handle_filename_extension_flag \", cmd.Parent.Name) + strings.Join(value, \"|\")\n\t\t\t} else {\n\t\t\t\text = \"_filedir\"\n\t\t\t}\n\t\t\twriteString(buf, fmt.Sprintf(\"    flags_completion+=(%q)\\n\", ext))\n\t\tcase BashCompCustom:\n\t\t\twriteString(buf, fmt.Sprintf(\"    flags_with_completion+=(%q)\\n\", name))\n\n\t\t\tif len(value) > 0 {\n\t\t\t\thandlers := strings.Join(value, \"; \")\n\t\t\t\twriteString(buf, fmt.Sprintf(\"    flags_completion+=(%q)\\n\", handlers))\n\t\t\t} else {\n\t\t\t\twriteString(buf, \"    flags_completion+=(:)\\n\")\n\t\t\t}\n\t\tcase BashCompSubdirsInDir:\n\t\t\twriteString(buf, fmt.Sprintf(\"    flags_with_completion+=(%q)\\n\", name))\n\n\t\t\tvar ext string\n\t\t\tif len(value) == 1 {\n\t\t\t\text = fmt.Sprintf(\"__%s_handle_subdirs_in_dir_flag \", cmd.Parent.Name) + value[0]\n\t\t\t} else {\n\t\t\t\text = \"_filedir -d\"\n\t\t\t}\n\t\t\twriteString(buf, fmt.Sprintf(\"    flags_completion+=(%q)\\n\", ext))\n\t\t}\n\t}\n}\n\nconst cbn = \"\\\")\\n\"\n\nfunc writeShortFlag(buf io.StringWriter, flag *kong.Flag, cmd *kong.Node) {\n\tname := fmt.Sprintf(\"%c\", flag.Short)\n\tformat := \"    \"\n\tif len(flag.DefaultValue.String()) == 0 {\n\t\tformat += \"two_word_\"\n\t}\n\tformat += \"flags+=(\\\"-%s\" + cbn\n\twriteString(buf, fmt.Sprintf(format, name))\n\twriteFlagHandler(buf, \"-\"+name, map[string][]string{}, cmd)\n}\n\nfunc writeFlag(buf io.StringWriter, flag *kong.Flag, cmd *kong.Node) {\n\tname := flag.Name\n\tformat := \"    flags+=(\\\"--%s\"\n\tif len(flag.DefaultValue.String()) == 0 {\n\t\tformat += \"=\"\n\t}\n\tformat += cbn\n\twriteString(buf, fmt.Sprintf(format, name))\n\tif len(flag.DefaultValue.String()) == 0 {\n\t\tformat = \"    two_word_flags+=(\\\"--%s\" + cbn\n\t\twriteString(buf, fmt.Sprintf(format, name))\n\t}\n\twriteFlagHandler(buf, \"--\"+name, map[string][]string{}, cmd)\n}\n\n//nolint:deadcode,unused\nfunc writeLocalNonPersistentFlag(buf io.StringWriter, flag *kong.Flag) {\n\tname := flag.Name\n\tformat := \"    local_nonpersistent_flags+=(\\\"--%[1]s\" + cbn\n\tif len(flag.DefaultValue.String()) == 0 {\n\t\tformat += \"    local_nonpersistent_flags+=(\\\"--%[1]s=\" + cbn\n\t}\n\twriteString(buf, fmt.Sprintf(format, name))\n\tif flag.Short > 0 {\n\t\twriteString(buf, fmt.Sprintf(\"    local_nonpersistent_flags+=(\\\"-%c\\\")\\n\", flag.Short))\n\t}\n}\n\nfunc writeFlags(buf io.StringWriter, cmd *kong.Node) {\n\twriteString(buf, `    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n`)\n\n\tfor _, flag := range cmd.Flags {\n\t\tif nonCompletableFlag(flag) {\n\t\t\tcontinue\n\t\t}\n\t\twriteFlag(buf, flag, cmd)\n\t\tif flag.Short != 0 {\n\t\t\twriteShortFlag(buf, flag, cmd)\n\t\t}\n\t}\n\n\twriteString(buf, \"\\n\")\n}\n\nfunc writeCmdAliases(buf io.StringWriter, cmd *kong.Node) {\n\tif len(cmd.Aliases) == 0 {\n\t\treturn\n\t}\n\n\tsort.Strings(cmd.Aliases)\n\n\twriteString(buf, fmt.Sprint(`    if [[ -z \"${BASH_VERSION:-}\" || \"${BASH_VERSINFO[0]:-}\" -gt 3 ]]; then`, \"\\n\"))\n\tfor _, value := range cmd.Aliases {\n\t\twriteString(buf, fmt.Sprintf(\"        command_aliases+=(%q)\\n\", value))\n\t\twriteString(buf, fmt.Sprintf(\"        aliashash[%q]=%q\\n\", value, cmd.Name))\n\t}\n\twriteString(buf, `    fi`)\n\twriteString(buf, \"\\n\")\n}\n\nfunc writeArgAliases(buf io.StringWriter, cmd *kong.Node) {\n\twriteString(buf, \"    noun_aliases=()\\n\")\n\tsort.Strings(cmd.Aliases)\n\tfor _, value := range cmd.Aliases {\n\t\twriteString(buf, fmt.Sprintf(\"    noun_aliases+=(%q)\\n\", value))\n\t}\n}\n\nfunc (b Bash) gen(buf io.StringWriter, cmd *kong.Node) {\n\tfor _, c := range cmd.Children {\n\t\tif c == nil || c.Hidden {\n\t\t\tcontinue\n\t\t}\n\t\tb.gen(buf, c)\n\t}\n\tcommandName := cmd.FullPath()\n\tcommandName = strings.ReplaceAll(commandName, \" \", \"_\")\n\tcommandName = strings.ReplaceAll(commandName, \":\", \"__\")\n\n\tif cmd.Parent == nil {\n\t\twriteString(buf, fmt.Sprintf(\"_%s_root_command()\\n{\\n\", commandName))\n\t} else {\n\t\twriteString(buf, fmt.Sprintf(\"_%s()\\n{\\n\", commandName))\n\t}\n\n\twriteString(buf, fmt.Sprintf(\"    last_command=%q\\n\", commandName))\n\twriteString(buf, \"\\n\")\n\twriteString(buf, \"    command_aliases=()\\n\")\n\twriteString(buf, \"\\n\")\n\n\twriteCommands(buf, cmd)\n\twriteFlags(buf, cmd)\n\twriteArgAliases(buf, cmd)\n\twriteString(buf, \"}\\n\\n\")\n}\n"
  },
  {
    "path": "completion/command.go",
    "content": "package completion\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/alecthomas/kong\"\n)\n\n// Completion command.\ntype Completion struct {\n\tBash Bash `cmd:\"\" help:\"Generate the autocompletion script for bash\"`\n\tZsh  Zsh  `cmd:\"\" help:\"Generate the autocompletion script for zsh\"`\n\tFish Fish `cmd:\"\" help:\"Generate the autocompletion script for fish\"`\n}\n\nfunc commandName(cmd *kong.Node) string {\n\tcommandName := cmd.FullPath()\n\tcommandName = strings.ReplaceAll(commandName, \" \", \"_\")\n\tcommandName = strings.ReplaceAll(commandName, \":\", \"__\")\n\treturn commandName\n}\n\nfunc hasCommands(cmd *kong.Node) bool {\n\tfor _, c := range cmd.Children {\n\t\tif !c.Hidden {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n//nolint:deadcode,unused\nfunc isArgument(cmd *kong.Node) bool {\n\treturn cmd.Type == kong.ArgumentNode\n}\n\n// writeString writes a string into a buffer, and checks if the error is not nil.\nfunc writeString(b io.StringWriter, s string) {\n\tif _, err := b.WriteString(s); err != nil {\n\t\tfmt.Fprintln(os.Stderr, \"Error:\", err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc nonCompletableFlag(flag *kong.Flag) bool {\n\treturn flag.Hidden\n}\n\nfunc flagPossibleValues(flag *kong.Flag) []string {\n\tvalues := make([]string, 0)\n\tfor _, enum := range flag.EnumSlice() {\n\t\tif strings.TrimSpace(enum) != \"\" {\n\t\t\tvalues = append(values, enum)\n\t\t}\n\t}\n\treturn values\n}\n"
  },
  {
    "path": "completion/fish.go",
    "content": "package completion\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/alecthomas/kong\"\n)\n\n// Fish is a fish shell completion generator.\ntype Fish struct{}\n\n// Run generates fish completion script.\nfunc (f Fish) Run(ctx *kong.Context) error {\n\tvar buf strings.Builder\n\tbuf.WriteString(`# Fish shell completion for gum\n# Generated by gum completion\n\n# disable file completion unless explicitly enabled\ncomplete -c gum -f\n\n`)\n\tnode := ctx.Model.Node\n\tf.gen(&buf, node)\n\t_, err := fmt.Fprint(ctx.Stdout, buf.String())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to generate fish completion: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (f Fish) gen(buf io.StringWriter, cmd *kong.Node) {\n\troot := cmd\n\tfor root.Parent != nil {\n\t\troot = root.Parent\n\t}\n\trootName := root.Name\n\tif cmd.Parent == nil {\n\t\t_, _ = buf.WriteString(fmt.Sprintf(\"# %s\\n\", rootName))\n\t} else {\n\t\t_, _ = buf.WriteString(fmt.Sprintf(\"# %s\\n\", cmd.Path()))\n\t\t_, _ = buf.WriteString(\n\t\t\tfmt.Sprintf(\"complete -c %s -f -n '__fish_use_subcommand' -a %s -d '%s'\\n\",\n\t\t\t\trootName,\n\t\t\t\tcmd.Name,\n\t\t\t\tcmd.Help,\n\t\t\t),\n\t\t)\n\t}\n\n\tfor _, f := range cmd.Flags {\n\t\tif f.Hidden {\n\t\t\tcontinue\n\t\t}\n\t\tif cmd.Parent == nil {\n\t\t\t_, _ = buf.WriteString(\n\t\t\t\tfmt.Sprintf(\"complete -c %s -f\",\n\t\t\t\t\trootName,\n\t\t\t\t),\n\t\t\t)\n\t\t} else {\n\t\t\t_, _ = buf.WriteString(\n\t\t\t\tfmt.Sprintf(\"complete -c %s -f -n '__fish_seen_subcommand_from %s'\",\n\t\t\t\t\trootName,\n\t\t\t\t\tcmd.Name,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tif !f.IsBool() {\n\t\t\tenums := flagPossibleValues(f)\n\t\t\tif len(enums) > 0 {\n\t\t\t\t_, _ = buf.WriteString(fmt.Sprintf(\" -xa '%s'\", strings.Join(enums, \" \")))\n\t\t\t} else {\n\t\t\t\t_, _ = buf.WriteString(\" -x\")\n\t\t\t}\n\t\t}\n\t\tif f.Short != 0 {\n\t\t\t_, _ = buf.WriteString(fmt.Sprintf(\" -s %c\", f.Short))\n\t\t}\n\t\t_, _ = buf.WriteString(fmt.Sprintf(\" -l %s\", f.Name))\n\t\t_, _ = buf.WriteString(fmt.Sprintf(\" -d \\\"%s\\\"\", f.Help))\n\t\t_, _ = buf.WriteString(\"\\n\")\n\t}\n\t_, _ = buf.WriteString(\"\\n\")\n\n\tfor _, c := range cmd.Children {\n\t\tif c == nil || c.Hidden {\n\t\t\tcontinue\n\t\t}\n\t\tf.gen(buf, c)\n\t}\n}\n"
  },
  {
    "path": "completion/zsh.go",
    "content": "package completion\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/alecthomas/kong\"\n)\n\n// Zsh is zsh completion generator.\ntype Zsh struct{}\n\n// Run generates zsh completion script.\nfunc (z Zsh) Run(ctx *kong.Context) error {\n\tvar out strings.Builder\n\tformat := `#compdef %[1]s\n# zsh completion for %[1]s\n# generated by gum completion\n\n`\n\tfmt.Fprintf(&out, format, ctx.Model.Name)\n\tz.gen(&out, ctx.Model.Node)\n\t_, err := fmt.Fprint(ctx.Stdout, out.String())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to generate zsh completion: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (z Zsh) writeFlag(buf io.StringWriter, f *kong.Flag) {\n\tvar str strings.Builder\n\tstr.WriteString(\"        \")\n\tif f.Short != 0 {\n\t\tstr.WriteString(\"'(\")\n\t\tstr.WriteString(fmt.Sprintf(\"-%c --%s\", f.Short, f.Name))\n\t\tif !f.IsBool() {\n\t\t\tstr.WriteString(\"=\")\n\t\t}\n\t\tstr.WriteString(\")'\")\n\t\tstr.WriteString(\"{\")\n\t\tstr.WriteString(fmt.Sprintf(\"-%c,--%s\", f.Short, f.Name))\n\t\tif !f.IsBool() {\n\t\t\tstr.WriteString(\"=\")\n\t\t}\n\t\tstr.WriteString(\"}\")\n\t\tstr.WriteString(\"\\\"\")\n\t} else {\n\t\tstr.WriteString(\"\\\"\")\n\t\tstr.WriteString(fmt.Sprintf(\"--%s\", f.Name))\n\t\tif !f.IsBool() {\n\t\t\tstr.WriteString(\"=\")\n\t\t}\n\t}\n\tstr.WriteString(fmt.Sprintf(\"[%s]\", f.Help))\n\tif !f.IsBool() {\n\t\tstr.WriteString(\":\")\n\t\tstr.WriteString(strings.ToLower(f.Help))\n\t\tstr.WriteString(\":\")\n\t}\n\tvalues := flagPossibleValues(f)\n\tif len(values) > 0 {\n\t\tstr.WriteString(\"(\")\n\t\tfor i, v := range f.EnumSlice() {\n\t\t\tstr.WriteString(v)\n\t\t\tif i < len(values)-1 {\n\t\t\t\tstr.WriteString(\" \")\n\t\t\t}\n\t\t}\n\t\tstr.WriteString(\")\")\n\t}\n\tstr.WriteString(\"\\\"\")\n\twriteString(buf, str.String())\n}\n\nfunc (z Zsh) writeFlags(buf io.StringWriter, cmd *kong.Node) {\n\tfor i, f := range cmd.Flags {\n\t\tif f.Hidden {\n\t\t\tcontinue\n\t\t}\n\t\tz.writeFlag(buf, f)\n\t\tif i < len(cmd.Flags)-1 {\n\t\t\twriteString(buf, \" \\\\\\n\")\n\t\t}\n\t}\n}\n\nfunc (z Zsh) writeCommand(buf io.StringWriter, c *kong.Node) {\n\twriteString(buf, fmt.Sprintf(\"                \\\"%s[%s]\\\"\", c.Name, c.Help))\n}\n\nfunc (z Zsh) writeCommands(buf io.StringWriter, cmd *kong.Node) {\n\tfor i, c := range cmd.Children {\n\t\tif c == nil || c.Hidden {\n\t\t\tcontinue\n\t\t}\n\t\tz.writeCommand(buf, c)\n\t\tif i < len(cmd.Children)-1 {\n\t\t\t_, _ = buf.WriteString(\" \\\\\")\n\t\t}\n\t\twriteString(buf, \"\\n\")\n\t}\n}\n\nfunc (z Zsh) gen(buf io.StringWriter, cmd *kong.Node) {\n\tfor _, c := range cmd.Children {\n\t\tif c == nil || c.Hidden {\n\t\t\tcontinue\n\t\t}\n\t\tz.gen(buf, c)\n\t}\n\tcmdName := commandName(cmd)\n\n\twriteString(buf, fmt.Sprintf(\"_%s() {\\n\", cmdName))\n\tif hasCommands(cmd) {\n\t\twriteString(buf, \"    local line state\\n\")\n\t}\n\twriteString(buf, \"    _arguments -C \\\\\\n\")\n\tz.writeFlags(buf, cmd)\n\tif hasCommands(cmd) {\n\t\twriteString(buf, \" \\\\\\n\")\n\t\twriteString(buf, \"        \\\"1: :->cmds\\\" \\\\\\n\")\n\t\twriteString(buf, \"        \\\"*::arg:->args\\\"\\n\")\n\t\twriteString(buf, \"    case \\\"$state\\\" in\\n\")\n\t\twriteString(buf, \"        cmds)\\n\")\n\t\twriteString(buf, fmt.Sprintf(\"            _values \\\"%s command\\\" \\\\\\n\", cmdName))\n\t\tz.writeCommands(buf, cmd)\n\t\twriteString(buf, \"            ;;\\n\")\n\t\twriteString(buf, \"        args)\\n\")\n\t\twriteString(buf, \"            case \\\"$line[1]\\\" in\\n\")\n\t\tfor _, c := range cmd.Children {\n\t\t\tif c == nil || c.Hidden {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\twriteString(buf, fmt.Sprintf(\"                %s)\\n\", c.Name))\n\t\t\twriteString(buf, fmt.Sprintf(\"                    _%s\\n\", commandName(c)))\n\t\t\twriteString(buf, \"                    ;;\\n\")\n\t\t}\n\t\twriteString(buf, \"            esac\\n\")\n\t\twriteString(buf, \"            ;;\\n\")\n\t\twriteString(buf, \"    esac\\n\")\n\t}\n\t// writeArgAliases(buf, cmd)\n\twriteString(buf, \"\\n\")\n\twriteString(buf, \"}\\n\\n\")\n}\n"
  },
  {
    "path": "confirm/command.go",
    "content": "package confirm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/gum/internal/exit\"\n\t\"github.com/charmbracelet/gum/internal/stdin\"\n\t\"github.com/charmbracelet/gum/internal/timeout\"\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Run provides a shell script interface for prompting a user to confirm an\n// action with an affirmative or negative answer.\nfunc (o Options) Run() error {\n\tline, err := stdin.Read(stdin.SingleLine(true))\n\tif err == nil {\n\t\tswitch line {\n\t\tcase \"yes\", \"y\":\n\t\t\treturn nil\n\t\tdefault:\n\t\t\treturn exit.ErrExit(1)\n\t\t}\n\t}\n\n\tctx, cancel := timeout.Context(o.Timeout)\n\tdefer cancel()\n\n\ttop, right, bottom, left := style.ParsePadding(o.Padding)\n\tm := model{\n\t\taffirmative:      o.Affirmative,\n\t\tnegative:         o.Negative,\n\t\tshowOutput:       o.ShowOutput,\n\t\tconfirmation:     o.Default,\n\t\tdefaultSelection: o.Default,\n\t\tkeys:             defaultKeymap(o.Affirmative, o.Negative),\n\t\thelp:             help.New(),\n\t\tshowHelp:         o.ShowHelp,\n\t\tprompt:           o.Prompt,\n\t\tselectedStyle:    o.SelectedStyle.ToLipgloss(),\n\t\tunselectedStyle:  o.UnselectedStyle.ToLipgloss(),\n\t\tpromptStyle:      o.PromptStyle.ToLipgloss(),\n\t\tpadding:          []int{top, right, bottom, left},\n\t}\n\ttm, err := tea.NewProgram(\n\t\tm,\n\t\ttea.WithOutput(os.Stderr),\n\t\ttea.WithContext(ctx),\n\t).Run()\n\tif err != nil && ctx.Err() != context.DeadlineExceeded {\n\t\treturn fmt.Errorf(\"unable to confirm: %w\", err)\n\t}\n\tm = tm.(model)\n\n\tif o.ShowOutput {\n\t\tconfirmationText := m.negative\n\t\tif m.confirmation {\n\t\t\tconfirmationText = m.affirmative\n\t\t}\n\t\tfmt.Println(m.prompt, confirmationText)\n\t}\n\n\tif m.confirmation {\n\t\treturn nil\n\t}\n\n\treturn exit.ErrExit(1)\n}\n"
  },
  {
    "path": "confirm/confirm.go",
    "content": "// Package confirm provides an interface to ask a user to confirm an action.\n// The user is provided with an interface to choose an affirmative or negative\n// answer, which is then reflected in the exit code for use in scripting.\n//\n// If the user selects the affirmative answer, the program exits with 0. If the\n// user selects the negative answer, the program exits with 1.\n//\n// I.e. confirm if the user wants to delete a file\n//\n// $ gum confirm \"Are you sure?\" && rm file.txt\npackage confirm\n\nimport (\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\nfunc defaultKeymap(affirmative, negative string) keymap {\n\treturn keymap{\n\t\tAbort: key.NewBinding(\n\t\t\tkey.WithKeys(\"ctrl+c\"),\n\t\t\tkey.WithHelp(\"ctrl+c\", \"cancel\"),\n\t\t),\n\t\tQuit: key.NewBinding(\n\t\t\tkey.WithKeys(\"esc\"),\n\t\t\tkey.WithHelp(\"esc\", \"quit\"),\n\t\t),\n\t\tNegative: key.NewBinding(\n\t\t\tkey.WithKeys(\"n\", \"N\", \"q\"),\n\t\t\tkey.WithHelp(\"n\", negative),\n\t\t),\n\t\tAffirmative: key.NewBinding(\n\t\t\tkey.WithKeys(\"y\", \"Y\"),\n\t\t\tkey.WithHelp(\"y\", affirmative),\n\t\t),\n\t\tToggle: key.NewBinding(\n\t\t\tkey.WithKeys(\n\t\t\t\t\"left\",\n\t\t\t\t\"h\",\n\t\t\t\t\"ctrl+n\",\n\t\t\t\t\"shift+tab\",\n\t\t\t\t\"right\",\n\t\t\t\t\"l\",\n\t\t\t\t\"ctrl+p\",\n\t\t\t\t\"tab\",\n\t\t\t),\n\t\t\tkey.WithHelp(\"←→\", \"toggle\"),\n\t\t),\n\t\tSubmit: key.NewBinding(\n\t\t\tkey.WithKeys(\"enter\"),\n\t\t\tkey.WithHelp(\"enter\", \"submit\"),\n\t\t),\n\t}\n}\n\ntype keymap struct {\n\tAbort       key.Binding\n\tQuit        key.Binding\n\tNegative    key.Binding\n\tAffirmative key.Binding\n\tToggle      key.Binding\n\tSubmit      key.Binding\n}\n\n// FullHelp implements help.KeyMap.\nfunc (k keymap) FullHelp() [][]key.Binding { return nil }\n\n// ShortHelp implements help.KeyMap.\nfunc (k keymap) ShortHelp() []key.Binding {\n\treturn []key.Binding{k.Toggle, k.Submit, k.Affirmative, k.Negative}\n}\n\ntype model struct {\n\tprompt      string\n\taffirmative string\n\tnegative    string\n\tquitting    bool\n\tshowHelp    bool\n\thelp        help.Model\n\tkeys        keymap\n\n\tshowOutput   bool\n\tconfirmation bool\n\n\tdefaultSelection bool\n\n\t// styles\n\tpromptStyle     lipgloss.Style\n\tselectedStyle   lipgloss.Style\n\tunselectedStyle lipgloss.Style\n\tpadding         []int\n}\n\nfunc (m model) Init() tea.Cmd { return nil }\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\treturn m, nil\n\tcase tea.KeyMsg:\n\t\tswitch {\n\t\tcase key.Matches(msg, m.keys.Abort):\n\t\t\tm.confirmation = false\n\t\t\treturn m, tea.Interrupt\n\t\tcase key.Matches(msg, m.keys.Quit):\n\t\t\tm.confirmation = false\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, m.keys.Negative):\n\t\t\tm.confirmation = false\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, m.keys.Toggle):\n\t\t\tif m.negative == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tm.confirmation = !m.confirmation\n\t\tcase key.Matches(msg, m.keys.Submit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, m.keys.Affirmative):\n\t\t\tm.quitting = true\n\t\t\tm.confirmation = true\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() string {\n\tif m.quitting {\n\t\treturn \"\"\n\t}\n\n\tvar aff, neg string\n\n\tif m.confirmation {\n\t\taff = m.selectedStyle.Render(m.affirmative)\n\t\tneg = m.unselectedStyle.Render(m.negative)\n\t} else {\n\t\taff = m.unselectedStyle.Render(m.affirmative)\n\t\tneg = m.selectedStyle.Render(m.negative)\n\t}\n\n\t// If the option is intentionally empty, do not show it.\n\tif m.negative == \"\" {\n\t\tneg = \"\"\n\t}\n\n\tparts := []string{\n\t\tm.promptStyle.Render(m.prompt) + \"\\n\",\n\t\tlipgloss.JoinHorizontal(lipgloss.Left, aff, neg),\n\t}\n\n\tif m.showHelp {\n\t\tparts = append(parts, \"\", m.help.View(m.keys))\n\t}\n\n\treturn lipgloss.NewStyle().\n\t\tPadding(m.padding...).\n\t\tRender(lipgloss.JoinVertical(\n\t\t\tlipgloss.Left,\n\t\t\tparts...,\n\t\t))\n}\n"
  },
  {
    "path": "confirm/options.go",
    "content": "package confirm\n\nimport (\n\t\"time\"\n\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Options is the customization options for the confirm command.\ntype Options struct {\n\tDefault     bool   `help:\"Default confirmation action\" default:\"true\"`\n\tShowOutput  bool   `help:\"Print prompt and chosen action to output\" default:\"false\"`\n\tAffirmative string `help:\"The title of the affirmative action\" default:\"Yes\"`\n\tNegative    string `help:\"The title of the negative action\" default:\"No\"`\n\tPrompt      string `arg:\"\" help:\"Prompt to display.\" default:\"Are you sure?\"`\n\t//nolint:staticcheck\n\tPromptStyle style.Styles `embed:\"\" prefix:\"prompt.\" help:\"The style of the prompt\" set:\"defaultMargin=0 0 0 1\" set:\"defaultForeground=#7571F9\" set:\"defaultBold=true\" envprefix:\"GUM_CONFIRM_PROMPT_\"`\n\t//nolint:staticcheck\n\tSelectedStyle style.Styles `embed:\"\" prefix:\"selected.\" help:\"The style of the selected action\" set:\"defaultBackground=212\" set:\"defaultForeground=230\" set:\"defaultPadding=0 3\" set:\"defaultMargin=0 1\" envprefix:\"GUM_CONFIRM_SELECTED_\"`\n\t//nolint:staticcheck\n\tUnselectedStyle style.Styles  `embed:\"\" prefix:\"unselected.\" help:\"The style of the unselected action\" set:\"defaultBackground=235\" set:\"defaultForeground=254\" set:\"defaultPadding=0 3\" set:\"defaultMargin=0 1\" envprefix:\"GUM_CONFIRM_UNSELECTED_\"`\n\tShowHelp        bool          `help:\"Show help key binds\" negatable:\"\" default:\"true\" env:\"GUM_CONFIRM_SHOW_HELP\"`\n\tTimeout         time.Duration `help:\"Timeout until confirm returns selected value or default if provided\" default:\"0s\" env:\"GUM_CONFIRM_TIMEOUT\"`\n\tPadding         string        `help:\"Padding\" default:\"${defaultPadding}\" group:\"Style Flags\" env:\"GUM_CONFIRM_PADDING\"`\n}\n"
  },
  {
    "path": "cursor/cursor.go",
    "content": "// Package cursor provides cursor modes.\npackage cursor\n\nimport (\n\t\"github.com/charmbracelet/bubbles/cursor\"\n)\n\n// Modes maps strings to cursor modes.\nvar Modes = map[string]cursor.Mode{\n\t\"blink\":  cursor.CursorBlink,\n\t\"hide\":   cursor.CursorHide,\n\t\"static\": cursor.CursorStatic,\n}\n"
  },
  {
    "path": "default.nix",
    "content": "{ pkgs }:\n\npkgs.buildGoModule rec {\n  pname = \"gum\";\n  version = \"0.15.2\";\n\n  src = ./.;\n\n  vendorHash = \"sha256-TK2Fc4bTkiSpyYrg4dJOzamEnii03P7kyHZdah9izqY=\";\n\n  ldflags = [ \"-s\" \"-w\" \"-X=main.Version=${version}\" ];\n}\n"
  },
  {
    "path": "examples/.gitignore",
    "content": "*.gif\n*.png\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Glamour\n\nA casual introduction. 你好世界！\n\n## Let's talk about artichokes\n\nThe artichoke is mentioned as a garden\nplant in the 8th century BC by Homer\nand Hesiod. The naturally occurring\nvariant of the artichoke, the cardoon,\nwhich is native to the Mediterranean\narea, also has records of use as a\nfood among the ancient Greeks and\nRomans. Pliny the Elder mentioned\ngrowing of 'carduus' in Carthage\nand Cordoba.\n\nHe holds him with his skinny hand,\nThere was ship,' quoth he.\n'Hold off! unhand me, grey-beard loon!'\nAn artichoke dropt he.\n\n## Other foods worth mentioning\n\n1. Carrots\n2. Celery\n3. Tacos\n• Soft\n• Hard\n4. Cucumber\n\n## Things to eat today\n\n* Carrots\n* Ramen\n* Currywurst\n"
  },
  {
    "path": "examples/choose.tape",
    "content": "Output choose.gif\n\nSet Width 1000\nSet Height 430\nSet Shell bash\n\nType \"gum choose {1..5}\"\nSleep 500ms\nEnter\nSleep 500ms\nDown@250ms 3\nSleep 500ms\nUp@250ms 2\nEnter\nSleep 1.5s\nCtrl+L\nSleep 500ms\nType \"gum choose --limit 2 Banana Cherry Orange\"\nSleep 500ms\nEnter\nSleep 500ms\nType@250ms \"jxjxk\"\nSleep 1s\nEnter\nSleep 2s\n\n"
  },
  {
    "path": "examples/commit.sh",
    "content": "#!/bin/sh\n\n# This script is used to write a conventional commit message.\n# It prompts the user to choose the type of commit as specified in the\n# conventional commit spec. And then prompts for the summary and detailed\n# description of the message and uses the values provided. as the summary and\n# details of the message.\n#\n# If you want to add a simpler version of this script to your dotfiles, use:\n#\n# alias gcm='git commit -m \"$(gum input)\" -m \"$(gum write)\"'\n\n# if [ -z \"$(git status -s -uno | grep -v '^ ' | awk '{print $2}')\" ]; then\n#     gum confirm \"Stage all?\" && git add .\n# fi\n\nTYPE=$(gum choose \"fix\" \"feat\" \"docs\" \"style\" \"refactor\" \"test\" \"chore\" \"revert\")\nSCOPE=$(gum input --placeholder \"scope\")\n\n# Since the scope is optional, wrap it in parentheses if it has a value.\ntest -n \"$SCOPE\" && SCOPE=\"($SCOPE)\"\n\n# Pre-populate the input with the type(scope): so that the user may change it\nSUMMARY=$(gum input --value \"$TYPE$SCOPE: \" --placeholder \"Summary of this change\")\nDESCRIPTION=$(gum write --placeholder \"Details of this change\")\n\n# Commit these changes if user confirms\ngum confirm \"Commit changes?\" && git commit -m \"$SUMMARY\" -m \"$DESCRIPTION\"\n"
  },
  {
    "path": "examples/commit.tape",
    "content": "Output commit.gif\n\nSet Shell \"bash\"\nSet FontSize 32\nSet Width 1200\nSet Height 600\n\nType \"./commit.sh\" Sleep 500ms  Enter\n\nSleep 1s\nDown@250ms 2\nSleep 500ms\nEnter\n\nSleep 500ms\n\nType \"gum\"\n\nSleep 500ms\nEnter\n\nSleep 1s\n\nType \"Gum is sooo tasty\"\nSleep 500ms\n\nEnter\n\nSleep 1s\n\nType@65ms \"I love bubble gum.\"\nSleep 500ms\nAlt+Enter\nSleep 500ms\nAlt+Enter\nSleep 500ms\nType \"This commit shows how much I love chewing bubble gum!!!\"\nSleep 500ms\nEnter\n\nSleep 1s\n\nLeft@400ms 3\n\nSleep 1s\n"
  },
  {
    "path": "examples/confirm.tape",
    "content": "Output confirm.gif\n\nSet Width 1000\nSet Height 350\nSet Shell bash\n\nSleep 500ms\nType \"gum confirm && echo 'Me too!' || echo 'Me neither.'\"\nSleep 1s\nEnter\nSleep 1s\nRight\nSleep 500ms\nLeft\nSleep 500ms\nEnter\nSleep 1.5s\nCtrl+L\nType \"gum confirm && echo 'Me too!' || echo 'Me neither.'\"\nSleep 500ms\nEnter\nSleep 500ms\nRight\nSleep 500ms\nEnter\nSleep 1s\n"
  },
  {
    "path": "examples/convert-to-gif.sh",
    "content": "#!/bin/bash\n\n# This script converts some video to a GIF. It prompts the user to select an\n# video file with `gum filter` Set the frame rate, desired width, and max\n# colors to use Then, converts the video to a GIF.\n\nINPUT=$(gum filter --placeholder \"Input file\")\nFRAMERATE=$(gum input --prompt \"Frame rate: \" --placeholder \"Frame Rate\" --prompt.foreground 240 --value \"50\")\nWIDTH=$(gum input --prompt \"Width: \" --placeholder \"Width\" --prompt.foreground 240 --value \"1200\")\nMAXCOLORS=$(gum input --prompt \"Max Colors: \" --placeholder \"Max Colors\" --prompt.foreground 240 --value \"256\")\n\nBASENAME=$(basename \"$INPUT\")\nBASENAME=\"${BASENAME%%.*}\"\n\ngum spin --title \"Converting to GIF\" -- ffmpeg -i \"$INPUT\" -vf \"fps=$FRAMERATE,scale=$WIDTH:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=$MAXCOLORS[p];[s1][p]paletteuse\" \"$BASENAME.gif\"\n"
  },
  {
    "path": "examples/customize.tape",
    "content": "Output customize.gif\n\nSet Width 1000\nSet Height 350\nSet Shell bash\n\nSleep 1s\nType `gum input --cursor.foreground \"#F4AC45\" \\` Enter\nType `--prompt.foreground \"#04B575\" --prompt \"What's up? \" \\` Enter\nType `--placeholder \"Not much, you?\" --value \"Not much, you?\" \\` Enter\nType `--width 80` Enter\nSleep 1s\nCtrl+A\nSleep 1s\nCtrl+E\nSleep 1s\nCtrl+U\nSleep 1s\n\n"
  },
  {
    "path": "examples/demo.sh",
    "content": "#!/bin/bash\n\ngum style --border normal --margin \"1\" --padding \"1 2\" --border-foreground 212 \"Hello, there! Welcome to $(gum style --foreground 212 'Gum').\"\nNAME=$(gum input --placeholder \"What is your name?\")\n\necho -e \"Well, it is nice to meet you, $(gum style --foreground 212 \"$NAME\").\"\n\nsleep 1; clear\n\necho -e \"Can you tell me a $(gum style --italic --foreground 99 'secret')?\\n\"\n\ngum write --placeholder \"I'll keep it to myself, I promise!\" > /dev/null # we keep the secret to ourselves\n\nclear; echo \"What should I do with this information?\"; sleep 1\n\nREAD=\"Read\"; THINK=\"Think\"; DISCARD=\"Discard\"\nACTIONS=$(gum choose --no-limit \"$READ\" \"$THINK\" \"$DISCARD\")\n\nclear; echo \"One moment, please.\"\n\ngrep -q \"$READ\" <<< \"$ACTIONS\" && gum spin -s line --title \"Reading the secret...\" -- sleep 1\ngrep -q \"$THINK\" <<< \"$ACTIONS\" && gum spin -s pulse --title \"Thinking about your secret...\" -- sleep 1\ngrep -q \"$DISCARD\" <<< \"$ACTIONS\" && gum spin -s monkey --title \" Discarding your secret...\" -- sleep 2\n\nsleep 1; clear\n\nGUM=$(echo -e \"Cherry\\nGrape\\nLime\\nOrange\" | gum filter --placeholder \"Favorite flavor?\")\necho \"I'll keep that in mind!\"\n\nsleep 1; clear\n\necho \"Do you like $(gum style --foreground \"#04B575\" \"Bubble Gum?\")\"\nsleep 1\n\nCHOICE=$(gum choose --item.foreground 250 \"Yes\" \"No\" \"It's complicated\")\n\n[[ \"$CHOICE\" == \"Yes\" ]] && echo \"I thought so, $(gum style --bold \"Bubble Gum\") is the best.\" || echo \"I'm sorry to hear that.\"\n\nsleep 1\n\ngum spin --title \"Chewing some $(gum style --foreground \"#04B575\" \"$GUM\") bubble gum...\" -- sleep 2.5\n\nclear\n\nNICE_MEETING_YOU=$(gum style --height 5 --width 20 --padding '1 3' --border double --border-foreground 57 \"Nice meeting you, $(gum style --foreground 212 \"$NAME\"). See you soon!\")\nCHEW_BUBBLE_GUM=$(gum style --width 17 --padding '1 3' --border double --border-foreground 212 \"Go chew some $(gum style --foreground \"#04B575\" \"$GUM\") bubble gum.\")\ngum join --horizontal \"$NICE_MEETING_YOU\" \"$CHEW_BUBBLE_GUM\"\n"
  },
  {
    "path": "examples/demo.tape",
    "content": "Output ./demo.gif\n\nSet Shell bash\n\nSet FontSize 22\nSet Width 800\nSet Height 450\n\nType \"./demo.sh\"\nEnter\nSleep 1s\nType \"Walter\"\nSleep 500ms\nEnter\n\nSleep 2s\n\nType \"Nope, sorry!\"\nSleep 500ms\nAlt+Enter\nSleep 200ms\nAlt+Enter\nSleep 500ms\nType \"I don't trust you.\"\nSleep 1s\nEnter\n\nSleep 2s\n\nType \"x\" Sleep 250ms Type \"j\" Sleep 250ms\nType \"x\" Sleep 250ms Type \"j\" Sleep 250ms\nType \"x\" Sleep 1s\n\nEnter\n\nSleep 6s\n\nType \"li\"\nSleep 1s\nEnter\n\nSleep 3s\nDown@500ms 2\nUp@500ms 2\nSleep 1s\nEnter\n\n\nSleep 6s\n"
  },
  {
    "path": "examples/diyfetch",
    "content": "#!/bin/sh\n\n#  ____ _____   ____      _       _\n# |  _ \\_ _\\ \\ / / _| ___| |_ ___| |__\n# | | | | | \\ V / |_ / _ \\ __/ __| '_ \\\n# | |_| | |  | ||  _|  __/ || (__| | | |\n# |____/___| |_||_|  \\___|\\__\\___|_| |_|\n#\n# About:\n#   DIYfetch it the shell script template for writing fetch tool\n#   utilizing `gum join` command (https://github.com/charmbracelet/gum#join).\n#\n#   This script is written in POSIX-shell for portability\n#   feel free to switch it to any scripting language that you prefer.\n#\n# Note:\n#   When copy ANSI string from random script make sure to replace \"\\033\" and \"\\e\" to \"\u001b\"\n#   or wrap it in `$(printf '%b' \"<ansi_string>\")`.\n#\n# URL: https://github.com/info-mono/diyfetch\n\n\n# Prepare ------------------------------------------------------------------------------------------\n\n# You can lookup the color codes on https://wikipedia.org/wiki/ANSI_escape_code#8-bit\nmain_color=4\n\n# You can add some eye candy icons with Emoji of use Nerd Fonts (https://www.nerdfonts.com).\ninfo=$(gum style \"\u001b[1;38;5;${main_color}m${USER}\u001b[0m@\u001b[1;38;5;${main_color}m$(hostname)\u001b[0m\n----------------\n\u001b[1;38;5;${main_color}mOS:        \u001b[0m<your_os>\n\u001b[1;38;5;${main_color}mKERNEL:    \u001b[0m$(uname -sr)\n\u001b[1;38;5;${main_color}mUPTIME:    \u001b[0m$(uptime -p | cut -c 4-)\n\u001b[1;38;5;${main_color}mSHELL:     \u001b[0m$(basename \"${SHELL}\")\n\u001b[1;38;5;${main_color}mEDITOR:    \u001b[0m$(basename \"${EDITOR:-<your_editor>}\")\n\u001b[1;38;5;${main_color}mDE:        \u001b[0m<your_de>\n\u001b[1;38;5;${main_color}mWM:        \u001b[0m<your_wm>\n\u001b[1;38;5;${main_color}mTERMINAL:  \u001b[0m<your_terminal>\")\n\n# You can get OS arts on https://github.com/info-mono/os-ansi\n# copy the raw data of the .ansi file then paste it down below.\nart=$(gum style '    \u001b[34m___\u001b[0m\n   \u001b[34m(\u001b[0m.. \u001b[34m|\u001b[0m\n   \u001b[34m(\u001b[33m<> \u001b[34m|\u001b[0m\n  \u001b[34m/ \u001b[0m__  \u001b[34m\\\u001b[0m\n \u001b[34m( \u001b[0m/  \\\u001b[34m/ |\u001b[0m\n\u001b[33m_\u001b[34m/\\ \u001b[0m__)\u001b[34m/\u001b[33m_\u001b[34m)\u001b[0m\n\u001b[33m\\/\u001b[34m-____\u001b[33m\\/\u001b[0m')\n\n# You can generate colorstrip using https://github.com/info-mono/colorstrip\ncolor=$(gum style '\u001b[0;30m███\u001b[0;31m███\u001b[0;32m███\u001b[0;33m███\u001b[0;34m███\u001b[0;35m███\u001b[0;36m███\u001b[0;37m███\u001b[0m\n\u001b[0;1;90m███\u001b[0;1;91m███\u001b[0;1;92m███\u001b[0;1;93m███\u001b[0;1;94m███\u001b[0;1;95m███\u001b[0;1;96m███\u001b[0;1;97m███\u001b[0m')\n\n\n# Display ------------------------------------------------------------------------------------------\n\n# The code in this section is to display the fetch adaptively to the terminal's size.\n# If you just want a static fetch display, you can just use something like this:\n#\n#   group_info_color=$(gum join --vertical \"${info}\" '' \"${color}\")\n#   gum join --horizontal --align center '  ' \"${art}\" '  ' \"${group_info_color}\"\n\nterminal_size=$(stty size)\nterminal_height=${terminal_size% *}\nterminal_width=${terminal_size#* }\n\n# Acknowledge of how high the shell prompt is so the prompt don't push the fetch out.\nprompt_height=${PROMPT_HEIGHT:-1}\n\nprint_test() {\n\tno_color=$(printf '%b' \"${1}\" | sed -e 's/\\x1B\\[[0-9;]*[JKmsu]//g')\n\n\t[ \"$(printf '%s' \"${no_color}\" | wc --lines)\" -gt $(( terminal_height - prompt_height )) ] && return 1\n\t[ \"$(printf '%s' \"${no_color}\" | wc --max-line-length)\" -gt \"${terminal_width}\" ] && return 1\n\n\tgum style --align center --width=\"${terminal_width}\" \"${1}\" ''\n\tprintf '%b' \"\\033[A\"\n\n\texit 0\n}\n\n\n# Paper layout\nprint_test \"$(gum join --vertical --align center \"${art}\" '' \"${info}\" '' \"${color}\")\"\n\n# Classic layout\ngroup_info_color=$(gum join --vertical \"${info}\" '' \"${color}\")\nprint_test \"$(gum join --horizontal --align center \"${art}\" '  ' \"${group_info_color}\")\"\n\n# Hybrid layout\ngroup_art_info=$(gum join --horizontal --align center \"${art}\" '  ' \"${info}\")\nprint_test \"$(gum join --vertical --align center \"${group_art_info}\" '' \"${color}\")\"\n\n# Other layout\nprint_test \"$(gum join --vertical --align center \"${art}\" '' \"${info}\")\"\nprint_test \"${group_art_info}\"\nprint_test \"${group_info_color}\"\nprint_test \"${info}\"\n\nexit 1"
  },
  {
    "path": "examples/fav.txt",
    "content": "Banana\n"
  },
  {
    "path": "examples/file.tape",
    "content": "Output file.gif\nSet Width 800\nSet Height 525\nSet Shell bash\n\nType \"gum file ..\"\nEnter\nSleep 1s\nDown@150ms 6\nSleep 1s\nEnter\nSleep 1s\nType \"j\"\nSleep 1s\n\n"
  },
  {
    "path": "examples/filter-key-value.sh",
    "content": "#!/bin/bash\n\nexport LIST=$(cat <<END\nCow:Moo\nCat:Meow\nDog:Woof\nEND\n)\n\nANIMAL=$(echo \"$LIST\" | cut -d':' -f1 | gum filter)\nSOUND=$(echo \"$LIST\" | grep $ANIMAL | cut -d':' -f2)\n\necho \"The $ANIMAL goes $SOUND\"\n"
  },
  {
    "path": "examples/flavors.txt",
    "content": "Banana\nCherry\nOrange\nStrawberry\n"
  },
  {
    "path": "examples/format.ansi",
    "content": "\u001b[38;2;90;86;224m> \u001b[0mgum format -t code < main.go\n\n  \n\u001b[38;5;204m\u001b[0m\u001b[38;5;252m\u001b[0m  \u001b[38;5;252m \u001b[0m\u001b[38;5;252m \u001b[0m\u001b[38;5;204mpackage\u001b[0m\u001b[38;5;251m \u001b[0m\u001b[38;5;251mmain\u001b[0m\u001b[38;5;251m\u001b[0m\n\u001b[0m\u001b[38;5;252m\u001b[0m  \u001b[38;5;252m \u001b[0m\u001b[38;5;252m \u001b[0m\u001b[38;5;251m\u001b[0m\n\u001b[0m\u001b[38;5;204m\u001b[0m\u001b[38;5;252m\u001b[0m  \u001b[38;5;252m \u001b[0m\u001b[38;5;252m \u001b[0m\u001b[38;5;204mimport\u001b[0m\u001b[38;5;251m \u001b[0m\u001b[38;5;173m\"fmt\"\u001b[0m\u001b[38;5;251m\u001b[0m\n\u001b[0m\u001b[38;5;252m\u001b[0m  \u001b[38;5;252m \u001b[0m\u001b[38;5;252m \u001b[0m\u001b[38;5;251m\u001b[0m\n\u001b[0m\u001b[38;5;39m\u001b[0m\u001b[38;5;252m\u001b[0m  \u001b[38;5;252m \u001b[0m\u001b[38;5;252m \u001b[0m\u001b[38;5;39mfunc\u001b[0m\u001b[38;5;251m \u001b[0m\u001b[38;5;42mmain\u001b[0m\u001b[38;5;187m()\u001b[0m\u001b[38;5;251m \u001b[0m\u001b[38;5;187m{\u001b[0m\u001b[38;5;251m\u001b[0m\n\u001b[0m\u001b[38;5;252m\u001b[0m  \u001b[38;5;252m \u001b[0m\u001b[38;5;252m \u001b[0m\u001b[38;5;251m    \u001b[0m\u001b[38;5;251mfmt\u001b[0m\u001b[38;5;187m.\u001b[0m\u001b[38;5;42mPrintln\u001b[0m\u001b[38;5;187m(\u001b[0m\u001b[38;5;173m\"Charm_™ Gum\"\u001b[0m\u001b[38;5;187m)\u001b[0m\u001b[38;5;251m\u001b[0m\n\u001b[0m\u001b[38;5;187m\u001b[0m\u001b[38;5;252m\u001b[0m  \u001b[38;5;252m \u001b[0m\u001b[38;5;252m \u001b[0m\u001b[38;5;187m}\u001b[0m\u001b[38;5;251m\u001b[0m\n\u001b[0m\u001b[38;5;252m\u001b[0m  \u001b[38;5;252m \u001b[0m\u001b[38;5;252m \u001b[0m\u001b[38;5;251m\u001b[0m\n\u001b[0m\n"
  },
  {
    "path": "examples/git-branch-manager.sh",
    "content": "#! /bin/sh\n\n# This script is used to manage git branches such as delete, update, and rebase\n# them. It prompts the user to choose the branches and the action they want to\n# perform.\n#\n# For an explanation on the script and tutorial on how to create it, watch:\n# https://www.youtube.com/watch?v=tnikefEuArQ\n\nGIT_COLOR=\"#f14e32\"\n\ngit_color_text () {\n  gum style --foreground \"$GIT_COLOR\" \"$1\"\n}\n\nget_branches () {\n  if [ ${1+x} ]; then\n    gum choose --selected.foreground=\"$GIT_COLOR\" --limit=\"$1\" $(git branch --format=\"%(refname:short)\")\n  else\n    gum choose --selected.foreground=\"$GIT_COLOR\" --no-limit $(git branch --format=\"%(refname:short)\")\n  fi\n}\n\ngit rev-parse --git-dir > /dev/null 2>&1\n\nif [ $? -ne 0 ];\nthen\n  echo \"$(git_color_text \"!!\") Must be run in a $(git_color_text \"git\") repo\" \n  exit 1\nfi\n\ngum style \\\n  --border normal \\\n  --margin \"1\" \\\n  --padding \"1\" \\\n  --border-foreground \"$GIT_COLOR\" \\\n  \"$(git_color_text ' Git') Branch Manager\"\n\necho \"Choose $(git_color_text 'branches') to operate on:\"\nbranches=$(get_branches)\n\necho \"\"\necho \"Choose a $(git_color_text \"command\"):\"\ncommand=$(gum choose --cursor.foreground=\"$GIT_COLOR\" rebase delete update)\necho \"\"\n\necho $branches | tr \" \" \"\\n\" | while read -r branch\ndo\n  case $command in\n    rebase)\n      base_branch=$(get_branches 1)\n      git fetch origin\n      git checkout \"$branch\"\n      git rebase \"origin/$base_branch\"\n      ;;\n    delete)\n      git branch -D \"$branch\"\n      ;;\n    update)\n      git checkout \"$branch\"\n      git pull --ff-only\n      ;;\n  esac\ndone\n"
  },
  {
    "path": "examples/git-stage.sh",
    "content": "#!/bin/bash\n\nADD=\"Add\"\nRESET=\"Reset\"\n\nACTION=$(gum choose \"$ADD\" \"$RESET\")\n\nif [ \"$ACTION\" == \"$ADD\" ]; then\n    git status --short | cut -c 4- | gum choose --no-limit | xargs git add\nelse\n    git status --short | cut -c 4- | gum choose --no-limit | xargs git restore\nfi\n"
  },
  {
    "path": "examples/gum.js",
    "content": "const { spawn } = require(\"child_process\");\n\nconst activities = [\"walking\", \"running\", \"cycling\", \"driving\", \"transport\"];\n\nconsole.log(\"What's your favorite activity?\")\nconst gum = spawn(\"gum\", [\"choose\", ...activities]);\n\ngum.stderr.pipe(process.stderr);\n\ngum.stdout.on(\"data\", data => {\n    const activity = data.toString().trim();\n    console.log(`I like ${activity} too!`);\n});\n"
  },
  {
    "path": "examples/gum.py",
    "content": "import subprocess\n\nprint(\"What's your favorite language?\")\n\nresult = subprocess.run([\"gum\", \"choose\", \"Go\", \"Python\"], stdout=subprocess.PIPE, text=True)\n\nprint(f\"I like {result.stdout.strip()}, too!\")\n"
  },
  {
    "path": "examples/gum.rb",
    "content": "puts 'What is your name?'\nname = `gum input --placeholder \"Your name\"`.chomp\n\nputs \"Hello #{name}!\"\n\nputs 'Pick your 2 favorite colors'\n\nCOLORS = {\n  'Red' => '#FF0000',\n  'Blue' => '#0000FF',\n  'Green' => '#00FF00',\n  'Yellow' => '#FFFF00',\n  'Orange' => '#FFA500',\n  'Purple' => '#800080',\n  'Pink' => '#FF00FF'\n}.freeze\n\ncolors = `gum choose #{COLORS.keys.join(' ')} --limit 2`.chomp.split(\"\\n\")\n\nif colors.length == 2\n  first = `gum style --foreground '#{COLORS[colors[0]]}' '#{colors[0]}'`.chomp\n  second = `gum style --foreground '#{COLORS[colors[1]]}' '#{colors[1]}'`.chomp\n  puts \"You chose #{first} and #{second}.\"\nelsif colors.length == 1\n  first = `gum style --foreground '#{COLORS[colors[0]]}' '#{colors[0]}'`.chomp\n  puts \"You chose #{first}.\"\nelse\n  puts \"You didn't pick any colors!\"\nend\n"
  },
  {
    "path": "examples/input.tape",
    "content": "Output input.gif\n\nSet Width 800\nSet Height 250\nSet Shell bash\n\nSleep 1s\nType `gum input --placeholder \"What's up?\"`\nSleep 1s\nEnter\nSleep 1s\nType \"Not much, you?\"\nSleep 1s\nEnter\nSleep 1s\n\n"
  },
  {
    "path": "examples/kaomoji.sh",
    "content": "#!/usr/bin/env bash\n\n# If the user passes '-h', '--help', or 'help' print out a little bit of help.\n# text.\ncase \"$1\" in\n\t\"-h\" | \"--help\" | \"help\")\n        printf 'Generate kaomojis on request.\\n\\n'\n        printf 'Usage: %s [kind]\\n' \"$(basename \"$0\")\"\n        exit 1\n        ;;\nesac\n\n# The user can pass an argument like \"bear\" or \"angry\" to specify the general\n# kind of Kaomoji produced.\nsentiment=\"\"\nif [[ $1 != \"\" ]]; then\n\tsentiment=\" $1\"\nfi\n\n# Ask mods to generate Kaomojis. Save the output in a variable.\nkaomoji=\"$(mods \"generate 10${sentiment} kaomojis. number them and put each one on its own line.\")\"\nif [[ $kaomoji == \"\" ]]; then\n\texit 1\nfi\n\n# Pipe mods output to gum so the user can choose the perfect kaomoji. Save that\n# choice in a variable. Also note that we're using cut to drop the item number\n# in front of the Kaomoji.\nchoice=\"$(echo \"$kaomoji\" | gum choose | cut -d ' ' -f 2)\"\nif [[ $choice == \"\" ]]; then\n\texit 1\nfi\n\n# If xsel (X11) or pbcopy (macOS) exists, copy to the clipboard. If not, just\n# print the Kaomoji.\nif command -v xsel &> /dev/null; then\n\tprintf '%s' \"$choice\" | xclip -sel clip # X11\nelif command -v pbcopy &> /dev/null; then\n\tprintf '%s' \"$choice\" | pbcopy # macOS\nelse\n\t# We can't copy, so just print it out.\n\tprintf 'Here you go: %s\\n' \"$choice\"\n\texit 0\nfi\n\n# We're done!\nprintf 'Copied %s to the clipboard\\n' \"$choice\"\n"
  },
  {
    "path": "examples/magic.sh",
    "content": "#!/bin/bash\n\n# Always ask for permission!\necho \"Do you want to see a magic trick?\"\n\nYES=\"Yes, please!\"\nNO=\"No, thank you!\"\n\nCHOICE=$(gum choose \"$YES\" \"$NO\")\n\nif [ \"$CHOICE\" != \"$YES\" ]; then\n    echo \"Alright, then. Have a nice day!\"\n    exit 1\nfi\n\n\n# Let the magic begin.\necho \"Alright, then. Let's begin!\"\ngum style --foreground 212 \"Pick a card, any card...\"\n\nCARD=$(gum choose \"Ace (A)\" \"Two (2)\" \"Three (3)\" \"Four (4)\" \"Five (5)\" \"Six (6)\" \"Seven (7)\" \"Eight (8)\" \"Nine (9)\" \"Ten (10)\" \"Jack (J)\" \"Queen (Q)\" \"King (K)\")\nSUIT=$(gum choose \"Hearts (♥)\" \"Diamonds (♦)\" \"Clubs (♣)\" \"Spades (♠)\")\n\ngum style --foreground 212 \"You picked the $CARD of $SUIT.\"\n\nSHORT_CARD=$(echo $CARD | cut -d' ' -f2 | tr -d '()')\nSHORT_SUIT=$(echo $SUIT | cut -d' ' -f2 | tr -d '()')\n\nTOP_LEFT=$(gum join --vertical \"$SHORT_CARD\" \"$SHORT_SUIT\")\nBOTTOM_RIGHT=$(gum join --vertical \"$SHORT_SUIT\" \"$SHORT_CARD\")\n\nTOP_LEFT=$(gum style --width 10 --height 5 --align left \"$TOP_LEFT\")\nBOTTOM_RIGHT=$(gum style --width 10 --align right \"$BOTTOM_RIGHT\")\n\nif [[ \"$SHORT_SUIT\" == \"♥\" || \"$SHORT_SUIT\" == \"♦\" ]]; then\n    CARD_COLOR=\"1\" # Red\nelse\n    CARD_COLOR=\"7\" # Black\nfi\n\ngum style --border rounded --padding \"0 1\" --margin 2 --border-foreground \"$CARD_COLOR\" --foreground \"$CARD_COLOR\" \"$(gum join --vertical \"$TOP_LEFT\" \"$BOTTOM_RIGHT\")\"\n\necho \"Is this your card?\"\n\ngum choose \"Omg, yes!\" \"Nope, sorry!\"\n"
  },
  {
    "path": "examples/pager.tape",
    "content": "Output pager.gif\n\nSet Shell bash\nSet Width 900\nSet Height 750\n\nSleep 1s\nType \"gum pager < README.md\"\nEnter\nSleep 1.5s\nDown@100ms 25\nSleep 1s\nUp@100ms 25\nSleep 3s\n\n"
  },
  {
    "path": "examples/posix.sh",
    "content": "#!/bin/sh\n\necho \"What's your favorite shell?\"\n\ngum choose \"Posix\" \"Bash\" \"Zsh\" \"Fish\" \"Elvish\"\n"
  },
  {
    "path": "examples/skate.sh",
    "content": "#!/bin/sh\n\n# Building a simple `skate` TUI with gum to allow you to select a database and\n# pick a value from skate.\n\nDATABASE=$(skate list-dbs | gum choose)\nskate list --keys-only \"$DATABASE\" | gum filter | xargs -I {} skate get {}\"$DATABASE\"\n"
  },
  {
    "path": "examples/spin.tape",
    "content": "Output spin.gif\n\nSet Shell bash\nSet Width 1200\nSet Height 300\nSet FontSize 36\n\nSleep 500ms\nType `gum spin --title \"Buying Gum...\" -- sleep 5`\nSleep 1s\nEnter\nSleep 4s\n\n"
  },
  {
    "path": "examples/story.txt",
    "content": "Once upon a time\nIn a land far, far away....\n"
  },
  {
    "path": "examples/test.sh",
    "content": "#!/bin/sh\n\n# Choose\ngum choose Foo Bar Baz\ngum choose Choose One Item --cursor \"* \" --cursor.foreground 99 --selected.foreground 99\ngum choose Pick Two Items Maximum --limit 2 --cursor \"* \" --cursor-prefix \"(•) \" --selected-prefix \"(x) \" --unselected-prefix \"( ) \" --cursor.foreground 99 --selected.foreground 99\ngum choose Unlimited Choice Of Items --no-limit --cursor \"* \" --cursor-prefix \"(•) \" --selected-prefix \"(x) \" --unselected-prefix \"( ) \" --cursor.foreground 99 --selected.foreground 99\n\n# Confirm\ngum confirm \"Testing?\"\ngum confirm \"No?\" --default=false --affirmative \"Okay.\" --negative \"Cancel.\"\n\n# Filter\ngum filter\necho {1..500} | sed 's/ /\\n/g' | gum filter\necho {1..500} | sed 's/ /\\n/g' | gum filter --indicator \">\" --placeholder \"Pick a number...\" --indicator.foreground 1 --text.foreground 2 --match.foreground 3 --prompt.foreground 4 --height 5\n\n# Format\necho \"# Header\\nBody\" | gum format \necho 'package main\\n\\nimport \"fmt\"\\n\\nfunc main() {\\n\\tfmt.Println(\"Hello, Gum!\")\\n}\\n' | gum format -t code\necho \":candy:\" | gum format -t emoji\necho '{{ Bold \"Bold\" }}' | gum format -t template\n\n# Input\ngum input\ngum input --prompt \"Email: \" --placeholder \"john@doe.com\" --prompt.foreground 99 --cursor.foreground 99 --width 50\ngum input --password --prompt \"Password: \" --placeholder \"hunter2\" --prompt.foreground 99 --cursor.foreground 99 --width 50\n\n# Join\ngum join \"Horizontal\" \"Join\"\ngum join --vertical \"Vertical\" \"Join\"\n\n# Spin\ngum spin -- sleep 1\ngum spin --spinner minidot --title \"Loading...\" --title.foreground 99 -- sleep 1\ngum spin --show-output --spinner monkey --title \"Loading...\" --title.foreground 99 -- sh -c 'sleep 1; echo \"Hello, Gum!\"'\n\n# Style\ngum style --foreground 99 --border double --border-foreground 99 --padding \"1 2\" --margin 1 \"Hello, Gum.\"\n\n# Write\ngum write\ngum write --width 40 --height 6 --placeholder \"Type whatever you want\" --prompt \"| \" --show-cursor-line --show-line-numbers --value \"Something...\" --base.padding 1 --cursor.foreground 99 --prompt.foreground 99\n\n# Table\ngum table < table/example.csv\n\n# Pager\ngum pager < README.md\n\n# File\ngum file\n"
  },
  {
    "path": "examples/write.tape",
    "content": "Output write.gif\n\nSet Width 800\nSet Height 350\nSet Shell bash\n\nSleep 500ms\nType \"gum write > story.txt\"\nEnter\nSleep 1s\nType \"Once upon a time\"\nSleep 1s\nAlt+Enter\nType \"In a land far, far away....\"\nSleep 500ms\nEnter\nSleep 1s\nType \"cat story.txt\"\nEnter\nSleep 2s\n\n"
  },
  {
    "path": "file/command.go",
    "content": "package file\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/charmbracelet/bubbles/filepicker\"\n\t\"github.com/charmbracelet/bubbles/help\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/gum/internal/timeout\"\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Run is the interface to picking a file.\nfunc (o Options) Run() error {\n\tif !o.File && !o.Directory {\n\t\treturn errors.New(\"at least one between --file and --directory must be set\")\n\t}\n\n\tif o.Path == \"\" {\n\t\to.Path = \".\"\n\t}\n\n\tpath, err := filepath.Abs(o.Path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"file not found: %w\", err)\n\t}\n\n\tfp := filepicker.New()\n\tfp.CurrentDirectory = path\n\tfp.Path = path\n\tfp.SetHeight(o.Height)\n\tfp.AutoHeight = o.Height == 0\n\tfp.Cursor = o.Cursor\n\tfp.DirAllowed = o.Directory\n\tfp.FileAllowed = o.File\n\tfp.ShowPermissions = o.Permissions\n\tfp.ShowSize = o.Size\n\tfp.ShowHidden = o.All\n\tfp.Styles = filepicker.DefaultStyles()\n\tfp.Styles.Cursor = o.CursorStyle.ToLipgloss()\n\tfp.Styles.Symlink = o.SymlinkStyle.ToLipgloss()\n\tfp.Styles.Directory = o.DirectoryStyle.ToLipgloss()\n\tfp.Styles.File = o.FileStyle.ToLipgloss()\n\tfp.Styles.Permission = o.PermissionsStyle.ToLipgloss()\n\tfp.Styles.Selected = o.SelectedStyle.ToLipgloss()\n\tfp.Styles.FileSize = o.FileSizeStyle.ToLipgloss()\n\ttop, right, bottom, left := style.ParsePadding(o.Padding)\n\tm := model{\n\t\tfilepicker:  fp,\n\t\tpadding:     []int{top, right, bottom, left},\n\t\tshowHelp:    o.ShowHelp,\n\t\thelp:        help.New(),\n\t\tkeymap:      defaultKeymap(),\n\t\theaderStyle: o.HeaderStyle.ToLipgloss(),\n\t\theader:      o.Header,\n\t}\n\n\tctx, cancel := timeout.Context(o.Timeout)\n\tdefer cancel()\n\n\ttm, err := tea.NewProgram(\n\t\t&m,\n\t\ttea.WithOutput(os.Stderr),\n\t\ttea.WithContext(ctx),\n\t).Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to pick selection: %w\", err)\n\t}\n\tm = tm.(model)\n\tif m.selectedPath == \"\" {\n\t\treturn errors.New(\"no file selected\")\n\t}\n\n\tfmt.Println(m.selectedPath)\n\treturn nil\n}\n"
  },
  {
    "path": "file/file.go",
    "content": "// Package file provides an interface to pick a file from a folder (tree).\n// The user is provided a file manager-like interface to navigate, to\n// select a file.\n//\n// Let's pick a file from the current directory:\n//\n// $ gum file\n// $ gum file .\n//\n// Let's pick a file from the home directory:\n//\n// $ gum file $HOME\npackage file\n\nimport (\n\t\"github.com/charmbracelet/bubbles/filepicker\"\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\ntype keymap filepicker.KeyMap\n\nvar keyQuit = key.NewBinding(\n\tkey.WithKeys(\"esc\", \"q\"),\n\tkey.WithHelp(\"esc\", \"close\"),\n)\n\nvar keyAbort = key.NewBinding(\n\tkey.WithKeys(\"ctrl+c\"),\n\tkey.WithHelp(\"ctrl+c\", \"abort\"),\n)\n\nfunc defaultKeymap() keymap {\n\tkm := filepicker.DefaultKeyMap()\n\treturn keymap(km)\n}\n\n// FullHelp implements help.KeyMap.\nfunc (k keymap) FullHelp() [][]key.Binding { return nil }\n\n// ShortHelp implements help.KeyMap.\nfunc (k keymap) ShortHelp() []key.Binding {\n\treturn []key.Binding{\n\t\tkey.NewBinding(\n\t\t\tkey.WithKeys(\"up\", \"down\"),\n\t\t\tkey.WithHelp(\"↓↑\", \"navigate\"),\n\t\t),\n\t\tkeyQuit,\n\t\tk.Select,\n\t}\n}\n\ntype model struct {\n\theader       string\n\theaderStyle  lipgloss.Style\n\tfilepicker   filepicker.Model\n\tselectedPath string\n\tquitting     bool\n\tshowHelp     bool\n\tpadding      []int\n\thelp         help.Model\n\tkeymap       keymap\n}\n\nfunc (m model) Init() tea.Cmd { return m.filepicker.Init() }\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\theight := msg.Height - m.padding[0] - m.padding[2]\n\t\tif m.showHelp {\n\t\t\theight -= lipgloss.Height(m.helpView())\n\t\t}\n\t\tm.filepicker.SetHeight(height)\n\tcase tea.KeyMsg:\n\t\tswitch {\n\t\tcase key.Matches(msg, keyAbort):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Interrupt\n\t\tcase key.Matches(msg, keyQuit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\tvar cmd tea.Cmd\n\tm.filepicker, cmd = m.filepicker.Update(msg)\n\tif didSelect, path := m.filepicker.DidSelectFile(msg); didSelect {\n\t\tm.selectedPath = path\n\t\tm.quitting = true\n\t\treturn m, tea.Quit\n\t}\n\treturn m, cmd\n}\n\nfunc (m model) View() string {\n\tif m.quitting {\n\t\treturn \"\"\n\t}\n\tvar parts []string\n\tif m.header != \"\" {\n\t\tparts = append(parts, m.headerStyle.Render(m.header))\n\t}\n\tparts = append(parts, m.filepicker.View())\n\tif m.showHelp {\n\t\tparts = append(parts, m.helpView())\n\t}\n\treturn lipgloss.NewStyle().\n\t\tPadding(m.padding...).\n\t\tRender(lipgloss.JoinVertical(\n\t\t\tlipgloss.Left,\n\t\t\tparts...,\n\t\t))\n}\n\nfunc (m model) helpView() string {\n\treturn m.help.View(m.keymap)\n}\n"
  },
  {
    "path": "file/options.go",
    "content": "package file\n\nimport (\n\t\"time\"\n\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Options are the options for the file command.\ntype Options struct {\n\t// Path is the path to the folder / directory to begin traversing.\n\tPath string `arg:\"\" optional:\"\" name:\"path\" help:\"The path to the folder to begin traversing\" env:\"GUM_FILE_PATH\"`\n\t// Cursor is the character to display in front of the current selected items.\n\tCursor      string        `short:\"c\" help:\"The cursor character\" default:\">\" env:\"GUM_FILE_CURSOR\"`\n\tAll         bool          `short:\"a\" help:\"Show hidden and 'dot' files\" default:\"false\" env:\"GUM_FILE_ALL\"`\n\tPermissions bool          `short:\"p\" help:\"Show file permissions\" default:\"true\" negatable:\"\" env:\"GUM_FILE_PERMISSION\"`\n\tSize        bool          `short:\"s\" help:\"Show file size\" default:\"true\" negatable:\"\" env:\"GUM_FILE_SIZE\"`\n\tFile        bool          `help:\"Allow files selection\" default:\"true\" env:\"GUM_FILE_FILE\"`\n\tDirectory   bool          `help:\"Allow directories selection\" default:\"false\" env:\"GUM_FILE_DIRECTORY\"`\n\tShowHelp    bool          `help:\"Show help key binds\" negatable:\"\" default:\"true\" env:\"GUM_FILE_SHOW_HELP\"`\n\tTimeout     time.Duration `help:\"Timeout until command aborts without a selection\" default:\"0s\" env:\"GUM_FILE_TIMEOUT\"`\n\tHeader      string        `help:\"Header value\" default:\"\" env:\"GUM_FILE_HEADER\"`\n\tHeight      int           `help:\"Maximum number of files to display\" default:\"10\" env:\"GUM_FILE_HEIGHT\"`\n\n\tCursorStyle      style.Styles `embed:\"\" prefix:\"cursor.\" help:\"The cursor style\" set:\"defaultForeground=212\" envprefix:\"GUM_FILE_CURSOR_\"`\n\tSymlinkStyle     style.Styles `embed:\"\" prefix:\"symlink.\" help:\"The style to use for symlinks\" set:\"defaultForeground=36\" envprefix:\"GUM_FILE_SYMLINK_\"`\n\tDirectoryStyle   style.Styles `embed:\"\" prefix:\"directory.\" help:\"The style to use for directories\" set:\"defaultForeground=99\" envprefix:\"GUM_FILE_DIRECTORY_\"`\n\tFileStyle        style.Styles `embed:\"\" prefix:\"file.\" help:\"The style to use for files\" envprefix:\"GUM_FILE_FILE_\"`\n\tPermissionsStyle style.Styles `embed:\"\" prefix:\"permissions.\" help:\"The style to use for permissions\" set:\"defaultForeground=244\" envprefix:\"GUM_FILE_PERMISSIONS_\"`\n\tSelectedStyle    style.Styles `embed:\"\" prefix:\"selected.\" help:\"The style to use for the selected item\" set:\"defaultBold=true\" set:\"defaultForeground=212\" envprefix:\"GUM_FILE_SELECTED_\"`                    //nolint:staticcheck\n\tFileSizeStyle    style.Styles `embed:\"\" prefix:\"file-size.\" help:\"The style to use for file sizes\" set:\"defaultWidth=8\" set:\"defaultAlign=right\" set:\"defaultForeground=240\"  envprefix:\"GUM_FILE_FILE_SIZE_\"` //nolint:staticcheck\n\tHeaderStyle      style.Styles `embed:\"\" prefix:\"header.\" set:\"defaultForeground=99\" envprefix:\"GUM_FILE_HEADER_\"`\n\tPadding          string       `help:\"Padding\" default:\"${defaultPadding}\" group:\"Style Flags\" env:\"GUM_FILE_PADDING\"`\n}\n"
  },
  {
    "path": "filter/command.go",
    "content": "package filter\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/textinput\"\n\t\"github.com/charmbracelet/bubbles/viewport\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/gum/internal/files\"\n\t\"github.com/charmbracelet/gum/internal/stdin\"\n\t\"github.com/charmbracelet/gum/internal/timeout\"\n\t\"github.com/charmbracelet/gum/internal/tty\"\n\t\"github.com/charmbracelet/gum/style\"\n\t\"github.com/charmbracelet/x/ansi\"\n\t\"github.com/sahilm/fuzzy\"\n)\n\n// Run provides a shell script interface for filtering through options, powered\n// by the textinput bubble.\nfunc (o Options) Run() error {\n\ti := textinput.New()\n\ti.Focus()\n\n\ti.Prompt = o.Prompt\n\ti.PromptStyle = o.PromptStyle.ToLipgloss()\n\ti.PlaceholderStyle = o.PlaceholderStyle.ToLipgloss()\n\ti.Placeholder = o.Placeholder\n\ti.Width = o.Width\n\n\tv := viewport.New(o.Width, o.Height)\n\n\tif len(o.Options) == 0 {\n\t\tif input, _ := stdin.Read(stdin.StripANSI(o.StripANSI)); input != \"\" {\n\t\t\to.Options = strings.Split(input, o.InputDelimiter)\n\t\t} else {\n\t\t\to.Options = files.List()\n\t\t}\n\t}\n\n\tif len(o.Options) == 0 {\n\t\treturn errors.New(\"no options provided, see `gum filter --help`\")\n\t}\n\n\tctx, cancel := timeout.Context(o.Timeout)\n\tdefer cancel()\n\n\toptions := []tea.ProgramOption{\n\t\ttea.WithOutput(os.Stderr),\n\t\ttea.WithReportFocus(),\n\t\ttea.WithContext(ctx),\n\t}\n\tif o.Height == 0 {\n\t\toptions = append(options, tea.WithAltScreen())\n\t}\n\n\tvar matches []fuzzy.Match\n\tif o.Value != \"\" {\n\t\ti.SetValue(o.Value)\n\t}\n\n\tchoices := map[string]string{}\n\tfilteringChoices := []string{}\n\tfor _, opt := range o.Options {\n\t\ts := ansi.Strip(opt)\n\t\tchoices[s] = opt\n\t\tfilteringChoices = append(filteringChoices, s)\n\t}\n\tswitch {\n\tcase o.Value != \"\" && o.Fuzzy:\n\t\tmatches = fuzzy.Find(o.Value, filteringChoices)\n\tcase o.Value != \"\" && !o.Fuzzy:\n\t\tmatches = exactMatches(o.Value, filteringChoices)\n\tdefault:\n\t\tmatches = matchAll(filteringChoices)\n\t}\n\n\tif o.NoLimit {\n\t\to.Limit = len(o.Options)\n\t}\n\n\tif o.SelectIfOne && len(matches) == 1 {\n\t\ttty.Println(matches[0].Str)\n\t\treturn nil\n\t}\n\n\tkm := defaultKeymap()\n\tif o.NoLimit || o.Limit > 1 {\n\t\tkm.Toggle.SetEnabled(true)\n\t\tkm.ToggleAndPrevious.SetEnabled(true)\n\t\tkm.ToggleAndNext.SetEnabled(true)\n\t\tkm.ToggleAll.SetEnabled(true)\n\t}\n\ttop, right, bottom, left := style.ParsePadding(o.Padding)\n\tm := model{\n\t\tchoices:               choices,\n\t\tfilteringChoices:      filteringChoices,\n\t\tindicator:             o.Indicator,\n\t\tmatches:               matches,\n\t\theader:                o.Header,\n\t\ttextinput:             i,\n\t\tviewport:              &v,\n\t\tindicatorStyle:        o.IndicatorStyle.ToLipgloss(),\n\t\tselectedPrefixStyle:   o.SelectedPrefixStyle.ToLipgloss(),\n\t\tselectedPrefix:        o.SelectedPrefix,\n\t\tunselectedPrefixStyle: o.UnselectedPrefixStyle.ToLipgloss(),\n\t\tunselectedPrefix:      o.UnselectedPrefix,\n\t\tmatchStyle:            o.MatchStyle.ToLipgloss(),\n\t\theaderStyle:           o.HeaderStyle.ToLipgloss(),\n\t\ttextStyle:             o.TextStyle.ToLipgloss(),\n\t\tcursorTextStyle:       o.CursorTextStyle.ToLipgloss(),\n\t\theight:                o.Height,\n\t\tpadding:               []int{top, right, bottom, left},\n\t\tselected:              make(map[string]struct{}),\n\t\tlimit:                 o.Limit,\n\t\treverse:               o.Reverse,\n\t\tfuzzy:                 o.Fuzzy,\n\t\tsort:                  o.Sort && o.FuzzySort,\n\t\tstrict:                o.Strict,\n\t\tshowHelp:              o.ShowHelp,\n\t\tkeymap:                km,\n\t\thelp:                  help.New(),\n\t}\n\n\tisSelectAll := len(o.Selected) == 1 && o.Selected[0] == \"*\"\n\tcurrentSelected := 0\n\tif len(o.Selected) > 0 {\n\t\tfor i, option := range matches {\n\t\t\tif currentSelected >= o.Limit || (!isSelectAll && !slices.Contains(o.Selected, option.Str)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif o.Limit == 1 {\n\t\t\t\tm.cursor = i\n\t\t\t\tm.selected[option.Str] = struct{}{}\n\t\t\t} else {\n\t\t\t\tcurrentSelected++\n\t\t\t\tm.selected[option.Str] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\n\ttm, err := tea.NewProgram(m, options...).Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to run filter: %w\", err)\n\t}\n\n\tm = tm.(model)\n\tif !m.submitted {\n\t\treturn errors.New(\"nothing selected\")\n\t}\n\n\t// allSelections contains values only if limit is greater\n\t// than 1 or if flag --no-limit is passed, hence there is\n\t// no need to further checks\n\tif len(m.selected) > 0 {\n\t\to.checkSelected(m)\n\t} else if len(m.matches) > m.cursor && m.cursor >= 0 {\n\t\ttty.Println(m.matches[m.cursor].Str)\n\t}\n\n\treturn nil\n}\n\nfunc (o Options) checkSelected(m model) {\n\tout := []string{}\n\tfor k := range m.selected {\n\t\tout = append(out, k)\n\t}\n\ttty.Println(strings.Join(out, o.OutputDelimiter))\n}\n"
  },
  {
    "path": "filter/filter.go",
    "content": "// Package filter provides a fuzzy searching text input to allow filtering a\n// list of options to select one option.\n//\n// By default it will list all the files (recursively) in the current directory\n// for the user to choose one, but the script (or user) can provide different\n// new-line separated options to choose from.\n//\n// I.e. let's pick from a list of gum flavors:\n//\n// $ cat flavors.text | gum filter\npackage filter\n\nimport (\n\t\"strings\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/charmbracelet/bubbles/textinput\"\n\t\"github.com/charmbracelet/bubbles/viewport\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/charmbracelet/x/exp/ordered\"\n\t\"github.com/rivo/uniseg\"\n\t\"github.com/sahilm/fuzzy\"\n)\n\nfunc defaultKeymap() keymap {\n\treturn keymap{\n\t\tDown: key.NewBinding(\n\t\t\tkey.WithKeys(\"down\", \"ctrl+j\", \"ctrl+n\"),\n\t\t),\n\t\tUp: key.NewBinding(\n\t\t\tkey.WithKeys(\"up\", \"ctrl+k\", \"ctrl+p\"),\n\t\t),\n\t\tNDown: key.NewBinding(\n\t\t\tkey.WithKeys(\"j\"),\n\t\t),\n\t\tNUp: key.NewBinding(\n\t\t\tkey.WithKeys(\"k\"),\n\t\t),\n\t\tHome: key.NewBinding(\n\t\t\tkey.WithKeys(\"g\", \"home\"),\n\t\t),\n\t\tEnd: key.NewBinding(\n\t\t\tkey.WithKeys(\"G\", \"end\"),\n\t\t),\n\t\tToggleAndNext: key.NewBinding(\n\t\t\tkey.WithKeys(\"tab\"),\n\t\t\tkey.WithHelp(\"tab\", \"toggle\"),\n\t\t\tkey.WithDisabled(),\n\t\t),\n\t\tToggleAndPrevious: key.NewBinding(\n\t\t\tkey.WithKeys(\"shift+tab\"),\n\t\t\tkey.WithHelp(\"shift+tab\", \"toggle\"),\n\t\t\tkey.WithDisabled(),\n\t\t),\n\t\tToggle: key.NewBinding(\n\t\t\tkey.WithKeys(\"ctrl+@\"),\n\t\t\tkey.WithHelp(\"ctrl+@\", \"toggle\"),\n\t\t\tkey.WithDisabled(),\n\t\t),\n\t\tToggleAll: key.NewBinding(\n\t\t\tkey.WithKeys(\"ctrl+a\"),\n\t\t\tkey.WithHelp(\"ctrl+a\", \"select all\"),\n\t\t\tkey.WithDisabled(),\n\t\t),\n\t\tFocusInSearch: key.NewBinding(\n\t\t\tkey.WithKeys(\"/\"),\n\t\t\tkey.WithHelp(\"/\", \"search\"),\n\t\t),\n\t\tFocusOutSearch: key.NewBinding(\n\t\t\tkey.WithKeys(\"esc\"),\n\t\t\tkey.WithHelp(\"esc\", \"blur search\"),\n\t\t),\n\t\tQuit: key.NewBinding(\n\t\t\tkey.WithKeys(\"esc\"),\n\t\t\tkey.WithHelp(\"esc\", \"quit\"),\n\t\t),\n\t\tAbort: key.NewBinding(\n\t\t\tkey.WithKeys(\"ctrl+c\"),\n\t\t\tkey.WithHelp(\"ctrl+c\", \"abort\"),\n\t\t),\n\t\tSubmit: key.NewBinding(\n\t\t\tkey.WithKeys(\"enter\", \"ctrl+q\"),\n\t\t\tkey.WithHelp(\"enter\", \"submit\"),\n\t\t),\n\t}\n}\n\ntype keymap struct {\n\tFocusInSearch,\n\tFocusOutSearch,\n\tDown,\n\tUp,\n\tNDown,\n\tNUp,\n\tHome,\n\tEnd,\n\tToggleAndNext,\n\tToggleAndPrevious,\n\tToggleAll,\n\tToggle,\n\tAbort,\n\tQuit,\n\tSubmit key.Binding\n}\n\n// FullHelp implements help.KeyMap.\nfunc (k keymap) FullHelp() [][]key.Binding { return nil }\n\n// ShortHelp implements help.KeyMap.\nfunc (k keymap) ShortHelp() []key.Binding {\n\treturn []key.Binding{\n\t\tkey.NewBinding(\n\t\t\tkey.WithKeys(\"up\", \"down\"),\n\t\t\tkey.WithHelp(\"↓↑\", \"navigate\"),\n\t\t),\n\t\tk.FocusInSearch,\n\t\tk.FocusOutSearch,\n\t\tk.ToggleAndNext,\n\t\tk.ToggleAll,\n\t\tk.Submit,\n\t}\n}\n\ntype model struct {\n\ttextinput             textinput.Model\n\tviewport              *viewport.Model\n\tchoices               map[string]string\n\tfilteringChoices      []string\n\tmatches               []fuzzy.Match\n\tcursor                int\n\theader                string\n\tselected              map[string]struct{}\n\tlimit                 int\n\tnumSelected           int\n\tindicator             string\n\tselectedPrefix        string\n\tunselectedPrefix      string\n\theight                int\n\tpadding               []int\n\tquitting              bool\n\theaderStyle           lipgloss.Style\n\tmatchStyle            lipgloss.Style\n\ttextStyle             lipgloss.Style\n\tcursorTextStyle       lipgloss.Style\n\tindicatorStyle        lipgloss.Style\n\tselectedPrefixStyle   lipgloss.Style\n\tunselectedPrefixStyle lipgloss.Style\n\treverse               bool\n\tfuzzy                 bool\n\tsort                  bool\n\tshowHelp              bool\n\tkeymap                keymap\n\thelp                  help.Model\n\tstrict                bool\n\tsubmitted             bool\n}\n\nfunc (m model) Init() tea.Cmd { return textinput.Blink }\n\nfunc (m model) View() string {\n\tif m.quitting {\n\t\treturn \"\"\n\t}\n\n\tvar s strings.Builder\n\tvar lineTextStyle lipgloss.Style\n\n\t// For reverse layout, if the number of matches is less than the viewport\n\t// height, we need to offset the matches so that the first match is at the\n\t// bottom edge of the viewport instead of in the middle.\n\tif m.reverse && len(m.matches) < m.viewport.Height {\n\t\ts.WriteString(strings.Repeat(\"\\n\", m.viewport.Height-len(m.matches)))\n\t}\n\n\t// Since there are matches, display them so that the user can see, in real\n\t// time, what they are searching for.\n\tlast := len(m.matches) - 1\n\tfor i := range m.matches {\n\t\t// For reverse layout, the matches are displayed in reverse order.\n\t\tif m.reverse {\n\t\t\ti = last - i\n\t\t}\n\t\tmatch := m.matches[i]\n\n\t\t// If this is the current selected index, we add a small indicator to\n\t\t// represent it. Otherwise, simply pad the string.\n\t\t// The line's text style is set depending on whether or not the cursor\n\t\t// points to this line.\n\t\tif i == m.cursor {\n\t\t\ts.WriteString(m.indicatorStyle.Render(m.indicator))\n\t\t\tlineTextStyle = m.cursorTextStyle\n\t\t} else {\n\t\t\ts.WriteString(strings.Repeat(\" \", lipgloss.Width(m.indicator)))\n\t\t\tlineTextStyle = m.textStyle\n\t\t}\n\n\t\t// If there are multiple selections mark them, otherwise leave an empty space\n\t\tif _, ok := m.selected[match.Str]; ok {\n\t\t\ts.WriteString(m.selectedPrefixStyle.Render(m.selectedPrefix))\n\t\t} else if m.limit > 1 {\n\t\t\ts.WriteString(m.unselectedPrefixStyle.Render(m.unselectedPrefix))\n\t\t} else {\n\t\t\ts.WriteString(\" \")\n\t\t}\n\n\t\tstyledOption := m.choices[match.Str]\n\t\tif len(match.MatchedIndexes) == 0 {\n\t\t\t// No matches, just render the text.\n\t\t\ts.WriteString(lineTextStyle.Render(styledOption))\n\t\t\ts.WriteRune('\\n')\n\t\t\tcontinue\n\t\t}\n\n\t\tvar ranges []lipgloss.Range\n\t\tfor _, rng := range matchedRanges(match.MatchedIndexes) {\n\t\t\t// ansi.Cut is grapheme and ansi sequence aware, we match against a ansi.Stripped string, but we might still have graphemes.\n\t\t\t// all that to say that rng is byte positions, but we need to pass it down to ansi.Cut as char positions.\n\t\t\t// so we need to adjust it here:\n\t\t\tstart, stop := bytePosToVisibleCharPos(match.Str, rng)\n\t\t\tranges = append(ranges, lipgloss.NewRange(start, stop+1, m.matchStyle))\n\t\t}\n\n\t\ts.WriteString(lineTextStyle.Render(lipgloss.StyleRanges(styledOption, ranges...)))\n\n\t\t// We have finished displaying the match with all of it's matched\n\t\t// characters highlighted and the rest filled in.\n\t\t// Move on to the next match.\n\t\ts.WriteRune('\\n')\n\t}\n\n\tm.viewport.SetContent(s.String())\n\n\t// View the input and the filtered choices\n\theader := m.headerStyle.Render(m.header)\n\tif m.reverse {\n\t\tview := m.viewport.View()\n\t\tif m.header != \"\" {\n\t\t\tview += \"\\n\" + header\n\t\t}\n\t\tview += \"\\n\" + m.textinput.View()\n\t\tif m.showHelp {\n\t\t\tview += m.helpView()\n\t\t}\n\t\treturn lipgloss.NewStyle().\n\t\t\tPadding(m.padding...).\n\t\t\tRender(view)\n\t}\n\n\tview := m.textinput.View() + \"\\n\" + m.viewport.View()\n\tif m.showHelp {\n\t\tview += m.helpView()\n\t}\n\tif m.header != \"\" {\n\t\treturn lipgloss.NewStyle().\n\t\t\tPadding(m.padding...).\n\t\t\tRender(header + \"\\n\" + view)\n\t}\n\n\treturn lipgloss.NewStyle().\n\t\tPadding(m.padding...).\n\t\tRender(view)\n}\n\nfunc (m model) helpView() string {\n\treturn \"\\n\\n\" + m.help.View(m.keymap)\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd, icmd tea.Cmd\n\tm.textinput, icmd = m.textinput.Update(msg)\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tif m.height == 0 || m.height > msg.Height {\n\t\t\tm.viewport.Height = msg.Height - lipgloss.Height(m.textinput.View())\n\t\t}\n\t\t// Include the header in the height calculation.\n\t\tif m.header != \"\" {\n\t\t\tm.viewport.Height = m.viewport.Height - lipgloss.Height(m.headerStyle.Render(m.header))\n\t\t}\n\t\t// Include the help in the total height calculation.\n\t\tif m.showHelp {\n\t\t\tm.viewport.Height = m.viewport.Height - lipgloss.Height(m.helpView())\n\t\t}\n\t\tm.viewport.Height = m.viewport.Height - m.padding[0] - m.padding[2]\n\t\tm.viewport.Width = msg.Width - m.padding[1] - m.padding[3]\n\t\tm.textinput.Width = msg.Width - m.padding[1] - m.padding[3]\n\t\tif m.reverse {\n\t\t\tm.viewport.YOffset = ordered.Clamp(len(m.matches)-m.viewport.Height, 0, len(m.matches))\n\t\t}\n\tcase tea.KeyMsg:\n\t\tkm := m.keymap\n\t\tswitch {\n\t\tcase key.Matches(msg, km.FocusInSearch):\n\t\t\tm.textinput.Focus()\n\t\tcase key.Matches(msg, km.FocusOutSearch):\n\t\t\tm.textinput.Blur()\n\t\tcase key.Matches(msg, km.Quit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, km.Abort):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Interrupt\n\t\tcase key.Matches(msg, km.Submit):\n\t\t\tm.quitting = true\n\t\t\tm.submitted = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, km.Down, km.NDown):\n\t\t\tm.CursorDown()\n\t\tcase key.Matches(msg, km.Up, km.NUp):\n\t\t\tm.CursorUp()\n\t\tcase key.Matches(msg, km.Home):\n\t\t\tm.cursor = 0\n\t\t\tm.viewport.GotoTop()\n\t\tcase key.Matches(msg, km.End):\n\t\t\tm.cursor = len(m.choices) - 1\n\t\t\tm.viewport.GotoBottom()\n\t\tcase key.Matches(msg, km.ToggleAndNext):\n\t\t\tif m.limit == 1 {\n\t\t\t\tbreak // no op\n\t\t\t}\n\t\t\tm.ToggleSelection()\n\t\t\tm.CursorDown()\n\t\tcase key.Matches(msg, km.ToggleAndPrevious):\n\t\t\tif m.limit == 1 {\n\t\t\t\tbreak // no op\n\t\t\t}\n\t\t\tm.ToggleSelection()\n\t\t\tm.CursorUp()\n\t\tcase key.Matches(msg, km.Toggle):\n\t\t\tif m.limit == 1 {\n\t\t\t\tbreak // no op\n\t\t\t}\n\t\t\tm.ToggleSelection()\n\t\tcase key.Matches(msg, km.ToggleAll):\n\t\t\tif m.limit <= 1 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif m.numSelected < len(m.matches) && m.numSelected < m.limit {\n\t\t\t\tm = m.selectAll()\n\t\t\t} else {\n\t\t\t\tm = m.deselectAll()\n\t\t\t}\n\t\tdefault:\n\t\t\t// yOffsetFromBottom is the number of lines from the bottom of the\n\t\t\t// list to the top of the viewport. This is used to keep the viewport\n\t\t\t// at a constant position when the number of matches are reduced\n\t\t\t// in the reverse layout.\n\t\t\tvar yOffsetFromBottom int\n\t\t\tif m.reverse {\n\t\t\t\tyOffsetFromBottom = max(0, len(m.matches)-m.viewport.YOffset)\n\t\t\t}\n\n\t\t\t// A character was entered, this likely means that the text input has\n\t\t\t// changed. This suggests that the matches are outdated, so update them.\n\t\t\tvar choices []string\n\t\t\tif !m.strict {\n\t\t\t\tchoices = append(choices, m.textinput.Value())\n\t\t\t}\n\t\t\tchoices = append(choices, m.filteringChoices...)\n\t\t\tif m.fuzzy {\n\t\t\t\tif m.sort {\n\t\t\t\t\tm.matches = fuzzy.Find(m.textinput.Value(), choices)\n\t\t\t\t} else {\n\t\t\t\t\tm.matches = fuzzy.FindNoSort(m.textinput.Value(), choices)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tm.matches = exactMatches(m.textinput.Value(), choices)\n\t\t\t}\n\n\t\t\t// If the search field is empty, let's not display the matches\n\t\t\t// (none), but rather display all possible choices.\n\t\t\tif m.textinput.Value() == \"\" {\n\t\t\t\tm.matches = matchAll(m.filteringChoices)\n\t\t\t}\n\n\t\t\t// For reverse layout, we need to offset the viewport so that the\n\t\t\t// it remains at a constant position relative to the cursor.\n\t\t\tif m.reverse {\n\t\t\t\tmaxYOffset := max(0, len(m.matches)-m.viewport.Height)\n\t\t\t\tm.viewport.YOffset = ordered.Clamp(len(m.matches)-yOffsetFromBottom, 0, maxYOffset)\n\t\t\t}\n\t\t}\n\t}\n\n\tm.keymap.FocusInSearch.SetEnabled(!m.textinput.Focused())\n\tm.keymap.FocusOutSearch.SetEnabled(m.textinput.Focused())\n\tm.keymap.NUp.SetEnabled(!m.textinput.Focused())\n\tm.keymap.NDown.SetEnabled(!m.textinput.Focused())\n\tm.keymap.Home.SetEnabled(!m.textinput.Focused())\n\tm.keymap.End.SetEnabled(!m.textinput.Focused())\n\n\t// It's possible that filtering items have caused fewer matches. So, ensure\n\t// that the selected index is within the bounds of the number of matches.\n\tm.cursor = ordered.Clamp(m.cursor, 0, len(m.matches)-1)\n\treturn m, tea.Batch(cmd, icmd)\n}\n\nfunc (m *model) CursorUp() {\n\tif len(m.matches) == 0 {\n\t\treturn\n\t}\n\tif m.reverse { //nolint:nestif\n\t\tm.cursor = (m.cursor + 1) % len(m.matches)\n\t\tif len(m.matches)-m.cursor <= m.viewport.YOffset {\n\t\t\tm.viewport.ScrollUp(1)\n\t\t}\n\t\tif len(m.matches)-m.cursor > m.viewport.Height+m.viewport.YOffset {\n\t\t\tm.viewport.SetYOffset(len(m.matches) - m.viewport.Height)\n\t\t}\n\t} else {\n\t\tm.cursor = (m.cursor - 1 + len(m.matches)) % len(m.matches)\n\t\tif m.cursor < m.viewport.YOffset {\n\t\t\tm.viewport.ScrollUp(1)\n\t\t}\n\t\tif m.cursor >= m.viewport.YOffset+m.viewport.Height {\n\t\t\tm.viewport.SetYOffset(len(m.matches) - m.viewport.Height)\n\t\t}\n\t}\n}\n\nfunc (m *model) CursorDown() {\n\tif len(m.matches) == 0 {\n\t\treturn\n\t}\n\tif m.reverse { //nolint:nestif\n\t\tm.cursor = (m.cursor - 1 + len(m.matches)) % len(m.matches)\n\t\tif len(m.matches)-m.cursor > m.viewport.Height+m.viewport.YOffset {\n\t\t\tm.viewport.ScrollDown(1)\n\t\t}\n\t\tif len(m.matches)-m.cursor <= m.viewport.YOffset {\n\t\t\tm.viewport.GotoTop()\n\t\t}\n\t} else {\n\t\tm.cursor = (m.cursor + 1) % len(m.matches)\n\t\tif m.cursor >= m.viewport.YOffset+m.viewport.Height {\n\t\t\tm.viewport.ScrollDown(1)\n\t\t}\n\t\tif m.cursor < m.viewport.YOffset {\n\t\t\tm.viewport.GotoTop()\n\t\t}\n\t}\n}\n\nfunc (m *model) ToggleSelection() {\n\tif _, ok := m.selected[m.matches[m.cursor].Str]; ok {\n\t\tdelete(m.selected, m.matches[m.cursor].Str)\n\t\tm.numSelected--\n\t} else if m.numSelected < m.limit {\n\t\tm.selected[m.matches[m.cursor].Str] = struct{}{}\n\t\tm.numSelected++\n\t}\n}\n\nfunc (m model) selectAll() model {\n\tfor i := range m.matches {\n\t\tif m.numSelected >= m.limit {\n\t\t\tbreak // do not exceed given limit\n\t\t}\n\t\tif _, ok := m.selected[m.matches[i].Str]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tm.selected[m.matches[i].Str] = struct{}{}\n\t\tm.numSelected++\n\t}\n\treturn m\n}\n\nfunc (m model) deselectAll() model {\n\tm.selected = make(map[string]struct{})\n\tm.numSelected = 0\n\treturn m\n}\n\nfunc matchAll(options []string) []fuzzy.Match {\n\tmatches := make([]fuzzy.Match, len(options))\n\tfor i, option := range options {\n\t\tmatches[i] = fuzzy.Match{Str: option}\n\t}\n\treturn matches\n}\n\nfunc exactMatches(search string, choices []string) []fuzzy.Match {\n\tmatches := fuzzy.Matches{}\n\tfor i, choice := range choices {\n\t\tsearch = strings.ToLower(search)\n\t\tmatchedString := strings.ToLower(choice)\n\n\t\tindex := strings.Index(matchedString, search)\n\t\tif index >= 0 {\n\t\t\tmatchedIndexes := []int{}\n\t\t\tfor s := range search {\n\t\t\t\tmatchedIndexes = append(matchedIndexes, index+s)\n\t\t\t}\n\t\t\tmatches = append(matches, fuzzy.Match{\n\t\t\t\tStr:            choice,\n\t\t\t\tIndex:          i,\n\t\t\t\tMatchedIndexes: matchedIndexes,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn matches\n}\n\nfunc matchedRanges(in []int) [][2]int {\n\tif len(in) == 0 {\n\t\treturn [][2]int{}\n\t}\n\tcurrent := [2]int{in[0], in[0]}\n\tif len(in) == 1 {\n\t\treturn [][2]int{current}\n\t}\n\tvar out [][2]int\n\tfor i := 1; i < len(in); i++ {\n\t\tif in[i] == current[1]+1 {\n\t\t\tcurrent[1] = in[i]\n\t\t} else {\n\t\t\tout = append(out, current)\n\t\t\tcurrent = [2]int{in[i], in[i]}\n\t\t}\n\t}\n\tout = append(out, current)\n\treturn out\n}\n\nfunc bytePosToVisibleCharPos(str string, rng [2]int) (int, int) {\n\tbytePos, byteStart, byteStop := 0, rng[0], rng[1]\n\tpos, start, stop := 0, 0, 0\n\tgr := uniseg.NewGraphemes(str)\n\tfor byteStart > bytePos {\n\t\tif !gr.Next() {\n\t\t\tbreak\n\t\t}\n\t\tbytePos += len(gr.Str())\n\t\tpos += max(1, gr.Width())\n\t}\n\tstart = pos\n\tfor byteStop > bytePos {\n\t\tif !gr.Next() {\n\t\t\tbreak\n\t\t}\n\t\tbytePos += len(gr.Str())\n\t\tpos += max(1, gr.Width())\n\t}\n\tstop = pos\n\treturn start, stop\n}\n"
  },
  {
    "path": "filter/filter_test.go",
    "content": "package filter\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\nfunc TestMatchedRanges(t *testing.T) {\n\tfor name, tt := range map[string]struct {\n\t\tin  []int\n\t\tout [][2]int\n\t}{\n\t\t\"empty\": {\n\t\t\tin:  []int{},\n\t\t\tout: [][2]int{},\n\t\t},\n\t\t\"one char\": {\n\t\t\tin:  []int{1},\n\t\t\tout: [][2]int{{1, 1}},\n\t\t},\n\t\t\"2 char range\": {\n\t\t\tin:  []int{1, 2},\n\t\t\tout: [][2]int{{1, 2}},\n\t\t},\n\t\t\"multiple char range\": {\n\t\t\tin:  []int{1, 2, 3, 4, 5, 6},\n\t\t\tout: [][2]int{{1, 6}},\n\t\t},\n\t\t\"multiple char ranges\": {\n\t\t\tin:  []int{1, 2, 3, 5, 6, 10, 11, 12, 13, 23, 24, 40, 42, 43, 45, 52},\n\t\t\tout: [][2]int{{1, 3}, {5, 6}, {10, 13}, {23, 24}, {40, 40}, {42, 43}, {45, 45}, {52, 52}},\n\t\t},\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmatch := matchedRanges(tt.in)\n\t\t\tif !reflect.DeepEqual(match, tt.out) {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tt.out, match)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestByteToChar(t *testing.T) {\n\tstStr := \"\\x1b[90m\\ue615\\x1b[39m \\x1b[3m\\x1b[32mDow\\x1b[0m\\x1b[90m\\x1b[39m\\x1b[3wnloads\"\n\tstr := \" Downloads\"\n\trng := [2]int{4, 7}\n\texpect := \"Dow\"\n\n\tif got := str[rng[0]:rng[1]]; got != expect {\n\t\tt.Errorf(\"expected %q, got %q\", expect, got)\n\t}\n\n\tstart, stop := bytePosToVisibleCharPos(str, rng)\n\tif got := ansi.Strip(ansi.Cut(stStr, start, stop)); got != expect {\n\t\tt.Errorf(\"expected %+q, got %+q\", expect, got)\n\t}\n}\n"
  },
  {
    "path": "filter/options.go",
    "content": "package filter\n\nimport (\n\t\"time\"\n\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Options is the customization options for the filter command.\ntype Options struct {\n\tOptions []string `arg:\"\" optional:\"\" help:\"Options to filter.\"`\n\n\tIndicator             string        `help:\"Character for selection\" default:\"•\" env:\"GUM_FILTER_INDICATOR\"`\n\tIndicatorStyle        style.Styles  `embed:\"\" prefix:\"indicator.\" set:\"defaultForeground=212\" envprefix:\"GUM_FILTER_INDICATOR_\"`\n\tLimit                 int           `help:\"Maximum number of options to pick\" default:\"1\" group:\"Selection\"`\n\tNoLimit               bool          `help:\"Pick unlimited number of options (ignores limit)\" group:\"Selection\"`\n\tSelectIfOne           bool          `help:\"Select the given option if there is only one\" group:\"Selection\"`\n\tSelected              []string      `help:\"Options that should start as selected (selects all if given *)\" default:\"\" env:\"GUM_FILTER_SELECTED\"`\n\tShowHelp              bool          `help:\"Show help keybinds\" default:\"true\" negatable:\"\" env:\"GUM_FILTER_SHOW_HELP\"`\n\tStrict                bool          `help:\"Only returns if anything matched. Otherwise return Filter\" negatable:\"\" default:\"true\" group:\"Selection\"`\n\tSelectedPrefix        string        `help:\"Character to indicate selected items (hidden if limit is 1)\" default:\" ◉ \" env:\"GUM_FILTER_SELECTED_PREFIX\"`\n\tSelectedPrefixStyle   style.Styles  `embed:\"\" prefix:\"selected-indicator.\" set:\"defaultForeground=212\" envprefix:\"GUM_FILTER_SELECTED_PREFIX_\"`\n\tUnselectedPrefix      string        `help:\"Character to indicate unselected items (hidden if limit is 1)\" default:\" ○ \" env:\"GUM_FILTER_UNSELECTED_PREFIX\"`\n\tUnselectedPrefixStyle style.Styles  `embed:\"\" prefix:\"unselected-prefix.\" set:\"defaultForeground=240\" envprefix:\"GUM_FILTER_UNSELECTED_PREFIX_\"`\n\tHeaderStyle           style.Styles  `embed:\"\" prefix:\"header.\" set:\"defaultForeground=99\" envprefix:\"GUM_FILTER_HEADER_\"`\n\tHeader                string        `help:\"Header value\" default:\"\" env:\"GUM_FILTER_HEADER\"`\n\tTextStyle             style.Styles  `embed:\"\" prefix:\"text.\" envprefix:\"GUM_FILTER_TEXT_\"`\n\tCursorTextStyle       style.Styles  `embed:\"\" prefix:\"cursor-text.\" envprefix:\"GUM_FILTER_CURSOR_TEXT_\"`\n\tMatchStyle            style.Styles  `embed:\"\" prefix:\"match.\" set:\"defaultForeground=212\" envprefix:\"GUM_FILTER_MATCH_\"`\n\tPlaceholder           string        `help:\"Placeholder value\" default:\"Filter...\" env:\"GUM_FILTER_PLACEHOLDER\"`\n\tPrompt                string        `help:\"Prompt to display\" default:\"> \" env:\"GUM_FILTER_PROMPT\"`\n\tPromptStyle           style.Styles  `embed:\"\" prefix:\"prompt.\" set:\"defaultForeground=240\" envprefix:\"GUM_FILTER_PROMPT_\"`\n\tPlaceholderStyle      style.Styles  `embed:\"\" prefix:\"placeholder.\" set:\"defaultForeground=240\" envprefix:\"GUM_FILTER_PLACEHOLDER_\"`\n\tWidth                 int           `help:\"Input width\" default:\"0\" env:\"GUM_FILTER_WIDTH\"`\n\tHeight                int           `help:\"Input height\" default:\"0\" env:\"GUM_FILTER_HEIGHT\"`\n\tValue                 string        `help:\"Initial filter value\" default:\"\" env:\"GUM_FILTER_VALUE\"`\n\tReverse               bool          `help:\"Display from the bottom of the screen\" env:\"GUM_FILTER_REVERSE\"`\n\tFuzzy                 bool          `help:\"Enable fuzzy matching; otherwise match from start of word\" default:\"true\" env:\"GUM_FILTER_FUZZY\" negatable:\"\"`\n\tFuzzySort             bool          `help:\"Sort fuzzy results by their scores\" default:\"true\" env:\"GUM_FILTER_FUZZY_SORT\" negatable:\"\"`\n\tTimeout               time.Duration `help:\"Timeout until filter command aborts\" default:\"0s\" env:\"GUM_FILTER_TIMEOUT\"`\n\tInputDelimiter        string        `help:\"Option delimiter when reading from STDIN\" default:\"\\n\" env:\"GUM_FILTER_INPUT_DELIMITER\"`\n\tOutputDelimiter       string        `help:\"Option delimiter when writing to STDOUT\" default:\"\\n\" env:\"GUM_FILTER_OUTPUT_DELIMITER\"`\n\tStripANSI             bool          `help:\"Strip ANSI sequences when reading from STDIN\" default:\"true\" negatable:\"\" env:\"GUM_FILTER_STRIP_ANSI\"`\n\tPadding               string        `help:\"Padding\" default:\"${defaultPadding}\" group:\"Style Flags\" env:\"GUM_FILTER_PADDING\"`\n\n\t// Deprecated: use [FuzzySort]. This will be removed at some point.\n\tSort bool `help:\"Sort fuzzy results by their scores\" default:\"true\" env:\"GUM_FILTER_FUZZY_SORT\" negatable:\"\" hidden:\"\"`\n}\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"A tool for glamorous shell scripts\";\n\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs/nixos-unstable\";\n    flake-utils.url = \"github:numtide/flake-utils\";\n  };\n\n  outputs = { self, nixpkgs, flake-utils }:\n    flake-utils.lib.eachDefaultSystem (system:\n      let pkgs = import nixpkgs { inherit system; }; in\n      rec {\n        packages.default = import ./default.nix { inherit pkgs; };\n      }) // {\n        overlays.default = final: prev: { \n          gum = import ./default.nix { pkgs = final; };\n        };\n      };\n}\n"
  },
  {
    "path": "format/README.md",
    "content": "# Gum Format\n\nGum format allows you to format different text into human readable output.\n\nFour different parse-able formats exist:\n\n1. [Markdown](#markdown)\n2. [Code](#code)\n3. [Template](#template)\n4. [Emoji](#emoji)\n\n## Markdown\n\nRender any input as markdown text. This uses\n[Glamour](https://github.com/charmbracelet/glamour) behind the scenes.\n\nYou can pass input as lines directly as arguments to the command invocation or\npass markdown over `stdin`.\n\n```bash\ngum format --type markdown < README.md\n# Or, directly as arguments (useful for quick lists)\ngum format --type markdown -- \"# Gum Formats\" \"- Markdown\" \"- Code\" \"- Template\" \"- Emoji\"\n```\n\n## Code\n\nRender any code snippet with syntax highlighting.\n[Glamour](https://github.com/charmbracelet/glamour), which uses\n[Chroma](https://github.com/alecthomas/chroma) under the hood, handles styling.\n\nSimilarly to the `markdown` format, `code` can take input over `stdin`.\n\n```bash\ncat options.go | gum format --type code\n```\n\n## Template\n\nRender styled input from a string template. Templating is handled by\n[Termenv](https://github.com/muesli/termenv).\n\n```bash\ngum format --type template '{{ Bold \"Tasty\" }} {{ Italic \"Bubble\" }} {{ Color \"99\" \"0\" \" Gum \" }}'\n# Or, via stdin\necho '{{ Bold \"Tasty\" }} {{ Italic \"Bubble\" }} {{ Color \"99\" \"0\" \" Gum \" }}' | gum format --type template\n```\n\n## Emoji\n\nParse and render emojis from their matching `:name:`s. Powered by\n[Glamour](https://github.com/charmbracelet/glamour) and [Goldmark\nEmoji](https://github.com/yuin/goldmark-emoji)\n\n```bash\ngum format --type emoji 'I :heart: Bubble Gum :candy:'\n# You know the drill, also via stdin\necho 'I :heart: Bubble Gum :candy:' | gum format --type emoji\n```\n\n## Tables\n\nTables are rendered using [Glamour](https://github.com/charmbracelet/glamour).\n\n| Bubble Gum Flavor | Price |\n| ----------------- | ----- |\n| Strawberry        | $0.99 |\n| Cherry            | $0.50 |\n| Banana            | $0.75 |\n| Orange            | $0.25 |\n| Lemon             | $0.50 |\n| Lime              | $0.50 |\n| Grape             | $0.50 |\n| Watermelon        | $0.50 |\n| Pineapple         | $0.50 |\n| Blueberry         | $0.50 |\n| Raspberry         | $0.50 |\n| Cranberry         | $0.50 |\n| Peach             | $0.50 |\n| Apple             | $0.50 |\n| Mango             | $0.50 |\n| Pomegranate       | $0.50 |\n| Coconut           | $0.50 |\n| Cinnamon          | $0.50 |\n"
  },
  {
    "path": "format/command.go",
    "content": "// Package format allows you to render formatted text from the command line.\n//\n// It supports the following types:\n//\n// 1. Markdown\n// 2. Code\n// 3. Emoji\n// 4. Template\n//\n// For more information, see the format/README.md file.\npackage format\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/gum/internal/stdin\"\n)\n\n// Run runs the format command.\nfunc (o Options) Run() error {\n\tvar input, output string\n\tvar err error\n\tif len(o.Template) > 0 {\n\t\tinput = strings.Join(o.Template, \"\\n\")\n\t} else {\n\t\tinput, _ = stdin.Read(stdin.StripANSI(o.StripANSI))\n\t}\n\n\tswitch o.Type {\n\tcase \"code\":\n\t\toutput, err = code(input, o.Language)\n\tcase \"emoji\":\n\t\toutput, err = emoji(input)\n\tcase \"template\":\n\t\toutput, err = template(input)\n\tdefault:\n\t\toutput, err = markdown(input, o.Theme)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfmt.Print(output)\n\treturn nil\n}\n"
  },
  {
    "path": "format/formats.go",
    "content": "package format\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\ttpl \"text/template\"\n\n\t\"github.com/charmbracelet/glamour\"\n\t\"github.com/muesli/termenv\"\n)\n\nfunc code(input, language string) (string, error) {\n\trenderer, err := glamour.NewTermRenderer(\n\t\tglamour.WithAutoStyle(),\n\t\tglamour.WithWordWrap(0),\n\t)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to create renderer: %w\", err)\n\t}\n\toutput, err := renderer.Render(fmt.Sprintf(\"```%s\\n%s\\n```\", language, input))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to render: %w\", err)\n\t}\n\treturn output, nil\n}\n\nfunc emoji(input string) (string, error) {\n\trenderer, err := glamour.NewTermRenderer(\n\t\tglamour.WithEmoji(),\n\t)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to create renderer: %w\", err)\n\t}\n\toutput, err := renderer.Render(input)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to render: %w\", err)\n\t}\n\treturn output, nil\n}\n\nfunc markdown(input string, theme string) (string, error) {\n\trenderer, err := glamour.NewTermRenderer(\n\t\tglamour.WithStylePath(theme),\n\t\tglamour.WithWordWrap(0),\n\t)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to render: %w\", err)\n\t}\n\toutput, err := renderer.Render(input)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to render: %w\", err)\n\t}\n\treturn output, nil\n}\n\nfunc template(input string) (string, error) {\n\tf := termenv.TemplateFuncs(termenv.ANSI256)\n\tt, err := tpl.New(\"tpl\").Funcs(f).Parse(input)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to parse template: %w\", err)\n\t}\n\n\tvar buf bytes.Buffer\n\terr = t.Execute(&buf, nil)\n\treturn buf.String(), err\n}\n"
  },
  {
    "path": "format/options.go",
    "content": "package format\n\n// Options is customization options for the format command.\ntype Options struct {\n\tTemplate []string `arg:\"\" optional:\"\" help:\"Template string to format (can also be provided via stdin)\"`\n\tTheme    string   `help:\"Glamour theme to use for markdown formatting\" default:\"pink\" env:\"GUM_FORMAT_THEME\"`\n\tLanguage string   `help:\"Programming language to parse code\" short:\"l\" default:\"\" env:\"GUM_FORMAT_LANGUAGE\"`\n\n\tStripANSI bool `help:\"Strip ANSI sequences when reading from STDIN\" default:\"true\" negatable:\"\" env:\"GUM_FORMAT_STRIP_ANSI\"`\n\n\tType string `help:\"Format to use (markdown,template,code,emoji)\" enum:\"markdown,template,code,emoji\" short:\"t\" default:\"markdown\" env:\"GUM_FORMAT_TYPE\"`\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/charmbracelet/gum\n\ngo 1.24.2\n\nrequire (\n\tgithub.com/Masterminds/semver/v3 v3.4.0\n\tgithub.com/alecthomas/kong v1.14.0\n\tgithub.com/alecthomas/mango-kong v0.1.0\n\tgithub.com/charmbracelet/bubbles v1.0.0\n\tgithub.com/charmbracelet/bubbletea v1.3.10\n\tgithub.com/charmbracelet/glamour v0.10.0\n\tgithub.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834\n\tgithub.com/charmbracelet/log v0.4.2\n\tgithub.com/charmbracelet/x/ansi v0.11.6\n\tgithub.com/charmbracelet/x/editor v0.2.0\n\tgithub.com/charmbracelet/x/exp/ordered v0.1.0\n\tgithub.com/charmbracelet/x/term v0.2.2\n\tgithub.com/charmbracelet/x/xpty v0.1.3\n\tgithub.com/muesli/roff v0.1.0\n\tgithub.com/muesli/termenv v0.16.0\n\tgithub.com/rivo/uniseg v0.4.7\n\tgithub.com/sahilm/fuzzy v0.1.1\n\tgolang.org/x/text v0.34.0\n)\n\nrequire (\n\tgithub.com/alecthomas/chroma/v2 v2.14.0 // indirect\n\tgithub.com/atotto/clipboard v0.1.4 // indirect\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/aymerick/douceur v0.2.0 // indirect\n\tgithub.com/charmbracelet/colorprofile v0.4.1 // indirect\n\tgithub.com/charmbracelet/x/cellbuf v0.0.15 // indirect\n\tgithub.com/charmbracelet/x/conpty v0.1.1 // indirect\n\tgithub.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 // indirect\n\tgithub.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect\n\tgithub.com/charmbracelet/x/termios v0.1.1 // indirect\n\tgithub.com/clipperhouse/displaywidth v0.9.0 // indirect\n\tgithub.com/clipperhouse/stringish v0.1.1 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.5.0 // indirect\n\tgithub.com/creack/pty v1.1.24 // indirect\n\tgithub.com/dlclark/regexp2 v1.11.0 // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect\n\tgithub.com/go-logfmt/logfmt v0.6.0 // indirect\n\tgithub.com/gorilla/css v1.0.1 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.3.0 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-localereader v0.0.1 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.19 // indirect\n\tgithub.com/microcosm-cc/bluemonday v1.0.27 // indirect\n\tgithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect\n\tgithub.com/muesli/cancelreader v0.2.2 // indirect\n\tgithub.com/muesli/mango v0.2.0 // indirect\n\tgithub.com/muesli/reflow v0.3.0 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgithub.com/yuin/goldmark v1.7.8 // indirect\n\tgithub.com/yuin/goldmark-emoji v1.0.5 // indirect\n\tgolang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect\n\tgolang.org/x/net v0.40.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/term v0.32.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=\ngithub.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=\ngithub.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=\ngithub.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=\ngithub.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s=\ngithub.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=\ngithub.com/alecthomas/mango-kong v0.1.0 h1:iFVfP1k1K4qpml3JUQmD5I8MCQYfIvsD9mRdrw7jJC4=\ngithub.com/alecthomas/mango-kong v0.1.0/go.mod h1:t+TYVdsONUolf/BwVcm+15eqcdAj15h4Qe9MMFAwwT4=\ngithub.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=\ngithub.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=\ngithub.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=\ngithub.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=\ngithub.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=\ngithub.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=\ngithub.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=\ngithub.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=\ngithub.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=\ngithub.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=\ngithub.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=\ngithub.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=\ngithub.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=\ngithub.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=\ngithub.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=\ngithub.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=\ngithub.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=\ngithub.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=\ngithub.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=\ngithub.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=\ngithub.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=\ngithub.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=\ngithub.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=\ngithub.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=\ngithub.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk=\ngithub.com/charmbracelet/x/editor v0.2.0 h1:7XLUKtaRaB8jN7bWU2p2UChiySyaAuIfYiIRg8gGWwk=\ngithub.com/charmbracelet/x/editor v0.2.0/go.mod h1:p3oQ28TSL3YPd+GKJ1fHWcp+7bVGpedHpXmo0D6t1dY=\ngithub.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=\ngithub.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=\ngithub.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE=\ngithub.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8=\ngithub.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=\ngithub.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=\ngithub.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=\ngithub.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=\ngithub.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=\ngithub.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=\ngithub.com/charmbracelet/x/xpty v0.1.3 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw=\ngithub.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y=\ngithub.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=\ngithub.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=\ngithub.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=\ngithub.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=\ngithub.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=\ngithub.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=\ngithub.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=\ngithub.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=\ngithub.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=\ngithub.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=\ngithub.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=\ngithub.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=\ngithub.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=\ngithub.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=\ngithub.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=\ngithub.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=\ngithub.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=\ngithub.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=\ngithub.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=\ngithub.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=\ngithub.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=\ngithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=\ngithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=\ngithub.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=\ngithub.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=\ngithub.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ=\ngithub.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=\ngithub.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=\ngithub.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=\ngithub.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=\ngithub.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=\ngithub.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=\ngithub.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=\ngithub.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=\ngithub.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=\ngithub.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=\ngithub.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=\ngithub.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=\ngolang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=\ngolang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=\ngolang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=\ngolang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=\ngolang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=\ngolang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "gum.go",
    "content": "package main\n\nimport (\n\t\"github.com/alecthomas/kong\"\n\n\t\"github.com/charmbracelet/gum/choose\"\n\t\"github.com/charmbracelet/gum/completion\"\n\t\"github.com/charmbracelet/gum/confirm\"\n\t\"github.com/charmbracelet/gum/file\"\n\t\"github.com/charmbracelet/gum/filter\"\n\t\"github.com/charmbracelet/gum/format\"\n\t\"github.com/charmbracelet/gum/input\"\n\t\"github.com/charmbracelet/gum/join\"\n\t\"github.com/charmbracelet/gum/log\"\n\t\"github.com/charmbracelet/gum/man\"\n\t\"github.com/charmbracelet/gum/pager\"\n\t\"github.com/charmbracelet/gum/spin\"\n\t\"github.com/charmbracelet/gum/style\"\n\t\"github.com/charmbracelet/gum/table\"\n\t\"github.com/charmbracelet/gum/version\"\n\t\"github.com/charmbracelet/gum/write\"\n)\n\n// Gum is the command-line interface for Gum.\ntype Gum struct {\n\t// Version is a flag that can be used to display the version number.\n\tVersion kong.VersionFlag `short:\"v\" help:\"Print the version number\"`\n\n\t// Completion generates Gum shell completion scripts.\n\tCompletion completion.Completion `cmd:\"\" hidden:\"\" help:\"Request shell completion\"`\n\n\t// Man is a hidden command that generates Gum man pages.\n\tMan man.Man `cmd:\"\" hidden:\"\" help:\"Generate man pages\"`\n\n\t// Choose provides an interface to choose one option from a given list of\n\t// options. The options can be provided as (new-line separated) stdin or a\n\t// list of arguments.\n\t//\n\t// It is different from the filter command as it does not provide a fuzzy\n\t// finding input, so it is best used for smaller lists of options.\n\t//\n\t// Let's pick from a list of gum flavors:\n\t//\n\t// $ gum choose \"Strawberry\" \"Banana\" \"Cherry\"\n\t//\n\tChoose choose.Options `cmd:\"\" help:\"Choose an option from a list of choices\"`\n\n\t// Confirm provides an interface to ask a user to confirm an action.\n\t// The user is provided with an interface to choose an affirmative or\n\t// negative answer, which is then reflected in the exit code for use in\n\t// scripting.\n\t//\n\t// If the user selects the affirmative answer, the program exits with 0.\n\t// If the user selects the negative answer, the program exits with 1.\n\t//\n\t// I.e. confirm if the user wants to delete a file\n\t//\n\t// $ gum confirm \"Are you sure?\" && rm file.txt\n\t//\n\tConfirm confirm.Options `cmd:\"\" help:\"Ask a user to confirm an action\"`\n\n\t// File provides an interface to pick a file from a folder (tree).\n\t// The user is provided a file manager-like interface to navigate, to\n\t// select a file.\n\t//\n\t// Let's pick a file from the current directory:\n\t//\n\t// $ gum file\n\t// $ gum file .\n\t//\n\t// Let's pick a file from the home directory:\n\t//\n\t// $ gum file $HOME\n\tFile file.Options `cmd:\"\" help:\"Pick a file from a folder\"`\n\n\t// Filter provides a fuzzy searching text input to allow filtering a list of\n\t// options to select one option.\n\t//\n\t// By default it will list all the files (recursively) in the current directory\n\t// for the user to choose one, but the script (or user) can provide different\n\t// new-line separated options to choose from.\n\t//\n\t// I.e. let's pick from a list of gum flavors:\n\t//\n\t// $ cat flavors.text | gum filter\n\t//\n\tFilter filter.Options `cmd:\"\" help:\"Filter items from a list\"`\n\n\t// Format allows you to render styled text from `markdown`, `code`,\n\t// `template` strings, or embedded `emoji` strings.\n\t// For more information see the format/README.md file.\n\tFormat format.Options `cmd:\"\" help:\"Format a string using a template\"`\n\n\t// Input provides a shell script interface for the text input bubble.\n\t// https://github.com/charmbracelet/bubbles/tree/master/textinput\n\t//\n\t// It can be used to prompt the user for some input. The text the user\n\t// entered will be sent to stdout.\n\t//\n\t// $ gum input --placeholder \"What's your favorite gum?\" > answer.text\n\t//\n\tInput input.Options `cmd:\"\" help:\"Prompt for some input\"`\n\n\t// Join provides a shell script interface for the lipgloss JoinHorizontal\n\t// and JoinVertical commands. It allows you to join multi-line text to\n\t// build different layouts.\n\t//\n\t// For example, you can place two bordered boxes next to each other:\n\t// Note: We wrap the variable in quotes to ensure the new lines are part of a\n\t// single argument. Otherwise, the command won't work as expected.\n\t//\n\t// $ gum join --horizontal \"$BUBBLE_BOX\" \"$GUM_BOX\"\n\t//\n\t//   ╔══════════════════════╗╔═════════════╗\n\t//   ║                      ║║             ║\n\t//   ║        Bubble        ║║     Gum     ║\n\t//   ║                      ║║             ║\n\t//   ╚══════════════════════╝╚═════════════╝\n\t//\n\tJoin join.Options `cmd:\"\" help:\"Join text vertically or horizontally\"`\n\n\t// Pager provides a shell script interface for the viewport bubble.\n\t// https://github.com/charmbracelet/bubbles/tree/master/viewport\n\t//\n\t// It allows the user to scroll through content like a pager.\n\t//\n\t// ╭────────────────────────────────────────────────╮\n\t// │    1 │ Gum Pager                               │\n\t// │    2 │ =========                               │\n\t// │    3 │                                         │\n\t// │    4 │ ```                                     │\n\t// │    5 │ gum pager --height 10 --width 25 < text │\n\t// │    6 │ ```                                     │\n\t// │    7 │                                         │\n\t// │    8 │                                         │\n\t// ╰────────────────────────────────────────────────╯\n\t//  ↓↑: navigate • q: quit\n\t//\n\tPager pager.Options `cmd:\"\" help:\"Scroll through a file\"`\n\n\t// Spin provides a shell script interface for the spinner bubble.\n\t// https://github.com/charmbracelet/bubbles/tree/master/spinner\n\t//\n\t// It is useful for displaying that some task is running in the background\n\t// while consuming it's output so that it is not shown to the user.\n\t//\n\t// For example, let's do a long running task: $ sleep 5\n\t//\n\t// We can simply prepend a spinner to this task to show it to the user,\n\t// while performing the task / command in the background.\n\t//\n\t// $ gum spin -t \"Taking a nap...\" -- sleep 5\n\t//\n\t// The spinner will automatically exit when the task is complete.\n\t//\n\tSpin spin.Options `cmd:\"\" help:\"Display spinner while running a command\"`\n\n\t// Style provides a shell script interface for Lip Gloss.\n\t// https://github.com/charmbracelet/lipgloss\n\t//\n\t// It allows you to use Lip Gloss to style text without needing to use Go.\n\t// All of the styling options are available as flags.\n\t//\n\t// Let's make some text glamorous using bash:\n\t//\n\t// $ gum style \\\n\t//  \t--foreground 212 --border double --align center \\\n\t//  \t--width 50 --margin 2 --padding \"2 4\" \\\n\t//  \t\"Bubble Gum (1¢)\" \"So sweet and so fresh\\!\"\n\t//\n\t//\n\t//    ╔══════════════════════════════════════════════════╗\n\t//    ║                                                  ║\n\t//    ║                                                  ║\n\t//    ║                 Bubble Gum (1¢)                  ║\n\t//    ║              So sweet and so fresh!              ║\n\t//    ║                                                  ║\n\t//    ║                                                  ║\n\t//    ╚══════════════════════════════════════════════════╝\n\t//\n\tStyle style.Options `cmd:\"\" help:\"Apply coloring, borders, spacing to text\"`\n\n\t// Table provides a shell script interface for the table bubble.\n\t// https://github.com/charmbracelet/bubbles/tree/master/table\n\t//\n\t// It is useful to render tabular (CSV) data in a terminal and allows\n\t// the user to select a row from the table.\n\t//\n\t// Let's render a table of gum flavors:\n\t//\n\t// $ gum table <<< \"Flavor,Price\\nStrawberry,$0.50\\nBanana,$0.99\\nCherry,$0.75\"\n\t//\n\t//  Flavor      Price\n\t//  Strawberry  $0.50\n\t//  Banana      $0.99\n\t//  Cherry      $0.75\n\t//\n\tTable table.Options `cmd:\"\" help:\"Render a table of data\"`\n\n\t// Write provides a shell script interface for the text area bubble.\n\t// https://github.com/charmbracelet/bubbles/tree/master/textarea\n\t//\n\t// It can be used to ask the user to write some long form of text\n\t// (multi-line) input. The text the user entered will be sent to stdout.\n\t//\n\t// $ gum write > output.text\n\t//\n\tWrite write.Options `cmd:\"\" help:\"Prompt for long-form text\"`\n\n\t// Log provides a shell script interface for logging using Log.\n\t// https://github.com/charmbracelet/log\n\t//\n\t// It can be used to log messages to output.\n\t//\n\t// $ gum log --level info \"Hello, world!\"\n\t//\n\tLog log.Options `cmd:\"\" help:\"Log messages to output\"`\n\n\t// VersionCheck provides a command that checks if the current gum version\n\t// matches a given semantic version constraint.\n\t//\n\t// It can be used to check that a minimum gum version is installed in a\n\t// script.\n\t//\n\t// $ gum version-check '~> 0.15'\n\t//\n\tVersionCheck version.Options `cmd:\"\" help:\"Semver check current gum version\"`\n}\n"
  },
  {
    "path": "input/command.go",
    "content": "package input\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/textinput\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/gum/cursor\"\n\t\"github.com/charmbracelet/gum/internal/stdin\"\n\t\"github.com/charmbracelet/gum/internal/timeout\"\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Run provides a shell script interface for the text input bubble.\n// https://github.com/charmbracelet/bubbles/textinput\nfunc (o Options) Run() error {\n\tif o.Value == \"\" {\n\t\tif in, _ := stdin.Read(stdin.StripANSI(o.StripANSI)); in != \"\" {\n\t\t\to.Value = in\n\t\t}\n\t}\n\n\ti := textinput.New()\n\tif o.Value != \"\" {\n\t\ti.SetValue(o.Value)\n\t} else if in, _ := stdin.Read(stdin.StripANSI(o.StripANSI)); in != \"\" {\n\t\ti.SetValue(in)\n\t}\n\ti.Focus()\n\ti.Prompt = o.Prompt\n\ti.Placeholder = o.Placeholder\n\ti.Width = o.Width\n\ti.PromptStyle = o.PromptStyle.ToLipgloss()\n\ti.PlaceholderStyle = o.PlaceholderStyle.ToLipgloss()\n\ti.Cursor.Style = o.CursorStyle.ToLipgloss()\n\ti.Cursor.SetMode(cursor.Modes[o.CursorMode])\n\ti.CharLimit = o.CharLimit\n\n\tif o.Password {\n\t\ti.EchoMode = textinput.EchoPassword\n\t\ti.EchoCharacter = '•'\n\t}\n\n\ttop, right, bottom, left := style.ParsePadding(o.Padding)\n\tm := model{\n\t\ttextinput:   i,\n\t\theader:      o.Header,\n\t\theaderStyle: o.HeaderStyle.ToLipgloss(),\n\t\tpadding:     []int{top, right, bottom, left},\n\t\tautoWidth:   o.Width < 1,\n\t\tshowHelp:    o.ShowHelp,\n\t\thelp:        help.New(),\n\t\tkeymap:      defaultKeymap(),\n\t}\n\n\tctx, cancel := timeout.Context(o.Timeout)\n\tdefer cancel()\n\n\tp := tea.NewProgram(\n\t\tm,\n\t\ttea.WithOutput(os.Stderr),\n\t\ttea.WithReportFocus(),\n\t\ttea.WithContext(ctx),\n\t)\n\ttm, err := p.Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run input: %w\", err)\n\t}\n\n\tm = tm.(model)\n\tif !m.submitted {\n\t\treturn errors.New(\"not submitted\")\n\t}\n\tfmt.Println(m.textinput.Value())\n\treturn nil\n}\n"
  },
  {
    "path": "input/input.go",
    "content": "// Package input provides a shell script interface for the text input bubble.\n// https://github.com/charmbracelet/bubbles/tree/master/textinput\n//\n// It can be used to prompt the user for some input. The text the user entered\n// will be sent to stdout.\n//\n// $ gum input --placeholder \"What's your favorite gum?\" > answer.text\npackage input\n\nimport (\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/charmbracelet/bubbles/textinput\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\ntype keymap textinput.KeyMap\n\nfunc defaultKeymap() keymap {\n\tk := textinput.DefaultKeyMap\n\treturn keymap(k)\n}\n\n// FullHelp implements help.KeyMap.\nfunc (k keymap) FullHelp() [][]key.Binding { return nil }\n\n// ShortHelp implements help.KeyMap.\nfunc (k keymap) ShortHelp() []key.Binding {\n\treturn []key.Binding{\n\t\tkey.NewBinding(\n\t\t\tkey.WithKeys(\"enter\"),\n\t\t\tkey.WithHelp(\"enter\", \"submit\"),\n\t\t),\n\t}\n}\n\ntype model struct {\n\tautoWidth   bool\n\theader      string\n\tpadding     []int\n\theaderStyle lipgloss.Style\n\ttextinput   textinput.Model\n\tquitting    bool\n\tsubmitted   bool\n\tshowHelp    bool\n\thelp        help.Model\n\tkeymap      keymap\n}\n\nfunc (m model) Init() tea.Cmd { return textinput.Blink }\n\nfunc (m model) View() string {\n\tif m.quitting {\n\t\treturn \"\"\n\t}\n\tvar parts []string\n\tif m.header != \"\" {\n\t\tparts = append(parts, m.headerStyle.Render(m.header))\n\t}\n\n\tparts = append(parts, m.textinput.View())\n\tif m.showHelp {\n\t\tparts = append(parts, \"\", m.help.View(m.keymap))\n\t}\n\treturn lipgloss.NewStyle().\n\t\tPadding(m.padding...).\n\t\tRender(lipgloss.JoinVertical(\n\t\t\tlipgloss.Top,\n\t\t\tparts...,\n\t\t))\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tif m.autoWidth {\n\t\t\tm.textinput.Width = msg.Width - 1 -\n\t\t\t\tlipgloss.Width(m.textinput.Prompt) -\n\t\t\t\tm.padding[1] - m.padding[3]\n\t\t}\n\tcase tea.KeyMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Interrupt\n\t\tcase \"esc\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"enter\":\n\t\t\tm.quitting = true\n\t\t\tm.submitted = true\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\tvar cmd tea.Cmd\n\tm.textinput, cmd = m.textinput.Update(msg)\n\treturn m, cmd\n}\n"
  },
  {
    "path": "input/options.go",
    "content": "package input\n\nimport (\n\t\"time\"\n\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Options are the customization options for the input.\ntype Options struct {\n\tPlaceholder      string        `help:\"Placeholder value\" default:\"Type something...\" env:\"GUM_INPUT_PLACEHOLDER\"`\n\tPrompt           string        `help:\"Prompt to display\" default:\"> \" env:\"GUM_INPUT_PROMPT\"`\n\tPromptStyle      style.Styles  `embed:\"\" prefix:\"prompt.\" envprefix:\"GUM_INPUT_PROMPT_\"`\n\tPlaceholderStyle style.Styles  `embed:\"\" prefix:\"placeholder.\" set:\"defaultForeground=240\" envprefix:\"GUM_INPUT_PLACEHOLDER_\"`\n\tCursorStyle      style.Styles  `embed:\"\" prefix:\"cursor.\" set:\"defaultForeground=212\" envprefix:\"GUM_INPUT_CURSOR_\"`\n\tCursorMode       string        `prefix:\"cursor.\" name:\"mode\" help:\"Cursor mode\" default:\"blink\" enum:\"blink,hide,static\" env:\"GUM_INPUT_CURSOR_MODE\"`\n\tValue            string        `help:\"Initial value (can also be passed via stdin)\" default:\"\"`\n\tCharLimit        int           `help:\"Maximum value length (0 for no limit)\" default:\"400\"`\n\tWidth            int           `help:\"Input width (0 for terminal width)\" default:\"0\" env:\"GUM_INPUT_WIDTH\"`\n\tPassword         bool          `help:\"Mask input characters\" default:\"false\"`\n\tShowHelp         bool          `help:\"Show help keybinds\" default:\"true\" negatable:\"\" env:\"GUM_INPUT_SHOW_HELP\"`\n\tHeader           string        `help:\"Header value\" default:\"\" env:\"GUM_INPUT_HEADER\"`\n\tHeaderStyle      style.Styles  `embed:\"\" prefix:\"header.\" set:\"defaultForeground=240\" envprefix:\"GUM_INPUT_HEADER_\"`\n\tTimeout          time.Duration `help:\"Timeout until input aborts\" default:\"0s\" env:\"GUM_INPUT_TIMEOUT\"`\n\tStripANSI        bool          `help:\"Strip ANSI sequences when reading from STDIN\" default:\"true\" negatable:\"\" env:\"GUM_INPUT_STRIP_ANSI\"`\n\tPadding          string        `help:\"Padding\" default:\"${defaultPadding}\" group:\"Style Flags\" env:\"GUM_INPUT_PADDING\"`\n}\n"
  },
  {
    "path": "internal/decode/align.go",
    "content": "// Package decode position strings to lipgloss.\npackage decode\n\nimport \"github.com/charmbracelet/lipgloss\"\n\n// Align maps strings to `lipgloss.Position`s.\nvar Align = map[string]lipgloss.Position{\n\t\"center\": lipgloss.Center,\n\t\"left\":   lipgloss.Left,\n\t\"top\":    lipgloss.Top,\n\t\"bottom\": lipgloss.Bottom,\n\t\"right\":  lipgloss.Right,\n}\n"
  },
  {
    "path": "internal/exit/exit.go",
    "content": "// Package exit code implementation.\npackage exit\n\nimport \"strconv\"\n\n// StatusTimeout is the exit code for timed out commands.\nconst StatusTimeout = 124\n\n// StatusAborted is the exit code for aborted commands.\nconst StatusAborted = 130\n\n// ErrExit is a custom exit error.\ntype ErrExit int\n\n// Error implements error.\nfunc (e ErrExit) Error() string { return \"exit \" + strconv.Itoa(int(e)) }\n"
  },
  {
    "path": "internal/files/files.go",
    "content": "// Package files handles files.\npackage files\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// List returns a list of all files in the current directory.\n// It ignores the .git directory.\nfunc List() []string {\n\tvar files []string\n\terr := filepath.Walk(\".\",\n\t\tfunc(path string, info os.FileInfo, err error) error {\n\t\t\tif shouldIgnore(path) || info.IsDir() || err != nil {\n\t\t\t\treturn nil //nolint:nilerr\n\t\t\t}\n\t\t\tfiles = append(files, path)\n\t\t\treturn nil\n\t\t})\n\tif err != nil {\n\t\treturn []string{}\n\t}\n\treturn files\n}\n\nvar defaultIgnorePatterns = []string{\"node_modules\", \".git\", \".\"}\n\nfunc shouldIgnore(path string) bool {\n\tfor _, prefix := range defaultIgnorePatterns {\n\t\tif strings.HasPrefix(path, prefix) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "internal/stdin/stdin.go",
    "content": "// Package stdin handles processing input from stdin.\npackage stdin\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\ntype options struct {\n\tansiStrip  bool\n\tsingleLine bool\n}\n\n// Option is a read option.\ntype Option func(*options)\n\n// StripANSI optionally strips ansi sequences.\nfunc StripANSI(b bool) Option {\n\treturn func(o *options) {\n\t\to.ansiStrip = b\n\t}\n}\n\n// SingleLine reads a single line.\nfunc SingleLine(b bool) Option {\n\treturn func(o *options) {\n\t\to.singleLine = b\n\t}\n}\n\n// Read reads input from an stdin pipe.\nfunc Read(opts ...Option) (string, error) {\n\tif IsEmpty() {\n\t\treturn \"\", fmt.Errorf(\"stdin is empty\")\n\t}\n\n\toptions := options{}\n\tfor _, opt := range opts {\n\t\topt(&options)\n\t}\n\n\treader := bufio.NewReader(os.Stdin)\n\tvar b strings.Builder\n\n\tif options.singleLine {\n\t\tline, _, err := reader.ReadLine()\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to read line: %w\", err)\n\t\t}\n\t\t_, err = b.Write(line)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to write: %w\", err)\n\t\t}\n\t}\n\n\tfor !options.singleLine {\n\t\tr, _, err := reader.ReadRune()\n\t\tif err != nil && err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\t_, err = b.WriteRune(r)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to write rune: %w\", err)\n\t\t}\n\t}\n\n\ts := strings.TrimSpace(b.String())\n\tif options.ansiStrip {\n\t\treturn ansi.Strip(s), nil\n\t}\n\treturn s, nil\n}\n\n// IsEmpty returns whether stdin is empty.\nfunc IsEmpty() bool {\n\tstat, err := os.Stdin.Stat()\n\tif err != nil {\n\t\treturn true\n\t}\n\n\tif stat.Mode()&os.ModeNamedPipe == 0 && stat.Size() == 0 {\n\t\treturn true\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "internal/timeout/context.go",
    "content": "// Package timeout handles context timeouts.\npackage timeout\n\nimport (\n\t\"context\"\n\t\"time\"\n)\n\n// Context setup a new context that times out if the given timeout is > 0.\nfunc Context(timeout time.Duration) (context.Context, context.CancelFunc) {\n\tctx := context.Background()\n\tif timeout == 0 {\n\t\treturn ctx, func() {}\n\t}\n\treturn context.WithTimeout(ctx, timeout)\n}\n"
  },
  {
    "path": "internal/tty/tty.go",
    "content": "// Package tty provides tty-aware printing.\npackage tty\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n\t\"github.com/charmbracelet/x/term\"\n)\n\nvar isTTY = sync.OnceValue(func() bool {\n\treturn term.IsTerminal(os.Stdout.Fd())\n})\n\n// Println handles println, striping ansi sequences if stdout is not a tty.\nfunc Println(s string) {\n\tif isTTY() {\n\t\tfmt.Println(s)\n\t\treturn\n\t}\n\tfmt.Println(ansi.Strip(s))\n}\n"
  },
  {
    "path": "join/command.go",
    "content": "// Package join provides a shell script interface for the lipgloss\n// JoinHorizontal and JoinVertical commands. It allows you to join multi-line\n// text to build different layouts.\n//\n// For example, you can place two bordered boxes next to each other: Note: We\n// wrap the variable in quotes to ensure the new lines are part of a single\n// argument. Otherwise, the command won't work as expected.\n//\n// $ gum join --horizontal \"$BUBBLE_BOX\" \"$GUM_BOX\"\n//\n// ╔══════════════════════╗╔═════════════╗\n// ║                      ║║             ║\n// ║        Bubble        ║║     Gum     ║\n// ║                      ║║             ║\n// ╚══════════════════════╝╚═════════════╝\npackage join\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/charmbracelet/lipgloss\"\n\n\t\"github.com/charmbracelet/gum/internal/decode\"\n)\n\n// Run is the command-line interface for the joining strings through lipgloss.\nfunc (o Options) Run() error {\n\tjoin := lipgloss.JoinHorizontal\n\tif o.Vertical {\n\t\tjoin = lipgloss.JoinVertical\n\t}\n\tfmt.Println(join(decode.Align[o.Align], o.Text...))\n\treturn nil\n}\n"
  },
  {
    "path": "join/options.go",
    "content": "package join\n\n// Options is the set of options that can configure a join.\ntype Options struct {\n\tText []string `arg:\"\" help:\"Text to join.\"`\n\n\tAlign      string `help:\"Text alignment\" enum:\"left,center,right,bottom,middle,top\" default:\"left\"`\n\tHorizontal bool   `help:\"Join (potentially multi-line) strings horizontally\"`\n\tVertical   bool   `help:\"Join (potentially multi-line) strings vertically\"`\n}\n"
  },
  {
    "path": "log/command.go",
    "content": "// Package log the log command.\npackage log\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/charmbracelet/log\"\n)\n\n// Run is the command-line interface for logging text.\nfunc (o Options) Run() error {\n\tl := log.New(os.Stderr)\n\n\tif o.File != \"\" {\n\t\tf, err := os.OpenFile(o.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm) //nolint:gosec\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error opening file: %w\", err)\n\t\t}\n\n\t\tdefer f.Close() //nolint:errcheck\n\t\tl.SetOutput(f)\n\t}\n\n\tl.SetPrefix(o.Prefix)\n\tl.SetLevel(-math.MaxInt32) // log all levels\n\tl.SetReportTimestamp(o.Time != \"\")\n\tif o.MinLevel != \"\" {\n\t\tlvl, err := log.ParseLevel(o.MinLevel)\n\t\tif err != nil {\n\t\t\treturn err //nolint:wrapcheck\n\t\t}\n\t\tl.SetLevel(lvl)\n\t}\n\n\ttimeFormats := map[string]string{\n\t\t\"layout\":      time.Layout,\n\t\t\"ansic\":       time.ANSIC,\n\t\t\"unixdate\":    time.UnixDate,\n\t\t\"rubydate\":    time.RubyDate,\n\t\t\"rfc822\":      time.RFC822,\n\t\t\"rfc822z\":     time.RFC822Z,\n\t\t\"rfc850\":      time.RFC850,\n\t\t\"rfc1123\":     time.RFC1123,\n\t\t\"rfc1123z\":    time.RFC1123Z,\n\t\t\"rfc3339\":     time.RFC3339,\n\t\t\"rfc3339nano\": time.RFC3339Nano,\n\t\t\"kitchen\":     time.Kitchen,\n\t\t\"stamp\":       time.Stamp,\n\t\t\"stampmilli\":  time.StampMilli,\n\t\t\"stampmicro\":  time.StampMicro,\n\t\t\"stampnano\":   time.StampNano,\n\t\t\"datetime\":    time.DateTime,\n\t\t\"dateonly\":    time.DateOnly,\n\t\t\"timeonly\":    time.TimeOnly,\n\t}\n\n\ttf, ok := timeFormats[strings.ToLower(o.Time)]\n\tif ok {\n\t\tl.SetTimeFormat(tf)\n\t} else {\n\t\tl.SetTimeFormat(o.Time)\n\t}\n\n\tst := log.DefaultStyles()\n\tlvl := levelToLog[o.Level]\n\tlvlStyle := o.LevelStyle.ToLipgloss()\n\tif lvlStyle.GetForeground() == lipgloss.Color(\"\") {\n\t\tlvlStyle = lvlStyle.Foreground(st.Levels[lvl].GetForeground())\n\t}\n\n\tst.Levels[lvl] = lvlStyle.\n\t\tSetString(strings.ToUpper(lvl.String())).\n\t\tInline(true)\n\n\tst.Timestamp = o.TimeStyle.ToLipgloss().\n\t\tInline(true)\n\tst.Prefix = o.PrefixStyle.ToLipgloss().\n\t\tInline(true)\n\tst.Message = o.MessageStyle.ToLipgloss().\n\t\tInline(true)\n\tst.Key = o.KeyStyle.ToLipgloss().\n\t\tInline(true)\n\tst.Value = o.ValueStyle.ToLipgloss().\n\t\tInline(true)\n\tst.Separator = o.SeparatorStyle.ToLipgloss().\n\t\tInline(true)\n\n\tl.SetStyles(st)\n\n\tswitch o.Formatter {\n\tcase \"json\":\n\t\tl.SetFormatter(log.JSONFormatter)\n\tcase \"logfmt\":\n\t\tl.SetFormatter(log.LogfmtFormatter)\n\tcase \"text\":\n\t\tl.SetFormatter(log.TextFormatter)\n\t}\n\n\tvar arg0 string\n\tvar args []interface{}\n\tif len(o.Text) > 0 {\n\t\targ0 = o.Text[0]\n\t}\n\n\tif len(o.Text) > 1 {\n\t\targs = make([]interface{}, len(o.Text[1:]))\n\t\tfor i, arg := range o.Text[1:] {\n\t\t\targs[i] = arg\n\t\t}\n\t}\n\n\tlogger := map[string]logger{\n\t\t\"none\":  {printf: l.Printf, print: l.Print},\n\t\t\"debug\": {printf: l.Debugf, print: l.Debug},\n\t\t\"info\":  {printf: l.Infof, print: l.Info},\n\t\t\"warn\":  {printf: l.Warnf, print: l.Warn},\n\t\t\"error\": {printf: l.Errorf, print: l.Error},\n\t\t\"fatal\": {printf: l.Fatalf, print: l.Fatal},\n\t}[o.Level]\n\n\tif o.Format {\n\t\tlogger.printf(arg0, args...)\n\t} else if o.Structured {\n\t\tlogger.print(arg0, args...)\n\t} else {\n\t\tlogger.print(strings.Join(o.Text, \" \"))\n\t}\n\n\treturn nil\n}\n\ntype logger struct {\n\tprintf func(string, ...interface{})\n\tprint  func(interface{}, ...interface{})\n}\n\nvar levelToLog = map[string]log.Level{\n\t\"none\":  log.Level(math.MaxInt32),\n\t\"debug\": log.DebugLevel,\n\t\"info\":  log.InfoLevel,\n\t\"warn\":  log.WarnLevel,\n\t\"error\": log.ErrorLevel,\n\t\"fatal\": log.FatalLevel,\n}\n"
  },
  {
    "path": "log/options.go",
    "content": "package log\n\nimport (\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Options is the set of options that can configure a join.\ntype Options struct {\n\tText []string `arg:\"\" help:\"Text to log\"`\n\n\tFile       string `short:\"o\" help:\"Log to file\"`\n\tFormat     bool   `short:\"f\" help:\"Format message using printf\" xor:\"format,structured\"`\n\tFormatter  string `help:\"The log formatter to use\" enum:\"json,logfmt,text\" default:\"text\"`\n\tLevel      string `short:\"l\" help:\"The log level to use\" enum:\"none,debug,info,warn,error,fatal\" default:\"none\"`\n\tPrefix     string `help:\"Prefix to print before the message\"`\n\tStructured bool   `short:\"s\" help:\"Use structured logging\" xor:\"format,structured\"`\n\tTime       string `short:\"t\" help:\"The time format to use (kitchen, layout, ansic, rfc822, etc...)\" default:\"\"`\n\n\tMinLevel string `help:\"Minimal level to show\" default:\"\" env:\"GUM_LOG_LEVEL\"`\n\n\tLevelStyle     style.Styles `embed:\"\" prefix:\"level.\" help:\"The style of the level being used\" set:\"defaultBold=true\" envprefix:\"GUM_LOG_LEVEL_\"`\n\tTimeStyle      style.Styles `embed:\"\" prefix:\"time.\" help:\"The style of the time\" envprefix:\"GUM_LOG_TIME_\"`\n\tPrefixStyle    style.Styles `embed:\"\" prefix:\"prefix.\" help:\"The style of the prefix\" set:\"defaultBold=true\" set:\"defaultFaint=true\" envprefix:\"GUM_LOG_PREFIX_\"` //nolint:staticcheck\n\tMessageStyle   style.Styles `embed:\"\" prefix:\"message.\" help:\"The style of the message\" envprefix:\"GUM_LOG_MESSAGE_\"`\n\tKeyStyle       style.Styles `embed:\"\" prefix:\"key.\" help:\"The style of the key\" set:\"defaultFaint=true\" envprefix:\"GUM_LOG_KEY_\"`\n\tValueStyle     style.Styles `embed:\"\" prefix:\"value.\" help:\"The style of the value\" envprefix:\"GUM_LOG_VALUE_\"`\n\tSeparatorStyle style.Styles `embed:\"\" prefix:\"separator.\" help:\"The style of the separator\" set:\"defaultFaint=true\" envprefix:\"GUM_LOG_SEPARATOR_\"`\n}\n"
  },
  {
    "path": "main.go",
    "content": "// Package main is Gum: a tool for glamorous shell scripts.\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\n\t\"github.com/alecthomas/kong\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/gum/internal/exit\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/muesli/termenv\"\n)\n\nconst shaLen = 7\n\nvar (\n\t// Version contains the application version number. It's set via ldflags\n\t// when building.\n\tVersion = \"\"\n\n\t// CommitSHA contains the SHA of the commit that this application was built\n\t// against. It's set via ldflags when building.\n\tCommitSHA = \"\"\n)\n\nvar bubbleGumPink = lipgloss.NewStyle().Foreground(lipgloss.Color(\"212\"))\n\nfunc main() {\n\tlipgloss.SetColorProfile(termenv.NewOutput(os.Stderr).Profile)\n\n\tif Version == \"\" {\n\t\tif info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != \"\" {\n\t\t\tVersion = info.Main.Version\n\t\t} else {\n\t\t\tVersion = \"unknown (built from source)\"\n\t\t}\n\t}\n\tversion := fmt.Sprintf(\"gum version %s\", Version)\n\tif len(CommitSHA) >= shaLen {\n\t\tversion += \" (\" + CommitSHA[:shaLen] + \")\"\n\t}\n\n\tgum := &Gum{}\n\tctx := kong.Parse(\n\t\tgum,\n\t\tkong.Description(fmt.Sprintf(\"A tool for %s shell scripts.\", bubbleGumPink.Render(\"glamorous\"))),\n\t\tkong.UsageOnError(),\n\t\tkong.ConfigureHelp(kong.HelpOptions{\n\t\t\tCompact:             true,\n\t\t\tSummary:             false,\n\t\t\tNoExpandSubcommands: true,\n\t\t}),\n\t\tkong.Vars{\n\t\t\t\"version\":                 version,\n\t\t\t\"versionNumber\":           Version,\n\t\t\t\"defaultHeight\":           \"0\",\n\t\t\t\"defaultWidth\":            \"0\",\n\t\t\t\"defaultAlign\":            \"left\",\n\t\t\t\"defaultBorder\":           \"none\",\n\t\t\t\"defaultBorderForeground\": \"\",\n\t\t\t\"defaultBorderBackground\": \"\",\n\t\t\t\"defaultBackground\":       \"\",\n\t\t\t\"defaultForeground\":       \"\",\n\t\t\t\"defaultMargin\":           \"0 0\",\n\t\t\t\"defaultPadding\":          \"0 0\",\n\t\t\t\"defaultUnderline\":        \"false\",\n\t\t\t\"defaultBold\":             \"false\",\n\t\t\t\"defaultFaint\":            \"false\",\n\t\t\t\"defaultItalic\":           \"false\",\n\t\t\t\"defaultStrikethrough\":    \"false\",\n\t\t},\n\t)\n\tif err := ctx.Run(); err != nil {\n\t\tvar ex exit.ErrExit\n\t\tif errors.As(err, &ex) {\n\t\t\tos.Exit(int(ex))\n\t\t}\n\t\tif errors.Is(err, tea.ErrInterrupted) {\n\t\t\tos.Exit(exit.StatusAborted)\n\t\t}\n\t\tif errors.Is(err, tea.ErrProgramKilled) {\n\t\t\tfmt.Fprintln(os.Stderr, \"timed out\")\n\t\t\tos.Exit(exit.StatusTimeout)\n\t\t}\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "man/command.go",
    "content": "// Package man the man command.\npackage man\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/alecthomas/kong\"\n\tmangokong \"github.com/alecthomas/mango-kong\"\n\t\"github.com/muesli/roff\"\n)\n\n// Man is a gum sub-command that generates man pages.\ntype Man struct{}\n\n// BeforeApply implements Kong BeforeApply hook.\nfunc (m Man) BeforeApply(ctx *kong.Context) error {\n\t// Set the correct man pages description without color escape sequences.\n\tctx.Model.Help = \"A tool for glamorous shell scripts.\"\n\tman := mangokong.NewManPage(1, ctx.Model)\n\tman = man.WithSection(\"Copyright\", \"(c) 2022-2024 Charmbracelet, Inc.\\n\"+\n\t\t\"Released under MIT license.\")\n\t_, _ = fmt.Fprint(ctx.Stdout, man.Build(roff.NewDocument()))\n\tctx.Exit(0)\n\treturn nil\n}\n"
  },
  {
    "path": "pager/command.go",
    "content": "package pager\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/viewport\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/gum/internal/stdin\"\n\t\"github.com/charmbracelet/gum/internal/timeout\"\n)\n\n// Run provides a shell script interface for the viewport bubble.\n// https://github.com/charmbracelet/bubbles/viewport\nfunc (o Options) Run() error {\n\tvp := viewport.New(o.Style.Width, o.Style.Height)\n\tvp.Style = o.Style.ToLipgloss()\n\n\tif o.Content == \"\" {\n\t\tstdin, err := stdin.Read()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to read stdin\")\n\t\t}\n\t\tif stdin != \"\" {\n\t\t\t// Sanitize the input from stdin by removing backspace sequences.\n\t\t\tbackspace := regexp.MustCompile(\".\\x08\")\n\t\t\to.Content = backspace.ReplaceAllString(stdin, \"\")\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"provide some content to display\")\n\t\t}\n\t}\n\n\tm := model{\n\t\tviewport:            vp,\n\t\thelp:                help.New(),\n\t\tcontent:             o.Content,\n\t\torigContent:         o.Content,\n\t\tshowLineNumbers:     o.ShowLineNumbers,\n\t\tlineNumberStyle:     o.LineNumberStyle.ToLipgloss(),\n\t\tsoftWrap:            o.SoftWrap,\n\t\tmatchStyle:          o.MatchStyle.ToLipgloss(),\n\t\tmatchHighlightStyle: o.MatchHighlightStyle.ToLipgloss(),\n\t\tkeymap:              defaultKeymap(),\n\t}\n\n\tctx, cancel := timeout.Context(o.Timeout)\n\tdefer cancel()\n\n\t_, err := tea.NewProgram(\n\t\tm,\n\t\ttea.WithAltScreen(),\n\t\ttea.WithReportFocus(),\n\t\ttea.WithContext(ctx),\n\t).Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to start program: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pager/options.go",
    "content": "package pager\n\nimport (\n\t\"time\"\n\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Options are the options for the pager.\ntype Options struct {\n\t//nolint:staticcheck\n\tStyle               style.Styles  `embed:\"\" help:\"Style the pager\" set:\"defaultBorder=rounded\" set:\"defaultPadding=0 1\" set:\"defaultBorderForeground=212\" envprefix:\"GUM_PAGER_\"`\n\tContent             string        `arg:\"\" optional:\"\" help:\"Display content to scroll\"`\n\tShowLineNumbers     bool          `help:\"Show line numbers\" default:\"true\"`\n\tLineNumberStyle     style.Styles  `embed:\"\" prefix:\"line-number.\" help:\"Style the line numbers\" set:\"defaultForeground=237\" envprefix:\"GUM_PAGER_LINE_NUMBER_\"`\n\tSoftWrap            bool          `help:\"Soft wrap lines\" default:\"true\" negatable:\"\"`\n\tMatchStyle          style.Styles  `embed:\"\" prefix:\"match.\" help:\"Style the matched text\" set:\"defaultForeground=212\" set:\"defaultBold=true\" envprefix:\"GUM_PAGER_MATCH_\"`                                                      //nolint:staticcheck\n\tMatchHighlightStyle style.Styles  `embed:\"\" prefix:\"match-highlight.\" help:\"Style the matched highlight text\" set:\"defaultForeground=235\" set:\"defaultBackground=225\" set:\"defaultBold=true\" envprefix:\"GUM_PAGER_MATCH_HIGH_\"` //nolint:staticcheck\n\tTimeout             time.Duration `help:\"Timeout until command exits\" default:\"0s\" env:\"GUM_PAGER_TIMEOUT\"`\n\n\t// Deprecated: this has no effect anymore.\n\tHelpStyle style.Styles `embed:\"\" prefix:\"help.\" help:\"Style the help text\" set:\"defaultForeground=241\" envprefix:\"GUM_PAGER_HELP_\" hidden:\"\"`\n}\n"
  },
  {
    "path": "pager/pager.go",
    "content": "// Package pager provides a pager (similar to less) for the terminal.\n//\n// $ cat file.txt | gum pager\npackage pager\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/charmbracelet/bubbles/textinput\"\n\t\"github.com/charmbracelet/bubbles/viewport\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\ntype keymap struct {\n\tHome,\n\tEnd,\n\tSearch,\n\tNextMatch,\n\tPrevMatch,\n\tAbort,\n\tQuit,\n\tConfirmSearch,\n\tCancelSearch key.Binding\n}\n\n// FullHelp implements help.KeyMap.\nfunc (k keymap) FullHelp() [][]key.Binding {\n\treturn nil\n}\n\n// ShortHelp implements help.KeyMap.\nfunc (k keymap) ShortHelp() []key.Binding {\n\treturn []key.Binding{\n\t\tkey.NewBinding(\n\t\t\tkey.WithKeys(\"up\", \"down\"),\n\t\t\tkey.WithHelp(\"↓↑\", \"navigate\"),\n\t\t),\n\t\tk.Quit,\n\t\tk.Search,\n\t\tk.NextMatch,\n\t\tk.PrevMatch,\n\t}\n}\n\nfunc defaultKeymap() keymap {\n\treturn keymap{\n\t\tHome: key.NewBinding(\n\t\t\tkey.WithKeys(\"g\", \"home\"),\n\t\t\tkey.WithHelp(\"h\", \"home\"),\n\t\t),\n\t\tEnd: key.NewBinding(\n\t\t\tkey.WithKeys(\"G\", \"end\"),\n\t\t\tkey.WithHelp(\"G\", \"end\"),\n\t\t),\n\t\tSearch: key.NewBinding(\n\t\t\tkey.WithKeys(\"/\"),\n\t\t\tkey.WithHelp(\"/\", \"search\"),\n\t\t),\n\t\tPrevMatch: key.NewBinding(\n\t\t\tkey.WithKeys(\"p\", \"N\"),\n\t\t\tkey.WithHelp(\"N\", \"previous match\"),\n\t\t),\n\t\tNextMatch: key.NewBinding(\n\t\t\tkey.WithKeys(\"n\"),\n\t\t\tkey.WithHelp(\"n\", \"next match\"),\n\t\t),\n\t\tAbort: key.NewBinding(\n\t\t\tkey.WithKeys(\"ctrl+c\"),\n\t\t\tkey.WithHelp(\"ctrl+c\", \"abort\"),\n\t\t),\n\t\tQuit: key.NewBinding(\n\t\t\tkey.WithKeys(\"q\", \"esc\"),\n\t\t\tkey.WithHelp(\"esc\", \"quit\"),\n\t\t),\n\t\tConfirmSearch: key.NewBinding(\n\t\t\tkey.WithKeys(\"enter\"),\n\t\t\tkey.WithHelp(\"enter\", \"confirm\"),\n\t\t),\n\t\tCancelSearch: key.NewBinding(\n\t\t\tkey.WithKeys(\"ctrl+c\", \"ctrl+d\", \"esc\"),\n\t\t\tkey.WithHelp(\"ctrl+c\", \"cancel\"),\n\t\t),\n\t}\n}\n\ntype model struct {\n\tcontent             string\n\torigContent         string\n\tviewport            viewport.Model\n\thelp                help.Model\n\tshowLineNumbers     bool\n\tlineNumberStyle     lipgloss.Style\n\tsoftWrap            bool\n\tsearch              search\n\tmatchStyle          lipgloss.Style\n\tmatchHighlightStyle lipgloss.Style\n\tmaxWidth            int\n\tkeymap              keymap\n}\n\nfunc (m model) Init() tea.Cmd { return nil }\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tm.processText(msg)\n\tcase tea.KeyMsg:\n\t\treturn m.keyHandler(msg)\n\t}\n\n\tm.keymap.PrevMatch.SetEnabled(m.search.query != nil)\n\tm.keymap.NextMatch.SetEnabled(m.search.query != nil)\n\n\tvar cmd tea.Cmd\n\tm.search.input, cmd = m.search.input.Update(msg)\n\treturn m, cmd\n}\n\nfunc (m *model) helpView() string {\n\treturn m.help.View(m.keymap)\n}\n\nfunc (m *model) processText(msg tea.WindowSizeMsg) {\n\tm.viewport.Height = msg.Height - lipgloss.Height(m.helpView())\n\tm.viewport.Width = msg.Width\n\ttextStyle := lipgloss.NewStyle().Width(m.viewport.Width)\n\tvar text strings.Builder\n\n\t// Determine max width of a line.\n\tm.maxWidth = m.viewport.Width\n\tif m.softWrap {\n\t\tvpStyle := m.viewport.Style\n\t\tm.maxWidth -= vpStyle.GetHorizontalBorderSize() + vpStyle.GetHorizontalMargins() + vpStyle.GetHorizontalPadding()\n\t\tif m.showLineNumbers {\n\t\t\tm.maxWidth -= lipgloss.Width(\"     │ \")\n\t\t}\n\t}\n\n\tfor i, line := range strings.Split(m.content, \"\\n\") {\n\t\tline = strings.ReplaceAll(line, \"\\t\", \"    \")\n\t\tif m.showLineNumbers {\n\t\t\ttext.WriteString(m.lineNumberStyle.Render(fmt.Sprintf(\"%4d │ \", i+1)))\n\t\t}\n\t\tidx := 0\n\t\tif w := ansi.StringWidth(line); m.softWrap && w > m.maxWidth {\n\t\t\tfor w > idx {\n\t\t\t\tif m.showLineNumbers && idx != 0 {\n\t\t\t\t\ttext.WriteString(m.lineNumberStyle.Render(\"     │ \"))\n\t\t\t\t}\n\t\t\t\ttruncatedLine := ansi.Cut(line, idx, m.maxWidth+idx)\n\t\t\t\tidx += m.maxWidth\n\t\t\t\ttext.WriteString(textStyle.Render(truncatedLine))\n\t\t\t\ttext.WriteString(\"\\n\")\n\t\t\t}\n\t\t} else {\n\t\t\ttext.WriteString(textStyle.Render(line))\n\t\t\ttext.WriteString(\"\\n\")\n\t\t}\n\t}\n\n\tdiffHeight := m.viewport.Height - lipgloss.Height(text.String())\n\tif diffHeight > 0 && m.showLineNumbers {\n\t\tremainingLines := \"   ~ │ \" + strings.Repeat(\"\\n   ~ │ \", diffHeight-1)\n\t\ttext.WriteString(m.lineNumberStyle.Render(remainingLines))\n\t}\n\tm.viewport.SetContent(text.String())\n}\n\nconst heightOffset = 2\n\nfunc (m model) keyHandler(msg tea.KeyMsg) (model, tea.Cmd) {\n\tkm := m.keymap\n\tvar cmd tea.Cmd\n\tif m.search.active {\n\t\tswitch {\n\t\tcase key.Matches(msg, km.ConfirmSearch):\n\t\t\tif m.search.input.Value() != \"\" {\n\t\t\t\tm.content = m.origContent\n\t\t\t\tm.search.Execute(&m)\n\n\t\t\t\t// Trigger a view update to highlight the found matches.\n\t\t\t\tm.search.NextMatch(&m)\n\t\t\t\tm.processText(tea.WindowSizeMsg{Height: m.viewport.Height + heightOffset, Width: m.viewport.Width})\n\t\t\t} else {\n\t\t\t\tm.search.Done()\n\t\t\t}\n\t\tcase key.Matches(msg, km.CancelSearch):\n\t\t\tm.search.Done()\n\t\tdefault:\n\t\t\tm.search.input, cmd = m.search.input.Update(msg)\n\t\t}\n\t} else {\n\t\tswitch {\n\t\tcase key.Matches(msg, km.Home):\n\t\t\tm.viewport.GotoTop()\n\t\tcase key.Matches(msg, km.End):\n\t\t\tm.viewport.GotoBottom()\n\t\tcase key.Matches(msg, km.Search):\n\t\t\tm.search.Begin()\n\t\t\treturn m, textinput.Blink\n\t\tcase key.Matches(msg, km.PrevMatch):\n\t\t\tm.search.PrevMatch(&m)\n\t\t\tm.processText(tea.WindowSizeMsg{Height: m.viewport.Height + heightOffset, Width: m.viewport.Width})\n\t\tcase key.Matches(msg, km.NextMatch):\n\t\t\tm.search.NextMatch(&m)\n\t\t\tm.processText(tea.WindowSizeMsg{Height: m.viewport.Height + heightOffset, Width: m.viewport.Width})\n\t\tcase key.Matches(msg, km.Quit):\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, km.Abort):\n\t\t\treturn m, tea.Interrupt\n\t\t}\n\t\tm.viewport, cmd = m.viewport.Update(msg)\n\t}\n\n\treturn m, cmd\n}\n\nfunc (m model) View() string {\n\tif m.search.active {\n\t\treturn m.viewport.View() + \"\\n \" + m.search.input.View()\n\t}\n\n\treturn m.viewport.View() + \"\\n\" + m.helpView()\n}\n"
  },
  {
    "path": "pager/search.go",
    "content": "package pager\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/bubbles/textinput\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\ntype search struct {\n\tactive           bool\n\tinput            textinput.Model\n\tquery            *regexp.Regexp\n\tmatchIndex       int\n\tmatchLipglossStr string\n\tmatchString      string\n}\n\nfunc (s *search) new() {\n\tinput := textinput.New()\n\tinput.Placeholder = \"search\"\n\tinput.Prompt = \"/\"\n\tinput.PromptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"240\"))\n\ts.input = input\n}\n\nfunc (s *search) Begin() {\n\ts.new()\n\ts.active = true\n\ts.input.Focus()\n}\n\n// Execute find all lines in the model with a match.\nfunc (s *search) Execute(m *model) {\n\tdefer s.Done()\n\tif s.input.Value() == \"\" {\n\t\ts.query = nil\n\t\treturn\n\t}\n\n\tvar err error\n\ts.query, err = regexp.Compile(s.input.Value())\n\tif err != nil {\n\t\ts.query = nil\n\t\treturn\n\t}\n\tquery := regexp.MustCompile(fmt.Sprintf(\"(%s)\", s.query.String()))\n\tm.content = query.ReplaceAllString(m.content, m.matchStyle.Render(\"$1\"))\n\n\t// Recompile the regex to match the an replace the highlights.\n\tleftPad, _ := lipglossPadding(m.matchStyle)\n\tmatchingString := regexp.QuoteMeta(m.matchStyle.Render()[:leftPad]) + s.query.String() + regexp.QuoteMeta(m.matchStyle.Render()[leftPad:])\n\ts.query, err = regexp.Compile(matchingString)\n\tif err != nil {\n\t\ts.query = nil\n\t}\n}\n\nfunc (s *search) Done() {\n\ts.active = false\n\n\t// To account for the first match is always executed.\n\ts.matchIndex = -1\n}\n\nfunc (s *search) NextMatch(m *model) {\n\t// Check that we are within bounds.\n\tif s.query == nil {\n\t\treturn\n\t}\n\n\t// Remove previous highlight.\n\tm.content = strings.Replace(m.content, s.matchLipglossStr, s.matchString, 1)\n\n\t// Highlight the next match.\n\tallMatches := s.query.FindAllStringIndex(m.content, -1)\n\tif len(allMatches) == 0 {\n\t\treturn\n\t}\n\n\tleftPad, rightPad := lipglossPadding(m.matchStyle)\n\ts.matchIndex = (s.matchIndex + 1) % len(allMatches)\n\tmatch := allMatches[s.matchIndex]\n\tlhs := m.content[:match[0]]\n\trhs := m.content[match[0]:]\n\ts.matchString = m.content[match[0]:match[1]]\n\ts.matchLipglossStr = m.matchHighlightStyle.Render(s.matchString[leftPad : len(s.matchString)-rightPad])\n\tm.content = lhs + strings.Replace(rhs, m.content[match[0]:match[1]], s.matchLipglossStr, 1)\n\n\t// Update the viewport position.\n\tvar line int\n\tformatStr := softWrapEm(m.content, m.maxWidth, m.softWrap)\n\tindex := strings.Index(formatStr, s.matchLipglossStr)\n\tif index != -1 {\n\t\tline = strings.Count(formatStr[:index], \"\\n\")\n\t}\n\n\t// Only update if the match is not within the viewport.\n\tif index != -1 && (line > m.viewport.YOffset-1+m.viewport.VisibleLineCount()-1 || line < m.viewport.YOffset) {\n\t\tm.viewport.SetYOffset(line)\n\t}\n}\n\nfunc (s *search) PrevMatch(m *model) {\n\t// Check that we are within bounds.\n\tif s.query == nil {\n\t\treturn\n\t}\n\n\t// Remove previous highlight.\n\tm.content = strings.Replace(m.content, s.matchLipglossStr, s.matchString, 1)\n\n\t// Highlight the previous match.\n\tallMatches := s.query.FindAllStringIndex(m.content, -1)\n\tif len(allMatches) == 0 {\n\t\treturn\n\t}\n\n\ts.matchIndex = (s.matchIndex - 1) % len(allMatches)\n\tif s.matchIndex < 0 {\n\t\ts.matchIndex = len(allMatches) - 1\n\t}\n\n\tleftPad, rightPad := lipglossPadding(m.matchStyle)\n\tmatch := allMatches[s.matchIndex]\n\tlhs := m.content[:match[0]]\n\trhs := m.content[match[0]:]\n\ts.matchString = m.content[match[0]:match[1]]\n\ts.matchLipglossStr = m.matchHighlightStyle.Render(s.matchString[leftPad : len(s.matchString)-rightPad])\n\tm.content = lhs + strings.Replace(rhs, m.content[match[0]:match[1]], s.matchLipglossStr, 1)\n\n\t// Update the viewport position.\n\tvar line int\n\tformatStr := softWrapEm(m.content, m.maxWidth, m.softWrap)\n\tindex := strings.Index(formatStr, s.matchLipglossStr)\n\tif index != -1 {\n\t\tline = strings.Count(formatStr[:index], \"\\n\")\n\t}\n\n\t// Only update if the match is not within the viewport.\n\tif index != -1 && (line > m.viewport.YOffset-1+m.viewport.VisibleLineCount()-1 || line < m.viewport.YOffset) {\n\t\tm.viewport.SetYOffset(line)\n\t}\n}\n\nfunc softWrapEm(str string, maxWidth int, softWrap bool) string {\n\tvar text strings.Builder\n\tfor _, line := range strings.Split(str, \"\\n\") {\n\t\tidx := 0\n\t\tif w := ansi.StringWidth(line); softWrap && w > maxWidth {\n\t\t\tfor w > idx {\n\t\t\t\ttruncatedLine := ansi.Cut(line, idx, maxWidth+idx)\n\t\t\t\tidx += maxWidth\n\t\t\t\ttext.WriteString(truncatedLine)\n\t\t\t\ttext.WriteString(\"\\n\")\n\t\t\t}\n\t\t} else {\n\t\t\ttext.WriteString(line)\n\t\t\ttext.WriteString(\"\\n\")\n\t\t}\n\t}\n\n\treturn text.String()\n}\n\n// lipglossPadding calculates how much padding a string is given by a style.\nfunc lipglossPadding(style lipgloss.Style) (int, int) {\n\trender := style.Render(\" \")\n\tbefore := strings.Index(render, \" \")\n\tafter := len(render) - len(\" \") - before\n\treturn before, after\n}\n"
  },
  {
    "path": "spin/command.go",
    "content": "package spin\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/charmbracelet/bubbles/spinner\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/gum/internal/exit\"\n\t\"github.com/charmbracelet/gum/internal/timeout\"\n\t\"github.com/charmbracelet/gum/style\"\n\t\"github.com/charmbracelet/x/term\"\n)\n\n// Run provides a shell script interface for the spinner bubble.\n// https://github.com/charmbracelet/bubbles/spinner\nfunc (o Options) Run() error {\n\tisOutTTY := term.IsTerminal(os.Stdout.Fd())\n\tisErrTTY := term.IsTerminal(os.Stderr.Fd())\n\n\ts := spinner.New()\n\ts.Style = o.SpinnerStyle.ToLipgloss()\n\ts.Spinner = spinnerMap[o.Spinner]\n\ttop, right, bottom, left := style.ParsePadding(o.Padding)\n\tm := model{\n\t\tspinner:    s,\n\t\ttitle:      o.TitleStyle.ToLipgloss().Render(o.Title),\n\t\tcommand:    o.Command,\n\t\talign:      o.Align,\n\t\tshowStdout: (o.ShowOutput || o.ShowStdout) && isOutTTY,\n\t\tshowStderr: (o.ShowOutput || o.ShowStderr) && isErrTTY,\n\t\tshowError:  o.ShowError,\n\t\tisTTY:      isErrTTY,\n\t\tpadding:    []int{top, right, bottom, left},\n\t}\n\n\tctx, cancel := timeout.Context(o.Timeout)\n\tdefer cancel()\n\n\ttm, err := tea.NewProgram(\n\t\tm,\n\t\ttea.WithOutput(os.Stderr),\n\t\ttea.WithContext(ctx),\n\t\ttea.WithInput(nil),\n\t).Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to run action: %w\", err)\n\t}\n\n\tm = tm.(model)\n\t// If the command succeeds, and we are printing output and we are in a TTY then push the STDOUT we got to the actual\n\t// STDOUT for piping or other things.\n\t//nolint:nestif\n\tif m.err != nil {\n\t\tif _, err := fmt.Fprintf(os.Stderr, \"%s\\n\", m.err.Error()); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to stdout: %w\", err)\n\t\t}\n\t\treturn exit.ErrExit(1)\n\t} else if m.status == 0 {\n\t\tvar output string\n\t\tif o.ShowOutput || (o.ShowStdout && o.ShowStderr) {\n\t\t\toutput = m.output\n\t\t} else if o.ShowStdout {\n\t\t\toutput = m.stdout\n\t\t} else if o.ShowStderr {\n\t\t\toutput = m.stderr\n\t\t}\n\t\tif output != \"\" {\n\t\t\tif _, err := os.Stdout.WriteString(output); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to write to stdout: %w\", err)\n\t\t\t}\n\t\t}\n\t} else if o.ShowError {\n\t\t// Otherwise if we are showing errors and the command did not exit with a 0 status code then push all of the command\n\t\t// output to the terminal. This way failed commands can be debugged.\n\t\tif _, err := os.Stdout.WriteString(m.output); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to stdout: %w\", err)\n\t\t}\n\t}\n\n\treturn exit.ErrExit(m.status)\n}\n"
  },
  {
    "path": "spin/options.go",
    "content": "package spin\n\nimport (\n\t\"time\"\n\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Options is the customization options for the spin command.\ntype Options struct {\n\tCommand []string `arg:\"\" help:\"Command to run\"`\n\n\tShowOutput   bool          `help:\"Show or pipe output of command during execution (shows both STDOUT and STDERR)\" default:\"false\" env:\"GUM_SPIN_SHOW_OUTPUT\"`\n\tShowError    bool          `help:\"Show output of command only if the command fails\" default:\"false\" env:\"GUM_SPIN_SHOW_ERROR\"`\n\tShowStdout   bool          `help:\"Show STDOUT output\" default:\"false\" env:\"GUM_SPIN_SHOW_STDOUT\"`\n\tShowStderr   bool          `help:\"Show STDERR errput\" default:\"false\" env:\"GUM_SPIN_SHOW_STDERR\"`\n\tSpinner      string        `help:\"Spinner type\" short:\"s\" type:\"spinner\" enum:\"line,dot,minidot,jump,pulse,points,globe,moon,monkey,meter,hamburger\" default:\"dot\" env:\"GUM_SPIN_SPINNER\"`\n\tSpinnerStyle style.Styles  `embed:\"\" prefix:\"spinner.\" set:\"defaultForeground=212\" envprefix:\"GUM_SPIN_SPINNER_\"`\n\tTitle        string        `help:\"Text to display to user while spinning\" default:\"Loading...\" env:\"GUM_SPIN_TITLE\"`\n\tTitleStyle   style.Styles  `embed:\"\" prefix:\"title.\" envprefix:\"GUM_SPIN_TITLE_\"`\n\tAlign        string        `help:\"Alignment of spinner with regard to the title\" short:\"a\" type:\"align\" enum:\"left,right\" default:\"left\" env:\"GUM_SPIN_ALIGN\"`\n\tTimeout      time.Duration `help:\"Timeout until spin command aborts\" default:\"0s\" env:\"GUM_SPIN_TIMEOUT\"`\n\tPadding      string        `help:\"Padding\" default:\"${defaultPadding}\" group:\"Style Flags\" env:\"GUM_SPIN_PADDING\"`\n}\n"
  },
  {
    "path": "spin/pty.go",
    "content": "package spin\n\nimport (\n\t\"os\"\n\n\t\"github.com/charmbracelet/x/term\"\n\t\"github.com/charmbracelet/x/xpty\"\n)\n\nfunc openPty(f *os.File) (pty xpty.Pty, err error) {\n\twidth, height, err := term.GetSize(f.Fd())\n\tif err != nil {\n\t\treturn nil, err //nolint:wrapcheck\n\t}\n\n\tpty, err = xpty.NewPty(width, height)\n\tif err != nil {\n\t\treturn nil, err //nolint:wrapcheck\n\t}\n\n\treturn pty, nil\n}\n"
  },
  {
    "path": "spin/spin.go",
    "content": "// Package spin provides a shell script interface for the spinner bubble.\n// https://github.com/charmbracelet/bubbles/tree/master/spinner\n//\n// It is useful for displaying that some task is running in the background\n// while consuming it's output so that it is not shown to the user.\n//\n// For example, let's do a long running task: $ sleep 5\n//\n// We can simply prepend a spinner to this task to show it to the user, while\n// performing the task / command in the background.\n//\n// $ gum spin -t \"Taking a nap...\" -- sleep 5\n//\n// The spinner will automatically exit when the task is complete.\npackage spin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"syscall\"\n\n\t\"github.com/charmbracelet/bubbles/spinner\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/charmbracelet/x/term\"\n\t\"github.com/charmbracelet/x/xpty\"\n)\n\ntype model struct {\n\tspinner    spinner.Model\n\ttitle      string\n\tpadding    []int\n\talign      string\n\tcommand    []string\n\tquitting   bool\n\tisTTY      bool\n\tstatus     int\n\tstdout     string\n\tstderr     string\n\toutput     string\n\tshowStdout bool\n\tshowStderr bool\n\tshowError  bool\n\terr        error\n}\n\nvar (\n\tbothbuf bytes.Buffer\n\toutbuf  bytes.Buffer\n\terrbuf  bytes.Buffer\n\n\texecuting *exec.Cmd\n)\n\ntype errorMsg error\n\ntype finishCommandMsg struct {\n\tstdout string\n\tstderr string\n\toutput string\n\tstatus int\n}\n\nfunc commandStart(command []string) tea.Cmd {\n\treturn func() tea.Msg {\n\t\tvar args []string\n\t\tif len(command) > 1 {\n\t\t\targs = command[1:]\n\t\t}\n\n\t\texecuting = exec.CommandContext(context.Background(), command[0], args...) //nolint:gosec\n\t\texecuting.Stdin = os.Stdin\n\n\t\tisTerminal := term.IsTerminal(os.Stdout.Fd())\n\n\t\t// NOTE(@andreynering): We had issues with Git Bash on Windows\n\t\t// when it comes to handling PTYs, so we're falling back to\n\t\t// to redirecting stdout/stderr as usual to avoid issues.\n\t\t//nolint:nestif\n\t\tif isTerminal && runtime.GOOS == \"windows\" {\n\t\t\texecuting.Stdout = io.MultiWriter(&bothbuf, &outbuf)\n\t\t\texecuting.Stderr = io.MultiWriter(&bothbuf, &errbuf)\n\t\t\t_ = executing.Run()\n\t\t} else if isTerminal {\n\t\t\tstdoutPty, err := openPty(os.Stdout)\n\t\t\tif err != nil {\n\t\t\t\treturn errorMsg(err)\n\t\t\t}\n\t\t\tdefer stdoutPty.Close() //nolint:errcheck\n\n\t\t\tstderrPty, err := openPty(os.Stderr)\n\t\t\tif err != nil {\n\t\t\t\treturn errorMsg(err)\n\t\t\t}\n\t\t\tdefer stderrPty.Close() //nolint:errcheck\n\n\t\t\tif outUnixPty, isOutUnixPty := stdoutPty.(*xpty.UnixPty); isOutUnixPty {\n\t\t\t\texecuting.Stdout = outUnixPty.Slave()\n\t\t\t}\n\t\t\tif errUnixPty, isErrUnixPty := stderrPty.(*xpty.UnixPty); isErrUnixPty {\n\t\t\t\texecuting.Stderr = errUnixPty.Slave()\n\t\t\t}\n\n\t\t\tgo io.Copy(io.MultiWriter(&bothbuf, &outbuf), stdoutPty) //nolint:errcheck\n\t\t\tgo io.Copy(io.MultiWriter(&bothbuf, &errbuf), stderrPty) //nolint:errcheck\n\n\t\t\tif err = stdoutPty.Start(executing); err != nil {\n\t\t\t\treturn errorMsg(err)\n\t\t\t}\n\t\t\t_ = xpty.WaitProcess(context.Background(), executing)\n\t\t} else {\n\t\t\texecuting.Stdout = os.Stdout\n\t\t\texecuting.Stderr = os.Stderr\n\t\t\t_ = executing.Run()\n\t\t}\n\n\t\tstatus := executing.ProcessState.ExitCode()\n\t\tif status == -1 {\n\t\t\tstatus = 1\n\t\t}\n\n\t\treturn finishCommandMsg{\n\t\t\tstdout: outbuf.String(),\n\t\t\tstderr: errbuf.String(),\n\t\t\toutput: bothbuf.String(),\n\t\t\tstatus: status,\n\t\t}\n\t}\n}\n\nfunc commandAbort() tea.Msg {\n\tif executing != nil && executing.Process != nil {\n\t\t_ = executing.Process.Signal(syscall.SIGINT)\n\t}\n\treturn tea.InterruptMsg{}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tea.Batch(\n\t\tm.spinner.Tick,\n\t\tcommandStart(m.command),\n\t)\n}\n\nfunc (m model) View() string {\n\tif m.quitting {\n\t\treturn \"\"\n\t}\n\n\tvar out string\n\tif m.showStderr {\n\t\tout += errbuf.String()\n\t}\n\tif m.showStdout {\n\t\tout += outbuf.String()\n\t}\n\n\tif !m.isTTY {\n\t\treturn m.title\n\t}\n\n\tvar header string\n\tif m.align == \"left\" {\n\t\theader = m.spinner.View() + \" \" + m.title\n\t} else {\n\t\theader = m.title + \" \" + m.spinner.View()\n\t}\n\treturn lipgloss.NewStyle().\n\t\tPadding(m.padding...).\n\t\tRender(header, \"\", out)\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase finishCommandMsg:\n\t\tm.stdout = msg.stdout\n\t\tm.stderr = msg.stderr\n\t\tm.output = msg.output\n\t\tm.status = msg.status\n\t\tm.quitting = true\n\t\treturn m, tea.Quit\n\tcase tea.KeyMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\":\n\t\t\treturn m, commandAbort\n\t\t}\n\tcase errorMsg:\n\t\tm.err = msg\n\t\tm.quitting = true\n\t\treturn m, tea.Quit\n\t}\n\n\tvar cmd tea.Cmd\n\tm.spinner, cmd = m.spinner.Update(msg)\n\treturn m, cmd\n}\n"
  },
  {
    "path": "spin/spinners.go",
    "content": "package spin\n\nimport \"github.com/charmbracelet/bubbles/spinner\"\n\nvar spinnerMap = map[string]spinner.Spinner{\n\t\"line\":      spinner.Line,\n\t\"dot\":       spinner.Dot,\n\t\"minidot\":   spinner.MiniDot,\n\t\"jump\":      spinner.Jump,\n\t\"pulse\":     spinner.Pulse,\n\t\"points\":    spinner.Points,\n\t\"globe\":     spinner.Globe,\n\t\"moon\":      spinner.Moon,\n\t\"monkey\":    spinner.Monkey,\n\t\"meter\":     spinner.Meter,\n\t\"hamburger\": spinner.Hamburger,\n}\n"
  },
  {
    "path": "style/ascii_a.txt",
    "content": "   #\n  # #\n #   #\n#     #\n#######\n#     #\n#     #\n"
  },
  {
    "path": "style/borders.go",
    "content": "package style\n\nimport \"github.com/charmbracelet/lipgloss\"\n\n// Border maps strings to `lipgloss.Border`s.\nvar Border map[string]lipgloss.Border = map[string]lipgloss.Border{\n\t\"double\":  lipgloss.DoubleBorder(),\n\t\"hidden\":  lipgloss.HiddenBorder(),\n\t\"none\":    {},\n\t\"normal\":  lipgloss.NormalBorder(),\n\t\"rounded\": lipgloss.RoundedBorder(),\n\t\"thick\":   lipgloss.ThickBorder(),\n}\n"
  },
  {
    "path": "style/command.go",
    "content": "// Package style provides a shell script interface for Lip Gloss.\n// https://github.com/charmbracelet/lipgloss\n//\n// It allows you to use Lip Gloss to style text without needing to use Go. All\n// of the styling options are available as flags.\npackage style\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/gum/internal/stdin\"\n)\n\n// Run provides a shell script interface for the Lip Gloss styling.\n// https://github.com/charmbracelet/lipgloss\nfunc (o Options) Run() error {\n\tvar text string\n\tif len(o.Text) > 0 {\n\t\ttext = strings.Join(o.Text, \"\\n\")\n\t} else {\n\t\ttext, _ = stdin.Read(stdin.StripANSI(o.StripANSI))\n\t\tif text == \"\" {\n\t\t\treturn errors.New(\"no input provided, see `gum style --help`\")\n\t\t}\n\t}\n\tif o.Trim {\n\t\tvar lines []string\n\t\tfor _, line := range strings.Split(text, \"\\n\") {\n\t\t\tlines = append(lines, strings.TrimSpace(line))\n\t\t}\n\t\ttext = strings.Join(lines, \"\\n\")\n\t}\n\tfmt.Println(o.Style.ToLipgloss().Render(text))\n\treturn nil\n}\n"
  },
  {
    "path": "style/lipgloss.go",
    "content": "package style\n\nimport (\n\t\"github.com/charmbracelet/lipgloss\"\n\n\t\"github.com/charmbracelet/gum/internal/decode\"\n)\n\n// ToLipgloss takes a Styles flag set and returns the corresponding\n// lipgloss.Style.\nfunc (s Styles) ToLipgloss() lipgloss.Style {\n\treturn lipgloss.NewStyle().\n\t\tBackground(lipgloss.Color(s.Background)).\n\t\tForeground(lipgloss.Color(s.Foreground)).\n\t\tBorderBackground(lipgloss.Color(s.BorderBackground)).\n\t\tBorderForeground(lipgloss.Color(s.BorderForeground)).\n\t\tAlign(decode.Align[s.Align]).\n\t\tBorder(Border[s.Border]).\n\t\tHeight(s.Height).\n\t\tWidth(s.Width).\n\t\tMargin(parseMargin(s.Margin)).\n\t\tPadding(ParsePadding(s.Padding)).\n\t\tBold(s.Bold).\n\t\tFaint(s.Faint).\n\t\tItalic(s.Italic).\n\t\tStrikethrough(s.Strikethrough).\n\t\tUnderline(s.Underline)\n}\n\n// ToLipgloss takes a Styles flag set and returns the corresponding\n// lipgloss.Style.\nfunc (s StylesNotHidden) ToLipgloss() lipgloss.Style {\n\treturn lipgloss.NewStyle().\n\t\tBackground(lipgloss.Color(s.Background)).\n\t\tForeground(lipgloss.Color(s.Foreground)).\n\t\tBorderBackground(lipgloss.Color(s.BorderBackground)).\n\t\tBorderForeground(lipgloss.Color(s.BorderForeground)).\n\t\tAlign(decode.Align[s.Align]).\n\t\tBorder(Border[s.Border]).\n\t\tHeight(s.Height).\n\t\tWidth(s.Width).\n\t\tMargin(parseMargin(s.Margin)).\n\t\tPadding(ParsePadding(s.Padding)).\n\t\tBold(s.Bold).\n\t\tFaint(s.Faint).\n\t\tItalic(s.Italic).\n\t\tStrikethrough(s.Strikethrough).\n\t\tUnderline(s.Underline)\n}\n"
  },
  {
    "path": "style/options.go",
    "content": "package style\n\n// Options is the customization options for the style command.\ntype Options struct {\n\tText      []string        `arg:\"\" optional:\"\" help:\"Text to which to apply the style\"`\n\tTrim      bool            `help:\"Trim whitespaces on every input line\" default:\"false\"`\n\tStripANSI bool            `help:\"Strip ANSI sequences when reading from STDIN\" default:\"true\" negatable:\"\" env:\"GUM_STYLE_STRIP_ANSI\"`\n\tStyle     StylesNotHidden `embed:\"\"`\n}\n\n// Styles is a flag set of possible styles.\n//\n// It corresponds to the available options in the lipgloss.Style struct.\n//\n// This flag set is used in other parts of the application to embed styles for\n// components, through embedding and prefixing.\ntype Styles struct {\n\t// Colors\n\tForeground string `help:\"Foreground Color\" default:\"${defaultForeground}\" group:\"Style Flags\" env:\"FOREGROUND\"`\n\tBackground string `help:\"Background Color\" default:\"${defaultBackground}\" group:\"Style Flags\" env:\"BACKGROUND\"`\n\n\t// Border\n\tBorder           string `help:\"Border Style\" enum:\"none,hidden,normal,rounded,thick,double\" default:\"${defaultBorder}\" group:\"Style Flags\" env:\"BORDER\" hidden:\"true\"`\n\tBorderBackground string `help:\"Border Background Color\" group:\"Style Flags\" default:\"${defaultBorderBackground}\" env:\"BORDER_BACKGROUND\" hidden:\"true\"`\n\tBorderForeground string `help:\"Border Foreground Color\" group:\"Style Flags\" default:\"${defaultBorderForeground}\" env:\"BORDER_FOREGROUND\" hidden:\"true\"`\n\n\t// Layout\n\tAlign   string `help:\"Text Alignment\" enum:\"left,center,right,bottom,middle,top\" default:\"${defaultAlign}\" group:\"Style Flags\" env:\"ALIGN\" hidden:\"true\"`\n\tHeight  int    `help:\"Text height\" default:\"${defaultHeight}\" group:\"Style Flags\" env:\"HEIGHT\" hidden:\"true\"`\n\tWidth   int    `help:\"Text width\" default:\"${defaultWidth}\" group:\"Style Flags\" env:\"WIDTH\" hidden:\"true\"`\n\tMargin  string `help:\"Text margin\" default:\"${defaultMargin}\" group:\"Style Flags\" env:\"MARGIN\" hidden:\"true\"`\n\tPadding string `help:\"Text padding\" default:\"${defaultPadding}\" group:\"Style Flags\" env:\"PADDING\" hidden:\"true\"`\n\n\t// Format\n\tBold          bool `help:\"Bold text\" default:\"${defaultBold}\" group:\"Style Flags\" env:\"BOLD\" hidden:\"true\"`\n\tFaint         bool `help:\"Faint text\" default:\"${defaultFaint}\" group:\"Style Flags\" env:\"FAINT\" hidden:\"true\"`\n\tItalic        bool `help:\"Italicize text\" default:\"${defaultItalic}\" group:\"Style Flags\" env:\"ITALIC\" hidden:\"true\"`\n\tStrikethrough bool `help:\"Strikethrough text\" default:\"${defaultStrikethrough}\" group:\"Style Flags\" env:\"STRIKETHROUGH\" hidden:\"true\"`\n\tUnderline     bool `help:\"Underline text\" default:\"${defaultUnderline}\" group:\"Style Flags\" env:\"UNDERLINE\" hidden:\"true\"`\n}\n\n// StylesNotHidden allows the style struct to display full help when not-embedded.\n//\n// NB: We must duplicate this struct to ensure that `gum style` does not hide\n// flags when an error pops up. Ideally, we can dynamically hide or show flags\n// based on the command run: https://github.com/alecthomas/kong/issues/316\ntype StylesNotHidden struct {\n\t// Colors\n\tForeground string `help:\"Foreground Color\" default:\"${defaultForeground}\" group:\"Style Flags\" env:\"FOREGROUND\"`\n\tBackground string `help:\"Background Color\" default:\"${defaultBackground}\" group:\"Style Flags\" env:\"BACKGROUND\"`\n\n\t// Border\n\tBorder           string `help:\"Border Style\" enum:\"none,hidden,normal,rounded,thick,double\" default:\"${defaultBorder}\" group:\"Style Flags\" env:\"BORDER\"`\n\tBorderBackground string `help:\"Border Background Color\" group:\"Style Flags\" default:\"${defaultBorderBackground}\" env:\"BORDER_BACKGROUND\"`\n\tBorderForeground string `help:\"Border Foreground Color\" group:\"Style Flags\" default:\"${defaultBorderForeground}\" env:\"BORDER_FOREGROUND\"`\n\n\t// Layout\n\tAlign   string `help:\"Text Alignment\" enum:\"left,center,right,bottom,middle,top\" default:\"${defaultAlign}\" group:\"Style Flags\" env:\"ALIGN\"`\n\tHeight  int    `help:\"Text height\" default:\"${defaultHeight}\" group:\"Style Flags\" env:\"HEIGHT\"`\n\tWidth   int    `help:\"Text width\" default:\"${defaultWidth}\" group:\"Style Flags\" env:\"WIDTH\"`\n\tMargin  string `help:\"Text margin\" default:\"${defaultMargin}\" group:\"Style Flags\" env:\"MARGIN\"`\n\tPadding string `help:\"Text padding\" default:\"${defaultPadding}\" group:\"Style Flags\" env:\"PADDING\"`\n\n\t// Format\n\tBold          bool `help:\"Bold text\" default:\"${defaultBold}\" group:\"Style Flags\" env:\"BOLD\"`\n\tFaint         bool `help:\"Faint text\" default:\"${defaultFaint}\" group:\"Style Flags\" env:\"FAINT\"`\n\tItalic        bool `help:\"Italicize text\" default:\"${defaultItalic}\" group:\"Style Flags\" env:\"ITALIC\"`\n\tStrikethrough bool `help:\"Strikethrough text\" default:\"${defaultStrikethrough}\" group:\"Style Flags\" env:\"STRIKETHROUGH\"`\n\tUnderline     bool `help:\"Underline text\" default:\"${defaultUnderline}\" group:\"Style Flags\" env:\"UNDERLINE\"`\n}\n"
  },
  {
    "path": "style/spacing.go",
    "content": "package style\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst (\n\tminTokens  = 1\n\thalfTokens = 2\n\tmaxTokens  = 4\n)\n\n// ParsePadding parses 1 - 4 integers from a string and returns them in a top,\n// right, bottom, left order for use in the lipgloss.Padding() method.\nfunc ParsePadding(s string) (int, int, int, int) {\n\tvar ints [maxTokens]int\n\n\ttokens := strings.Split(s, \" \")\n\n\tif len(tokens) > maxTokens {\n\t\treturn 0, 0, 0, 0\n\t}\n\n\t// All tokens must be an integer\n\tfor i, token := range tokens {\n\t\tparsed, err := strconv.Atoi(token)\n\t\tif err != nil {\n\t\t\treturn 0, 0, 0, 0\n\t\t}\n\t\tints[i] = parsed\n\t}\n\n\tif len(tokens) == minTokens {\n\t\treturn ints[0], ints[0], ints[0], ints[0]\n\t}\n\n\tif len(tokens) == halfTokens {\n\t\treturn ints[0], ints[1], ints[0], ints[1]\n\t}\n\n\tif len(tokens) == maxTokens {\n\t\treturn ints[0], ints[1], ints[2], ints[3]\n\t}\n\n\treturn 0, 0, 0, 0\n}\n\n// parseMargin is an alias for parsePadding since they involve the same logic\n// to parse integers to the same format.\nvar parseMargin = ParsePadding\n"
  },
  {
    "path": "table/bom.csv",
    "content": "﻿\"first_name\",\"last_name\",\"username\"\n\"Rob\",\"Pike\",rob\nKen,Thompson,ken\n\"Robert\",\"Griesemer\",\"gri\"\n"
  },
  {
    "path": "table/comma.csv",
    "content": "Bubble Gum,Price,Ingredients\r\nStrawberry,$0.88,\"Water,Sugar\"\r\nGuava,$1.00,\"Guava Flavoring,Food Coloring,Xanthan Gum\"\r\nOrange,$0.99,\"Sugar,Dextrose,Glucose\"\r\nCinnamon,$0.50,\"Cin\"\"na\"\"mon\""
  },
  {
    "path": "table/command.go",
    "content": "package table\n\nimport (\n\t\"encoding/csv\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/table\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/gum/internal/stdin\"\n\t\"github.com/charmbracelet/gum/internal/timeout\"\n\t\"github.com/charmbracelet/gum/style\"\n\t\"github.com/charmbracelet/lipgloss\"\n\tltable \"github.com/charmbracelet/lipgloss/table\"\n\t\"golang.org/x/text/encoding\"\n\t\"golang.org/x/text/encoding/unicode\"\n\t\"golang.org/x/text/transform\"\n)\n\n// Run provides a shell script interface for rendering tabular data (CSV).\nfunc (o Options) Run() error {\n\tvar input *os.File\n\tif o.File != \"\" {\n\t\tvar err error\n\t\tinput, err = os.Open(o.File)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not render file: %w\", err)\n\t\t}\n\t} else {\n\t\tif stdin.IsEmpty() {\n\t\t\treturn fmt.Errorf(\"no data provided\")\n\t\t}\n\t\tinput = os.Stdin\n\t}\n\tdefer input.Close() //nolint: errcheck\n\n\ttransformer := unicode.BOMOverride(encoding.Nop.NewDecoder())\n\treader := csv.NewReader(transform.NewReader(input, transformer))\n\treader.LazyQuotes = o.LazyQuotes\n\treader.FieldsPerRecord = o.FieldsPerRecord\n\tseparatorRunes := []rune(o.Separator)\n\tif len(separatorRunes) != 1 {\n\t\treturn fmt.Errorf(\"separator must be single character\")\n\t}\n\treader.Comma = separatorRunes[0]\n\n\twriter := csv.NewWriter(os.Stdout)\n\twriter.Comma = separatorRunes[0]\n\n\tvar columnNames []string\n\tvar err error\n\t// If no columns are provided we'll use the first row of the CSV as the\n\t// column names.\n\tif len(o.Columns) <= 0 {\n\t\tcolumnNames, err = reader.Read()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to parse columns\")\n\t\t}\n\t} else {\n\t\tcolumnNames = o.Columns\n\t}\n\n\tdata, err := reader.ReadAll()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid data provided\")\n\t}\n\tcolumns := make([]table.Column, 0, len(columnNames))\n\n\tfor i, title := range columnNames {\n\t\twidth := lipgloss.Width(title)\n\t\tif len(o.Widths) > i {\n\t\t\twidth = o.Widths[i]\n\t\t}\n\t\tcolumns = append(columns, table.Column{\n\t\t\tTitle: title,\n\t\t\tWidth: width,\n\t\t})\n\t}\n\n\tdefaultStyles := table.DefaultStyles()\n\ttop, right, bottom, left := style.ParsePadding(o.Padding)\n\n\tstyles := table.Styles{\n\t\tCell:     defaultStyles.Cell.Inherit(o.CellStyle.ToLipgloss()),\n\t\tHeader:   defaultStyles.Header.Inherit(o.HeaderStyle.ToLipgloss()),\n\t\tSelected: o.SelectedStyle.ToLipgloss(),\n\t}\n\n\trows := make([]table.Row, 0, len(data))\n\tfor row := range data {\n\t\tif len(data[row]) > len(columns) {\n\t\t\treturn fmt.Errorf(\"invalid number of columns\")\n\t\t}\n\n\t\t// fixes the data in case we have more columns than rows:\n\t\tfor len(data[row]) < len(columns) {\n\t\t\tdata[row] = append(data[row], \"\")\n\t\t}\n\n\t\tfor i, col := range data[row] {\n\t\t\tif len(o.Widths) == 0 {\n\t\t\t\twidth := lipgloss.Width(col)\n\t\t\t\tif width > columns[i].Width {\n\t\t\t\t\tcolumns[i].Width = width\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\trows = append(rows, table.Row(data[row]))\n\t}\n\n\tif o.Print {\n\t\ttable := ltable.New().\n\t\t\tHeaders(columnNames...).\n\t\t\tRows(data...).\n\t\t\tBorderStyle(o.BorderStyle.ToLipgloss()).\n\t\t\tBorder(style.Border[o.Border]).\n\t\t\tStyleFunc(func(row, _ int) lipgloss.Style {\n\t\t\t\tif row == 0 {\n\t\t\t\t\treturn styles.Header\n\t\t\t\t}\n\t\t\t\treturn styles.Cell\n\t\t\t})\n\n\t\tfmt.Println(table.Render())\n\t\treturn nil\n\t}\n\n\topts := []table.Option{\n\t\ttable.WithColumns(columns),\n\t\ttable.WithFocused(true),\n\t\ttable.WithRows(rows),\n\t\ttable.WithStyles(styles),\n\t}\n\tif o.Height > 0 {\n\t\topts = append(opts, table.WithHeight(o.Height-top-bottom))\n\t}\n\n\ttable := table.New(opts...)\n\n\tctx, cancel := timeout.Context(o.Timeout)\n\tdefer cancel()\n\n\tm := model{\n\t\ttable:     table,\n\t\tshowHelp:  o.ShowHelp,\n\t\thideCount: o.HideCount,\n\t\thelp:      help.New(),\n\t\tkeymap:    defaultKeymap(),\n\t\tpadding:   []int{top, right, bottom, left},\n\t}\n\ttm, err := tea.NewProgram(\n\t\tm,\n\t\ttea.WithOutput(os.Stderr),\n\t\ttea.WithContext(ctx),\n\t).Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to start tea program: %w\", err)\n\t}\n\n\tif tm == nil {\n\t\treturn fmt.Errorf(\"failed to get selection\")\n\t}\n\n\tm = tm.(model)\n\tif o.ReturnColumn > 0 && o.ReturnColumn <= len(m.selected) {\n\t\tif err = writer.Write([]string{m.selected[o.ReturnColumn-1]}); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write col %d of selected row: %w\", o.ReturnColumn, err)\n\t\t}\n\t} else {\n\t\tif err = writer.Write([]string(m.selected)); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write selected row: %w\", err)\n\t\t}\n\t}\n\n\twriter.Flush()\n\n\treturn nil\n}\n"
  },
  {
    "path": "table/example.csv",
    "content": "Bubble Gum Flavor,Price\nStrawberry,$0.99\nCherry,$0.50\nBanana,$0.75\nOrange,$0.25\nLemon,$0.50\nLime,$0.50\nGrape,$0.50\nWatermelon,$0.50\nPineapple,$0.50\nBlueberry,$0.50\nRaspberry,$0.50\nCranberry,$0.50\nPeach,$0.50\nApple,$0.50\nMango,$0.50\nPomegranate,$0.50\nCoconut,$0.50\nCinnamon,$0.50\n"
  },
  {
    "path": "table/invalid.csv",
    "content": "Bubble Gum Flavor\nStrawberry,$0.99\nCherry,$0.50\nBanana,$0.75\nOrange\nLemon,$0.50\nLime,$0.50\nGrape,$0.50\nWatermelon,$0.50\nPineapple,$0.50\nBlueberry,$0.50\nRaspberry,$0.50\nCranberry,$0.50\nPeach,$0.50\nApple,$0.50\nMango,$0.50\nPomegranate,$0.50\nCoconut,$0.50\nCinnamon,$0.50\n"
  },
  {
    "path": "table/options.go",
    "content": "package table\n\nimport (\n\t\"time\"\n\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Options is the customization options for the table command.\ntype Options struct {\n\tSeparator       string   `short:\"s\" help:\"Row separator\" default:\",\"`\n\tColumns         []string `short:\"c\" help:\"Column names\"`\n\tWidths          []int    `short:\"w\" help:\"Column widths\"`\n\tHeight          int      `help:\"Table height\" default:\"0\"`\n\tPrint           bool     `short:\"p\" help:\"static print\" default:\"false\"`\n\tFile            string   `short:\"f\" help:\"file path\" default:\"\"`\n\tBorder          string   `short:\"b\" help:\"border style\" default:\"rounded\" enum:\"rounded,thick,normal,hidden,double,none\"`\n\tShowHelp        bool     `help:\"Show help keybinds\" default:\"true\" negatable:\"\" env:\"GUM_TABLE_SHOW_HELP\"`\n\tHideCount       bool     `help:\"Hide item count on help keybinds\" default:\"false\" negatable:\"\" env:\"GUM_TABLE_HIDE_COUNT\"`\n\tLazyQuotes      bool     `help:\"If LazyQuotes is true, a quote may appear in an unquoted field and a non-doubled quote may appear in a quoted field\" default:\"false\" env:\"GUM_TABLE_LAZY_QUOTES\"`\n\tFieldsPerRecord int      `help:\"Sets the number of expected fields per record\" default:\"0\" env:\"GUM_TABLE_FIELDS_PER_RECORD\"`\n\n\tBorderStyle   style.Styles  `embed:\"\" prefix:\"border.\" envprefix:\"GUM_TABLE_BORDER_\"`\n\tCellStyle     style.Styles  `embed:\"\" prefix:\"cell.\" envprefix:\"GUM_TABLE_CELL_\"`\n\tHeaderStyle   style.Styles  `embed:\"\" prefix:\"header.\" envprefix:\"GUM_TABLE_HEADER_\"`\n\tSelectedStyle style.Styles  `embed:\"\" prefix:\"selected.\" set:\"defaultForeground=212\" envprefix:\"GUM_TABLE_SELECTED_\"`\n\tReturnColumn  int           `short:\"r\" help:\"Which column number should be returned instead of whole row as string. Default=0 returns whole Row\" default:\"0\"`\n\tTimeout       time.Duration `help:\"Timeout until choose returns selected element\" default:\"0s\" env:\"GUM_TABLE_TIMEOUT\"`\n\tPadding       string        `help:\"Padding\" default:\"${defaultPadding}\" group:\"Style Flags\" env:\"GUM_TABLE_PADDING\"`\n}\n"
  },
  {
    "path": "table/table.go",
    "content": "// Package table provides a shell script interface for the table bubble.\n// https://github.com/charmbracelet/bubbles/tree/master/table\n//\n// It is useful to render tabular (CSV) data in a terminal and allows\n// the user to select a row from the table.\n//\n// Let's render a table of gum flavors:\n//\n// $ gum table <<< \"Flavor,Price\\nStrawberry,$0.50\\nBanana,$0.99\\nCherry,$0.75\"\n//\n//\tFlavor      Price\n//\tStrawberry  $0.50\n//\tBanana      $0.99\n//\tCherry      $0.75\npackage table\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/charmbracelet/bubbles/table\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\ntype keymap struct {\n\tNavigate,\n\tSelect,\n\tQuit,\n\tAbort key.Binding\n}\n\n// FullHelp implements help.KeyMap.\nfunc (k keymap) FullHelp() [][]key.Binding { return nil }\n\n// ShortHelp implements help.KeyMap.\nfunc (k keymap) ShortHelp() []key.Binding {\n\treturn []key.Binding{\n\t\tk.Navigate,\n\t\tk.Select,\n\t\tk.Quit,\n\t}\n}\n\nfunc defaultKeymap() keymap {\n\treturn keymap{\n\t\tNavigate: key.NewBinding(\n\t\t\tkey.WithKeys(\"up\", \"down\"),\n\t\t\tkey.WithHelp(\"↓↑\", \"navigate\"),\n\t\t),\n\t\tSelect: key.NewBinding(\n\t\t\tkey.WithKeys(\"enter\"),\n\t\t\tkey.WithHelp(\"enter\", \"select\"),\n\t\t),\n\t\tQuit: key.NewBinding(\n\t\t\tkey.WithKeys(\"esc\", \"ctrl+q\", \"q\"),\n\t\t\tkey.WithHelp(\"esc\", \"quit\"),\n\t\t),\n\t\tAbort: key.NewBinding(\n\t\t\tkey.WithKeys(\"ctrl+c\"),\n\t\t\tkey.WithHelp(\"ctrl+c\", \"abort\"),\n\t\t),\n\t}\n}\n\ntype model struct {\n\ttable     table.Model\n\tselected  table.Row\n\tquitting  bool\n\tshowHelp  bool\n\thideCount bool\n\thelp      help.Model\n\tkeymap    keymap\n\tpadding   []int\n}\n\nfunc (m model) Init() tea.Cmd { return nil }\n\nfunc (m model) countView() string {\n\tif m.hideCount {\n\t\treturn \"\"\n\t}\n\n\tpadding := strconv.Itoa(numLen(len(m.table.Rows())))\n\treturn m.help.Styles.FullDesc.Render(fmt.Sprintf(\n\t\t\"%\"+padding+\"d/%d%s\",\n\t\tm.table.Cursor()+1,\n\t\tlen(m.table.Rows()),\n\t\tm.help.ShortSeparator,\n\t))\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd tea.Cmd\n\n\tswitch msg := msg.(type) {\n\tcase tea.KeyMsg:\n\t\tkm := m.keymap\n\t\tswitch {\n\t\tcase key.Matches(msg, km.Select):\n\t\t\tm.selected = m.table.SelectedRow()\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, km.Quit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, km.Abort):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Interrupt\n\t\t}\n\t}\n\n\tm.table, cmd = m.table.Update(msg)\n\treturn m, cmd\n}\n\nfunc (m model) View() string {\n\tif m.quitting {\n\t\treturn \"\"\n\t}\n\ts := m.table.View()\n\tif m.showHelp {\n\t\ts += \"\\n\" + m.countView() + m.help.View(m.keymap)\n\t}\n\treturn lipgloss.NewStyle().\n\t\tPadding(m.padding...).\n\t\tRender(s)\n}\n\nfunc numLen(i int) int {\n\tif i == 0 {\n\t\treturn 1\n\t}\n\tcount := 0\n\tfor i != 0 {\n\t\ti /= 10\n\t\tcount++\n\t}\n\treturn count\n}\n"
  },
  {
    "path": "version/command.go",
    "content": "// Package version the version command.\npackage version\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\t\"github.com/alecthomas/kong\"\n)\n\n// Run check that a given version matches a semantic version constraint.\nfunc (o Options) Run(ctx *kong.Context) error {\n\tc, err := semver.NewConstraint(o.Constraint)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not parse range %s: %w\", o.Constraint, err)\n\t}\n\tcurrent := ctx.Model.Vars()[\"versionNumber\"]\n\tv, err := semver.NewVersion(current)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not parse version %s: %w\", current, err)\n\t}\n\tif !c.Check(v) {\n\t\treturn fmt.Errorf(\"gum version %q is not within given range %q\", current, o.Constraint)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "version/options.go",
    "content": "package version\n\n// Options is the set of options that can be used with version.\ntype Options struct {\n\tConstraint string `arg:\"\" help:\"Semantic version constraint\"`\n}\n"
  },
  {
    "path": "write/command.go",
    "content": "package write\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/textarea\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/gum/cursor\"\n\t\"github.com/charmbracelet/gum/internal/stdin\"\n\t\"github.com/charmbracelet/gum/internal/timeout\"\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Run provides a shell script interface for the text area bubble.\n// https://github.com/charmbracelet/bubbles/textarea\nfunc (o Options) Run() error {\n\tin, _ := stdin.Read(stdin.StripANSI(o.StripANSI))\n\tif in != \"\" && o.Value == \"\" {\n\t\to.Value = strings.ReplaceAll(in, \"\\r\", \"\")\n\t}\n\n\ta := textarea.New()\n\ta.Focus()\n\n\ta.Prompt = o.Prompt\n\ta.Placeholder = o.Placeholder\n\ta.ShowLineNumbers = o.ShowLineNumbers\n\ta.CharLimit = o.CharLimit\n\ta.MaxHeight = o.MaxLines\n\ttop, right, bottom, left := style.ParsePadding(o.Padding)\n\n\tstyle := textarea.Style{\n\t\tBase:             o.BaseStyle.ToLipgloss(),\n\t\tPlaceholder:      o.PlaceholderStyle.ToLipgloss(),\n\t\tCursorLine:       o.CursorLineStyle.ToLipgloss(),\n\t\tCursorLineNumber: o.CursorLineNumberStyle.ToLipgloss(),\n\t\tEndOfBuffer:      o.EndOfBufferStyle.ToLipgloss(),\n\t\tLineNumber:       o.LineNumberStyle.ToLipgloss(),\n\t\tPrompt:           o.PromptStyle.ToLipgloss(),\n\t}\n\n\ta.BlurredStyle = style\n\ta.FocusedStyle = style\n\ta.Cursor.Style = o.CursorStyle.ToLipgloss()\n\ta.Cursor.SetMode(cursor.Modes[o.CursorMode])\n\n\ta.SetWidth(max(0, o.Width-left-right))\n\ta.SetHeight(max(0, o.Height-top-bottom))\n\ta.SetValue(o.Value)\n\n\tm := model{\n\t\ttextarea:    a,\n\t\theader:      o.Header,\n\t\theaderStyle: o.HeaderStyle.ToLipgloss(),\n\t\tautoWidth:   o.Width < 1,\n\t\thelp:        help.New(),\n\t\tshowHelp:    o.ShowHelp,\n\t\tkeymap:      defaultKeymap(),\n\t\tpadding:     []int{top, right, bottom, left},\n\t}\n\n\tm.textarea.KeyMap.InsertNewline = m.keymap.InsertNewline\n\n\tctx, cancel := timeout.Context(o.Timeout)\n\tdefer cancel()\n\n\tp := tea.NewProgram(\n\t\tm,\n\t\ttea.WithOutput(os.Stderr),\n\t\ttea.WithReportFocus(),\n\t\ttea.WithContext(ctx),\n\t)\n\ttm, err := p.Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run write: %w\", err)\n\t}\n\tm = tm.(model)\n\tif !m.submitted {\n\t\treturn errors.New(\"not submitted\")\n\t}\n\tfmt.Println(m.textarea.Value())\n\treturn nil\n}\n"
  },
  {
    "path": "write/options.go",
    "content": "package write\n\nimport (\n\t\"time\"\n\n\t\"github.com/charmbracelet/gum/style\"\n)\n\n// Options are the customization options for the textarea.\ntype Options struct {\n\tWidth           int           `help:\"Text area width (0 for terminal width)\" default:\"0\" env:\"GUM_WRITE_WIDTH\"`\n\tHeight          int           `help:\"Text area height\" default:\"5\" env:\"GUM_WRITE_HEIGHT\"`\n\tHeader          string        `help:\"Header value\" default:\"\" env:\"GUM_WRITE_HEADER\"`\n\tPlaceholder     string        `help:\"Placeholder value\" default:\"Write something...\" env:\"GUM_WRITE_PLACEHOLDER\"`\n\tPrompt          string        `help:\"Prompt to display\" default:\"┃ \" env:\"GUM_WRITE_PROMPT\"`\n\tShowCursorLine  bool          `help:\"Show cursor line\" default:\"false\" env:\"GUM_WRITE_SHOW_CURSOR_LINE\"`\n\tShowLineNumbers bool          `help:\"Show line numbers\" default:\"false\" env:\"GUM_WRITE_SHOW_LINE_NUMBERS\"`\n\tValue           string        `help:\"Initial value (can be passed via stdin)\" default:\"\" env:\"GUM_WRITE_VALUE\"`\n\tCharLimit       int           `help:\"Maximum value length (0 for no limit)\" default:\"0\"`\n\tMaxLines        int           `help:\"Maximum number of lines (0 for no limit)\" default:\"0\"`\n\tShowHelp        bool          `help:\"Show help key binds\" negatable:\"\" default:\"true\" env:\"GUM_WRITE_SHOW_HELP\"`\n\tCursorMode      string        `prefix:\"cursor.\" name:\"mode\" help:\"Cursor mode\" default:\"blink\" enum:\"blink,hide,static\" env:\"GUM_WRITE_CURSOR_MODE\"`\n\tTimeout         time.Duration `help:\"Timeout until choose returns selected element\" default:\"0s\" env:\"GUM_WRITE_TIMEOUT\"`\n\tStripANSI       bool          `help:\"Strip ANSI sequences when reading from STDIN\" default:\"true\" negatable:\"\" env:\"GUM_WRITE_STRIP_ANSI\"`\n\n\tBaseStyle             style.Styles `embed:\"\" prefix:\"base.\" envprefix:\"GUM_WRITE_BASE_\"`\n\tCursorLineNumberStyle style.Styles `embed:\"\" prefix:\"cursor-line-number.\" set:\"defaultForeground=7\" envprefix:\"GUM_WRITE_CURSOR_LINE_NUMBER_\"`\n\tCursorLineStyle       style.Styles `embed:\"\" prefix:\"cursor-line.\" envprefix:\"GUM_WRITE_CURSOR_LINE_\"`\n\tCursorStyle           style.Styles `embed:\"\" prefix:\"cursor.\" set:\"defaultForeground=212\" envprefix:\"GUM_WRITE_CURSOR_\"`\n\tEndOfBufferStyle      style.Styles `embed:\"\" prefix:\"end-of-buffer.\" set:\"defaultForeground=0\" envprefix:\"GUM_WRITE_END_OF_BUFFER_\"`\n\tLineNumberStyle       style.Styles `embed:\"\" prefix:\"line-number.\" set:\"defaultForeground=7\" envprefix:\"GUM_WRITE_LINE_NUMBER_\"`\n\tHeaderStyle           style.Styles `embed:\"\" prefix:\"header.\" set:\"defaultForeground=240\" envprefix:\"GUM_WRITE_HEADER_\"`\n\tPlaceholderStyle      style.Styles `embed:\"\" prefix:\"placeholder.\" set:\"defaultForeground=240\" envprefix:\"GUM_WRITE_PLACEHOLDER_\"`\n\tPromptStyle           style.Styles `embed:\"\" prefix:\"prompt.\" set:\"defaultForeground=7\" envprefix:\"GUM_WRITE_PROMPT_\"`\n\tPadding               string       `help:\"Padding\" default:\"${defaultPadding}\" group:\"Style Flags\" env:\"GUM_WRITE_PADDING\"`\n}\n"
  },
  {
    "path": "write/write.go",
    "content": "// Package write provides a shell script interface for the text area bubble.\n// https://github.com/charmbracelet/bubbles/tree/master/textarea\n//\n// It can be used to ask the user to write some long form of text (multi-line)\n// input. The text the user entered will be sent to stdout.\n// Text entry is completed with CTRL+D and aborted with CTRL+C or Escape.\n//\n// $ gum write > output.text\npackage write\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/charmbracelet/bubbles/textarea\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/charmbracelet/x/editor\"\n)\n\ntype keymap struct {\n\ttextarea.KeyMap\n\tSubmit       key.Binding\n\tQuit         key.Binding\n\tAbort        key.Binding\n\tOpenInEditor key.Binding\n}\n\n// FullHelp implements help.KeyMap.\nfunc (k keymap) FullHelp() [][]key.Binding { return nil }\n\n// ShortHelp implements help.KeyMap.\nfunc (k keymap) ShortHelp() []key.Binding {\n\treturn []key.Binding{\n\t\tk.InsertNewline,\n\t\tk.OpenInEditor,\n\t\tk.Submit,\n\t}\n}\n\nfunc defaultKeymap() keymap {\n\tkm := textarea.DefaultKeyMap\n\tkm.InsertNewline = key.NewBinding(\n\t\tkey.WithKeys(\"ctrl+j\"),\n\t\tkey.WithHelp(\"ctrl+j\", \"insert newline\"),\n\t)\n\treturn keymap{\n\t\tKeyMap: km,\n\t\tQuit: key.NewBinding(\n\t\t\tkey.WithKeys(\"esc\"),\n\t\t\tkey.WithHelp(\"esc\", \"quit\"),\n\t\t),\n\t\tAbort: key.NewBinding(\n\t\t\tkey.WithKeys(\"ctrl+c\"),\n\t\t\tkey.WithHelp(\"ctrl+c\", \"cancel\"),\n\t\t),\n\t\tOpenInEditor: key.NewBinding(\n\t\t\tkey.WithKeys(\"ctrl+e\"),\n\t\t\tkey.WithHelp(\"ctrl+e\", \"open editor\"),\n\t\t),\n\t\tSubmit: key.NewBinding(\n\t\t\tkey.WithKeys(\"enter\"),\n\t\t\tkey.WithHelp(\"enter\", \"submit\"),\n\t\t),\n\t}\n}\n\ntype model struct {\n\tautoWidth   bool\n\theader      string\n\theaderStyle lipgloss.Style\n\tquitting    bool\n\tsubmitted   bool\n\ttextarea    textarea.Model\n\tshowHelp    bool\n\thelp        help.Model\n\tkeymap      keymap\n\tpadding     []int\n}\n\nfunc (m model) Init() tea.Cmd { return textarea.Blink }\n\nfunc (m model) View() string {\n\tif m.quitting {\n\t\treturn \"\"\n\t}\n\n\tvar parts []string\n\n\t// Display the header above the text area if it is not empty.\n\tif m.header != \"\" {\n\t\tparts = append(parts, m.headerStyle.Render(m.header))\n\t}\n\tparts = append(parts, m.textarea.View())\n\tif m.showHelp {\n\t\tparts = append(parts, \"\", m.help.View(m.keymap))\n\t}\n\treturn lipgloss.NewStyle().\n\t\tPadding(m.padding...).\n\t\tRender(lipgloss.JoinVertical(\n\t\t\tlipgloss.Left,\n\t\t\tparts...,\n\t\t))\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tif m.autoWidth {\n\t\t\tm.textarea.SetWidth(msg.Width - m.padding[1] - m.padding[3])\n\t\t}\n\tcase tea.FocusMsg, tea.BlurMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.textarea, cmd = m.textarea.Update(msg)\n\t\treturn m, cmd\n\tcase startEditorMsg:\n\t\treturn m, openEditor(msg.path, msg.lineno)\n\tcase editorFinishedMsg:\n\t\tif msg.err != nil {\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Interrupt\n\t\t}\n\t\tm.textarea.SetValue(msg.content)\n\tcase tea.KeyMsg:\n\t\tkm := m.keymap\n\t\tswitch {\n\t\tcase key.Matches(msg, km.Abort):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Interrupt\n\t\tcase key.Matches(msg, km.Quit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, km.Submit):\n\t\t\tm.quitting = true\n\t\t\tm.submitted = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, km.OpenInEditor):\n\t\t\treturn m, createTempFile(m.textarea.Value(), m.textarea.Line()+1)\n\t\t}\n\t}\n\n\tvar cmd tea.Cmd\n\tm.textarea, cmd = m.textarea.Update(msg)\n\treturn m, cmd\n}\n\ntype startEditorMsg struct {\n\tpath   string\n\tlineno int\n}\n\ntype editorFinishedMsg struct {\n\tcontent string\n\terr     error\n}\n\nfunc createTempFile(content string, lineno int) tea.Cmd {\n\treturn func() tea.Msg {\n\t\tf, err := os.CreateTemp(\"\", \"gum.*.md\")\n\t\tif err != nil {\n\t\t\treturn editorFinishedMsg{err: err}\n\t\t}\n\t\t_, err = io.WriteString(f, content)\n\t\tif err != nil {\n\t\t\treturn editorFinishedMsg{err: err}\n\t\t}\n\t\t_ = f.Close()\n\t\treturn startEditorMsg{\n\t\t\tpath:   f.Name(),\n\t\t\tlineno: lineno,\n\t\t}\n\t}\n}\n\nfunc openEditor(path string, lineno int) tea.Cmd {\n\tcb := func(err error) tea.Msg {\n\t\tif err != nil {\n\t\t\treturn editorFinishedMsg{\n\t\t\t\terr: err,\n\t\t\t}\n\t\t}\n\t\tbts, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn editorFinishedMsg{err: err}\n\t\t}\n\t\treturn editorFinishedMsg{\n\t\t\tcontent: string(bts),\n\t\t}\n\t}\n\tcmd, err := editor.Cmd(\n\t\t\"Gum\",\n\t\tpath,\n\t\teditor.LineNumber(lineno),\n\t\teditor.EndOfLine(),\n\t)\n\tif err != nil {\n\t\treturn func() tea.Msg { return cb(err) }\n\t}\n\treturn tea.ExecProcess(cmd, cb)\n}\n"
  }
]