pax_global_header00006660000000000000000000000064146643600400014515gustar00rootroot0000000000000052 comment=a5ea76b48e7138f942d49729321bf81728ec73d6 gum-0.14.4/000077500000000000000000000000001466436004000123735ustar00rootroot00000000000000gum-0.14.4/.github/000077500000000000000000000000001466436004000137335ustar00rootroot00000000000000gum-0.14.4/.github/CODEOWNERS000066400000000000000000000000331466436004000153220ustar00rootroot00000000000000* @charmbracelet/everyone gum-0.14.4/.github/ISSUE_TEMPLATE/000077500000000000000000000000001466436004000161165ustar00rootroot00000000000000gum-0.14.4/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000015021466436004000206060ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. gum-0.14.4/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011231466436004000216400ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. gum-0.14.4/.github/dependabot.yml000066400000000000000000000006241466436004000165650ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" labels: - "dependencies" commit-message: prefix: "feat" include: "scope" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" labels: - "dependencies" commit-message: prefix: "chore" include: "scope" gum-0.14.4/.github/pull_request_template.md000066400000000000000000000000401466436004000206660ustar00rootroot00000000000000Fixes #... ### Changes - - - gum-0.14.4/.github/workflows/000077500000000000000000000000001466436004000157705ustar00rootroot00000000000000gum-0.14.4/.github/workflows/build.yml000066400000000000000000000013031466436004000176070ustar00rootroot00000000000000name: build on: [push, pull_request] jobs: build: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} env: GO111MODULE: "on" steps: - name: Install Go uses: actions/setup-go@v5 with: go-version: ~1.21 - name: Checkout code uses: actions/checkout@v4 - name: Download Go modules run: go mod download - name: Build run: go build -v ./... - name: Test run: go test -v -cover -timeout=30s ./... snapshot: uses: charmbracelet/meta/.github/workflows/snapshot.yml@main secrets: goreleaser_key: ${{ secrets.GORELEASER_KEY }} gum-0.14.4/.github/workflows/goreleaser.yml000066400000000000000000000016751466436004000206540ustar00rootroot00000000000000# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: goreleaser on: push: tags: - v*.*.* concurrency: group: goreleaser cancel-in-progress: true jobs: goreleaser: uses: charmbracelet/meta/.github/workflows/goreleaser.yml@main secrets: docker_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_token: ${{ secrets.DOCKERHUB_TOKEN }} gh_pat: ${{ secrets.PERSONAL_ACCESS_TOKEN }} goreleaser_key: ${{ secrets.GORELEASER_KEY }} fury_token: ${{ secrets.FURY_TOKEN }} nfpm_gpg_key: ${{ secrets.NFPM_GPG_KEY }} nfpm_passphrase: ${{ secrets.NFPM_PASSPHRASE }} macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }} macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }} macos_notary_issuer_id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} macos_notary_key_id: ${{ secrets.MACOS_NOTARY_KEY_ID }} macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }} gum-0.14.4/.github/workflows/lint-soft.yml000066400000000000000000000013361466436004000204350ustar00rootroot00000000000000name: lint-soft on: push: pull_request: permissions: contents: read # Optional: allow read access to pull request. Use with `only-new-issues` option. pull-requests: read jobs: golangci: name: lint-soft runs-on: ubuntu-latest steps: - name: Install Go uses: actions/setup-go@v5 with: go-version: ^1 - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: # Optional: golangci-lint command line arguments. args: --config .golangci-soft.yml --issues-exit-code=0 # Optional: show only new issues if it's a pull request. The default value is `false`. only-new-issues: true gum-0.14.4/.github/workflows/lint.yml000066400000000000000000000012441466436004000174620ustar00rootroot00000000000000name: lint on: push: pull_request: permissions: contents: read # Optional: allow read access to pull request. Use with `only-new-issues` option. pull-requests: read jobs: golangci: name: lint runs-on: ubuntu-latest steps: - name: Install Go uses: actions/setup-go@v5 with: go-version: ^1 - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: # Optional: golangci-lint command line arguments. #args: # Optional: show only new issues if it's a pull request. The default value is `false`. only-new-issues: true gum-0.14.4/.github/workflows/nightly.yml000066400000000000000000000011411466436004000201660ustar00rootroot00000000000000name: nightly on: push: branches: - main jobs: nightly: uses: charmbracelet/meta/.github/workflows/nightly.yml@main secrets: docker_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_token: ${{ secrets.DOCKERHUB_TOKEN }} goreleaser_key: ${{ secrets.GORELEASER_KEY }} macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }} macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }} macos_notary_issuer_id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} macos_notary_key_id: ${{ secrets.MACOS_NOTARY_KEY_ID }} macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }} gum-0.14.4/.gitignore000066400000000000000000000001451466436004000143630ustar00rootroot00000000000000# Files test .DS_Store # Binaries gum dist testdata # Folders completions/ manpages/ # nix result gum-0.14.4/.golangci-soft.yml000066400000000000000000000012551466436004000157330ustar00rootroot00000000000000run: tests: false issues: include: - EXC0001 - EXC0005 - EXC0011 - EXC0012 - EXC0013 max-issues-per-linter: 0 max-same-issues: 0 linters: enable: # - dupl - exhaustive # - exhaustivestruct - goconst - godot - godox - gomnd - gomoddirectives - goprintffuncname # - ifshort # - lll - misspell - nakedret - nestif - noctx - nolintlint - prealloc # disable default linters, they are already enabled in .golangci.yml disable: - wrapcheck - deadcode - errcheck - gosimple - govet - ineffassign - staticcheck - structcheck - typecheck - varcheck gum-0.14.4/.golangci.yml000066400000000000000000000006051466436004000147600ustar00rootroot00000000000000run: tests: false issues: include: - EXC0001 - EXC0005 - EXC0011 - EXC0012 - EXC0013 max-issues-per-linter: 0 max-same-issues: 0 linters: enable: - bodyclose - exportloopref - goimports - gosec - nilerr - predeclared - revive - rowserrcheck - sqlclosecheck - tparallel - unconvert - unparam - whitespace gum-0.14.4/.goreleaser.yml000066400000000000000000000007261466436004000153310ustar00rootroot00000000000000# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json includes: - from_url: url: charmbracelet/meta/main/goreleaser-full.yaml variables: main: "." scoop_name: charm-gum description: "A tool for glamorous shell scripts" github_url: "https://github.com/charmbracelet/gum" maintainer: "Maas Lalani " brew_commit_author_name: "Maas Lalani" brew_commit_author_email: "maas@charm.sh" milestones: - close: true gum-0.14.4/Dockerfile000066400000000000000000000001361466436004000143650ustar00rootroot00000000000000FROM gcr.io/distroless/static COPY gum /usr/local/bin/gum ENTRYPOINT [ "/usr/local/bin/gum" ] gum-0.14.4/LICENSE000066400000000000000000000020701466436004000133770ustar00rootroot00000000000000MIT License Copyright (c) 2022-2024 Charmbracelet, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gum-0.14.4/README.md000066400000000000000000000322211466436004000136520ustar00rootroot00000000000000# Gum

Gum Image

Latest Release Go Docs Build Status

A tool for glamorous shell scripts. Leverage the power of [Bubbles](https://github.com/charmbracelet/bubbles) and [Lip Gloss](https://github.com/charmbracelet/lipgloss) in your scripts and aliases without writing any Go code! Shell running the ./demo.sh script The above example is running from a single shell script ([source](./examples/demo.sh)). ## Tutorial Gum provides highly configurable, ready-to-use utilities to help you write useful shell scripts and dotfiles aliases with just a few lines of code. Let's build a simple script to help you write [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) for your dotfiles. Ask for the commit type with gum choose: ```bash gum choose "fix" "feat" "docs" "style" "refactor" "test" "chore" "revert" ``` > [!NOTE] > 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`. Prompt for the scope of these changes: ```bash gum input --placeholder "scope" ``` Prompt for the summary and description of changes: ```bash gum input --value "$TYPE$SCOPE: " --placeholder "Summary of this change" gum write --placeholder "Details of this change" ``` Confirm before committing: ```bash gum confirm "Commit changes?" && git commit -m "$SUMMARY" -m "$DESCRIPTION" ``` Check out the [complete example](https://github.com/charmbracelet/gum/blob/main/examples/commit.sh) for combining these commands in a single script. Running the ./examples/commit.sh script to commit to git ## Installation Use a package manager: ```bash # macOS or Linux brew install gum # Arch Linux (btw) pacman -S gum # Nix nix-env -iA nixpkgs.gum # Flox flox install gum # Windows (via WinGet or Scoop) winget install charmbracelet.gum scoop install charm-gum ```
Debian/Ubuntu ```bash sudo mkdir -p /etc/apt/keyrings curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list sudo apt update && sudo apt install gum ```
Fedora/RHEL/OpenSuse ```bash echo '[charm] name=Charm baseurl=https://repo.charm.sh/yum/ enabled=1 gpgcheck=1 gpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo sudo rpm --import https://repo.charm.sh/yum/gpg.key # yum sudo yum install gum # zypper sudo zypper refresh sudo zypper install gum ```
Or download it: - [Packages][releases] are available in Debian, RPM, and Alpine formats - [Binaries][releases] are available for Linux, macOS, Windows, FreeBSD, OpenBSD, and NetBSD Or just install it with `go`: ```bash go install github.com/charmbracelet/gum@latest ``` [releases]: https://github.com/charmbracelet/gum/releases ## Commands - [`choose`](#choose): Choose an option from a list of choices - [`confirm`](#confirm): Ask a user to confirm an action - [`file`](#file): Pick a file from a folder - [`filter`](#filter): Filter items from a list - [`format`](#format): Format a string using a template - [`input`](#input): Prompt for some input - [`join`](#join): Join text vertically or horizontally - [`pager`](#pager): Scroll through a file - [`spin`](#spin): Display spinner while running a command - [`style`](#style): Apply coloring, borders, spacing to text - [`table`](#table): Render a table of data - [`write`](#write): Prompt for long-form text - [`log`](#log): Log messages to output ## Customization You can customize `gum` options and styles with `--flags` and `$ENVIRONMENT_VARIABLES`. See `gum --help` for a full view of each command's customization and configuration options. Customize with `--flags`: ```bash gum input --cursor.foreground "#FF0" \ --prompt.foreground "#0FF" \ --placeholder "What's up?" \ --prompt "* " \ --width 80 \ --value "Not much, hby?" ``` Customize with `ENVIRONMENT_VARIABLES`: ```bash export GUM_INPUT_CURSOR_FOREGROUND="#FF0" export GUM_INPUT_PROMPT_FOREGROUND="#0FF" export GUM_INPUT_PLACEHOLDER="What's up?" export GUM_INPUT_PROMPT="* " export GUM_INPUT_WIDTH=80 # --flags can override values set with environment gum input ``` Gum input displaying most customization options ## Input Prompt for input with a simple command. ```bash gum input > answer.txt gum input --password > password.txt ``` Shell running gum input typing Not much, you? ## Write Prompt for some multi-line text (`ctrl+d` to complete text entry). ```bash gum write > story.txt ``` Shell running gum write typing a story ## Filter Filter a list of values with fuzzy matching: ```bash echo Strawberry >> flavors.txt echo Banana >> flavors.txt echo Cherry >> flavors.txt gum filter < flavors.txt > selection.txt ``` Shell running gum filter on different bubble gum flavors Select multiple options with the `--limit` flag or `--no-limit` flag. Use `tab` or `ctrl+space` to select, `enter` to confirm. ```bash cat flavors.txt | gum filter --limit 2 cat flavors.txt | gum filter --no-limit ``` ## Choose Choose an option from a list of choices. ```bash echo "Pick a card, any card..." CARD=$(gum choose --height 15 {{A,K,Q,J},{10..2}}" "{♠,♥,♣,♦}) echo "Was your card the $CARD?" ``` You can also select multiple items with the `--limit` or `--no-limit` flag, which determines the maximum of items that can be chosen. ```bash cat songs.txt | gum choose --limit 5 cat foods.txt | gum choose --no-limit --header "Grocery Shopping" ``` Shell running gum choose with numbers and gum flavors ## Confirm Confirm whether to perform an action. Exits with code `0` (affirmative) or `1` (negative) depending on selection. ```bash gum confirm && rm file.txt || echo "File not removed" ``` Shell running gum confirm ## File Prompt the user to select a file from the file tree. ```bash EDITOR $(gum file $HOME) ``` Shell running gum file ## Pager Scroll through a long document with line numbers and a fully customizable viewport. ```bash gum pager < README.md ``` Shell running gum pager ## Spin Display a spinner while running a script or command. The spinner will automatically stop after the given command exits. To view or pipe the command's output, use the `--show-output` flag. ```bash gum spin --spinner dot --title "Buying Bubble Gum..." -- sleep 5 ``` Shell running gum spin while sleeping for 5 seconds Available spinner types include: `line`, `dot`, `minidot`, `jump`, `pulse`, `points`, `globe`, `moon`, `monkey`, `meter`, `hamburger`. ## Table Select a row from some tabular data. ```bash gum table < flavors.csv | cut -d ',' -f 1 ``` ## Style Pretty print any string with any layout with one command. ```bash gum style \ --foreground 212 --border-foreground 212 --border double \ --align center --width 50 --margin "1 2" --padding "2 4" \ 'Bubble Gum (1¢)' 'So sweet and so fresh!' ``` Bubble Gum, So sweet and so fresh! ## Join Combine text vertically or horizontally. Use this command with `gum style` to build layouts and pretty output. Tip: Always wrap the output of `gum style` in quotes to preserve newlines (`\n`) when using it as an argument in the `join` command. ```bash I=$(gum style --padding "1 5" --border double --border-foreground 212 "I") LOVE=$(gum style --padding "1 4" --border double --border-foreground 57 "LOVE") BUBBLE=$(gum style --padding "1 8" --border double --border-foreground 255 "Bubble") GUM=$(gum style --padding "1 5" --border double --border-foreground 240 "Gum") I_LOVE=$(gum join "$I" "$LOVE") BUBBLE_GUM=$(gum join "$BUBBLE" "$GUM") gum join --align center --vertical "$I_LOVE" "$BUBBLE_GUM" ``` I LOVE Bubble Gum written out in four boxes with double borders around them. ## Format `format` processes and formats bodies of text. `gum format` can parse markdown, template strings, and named emojis. ```bash # Format some markdown gum format -- "# Gum Formats" "- Markdown" "- Code" "- Template" "- Emoji" echo "# Gum Formats\n- Markdown\n- Code\n- Template\n- Emoji" | gum format # Syntax highlight some code cat main.go | gum format -t code # Render text any way you want with templates echo '{{ Bold "Tasty" }} {{ Italic "Bubble" }} {{ Color "99" "0" " Gum " }}' \ | gum format -t template # Display your favorite emojis! echo 'I :heart: Bubble Gum :candy:' | gum format -t emoji ``` For more information on template helpers, see the [Termenv docs](https://github.com/muesli/termenv#template-helpers). For a full list of named emojis see the [GitHub API](https://api.github.com/emojis). Running gum format for different types of formats ## Log `log` logs messages to the terminal at using different levels and styling using the [`charmbracelet/log`](https://github.com/charmbracelet/log) library. ```bash # Log some debug information. gum log --structured --level debug "Creating file..." name file.txt # DEBUG Unable to create file. name=temp.txt # Log some error. gum log --structured --level error "Unable to create file." name file.txt # ERROR Unable to create file. name=temp.txt # Include a timestamp. gum log --time rfc822 --level error "Unable to create file." ``` See the Go [`time` package](https://pkg.go.dev/time#pkg-constants) for acceptable `--time` formats. See [`charmbracelet/log`](https://github.com/charmbracelet/log) for more usage. Running gum log with debug and error levels ## Examples How to use `gum` in your daily workflows: See the [examples](./examples/) directory for more real world use cases. - Write a commit message: ```bash git commit -m "$(gum input --width 50 --placeholder "Summary of changes")" \ -m "$(gum write --width 80 --placeholder "Details of changes")" ``` - Open files in your `$EDITOR` ```bash $EDITOR $(gum filter) ``` - Connect to a `tmux` session ```bash SESSION=$(tmux list-sessions -F \#S | gum filter --placeholder "Pick session...") tmux switch-client -t $SESSION || tmux attach -t $SESSION ``` - Pick a commit hash from `git` history ```bash git log --oneline | gum filter | cut -d' ' -f1 # | copy ``` - Simple [`skate`](https://github.com/charmbracelet/skate) password selector. ``` skate list -k | gum filter | xargs skate get ``` - Uninstall packages ```bash brew list | gum choose --no-limit | xargs brew uninstall ``` - Clean up `git` branches ```bash git branch | cut -c 3- | gum choose --no-limit | xargs git branch -D ``` - Checkout GitHub pull requests with [`gh`](https://cli.github.com/) ```bash gh pr list | cut -f1,2 | gum choose | cut -f1 | xargs gh pr checkout ``` - Copy command from shell history ```bash gum filter < $HISTFILE --height 20 ``` - `sudo` replacement ```bash alias please="gum input --password | sudo -nS" ``` ## Feedback We’d love to hear your thoughts on this project. Feel free to drop us a note! - [Twitter](https://twitter.com/charmcli) - [The Fediverse](https://mastodon.social/@charmcli) - [Discord](https://charm.sh/chat) ## License [MIT](https://github.com/charmbracelet/gum/raw/main/LICENSE) --- Part of [Charm](https://charm.sh). The Charm logo Charm热爱开源 • Charm loves open source gum-0.14.4/choose/000077500000000000000000000000001466436004000136535ustar00rootroot00000000000000gum-0.14.4/choose/command.go000066400000000000000000000054151466436004000156250ustar00rootroot00000000000000package choose import ( "errors" "fmt" "os" "strings" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/term" "github.com/charmbracelet/gum/internal/stdin" ) const widthBuffer = 2 // Run provides a shell script interface for choosing between different through // options. func (o Options) Run() error { if len(o.Options) <= 0 { input, _ := stdin.Read() if input == "" { return errors.New("no options provided, see `gum choose --help`") } o.Options = strings.Split(input, "\n") } if o.SelectIfOne && len(o.Options) == 1 { fmt.Println(o.Options[0]) return nil } theme := huh.ThemeCharm() options := huh.NewOptions(o.Options...) theme.Focused.Base = lipgloss.NewStyle() theme.Focused.Title = o.HeaderStyle.ToLipgloss() theme.Focused.SelectSelector = o.CursorStyle.ToLipgloss().SetString(o.Cursor) theme.Focused.MultiSelectSelector = o.CursorStyle.ToLipgloss().SetString(o.Cursor) theme.Focused.SelectedOption = o.SelectedItemStyle.ToLipgloss() theme.Focused.UnselectedOption = o.ItemStyle.ToLipgloss() theme.Focused.SelectedPrefix = o.SelectedItemStyle.ToLipgloss().SetString(o.SelectedPrefix) theme.Focused.UnselectedPrefix = o.ItemStyle.ToLipgloss().SetString(o.UnselectedPrefix) for _, s := range o.Selected { for i, opt := range options { if s == opt.Key || s == opt.Value { options[i] = opt.Selected(true) } } } width := max(widest(o.Options)+ max(lipgloss.Width(o.SelectedPrefix)+lipgloss.Width(o.UnselectedPrefix))+ lipgloss.Width(o.Cursor)+1, lipgloss.Width(o.Header)+widthBuffer) if o.NoLimit { o.Limit = 0 } if o.Limit > 1 || o.NoLimit { var choices []string field := huh.NewMultiSelect[string](). Options(options...). Title(o.Header). Height(o.Height). Limit(o.Limit). Value(&choices) form := huh.NewForm(huh.NewGroup(field)) err := form. WithWidth(width). WithShowHelp(o.ShowHelp). WithTheme(theme). Run() if err != nil { return err } if len(choices) > 0 { s := strings.Join(choices, "\n") ansiprint(s) } return nil } var choice string err := huh.NewForm( huh.NewGroup( huh.NewSelect[string](). Options(options...). Title(o.Header). Height(o.Height). Value(&choice), ), ). WithWidth(width). WithTheme(theme). WithShowHelp(o.ShowHelp). Run() if err != nil { return err } if term.IsTerminal(os.Stdout.Fd()) { fmt.Println(choice) } else { fmt.Print(ansi.Strip(choice)) } return nil } func widest(options []string) int { var max int for _, o := range options { w := lipgloss.Width(o) if w > max { max = w } } return max } func ansiprint(s string) { if term.IsTerminal(os.Stdout.Fd()) { fmt.Println(s) } else { fmt.Print(ansi.Strip(s)) } } gum-0.14.4/choose/options.go000066400000000000000000000044321466436004000157000ustar00rootroot00000000000000package choose import ( "time" "github.com/charmbracelet/gum/style" ) // Options is the customization options for the choose command. type Options struct { Options []string `arg:"" optional:"" help:"Options to choose from."` Limit int `help:"Maximum number of options to pick" default:"1" group:"Selection"` NoLimit bool `help:"Pick unlimited number of options (ignores limit)" group:"Selection"` Ordered bool `help:"Maintain the order of the selected options" env:"GUM_CHOOSE_ORDERED"` Height int `help:"Height of the list" default:"0" env:"GUM_CHOOSE_HEIGHT"` Cursor string `help:"Prefix to show on item that corresponds to the cursor position" default:"> " env:"GUM_CHOOSE_CURSOR"` ShowHelp bool `help:"Show help keybinds" default:"true" negatable:"true" env:"GUM_CHOOSE_SHOW_HELP"` Header string `help:"Header value" default:"Choose:" env:"GUM_CHOOSE_HEADER"` CursorPrefix string `help:"Prefix to show on the cursor item (hidden if limit is 1)" default:"• " env:"GUM_CHOOSE_CURSOR_PREFIX"` SelectedPrefix string `help:"Prefix to show on selected items (hidden if limit is 1)" default:"✓ " env:"GUM_CHOOSE_SELECTED_PREFIX"` UnselectedPrefix string `help:"Prefix to show on unselected items (hidden if limit is 1)" default:"• " env:"GUM_CHOOSE_UNSELECTED_PREFIX"` Selected []string `help:"Options that should start as selected" default:"" env:"GUM_CHOOSE_SELECTED"` SelectIfOne bool `help:"Select the given option if there is only one" group:"Selection"` CursorStyle style.Styles `embed:"" prefix:"cursor." set:"defaultForeground=212" envprefix:"GUM_CHOOSE_CURSOR_"` HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=99" envprefix:"GUM_CHOOSE_HEADER_"` ItemStyle style.Styles `embed:"" prefix:"item." hidden:"" envprefix:"GUM_CHOOSE_ITEM_"` SelectedItemStyle style.Styles `embed:"" prefix:"selected." set:"defaultForeground=212" envprefix:"GUM_CHOOSE_SELECTED_"` Timeout time.Duration `help:"Timeout until choose returns selected element" default:"0" env:"GUM_CCHOOSE_TIMEOUT"` // including timeout command options [Timeout,...] } gum-0.14.4/completion/000077500000000000000000000000001466436004000145445ustar00rootroot00000000000000gum-0.14.4/completion/bash.go000066400000000000000000000525111466436004000160140ustar00rootroot00000000000000package completion import ( "bytes" "fmt" "io" "sort" "strings" "github.com/alecthomas/kong" ) // Bash is a bash completion generator. type Bash struct{} // Run generates bash completion script. func (b Bash) Run(ctx *kong.Context) error { buf := new(bytes.Buffer) writePreamble(buf, ctx.Model.Name) b.gen(buf, ctx.Model.Node) writePostscript(buf, ctx.Model.Name) _, err := fmt.Fprint(ctx.Stdout, buf.String()) if err != nil { return fmt.Errorf("unable to generate bash completion: %v", err) } return nil } // ShellCompDirective is a bit map representing the different behaviors the shell // can be instructed to have once completions have been provided. type ShellCompDirective int const ( // ShellCompDirectiveError indicates an error occurred and completions should be ignored. ShellCompDirectiveError ShellCompDirective = 1 << iota // ShellCompDirectiveNoSpace indicates that the shell should not add a space // after the completion even if there is a single completion provided. ShellCompDirectiveNoSpace // ShellCompDirectiveNoFileComp indicates that the shell should not provide // file completion even when no completion is provided. ShellCompDirectiveNoFileComp // ShellCompDirectiveFilterFileExt indicates that the provided completions // should be used as file extension filters. // For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename() // is a shortcut to using this directive explicitly. The BashCompFilenameExt // annotation can also be used to obtain the same behavior for flags. ShellCompDirectiveFilterFileExt // ShellCompDirectiveFilterDirs indicates that only directory names should // be provided in file completion. To request directory names within another // directory, the returned completions should specify the directory within // which to search. The BashCompSubdirsInDir annotation can be used to // obtain the same behavior but only for flags. ShellCompDirectiveFilterDirs // =========================================================================== // // All directives using iota should be above this one. // For internal use. shellCompDirectiveMaxValue //nolint:deadcode,unused,varcheck // ShellCompDirectiveDefault indicates to let the shell perform its default // behavior after completions have been provided. // This one must be last to avoid messing up the iota count. ShellCompDirectiveDefault ShellCompDirective = 0 ) // Annotations for Bash completion. const ( // ShellCompNoDescRequestCmd is the name of the hidden command that is used to request // completion results without their description. It is used by the shell completion scripts. ShellCompNoDescRequestCmd = "completion completeNoDesc" BashCompFilenameExt = "kong_annotation_bash_completion_filename_extensions" BashCompCustom = "kong_annotation_bash_completion_custom" BashCompOneRequiredFlag = "kong_annotation_bash_completion_one_required_flag" BashCompSubdirsInDir = "kong_annotation_bash_completion_subdirs_in_dir" activeHelpEnvVarSuffix = "_ACTIVE_HELP" ) // activeHelpEnvVar returns the name of the program-specific ActiveHelp environment // variable. It has the format _ACTIVE_HELP where is the name of the // root command in upper case, with all - replaced by _. func activeHelpEnvVar(name string) string { // This format should not be changed: users will be using it explicitly. activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix)) return strings.ReplaceAll(activeHelpEnvVar, "-", "_") } func writePreamble(buf io.StringWriter, name string) { writeString(buf, fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name)) writeString(buf, fmt.Sprintf(` __%[1]s_debug() { if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then echo "$*" >> "${BASH_COMP_DEBUG_FILE}" fi } # Homebrew on Macs have version 1.3 of bash-completion which doesn't include # _init_completion. This is a very minimal version of that function. __%[1]s_init_completion() { COMPREPLY=() _get_comp_words_by_ref "$@" cur prev words cword } __%[1]s_index_of_word() { local w word=$1 shift index=0 for w in "$@"; do [[ $w = "$word" ]] && return index=$((index+1)) done index=-1 } __%[1]s_contains_word() { local w word=$1; shift for w in "$@"; do [[ $w = "$word" ]] && return done return 1 } __%[1]s_handle_go_custom_completion() { __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" local shellCompDirectiveError=%[3]d local shellCompDirectiveNoSpace=%[4]d local shellCompDirectiveNoFileComp=%[5]d local shellCompDirectiveFilterFileExt=%[6]d local shellCompDirectiveFilterDirs=%[7]d local out requestComp lastParam lastChar comp directive args # Prepare the command to request completions for the program. # Calling ${words[0]} instead of directly %[1]s allows to handle aliases args=("${words[@]:1}") # Disable ActiveHelp which is not supported for bash completion v1 requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}" lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} __%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then # If the last parameter is complete (there is a space following it) # We add an extra empty parameter so we can indicate this to the go method. __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter" requestComp="${requestComp} \"\"" fi __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}" # Use eval to handle any environment variables and such out=$(eval "${requestComp}" 2>/dev/null) # Extract the directive integer at the very end of the output following a colon (:) directive=${out##*:} # Remove the directive out=${out%%:*} if [ "${directive}" = "${out}" ]; then # There is not directive specified directive=0 fi __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out}" if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then # Error code. No completion. __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code" return else if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then if [[ $(type -t compopt) = "builtin" ]]; then __%[1]s_debug "${FUNCNAME[0]}: activating no space" compopt -o nospace fi fi if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then if [[ $(type -t compopt) = "builtin" ]]; then __%[1]s_debug "${FUNCNAME[0]}: activating no file completion" compopt +o default fi fi fi if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then # File extension filtering local fullFilter filter filteringCmd # Do not use quotes around the $out variable or else newline # characters will be kept. for filter in ${out}; do fullFilter+="$filter|" done filteringCmd="_filedir $fullFilter" __%[1]s_debug "File filtering command: $filteringCmd" $filteringCmd elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then # File completion for directories only local subdir # Use printf to strip any trailing newline subdir=$(printf "%%s" "${out}") if [ -n "$subdir" ]; then __%[1]s_debug "Listing directories in $subdir" __%[1]s_handle_subdirs_in_dir_flag "$subdir" else __%[1]s_debug "Listing directories in ." _filedir -d fi else while IFS='' read -r comp; do COMPREPLY+=("$comp") done < <(compgen -W "${out}" -- "$cur") fi } __%[1]s_handle_reply() { __%[1]s_debug "${FUNCNAME[0]}" local comp case $cur in -*) if [[ $(type -t compopt) = "builtin" ]]; then compopt -o nospace fi local allflags if [ ${#must_have_one_flag[@]} -ne 0 ]; then allflags=("${must_have_one_flag[@]}") else allflags=("${flags[*]} ${two_word_flags[*]}") fi while IFS='' read -r comp; do COMPREPLY+=("$comp") done < <(compgen -W "${allflags[*]}" -- "$cur") if [[ $(type -t compopt) = "builtin" ]]; then [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace fi # complete after --flag=abc if [[ $cur == *=* ]]; then if [[ $(type -t compopt) = "builtin" ]]; then compopt +o nospace fi local index flag flag="${cur%%=*}" __%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}" COMPREPLY=() if [[ ${index} -ge 0 ]]; then PREFIX="" cur="${cur#*=}" ${flags_completion[${index}]} if [ -n "${ZSH_VERSION:-}" ]; then # zsh completion needs --flag= prefix eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" fi fi fi if [[ -z "${flag_parsing_disabled}" ]]; then # If flag parsing is enabled, we have completed the flags and can return. # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough # to possibly call handle_go_custom_completion. return 0; fi ;; esac # check if we are handling a flag with special work handling local index __%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}" if [[ ${index} -ge 0 ]]; then ${flags_completion[${index}]} return fi # we are parsing a flag and don't have a special handler, no completion if [[ ${cur} != "${words[cword]}" ]]; then return fi local completions completions=("${commands[@]}") if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then completions+=("${must_have_one_noun[@]}") elif [[ -n "${has_completion_function}" ]]; then # if a go completion function is provided, defer to that function __%[1]s_handle_go_custom_completion fi if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then completions+=("${must_have_one_flag[@]}") fi while IFS='' read -r comp; do COMPREPLY+=("$comp") done < <(compgen -W "${completions[*]}" -- "$cur") if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then while IFS='' read -r comp; do COMPREPLY+=("$comp") done < <(compgen -W "${noun_aliases[*]}" -- "$cur") fi if [[ ${#COMPREPLY[@]} -eq 0 ]]; then if declare -F __%[1]s_custom_func >/dev/null; then # try command name qualified custom func __%[1]s_custom_func else # otherwise fall back to unqualified for compatibility declare -F __custom_func >/dev/null && __custom_func fi fi # available in bash-completion >= 2, not always present on macOS if declare -F __ltrim_colon_completions >/dev/null; then __ltrim_colon_completions "$cur" fi # If there is only 1 completion and it is a flag with an = it will be completed # but we don't want a space after the = if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then compopt -o nospace fi } # The arguments should be in the form "ext1|ext2|extn" __%[1]s_handle_filename_extension_flag() { local ext="$1" _filedir "@(${ext})" } __%[1]s_handle_subdirs_in_dir_flag() { local dir="$1" pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return } __%[1]s_handle_flag() { __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" # if a command required a flag, and we found it, unset must_have_one_flag() local flagname=${words[c]} local flagvalue="" # if the word contained an = if [[ ${words[c]} == *"="* ]]; then flagvalue=${flagname#*=} # take in as flagvalue after the = flagname=${flagname%%=*} # strip everything after the = flagname="${flagname}=" # but put the = back fi __%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}" if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then must_have_one_flag=() fi # if you set a flag which only applies to this command, don't show subcommands if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then commands=() fi # keep flag value with flagname as flaghash # flaghash variable is an associative array which is only supported in bash > 3. if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then if [ -n "${flagvalue}" ] ; then flaghash[${flagname}]=${flagvalue} elif [ -n "${words[ $((c+1)) ]}" ] ; then flaghash[${flagname}]=${words[ $((c+1)) ]} else flaghash[${flagname}]="true" # pad "true" for bool flag fi fi # skip the argument to a two word flag if [[ ${words[c]} != *"="* ]] && __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then __%[1]s_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument" c=$((c+1)) # if we are looking for a flags value, don't show commands if [[ $c -eq $cword ]]; then commands=() fi fi c=$((c+1)) } __%[1]s_handle_noun() { __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then must_have_one_noun=() elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then must_have_one_noun=() fi nouns+=("${words[c]}") c=$((c+1)) } __%[1]s_handle_command() { __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" local next_command if [[ -n ${last_command} ]]; then next_command="_${last_command}_${words[c]//:/__}" else if [[ $c -eq 0 ]]; then next_command="_%[1]s_root_command" else next_command="_${words[c]//:/__}" fi fi c=$((c+1)) __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}" declare -F "$next_command" >/dev/null && $next_command } __%[1]s_handle_word() { if [[ $c -ge $cword ]]; then __%[1]s_handle_reply return fi __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" if [[ "${words[c]}" == -* ]]; then __%[1]s_handle_flag elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then __%[1]s_handle_command elif [[ $c -eq 0 ]]; then __%[1]s_handle_command elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then # aliashash variable is an associative array which is only supported in bash > 3. if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then words[c]=${aliashash[${words[c]}]} __%[1]s_handle_command else __%[1]s_handle_noun fi else __%[1]s_handle_noun fi __%[1]s_handle_word } `, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name))) } func writePostscript(buf io.StringWriter, name string) { name = strings.ReplaceAll(name, ":", "__") writeString(buf, fmt.Sprintf("__start_%s()\n", name)) writeString(buf, fmt.Sprintf(`{ local cur prev words cword split declare -A flaghash 2>/dev/null || : declare -A aliashash 2>/dev/null || : if declare -F _init_completion >/dev/null 2>&1; then _init_completion -s || return else __%[1]s_init_completion -n "=" || return fi local c=0 local flag_parsing_disabled= local flags=() local two_word_flags=() local local_nonpersistent_flags=() local flags_with_completion=() local flags_completion=() local commands=("%[1]s") local command_aliases=() local must_have_one_flag=() local must_have_one_noun=() local has_completion_function="" local last_command="" local nouns=() local noun_aliases=() __%[1]s_handle_word } `, name)) writeString(buf, fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then complete -o default -F __start_%s %s else complete -o default -o nospace -F __start_%s %s fi `, name, name, name, name)) writeString(buf, "# ex: ts=4 sw=4 et filetype=sh\n") } func writeCommands(buf io.StringWriter, cmd *kong.Node) { writeString(buf, " commands=()\n") for _, c := range cmd.Children { if c == nil || c.Hidden { continue } writeString(buf, fmt.Sprintf(" commands+=(%q)\n", c.Name)) writeCmdAliases(buf, c) } writeString(buf, "\n") } func writeFlagHandler(buf io.StringWriter, name string, annotations map[string][]string, cmd *kong.Node) { for key, value := range annotations { switch key { case BashCompFilenameExt: writeString(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) var ext string if len(value) > 0 { ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Parent.Name) + strings.Join(value, "|") } else { ext = "_filedir" } writeString(buf, fmt.Sprintf(" flags_completion+=(%q)\n", ext)) case BashCompCustom: writeString(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) if len(value) > 0 { handlers := strings.Join(value, "; ") writeString(buf, fmt.Sprintf(" flags_completion+=(%q)\n", handlers)) } else { writeString(buf, " flags_completion+=(:)\n") } case BashCompSubdirsInDir: writeString(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) var ext string if len(value) == 1 { ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Parent.Name) + value[0] } else { ext = "_filedir -d" } writeString(buf, fmt.Sprintf(" flags_completion+=(%q)\n", ext)) } } } const cbn = "\")\n" func writeShortFlag(buf io.StringWriter, flag *kong.Flag, cmd *kong.Node) { name := fmt.Sprintf("%c", flag.Short) format := " " if len(flag.DefaultValue.String()) == 0 { format += "two_word_" } format += "flags+=(\"-%s" + cbn writeString(buf, fmt.Sprintf(format, name)) writeFlagHandler(buf, "-"+name, map[string][]string{}, cmd) } func writeFlag(buf io.StringWriter, flag *kong.Flag, cmd *kong.Node) { name := flag.Name format := " flags+=(\"--%s" if len(flag.DefaultValue.String()) == 0 { format += "=" } format += cbn writeString(buf, fmt.Sprintf(format, name)) if len(flag.DefaultValue.String()) == 0 { format = " two_word_flags+=(\"--%s" + cbn writeString(buf, fmt.Sprintf(format, name)) } writeFlagHandler(buf, "--"+name, map[string][]string{}, cmd) } //nolint:deadcode,unused func writeLocalNonPersistentFlag(buf io.StringWriter, flag *kong.Flag) { name := flag.Name format := " local_nonpersistent_flags+=(\"--%[1]s" + cbn if len(flag.DefaultValue.String()) == 0 { format += " local_nonpersistent_flags+=(\"--%[1]s=" + cbn } writeString(buf, fmt.Sprintf(format, name)) if flag.Short > 0 { writeString(buf, fmt.Sprintf(" local_nonpersistent_flags+=(\"-%c\")\n", flag.Short)) } } func writeFlags(buf io.StringWriter, cmd *kong.Node) { writeString(buf, ` flags=() two_word_flags=() local_nonpersistent_flags=() flags_with_completion=() flags_completion=() `) for _, flag := range cmd.Flags { if nonCompletableFlag(flag) { continue } writeFlag(buf, flag, cmd) if flag.Short != 0 { writeShortFlag(buf, flag, cmd) } } writeString(buf, "\n") } func writeCmdAliases(buf io.StringWriter, cmd *kong.Node) { if len(cmd.Aliases) == 0 { return } sort.Strings(cmd.Aliases) writeString(buf, fmt.Sprint(` if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then`, "\n")) for _, value := range cmd.Aliases { writeString(buf, fmt.Sprintf(" command_aliases+=(%q)\n", value)) writeString(buf, fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name)) } writeString(buf, ` fi`) writeString(buf, "\n") } func writeArgAliases(buf io.StringWriter, cmd *kong.Node) { writeString(buf, " noun_aliases=()\n") sort.Strings(cmd.Aliases) for _, value := range cmd.Aliases { writeString(buf, fmt.Sprintf(" noun_aliases+=(%q)\n", value)) } } func (b Bash) gen(buf io.StringWriter, cmd *kong.Node) { for _, c := range cmd.Children { if c == nil || c.Hidden { continue } b.gen(buf, c) } commandName := cmd.FullPath() commandName = strings.ReplaceAll(commandName, " ", "_") commandName = strings.ReplaceAll(commandName, ":", "__") if cmd.Parent == nil { writeString(buf, fmt.Sprintf("_%s_root_command()\n{\n", commandName)) } else { writeString(buf, fmt.Sprintf("_%s()\n{\n", commandName)) } writeString(buf, fmt.Sprintf(" last_command=%q\n", commandName)) writeString(buf, "\n") writeString(buf, " command_aliases=()\n") writeString(buf, "\n") writeCommands(buf, cmd) writeFlags(buf, cmd) writeArgAliases(buf, cmd) writeString(buf, "}\n\n") } gum-0.14.4/completion/command.go000066400000000000000000000024541466436004000165160ustar00rootroot00000000000000package completion import ( "fmt" "io" "os" "strings" "github.com/alecthomas/kong" ) // Completion command. type Completion struct { Bash Bash `cmd:"" help:"Generate the autocompletion script for bash"` Zsh Zsh `cmd:"" help:"Generate the autocompletion script for zsh"` Fish Fish `cmd:"" help:"Generate the autocompletion script for fish"` } func commandName(cmd *kong.Node) string { commandName := cmd.FullPath() commandName = strings.ReplaceAll(commandName, " ", "_") commandName = strings.ReplaceAll(commandName, ":", "__") return commandName } func hasCommands(cmd *kong.Node) bool { for _, c := range cmd.Children { if !c.Hidden { return true } } return false } //nolint:deadcode,unused func isArgument(cmd *kong.Node) bool { return cmd.Type == kong.ArgumentNode } // writeString writes a string into a buffer, and checks if the error is not nil. func writeString(b io.StringWriter, s string) { if _, err := b.WriteString(s); err != nil { fmt.Fprintln(os.Stderr, "Error:", err) os.Exit(1) } } func nonCompletableFlag(flag *kong.Flag) bool { return flag.Hidden } func flagPossibleValues(flag *kong.Flag) []string { values := make([]string, 0) for _, enum := range flag.EnumSlice() { if strings.TrimSpace(enum) != "" { values = append(values, enum) } } return values } gum-0.14.4/completion/fish.go000066400000000000000000000036671466436004000160400ustar00rootroot00000000000000package completion import ( "fmt" "io" "strings" "github.com/alecthomas/kong" ) // Fish is a fish shell completion generator. type Fish struct{} // Run generates fish completion script. func (f Fish) Run(ctx *kong.Context) error { var buf strings.Builder buf.WriteString(`# Fish shell completion for gum # Generated by gum completion # disable file completion unless explicitly enabled complete -c gum -f `) node := ctx.Model.Node f.gen(&buf, node) _, err := fmt.Fprint(ctx.Stdout, buf.String()) if err != nil { return fmt.Errorf("unable to generate fish completion: %w", err) } return nil } func (f Fish) gen(buf io.StringWriter, cmd *kong.Node) { root := cmd for root.Parent != nil { root = root.Parent } rootName := root.Name if cmd.Parent == nil { _, _ = buf.WriteString(fmt.Sprintf("# %s\n", rootName)) } else { _, _ = buf.WriteString(fmt.Sprintf("# %s\n", cmd.Path())) _, _ = buf.WriteString( fmt.Sprintf("complete -c %s -f -n '__fish_use_subcommand' -a %s -d '%s'\n", rootName, cmd.Name, cmd.Help, ), ) } for _, f := range cmd.Flags { if f.Hidden { continue } if cmd.Parent == nil { _, _ = buf.WriteString( fmt.Sprintf("complete -c %s -f", rootName, ), ) } else { _, _ = buf.WriteString( fmt.Sprintf("complete -c %s -f -n '__fish_seen_subcommand_from %s'", rootName, cmd.Name, ), ) } if !f.IsBool() { enums := flagPossibleValues(f) if len(enums) > 0 { _, _ = buf.WriteString(fmt.Sprintf(" -xa '%s'", strings.Join(enums, " "))) } else { _, _ = buf.WriteString(" -x") } } if f.Short != 0 { _, _ = buf.WriteString(fmt.Sprintf(" -s %c", f.Short)) } _, _ = buf.WriteString(fmt.Sprintf(" -l %s", f.Name)) _, _ = buf.WriteString(fmt.Sprintf(" -d '%s'", f.Help)) _, _ = buf.WriteString("\n") } _, _ = buf.WriteString("\n") for _, c := range cmd.Children { if c == nil || c.Hidden { continue } f.gen(buf, c) } } gum-0.14.4/completion/zsh.go000066400000000000000000000067371466436004000157140ustar00rootroot00000000000000package completion import ( "fmt" "io" "strings" "github.com/alecthomas/kong" ) // Zsh is zsh completion generator. type Zsh struct{} // Run generates zsh completion script. func (z Zsh) Run(ctx *kong.Context) error { var out strings.Builder format := `#compdef %[1]s # zsh completion for %[1]s # generated by gum completion ` fmt.Fprintf(&out, format, ctx.Model.Name) z.gen(&out, ctx.Model.Node) _, err := fmt.Fprint(ctx.Stdout, out.String()) if err != nil { return fmt.Errorf("unable to generate zsh completion: %w", err) } return nil } func (z Zsh) writeFlag(buf io.StringWriter, f *kong.Flag) { var str strings.Builder str.WriteString(" ") if f.Short != 0 { str.WriteString("'(") str.WriteString(fmt.Sprintf("-%c --%s", f.Short, f.Name)) if !f.IsBool() { str.WriteString("=") } str.WriteString(")'") str.WriteString("{") str.WriteString(fmt.Sprintf("-%c,--%s", f.Short, f.Name)) if !f.IsBool() { str.WriteString("=") } str.WriteString("}") str.WriteString("\"") } else { str.WriteString("\"") str.WriteString(fmt.Sprintf("--%s", f.Name)) if !f.IsBool() { str.WriteString("=") } } str.WriteString(fmt.Sprintf("[%s]", f.Help)) if !f.IsBool() { str.WriteString(":") str.WriteString(strings.ToLower(f.Help)) str.WriteString(":") } values := flagPossibleValues(f) if len(values) > 0 { str.WriteString("(") for i, v := range f.EnumSlice() { str.WriteString(v) if i < len(values)-1 { str.WriteString(" ") } } str.WriteString(")") } str.WriteString("\"") writeString(buf, str.String()) } func (z Zsh) writeFlags(buf io.StringWriter, cmd *kong.Node) { for i, f := range cmd.Flags { if f.Hidden { continue } z.writeFlag(buf, f) if i < len(cmd.Flags)-1 { writeString(buf, " \\\n") } } } func (z Zsh) writeCommand(buf io.StringWriter, c *kong.Node) { writeString(buf, fmt.Sprintf(" \"%s[%s]\"", c.Name, c.Help)) } func (z Zsh) writeCommands(buf io.StringWriter, cmd *kong.Node) { for i, c := range cmd.Children { if c == nil || c.Hidden { continue } z.writeCommand(buf, c) if i < len(cmd.Children)-1 { _, _ = buf.WriteString(" \\") } writeString(buf, "\n") } } func (z Zsh) gen(buf io.StringWriter, cmd *kong.Node) { for _, c := range cmd.Children { if c == nil || c.Hidden { continue } z.gen(buf, c) } cmdName := commandName(cmd) writeString(buf, fmt.Sprintf("_%s() {\n", cmdName)) if hasCommands(cmd) { writeString(buf, " local line state\n") } writeString(buf, " _arguments -C \\\n") z.writeFlags(buf, cmd) if hasCommands(cmd) { writeString(buf, " \\\n") writeString(buf, " \"1: :->cmds\" \\\n") writeString(buf, " \"*::arg:->args\"\n") writeString(buf, " case \"$state\" in\n") writeString(buf, " cmds)\n") writeString(buf, fmt.Sprintf(" _values \"%s command\" \\\n", cmdName)) z.writeCommands(buf, cmd) writeString(buf, " ;;\n") writeString(buf, " args)\n") writeString(buf, " case \"$line[1]\" in\n") for _, c := range cmd.Children { if c == nil || c.Hidden { continue } writeString(buf, fmt.Sprintf(" %s)\n", c.Name)) writeString(buf, fmt.Sprintf(" _%s\n", commandName(c))) writeString(buf, " ;;\n") } writeString(buf, " esac\n") writeString(buf, " ;;\n") writeString(buf, " esac\n") } // writeArgAliases(buf, cmd) writeString(buf, "\n") writeString(buf, "}\n\n") } gum-0.14.4/confirm/000077500000000000000000000000001466436004000140305ustar00rootroot00000000000000gum-0.14.4/confirm/command.go000066400000000000000000000014351466436004000160000ustar00rootroot00000000000000package confirm import ( "fmt" "os" "github.com/charmbracelet/huh" ) // Run provides a shell script interface for prompting a user to confirm an // action with an affirmative or negative answer. func (o Options) Run() error { theme := huh.ThemeCharm() theme.Focused.Title = o.PromptStyle.ToLipgloss() theme.Focused.FocusedButton = o.SelectedStyle.ToLipgloss() theme.Focused.BlurredButton = o.UnselectedStyle.ToLipgloss() choice := o.Default err := huh.NewForm( huh.NewGroup( huh.NewConfirm(). Affirmative(o.Affirmative). Negative(o.Negative). Title(o.Prompt). Value(&choice), ), ). WithTheme(theme). WithShowHelp(o.ShowHelp). Run() if err != nil { return fmt.Errorf("unable to run confirm: %w", err) } if !choice { os.Exit(1) } return nil } gum-0.14.4/confirm/options.go000066400000000000000000000027111466436004000160530ustar00rootroot00000000000000package confirm import ( "time" "github.com/charmbracelet/gum/style" ) // Options is the customization options for the confirm command. type Options struct { Default bool `help:"Default confirmation action" default:"true"` Affirmative string `help:"The title of the affirmative action" default:"Yes"` Negative string `help:"The title of the negative action" default:"No"` Prompt string `arg:"" help:"Prompt to display." default:"Are you sure?"` //nolint:staticcheck PromptStyle 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_"` //nolint:staticcheck SelectedStyle 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_"` //nolint:staticcheck UnselectedStyle 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_"` ShowHelp bool `help:"Show help key binds" negatable:"" default:"true" env:"GUM_CONFIRM_SHOW_HELP"` Timeout time.Duration `help:"Timeout until confirm returns selected value or default if provided" default:"0" env:"GUM_CONFIRM_TIMEOUT"` } gum-0.14.4/cursor/000077500000000000000000000000001466436004000137105ustar00rootroot00000000000000gum-0.14.4/cursor/cursor.go000066400000000000000000000003611466436004000155540ustar00rootroot00000000000000package cursor import ( "github.com/charmbracelet/bubbles/cursor" ) // Modes maps strings to cursor modes. var Modes = map[string]cursor.Mode{ "blink": cursor.CursorBlink, "hide": cursor.CursorHide, "static": cursor.CursorStatic, } gum-0.14.4/default.nix000066400000000000000000000003331466436004000145360ustar00rootroot00000000000000{ pkgs }: pkgs.buildGoModule rec { pname = "gum"; version = "0.14.0"; src = ./.; vendorHash = "sha256-gDDaKrwlrJyyDzgyGf9iP/XPnOAwpkvIyzCXobXrlF4="; ldflags = [ "-s" "-w" "-X=main.Version=${version}" ]; } gum-0.14.4/examples/000077500000000000000000000000001466436004000142115ustar00rootroot00000000000000gum-0.14.4/examples/.gitignore000066400000000000000000000000141466436004000161740ustar00rootroot00000000000000*.gif *.png gum-0.14.4/examples/README.md000066400000000000000000000012701466436004000154700ustar00rootroot00000000000000# Glamour A casual introduction. 你好世界! ## Let's talk about artichokes The artichoke is mentioned as a garden plant in the 8th century BC by Homer and Hesiod. The naturally occurring variant of the artichoke, the cardoon, which is native to the Mediterranean area, also has records of use as a food among the ancient Greeks and Romans. Pliny the Elder mentioned growing of 'carduus' in Carthage and Cordoba. He holds him with his skinny hand, There was ship,' quoth he. 'Hold off! unhand me, grey-beard loon!' An artichoke dropt he. ## Other foods worth mentioning 1. Carrots 2. Celery 3. Tacos • Soft • Hard 4. Cucumber ## Things to eat today * Carrots * Ramen * Currywurst gum-0.14.4/examples/choose.tape000066400000000000000000000004731466436004000163500ustar00rootroot00000000000000Output choose.gif Set Width 1000 Set Height 430 Set Shell bash Type "gum choose {1..5}" Sleep 500ms Enter Sleep 500ms Down@250ms 3 Sleep 500ms Up@250ms 2 Enter Sleep 1.5s Ctrl+L Sleep 500ms Type "gum choose --limit 2 Banana Cherry Orange" Sleep 500ms Enter Sleep 500ms Type@250ms "jxjxk" Sleep 1s Enter Sleep 2s gum-0.14.4/examples/commit.sh000077500000000000000000000022211466436004000160350ustar00rootroot00000000000000#!/bin/sh # This script is used to write a conventional commit message. # It prompts the user to choose the type of commit as specified in the # conventional commit spec. And then prompts for the summary and detailed # description of the message and uses the values provided. as the summary and # details of the message. # # If you want to add a simpler version of this script to your dotfiles, use: # # alias gcm='git commit -m "$(gum input)" -m "$(gum write)"' # if [ -z "$(git status -s -uno | grep -v '^ ' | awk '{print $2}')" ]; then # gum confirm "Stage all?" && git add . # fi TYPE=$(gum choose "fix" "feat" "docs" "style" "refactor" "test" "chore" "revert") SCOPE=$(gum input --placeholder "scope") # Since the scope is optional, wrap it in parentheses if it has a value. test -n "$SCOPE" && SCOPE="($SCOPE)" # Pre-populate the input with the type(scope): so that the user may change it SUMMARY=$(gum input --value "$TYPE$SCOPE: " --placeholder "Summary of this change") DESCRIPTION=$(gum write --placeholder "Details of this change") # Commit these changes if user confirms gum confirm "Commit changes?" && git commit -m "$SUMMARY" -m "$DESCRIPTION" gum-0.14.4/examples/commit.tape000066400000000000000000000007321466436004000163560ustar00rootroot00000000000000Output commit.gif Set Shell "bash" Set FontSize 32 Set Width 1200 Set Height 600 Type "./commit.sh" Sleep 500ms Enter Sleep 1s Down@250ms 2 Sleep 500ms Enter Sleep 500ms Type "gum" Sleep 500ms Enter Sleep 1s Type "Gum is sooo tasty" Sleep 500ms Enter Sleep 1s Type@65ms "I love bubble gum." Sleep 500ms Alt+Enter Sleep 500ms Alt+Enter Sleep 500ms Type "This commit shows how much I love chewing bubble gum!!!" Sleep 500ms Enter Sleep 1s Left@400ms 3 Sleep 1s gum-0.14.4/examples/confirm.tape000066400000000000000000000005261466436004000165240ustar00rootroot00000000000000Output confirm.gif Set Width 1000 Set Height 350 Set Shell bash Sleep 500ms Type "gum confirm && echo 'Me too!' || echo 'Me neither.'" Sleep 1s Enter Sleep 1s Right Sleep 500ms Left Sleep 500ms Enter Sleep 1.5s Ctrl+L Type "gum confirm && echo 'Me too!' || echo 'Me neither.'" Sleep 500ms Enter Sleep 500ms Right Sleep 500ms Enter Sleep 1s gum-0.14.4/examples/convert-to-gif.sh000077500000000000000000000015121466436004000174120ustar00rootroot00000000000000#!/bin/bash # This script converts some video to a GIF. It prompts the user to select an # video file with `gum filter` Set the frame rate, desired width, and max # colors to use Then, converts the video to a GIF. INPUT=$(gum filter --placeholder "Input file") FRAMERATE=$(gum input --prompt "Frame rate: " --placeholder "Frame Rate" --prompt.foreground 240 --value "50") WIDTH=$(gum input --prompt "Width: " --placeholder "Width" --prompt.foreground 240 --value "1200") MAXCOLORS=$(gum input --prompt "Max Colors: " --placeholder "Max Colors" --prompt.foreground 240 --value "256") BASENAME=$(basename "$INPUT") BASENAME="${BASENAME%%.*}" gum 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" gum-0.14.4/examples/customize.tape000066400000000000000000000005411466436004000171060ustar00rootroot00000000000000Output customize.gif Set Width 1000 Set Height 350 Set Shell bash Sleep 1s Type `gum input --cursor.foreground "#F4AC45" \` Enter Type `--prompt.foreground "#04B575" --prompt "What's up? " \` Enter Type `--placeholder "Not much, you?" --value "Not much, you?" \` Enter Type `--width 80` Enter Sleep 1s Ctrl+A Sleep 1s Ctrl+E Sleep 1s Ctrl+U Sleep 1s gum-0.14.4/examples/demo.sh000077500000000000000000000036561466436004000155060ustar00rootroot00000000000000#!/bin/bash gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "Hello, there! Welcome to $(gum style --foreground 212 'Gum')." NAME=$(gum input --placeholder "What is your name?") echo -e "Well, it is nice to meet you, $(gum style --foreground 212 "$NAME")." sleep 1; clear echo -e "Can you tell me a $(gum style --italic --foreground 99 'secret')?\n" gum write --placeholder "I'll keep it to myself, I promise!" > /dev/null # we keep the secret to ourselves clear; echo "What should I do with this information?"; sleep 1 READ="Read"; THINK="Think"; DISCARD="Discard" ACTIONS=$(gum choose --no-limit "$READ" "$THINK" "$DISCARD") clear; echo "One moment, please." grep -q "$READ" <<< "$ACTIONS" && gum spin -s line --title "Reading the secret..." -- sleep 1 grep -q "$THINK" <<< "$ACTIONS" && gum spin -s pulse --title "Thinking about your secret..." -- sleep 1 grep -q "$DISCARD" <<< "$ACTIONS" && gum spin -s monkey --title " Discarding your secret..." -- sleep 2 sleep 1; clear GUM=$(echo -e "Cherry\nGrape\nLime\nOrange" | gum filter --placeholder "Favorite flavor?") echo "I'll keep that in mind!" sleep 1; clear echo "Do you like $(gum style --foreground "#04B575" "Bubble Gum?")" sleep 1 CHOICE=$(gum choose --item.foreground 250 "Yes" "No" "It's complicated") [[ "$CHOICE" == "Yes" ]] && echo "I thought so, $(gum style --bold "Bubble Gum") is the best." || echo "I'm sorry to hear that." sleep 1 gum spin --title "Chewing some $(gum style --foreground "#04B575" "$GUM") bubble gum..." -- sleep 2.5 clear NICE_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!") CHEW_BUBBLE_GUM=$(gum style --width 17 --padding '1 3' --border double --border-foreground 212 "Go chew some $(gum style --foreground "#04B575" "$GUM") bubble gum.") gum join --horizontal "$NICE_MEETING_YOU" "$CHEW_BUBBLE_GUM" gum-0.14.4/examples/demo.tape000066400000000000000000000007511466436004000160130ustar00rootroot00000000000000Output ./demo.gif Set Shell bash Set FontSize 22 Set Width 800 Set Height 450 Type "./demo.sh" Enter Sleep 1s Type "Walter" Sleep 500ms Enter Sleep 2s Type "Nope, sorry!" Sleep 500ms Alt+Enter Sleep 200ms Alt+Enter Sleep 500ms Type "I don't trust you." Sleep 1s Enter Sleep 2s Type "x" Sleep 250ms Type "j" Sleep 250ms Type "x" Sleep 250ms Type "j" Sleep 250ms Type "x" Sleep 1s Enter Sleep 6s Type "li" Sleep 1s Enter Sleep 3s Down@500ms 2 Up@500ms 2 Sleep 1s Enter Sleep 6s gum-0.14.4/examples/diyfetch000077500000000000000000000075431466436004000157470ustar00rootroot00000000000000#!/bin/sh # ____ _____ ____ _ _ # | _ \_ _\ \ / / _| ___| |_ ___| |__ # | | | | | \ V / |_ / _ \ __/ __| '_ \ # | |_| | | | || _| __/ || (__| | | | # |____/___| |_||_| \___|\__\___|_| |_| # # About: # DIYfetch it the shell script template for writing fetch tool # utilizing `gum join` command (https://github.com/charmbracelet/gum#join). # # This script is written in POSIX-shell for portability # feel free to switch it to any scripting language that you prefer. # # Note: # When copy ANSI string from random script make sure to replace "\033" and "\e" to "" # or wrap it in `$(printf '%b' "")`. # # URL: https://github.com/info-mono/diyfetch # Prepare ------------------------------------------------------------------------------------------ # You can lookup the color codes on https://wikipedia.org/wiki/ANSI_escape_code#8-bit main_color=4 # You can add some eye candy icons with Emoji of use Nerd Fonts (https://www.nerdfonts.com). info=$(gum style "[1;38;5;${main_color}m${USER}@[1;38;5;${main_color}m$(hostname) ---------------- [1;38;5;${main_color}mOS:  [1;38;5;${main_color}mKERNEL: $(uname -sr) [1;38;5;${main_color}mUPTIME: $(uptime -p | cut -c 4-) [1;38;5;${main_color}mSHELL: $(basename "${SHELL}") [1;38;5;${main_color}mEDITOR: $(basename "${EDITOR:-}") [1;38;5;${main_color}mDE:  [1;38;5;${main_color}mWM:  [1;38;5;${main_color}mTERMINAL: ") # You can get OS arts on https://github.com/info-mono/os-ansi # copy the raw data of the .ansi file then paste it down below. art=$(gum style ' ___ (.. | (<> | / __ \ ( / \/ | _/\ __)/_) \/-____\/') # You can generate colorstrip using https://github.com/info-mono/colorstrip color=$(gum style '████████████████████████ ████████████████████████') # Display ------------------------------------------------------------------------------------------ # The code in this section is to display the fetch adaptively to the terminal's size. # If you just want a static fetch display, you can just use something like this: # # group_info_color=$(gum join --vertical "${info}" '' "${color}") # gum join --horizontal --align center ' ' "${art}" ' ' "${group_info_color}" terminal_size=$(stty size) terminal_height=${terminal_size% *} terminal_width=${terminal_size#* } # Acknowledge of how high the shell prompt is so the prompt don't push the fetch out. prompt_height=${PROMPT_HEIGHT:-1} print_test() { no_color=$(printf '%b' "${1}" | sed -e 's/\x1B\[[0-9;]*[JKmsu]//g') [ "$(printf '%s' "${no_color}" | wc --lines)" -gt $(( terminal_height - prompt_height )) ] && return 1 [ "$(printf '%s' "${no_color}" | wc --max-line-length)" -gt "${terminal_width}" ] && return 1 gum style --align center --width="${terminal_width}" "${1}" '' printf '%b' "\033[A" exit 0 } # Paper layout print_test "$(gum join --vertical --align center "${art}" '' "${info}" '' "${color}")" # Classic layout group_info_color=$(gum join --vertical "${info}" '' "${color}") print_test "$(gum join --horizontal --align center "${art}" ' ' "${group_info_color}")" # Hybrid layout group_art_info=$(gum join --horizontal --align center "${art}" ' ' "${info}") print_test "$(gum join --vertical --align center "${group_art_info}" '' "${color}")" # Other layout print_test "$(gum join --vertical --align center "${art}" '' "${info}")" print_test "${group_art_info}" print_test "${group_info_color}" print_test "${info}" exit 1gum-0.14.4/examples/fav.txt000066400000000000000000000000071466436004000155230ustar00rootroot00000000000000Banana gum-0.14.4/examples/file.tape000066400000000000000000000002271466436004000160040ustar00rootroot00000000000000Output file.gif Set Width 800 Set Height 525 Set Shell bash Type "gum file .." Enter Sleep 1s Down@150ms 6 Sleep 1s Enter Sleep 1s Type "j" Sleep 1s gum-0.14.4/examples/filter-key-value.sh000077500000000000000000000003171466436004000177360ustar00rootroot00000000000000#!/bin/bash export LIST=$(cat < gum format -t code < main.go    package main        import "fmt"        func main() {     fmt.Println("Charm_™ Gum")    }      gum-0.14.4/examples/git-branch-manager.sh000077500000000000000000000030131466436004000201730ustar00rootroot00000000000000#! /bin/sh # This script is used to manage git branches such as delete, update, and rebase # them. It prompts the user to choose the branches and the action they want to # perform. # # For an explanation on the script and tutorial on how to create it, watch: # https://www.youtube.com/watch?v=tnikefEuArQ GIT_COLOR="#f14e32" git_color_text () { gum style --foreground "$GIT_COLOR" "$1" } get_branches () { if [ ${1+x} ]; then gum choose --selected.foreground="$GIT_COLOR" --limit="$1" $(git branch --format="%(refname:short)") else gum choose --selected.foreground="$GIT_COLOR" --no-limit $(git branch --format="%(refname:short)") fi } git rev-parse --git-dir > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "$(git_color_text "!!") Must be run in a $(git_color_text "git") repo" exit 1 fi gum style \ --border normal \ --margin "1" \ --padding "1" \ --border-foreground "$GIT_COLOR" \ "$(git_color_text ' Git') Branch Manager" echo "Choose $(git_color_text 'branches') to operate on:" branches=$(get_branches) echo "" echo "Choose a $(git_color_text "command"):" command=$(gum choose --cursor.foreground="$GIT_COLOR" rebase delete update) echo "" echo $branches | tr " " "\n" | while read -r branch do case $command in rebase) base_branch=$(get_branches 1) git fetch origin git checkout "$branch" git rebase "origin/$base_branch" ;; delete) git branch -D "$branch" ;; update) git checkout "$branch" git pull --ff-only ;; esac done gum-0.14.4/examples/git-stage.sh000077500000000000000000000004171466436004000164360ustar00rootroot00000000000000#!/bin/bash ADD="Add" RESET="Reset" ACTION=$(gum choose "$ADD" "$RESET") if [ "$ACTION" == "$ADD" ]; then git status --short | cut -c 4- | gum choose --no-limit | xargs git add else git status --short | cut -c 4- | gum choose --no-limit | xargs git restore fi gum-0.14.4/examples/gum.js000066400000000000000000000005771466436004000153500ustar00rootroot00000000000000const { spawn } = require("child_process"); const activities = ["walking", "running", "cycling", "driving", "transport"]; console.log("What's your favorite activity?") const gum = spawn("gum", ["choose", ...activities]); gum.stderr.pipe(process.stderr); gum.stdout.on("data", data => { const activity = data.toString().trim(); console.log(`I like ${activity} too!`); }); gum-0.14.4/examples/gum.py000066400000000000000000000003121466436004000153470ustar00rootroot00000000000000import subprocess print("What's your favorite language?") result = subprocess.run(["gum", "choose", "Go", "Python"], stdout=subprocess.PIPE, text=True) print(f"I like {result.stdout.strip()}, too!") gum-0.14.4/examples/gum.rb000066400000000000000000000014431466436004000153300ustar00rootroot00000000000000puts 'What is your name?' name = `gum input --placeholder "Your name"`.chomp puts "Hello #{name}!" puts 'Pick your 2 favorite colors' COLORS = { 'Red' => '#FF0000', 'Blue' => '#0000FF', 'Green' => '#00FF00', 'Yellow' => '#FFFF00', 'Orange' => '#FFA500', 'Purple' => '#800080', 'Pink' => '#FF00FF' }.freeze colors = `gum choose #{COLORS.keys.join(' ')} --limit 2`.chomp.split("\n") if colors.length == 2 first = `gum style --foreground '#{COLORS[colors[0]]}' '#{colors[0]}'`.chomp second = `gum style --foreground '#{COLORS[colors[1]]}' '#{colors[1]}'`.chomp puts "You chose #{first} and #{second}." elsif colors.length == 1 first = `gum style --foreground '#{COLORS[colors[0]]}' '#{colors[0]}'`.chomp puts "You chose #{first}." else puts "You didn't pick any colors!" end gum-0.14.4/examples/input.tape000066400000000000000000000002731466436004000162250ustar00rootroot00000000000000Output input.gif Set Width 800 Set Height 250 Set Shell bash Sleep 1s Type `gum input --placeholder "What's up?"` Sleep 1s Enter Sleep 1s Type "Not much, you?" Sleep 1s Enter Sleep 1s gum-0.14.4/examples/kaomoji.sh000066400000000000000000000025251466436004000162020ustar00rootroot00000000000000#!/usr/bin/env bash # If the user passes '-h', '--help', or 'help' print out a little bit of help. # text. case "$1" in "-h" | "--help" | "help") printf 'Generate kaomojis on request.\n\n' printf 'Usage: %s [kind]\n' "$(basename "$0")" exit 1 ;; esac # The user can pass an argument like "bear" or "angry" to specify the general # kind of Kaomoji produced. sentiment="" if [[ $1 != "" ]]; then sentiment=" $1" fi # Ask mods to generate Kaomojis. Save the output in a variable. kaomoji="$(mods "generate 10${sentiment} kaomojis. number them and put each one on its own line.")" if [[ $kaomoji == "" ]]; then exit 1 fi # Pipe mods output to gum so the user can choose the perfect kaomoji. Save that # choice in a variable. Also note that we're using cut to drop the item number # in front of the Kaomoji. choice="$(echo "$kaomoji" | gum choose | cut -d ' ' -f 2)" if [[ $choice == "" ]]; then exit 1 fi # If xsel (X11) or pbcopy (macOS) exists, copy to the clipboard. If not, just # print the Kaomoji. if command -v xsel &> /dev/null; then printf '%s' "$choice" | xclip -sel clip # X11 elif command -v pbcopy &> /dev/null; then printf '%s' "$choice" | pbcopy # macOS else # We can't copy, so just print it out. printf 'Here you go: %s\n' "$choice" exit 0 fi # We're done! printf 'Copied %s to the clipboard\n' "$choice" gum-0.14.4/examples/magic.sh000077500000000000000000000025671466436004000156420ustar00rootroot00000000000000#!/bin/bash # Always ask for permission! echo "Do you want to see a magic trick?" YES="Yes, please!" NO="No, thank you!" CHOICE=$(gum choose "$YES" "$NO") if [ "$CHOICE" != "$YES" ]; then echo "Alright, then. Have a nice day!" exit 1 fi # Let the magic begin. echo "Alright, then. Let's begin!" gum style --foreground 212 "Pick a card, any card..." CARD=$(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)") SUIT=$(gum choose "Hearts (♥)" "Diamonds (♦)" "Clubs (♣)" "Spades (♠)") gum style --foreground 212 "You picked the $CARD of $SUIT." SHORT_CARD=$(echo $CARD | cut -d' ' -f2 | tr -d '()') SHORT_SUIT=$(echo $SUIT | cut -d' ' -f2 | tr -d '()') TOP_LEFT=$(gum join --vertical "$SHORT_CARD" "$SHORT_SUIT") BOTTOM_RIGHT=$(gum join --vertical "$SHORT_SUIT" "$SHORT_CARD") TOP_LEFT=$(gum style --width 10 --height 5 --align left "$TOP_LEFT") BOTTOM_RIGHT=$(gum style --width 10 --align right "$BOTTOM_RIGHT") if [[ "$SHORT_SUIT" == "♥" || "$SHORT_SUIT" == "♦" ]]; then CARD_COLOR="1" # Red else CARD_COLOR="7" # Black fi gum style --border rounded --padding "0 1" --margin 2 --border-foreground "$CARD_COLOR" --foreground "$CARD_COLOR" "$(gum join --vertical "$TOP_LEFT" "$BOTTOM_RIGHT")" echo "Is this your card?" gum choose "Omg, yes!" "Nope, sorry!" gum-0.14.4/examples/pager.tape000066400000000000000000000002431466436004000161610ustar00rootroot00000000000000Output pager.gif Set Shell bash Set Width 900 Set Height 750 Sleep 1s Type "gum pager < README.md" Enter Sleep 1.5s Down@100ms 25 Sleep 1s Up@100ms 25 Sleep 3s gum-0.14.4/examples/posix.sh000077500000000000000000000001371466436004000157130ustar00rootroot00000000000000#!/bin/sh echo "What's your favorite shell?" gum choose "Posix" "Bash" "Zsh" "Fish" "Elvish" gum-0.14.4/examples/skate.sh000077500000000000000000000003641466436004000156620ustar00rootroot00000000000000#!/bin/sh # Building a simple `skate` TUI with gum to allow you to select a database and # pick a value from skate. DATABASE=$(skate list-dbs | gum choose) skate list --keys-only "$DATABASE" | gum filter | xargs -I {} skate get {}"$DATABASE" gum-0.14.4/examples/spin.tape000066400000000000000000000002471466436004000160400ustar00rootroot00000000000000Output spin.gif Set Shell bash Set Width 1200 Set Height 300 Set FontSize 36 Sleep 500ms Type `gum spin --title "Buying Gum..." -- sleep 5` Sleep 1s Enter Sleep 4s gum-0.14.4/examples/story.txt000066400000000000000000000000551466436004000161320ustar00rootroot00000000000000Once upon a time In a land far, far away.... gum-0.14.4/examples/test.sh000077500000000000000000000040731466436004000155330ustar00rootroot00000000000000#!/bin/sh # Choose gum choose Foo Bar Baz gum choose Choose One Item --cursor "* " --cursor.foreground 99 --selected.foreground 99 gum choose Pick Two Items Maximum --limit 2 --cursor "* " --cursor-prefix "(•) " --selected-prefix "(x) " --unselected-prefix "( ) " --cursor.foreground 99 --selected.foreground 99 gum choose Unlimited Choice Of Items --no-limit --cursor "* " --cursor-prefix "(•) " --selected-prefix "(x) " --unselected-prefix "( ) " --cursor.foreground 99 --selected.foreground 99 # Confirm gum confirm "Testing?" gum confirm "No?" --default=false --affirmative "Okay." --negative "Cancel." # Filter gum filter echo {1..500} | sed 's/ /\n/g' | gum filter echo {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 # Format echo "# Header\nBody" | gum format echo 'package main\n\nimport "fmt"\n\nfunc main() {\n\tfmt.Println("Hello, Gum!")\n}\n' | gum format -t code echo ":candy:" | gum format -t emoji echo '{{ Bold "Bold" }}' | gum format -t template # Input gum input gum input --prompt "Email: " --placeholder "john@doe.com" --prompt.foreground 99 --cursor.foreground 99 --width 50 gum input --password --prompt "Password: " --placeholder "hunter2" --prompt.foreground 99 --cursor.foreground 99 --width 50 # Join gum join "Horizontal" "Join" gum join --vertical "Vertical" "Join" # Spin gum spin -- sleep 1 gum spin --spinner minidot --title "Loading..." --title.foreground 99 -- sleep 1 gum spin --show-output --spinner monkey --title "Loading..." --title.foreground 99 -- sh -c 'sleep 1; echo "Hello, Gum!"' # Style gum style --foreground 99 --border double --border-foreground 99 --padding "1 2" --margin 1 "Hello, Gum." # Write gum write gum 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 # Table gum table < table/example.csv # Pager gum pager < README.md # File gum file gum-0.14.4/examples/write.tape000066400000000000000000000004051466436004000162150ustar00rootroot00000000000000Output write.gif Set Width 800 Set Height 350 Set Shell bash Sleep 500ms Type "gum write > story.txt" Enter Sleep 1s Type "Once upon a time" Sleep 1s Alt+Enter Type "In a land far, far away...." Sleep 500ms Enter Sleep 1s Type "cat story.txt" Enter Sleep 2s gum-0.14.4/file/000077500000000000000000000000001466436004000133125ustar00rootroot00000000000000gum-0.14.4/file/command.go000066400000000000000000000024631466436004000152640ustar00rootroot00000000000000package file import ( "errors" "fmt" "path/filepath" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" ) // Run is the interface to picking a file. func (o Options) Run() error { if !o.File && !o.Directory { return errors.New("at least one between --file and --directory must be set") } if o.Path == "" { o.Path = "." } path, err := filepath.Abs(o.Path) if err != nil { return fmt.Errorf("file not found: %w", err) } theme := huh.ThemeCharm() theme.Focused.Base = lipgloss.NewStyle() theme.Focused.File = o.FileStyle.ToLipgloss() theme.Focused.Directory = o.DirectoryStyle.ToLipgloss() theme.Focused.SelectedOption = o.SelectedStyle.ToLipgloss() keymap := huh.NewDefaultKeyMap() keymap.FilePicker.Open.SetEnabled(false) // XXX: These should be file selected specific. theme.Focused.TextInput.Placeholder = o.PermissionsStyle.ToLipgloss() theme.Focused.TextInput.Prompt = o.CursorStyle.ToLipgloss() err = huh.NewForm( huh.NewGroup( huh.NewFilePicker(). Picking(true). CurrentDirectory(path). DirAllowed(o.Directory). FileAllowed(o.File). Height(o.Height). ShowHidden(o.All). Value(&path), ), ). WithShowHelp(o.ShowHelp). WithKeyMap(keymap). WithTheme(theme). Run() if err != nil { return err } fmt.Println(path) return nil } gum-0.14.4/file/options.go000066400000000000000000000043601466436004000153370ustar00rootroot00000000000000package file import ( "time" "github.com/charmbracelet/gum/style" ) // Options are the options for the file command. type Options struct { // Path is the path to the folder / directory to begin traversing. Path string `arg:"" optional:"" name:"path" help:"The path to the folder to begin traversing" env:"GUM_FILE_PATH"` // Cursor is the character to display in front of the current selected items. Cursor string `short:"c" help:"The cursor character" default:">" env:"GUM_FILE_CURSOR"` All bool `short:"a" help:"Show hidden and 'dot' files" default:"false" env:"GUM_FILE_ALL"` File bool `help:"Allow files selection" default:"true" env:"GUM_FILE_FILE"` Directory bool `help:"Allow directories selection" default:"false" env:"GUM_FILE_DIRECTORY"` ShowHelp bool `help:"Show help key binds" negatable:"" default:"true" env:"GUM_FILE_SHOW_HELP"` Height int `help:"Maximum number of files to display" default:"0" env:"GUM_FILE_HEIGHT"` CursorStyle style.Styles `embed:"" prefix:"cursor." help:"The cursor style" set:"defaultForeground=212" envprefix:"GUM_FILE_CURSOR_"` SymlinkStyle style.Styles `embed:"" prefix:"symlink." help:"The style to use for symlinks" set:"defaultForeground=36" envprefix:"GUM_FILE_SYMLINK_"` DirectoryStyle style.Styles `embed:"" prefix:"directory." help:"The style to use for directories" set:"defaultForeground=99" envprefix:"GUM_FILE_DIRECTORY_"` FileStyle style.Styles `embed:"" prefix:"file." help:"The style to use for files" envprefix:"GUM_FILE_FILE_"` PermissionsStyle style.Styles `embed:"" prefix:"permissions." help:"The style to use for permissions" set:"defaultForeground=244" envprefix:"GUM_FILE_PERMISSIONS_"` //nolint:staticcheck SelectedStyle 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 FileSizeStyle 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_"` Timeout time.Duration `help:"Timeout until command aborts without a selection" default:"0" env:"GUM_FILE_TIMEOUT"` } gum-0.14.4/filter/000077500000000000000000000000001466436004000136605ustar00rootroot00000000000000gum-0.14.4/filter/command.go000066400000000000000000000066751466436004000156430ustar00rootroot00000000000000package filter import ( "errors" "fmt" "os" "strings" "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/term" "github.com/sahilm/fuzzy" "github.com/charmbracelet/gum/internal/exit" "github.com/charmbracelet/gum/internal/files" "github.com/charmbracelet/gum/internal/stdin" ) // Run provides a shell script interface for filtering through options, powered // by the textinput bubble. func (o Options) Run() error { i := textinput.New() i.Focus() i.Prompt = o.Prompt i.PromptStyle = o.PromptStyle.ToLipgloss() i.PlaceholderStyle = o.PlaceholderStyle.ToLipgloss() i.Placeholder = o.Placeholder i.Width = o.Width v := viewport.New(o.Width, o.Height) if len(o.Options) == 0 { if input, _ := stdin.Read(); input != "" { o.Options = strings.Split(input, "\n") } else { o.Options = files.List() } } if len(o.Options) == 0 { return errors.New("no options provided, see `gum filter --help`") } if o.SelectIfOne && len(o.Options) == 1 { fmt.Println(o.Options[0]) return nil } options := []tea.ProgramOption{tea.WithOutput(os.Stderr)} if o.Height == 0 { options = append(options, tea.WithAltScreen()) } var matches []fuzzy.Match if o.Value != "" { i.SetValue(o.Value) } switch { case o.Value != "" && o.Fuzzy: matches = fuzzy.Find(o.Value, o.Options) case o.Value != "" && !o.Fuzzy: matches = exactMatches(o.Value, o.Options) default: matches = matchAll(o.Options) } if o.NoLimit { o.Limit = len(o.Options) } p := tea.NewProgram(model{ choices: o.Options, indicator: o.Indicator, matches: matches, header: o.Header, textinput: i, viewport: &v, indicatorStyle: o.IndicatorStyle.ToLipgloss(), selectedPrefixStyle: o.SelectedPrefixStyle.ToLipgloss(), selectedPrefix: o.SelectedPrefix, unselectedPrefixStyle: o.UnselectedPrefixStyle.ToLipgloss(), unselectedPrefix: o.UnselectedPrefix, matchStyle: o.MatchStyle.ToLipgloss(), headerStyle: o.HeaderStyle.ToLipgloss(), textStyle: o.TextStyle.ToLipgloss(), cursorTextStyle: o.CursorTextStyle.ToLipgloss(), height: o.Height, selected: make(map[string]struct{}), limit: o.Limit, reverse: o.Reverse, fuzzy: o.Fuzzy, timeout: o.Timeout, hasTimeout: o.Timeout > 0, sort: o.Sort, }, options...) tm, err := p.Run() if err != nil { return fmt.Errorf("unable to run filter: %w", err) } m := tm.(model) if m.aborted { return exit.ErrAborted } isTTY := term.IsTerminal(os.Stdout.Fd()) // allSelections contains values only if limit is greater // than 1 or if flag --no-limit is passed, hence there is // no need to further checks if len(m.selected) > 0 { o.checkSelected(m, isTTY) } else if len(m.matches) > m.cursor && m.cursor >= 0 { if isTTY { fmt.Println(m.matches[m.cursor].Str) } else { fmt.Println(ansi.Strip(m.matches[m.cursor].Str)) } } if !o.Strict && len(m.textinput.Value()) != 0 && len(m.matches) == 0 { fmt.Println(m.textinput.Value()) } return nil } func (o Options) checkSelected(m model, isTTY bool) { for k := range m.selected { if isTTY { fmt.Println(k) } else { fmt.Println(ansi.Strip(k)) } } } gum-0.14.4/filter/filter.go000066400000000000000000000231541466436004000155010ustar00rootroot00000000000000// Package filter provides a fuzzy searching text input to allow filtering a // list of options to select one option. // // By default it will list all the files (recursively) in the current directory // for the user to choose one, but the script (or user) can provide different // new-line separated options to choose from. // // I.e. let's pick from a list of gum flavors: // // $ cat flavors.text | gum filter package filter import ( "strings" "time" "github.com/charmbracelet/gum/timeout" "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/sahilm/fuzzy" ) type model struct { textinput textinput.Model viewport *viewport.Model choices []string matches []fuzzy.Match cursor int header string selected map[string]struct{} limit int numSelected int indicator string selectedPrefix string unselectedPrefix string height int aborted bool quitting bool headerStyle lipgloss.Style matchStyle lipgloss.Style textStyle lipgloss.Style cursorTextStyle lipgloss.Style indicatorStyle lipgloss.Style selectedPrefixStyle lipgloss.Style unselectedPrefixStyle lipgloss.Style reverse bool fuzzy bool sort bool timeout time.Duration hasTimeout bool } func (m model) Init() tea.Cmd { return timeout.Init(m.timeout, nil) } func (m model) View() string { if m.quitting { return "" } var s strings.Builder var lineTextStyle lipgloss.Style // For reverse layout, if the number of matches is less than the viewport // height, we need to offset the matches so that the first match is at the // bottom edge of the viewport instead of in the middle. if m.reverse && len(m.matches) < m.viewport.Height { s.WriteString(strings.Repeat("\n", m.viewport.Height-len(m.matches))) } // Since there are matches, display them so that the user can see, in real // time, what they are searching for. last := len(m.matches) - 1 for i := range m.matches { // For reverse layout, the matches are displayed in reverse order. if m.reverse { i = last - i } match := m.matches[i] // If this is the current selected index, we add a small indicator to // represent it. Otherwise, simply pad the string. // The line's text style is set depending on whether or not the cursor // points to this line. if i == m.cursor { s.WriteString(m.indicatorStyle.Render(m.indicator)) lineTextStyle = m.cursorTextStyle } else { s.WriteString(strings.Repeat(" ", lipgloss.Width(m.indicator))) lineTextStyle = m.textStyle } // If there are multiple selections mark them, otherwise leave an empty space if _, ok := m.selected[match.Str]; ok { s.WriteString(m.selectedPrefixStyle.Render(m.selectedPrefix)) } else if m.limit > 1 { s.WriteString(m.unselectedPrefixStyle.Render(m.unselectedPrefix)) } else { s.WriteString(" ") } // For this match, there are a certain number of characters that have // caused the match. i.e. fuzzy matching. // We should indicate to the users which characters are being matched. mi := 0 var buf strings.Builder for ci, c := range match.Str { // Check if the current character index matches the current matched // index. If so, color the character to indicate a match. if mi < len(match.MatchedIndexes) && ci == match.MatchedIndexes[mi] { // Flush text buffer. s.WriteString(lineTextStyle.Render(buf.String())) buf.Reset() s.WriteString(m.matchStyle.Render(string(c))) // We have matched this character, so we never have to check it // again. Move on to the next match. mi++ } else { // Not a match, buffer a regular character. buf.WriteRune(c) } } // Flush text buffer. s.WriteString(lineTextStyle.Render(buf.String())) // We have finished displaying the match with all of it's matched // characters highlighted and the rest filled in. // Move on to the next match. s.WriteRune('\n') } m.viewport.SetContent(s.String()) // View the input and the filtered choices header := m.headerStyle.Render(m.header) if m.reverse { view := m.viewport.View() + "\n" + m.textinput.View() if m.header != "" { return lipgloss.JoinVertical(lipgloss.Left, view, header) } return view } view := m.textinput.View() + "\n" + m.viewport.View() if m.header != "" { return lipgloss.JoinVertical(lipgloss.Left, header, view) } return view } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case timeout.TickTimeoutMsg: if msg.TimeoutValue <= 0 { m.quitting = true m.aborted = true return m, tea.Quit } m.timeout = msg.TimeoutValue return m, timeout.Tick(msg.TimeoutValue, msg.Data) case tea.WindowSizeMsg: if m.height == 0 || m.height > msg.Height { m.viewport.Height = msg.Height - lipgloss.Height(m.textinput.View()) } // Make place in the view port if header is set if m.header != "" { m.viewport.Height = m.viewport.Height - lipgloss.Height(m.headerStyle.Render(m.header)) } m.viewport.Width = msg.Width if m.reverse { m.viewport.YOffset = clamp(0, len(m.matches), len(m.matches)-m.viewport.Height) } case tea.KeyMsg: switch msg.String() { case "ctrl+c", "esc": m.aborted = true m.quitting = true return m, tea.Quit case "enter": m.quitting = true return m, tea.Quit case "ctrl+n", "ctrl+j", "down": m.CursorDown() case "ctrl+p", "ctrl+k", "up": m.CursorUp() case "tab": if m.limit == 1 { break // no op } m.ToggleSelection() m.CursorDown() case "shift+tab": if m.limit == 1 { break // no op } m.ToggleSelection() m.CursorUp() case "ctrl+@": if m.limit == 1 { break // no op } m.ToggleSelection() default: m.textinput, cmd = m.textinput.Update(msg) // yOffsetFromBottom is the number of lines from the bottom of the // list to the top of the viewport. This is used to keep the viewport // at a constant position when the number of matches are reduced // in the reverse layout. var yOffsetFromBottom int if m.reverse { yOffsetFromBottom = max(0, len(m.matches)-m.viewport.YOffset) } // A character was entered, this likely means that the text input has // changed. This suggests that the matches are outdated, so update them. if m.fuzzy { if m.sort { m.matches = fuzzy.Find(m.textinput.Value(), m.choices) } else { m.matches = fuzzy.FindNoSort(m.textinput.Value(), m.choices) } } else { m.matches = exactMatches(m.textinput.Value(), m.choices) } // If the search field is empty, let's not display the matches // (none), but rather display all possible choices. if m.textinput.Value() == "" { m.matches = matchAll(m.choices) } // For reverse layout, we need to offset the viewport so that the // it remains at a constant position relative to the cursor. if m.reverse { maxYOffset := max(0, len(m.matches)-m.viewport.Height) m.viewport.YOffset = clamp(0, maxYOffset, len(m.matches)-yOffsetFromBottom) } } } // It's possible that filtering items have caused fewer matches. So, ensure // that the selected index is within the bounds of the number of matches. m.cursor = clamp(0, len(m.matches)-1, m.cursor) return m, cmd } func (m *model) CursorUp() { if m.reverse { m.cursor = (m.cursor + 1) % len(m.matches) if len(m.matches)-m.cursor <= m.viewport.YOffset { m.viewport.LineUp(1) } if len(m.matches)-m.cursor > m.viewport.Height+m.viewport.YOffset { m.viewport.SetYOffset(len(m.matches) - m.viewport.Height) } } else { m.cursor = (m.cursor - 1 + len(m.matches)) % len(m.matches) if m.cursor < m.viewport.YOffset { m.viewport.LineUp(1) } if m.cursor >= m.viewport.YOffset+m.viewport.Height { m.viewport.SetYOffset(len(m.matches) - m.viewport.Height) } } } func (m *model) CursorDown() { if m.reverse { m.cursor = (m.cursor - 1 + len(m.matches)) % len(m.matches) if len(m.matches)-m.cursor > m.viewport.Height+m.viewport.YOffset { m.viewport.LineDown(1) } if len(m.matches)-m.cursor <= m.viewport.YOffset { m.viewport.GotoTop() } } else { m.cursor = (m.cursor + 1) % len(m.matches) if m.cursor >= m.viewport.YOffset+m.viewport.Height { m.viewport.LineDown(1) } if m.cursor < m.viewport.YOffset { m.viewport.GotoTop() } } } func (m *model) ToggleSelection() { if _, ok := m.selected[m.matches[m.cursor].Str]; ok { delete(m.selected, m.matches[m.cursor].Str) m.numSelected-- } else if m.numSelected < m.limit { m.selected[m.matches[m.cursor].Str] = struct{}{} m.numSelected++ } } func matchAll(options []string) []fuzzy.Match { matches := make([]fuzzy.Match, len(options)) for i, option := range options { matches[i] = fuzzy.Match{Str: option} } return matches } func exactMatches(search string, choices []string) []fuzzy.Match { matches := fuzzy.Matches{} for i, choice := range choices { search = strings.ToLower(search) matchedString := strings.ToLower(choice) index := strings.Index(matchedString, search) if index >= 0 { matchedIndexes := []int{} for s := range search { matchedIndexes = append(matchedIndexes, index+s) } matches = append(matches, fuzzy.Match{ Str: choice, Index: i, MatchedIndexes: matchedIndexes, }) } } return matches } //nolint:unparam func clamp(min, max, val int) int { if val < min { return min } if val > max { return max } return val } gum-0.14.4/filter/options.go000066400000000000000000000063701466436004000157100ustar00rootroot00000000000000package filter import ( "time" "github.com/charmbracelet/gum/style" ) // Options is the customization options for the filter command. type Options struct { Options []string `arg:"" optional:"" help:"Options to filter."` Indicator string `help:"Character for selection" default:"•" env:"GUM_FILTER_INDICATOR"` IndicatorStyle style.Styles `embed:"" prefix:"indicator." set:"defaultForeground=212" envprefix:"GUM_FILTER_INDICATOR_"` Limit int `help:"Maximum number of options to pick" default:"1" group:"Selection"` NoLimit bool `help:"Pick unlimited number of options (ignores limit)" group:"Selection"` SelectIfOne bool `help:"Select the given option if there is only one" group:"Selection"` Strict bool `help:"Only returns if anything matched. Otherwise return Filter" negatable:"true" default:"true" group:"Selection"` SelectedPrefix string `help:"Character to indicate selected items (hidden if limit is 1)" default:" ◉ " env:"GUM_FILTER_SELECTED_PREFIX"` SelectedPrefixStyle style.Styles `embed:"" prefix:"selected-indicator." set:"defaultForeground=212" envprefix:"GUM_FILTER_SELECTED_PREFIX_"` UnselectedPrefix string `help:"Character to indicate unselected items (hidden if limit is 1)" default:" ○ " env:"GUM_FILTER_UNSELECTED_PREFIX"` UnselectedPrefixStyle style.Styles `embed:"" prefix:"unselected-prefix." set:"defaultForeground=240" envprefix:"GUM_FILTER_UNSELECTED_PREFIX_"` HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=240" envprefix:"GUM_FILTER_HEADER_"` Header string `help:"Header value" default:"" env:"GUM_FILTER_HEADER"` TextStyle style.Styles `embed:"" prefix:"text." envprefix:"GUM_FILTER_TEXT_"` CursorTextStyle style.Styles `embed:"" prefix:"cursor-text." envprefix:"GUM_FILTER_CURSOR_TEXT_"` MatchStyle style.Styles `embed:"" prefix:"match." set:"defaultForeground=212" envprefix:"GUM_FILTER_MATCH_"` Placeholder string `help:"Placeholder value" default:"Filter..." env:"GUM_FILTER_PLACEHOLDER"` Prompt string `help:"Prompt to display" default:"> " env:"GUM_FILTER_PROMPT"` PromptStyle style.Styles `embed:"" prefix:"prompt." set:"defaultForeground=240" envprefix:"GUM_FILTER_PROMPT_"` PlaceholderStyle style.Styles `embed:"" prefix:"placeholder." set:"defaultForeground=240" envprefix:"GUM_FILTER_PLACEHOLDER_"` Width int `help:"Input width" default:"0" env:"GUM_FILTER_WIDTH"` Height int `help:"Input height" default:"0" env:"GUM_FILTER_HEIGHT"` Value string `help:"Initial filter value" default:"" env:"GUM_FILTER_VALUE"` Reverse bool `help:"Display from the bottom of the screen" env:"GUM_FILTER_REVERSE"` Fuzzy bool `help:"Enable fuzzy matching" default:"true" env:"GUM_FILTER_FUZZY" negatable:""` Sort bool `help:"Sort the results" default:"true" env:"GUM_FILTER_SORT" negatable:""` Timeout time.Duration `help:"Timeout until filter command aborts" default:"0" env:"GUM_FILTER_TIMEOUT"` } gum-0.14.4/flake.lock000066400000000000000000000027311466436004000143320ustar00rootroot00000000000000{ "nodes": { "flake-utils": { "inputs": { "systems": "systems" }, "locked": { "lastModified": 1710146030, "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { "owner": "numtide", "repo": "flake-utils", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1715447595, "narHash": "sha256-VsVAUQOj/cS1LCOmMjAGeRksXIAdPnFIjCQ0XLkCsT0=", "owner": "nixos", "repo": "nixpkgs", "rev": "062ca2a9370a27a35c524dc82d540e6e9824b652", "type": "github" }, "original": { "owner": "nixos", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" } }, "systems": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "owner": "nix-systems", "repo": "default", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default", "type": "github" } } }, "root": "root", "version": 7 } gum-0.14.4/flake.nix000066400000000000000000000010421466436004000141720ustar00rootroot00000000000000{ description = "A tool for glamorous shell scripts"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; in rec { packages.default = import ./default.nix { inherit pkgs; }; }) // { overlays.default = final: prev: { gum = import ./default.nix { pkgs = final; }; }; }; } gum-0.14.4/format/000077500000000000000000000000001466436004000136635ustar00rootroot00000000000000gum-0.14.4/format/README.md000066400000000000000000000032501466436004000151420ustar00rootroot00000000000000# Gum Format Gum format allows you to format different text into human readable output. Four different parse-able formats exist: 1. [Markdown](#markdown) 2. [Code](#code) 3. [Template](#template) 3. [Emoji](#emoji) ## Markdown Render any input as markdown text. This uses [Glamour](https://github.com/charmbracelet/glamour) behind the scenes. You can pass input as lines directly as arguments to the command invocation or pass markdown over `stdin`. ```bash gum format --type markdown < README.md # Or, directly as arguments (useful for quick lists) gum format --type markdown -- "# Gum Formats" "- Markdown" "- Code" "- Template" "- Emoji" ``` ## Code Render any code snippet with syntax highlighting. [Glamour](https://github.com/charmbracelet/glamour), which uses [Chroma](https://github.com/alecthomas/chroma) under the hood, handles styling. Similarly to the `markdown` format, `code` can take input over `stdin`. ```bash cat options.go | gum format --type code ``` ## Template Render styled input from a string template. Templating is handled by [Termenv](https://github.com/muesli/termenv). ```bash gum format --type template '{{ Bold "Tasty" }} {{ Italic "Bubble" }} {{ Color "99" "0" " Gum " }}' # Or, via stdin echo '{{ Bold "Tasty" }} {{ Italic "Bubble" }} {{ Color "99" "0" " Gum " }}' | gum format --type template ``` ## Emoji Parse and render emojis from their matching `:name:`s. Powered by [Glamour](https://github.com/charmbracelet/glamour) and [Goldmark Emoji](https://github.com/yuin/goldmark-emoji) ```bash gum format --type emoji 'I :heart: Bubble Gum :candy:' # You know the drill, also via stdin echo 'I :heart: Bubble Gum :candy:' | gum format --type emoji ``` gum-0.14.4/format/command.go000066400000000000000000000014661466436004000156370ustar00rootroot00000000000000// Package format allows you to render formatted text from the command line. // // It supports the following types: // // 1. Markdown // 2. Code // 3. Emoji // 4. Template // // For more information, see the format/README.md file. package format import ( "fmt" "strings" "github.com/charmbracelet/gum/internal/stdin" ) // Run runs the format command. func (o Options) Run() error { var input, output string var err error if len(o.Template) > 0 { input = strings.Join(o.Template, "\n") } else { input, _ = stdin.Read() } switch o.Type { case "code": output, err = code(input, o.Language) case "emoji": output, err = emoji(input) case "template": output, err = template(input) default: output, err = markdown(input, o.Theme) } if err != nil { return err } fmt.Print(output) return nil } gum-0.14.4/format/formats.go000066400000000000000000000030061466436004000156640ustar00rootroot00000000000000package format import ( "bytes" "fmt" tpl "text/template" "github.com/charmbracelet/glamour" "github.com/muesli/termenv" ) func code(input, language string) (string, error) { renderer, err := glamour.NewTermRenderer( glamour.WithAutoStyle(), glamour.WithWordWrap(0), ) if err != nil { return "", fmt.Errorf("unable to create renderer: %w", err) } output, err := renderer.Render(fmt.Sprintf("```%s\n%s\n```", language, input)) if err != nil { return "", fmt.Errorf("unable to render: %w", err) } return output, nil } func emoji(input string) (string, error) { renderer, err := glamour.NewTermRenderer( glamour.WithEmoji(), ) if err != nil { return "", fmt.Errorf("unable to create renderer: %w", err) } output, err := renderer.Render(input) if err != nil { return "", fmt.Errorf("unable to render: %w", err) } return output, nil } func markdown(input string, theme string) (string, error) { renderer, err := glamour.NewTermRenderer( glamour.WithStylePath(theme), glamour.WithWordWrap(0), ) if err != nil { return "", fmt.Errorf("unable to render: %w", err) } output, err := renderer.Render(input) if err != nil { return "", fmt.Errorf("unable to render: %w", err) } return output, nil } func template(input string) (string, error) { f := termenv.TemplateFuncs(termenv.ANSI256) t, err := tpl.New("tpl").Funcs(f).Parse(input) if err != nil { return "", fmt.Errorf("unable to parse template: %w", err) } var buf bytes.Buffer err = t.Execute(&buf, nil) return buf.String(), err } gum-0.14.4/format/options.go000066400000000000000000000011061466436004000157030ustar00rootroot00000000000000package format // Options is customization options for the format command. type Options struct { Template []string `arg:"" optional:"" help:"Template string to format (can also be provided via stdin)"` Theme string `help:"Glamour theme to use for markdown formatting" default:"pink" env:"GUM_FORMAT_THEME"` Language string `help:"Programming language to parse code" short:"l" default:"" env:"GUM_FORMAT_LANGUAGE"` Type string `help:"Format to use (markdown,template,code,emoji)" enum:"markdown,template,code,emoji" short:"t" default:"markdown" env:"GUM_FORMAT_TYPE"` } gum-0.14.4/go.mod000066400000000000000000000041101466436004000134750ustar00rootroot00000000000000module github.com/charmbracelet/gum go 1.21 require ( github.com/alecthomas/kong v0.9.0 github.com/alecthomas/mango-kong v0.1.0 github.com/charmbracelet/bubbles v0.19.0 github.com/charmbracelet/bubbletea v0.27.0 github.com/charmbracelet/glamour v0.8.0 github.com/charmbracelet/huh v0.5.3 github.com/charmbracelet/lipgloss v0.13.0 github.com/charmbracelet/log v0.4.0 github.com/charmbracelet/x/ansi v0.2.2 github.com/charmbracelet/x/term v0.2.0 github.com/muesli/reflow v0.3.0 github.com/muesli/roff v0.1.0 github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a github.com/sahilm/fuzzy v0.1.1 ) require ( github.com/alecthomas/chroma/v2 v2.14.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/catppuccin/go v0.2.0 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/dlclark/regexp2 v1.11.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/mango v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/yuin/goldmark v1.7.4 // indirect github.com/yuin/goldmark-emoji v1.0.3 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect ) gum-0.14.4/go.sum000066400000000000000000000240341466436004000135310ustar00rootroot00000000000000github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= github.com/alecthomas/mango-kong v0.1.0 h1:iFVfP1k1K4qpml3JUQmD5I8MCQYfIvsD9mRdrw7jJC4= github.com/alecthomas/mango-kong v0.1.0/go.mod h1:t+TYVdsONUolf/BwVcm+15eqcdAj15h4Qe9MMFAwwT4= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/charmbracelet/bubbles v0.19.0 h1:gKZkKXPP6GlDk6EcfujDK19PCQqRjaJZQ7QRERx1UF0= github.com/charmbracelet/bubbles v0.19.0/go.mod h1:WILteEqZ+krG5c3ntGEMeG99nCupcuIk7V0/zOP0tOA= github.com/charmbracelet/bubbletea v0.27.0 h1:Mznj+vvYuYagD9Pn2mY7fuelGvP0HAXtZYGgRBCbHvU= github.com/charmbracelet/bubbletea v0.27.0/go.mod h1:5MdP9XH6MbQkgGhnlxUqCNmBXf9I74KRQ8HIidRxV1Y= github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs= github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw= github.com/charmbracelet/huh v0.5.3 h1:3KLP4a/K1/S4dq4xFMTNMt3XWhgMl/yx8NYtygQ0bmg= github.com/charmbracelet/huh v0.5.3/go.mod h1:OZC3lshuF+/y8laj//DoZdFSHxC51OrtXLJI8xWVouQ= github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= github.com/charmbracelet/x/ansi v0.2.2 h1:BC7xzaVpfWIYZRNE8NhO9zo8KA4eGUL6L/JWXDh3GF0= github.com/charmbracelet/x/ansi v0.2.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q= github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ= github.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4= github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gum-0.14.4/gum.go000066400000000000000000000212331466436004000135130ustar00rootroot00000000000000package main import ( "github.com/alecthomas/kong" "github.com/charmbracelet/gum/choose" "github.com/charmbracelet/gum/completion" "github.com/charmbracelet/gum/confirm" "github.com/charmbracelet/gum/file" "github.com/charmbracelet/gum/filter" "github.com/charmbracelet/gum/format" "github.com/charmbracelet/gum/input" "github.com/charmbracelet/gum/join" "github.com/charmbracelet/gum/log" "github.com/charmbracelet/gum/man" "github.com/charmbracelet/gum/pager" "github.com/charmbracelet/gum/spin" "github.com/charmbracelet/gum/style" "github.com/charmbracelet/gum/table" "github.com/charmbracelet/gum/write" ) // Gum is the command-line interface for Gum. type Gum struct { // Version is a flag that can be used to display the version number. Version kong.VersionFlag `short:"v" help:"Print the version number"` // Completion generates Gum shell completion scripts. Completion completion.Completion `cmd:"" hidden:"" help:"Request shell completion"` // Man is a hidden command that generates Gum man pages. Man man.Man `cmd:"" hidden:"" help:"Generate man pages"` // Choose provides an interface to choose one option from a given list of // options. The options can be provided as (new-line separated) stdin or a // list of arguments. // // It is different from the filter command as it does not provide a fuzzy // finding input, so it is best used for smaller lists of options. // // Let's pick from a list of gum flavors: // // $ gum choose "Strawberry" "Banana" "Cherry" // Choose choose.Options `cmd:"" help:"Choose an option from a list of choices"` // Confirm provides an interface to ask a user to confirm an action. // The user is provided with an interface to choose an affirmative or // negative answer, which is then reflected in the exit code for use in // scripting. // // If the user selects the affirmative answer, the program exits with 0. // If the user selects the negative answer, the program exits with 1. // // I.e. confirm if the user wants to delete a file // // $ gum confirm "Are you sure?" && rm file.txt // Confirm confirm.Options `cmd:"" help:"Ask a user to confirm an action"` // File provides an interface to pick a file from a folder (tree). // The user is provided a file manager-like interface to navigate, to // select a file. // // Let's pick a file from the current directory: // // $ gum file // $ gum file . // // Let's pick a file from the home directory: // // $ gum file $HOME File file.Options `cmd:"" help:"Pick a file from a folder"` // Filter provides a fuzzy searching text input to allow filtering a list of // options to select one option. // // By default it will list all the files (recursively) in the current directory // for the user to choose one, but the script (or user) can provide different // new-line separated options to choose from. // // I.e. let's pick from a list of gum flavors: // // $ cat flavors.text | gum filter // Filter filter.Options `cmd:"" help:"Filter items from a list"` // Format allows you to render styled text from `markdown`, `code`, // `template` strings, or embedded `emoji` strings. // For more information see the format/README.md file. Format format.Options `cmd:"" help:"Format a string using a template"` // Input provides a shell script interface for the text input bubble. // https://github.com/charmbracelet/bubbles/tree/master/textinput // // It can be used to prompt the user for some input. The text the user // entered will be sent to stdout. // // $ gum input --placeholder "What's your favorite gum?" > answer.text // Input input.Options `cmd:"" help:"Prompt for some input"` // Join provides a shell script interface for the lipgloss JoinHorizontal // and JoinVertical commands. It allows you to join multi-line text to // build different layouts. // // For example, you can place two bordered boxes next to each other: // Note: We wrap the variable in quotes to ensure the new lines are part of a // single argument. Otherwise, the command won't work as expected. // // $ gum join --horizontal "$BUBBLE_BOX" "$GUM_BOX" // // ╔══════════════════════╗╔═════════════╗ // ║ ║║ ║ // ║ Bubble ║║ Gum ║ // ║ ║║ ║ // ╚══════════════════════╝╚═════════════╝ // Join join.Options `cmd:"" help:"Join text vertically or horizontally"` // Pager provides a shell script interface for the viewport bubble. // https://github.com/charmbracelet/bubbles/tree/master/viewport // // It allows the user to scroll through content like a pager. // // ╭────────────────────────────────────────────────╮ // │ 1 │ Gum Pager │ // │ 2 │ ========= │ // │ 3 │ │ // │ 4 │ ``` │ // │ 5 │ gum pager --height 10 --width 25 < text │ // │ 6 │ ``` │ // │ 7 │ │ // │ 8 │ │ // ╰────────────────────────────────────────────────╯ // ↑/↓: Navigate • q: Quit // Pager pager.Options `cmd:"" help:"Scroll through a file"` // Spin provides a shell script interface for the spinner bubble. // https://github.com/charmbracelet/bubbles/tree/master/spinner // // It is useful for displaying that some task is running in the background // while consuming it's output so that it is not shown to the user. // // For example, let's do a long running task: $ sleep 5 // // We can simply prepend a spinner to this task to show it to the user, // while performing the task / command in the background. // // $ gum spin -t "Taking a nap..." -- sleep 5 // // The spinner will automatically exit when the task is complete. // Spin spin.Options `cmd:"" help:"Display spinner while running a command"` // Style provides a shell script interface for Lip Gloss. // https://github.com/charmbracelet/lipgloss // // It allows you to use Lip Gloss to style text without needing to use Go. // All of the styling options are available as flags. // // Let's make some text glamorous using bash: // // $ gum style \ // --foreground 212 --border double --align center \ // --width 50 --margin 2 --padding "2 4" \ // "Bubble Gum (1¢)" "So sweet and so fresh\!" // // // ╔══════════════════════════════════════════════════╗ // ║ ║ // ║ ║ // ║ Bubble Gum (1¢) ║ // ║ So sweet and so fresh! ║ // ║ ║ // ║ ║ // ╚══════════════════════════════════════════════════╝ // Style style.Options `cmd:"" help:"Apply coloring, borders, spacing to text"` // Table provides a shell script interface for the table bubble. // https://github.com/charmbracelet/bubbles/tree/master/table // // It is useful to render tabular (CSV) data in a terminal and allows // the user to select a row from the table. // // Let's render a table of gum flavors: // // $ gum table <<< "Flavor,Price\nStrawberry,$0.50\nBanana,$0.99\nCherry,$0.75" // // Flavor Price // Strawberry $0.50 // Banana $0.99 // Cherry $0.75 // Table table.Options `cmd:"" help:"Render a table of data"` // Write provides a shell script interface for the text area bubble. // https://github.com/charmbracelet/bubbles/tree/master/textarea // // It can be used to ask the user to write some long form of text // (multi-line) input. The text the user entered will be sent to stdout. // // $ gum write > output.text // Write write.Options `cmd:"" help:"Prompt for long-form text"` // Log provides a shell script interface for logging using Log. // https://github.com/charmbracelet/log // // It can be used to log messages to output. // // $ gum log --level info "Hello, world!" // Log log.Options `cmd:"" help:"Log messages to output"` } gum-0.14.4/input/000077500000000000000000000000001466436004000135325ustar00rootroot00000000000000gum-0.14.4/input/command.go000066400000000000000000000027351466436004000155060ustar00rootroot00000000000000package input import ( "fmt" "os" "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/gum/internal/stdin" ) // Run provides a shell script interface for the text input bubble. // https://github.com/charmbracelet/bubbles/textinput func (o Options) Run() error { var value string if o.Value != "" { value = o.Value } else if in, _ := stdin.Read(); in != "" { value = in } theme := huh.ThemeCharm() theme.Focused.Base = lipgloss.NewStyle() theme.Focused.TextInput.Cursor = o.CursorStyle.ToLipgloss() theme.Focused.TextInput.Placeholder = o.PlaceholderStyle.ToLipgloss() theme.Focused.TextInput.Prompt = o.PromptStyle.ToLipgloss() theme.Focused.Title = o.HeaderStyle.ToLipgloss() // Keep input keymap backwards compatible keymap := huh.NewDefaultKeyMap() keymap.Quit = key.NewBinding(key.WithKeys("ctrl+c", "esc")) echoMode := huh.EchoModeNormal if o.Password { echoMode = huh.EchoModePassword } err := huh.NewForm( huh.NewGroup( huh.NewInput(). Prompt(o.Prompt). Placeholder(o.Placeholder). CharLimit(o.CharLimit). EchoMode(echoMode). Title(o.Header). Value(&value), ), ). WithShowHelp(false). WithWidth(o.Width). WithTheme(theme). WithKeyMap(keymap). WithShowHelp(o.ShowHelp). WithProgramOptions(tea.WithOutput(os.Stderr)). Run() if err != nil { return err } fmt.Println(value) return nil } gum-0.14.4/input/options.go000066400000000000000000000031721466436004000155570ustar00rootroot00000000000000package input import ( "time" "github.com/charmbracelet/gum/style" ) // Options are the customization options for the input. type Options struct { Placeholder string `help:"Placeholder value" default:"Type something..." env:"GUM_INPUT_PLACEHOLDER"` Prompt string `help:"Prompt to display" default:"> " env:"GUM_INPUT_PROMPT"` PromptStyle style.Styles `embed:"" prefix:"prompt." envprefix:"GUM_INPUT_PROMPT_"` PlaceholderStyle style.Styles `embed:"" prefix:"placeholder." set:"defaultForeground=240" envprefix:"GUM_INPUT_PLACEHOLDER_"` CursorStyle style.Styles `embed:"" prefix:"cursor." set:"defaultForeground=212" envprefix:"GUM_INPUT_CURSOR_"` CursorMode string `prefix:"cursor." name:"mode" help:"Cursor mode" default:"blink" enum:"blink,hide,static" env:"GUM_INPUT_CURSOR_MODE"` Value string `help:"Initial value (can also be passed via stdin)" default:""` CharLimit int `help:"Maximum value length (0 for no limit)" default:"400"` Width int `help:"Input width (0 for terminal width)" default:"0" env:"GUM_INPUT_WIDTH"` Password bool `help:"Mask input characters" default:"false"` ShowHelp bool `help:"Show help keybinds" default:"true" negatable:"true" env:"GUM_INPUT_SHOW_HELP"` Header string `help:"Header value" default:"" env:"GUM_INPUT_HEADER"` HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=240" envprefix:"GUM_INPUT_HEADER_"` Timeout time.Duration `help:"Timeout until input aborts" default:"0" env:"GUM_INPUT_TIMEOUT"` } gum-0.14.4/internal/000077500000000000000000000000001466436004000142075ustar00rootroot00000000000000gum-0.14.4/internal/decode/000077500000000000000000000000001466436004000154325ustar00rootroot00000000000000gum-0.14.4/internal/decode/align.go000066400000000000000000000004351466436004000170550ustar00rootroot00000000000000package decode import "github.com/charmbracelet/lipgloss" // Align maps strings to `lipgloss.Position`s. var Align = map[string]lipgloss.Position{ "center": lipgloss.Center, "left": lipgloss.Left, "top": lipgloss.Top, "bottom": lipgloss.Bottom, "right": lipgloss.Right, } gum-0.14.4/internal/exit/000077500000000000000000000000001466436004000151605ustar00rootroot00000000000000gum-0.14.4/internal/exit/exit.go000066400000000000000000000003461466436004000164630ustar00rootroot00000000000000package exit import "fmt" // StatusAborted is the exit code for aborted commands. const StatusAborted = 130 // ErrAborted is the error to return when a gum command is aborted by Ctrl + C. var ErrAborted = fmt.Errorf("aborted") gum-0.14.4/internal/files/000077500000000000000000000000001466436004000153115ustar00rootroot00000000000000gum-0.14.4/internal/files/files.go000066400000000000000000000013131466436004000167400ustar00rootroot00000000000000package files import ( "os" "path/filepath" "strings" ) // List returns a list of all files in the current directory. // It ignores the .git directory. func List() []string { var files []string err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { if shouldIgnore(path) || info.IsDir() || err != nil { return nil //nolint:nilerr } files = append(files, path) return nil }) if err != nil { return []string{} } return files } var defaultIgnorePatterns = []string{"node_modules", ".git", "."} func shouldIgnore(path string) bool { for _, prefix := range defaultIgnorePatterns { if strings.HasPrefix(path, prefix) { return true } } return false } gum-0.14.4/internal/log/000077500000000000000000000000001466436004000147705ustar00rootroot00000000000000gum-0.14.4/internal/log/log.go000066400000000000000000000002101466436004000160710ustar00rootroot00000000000000package log import "fmt" // Error prints an error message to the user. func Error(message string) { fmt.Println("Error:", message) } gum-0.14.4/internal/stack/000077500000000000000000000000001466436004000153145ustar00rootroot00000000000000gum-0.14.4/internal/stack/stack.go000066400000000000000000000007111466436004000167470ustar00rootroot00000000000000package stack // Stack is a stack interface for integers. type Stack struct { Push func(int) Pop func() int Length func() int } // NewStack returns a new stack of integers. func NewStack() Stack { slice := make([]int, 0) return Stack{ Push: func(i int) { slice = append(slice, i) }, Pop: func() int { res := slice[len(slice)-1] slice = slice[:len(slice)-1] return res }, Length: func() int { return len(slice) }, } } gum-0.14.4/internal/stdin/000077500000000000000000000000001466436004000153305ustar00rootroot00000000000000gum-0.14.4/internal/stdin/stdin.go000066400000000000000000000013361466436004000170030ustar00rootroot00000000000000package stdin import ( "bufio" "fmt" "io" "os" "strings" ) // Read reads input from an stdin pipe. func Read() (string, error) { if IsEmpty() { return "", fmt.Errorf("stdin is empty") } reader := bufio.NewReader(os.Stdin) var b strings.Builder for { r, _, err := reader.ReadRune() if err != nil && err == io.EOF { break } _, err = b.WriteRune(r) if err != nil { return "", fmt.Errorf("failed to write rune: %w", err) } } return strings.TrimSuffix(b.String(), "\n"), nil } // IsEmpty returns whether stdin is empty. func IsEmpty() bool { stat, err := os.Stdin.Stat() if err != nil { return true } if stat.Mode()&os.ModeNamedPipe == 0 && stat.Size() == 0 { return true } return false } gum-0.14.4/internal/utils/000077500000000000000000000000001466436004000153475ustar00rootroot00000000000000gum-0.14.4/internal/utils/utils.go000066400000000000000000000005261466436004000170410ustar00rootroot00000000000000package utils import ( "strings" "github.com/charmbracelet/lipgloss" ) // LipglossPadding calculates how much padding a string is given by a style. func LipglossPadding(style lipgloss.Style) (int, int) { render := style.Render(" ") before := strings.Index(render, " ") after := len(render) - len(" ") - before return before, after } gum-0.14.4/join/000077500000000000000000000000001466436004000133325ustar00rootroot00000000000000gum-0.14.4/join/command.go000066400000000000000000000023201466436004000152740ustar00rootroot00000000000000// Package join provides a shell script interface for the lipgloss // JoinHorizontal and JoinVertical commands. It allows you to join multi-line // text to build different layouts. // // For example, you can place two bordered boxes next to each other: Note: We // wrap the variable in quotes to ensure the new lines are part of a single // argument. Otherwise, the command won't work as expected. // // $ gum join --horizontal "$BUBBLE_BOX" "$GUM_BOX" // // ╔══════════════════════╗╔═════════════╗ // ║ ║║ ║ // ║ Bubble ║║ Gum ║ // ║ ║║ ║ // ╚══════════════════════╝╚═════════════╝ package join import ( "fmt" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/gum/internal/decode" ) // Run is the command-line interface for the joining strings through lipgloss. func (o Options) Run() error { join := lipgloss.JoinHorizontal if o.Vertical { join = lipgloss.JoinVertical } fmt.Println(join(decode.Align[o.Align], o.Text...)) return nil } gum-0.14.4/join/options.go000066400000000000000000000006211466436004000153530ustar00rootroot00000000000000package join // Options is the set of options that can configure a join. type Options struct { Text []string `arg:"" help:"Text to join."` Align string `help:"Text alignment" enum:"left,center,right,bottom,middle,top" default:"left"` Horizontal bool `help:"Join (potentially multi-line) strings horizontally"` Vertical bool `help:"Join (potentially multi-line) strings vertically"` } gum-0.14.4/log/000077500000000000000000000000001466436004000131545ustar00rootroot00000000000000gum-0.14.4/log/command.go000066400000000000000000000062001466436004000151170ustar00rootroot00000000000000package log import ( "fmt" "math" "os" "strings" "time" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/log" ) // Run is the command-line interface for logging text. func (o Options) Run() error { l := log.New(os.Stderr) if o.File != "" { f, err := os.OpenFile(o.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm) if err != nil { return fmt.Errorf("error opening file: %w", err) } defer f.Close() //nolint:errcheck l.SetOutput(f) } l.SetPrefix(o.Prefix) l.SetLevel(-math.MaxInt32) // log all levels l.SetReportTimestamp(o.Time != "") timeFormats := map[string]string{ "layout": time.Layout, "ansic": time.ANSIC, "unixdate": time.UnixDate, "rubydate": time.RubyDate, "rfc822": time.RFC822, "rfc822z": time.RFC822Z, "rfc850": time.RFC850, "rfc1123": time.RFC1123, "rfc1123z": time.RFC1123Z, "rfc3339": time.RFC3339, "rfc3339nano": time.RFC3339Nano, "kitchen": time.Kitchen, "stamp": time.Stamp, "stampmilli": time.StampMilli, "stampmicro": time.StampMicro, "stampnano": time.StampNano, "datetime": time.DateTime, "dateonly": time.DateOnly, "timeonly": time.TimeOnly, } tf, ok := timeFormats[strings.ToLower(o.Time)] if ok { l.SetTimeFormat(tf) } else { l.SetTimeFormat(o.Time) } st := log.DefaultStyles() lvl := levelToLog[o.Level] lvlStyle := o.LevelStyle.ToLipgloss() if lvlStyle.GetForeground() == lipgloss.Color("") { lvlStyle = lvlStyle.Foreground(st.Levels[lvl].GetForeground()) } st.Levels[lvl] = lvlStyle. SetString(strings.ToUpper(lvl.String())). Inline(true) st.Timestamp = o.TimeStyle.ToLipgloss(). Inline(true) st.Prefix = o.PrefixStyle.ToLipgloss(). Inline(true) st.Message = o.MessageStyle.ToLipgloss(). Inline(true) st.Key = o.KeyStyle.ToLipgloss(). Inline(true) st.Value = o.ValueStyle.ToLipgloss(). Inline(true) st.Separator = o.SeparatorStyle.ToLipgloss(). Inline(true) l.SetStyles(st) switch o.Formatter { case "json": l.SetFormatter(log.JSONFormatter) case "logfmt": l.SetFormatter(log.LogfmtFormatter) case "text": l.SetFormatter(log.TextFormatter) } var arg0 string var args []interface{} if len(o.Text) > 0 { arg0 = o.Text[0] } if len(o.Text) > 1 { args = make([]interface{}, len(o.Text[1:])) for i, arg := range o.Text[1:] { args[i] = arg } } logger := map[string]logger{ "none": {printf: l.Printf, print: l.Print}, "debug": {printf: l.Debugf, print: l.Debug}, "info": {printf: l.Infof, print: l.Info}, "warn": {printf: l.Warnf, print: l.Warn}, "error": {printf: l.Errorf, print: l.Error}, "fatal": {printf: l.Fatalf, print: l.Fatal}, }[o.Level] if o.Format { logger.printf(arg0, args...) } else if o.Structured { logger.print(arg0, args...) } else { logger.print(strings.Join(o.Text, " ")) } return nil } type logger struct { printf func(string, ...interface{}) print func(interface{}, ...interface{}) } var levelToLog = map[string]log.Level{ "none": log.Level(math.MaxInt32), "debug": log.DebugLevel, "info": log.InfoLevel, "warn": log.WarnLevel, "error": log.ErrorLevel, "fatal": log.FatalLevel, } gum-0.14.4/log/options.go000066400000000000000000000033551466436004000152040ustar00rootroot00000000000000package log import ( "github.com/charmbracelet/gum/style" ) // Options is the set of options that can configure a join. type Options struct { Text []string `arg:"" help:"Text to log"` File string `short:"o" help:"Log to file"` Format bool `short:"f" help:"Format message using printf" xor:"format,structured"` Formatter string `help:"The log formatter to use" enum:"json,logfmt,text" default:"text"` Level string `short:"l" help:"The log level to use" enum:"none,debug,info,warn,error,fatal" default:"none"` Prefix string `help:"Prefix to print before the message"` Structured bool `short:"s" help:"Use structured logging" xor:"format,structured"` Time string `short:"t" help:"The time format to use (kitchen, layout, ansic, rfc822, etc...)" default:""` LevelStyle style.Styles `embed:"" prefix:"level." help:"The style of the level being used" set:"defaultBold=true" envprefix:"GUM_LOG_LEVEL_"` //nolint:staticcheck TimeStyle style.Styles `embed:"" prefix:"time." help:"The style of the time" envprefix:"GUM_LOG_TIME_"` PrefixStyle style.Styles `embed:"" prefix:"prefix." help:"The style of the prefix" set:"defaultBold=true" set:"defaultFaint=true" envprefix:"GUM_LOG_PREFIX_"` //nolint:staticcheck MessageStyle style.Styles `embed:"" prefix:"message." help:"The style of the message" envprefix:"GUM_LOG_MESSAGE_"` KeyStyle style.Styles `embed:"" prefix:"key." help:"The style of the key" set:"defaultFaint=true" envprefix:"GUM_LOG_KEY_"` ValueStyle style.Styles `embed:"" prefix:"value." help:"The style of the value" envprefix:"GUM_LOG_VALUE_"` SeparatorStyle style.Styles `embed:"" prefix:"separator." help:"The style of the separator" set:"defaultFaint=true" envprefix:"GUM_LOG_SEPARATOR_"` } gum-0.14.4/main.go000066400000000000000000000040401466436004000136440ustar00rootroot00000000000000package main import ( "errors" "fmt" "os" "runtime/debug" "github.com/alecthomas/kong" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" "github.com/muesli/termenv" "github.com/charmbracelet/gum/internal/exit" ) const shaLen = 7 var ( // Version contains the application version number. It's set via ldflags // when building. Version = "" // CommitSHA contains the SHA of the commit that this application was built // against. It's set via ldflags when building. CommitSHA = "" ) var bubbleGumPink = lipgloss.NewStyle().Foreground(lipgloss.Color("212")) func main() { lipgloss.SetColorProfile(termenv.NewOutput(os.Stderr).Profile) if Version == "" { if info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != "" { Version = info.Main.Version } else { Version = "unknown (built from source)" } } version := fmt.Sprintf("gum version %s", Version) if len(CommitSHA) >= shaLen { version += " (" + CommitSHA[:shaLen] + ")" } gum := &Gum{} ctx := kong.Parse( gum, kong.Description(fmt.Sprintf("A tool for %s shell scripts.", bubbleGumPink.Render("glamorous"))), kong.UsageOnError(), kong.ConfigureHelp(kong.HelpOptions{ Compact: true, Summary: false, NoExpandSubcommands: true, }), kong.Vars{ "version": version, "defaultHeight": "0", "defaultWidth": "0", "defaultAlign": "left", "defaultBorder": "none", "defaultBorderForeground": "", "defaultBorderBackground": "", "defaultBackground": "", "defaultForeground": "", "defaultMargin": "0 0", "defaultPadding": "0 0", "defaultUnderline": "false", "defaultBold": "false", "defaultFaint": "false", "defaultItalic": "false", "defaultStrikethrough": "false", }, ) if err := ctx.Run(); err != nil { if errors.Is(err, exit.ErrAborted) || errors.Is(err, huh.ErrUserAborted) { os.Exit(exit.StatusAborted) } fmt.Println(err) os.Exit(1) } } gum-0.14.4/man/000077500000000000000000000000001466436004000131465ustar00rootroot00000000000000gum-0.14.4/man/command.go000066400000000000000000000012471466436004000151170ustar00rootroot00000000000000package man import ( "fmt" "github.com/alecthomas/kong" mangokong "github.com/alecthomas/mango-kong" "github.com/muesli/roff" ) // Man is a gum sub-command that generates man pages. type Man struct{} // BeforeApply implements Kong BeforeApply hook. func (m Man) BeforeApply(ctx *kong.Context) error { // Set the correct man pages description without color escape sequences. ctx.Model.Help = "A tool for glamorous shell scripts." man := mangokong.NewManPage(1, ctx.Model) man = man.WithSection("Copyright", "(c) 2022-2024 Charmbracelet, Inc.\n"+ "Released under MIT license.") _, _ = fmt.Fprint(ctx.Stdout, man.Build(roff.NewDocument())) ctx.Exit(0) return nil } gum-0.14.4/pager/000077500000000000000000000000001466436004000134715ustar00rootroot00000000000000gum-0.14.4/pager/command.go000066400000000000000000000026131466436004000154400ustar00rootroot00000000000000package pager import ( "fmt" "regexp" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/gum/internal/stdin" ) // Run provides a shell script interface for the viewport bubble. // https://github.com/charmbracelet/bubbles/viewport func (o Options) Run() error { vp := viewport.New(o.Style.Width, o.Style.Height) vp.Style = o.Style.ToLipgloss() if o.Content == "" { stdin, err := stdin.Read() if err != nil { return fmt.Errorf("unable to read stdin") } if stdin != "" { // Sanitize the input from stdin by removing backspace sequences. backspace := regexp.MustCompile(".\x08") o.Content = backspace.ReplaceAllString(stdin, "") } else { return fmt.Errorf("provide some content to display") } } model := model{ viewport: vp, helpStyle: o.HelpStyle.ToLipgloss(), content: o.Content, origContent: o.Content, showLineNumbers: o.ShowLineNumbers, lineNumberStyle: o.LineNumberStyle.ToLipgloss(), softWrap: o.SoftWrap, matchStyle: o.MatchStyle.ToLipgloss(), matchHighlightStyle: o.MatchHighlightStyle.ToLipgloss(), timeout: o.Timeout, hasTimeout: o.Timeout > 0, } _, err := tea.NewProgram(model, tea.WithAltScreen()).Run() if err != nil { return fmt.Errorf("unable to start program: %w", err) } return nil } gum-0.14.4/pager/options.go000066400000000000000000000027201466436004000155140ustar00rootroot00000000000000package pager import ( "time" "github.com/charmbracelet/gum/style" ) // Options are the options for the pager. type Options struct { //nolint:staticcheck Style style.Styles `embed:"" help:"Style the pager" set:"defaultBorder=rounded" set:"defaultPadding=0 1" set:"defaultBorderForeground=212" envprefix:"GUM_PAGER_"` HelpStyle style.Styles `embed:"" prefix:"help." help:"Style the help text" set:"defaultForeground=241" envprefix:"GUM_PAGER_HELP_"` Content string `arg:"" optional:"" help:"Display content to scroll"` ShowLineNumbers bool `help:"Show line numbers" default:"true"` LineNumberStyle style.Styles `embed:"" prefix:"line-number." help:"Style the line numbers" set:"defaultForeground=237" envprefix:"GUM_PAGER_LINE_NUMBER_"` SoftWrap bool `help:"Soft wrap lines" default:"false"` MatchStyle style.Styles `embed:"" prefix:"match." help:"Style the matched text" set:"defaultForeground=212" set:"defaultBold=true" envprefix:"GUM_PAGER_MATCH_"` //nolint:staticcheck MatchHighlightStyle 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 Timeout time.Duration `help:"Timeout until command exits" default:"0" env:"GUM_PAGER_TIMEOUT"` } gum-0.14.4/pager/pager.go000066400000000000000000000104151466436004000151170ustar00rootroot00000000000000// Package pager provides a pager (similar to less) for the terminal. // // $ cat file.txt | gum pager package pager import ( "fmt" "strings" "time" "github.com/charmbracelet/gum/timeout" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/muesli/reflow/truncate" ) type model struct { content string origContent string viewport viewport.Model helpStyle lipgloss.Style showLineNumbers bool lineNumberStyle lipgloss.Style softWrap bool search search matchStyle lipgloss.Style matchHighlightStyle lipgloss.Style maxWidth int timeout time.Duration hasTimeout bool } func (m model) Init() tea.Cmd { return timeout.Init(m.timeout, nil) } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case timeout.TickTimeoutMsg: if msg.TimeoutValue <= 0 { return m, tea.Quit } m.timeout = msg.TimeoutValue return m, timeout.Tick(msg.TimeoutValue, msg.Data) case tea.WindowSizeMsg: m.ProcessText(msg) case tea.KeyMsg: return m.KeyHandler(msg) } return m, nil } func (m *model) ProcessText(msg tea.WindowSizeMsg) { m.viewport.Height = msg.Height - lipgloss.Height(m.helpStyle.Render("?")) - 1 m.viewport.Width = msg.Width textStyle := lipgloss.NewStyle().Width(m.viewport.Width) var text strings.Builder // Determine max width of a line. m.maxWidth = m.viewport.Width if m.softWrap { vpStyle := m.viewport.Style m.maxWidth -= vpStyle.GetHorizontalBorderSize() + vpStyle.GetHorizontalMargins() + vpStyle.GetHorizontalPadding() if m.showLineNumbers { m.maxWidth -= lipgloss.Width(" │ ") } } for i, line := range strings.Split(m.content, "\n") { line = strings.ReplaceAll(line, "\t", " ") if m.showLineNumbers { text.WriteString(m.lineNumberStyle.Render(fmt.Sprintf("%4d │ ", i+1))) } for m.softWrap && lipgloss.Width(line) > m.maxWidth { truncatedLine := truncate.String(line, uint(m.maxWidth)) text.WriteString(textStyle.Render(truncatedLine)) text.WriteString("\n") if m.showLineNumbers { text.WriteString(m.lineNumberStyle.Render(" │ ")) } line = strings.Replace(line, truncatedLine, "", 1) } text.WriteString(textStyle.Render(truncate.String(line, uint(m.maxWidth)))) text.WriteString("\n") } diffHeight := m.viewport.Height - lipgloss.Height(text.String()) if diffHeight > 0 && m.showLineNumbers { remainingLines := " ~ │ " + strings.Repeat("\n ~ │ ", diffHeight-1) text.WriteString(m.lineNumberStyle.Render(remainingLines)) } m.viewport.SetContent(text.String()) } const heightOffset = 2 func (m model) KeyHandler(key tea.KeyMsg) (model, func() tea.Msg) { var cmd tea.Cmd if m.search.active { switch key.String() { case "enter": if m.search.input.Value() != "" { m.content = m.origContent m.search.Execute(&m) // Trigger a view update to highlight the found matches. m.search.NextMatch(&m) m.ProcessText(tea.WindowSizeMsg{Height: m.viewport.Height + heightOffset, Width: m.viewport.Width}) } else { m.search.Done() } case "ctrl+d", "ctrl+c", "esc": m.search.Done() default: m.search.input, cmd = m.search.input.Update(key) } } else { switch key.String() { case "g", "home": m.viewport.GotoTop() case "G", "end": m.viewport.GotoBottom() case "/": m.search.Begin() case "p", "N": m.search.PrevMatch(&m) m.ProcessText(tea.WindowSizeMsg{Height: m.viewport.Height + heightOffset, Width: m.viewport.Width}) case "n": m.search.NextMatch(&m) m.ProcessText(tea.WindowSizeMsg{Height: m.viewport.Height + heightOffset, Width: m.viewport.Width}) case "q", "ctrl+c", "esc": return m, tea.Quit } m.viewport, cmd = m.viewport.Update(key) } return m, cmd } func (m model) View() string { var timeoutStr string if m.hasTimeout { timeoutStr = timeout.Str(m.timeout) + " " } helpMsg := "\n" + timeoutStr + " ↑/↓: Navigate • q: Quit • /: Search " if m.search.query != nil { helpMsg += "• n: Next Match " helpMsg += "• N: Prev Match " } if m.search.active { return m.viewport.View() + "\n" + timeoutStr + " " + m.search.input.View() } return m.viewport.View() + m.helpStyle.Render(helpMsg) } gum-0.14.4/pager/search.go000066400000000000000000000107341466436004000152720ustar00rootroot00000000000000package pager import ( "fmt" "regexp" "strings" "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/gum/internal/utils" "github.com/charmbracelet/lipgloss" "github.com/muesli/reflow/truncate" ) type search struct { active bool input textinput.Model query *regexp.Regexp matchIndex int matchLipglossStr string matchString string } func (s *search) new() { input := textinput.New() input.Placeholder = "search" input.Prompt = "/" input.PromptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240")) s.input = input } func (s *search) Begin() { s.new() s.active = true s.input.Focus() } // Execute find all lines in the model with a match. func (s *search) Execute(m *model) { defer s.Done() if s.input.Value() == "" { s.query = nil return } var err error s.query, err = regexp.Compile(s.input.Value()) if err != nil { s.query = nil return } query := regexp.MustCompile(fmt.Sprintf("(%s)", s.query.String())) m.content = query.ReplaceAllString(m.content, m.matchStyle.Render("$1")) // Recompile the regex to match the an replace the highlights. leftPad, _ := utils.LipglossPadding(m.matchStyle) matchingString := regexp.QuoteMeta(m.matchStyle.Render()[:leftPad]) + s.query.String() + regexp.QuoteMeta(m.matchStyle.Render()[leftPad:]) s.query, err = regexp.Compile(matchingString) if err != nil { s.query = nil } } func (s *search) Done() { s.active = false // To account for the first match is always executed. s.matchIndex = -1 } func (s *search) NextMatch(m *model) { // Check that we are within bounds. if s.query == nil { return } // Remove previous highlight. m.content = strings.Replace(m.content, s.matchLipglossStr, s.matchString, 1) // Highlight the next match. allMatches := s.query.FindAllStringIndex(m.content, -1) if len(allMatches) == 0 { return } leftPad, rightPad := utils.LipglossPadding(m.matchStyle) s.matchIndex = (s.matchIndex + 1) % len(allMatches) match := allMatches[s.matchIndex] lhs := m.content[:match[0]] rhs := m.content[match[0]:] s.matchString = m.content[match[0]:match[1]] s.matchLipglossStr = m.matchHighlightStyle.Render(s.matchString[leftPad : len(s.matchString)-rightPad]) m.content = lhs + strings.Replace(rhs, m.content[match[0]:match[1]], s.matchLipglossStr, 1) // Update the viewport position. var line int formatStr := softWrapEm(m.content, m.maxWidth, m.softWrap) index := strings.Index(formatStr, s.matchLipglossStr) if index != -1 { line = strings.Count(formatStr[:index], "\n") } // Only update if the match is not within the viewport. if index != -1 && (line > m.viewport.YOffset-1+m.viewport.VisibleLineCount()-1 || line < m.viewport.YOffset) { m.viewport.SetYOffset(line) } } func (s *search) PrevMatch(m *model) { // Check that we are within bounds. if s.query == nil { return } // Remove previous highlight. m.content = strings.Replace(m.content, s.matchLipglossStr, s.matchString, 1) // Highlight the previous match. allMatches := s.query.FindAllStringIndex(m.content, -1) if len(allMatches) == 0 { return } s.matchIndex = (s.matchIndex - 1) % len(allMatches) if s.matchIndex < 0 { s.matchIndex = len(allMatches) - 1 } leftPad, rightPad := utils.LipglossPadding(m.matchStyle) match := allMatches[s.matchIndex] lhs := m.content[:match[0]] rhs := m.content[match[0]:] s.matchString = m.content[match[0]:match[1]] s.matchLipglossStr = m.matchHighlightStyle.Render(s.matchString[leftPad : len(s.matchString)-rightPad]) m.content = lhs + strings.Replace(rhs, m.content[match[0]:match[1]], s.matchLipglossStr, 1) // Update the viewport position. var line int formatStr := softWrapEm(m.content, m.maxWidth, m.softWrap) index := strings.Index(formatStr, s.matchLipglossStr) if index != -1 { line = strings.Count(formatStr[:index], "\n") } // Only update if the match is not within the viewport. if index != -1 && (line > m.viewport.YOffset-1+m.viewport.VisibleLineCount()-1 || line < m.viewport.YOffset) { m.viewport.SetYOffset(line) } } func softWrapEm(str string, maxWidth int, softWrap bool) string { var text strings.Builder for _, line := range strings.Split(str, "\n") { for softWrap && lipgloss.Width(line) > maxWidth { truncatedLine := truncate.String(line, uint(maxWidth)) text.WriteString(truncatedLine) text.WriteString("\n") line = strings.Replace(line, truncatedLine, "", 1) } text.WriteString(truncate.String(line, uint(maxWidth))) text.WriteString("\n") } return text.String() } gum-0.14.4/spin/000077500000000000000000000000001466436004000133445ustar00rootroot00000000000000gum-0.14.4/spin/command.go000066400000000000000000000034421466436004000153140ustar00rootroot00000000000000package spin import ( "fmt" "os" "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/x/term" "github.com/charmbracelet/gum/internal/exit" ) // Run provides a shell script interface for the spinner bubble. // https://github.com/charmbracelet/bubbles/spinner func (o Options) Run() error { isTTY := term.IsTerminal(os.Stdout.Fd()) s := spinner.New() s.Style = o.SpinnerStyle.ToLipgloss() s.Spinner = spinnerMap[o.Spinner] m := model{ spinner: s, title: o.TitleStyle.ToLipgloss().Render(o.Title), command: o.Command, align: o.Align, showOutput: o.ShowOutput && isTTY, showError: o.ShowError, timeout: o.Timeout, hasTimeout: o.Timeout > 0, } p := tea.NewProgram(m, tea.WithOutput(os.Stderr)) mm, err := p.Run() m = mm.(model) if err != nil { return fmt.Errorf("failed to run spin: %w", err) } if m.aborted { return exit.ErrAborted } // If the command succeeds, and we are printing output and we are in a TTY then push the STDOUT we got to the actual // STDOUT for piping or other things. //nolint:nestif if m.status == 0 { if o.ShowOutput { // BubbleTea writes the View() to stderr. // If the program is being piped then put the accumulated output in stdout. if !isTTY { _, err := os.Stdout.WriteString(m.stdout) if err != nil { return fmt.Errorf("failed to write to stdout: %w", err) } } } } else if o.ShowError { // Otherwise if we are showing errors and the command did not exit with a 0 status code then push all of the command // output to the terminal. This way failed commands can be debugged. _, err := os.Stdout.WriteString(m.output) if err != nil { return fmt.Errorf("failed to write to stdout: %w", err) } } os.Exit(m.status) return nil } gum-0.14.4/spin/options.go000066400000000000000000000023231466436004000153660ustar00rootroot00000000000000package spin import ( "time" "github.com/charmbracelet/gum/style" ) // Options is the customization options for the spin command. type Options struct { Command []string `arg:"" help:"Command to run"` ShowOutput bool `help:"Show or pipe output of command during execution" default:"false" env:"GUM_SPIN_SHOW_OUTPUT"` ShowError bool `help:"Show output of command only if the command fails" default:"false" env:"GUM_SPIN_SHOW_ERROR"` Spinner 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"` SpinnerStyle style.Styles `embed:"" prefix:"spinner." set:"defaultForeground=212" envprefix:"GUM_SPIN_SPINNER_"` Title string `help:"Text to display to user while spinning" default:"Loading..." env:"GUM_SPIN_TITLE"` TitleStyle style.Styles `embed:"" prefix:"title." envprefix:"GUM_SPIN_TITLE_"` Align string `help:"Alignment of spinner with regard to the title" short:"a" type:"align" enum:"left,right" default:"left" env:"GUM_SPIN_ALIGN"` Timeout time.Duration `help:"Timeout until spin command aborts" default:"0" env:"GUM_SPIN_TIMEOUT"` } gum-0.14.4/spin/spin.go000066400000000000000000000063771466436004000146610ustar00rootroot00000000000000// Package spin provides a shell script interface for the spinner bubble. // https://github.com/charmbracelet/bubbles/tree/master/spinner // // It is useful for displaying that some task is running in the background // while consuming it's output so that it is not shown to the user. // // For example, let's do a long running task: $ sleep 5 // // We can simply prepend a spinner to this task to show it to the user, while // performing the task / command in the background. // // $ gum spin -t "Taking a nap..." -- sleep 5 // // The spinner will automatically exit when the task is complete. package spin import ( "io" "os" "os/exec" "strings" "time" "github.com/charmbracelet/gum/internal/exit" "github.com/charmbracelet/gum/timeout" "github.com/charmbracelet/x/term" "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" ) type model struct { spinner spinner.Model title string align string command []string quitting bool aborted bool status int stdout string stderr string output string showOutput bool showError bool timeout time.Duration hasTimeout bool } var ( bothbuf strings.Builder outbuf strings.Builder errbuf strings.Builder ) type finishCommandMsg struct { stdout string stderr string output string status int } func commandStart(command []string) tea.Cmd { return func() tea.Msg { var args []string if len(command) > 1 { args = command[1:] } cmd := exec.Command(command[0], args...) //nolint:gosec if term.IsTerminal(os.Stdout.Fd()) { stdout := io.MultiWriter(&bothbuf, &errbuf) stderr := io.MultiWriter(&bothbuf, &outbuf) cmd.Stdout = stdout cmd.Stderr = stderr } else { cmd.Stdout = os.Stdout } _ = cmd.Run() status := cmd.ProcessState.ExitCode() if status == -1 { status = 1 } return finishCommandMsg{ stdout: outbuf.String(), stderr: errbuf.String(), output: bothbuf.String(), status: status, } } } func (m model) Init() tea.Cmd { return tea.Batch( m.spinner.Tick, commandStart(m.command), timeout.Init(m.timeout, nil), ) } func (m model) View() string { if m.quitting && m.showOutput { return strings.TrimPrefix(errbuf.String()+"\n"+outbuf.String(), "\n") } var str string if m.hasTimeout { str = timeout.Str(m.timeout) } var header string if m.align == "left" { header = m.spinner.View() + str + " " + m.title } else { header = str + " " + m.title + " " + m.spinner.View() } if !m.showOutput { return header } return header + errbuf.String() + "\n" + outbuf.String() } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case timeout.TickTimeoutMsg: if msg.TimeoutValue <= 0 { // grab current output before closing for piped instances m.stdout = outbuf.String() m.status = exit.StatusAborted return m, tea.Quit } m.timeout = msg.TimeoutValue return m, timeout.Tick(msg.TimeoutValue, msg.Data) case finishCommandMsg: m.stdout = msg.stdout m.stderr = msg.stderr m.output = msg.output m.status = msg.status m.quitting = true return m, tea.Quit case tea.KeyMsg: switch msg.String() { case "ctrl+c": m.aborted = true return m, tea.Quit } } m.spinner, cmd = m.spinner.Update(msg) return m, cmd } gum-0.14.4/spin/spinners.go000066400000000000000000000006621466436004000155400ustar00rootroot00000000000000package spin import "github.com/charmbracelet/bubbles/spinner" var spinnerMap = map[string]spinner.Spinner{ "line": spinner.Line, "dot": spinner.Dot, "minidot": spinner.MiniDot, "jump": spinner.Jump, "pulse": spinner.Pulse, "points": spinner.Points, "globe": spinner.Globe, "moon": spinner.Moon, "monkey": spinner.Monkey, "meter": spinner.Meter, "hamburger": spinner.Hamburger, } gum-0.14.4/style/000077500000000000000000000000001466436004000135335ustar00rootroot00000000000000gum-0.14.4/style/borders.go000066400000000000000000000005701466436004000155240ustar00rootroot00000000000000package style import "github.com/charmbracelet/lipgloss" // Border maps strings to `lipgloss.Border`s. var Border map[string]lipgloss.Border = map[string]lipgloss.Border{ "double": lipgloss.DoubleBorder(), "hidden": lipgloss.HiddenBorder(), "none": {}, "normal": lipgloss.NormalBorder(), "rounded": lipgloss.RoundedBorder(), "thick": lipgloss.ThickBorder(), } gum-0.14.4/style/command.go000066400000000000000000000013611466436004000155010ustar00rootroot00000000000000// Package style provides a shell script interface for Lip Gloss. // https://github.com/charmbracelet/lipgloss // // It allows you to use Lip Gloss to style text without needing to use Go. All // of the styling options are available as flags. package style import ( "errors" "fmt" "strings" "github.com/charmbracelet/gum/internal/stdin" ) // Run provides a shell script interface for the Lip Gloss styling. // https://github.com/charmbracelet/lipgloss func (o Options) Run() error { var text string if len(o.Text) > 0 { text = strings.Join(o.Text, "\n") } else { text, _ = stdin.Read() if text == "" { return errors.New("no input provided, see `gum style --help`") } } fmt.Println(o.Style.ToLipgloss().Render(text)) return nil } gum-0.14.4/style/lipgloss.go000066400000000000000000000026031466436004000157170ustar00rootroot00000000000000package style import ( "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/gum/internal/decode" ) // ToLipgloss takes a Styles flag set and returns the corresponding // lipgloss.Style. func (s Styles) ToLipgloss() lipgloss.Style { return lipgloss.NewStyle(). Background(lipgloss.Color(s.Background)). Foreground(lipgloss.Color(s.Foreground)). BorderBackground(lipgloss.Color(s.BorderBackground)). BorderForeground(lipgloss.Color(s.BorderForeground)). Align(decode.Align[s.Align]). Border(Border[s.Border]). Height(s.Height). Width(s.Width). Margin(parseMargin(s.Margin)). Padding(parsePadding(s.Padding)). Bold(s.Bold). Faint(s.Faint). Italic(s.Italic). Strikethrough(s.Strikethrough). Underline(s.Underline) } // ToLipgloss takes a Styles flag set and returns the corresponding // lipgloss.Style. func (s StylesNotHidden) ToLipgloss() lipgloss.Style { return lipgloss.NewStyle(). Background(lipgloss.Color(s.Background)). Foreground(lipgloss.Color(s.Foreground)). BorderBackground(lipgloss.Color(s.BorderBackground)). BorderForeground(lipgloss.Color(s.BorderForeground)). Align(decode.Align[s.Align]). Border(Border[s.Border]). Height(s.Height). Width(s.Width). Margin(parseMargin(s.Margin)). Padding(parsePadding(s.Padding)). Bold(s.Bold). Faint(s.Faint). Italic(s.Italic). Strikethrough(s.Strikethrough). Underline(s.Underline) } gum-0.14.4/style/options.go000066400000000000000000000107111466436004000155550ustar00rootroot00000000000000package style // Options is the customization options for the style command. type Options struct { Text []string `arg:"" optional:"" help:"Text to which to apply the style"` Style StylesNotHidden `embed:""` } // Styles is a flag set of possible styles. // // It corresponds to the available options in the lipgloss.Style struct. // // This flag set is used in other parts of the application to embed styles for // components, through embedding and prefixing. type Styles struct { // Colors Foreground string `help:"Foreground Color" default:"${defaultForeground}" group:"Style Flags" env:"FOREGROUND"` Background string `help:"Background Color" default:"${defaultBackground}" group:"Style Flags" env:"BACKGROUND"` // Border Border string `help:"Border Style" enum:"none,hidden,normal,rounded,thick,double" default:"${defaultBorder}" group:"Style Flags" env:"BORDER" hidden:"true"` BorderBackground string `help:"Border Background Color" group:"Style Flags" default:"${defaultBorderBackground}" env:"BORDER_BACKGROUND" hidden:"true"` BorderForeground string `help:"Border Foreground Color" group:"Style Flags" default:"${defaultBorderForeground}" env:"BORDER_FOREGROUND" hidden:"true"` // Layout Align string `help:"Text Alignment" enum:"left,center,right,bottom,middle,top" default:"${defaultAlign}" group:"Style Flags" env:"ALIGN" hidden:"true"` Height int `help:"Text height" default:"${defaultHeight}" group:"Style Flags" env:"HEIGHT" hidden:"true"` Width int `help:"Text width" default:"${defaultWidth}" group:"Style Flags" env:"WIDTH" hidden:"true"` Margin string `help:"Text margin" default:"${defaultMargin}" group:"Style Flags" env:"MARGIN" hidden:"true"` Padding string `help:"Text padding" default:"${defaultPadding}" group:"Style Flags" env:"PADDING" hidden:"true"` // Format Bold bool `help:"Bold text" default:"${defaultBold}" group:"Style Flags" env:"BOLD" hidden:"true"` Faint bool `help:"Faint text" default:"${defaultFaint}" group:"Style Flags" env:"FAINT" hidden:"true"` Italic bool `help:"Italicize text" default:"${defaultItalic}" group:"Style Flags" env:"ITALIC" hidden:"true"` Strikethrough bool `help:"Strikethrough text" default:"${defaultStrikethrough}" group:"Style Flags" env:"STRIKETHROUGH" hidden:"true"` Underline bool `help:"Underline text" default:"${defaultUnderline}" group:"Style Flags" env:"UNDERLINE" hidden:"true"` } // StylesNotHidden allows the style struct to display full help when not-embedded. // // NB: We must duplicate this struct to ensure that `gum style` does not hide // flags when an error pops up. Ideally, we can dynamically hide or show flags // based on the command run: https://github.com/alecthomas/kong/issues/316 type StylesNotHidden struct { // Colors Foreground string `help:"Foreground Color" default:"${defaultForeground}" group:"Style Flags" env:"FOREGROUND"` Background string `help:"Background Color" default:"${defaultBackground}" group:"Style Flags" env:"BACKGROUND"` // Border Border string `help:"Border Style" enum:"none,hidden,normal,rounded,thick,double" default:"${defaultBorder}" group:"Style Flags" env:"BORDER"` BorderBackground string `help:"Border Background Color" group:"Style Flags" default:"${defaultBorderBackground}" env:"BORDER_BACKGROUND"` BorderForeground string `help:"Border Foreground Color" group:"Style Flags" default:"${defaultBorderForeground}" env:"BORDER_FOREGROUND"` // Layout Align string `help:"Text Alignment" enum:"left,center,right,bottom,middle,top" default:"${defaultAlign}" group:"Style Flags" env:"ALIGN"` Height int `help:"Text height" default:"${defaultHeight}" group:"Style Flags" env:"HEIGHT"` Width int `help:"Text width" default:"${defaultWidth}" group:"Style Flags" env:"WIDTH"` Margin string `help:"Text margin" default:"${defaultMargin}" group:"Style Flags" env:"MARGIN"` Padding string `help:"Text padding" default:"${defaultPadding}" group:"Style Flags" env:"PADDING"` // Format Bold bool `help:"Bold text" default:"${defaultBold}" group:"Style Flags" env:"BOLD"` Faint bool `help:"Faint text" default:"${defaultFaint}" group:"Style Flags" env:"FAINT"` Italic bool `help:"Italicize text" default:"${defaultItalic}" group:"Style Flags" env:"ITALIC"` Strikethrough bool `help:"Strikethrough text" default:"${defaultStrikethrough}" group:"Style Flags" env:"STRIKETHROUGH"` Underline bool `help:"Underline text" default:"${defaultUnderline}" group:"Style Flags" env:"UNDERLINE"` } gum-0.14.4/style/spacing.go000066400000000000000000000017551466436004000155160ustar00rootroot00000000000000package style import ( "strconv" "strings" ) const minTokens = 1 const halfTokens = 2 const maxTokens = 4 // parsePadding parses 1 - 4 integers from a string and returns them in a top, // right, bottom, left order for use in the lipgloss.Padding() method. func parsePadding(s string) (int, int, int, int) { var ints [maxTokens]int tokens := strings.Split(s, " ") if len(tokens) > maxTokens { return 0, 0, 0, 0 } // All tokens must be an integer for i, token := range tokens { parsed, err := strconv.Atoi(token) if err != nil { return 0, 0, 0, 0 } ints[i] = parsed } if len(tokens) == minTokens { return ints[0], ints[0], ints[0], ints[0] } if len(tokens) == halfTokens { return ints[0], ints[1], ints[0], ints[1] } if len(tokens) == maxTokens { return ints[0], ints[1], ints[2], ints[3] } return 0, 0, 0, 0 } // parseMargin is an alias for parsePadding since they involve the same logic // to parse integers to the same format. var parseMargin = parsePadding gum-0.14.4/table/000077500000000000000000000000001466436004000134625ustar00rootroot00000000000000gum-0.14.4/table/comma.csv000066400000000000000000000002731466436004000152750ustar00rootroot00000000000000Bubble Gum,Price,Ingredients Strawberry,$0.88,"Water,Sugar" Guava,$1.00,"Guava Flavoring,Food Coloring,Xanthan Gum" Orange,$0.99,"Sugar,Dextrose,Glucose" Cinnamon,$0.50,"Cin""na""mon"gum-0.14.4/table/command.go000066400000000000000000000056351466436004000154400ustar00rootroot00000000000000package table import ( "encoding/csv" "fmt" "os" "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ltable "github.com/charmbracelet/lipgloss/table" "github.com/charmbracelet/gum/internal/stdin" "github.com/charmbracelet/gum/style" ) // Run provides a shell script interface for rendering tabular data (CSV). func (o Options) Run() error { var reader *csv.Reader if o.File != "" { file, err := os.Open(o.File) if err != nil { return fmt.Errorf("could not find file at path %s", o.File) } reader = csv.NewReader(file) } else { if stdin.IsEmpty() { return fmt.Errorf("no data provided") } reader = csv.NewReader(os.Stdin) } separatorRunes := []rune(o.Separator) if len(separatorRunes) != 1 { return fmt.Errorf("separator must be single character") } reader.Comma = separatorRunes[0] writer := csv.NewWriter(os.Stdout) writer.Comma = separatorRunes[0] var columnNames []string var err error // If no columns are provided we'll use the first row of the CSV as the // column names. if len(o.Columns) <= 0 { columnNames, err = reader.Read() if err != nil { return fmt.Errorf("unable to parse columns") } } else { columnNames = o.Columns } data, err := reader.ReadAll() if err != nil { return fmt.Errorf("invalid data provided") } columns := make([]table.Column, 0, len(columnNames)) for i, title := range columnNames { width := lipgloss.Width(title) if len(o.Widths) > i { width = o.Widths[i] } columns = append(columns, table.Column{ Title: title, Width: width, }) } defaultStyles := table.DefaultStyles() styles := table.Styles{ Cell: defaultStyles.Cell.Inherit(o.CellStyle.ToLipgloss()), Header: defaultStyles.Header.Inherit(o.HeaderStyle.ToLipgloss()), Selected: o.SelectedStyle.ToLipgloss(), } rows := make([]table.Row, 0, len(data)) for _, row := range data { if len(row) > len(columns) { return fmt.Errorf("invalid number of columns") } rows = append(rows, table.Row(row)) } if o.Print { table := ltable.New(). Headers(columnNames...). Rows(data...). BorderStyle(o.BorderStyle.ToLipgloss()). Border(style.Border[o.Border]). StyleFunc(func(row, _ int) lipgloss.Style { if row == 0 { return styles.Header } return styles.Cell }) fmt.Println(table.Render()) return nil } table := table.New( table.WithColumns(columns), table.WithFocused(true), table.WithHeight(o.Height), table.WithRows(rows), table.WithStyles(styles), ) tm, err := tea.NewProgram(model{table: table}, tea.WithOutput(os.Stderr)).Run() if err != nil { return fmt.Errorf("failed to start tea program: %w", err) } if tm == nil { return fmt.Errorf("failed to get selection") } m := tm.(model) if err = writer.Write([]string(m.selected)); err != nil { return fmt.Errorf("failed to write selected row: %w", err) } writer.Flush() return nil } gum-0.14.4/table/example.csv000066400000000000000000000004271466436004000156350ustar00rootroot00000000000000Bubble Gum Flavor,Price Strawberry,$0.99 Cherry,$0.50 Banana,$0.75 Orange,$0.25 Lemon,$0.50 Lime,$0.50 Grape,$0.50 Watermelon,$0.50 Pineapple,$0.50 Blueberry,$0.50 Raspberry,$0.50 Cranberry,$0.50 Peach,$0.50 Apple,$0.50 Mango,$0.50 Pomegranate,$0.50 Coconut,$0.50 Cinnamon,$0.50 gum-0.14.4/table/invalid.csv000066400000000000000000000004131466436004000156230ustar00rootroot00000000000000Bubble Gum Flavor Strawberry,$0.99 Cherry,$0.50 Banana,$0.75 Orange Lemon,$0.50 Lime,$0.50 Grape,$0.50 Watermelon,$0.50 Pineapple,$0.50 Blueberry,$0.50 Raspberry,$0.50 Cranberry,$0.50 Peach,$0.50 Apple,$0.50 Mango,$0.50 Pomegranate,$0.50 Coconut,$0.50 Cinnamon,$0.50 gum-0.14.4/table/options.go000066400000000000000000000017351466436004000155120ustar00rootroot00000000000000package table import "github.com/charmbracelet/gum/style" // Options is the customization options for the table command. type Options struct { Separator string `short:"s" help:"Row separator" default:","` Columns []string `short:"c" help:"Column names"` Widths []int `short:"w" help:"Column widths"` Height int `help:"Table height" default:"0"` Print bool `short:"p" help:"static print" default:"false"` File string `short:"f" help:"file path" default:""` Border string `short:"b" help:"border style" default:"rounded" enum:"rounded,thick,normal,hidden,double,none"` BorderStyle style.Styles `embed:"" prefix:"border." envprefix:"GUM_TABLE_BORDER_"` CellStyle style.Styles `embed:"" prefix:"cell." envprefix:"GUM_TABLE_CELL_"` HeaderStyle style.Styles `embed:"" prefix:"header." envprefix:"GUM_TABLE_HEADER_"` SelectedStyle style.Styles `embed:"" prefix:"selected." set:"defaultForeground=212" envprefix:"GUM_TABLE_SELECTED_"` } gum-0.14.4/table/table.go000066400000000000000000000022201466436004000150740ustar00rootroot00000000000000// Package table provides a shell script interface for the table bubble. // https://github.com/charmbracelet/bubbles/tree/master/table // // It is useful to render tabular (CSV) data in a terminal and allows // the user to select a row from the table. // // Let's render a table of gum flavors: // // $ gum table <<< "Flavor,Price\nStrawberry,$0.50\nBanana,$0.99\nCherry,$0.75" // // Flavor Price // Strawberry $0.50 // Banana $0.99 // Cherry $0.75 package table import ( "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" ) type model struct { table table.Model selected table.Row quitting bool } func (m model) Init() tea.Cmd { return nil } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "enter": m.selected = m.table.SelectedRow() m.quitting = true return m, tea.Quit case "ctrl+c", "q", "esc": m.quitting = true return m, tea.Quit } } m.table, cmd = m.table.Update(msg) return m, cmd } func (m model) View() string { if m.quitting { return "" } return m.table.View() } gum-0.14.4/timeout/000077500000000000000000000000001466436004000140615ustar00rootroot00000000000000gum-0.14.4/timeout/options.go000066400000000000000000000021531466436004000161040ustar00rootroot00000000000000package timeout import ( "fmt" "time" tea "github.com/charmbracelet/bubbletea" ) // Tick interval. const tickInterval = time.Second // TickTimeoutMsg will be dispatched for every tick. // Containing current timeout value // and optional parameter to be used when handling the timeout msg. type TickTimeoutMsg struct { TimeoutValue time.Duration Data interface{} } // Init Start Timeout ticker using with timeout in seconds and optional data. func Init(timeout time.Duration, data interface{}) tea.Cmd { if timeout > 0 { return Tick(timeout, data) } return nil } // Start ticker. func Tick(timeoutValue time.Duration, data interface{}) tea.Cmd { return tea.Tick(tickInterval, func(time.Time) tea.Msg { // every tick checks if the timeout needs to be decremented // and send as message if timeoutValue >= 0 { timeoutValue -= tickInterval return TickTimeoutMsg{ TimeoutValue: timeoutValue, Data: data, } } return nil }) } // Str produce Timeout String to be rendered. func Str(timeout time.Duration) string { return fmt.Sprintf(" (%d)", max(0, int(timeout.Seconds()))) } gum-0.14.4/write/000077500000000000000000000000001466436004000135255ustar00rootroot00000000000000gum-0.14.4/write/command.go000066400000000000000000000022631466436004000154750ustar00rootroot00000000000000package write import ( "fmt" "strings" "github.com/charmbracelet/gum/internal/stdin" "github.com/charmbracelet/huh" ) // Run provides a shell script interface for the text area bubble. // https://github.com/charmbracelet/bubbles/textarea func (o Options) Run() error { in, _ := stdin.Read() if in != "" && o.Value == "" { o.Value = strings.ReplaceAll(in, "\r", "") } var value = o.Value theme := huh.ThemeCharm() theme.Focused.Base = o.BaseStyle.ToLipgloss() theme.Focused.TextInput.Cursor = o.CursorStyle.ToLipgloss() theme.Focused.Title = o.HeaderStyle.ToLipgloss() theme.Focused.TextInput.Placeholder = o.PlaceholderStyle.ToLipgloss() theme.Focused.TextInput.Prompt = o.PromptStyle.ToLipgloss() keymap := huh.NewDefaultKeyMap() keymap.Text.NewLine.SetHelp("ctrl+j", "new line") err := huh.NewForm( huh.NewGroup( huh.NewText(). Title(o.Header). Placeholder(o.Placeholder). CharLimit(o.CharLimit). ShowLineNumbers(o.ShowLineNumbers). Value(&value), ), ). WithWidth(o.Width). WithHeight(o.Height). WithTheme(theme). WithKeyMap(keymap). WithShowHelp(o.ShowHelp). Run() if err != nil { return err } fmt.Println(value) return nil } gum-0.14.4/write/options.go000066400000000000000000000044271466436004000155560ustar00rootroot00000000000000package write import "github.com/charmbracelet/gum/style" // Options are the customization options for the textarea. type Options struct { Width int `help:"Text area width (0 for terminal width)" default:"0" env:"GUM_WRITE_WIDTH"` Height int `help:"Text area height" default:"5" env:"GUM_WRITE_HEIGHT"` Header string `help:"Header value" default:"" env:"GUM_WRITE_HEADER"` Placeholder string `help:"Placeholder value" default:"Write something..." env:"GUM_WRITE_PLACEHOLDER"` Prompt string `help:"Prompt to display" default:"┃ " env:"GUM_WRITE_PROMPT"` ShowCursorLine bool `help:"Show cursor line" default:"false" env:"GUM_WRITE_SHOW_CURSOR_LINE"` ShowLineNumbers bool `help:"Show line numbers" default:"false" env:"GUM_WRITE_SHOW_LINE_NUMBERS"` Value string `help:"Initial value (can be passed via stdin)" default:"" env:"GUM_WRITE_VALUE"` CharLimit int `help:"Maximum value length (0 for no limit)" default:"400"` ShowHelp bool `help:"Show help key binds" negatable:"" default:"true" env:"GUM_WRITE_SHOW_HELP"` CursorMode string `prefix:"cursor." name:"mode" help:"Cursor mode" default:"blink" enum:"blink,hide,static" env:"GUM_WRITE_CURSOR_MODE"` BaseStyle style.Styles `embed:"" prefix:"base." envprefix:"GUM_WRITE_BASE_"` CursorStyle style.Styles `embed:"" prefix:"cursor." set:"defaultForeground=212" envprefix:"GUM_WRITE_CURSOR_"` HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=240" envprefix:"GUM_WRITE_HEADER_"` PlaceholderStyle style.Styles `embed:"" prefix:"placeholder." set:"defaultForeground=240" envprefix:"GUM_WRITE_PLACEHOLDER_"` PromptStyle style.Styles `embed:"" prefix:"prompt." set:"defaultForeground=7" envprefix:"GUM_WRITE_PROMPT_"` EndOfBufferStyle style.Styles `embed:"" prefix:"end-of-buffer." set:"defaultForeground=0" envprefix:"GUM_WRITE_END_OF_BUFFER_"` LineNumberStyle style.Styles `embed:"" prefix:"line-number." set:"defaultForeground=7" envprefix:"GUM_WRITE_LINE_NUMBER_"` CursorLineNumberStyle style.Styles `embed:"" prefix:"cursor-line-number." set:"defaultForeground=7" envprefix:"GUM_WRITE_CURSOR_LINE_NUMBER_"` CursorLineStyle style.Styles `embed:"" prefix:"cursor-line." envprefix:"GUM_WRITE_CURSOR_LINE_"` }