[
  {
    "path": ".gitattributes",
    "content": "*.golden -text\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "*  @meowgorithm @aymanbagabas\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "content": "name: Bug Report\ndescription: File a bug report\nlabels: [bug]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report! Please fill the form below.\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What happened?\n      description: Also tell us, what did you expect to happen?\n    validations:\n      required: true\n  - type: textarea\n    id: reproducible\n    attributes:\n      label: How can we reproduce this?\n      description: |\n        Please share a code snippet, gist, or public repository that reproduces the issue.\n        Make sure to make the reproducible as concise as possible,\n        with only the minimum required code to reproduce the issue.\n    validations:\n      required: true\n  - type: textarea\n    id: version\n    attributes:\n      label: Which version of bubbletea are you using?\n      description: ''\n      render: bash\n    validations:\n      required: true\n  - type: textarea\n    id: terminaal\n    attributes:\n      label: Which terminals did you reproduce this with?\n      description: |\n        Other helpful information:\n        was it over SSH?\n        On tmux?\n        Which version of said terminal?\n    validations:\n      required: true\n  - type: checkboxes\n    id: search\n    attributes:\n      label: Search\n      options:\n        - label: |\n           I searched for other open and closed issues and pull requests before opening this,\n           and didn't find anything that seems related.\n          required: true\n  - type: textarea\n    id: ctx\n    attributes:\n      label: Additional context\n      description: Anything else you would like to add\n    validations:\n      required: false\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**Setup**\nPlease complete the following information along with version numbers, if applicable.\n - OS [e.g. Ubuntu, macOS]\n - Shell [e.g. zsh, fish]\n - Terminal Emulator [e.g. kitty, iterm]\n - Terminal Multiplexer [e.g. tmux]\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Source Code**\nPlease include source code if needed to reproduce the behavior. \n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nAdd screenshots to help explain your problem.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n- name: Discord\n  url: https://charm.sh/discord\n  about: Chat on our Discord.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\n\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"05:00\"\n      timezone: \"America/New_York\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    groups:\n      all:\n        patterns:\n          - \"*\"\n    ignore:\n      - dependency-name: github.com/charmbracelet/bubbletea/v2\n        versions:\n          - v2.0.0-beta1\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"05:00\"\n      timezone: \"America/New_York\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    groups:\n      all:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: \"docker\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"05:00\"\n      timezone: \"America/New_York\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    groups:\n      all:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: \"gomod\"\n    directory: \"/examples\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"05:00\"\n      timezone: \"America/New_York\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    groups:\n      all:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: \"gomod\"\n    directory: \"/tutorials\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"05:00\"\n      timezone: \"America/New_York\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    groups:\n      all:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\non: [push, pull_request]\n\njobs:\n  build:\n    uses: charmbracelet/meta/.github/workflows/build.yml@main\n\n  build-go-mod:\n    uses: charmbracelet/meta/.github/workflows/build.yml@main\n    with:\n      go-version: \"\"\n      go-version-file: ./go.mod\n\n  build-examples:\n    uses: charmbracelet/meta/.github/workflows/build.yml@main\n    with:\n      go-version: \"\"\n      go-version-file: ./examples/go.mod\n      working-directory: ./examples\n"
  },
  {
    "path": ".github/workflows/coverage.yml",
    "content": "name: coverage\non: [push, pull_request]\n\njobs:\n  coverage:\n    strategy:\n      matrix:\n        go-version: [^1]\n        os: [ubuntu-latest]\n    runs-on: ${{ matrix.os }}\n    env:\n      GO111MODULE: \"on\"\n    steps:\n      - name: Install Go\n        uses: actions/setup-go@v6\n        with:\n          go-version: ${{ matrix.go-version }}\n\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Coverage\n        run: |\n          go test -race -covermode=atomic -coverprofile=coverage.txt ./...\n\n      - uses: codecov/codecov-action@v5\n        with:\n          file: ./coverage.txt\n          token: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/dependabot-sync.yml",
    "content": "name: dependabot-sync\non:\n  schedule:\n    - cron: \"0 0 * * 0\" # every Sunday at midnight\n  workflow_dispatch: # allows manual triggering\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  dependabot-sync:\n    uses: charmbracelet/meta/.github/workflows/dependabot-sync.yml@main\n    with:\n      repo_name: ${{ github.event.repository.name }}\n    secrets:\n      gh_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/examples.yml",
    "content": "name: examples\n\non:\n  push:\n    branches:\n      - 'master'\n    paths:\n      - '.github/workflows/examples.yml'\n      - './examples/go.mod'\n      - './examples/go.sum'\n      - './tutorials/go.mod'\n      - './tutorials/go.sum'\n      - './go.mod'\n      - './go.sum'\n  workflow_dispatch: {}\n\njobs:\n  tidy:\n    permissions:\n      contents: write\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-go@v6\n        with:\n          go-version: '^1'\n          cache: true\n      - shell: bash\n        run: |\n          (cd ./examples && go mod tidy)\n          (cd ./tutorials && go mod tidy)\n      - uses: stefanzweifel/git-auto-commit-action@v7\n        with:\n          commit_message: \"chore: go mod tidy tutorials and examples\"\n          branch: master\n          commit_user_name: actions-user\n          commit_user_email: actions@github.com\n\n"
  },
  {
    "path": ".github/workflows/lint-sync.yml",
    "content": "name: lint-sync\non:\n  schedule:\n    # every Sunday at midnight\n    - cron: \"0 0 * * 0\"\n  workflow_dispatch: # allows manual triggering\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  lint:\n    uses: charmbracelet/meta/.github/workflows/lint-sync.yml@main\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: lint\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    uses: charmbracelet/meta/.github/workflows/lint.yml@main\n    with:\n      golangci_path: .golangci.yml\n      golangci_version: v2.9\n      timeout: 10m\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: goreleaser\n\non:\n  push:\n    tags:\n      - v*.*.*\n\nconcurrency:\n  group: goreleaser\n  cancel-in-progress: true\n\njobs:\n  goreleaser:\n    uses: charmbracelet/meta/.github/workflows/goreleaser.yml@main\n    secrets:\n      docker_username: ${{ secrets.DOCKERHUB_USERNAME }}\n      docker_token: ${{ secrets.DOCKERHUB_TOKEN }}\n      gh_pat: ${{ secrets.PERSONAL_ACCESS_TOKEN }}\n      goreleaser_key: ${{ secrets.GORELEASER_KEY }}\n      twitter_consumer_key: ${{ secrets.TWITTER_CONSUMER_KEY }}\n      twitter_consumer_secret: ${{ secrets.TWITTER_CONSUMER_SECRET }}\n      twitter_access_token: ${{ secrets.TWITTER_ACCESS_TOKEN }}\n      twitter_access_token_secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}\n      mastodon_client_id: ${{ secrets.MASTODON_CLIENT_ID }}\n      mastodon_client_secret: ${{ secrets.MASTODON_CLIENT_SECRET }}\n      mastodon_access_token: ${{ secrets.MASTODON_ACCESS_TOKEN }}\n      discord_webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}\n      discord_webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.envrc\n\nexamples/fullscreen/fullscreen\nexamples/help/help\nexamples/http/http\nexamples/list-default/list-default\nexamples/list-fancy/list-fancy\nexamples/list-simple/list-simple\nexamples/mouse/mouse\nexamples/pager/pager\nexamples/progress-download/color_vortex.blend\nexamples/progress-download/progress-download\nexamples/simple/simple\nexamples/spinner/spinner\nexamples/textinput/textinput\nexamples/textinputs/textinputs\nexamples/views/views\ntutorials/basics/basics\ntutorials/commands/commands\n.idea\ncoverage.txt\ndist/\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  tests: false\nlinters:\n  enable:\n    - bodyclose\n    - exhaustive\n    - goconst\n    - godot\n    - gomoddirectives\n    - goprintffuncname\n    - gosec\n    - misspell\n    - nakedret\n    - nestif\n    - nilerr\n    - noctx\n    - nolintlint\n    - prealloc\n    - revive\n    - rowserrcheck\n    - sqlclosecheck\n    - tparallel\n    - unconvert\n    - unparam\n    - whitespace\n    - wrapcheck\n  exclusions:\n    rules:\n      - text: '(slog|log)\\.\\w+'\n        linters:\n          - noctx\n    generated: lax\n    presets:\n      - common-false-positives\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\nformatters:\n  enable:\n    - gofumpt\n    - goimports\n  exclusions:\n    generated: lax\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json\nversion: 2\nincludes:\n  - from_url:\n      url: charmbracelet/meta/main/goreleaser-lib.yaml\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020-2026 Charmbracelet, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Bubble Tea\n\n<p>\n    <img src=\"https://github.com/user-attachments/assets/ad408275-8799-488f-9303-441e7f869535\" width=\"350\"><br>\n    <a href=\"https://github.com/charmbracelet/bubbletea/releases\"><img src=\"https://img.shields.io/github/release/charmbracelet/bubbletea.svg\" alt=\"Latest Release\"></a>\n    <a href=\"https://pkg.go.dev/charm.land/bubbletea/v2?tab=doc\"><img src=\"https://godoc.org/charm.land/bubbletea/v2?status.svg\" alt=\"GoDoc\"></a>\n    <a href=\"https://github.com/charmbracelet/bubbletea/actions\"><img src=\"https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg?branch=main\" alt=\"Build Status\"></a>\n</p>\n\nThe fun, functional and stateful way to build terminal apps. A Go framework\nbased on [The Elm Architecture][elm]. Bubble Tea is well-suited for simple and\ncomplex terminal applications, either inline, full-window, or a mix of both.\n\n<p>\n    <img src=\"https://stuff.charm.sh/bubbletea/bubbletea-example.gif\" width=\"100%\" alt=\"Bubble Tea Example\">\n</p>\n\nBubble Tea is in use in production and includes a number of features and\nperformance optimizations we’ve added along the way. Among those is\na high-performance cell-based renderer, built-in color downsampling,\ndeclarative views, high-fidelity keyboard and mouse handling, native clipboard\nsupport, and more.\n\nTo get started, see the tutorial below, the [examples][examples], the\n[docs][docs], and some common [resources](#libraries-we-use-with-bubble-tea).\n\n> [!TIP]\n>\n> Upgrading from v1? Check out the [upgrade guide](./UPGRADE_GUIDE_V2.md), or\n> point your LLM at it and let it go to town.\n\n## By the way\n\nBe sure to check out [Bubbles][bubbles], a library of common UI components for Bubble Tea.\n\n<p>\n    <a href=\"https://github.com/charmbracelet/bubbles\"><img src=\"https://github.com/user-attachments/assets/b6dc4638-b67a-4bfa-88d0-a8e8833c3ac9\" width=\"172\" alt=\"Bubbles Badge\"></a>&nbsp;&nbsp;\n    <a href=\"https://github.com/charmbracelet/bubbles\"><img src=\"https://stuff.charm.sh/bubbles-examples/textinput.gif\" width=\"400\" alt=\"Text Input Example from Bubbles\"></a>\n</p>\n\n---\n\n## Tutorial\n\nBubble Tea is based on the functional design paradigms of [The Elm\nArchitecture][elm], which happens to work nicely with Go. It's a delightful way\nto build applications.\n\nThis tutorial assumes you have a working knowledge of Go.\n\nBy the way, the non-annotated source code for this program is available\n[on GitHub][tut-source].\n\n[elm]: https://guide.elm-lang.org/architecture/\n[tut-source]: https://github.com/charmbracelet/bubbletea/tree/main/tutorials/basics\n\n### Enough! Let's get to it.\n\nFor this tutorial, we're making a shopping list.\n\nTo start we'll define our package and import some libraries. Our only external\nimport will be the Bubble Tea library, which we'll call `tea` for short.\n\n```go\npackage main\n\n// These imports will be used later in the tutorial. If you save the file\n// now, Go might complain they are unused, but that's fine.\n// You may also need to run `go mod tidy` to download bubbletea and its\n// dependencies.\nimport (\n    \"fmt\"\n    \"os\"\n\n    tea \"charm.land/bubbletea/v2\"\n)\n```\n\nBubble Tea programs are comprised of a **model** that describes the application\nstate and three simple methods on that model:\n\n- **Init**, a function that returns an initial command for the application to run.\n- **Update**, a function that handles incoming events and updates the model accordingly.\n- **View**, a function that renders the UI based on the data in the model.\n\n### The Model\n\nSo let's start by defining our model which will store our application's state.\nIt can be any type, but a `struct` usually makes the most sense.\n\n```go\ntype model struct {\n    choices  []string           // items on the to-do list\n    cursor   int                // which to-do list item our cursor is pointing at\n    selected map[int]struct{}   // which to-do items are selected\n}\n```\n\n## Initialization\n\nNext, we’ll define our application’s initial state. `Init` can return a `Cmd`\nthat could perform some initial I/O. For now, we don’t need to do any I/O, so\nfor the command, we’ll just return `nil`, which translates to “no command.”\n\n```go\nfunc initialModel() model {\n\treturn model{\n\t\t// Our to-do list is a grocery list\n\t\tchoices:  []string{\"Buy carrots\", \"Buy celery\", \"Buy kohlrabi\"},\n\n\t\t// A map which indicates which choices are selected. We're using\n\t\t// the  map like a mathematical set. The keys refer to the indexes\n\t\t// of the `choices` slice, above.\n\t\tselected: make(map[int]struct{}),\n\t}\n}\n```\n\nAfter that, we’ll define our application’s initial state in the `Init` method. `Init`\ncan return a `Cmd` that could perform some initial I/O. For now, we don't need\nto do any I/O, so for the command, we'll just return `nil`, which translates to\n\"no command.\"\n\n```go\nfunc (m model) Init() tea.Cmd {\n    // Just return `nil`, which means \"no I/O right now, please.\"\n    return nil\n}\n```\n\n### The Update Method\n\nNext up is the update method. The update function is called when “things\nhappen.” Its job is to look at what has happened and return an updated model in\nresponse. It can also return a `Cmd` to make more things happen, but for now\ndon't worry about that part.\n\nIn our case, when a user presses the down arrow, `Update`’s job is to notice\nthat the down arrow was pressed and move the cursor accordingly (or not).\n\nThe “something happened” comes in the form of a `Msg`, which can be any type.\nMessages are the result of some I/O that took place, such as a keypress, timer\ntick, or a response from a server.\n\nWe usually figure out which type of `Msg` we received with a type switch, but\nyou could also use a type assertion.\n\nFor now, we'll just deal with `tea.KeyPressMsg` messages, which are\nautomatically sent to the update function when keys are pressed.\n\n```go\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    switch msg := msg.(type) {\n\n    // Is it a key press?\n    case tea.KeyPressMsg:\n\n        // Cool, what was the actual key pressed?\n        switch msg.String() {\n\n        // These keys should exit the program.\n        case \"ctrl+c\", \"q\":\n            return m, tea.Quit\n\n        // The \"up\" and \"k\" keys move the cursor up\n        case \"up\", \"k\":\n            if m.cursor > 0 {\n                m.cursor--\n            }\n\n        // The \"down\" and \"j\" keys move the cursor down\n        case \"down\", \"j\":\n            if m.cursor < len(m.choices)-1 {\n                m.cursor++\n            }\n\n        // The \"enter\" key and the space bar toggle the selected state\n        // for the item that the cursor is pointing at.\n        case \"enter\", \"space\":\n            _, ok := m.selected[m.cursor]\n            if ok {\n                delete(m.selected, m.cursor)\n            } else {\n                m.selected[m.cursor] = struct{}{}\n            }\n        }\n    }\n\n    // Return the updated model to the Bubble Tea runtime for processing.\n    // Note that we're not returning a command.\n    return m, nil\n}\n```\n\nYou may have noticed that <kbd>ctrl+c</kbd> and <kbd>q</kbd> above return\na `tea.Quit` command with the model. That’s a special command which instructs\nthe Bubble Tea runtime to quit, exiting the program.\n\n### The View Method\n\nAt last, it’s time to render our UI. Of all the methods, the view is the\nsimplest. We look at the model in its current state and use it to build a\n`tea.View`. The view declares our UI content and, optionally, terminal features\nlike alt screen mode, mouse tracking, cursor position, and more.\n\nBecause the view describes the entire UI of your application, you don’t have to\nworry about redrawing logic and stuff like that. Bubble Tea takes care of it\nfor you.\n\n```go\nfunc (m model) View() tea.View {\n    // The header\n    s := \"What should we buy at the market?\\n\\n\"\n\n    // Iterate over our choices\n    for i, choice := range m.choices {\n\n        // Is the cursor pointing at this choice?\n        cursor := \" \" // no cursor\n        if m.cursor == i {\n            cursor = \">\" // cursor!\n        }\n\n        // Is this choice selected?\n        checked := \" \" // not selected\n        if _, ok := m.selected[i]; ok {\n            checked = \"x\" // selected!\n        }\n\n        // Render the row\n        s += fmt.Sprintf(\"%s [%s] %s\\n\", cursor, checked, choice)\n    }\n\n    // The footer\n    s += \"\\nPress q to quit.\\n\"\n\n    // Send the UI for rendering\n    return tea.NewView(s)\n}\n```\n\n### All Together Now\n\nThe last step is to simply run our program. We pass our initial model to\n`tea.NewProgram` and let it rip:\n\n```go\nfunc main() {\n    p := tea.NewProgram(initialModel())\n    if _, err := p.Run(); err != nil {\n        fmt.Printf(\"Alas, there's been an error: %v\", err)\n        os.Exit(1)\n    }\n}\n```\n\n## What’s Next?\n\nThis tutorial covers the basics of building an interactive terminal UI, but\nin the real world you'll also need to perform I/O. To learn about that have a\nlook at the [Command Tutorial][cmd]. It's pretty simple.\n\nThere are also several [Bubble Tea examples][examples] available and, of course,\nthere are [Go Docs][docs].\n\n[cmd]: https://github.com/charmbracelet/bubbletea/tree/main/tutorials/commands/\n[examples]: https://github.com/charmbracelet/bubbletea/tree/main/examples\n[docs]: https://pkg.go.dev/charm.land/bubbletea/v2?tab=doc\n\n## Debugging\n\n### Debugging with Delve\n\nSince Bubble Tea apps assume control of stdin and stdout, you’ll need to run\ndelve in headless mode and then connect to it:\n\n```bash\n# Start the debugger\n$ dlv debug --headless --api-version=2 --listen=127.0.0.1:43000 .\nAPI server listening at: 127.0.0.1:43000\n\n# Connect to it from another terminal\n$ dlv connect 127.0.0.1:43000\n```\n\nIf you do not explicitly supply the `--listen` flag, the port used will vary\nper run, so passing this in makes the debugger easier to use from a script\nor your IDE of choice.\n\nAdditionally, we pass in `--api-version=2` because delve defaults to version 1\nfor backwards compatibility reasons. However, delve recommends using version 2\nfor all new development and some clients may no longer work with version 1.\nFor more information, see the [Delve documentation](https://github.com/go-delve/delve/tree/master/Documentation/api).\n\n### Logging Stuff\n\nYou can’t really log to stdout with Bubble Tea because your TUI is busy\noccupying that! You can, however, log to a file by including something like\nthe following prior to starting your Bubble Tea program:\n\n```go\nif len(os.Getenv(\"DEBUG\")) > 0 {\n\tf, err := tea.LogToFile(\"debug.log\", \"debug\")\n\tif err != nil {\n\t\tfmt.Println(\"fatal:\", err)\n\t\tos.Exit(1)\n\t}\n\tdefer f.Close()\n}\n```\n\nTo see what’s being logged in real time, run `tail -f debug.log` while you run\nyour program in another window.\n\n## Libraries we use with Bubble Tea\n\n- [Bubbles][bubbles]: Common Bubble Tea components such as text inputs, viewports, spinners and so on\n- [Lip Gloss][lipgloss]: Style, format and layout tools for terminal applications\n- [Harmonica][harmonica]: A spring animation library for smooth, natural motion\n- [BubbleZone][bubblezone]: Easy mouse event tracking for Bubble Tea components\n- [ntcharts][ntcharts]: A terminal charting library built for Bubble Tea and [Lip Gloss][lipgloss]\n\n[bubbles]: https://github.com/charmbracelet/bubbles\n[lipgloss]: https://github.com/charmbracelet/lipgloss\n[harmonica]: https://github.com/charmbracelet/harmonica\n[bubblezone]: https://github.com/lrstanley/bubblezone\n[ntcharts]: https://github.com/NimbleMarkets/ntcharts\n\n## Bubble Tea in the Wild\n\nThere are over [18,000 applications](https://github.com/charmbracelet/bubbletea/network/dependents) built with Bubble Tea! Here are a handful of ’em.\n\n### Staff favourites\n\n- [chezmoi](https://github.com/twpayne/chezmoi): securely manage your dotfiles across multiple machines\n- [circumflex](https://github.com/bensadeh/circumflex): read Hacker News in the terminal\n- [gh-dash](https://www.github.com/dlvhdr/gh-dash): a GitHub CLI extension for PRs and issues\n- [Tetrigo](https://github.com/Broderick-Westrope/tetrigo): Tetris in the terminal\n- [Signls](https://github.com/emprcl/signls): a generative midi sequencer designed for composition and live performance\n- [Superfile](https://github.com/yorukot/superfile): a super file manager\n\n### In Industry\n\n- Microsoft Azure – [Aztify](https://github.com/Azure/aztfy): bring Microsoft Azure resources under Terraform\n- Daytona – [Daytona](https://github.com/daytonaio/daytona): an AI infrastructure platform\n- Cockroach Labs – [CockroachDB](https://github.com/cockroachdb/cockroach): a cloud-native, high-availability distributed SQL database\n- Truffle Security Co. – [Trufflehog](https://github.com/trufflesecurity/trufflehog): find leaked credentials\n- NVIDIA – [container-canary](https://github.com/NVIDIA/container-canary): a container validator\n- AWS – [eks-node-viewer](https://github.com/awslabs/eks-node-viewer): a tool for visualizing dynamic node usage within an EKS cluster\n- MinIO – [mc](https://github.com/minio/mc): the official [MinIO](https://min.io) client\n- Ubuntu – [Authd](https://github.com/ubuntu/authd): an authentication daemon for cloud-based identity providers\n\n### Charm stuff\n\n- [Glow](https://github.com/charmbracelet/glow): a markdown reader, browser, and online markdown stash\n- [Huh?](https://github.com/charmbracelet/huh): an interactive prompt and form toolkit\n- [Mods](https://github.com/charmbracelet/mods): AI on the CLI, built for pipelines\n- [Wishlist](https://github.com/charmbracelet/wishlist): an SSH directory (and bastion!)\n\n### There’s so much more where that came from\n\nFor more applications built with Bubble Tea see [Charm & Friends][community].\nIs there something cool you made with Bubble Tea you want to share? [PRs][community] are\nwelcome!\n\n## Contributing\n\nSee [contributing][contribute].\n\n[contribute]: https://github.com/charmbracelet/bubbletea/contribute\n\n## Feedback\n\nWe’d love to hear your thoughts on this project. Feel free to drop us a note!\n\n- [Twitter](https://twitter.com/charmcli)\n- [The Fediverse](https://mastodon.social/@charmcli)\n- [Discord](https://charm.sh/chat)\n\n## Acknowledgments\n\nBubble Tea is based on the paradigms of [The Elm Architecture][elm] by Evan\nCzaplicki et alia and the excellent [go-tea][gotea] by TJ Holowaychuk. It’s\ninspired by the many great [_Zeichenorientierte Benutzerschnittstellen_][zb]\nof days past.\n\n[elm]: https://guide.elm-lang.org/architecture/\n[gotea]: https://github.com/tj/go-tea\n[zb]: https://de.wikipedia.org/wiki/Zeichenorientierte_Benutzerschnittstelle\n[community]: https://github.com/charm-and-friends/charm-in-the-wild\n\n## License\n\n[MIT](https://github.com/charmbracelet/bubbletea/raw/main/LICENSE)\n\n---\n\nPart of [Charm](https://charm.sh).\n\n<a href=\"https://charm.sh/\"><img alt=\"The Charm logo\" src=\"https://stuff.charm.sh/charm-banner-next.jpg\" width=\"400\"></a>\n\nCharm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة\n"
  },
  {
    "path": "Taskfile.yaml",
    "content": "# https://taskfile.dev\n\nversion: '3'\n\ntasks:\n  lint:\n    desc: Run lint\n    cmds:\n      - golangci-lint run\n\n  test:\n    desc: Run tests\n    cmds:\n      - go test ./... {{.CLI_ARGS}}\n"
  },
  {
    "path": "UPGRADE_GUIDE_V2.md",
    "content": "# Bubble Tea v2 Upgrade Guide\n\nThis guide covers everything you need to change when upgrading from Bubble Tea v1 to v2. For a tour of all the exciting new features, check out the [What's New](https://github.com/charmbracelet/bubbletea/releases/tag/v2.0.0) doc.\n\n> [!NOTE]\n> We don't take API changes lightly and strive to make the upgrade process as simple as possible. If something feels way off, let us know.\n\n## Migration Checklist\n\nHere's the short version — a checklist you can follow top to bottom. Each item links to the relevant section below.\n\n- [ ] [Update import paths](#import-paths)\n- [ ] [Change `View() string` to `View() tea.View`](#view-returns-a-teaview-now)\n- [ ] [Replace `tea.KeyMsg` with `tea.KeyPressMsg`](#key-messages)\n- [ ] [Update key fields: `msg.Type` / `msg.Runes` / `msg.Alt`](#key-messages)\n- [ ] [Replace `case \" \":` with `case \"space\":`](#key-messages)\n- [ ] [Update mouse message usage](#mouse-messages)\n- [ ] [Rename mouse button constants](#mouse-messages)\n- [ ] [Remove old program options → use View fields](#removed-program-options)\n- [ ] [Remove imperative commands → use View fields](#removed-commands)\n- [ ] [Remove old program methods](#removed-program-methods)\n- [ ] [Rename `tea.WindowSize()` → `tea.RequestWindowSize`](#renamed-apis)\n- [ ] [Replace `tea.Sequentially(...)` → `tea.Sequence(...)`](#renamed-apis)\n\n## Import Paths\n\nThe module path changed to a vanity domain. Lip Gloss moved too.\n\n```go\n// Before\nimport tea \"github.com/charmbracelet/bubbletea\"\nimport \"github.com/charmbracelet/lipgloss\"\n\n// After\nimport tea \"charm.land/bubbletea/v2\"\nimport \"charm.land/lipgloss/v2\"\n```\n\n## The Big Idea: Declarative Views\n\nThe single biggest change in v2 is the shift from **imperative commands** to **declarative View fields**. In v1, you'd use program options like `tea.WithAltScreen()` and commands like `tea.EnterAltScreen` to toggle terminal features on and off. In v2, you just set fields on the `tea.View` struct in your `View()` method and Bubble Tea handles the rest.\n\nThis means: no more startup option flags, no more toggle commands, no more fighting over state. Just declare what you want and Bubble Tea will make it so.\n\n```go\n// v1: imperative — scattered across NewProgram, Init, and Update\np := tea.NewProgram(model{}, tea.WithAltScreen(), tea.WithMouseCellMotion())\n\n// v2: declarative — everything lives in View()\nfunc (m model) View() tea.View {\n    v := tea.NewView(\"Hello!\")\n    v.AltScreen = true\n    v.MouseMode = tea.MouseModeCellMotion\n    return v\n}\n```\n\nKeep this in mind as you go through the rest of the guide — most of the \"removed\" things simply moved into View fields.\n\n## View Returns a `tea.View` Now\n\nThe `View()` method no longer returns a `string`. It returns a `tea.View` struct.\n\n```go\n// Before:\nfunc (m model) View() string {\n    return \"Hello, world!\"\n}\n\n// After:\nfunc (m model) View() tea.View {\n    return tea.NewView(\"Hello, world!\")\n}\n```\n\nYou can also use the longer form if you need to set additional fields:\n\n```go\nfunc (m model) View() tea.View {\n    var v tea.View\n    v.SetContent(\"Hello, world!\")\n    v.AltScreen = true\n    return v\n}\n```\n\nThe `tea.View` struct has fields for everything that used to be controlled by options and commands:\n\n| View Field | What It Does |\n|---|---|\n| `Content` | The rendered string (set via `SetContent()` or `NewView()`) |\n| `AltScreen` | Enter/exit the alternate screen buffer |\n| `MouseMode` | `MouseModeNone`, `MouseModeCellMotion`, or `MouseModeAllMotion` |\n| `ReportFocus` | Enable focus/blur event reporting |\n| `DisableBracketedPasteMode` | Disable bracketed paste |\n| `WindowTitle` | Set the terminal window title |\n| `Cursor` | Control cursor position, shape, color, and blink |\n| `ForegroundColor` | Set the terminal foreground color |\n| `BackgroundColor` | Set the terminal background color |\n| `ProgressBar` | Show a native terminal progress bar |\n| `KeyboardEnhancements` | Request keyboard enhancement features |\n| `OnMouse` | Intercept mouse messages based on view content |\n\n## Key Messages\n\nKey messages got a major overhaul. Here's the quick rundown:\n\n### `tea.KeyMsg` is now an interface\n\nIn v1, `tea.KeyMsg` was a struct you'd match on for key presses. In v2, it's an **interface** that covers both key presses and releases. For most code, you want `tea.KeyPressMsg`:\n\n```go\n// Before:\ncase tea.KeyMsg:\n    switch msg.String() {\n    case \"q\":\n        return m, tea.Quit\n    }\n\n// After:\ncase tea.KeyPressMsg:\n    switch msg.String() {\n    case \"q\":\n        return m, tea.Quit\n    }\n```\n\nIf you want to handle both presses and releases, use `tea.KeyMsg` and type-switch inside:\n\n```go\ncase tea.KeyMsg:\n    switch key := msg.(type) {\n    case tea.KeyPressMsg:\n        // key press\n    case tea.KeyReleaseMsg:\n        // key release\n    }\n```\n\n### Key fields changed\n\n| v1 | v2 | Notes |\n|---|---|---|\n| `msg.Type` | `msg.Code` | A `rune` — can be `tea.KeyEnter`, `'a'`, etc. |\n| `msg.Runes` | `msg.Text` | Now a `string`, not `[]rune` |\n| `msg.Alt` | `msg.Mod` | `msg.Mod.Contains(tea.ModAlt)` for alt, etc. |\n| `tea.KeyRune` | — | Check `len(msg.Text) > 0` instead |\n| `tea.KeyCtrlC` | — | Use `msg.String() == \"ctrl+c\"` or check `msg.Code` + `msg.Mod` |\n\n### Space bar changed\n\nSpace bar now returns `\"space\"` instead of `\" \"` when using `msg.String()`:\n\n```go\n// Before:\ncase \" \":\n\n// After:\ncase \"space\":\n```\n\n`key.Code` is still `' '` and `key.Text` is still `\" \"`, but `String()` returns `\"space\"`.\n\n### Ctrl+key matching\n\n```go\n// Before:\ncase tea.KeyCtrlC:\n    // ctrl+c\n\n// After (option A — string matching):\ncase tea.KeyPressMsg:\n    switch msg.String() {\n    case \"ctrl+c\":\n        // ctrl+c\n    }\n\n// After (option B — field matching):\ncase tea.KeyPressMsg:\n    if msg.Code == 'c' && msg.Mod == tea.ModCtrl {\n        // ctrl+c\n    }\n```\n\n### New Key fields\n\nThese are new in v2 and don't have v1 equivalents:\n\n- **`key.ShiftedCode`** — the shifted key code (e.g., `'B'` when pressing shift+b)\n- **`key.BaseCode`** — the key on a US PC-101 layout (handy for international keyboards)\n- **`key.IsRepeat`** — whether the key is auto-repeating (Kitty protocol / Windows Console only)\n- **`key.Keystroke()`** — like `String()` but always includes modifier info\n\n## Paste Messages\n\nPaste events no longer come in as `tea.KeyMsg` with a `Paste` flag. They're now their own message types:\n\n```go\n// Before:\ncase tea.KeyMsg:\n    if msg.Paste {\n        m.text += string(msg.Runes)\n    }\n\n// After:\ncase tea.PasteMsg:\n    m.text += msg.Content\ncase tea.PasteStartMsg:\n    // paste started\ncase tea.PasteEndMsg:\n    // paste ended\n```\n\n## Mouse Messages\n\n### `tea.MouseMsg` is now an interface\n\nIn v1, `tea.MouseMsg` was a struct with `X`, `Y`, `Button`, etc. In v2, it's an **interface**. You get the coordinates by calling `msg.Mouse()`:\n\n```go\n// Before:\ncase tea.MouseMsg:\n    x, y := msg.X, msg.Y\n\n// After:\ncase tea.MouseMsg:\n    mouse := msg.Mouse()\n    x, y := mouse.X, mouse.Y\n```\n\n### Mouse events are split by type\n\nInstead of checking `msg.Action`, match on specific message types:\n\n```go\n// Before:\ncase tea.MouseMsg:\n    if msg.Action == tea.MouseActionPress && msg.Button == tea.MouseButtonLeft {\n        // left click\n    }\n\n// After:\ncase tea.MouseClickMsg:\n    if msg.Button == tea.MouseLeft {\n        // left click\n    }\ncase tea.MouseReleaseMsg:\n    // release\ncase tea.MouseWheelMsg:\n    // scroll\ncase tea.MouseMotionMsg:\n    // movement\n```\n\n### Button constants renamed\n\n| v1 | v2 |\n|---|---|\n| `tea.MouseButtonLeft` | `tea.MouseLeft` |\n| `tea.MouseButtonRight` | `tea.MouseRight` |\n| `tea.MouseButtonMiddle` | `tea.MouseMiddle` |\n| `tea.MouseButtonWheelUp` | `tea.MouseWheelUp` |\n| `tea.MouseButtonWheelDown` | `tea.MouseWheelDown` |\n| `tea.MouseButtonWheelLeft` | `tea.MouseWheelLeft` |\n| `tea.MouseButtonWheelRight` | `tea.MouseWheelRight` |\n\n### `tea.MouseEvent` → `tea.Mouse`\n\nThe `MouseEvent` struct is gone. The new `Mouse` struct has `X`, `Y`, `Button`, and `Mod` fields.\n\n### Mouse mode is now a View field\n\n```go\n// Before:\np := tea.NewProgram(model{}, tea.WithMouseCellMotion())\n\n// After:\nfunc (m model) View() tea.View {\n    v := tea.NewView(\"...\")\n    v.MouseMode = tea.MouseModeCellMotion\n    return v\n}\n```\n\n## Removed Program Options\n\nThese options no longer exist. They all moved to View fields.\n\n| Removed Option | Do This Instead |\n|---|---|\n| `tea.WithAltScreen()` | `view.AltScreen = true` |\n| `tea.WithMouseCellMotion()` | `view.MouseMode = tea.MouseModeCellMotion` |\n| `tea.WithMouseAllMotion()` | `view.MouseMode = tea.MouseModeAllMotion` |\n| `tea.WithReportFocus()` | `view.ReportFocus = true` |\n| `tea.WithoutBracketedPaste()` | `view.DisableBracketedPasteMode = true` |\n| `tea.WithInputTTY()` | Just remove it — v2 always opens the TTY for input automatically |\n| `tea.WithANSICompressor()` | Just remove it — the new renderer handles optimization automatically |\n\n## Removed Commands\n\nThese commands no longer exist. Set the corresponding View field instead.\n\n| Removed Command | Do This Instead |\n|---|---|\n| `tea.EnterAltScreen` | `view.AltScreen = true` |\n| `tea.ExitAltScreen` | `view.AltScreen = false` |\n| `tea.EnableMouseCellMotion` | `view.MouseMode = tea.MouseModeCellMotion` |\n| `tea.EnableMouseAllMotion` | `view.MouseMode = tea.MouseModeAllMotion` |\n| `tea.DisableMouse` | `view.MouseMode = tea.MouseModeNone` |\n| `tea.HideCursor` | `view.Cursor = nil` |\n| `tea.ShowCursor` | `view.Cursor = &tea.Cursor{...}` or `tea.NewCursor(x, y)` |\n| `tea.EnableBracketedPaste` | `view.DisableBracketedPasteMode = false` |\n| `tea.DisableBracketedPaste` | `view.DisableBracketedPasteMode = true` |\n| `tea.EnableReportFocus` | `view.ReportFocus = true` |\n| `tea.DisableReportFocus` | `view.ReportFocus = false` |\n| `tea.SetWindowTitle(\"...\")` | `view.WindowTitle = \"...\"` |\n\n## Removed Program Methods\n\nThese methods on `*Program` are gone.\n\n| Removed Method | Do This Instead |\n|---|---|\n| `p.Start()` | `p.Run()` |\n| `p.StartReturningModel()` | `p.Run()` |\n| `p.EnterAltScreen()` | `view.AltScreen = true` in `View()` |\n| `p.ExitAltScreen()` | `view.AltScreen = false` in `View()` |\n| `p.EnableMouseCellMotion()` | `view.MouseMode` in `View()` |\n| `p.DisableMouseCellMotion()` | `view.MouseMode = tea.MouseModeNone` in `View()` |\n| `p.EnableMouseAllMotion()` | `view.MouseMode` in `View()` |\n| `p.DisableMouseAllMotion()` | `view.MouseMode = tea.MouseModeNone` in `View()` |\n| `p.SetWindowTitle(...)` | `view.WindowTitle` in `View()` |\n\n## Renamed APIs\n\n| v1 | v2 | Notes |\n|---|---|---|\n| `tea.Sequentially(...)` | `tea.Sequence(...)` | `Sequentially` was already deprecated in v1 |\n| `tea.WindowSize()` | `tea.RequestWindowSize` | Now returns `Msg` directly, not a `Cmd` |\n\n## New Program Options\n\nThese are new in v2:\n\n| Option | What It Does |\n|---|---|\n| `tea.WithColorProfile(p)` | Force a specific color profile (great for testing) |\n| `tea.WithWindowSize(w, h)` | Set initial terminal size (great for testing) |\n\n## Complete Before & After\n\nHere's a minimal but complete program showing the most common migration patterns side by side.\n\n**v1:**\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"os\"\n\n    tea \"github.com/charmbracelet/bubbletea\"\n)\n\ntype model struct {\n    count int\n}\n\nfunc (m model) Init() tea.Cmd {\n    return nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    switch msg := msg.(type) {\n    case tea.KeyMsg:\n        switch msg.String() {\n        case \"q\", \"ctrl+c\":\n            return m, tea.Quit\n        case \" \":\n            m.count++\n        }\n    case tea.MouseMsg:\n        if msg.Action == tea.MouseActionPress && msg.Button == tea.MouseButtonLeft {\n            m.count++\n        }\n    }\n    return m, nil\n}\n\nfunc (m model) View() string {\n    return fmt.Sprintf(\"Count: %d\\n\\nSpace or click to increment. q to quit.\\n\", m.count)\n}\n\nfunc main() {\n    p := tea.NewProgram(model{}, tea.WithAltScreen(), tea.WithMouseCellMotion())\n    if _, err := p.Run(); err != nil {\n        fmt.Fprintln(os.Stderr, err)\n        os.Exit(1)\n    }\n}\n```\n\n**v2:**\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"os\"\n\n    tea \"charm.land/bubbletea/v2\"\n)\n\ntype model struct {\n    count int\n}\n\nfunc (m model) Init() tea.Cmd {\n    return nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    switch msg := msg.(type) {\n    case tea.KeyPressMsg:\n        switch msg.String() {\n        case \"q\", \"ctrl+c\":\n            return m, tea.Quit\n        case \"space\":\n            m.count++\n        }\n    case tea.MouseClickMsg:\n        if msg.Button == tea.MouseLeft {\n            m.count++\n        }\n    }\n    return m, nil\n}\n\nfunc (m model) View() tea.View {\n    v := tea.NewView(fmt.Sprintf(\"Count: %d\\n\\nSpace or click to increment. q to quit.\\n\", m.count))\n    v.AltScreen = true\n    v.MouseMode = tea.MouseModeCellMotion\n    return v\n}\n\nfunc main() {\n    p := tea.NewProgram(model{})\n    if _, err := p.Run(); err != nil {\n        fmt.Fprintln(os.Stderr, err)\n        os.Exit(1)\n    }\n}\n```\n\nNotice how the `NewProgram` call got simpler? All the terminal feature flags moved into `View()` where they belong.\n\n## Quick Reference\n\nA flat old → new lookup table. Handy for search-and-replace and LLM-assisted migration.\n\n### Import Paths\n\n| v1 | v2 |\n|---|---|\n| `github.com/charmbracelet/bubbletea` | `charm.land/bubbletea/v2` |\n| `github.com/charmbracelet/lipgloss` | `charm.land/lipgloss/v2` |\n\n### Model Interface\n\n| v1 | v2 |\n|---|---|\n| `View() string` | `View() tea.View` |\n\n### Key Events\n\n| v1 | v2 |\n|---|---|\n| `tea.KeyMsg` (struct) | `tea.KeyPressMsg` for presses, `tea.KeyMsg` (interface) for both |\n| `msg.Type` | `msg.Code` |\n| `msg.Runes` | `msg.Text` (string, not `[]rune`) |\n| `msg.Alt` | `msg.Mod.Contains(tea.ModAlt)` |\n| `tea.KeyRune` | check `len(msg.Text) > 0` |\n| `tea.KeyCtrlC` | `msg.Code == 'c' && msg.Mod == tea.ModCtrl` or `msg.String() == \"ctrl+c\"` |\n| `case \" \":` (space) | `case \"space\":` |\n\n### Mouse Events\n\n| v1 | v2 |\n|---|---|\n| `tea.MouseMsg` (struct) | `tea.MouseMsg` (interface) — call `.Mouse()` for the data |\n| `tea.MouseEvent` | `tea.Mouse` |\n| `tea.MouseButtonLeft` | `tea.MouseLeft` |\n| `tea.MouseButtonRight` | `tea.MouseRight` |\n| `tea.MouseButtonMiddle` | `tea.MouseMiddle` |\n| `tea.MouseButtonWheelUp` | `tea.MouseWheelUp` |\n| `tea.MouseButtonWheelDown` | `tea.MouseWheelDown` |\n| `msg.X`, `msg.Y` (direct) | `msg.Mouse().X`, `msg.Mouse().Y` |\n\n### Options → View Fields\n\n| v1 Option | v2 View Field |\n|---|---|\n| `tea.WithAltScreen()` | `view.AltScreen = true` |\n| `tea.WithMouseCellMotion()` | `view.MouseMode = tea.MouseModeCellMotion` |\n| `tea.WithMouseAllMotion()` | `view.MouseMode = tea.MouseModeAllMotion` |\n| `tea.WithReportFocus()` | `view.ReportFocus = true` |\n| `tea.WithoutBracketedPaste()` | `view.DisableBracketedPasteMode = true` |\n\n### Commands → View Fields\n\n| v1 Command | v2 View Field |\n|---|---|\n| `tea.EnterAltScreen` / `tea.ExitAltScreen` | `view.AltScreen = true/false` |\n| `tea.EnableMouseCellMotion` | `view.MouseMode = tea.MouseModeCellMotion` |\n| `tea.EnableMouseAllMotion` | `view.MouseMode = tea.MouseModeAllMotion` |\n| `tea.DisableMouse` | `view.MouseMode = tea.MouseModeNone` |\n| `tea.HideCursor` / `tea.ShowCursor` | `view.Cursor = nil` / `view.Cursor = &tea.Cursor{...}` |\n| `tea.EnableBracketedPaste` / `tea.DisableBracketedPaste` | `view.DisableBracketedPasteMode = false/true` |\n| `tea.EnableReportFocus` / `tea.DisableReportFocus` | `view.ReportFocus = true/false` |\n| `tea.SetWindowTitle(\"...\")` | `view.WindowTitle = \"...\"` |\n\n### Removed Options (No Replacement Needed)\n\n| v1 Option | What Happened |\n|---|---|\n| `tea.WithInputTTY()` | v2 always opens the TTY for input automatically |\n| `tea.WithANSICompressor()` | The new renderer handles optimization automatically |\n\n### Removed Program Methods\n\n| v1 Method | v2 Replacement |\n|---|---|\n| `p.Start()` | `p.Run()` |\n| `p.StartReturningModel()` | `p.Run()` |\n| `p.EnterAltScreen()` | `view.AltScreen = true` in `View()` |\n| `p.ExitAltScreen()` | `view.AltScreen = false` in `View()` |\n| `p.EnableMouseCellMotion()` | `view.MouseMode` in `View()` |\n| `p.DisableMouseCellMotion()` | `view.MouseMode = tea.MouseModeNone` in `View()` |\n| `p.EnableMouseAllMotion()` | `view.MouseMode` in `View()` |\n| `p.DisableMouseAllMotion()` | `view.MouseMode = tea.MouseModeNone` in `View()` |\n| `p.SetWindowTitle(...)` | `view.WindowTitle` in `View()` |\n\n### Other Renames\n\n| v1 | v2 |\n|---|---|\n| `tea.Sequentially(...)` | `tea.Sequence(...)` |\n| `tea.WindowSize()` | `tea.RequestWindowSize` (now returns `Msg`, not `Cmd`) |\n\n### New Program Options\n\n| Option | Description |\n|---|---|\n| `tea.WithColorProfile(p)` | Force a specific color profile |\n| `tea.WithWindowSize(w, h)` | Set initial window size (great for testing) |\n\n## Feedback\n\nHave thoughts on the v2 upgrade? We'd _love_ to hear about it. Let us know on…\n\n- [Discord](https://charm.land/chat)\n- [Matrix](https://charm.land/matrix)\n- [Email](mailto:vt100@charm.land)\n\n---\n\nPart of [Charm](https://charm.land).\n\n<a href=\"https://charm.land/\"><img alt=\"The Charm logo\" src=\"https://stuff.charm.sh/charm-badge.jpg\" width=\"400\"></a>\n\nCharm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة\n"
  },
  {
    "path": "clipboard.go",
    "content": "package tea\n\n// ClipboardMsg is a clipboard read message event. This message is emitted when\n// a terminal receives an OSC52 clipboard read message event.\ntype ClipboardMsg struct {\n\tContent   string\n\tSelection byte\n}\n\n// Clipboard returns the clipboard selection type. This will be one of the\n// following values:\n//\n//   - c: System clipboard.\n//   - p: Primary clipboard (X11/Wayland only).\nfunc (e ClipboardMsg) Clipboard() byte {\n\treturn e.Selection\n}\n\n// String returns the string representation of the clipboard message.\nfunc (e ClipboardMsg) String() string {\n\treturn e.Content\n}\n\n// setClipboardMsg is an internal message used to set the system clipboard\n// using OSC52.\ntype setClipboardMsg string\n\n// SetClipboard produces a command that sets the system clipboard using OSC52.\n// Note that OSC52 is not supported in all terminals.\nfunc SetClipboard(s string) Cmd {\n\treturn func() Msg {\n\t\treturn setClipboardMsg(s)\n\t}\n}\n\n// readClipboardMsg is an internal message used to read the system clipboard\n// using OSC52.\ntype readClipboardMsg struct{}\n\n// ReadClipboard produces a command that reads the system clipboard using OSC52.\n// Note that OSC52 is not supported in all terminals.\nfunc ReadClipboard() Msg {\n\treturn readClipboardMsg{}\n}\n\n// setPrimaryClipboardMsg is an internal message used to set the primary\n// clipboard using OSC52.\ntype setPrimaryClipboardMsg string\n\n// SetPrimaryClipboard produces a command that sets the primary clipboard using\n// OSC52. Primary clipboard selection is a feature present in X11 and Wayland\n// only.\n// Note that OSC52 is not supported in all terminals.\nfunc SetPrimaryClipboard(s string) Cmd {\n\treturn func() Msg {\n\t\treturn setPrimaryClipboardMsg(s)\n\t}\n}\n\n// readPrimaryClipboardMsg is an internal message used to read the primary\n// clipboard using OSC52.\ntype readPrimaryClipboardMsg struct{}\n\n// ReadPrimaryClipboard produces a command that reads the primary clipboard\n// using OSC52. Primary clipboard selection is a feature present in X11 and\n// Wayland only.\n// Note that OSC52 is not supported in all terminals.\nfunc ReadPrimaryClipboard() Msg {\n\treturn readPrimaryClipboardMsg{}\n}\n"
  },
  {
    "path": "color.go",
    "content": "package tea\n\nimport (\n\t\"image/color\"\n\n\tuv \"github.com/charmbracelet/ultraviolet\"\n)\n\n// backgroundColorMsg is a message that requests the terminal background color.\ntype backgroundColorMsg struct{}\n\n// RequestBackgroundColor is a command that requests the terminal background color.\nfunc RequestBackgroundColor() Msg {\n\treturn backgroundColorMsg{}\n}\n\n// foregroundColorMsg is a message that requests the terminal foreground color.\ntype foregroundColorMsg struct{}\n\n// RequestForegroundColor is a command that requests the terminal foreground color.\nfunc RequestForegroundColor() Msg {\n\treturn foregroundColorMsg{}\n}\n\n// cursorColorMsg is a message that requests the terminal cursor color.\ntype cursorColorMsg struct{}\n\n// RequestCursorColor is a command that requests the terminal cursor color.\nfunc RequestCursorColor() Msg {\n\treturn cursorColorMsg{}\n}\n\n// ForegroundColorMsg represents a foreground color message. This message is\n// emitted when the program requests the terminal foreground color with the\n// [RequestForegroundColor] Cmd.\ntype ForegroundColorMsg struct{ color.Color }\n\n// String returns the hex representation of the color.\nfunc (e ForegroundColorMsg) String() string {\n\treturn uv.ForegroundColorEvent(e).String()\n}\n\n// IsDark returns whether the color is dark.\nfunc (e ForegroundColorMsg) IsDark() bool {\n\treturn uv.ForegroundColorEvent(e).IsDark()\n}\n\n// BackgroundColorMsg represents a background color message. This message is\n// emitted when the program requests the terminal background color with the\n// [RequestBackgroundColor] Cmd.\n//\n// This is commonly used in [Update.Init] to get the terminal background color\n// for style definitions. For that you'll want to call\n// [BackgroundColorMsg.IsDark] to determine if the color is dark or light. For\n// example:\n//\n//\tfunc (m Model) Init() (Model, Cmd) {\n//\t  return m, RequestBackgroundColor()\n//\t}\n//\n//\tfunc (m Model) Update(msg Msg) (Model, Cmd) {\n//\t  switch msg := msg.(type) {\n//\t  case BackgroundColorMsg:\n//\t      m.styles = newStyles(msg.IsDark())\n//\t  }\n//\t}\ntype BackgroundColorMsg struct{ color.Color }\n\n// String returns the hex representation of the color.\nfunc (e BackgroundColorMsg) String() string {\n\treturn uv.BackgroundColorEvent(e).String()\n}\n\n// IsDark returns whether the color is dark.\nfunc (e BackgroundColorMsg) IsDark() bool {\n\treturn uv.BackgroundColorEvent(e).IsDark()\n}\n\n// CursorColorMsg represents a cursor color change message. This message is\n// emitted when the program requests the terminal cursor color.\ntype CursorColorMsg struct{ color.Color }\n\n// String returns the hex representation of the color.\nfunc (e CursorColorMsg) String() string {\n\treturn uv.CursorColorEvent(e).String()\n}\n\n// IsDark returns whether the color is dark.\nfunc (e CursorColorMsg) IsDark() bool {\n\treturn uv.CursorColorEvent(e).IsDark()\n}\n"
  },
  {
    "path": "commands.go",
    "content": "package tea\n\nimport (\n\t\"time\"\n)\n\n// Batch performs a bunch of commands concurrently with no ordering guarantees\n// about the results. Use a Batch to return several commands.\n//\n// Example:\n//\n//\t    func (m model) Init() (Model, Cmd) {\n//\t\t       return m, tea.Batch(someCommand, someOtherCommand)\n//\t    }\nfunc Batch(cmds ...Cmd) Cmd {\n\treturn compactCmds[BatchMsg](cmds)\n}\n\n// BatchMsg is a message used to perform a bunch of commands concurrently with\n// no ordering guarantees. You can send a BatchMsg with Batch.\ntype BatchMsg []Cmd\n\n// Sequence runs the given commands one at a time, in order. Contrast this with\n// Batch, which runs commands concurrently.\nfunc Sequence(cmds ...Cmd) Cmd {\n\treturn compactCmds[sequenceMsg](cmds)\n}\n\n// sequenceMsg is used internally to run the given commands in order.\ntype sequenceMsg []Cmd\n\n// compactCmds ignores any nil commands in cmds, and returns the most direct\n// command possible. That is, considering the non-nil commands, if there are\n// none it returns nil, if there is exactly one it returns that command\n// directly, else it returns the non-nil commands as type T.\nfunc compactCmds[T ~[]Cmd](cmds []Cmd) Cmd {\n\tvar validCmds []Cmd\n\tfor _, c := range cmds {\n\t\tif c == nil {\n\t\t\tcontinue\n\t\t}\n\t\tvalidCmds = append(validCmds, c)\n\t}\n\tswitch len(validCmds) {\n\tcase 0:\n\t\treturn nil\n\tcase 1:\n\t\treturn validCmds[0]\n\tdefault:\n\t\treturn func() Msg {\n\t\t\treturn T(validCmds)\n\t\t}\n\t}\n}\n\n// Every is a command that ticks in sync with the system clock. So, if you\n// wanted to tick with the system clock every second, minute or hour you\n// could use this. It's also handy for having different things tick in sync.\n//\n// Because we're ticking with the system clock the tick will likely not run for\n// the entire specified duration. For example, if we're ticking for one minute\n// and the clock is at 12:34:20 then the next tick will happen at 12:35:00, 40\n// seconds later.\n//\n// To produce the command, pass a duration and a function which returns\n// a message containing the time at which the tick occurred.\n//\n//\ttype TickMsg time.Time\n//\n//\tcmd := Every(time.Second, func(t time.Time) Msg {\n//\t   return TickMsg(t)\n//\t})\n//\n// Beginners' note: Every sends a single message and won't automatically\n// dispatch messages at an interval. To do that, you'll want to return another\n// Every command after receiving your tick message. For example:\n//\n//\ttype TickMsg time.Time\n//\n//\t// Send a message every second.\n//\tfunc tickEvery() Cmd {\n//\t    return Every(time.Second, func(t time.Time) Msg {\n//\t        return TickMsg(t)\n//\t    })\n//\t}\n//\n//\tfunc (m model) Init() (Model, Cmd) {\n//\t    // Start ticking.\n//\t    return m, tickEvery()\n//\t}\n//\n//\tfunc (m model) Update(msg Msg) (Model, Cmd) {\n//\t    switch msg.(type) {\n//\t    case TickMsg:\n//\t        // Return your Every command again to loop.\n//\t        return m, tickEvery()\n//\t    }\n//\t    return m, nil\n//\t}\n//\n// Every is analogous to Tick in the Elm Architecture.\nfunc Every(duration time.Duration, fn func(time.Time) Msg) Cmd {\n\tn := time.Now()\n\td := n.Truncate(duration).Add(duration).Sub(n)\n\tt := time.NewTimer(d)\n\treturn func() Msg {\n\t\tts := <-t.C\n\t\tt.Stop()\n\t\tfor len(t.C) > 0 {\n\t\t\t<-t.C\n\t\t}\n\t\treturn fn(ts)\n\t}\n}\n\n// Tick produces a command at an interval independent of the system clock at\n// the given duration. That is, the timer begins precisely when invoked,\n// and runs for its entire duration.\n//\n// To produce the command, pass a duration and a function which returns\n// a message containing the time at which the tick occurred.\n//\n//\ttype TickMsg time.Time\n//\n//\tcmd := Tick(time.Second, func(t time.Time) Msg {\n//\t   return TickMsg(t)\n//\t})\n//\n// Beginners' note: Tick sends a single message and won't automatically\n// dispatch messages at an interval. To do that, you'll want to return another\n// Tick command after receiving your tick message. For example:\n//\n//\ttype TickMsg time.Time\n//\n//\tfunc doTick() Cmd {\n//\t    return Tick(time.Second, func(t time.Time) Msg {\n//\t        return TickMsg(t)\n//\t    })\n//\t}\n//\n//\tfunc (m model) Init() (Model, Cmd) {\n//\t    // Start ticking.\n//\t    return m, doTick()\n//\t}\n//\n//\tfunc (m model) Update(msg Msg) (Model, Cmd) {\n//\t    switch msg.(type) {\n//\t    case TickMsg:\n//\t        // Return your Tick command again to loop.\n//\t        return m, doTick()\n//\t    }\n//\t    return m, nil\n//\t}\nfunc Tick(d time.Duration, fn func(time.Time) Msg) Cmd {\n\tt := time.NewTimer(d)\n\treturn func() Msg {\n\t\tts := <-t.C\n\t\tt.Stop()\n\t\tfor len(t.C) > 0 {\n\t\t\t<-t.C\n\t\t}\n\t\treturn fn(ts)\n\t}\n}\n\ntype windowSizeMsg struct{}\n\n// RequestWindowSize is a command that queries the terminal for its current\n// size. It delivers the results to Update via a [WindowSizeMsg]. Keep in mind\n// that WindowSizeMsgs will automatically be delivered to Update when the\n// [Program] starts and when the window dimensions change so in many cases you\n// will not need to explicitly invoke this command.\nfunc RequestWindowSize() Msg {\n\treturn windowSizeMsg{}\n}\n"
  },
  {
    "path": "commands_test.go",
    "content": "package tea\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestEvery(t *testing.T) {\n\texpected := \"every ms\"\n\tmsg := Every(time.Millisecond, func(t time.Time) Msg {\n\t\treturn expected\n\t})()\n\tif expected != msg {\n\t\tt.Fatalf(\"expected a msg %v but got %v\", expected, msg)\n\t}\n}\n\nfunc TestTick(t *testing.T) {\n\texpected := \"tick\"\n\tmsg := Tick(time.Millisecond, func(t time.Time) Msg {\n\t\treturn expected\n\t})()\n\tif expected != msg {\n\t\tt.Fatalf(\"expected a msg %v but got %v\", expected, msg)\n\t}\n}\n\nfunc TestBatch(t *testing.T) {\n\ttestMultipleCommands[BatchMsg](t, Batch)\n}\n\nfunc TestSequence(t *testing.T) {\n\ttestMultipleCommands[sequenceMsg](t, Sequence)\n}\n\nfunc testMultipleCommands[T ~[]Cmd](t *testing.T, createFn func(cmd ...Cmd) Cmd) {\n\tt.Run(\"nil cmd\", func(t *testing.T) {\n\t\tif b := createFn(nil); b != nil {\n\t\t\tt.Fatalf(\"expected nil, got %+v\", b)\n\t\t}\n\t})\n\tt.Run(\"empty cmd\", func(t *testing.T) {\n\t\tif b := createFn(); b != nil {\n\t\t\tt.Fatalf(\"expected nil, got %+v\", b)\n\t\t}\n\t})\n\tt.Run(\"single cmd\", func(t *testing.T) {\n\t\tb := createFn(Quit)()\n\t\tif _, ok := b.(QuitMsg); !ok {\n\t\t\tt.Fatalf(\"expected a QuitMsg, got %T\", b)\n\t\t}\n\t})\n\tt.Run(\"mixed nil cmds\", func(t *testing.T) {\n\t\tb := createFn(nil, Quit, nil, Quit, nil, nil)()\n\t\tif l := len(b.(T)); l != 2 {\n\t\t\tt.Fatalf(\"expected a []Cmd with len 2, got %d\", l)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "cursed_renderer.go",
    "content": "package tea\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"image/color\"\n\t\"io\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/charmbracelet/colorprofile\"\n\tuv \"github.com/charmbracelet/ultraviolet\"\n\t\"github.com/charmbracelet/x/ansi\"\n\t\"github.com/lucasb-eyer/go-colorful\"\n)\n\ntype cursedRenderer struct {\n\tw             io.Writer\n\tbuf           bytes.Buffer // updates buffer to be flushed to [w]\n\tscr           *uv.TerminalRenderer\n\tcellbuf       uv.ScreenBuffer\n\tlastView      *View\n\tenv           []string\n\tterm          string // the terminal type $TERM\n\twidth, height int\n\tmu            sync.Mutex\n\tprofile       colorprofile.Profile\n\tlogger        uv.Logger\n\tview          View\n\thardTabs      bool // whether to use hard tabs to optimize cursor movements\n\tbackspace     bool // whether to use backspace to optimize cursor movements\n\tmapnl         bool\n\tsyncdUpdates  bool // whether to use synchronized output mode for updates\n\tstarting      bool // indicates whether the renderer is starting after being stopped\n}\n\nvar _ renderer = &cursedRenderer{}\n\nfunc newCursedRenderer(w io.Writer, env []string, width, height int) (s *cursedRenderer) {\n\ts = new(cursedRenderer)\n\ts.w = w\n\ts.env = env\n\ts.term = uv.Environ(env).Getenv(\"TERM\")\n\ts.width, s.height = width, height // This needs to happen before [cursedRenderer.reset].\n\ts.cellbuf = uv.NewScreenBuffer(s.width, s.height)\n\treset(s)\n\treturn\n}\n\n// setLogger sets the logger for the renderer.\nfunc (s *cursedRenderer) setLogger(logger uv.Logger) {\n\ts.mu.Lock()\n\ts.logger = logger\n\ts.mu.Unlock()\n}\n\n// setOptimizations sets the cursor movement optimizations.\nfunc (s *cursedRenderer) setOptimizations(hardTabs, backspace, mapnl bool) {\n\ts.mu.Lock()\n\ts.hardTabs = hardTabs\n\ts.backspace = backspace\n\ts.mapnl = mapnl\n\ts.scr.SetTabStops(s.width)\n\ts.scr.SetBackspace(s.backspace)\n\ts.scr.SetMapNewline(s.mapnl)\n\ts.mu.Unlock()\n}\n\n// start implements renderer.\nfunc (s *cursedRenderer) start() {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\t// Mark that we're starting. This is used to restore some state when\n\t// starting the renderer again after it was stopped.\n\ts.starting = true\n\n\tif s.lastView == nil {\n\t\treturn\n\t}\n\n\tif s.lastView.AltScreen {\n\t\tenableAltScreen(s, true, true)\n\t}\n\tenableTextCursor(s, s.lastView.Cursor != nil)\n\tif s.lastView.Cursor != nil {\n\t\tif s.lastView.Cursor.Color != nil {\n\t\t\tcol, ok := colorful.MakeColor(s.lastView.Cursor.Color)\n\t\t\tif ok {\n\t\t\t\t_, _ = s.scr.WriteString(ansi.SetCursorColor(col.Hex()))\n\t\t\t}\n\t\t}\n\t\tcurStyle := encodeCursorStyle(s.lastView.Cursor.Shape, s.lastView.Cursor.Blink)\n\t\tif curStyle != 0 && curStyle != 1 {\n\t\t\t_, _ = s.scr.WriteString(ansi.SetCursorStyle(curStyle))\n\t\t}\n\t}\n\tif s.lastView.ForegroundColor != nil {\n\t\tcol, ok := colorful.MakeColor(s.lastView.ForegroundColor)\n\t\tif ok {\n\t\t\t_, _ = s.scr.WriteString(ansi.SetForegroundColor(col.Hex()))\n\t\t}\n\t}\n\tif s.lastView.BackgroundColor != nil {\n\t\tcol, ok := colorful.MakeColor(s.lastView.BackgroundColor)\n\t\tif ok {\n\t\t\t_, _ = s.scr.WriteString(ansi.SetBackgroundColor(col.Hex()))\n\t\t}\n\t}\n\tif !s.lastView.DisableBracketedPasteMode {\n\t\t_, _ = s.scr.WriteString(ansi.SetModeBracketedPaste)\n\t}\n\tif s.lastView.ReportFocus {\n\t\t_, _ = s.scr.WriteString(ansi.SetModeFocusEvent)\n\t}\n\tswitch s.lastView.MouseMode {\n\tcase MouseModeNone:\n\tcase MouseModeCellMotion:\n\t\t_, _ = s.scr.WriteString(ansi.SetModeMouseButtonEvent + ansi.SetModeMouseExtSgr)\n\tcase MouseModeAllMotion:\n\t\t_, _ = s.scr.WriteString(ansi.SetModeMouseAnyEvent + ansi.SetModeMouseExtSgr)\n\t}\n\tif s.lastView.WindowTitle != \"\" {\n\t\t_, _ = s.scr.WriteString(ansi.SetWindowTitle(s.lastView.WindowTitle))\n\t}\n\tif s.lastView.ProgressBar != nil {\n\t\tsetProgressBar(s, s.lastView.ProgressBar)\n\t}\n\t// Enable modifyOtherKeys and Kitty keyboard protocol.\n\t// Both can coexist; terminals ignore what they don't support.\n\t_, _ = s.scr.WriteString(ansi.SetModifyOtherKeys2)\n\n\tkittyFlags := ansi.KittyDisambiguateEscapeCodes\n\tif s.lastView.KeyboardEnhancements.ReportEventTypes {\n\t\tkittyFlags |= ansi.KittyReportEventTypes\n\t}\n\t_, _ = s.scr.WriteString(ansi.KittyKeyboard(kittyFlags, 1))\n}\n\n// close implements renderer.\nfunc (s *cursedRenderer) close() (err error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\t// Exit the altScreen and show cursor before closing. It's important that\n\t// we don't change the [cursedRenderer] altScreen and cursorHidden states\n\t// so that we can restore them when we start the renderer again. This is\n\t// used when the user suspends the program and then resumes it.\n\tif lv := s.lastView; lv != nil { //nolint:nestif\n\t\t// NOTE: The Kitty keyboard specs specify that the terminal should have\n\t\t// two registries for the main and alt screens. We disable keyboard\n\t\t// enhancements whenever we enter/exit alt screen mode in\n\t\t// [cursedRenderer.flush].\n\t\t// Here, we reset the keyboard protocol of the last screen used\n\t\t// assuming the other screen is already reset when we switched screens.\n\t\t_, _ = s.buf.WriteString(ansi.ResetModifyOtherKeys)\n\t\t_, _ = s.buf.WriteString(ansi.KittyKeyboard(0, 1))\n\n\t\t// Go to the bottom of the screen.\n\t\t// We need to go to the bottom of the screen regardless of whether\n\t\t// we're in alt screen mode or not to avoid leaving the cursor in the\n\t\t// middle in terminals that don't support alt screen mode.\n\t\ts.scr.MoveTo(0, s.cellbuf.Height()-1)\n\t\t_ = s.scr.Flush() // we need to flush to write the cursor movement\n\t\tif lv.AltScreen {\n\t\t\tenableAltScreen(s, false, true)\n\t\t} else {\n\t\t\t_, _ = s.scr.WriteString(ansi.EraseScreenBelow)\n\t\t}\n\t\tif lv.Cursor == nil {\n\t\t\tenableTextCursor(s, true)\n\t\t}\n\t\tif !lv.DisableBracketedPasteMode {\n\t\t\t_, _ = s.scr.WriteString(ansi.ResetModeBracketedPaste)\n\t\t}\n\t\tif lv.ReportFocus {\n\t\t\t_, _ = s.scr.WriteString(ansi.ResetModeFocusEvent)\n\t\t}\n\t\tswitch lv.MouseMode {\n\t\tcase MouseModeNone:\n\t\tcase MouseModeCellMotion, MouseModeAllMotion:\n\t\t\t_, _ = s.scr.WriteString(ansi.ResetModeMouseButtonEvent +\n\t\t\t\tansi.ResetModeMouseAnyEvent +\n\t\t\t\tansi.ResetModeMouseExtSgr)\n\t\t}\n\n\t\tif lv.WindowTitle != \"\" {\n\t\t\t// Clear the window title if it was set.\n\t\t\t_, _ = s.scr.WriteString(ansi.SetWindowTitle(\"\"))\n\t\t}\n\t\tif lc := lv.Cursor; lc != nil {\n\t\t\tcurShape := encodeCursorStyle(lc.Shape, lc.Blink)\n\t\t\tif curShape != 0 && curShape != 1 {\n\t\t\t\t// Reset the cursor style to default if it was set to something other\n\t\t\t\t// blinking block.\n\t\t\t\t_, _ = s.scr.WriteString(ansi.SetCursorStyle(0))\n\t\t\t}\n\n\t\t\tif lc.Color != nil {\n\t\t\t\t_, _ = s.scr.WriteString(ansi.ResetCursorColor)\n\t\t\t}\n\t\t}\n\n\t\tif lv.BackgroundColor != nil {\n\t\t\t_, _ = s.scr.WriteString(ansi.ResetBackgroundColor)\n\t\t}\n\t\tif lv.ForegroundColor != nil {\n\t\t\t_, _ = s.scr.WriteString(ansi.ResetForegroundColor)\n\t\t}\n\t\tif lv.ProgressBar != nil && lv.ProgressBar.State != ProgressBarNone {\n\t\t\t_, _ = s.scr.WriteString(ansi.ResetProgressBar)\n\t\t}\n\t}\n\n\tif s.cellbuf.Method == ansi.GraphemeWidth {\n\t\t// Make sure to turn off Unicode mode (2027)\n\t\t_, _ = s.scr.WriteString(ansi.ResetModeUnicodeCore)\n\t}\n\n\tif err := s.scr.Flush(); err != nil {\n\t\treturn fmt.Errorf(\"bubbletea: error closing screen writer: %w\", err)\n\t}\n\n\tif s.buf.Len() > 0 {\n\t\tif s.logger != nil {\n\t\t\ts.logger.Printf(\"output: %q\", s.buf.String())\n\t\t}\n\t\tif _, err := io.Copy(s.w, &s.buf); err != nil {\n\t\t\treturn fmt.Errorf(\"bubbletea: error writing to screen: %w\", err)\n\t\t}\n\t\ts.buf.Reset()\n\t}\n\n\tx, y := s.scr.Position()\n\n\t// We want to clear the renderer state but not the cursor position. This is\n\t// because we might be putting the tea process in the background, run some\n\t// other process, and then return to the tea process. We want to keep the\n\t// cursor position so that we can continue where we left off.\n\treset(s)\n\ts.scr.SetPosition(x, y)\n\n\treturn nil\n}\n\n// writeString implements renderer.\nfunc (s *cursedRenderer) writeString(str string) (int, error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\treturn s.scr.WriteString(str) //nolint:wrapcheck\n}\n\n// flush implements renderer.\nfunc (s *cursedRenderer) flush(closing bool) error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tview := s.view\n\tframeArea := uv.Rect(0, 0, s.width, s.height)\n\tif len(view.Content) == 0 {\n\t\t// If the component is nil, we should clear the screen buffer.\n\t\tframeArea.Max.Y = 0\n\t}\n\n\tcontent := uv.NewStyledString(view.Content)\n\tif !view.AltScreen {\n\t\t// We need to resizes the screen based on the frame height and\n\t\t// terminal width. This is because the frame height can change based on\n\t\t// the content of the frame. For example, if the frame contains a list\n\t\t// of items, the height of the frame will be the number of items in the\n\t\t// list. This is different from the alt screen buffer, which has a\n\t\t// fixed height and width.\n\t\tframeHeight := content.Height()\n\t\tif frameHeight != frameArea.Dy() {\n\t\t\tframeArea.Max.Y = frameHeight\n\t\t}\n\t}\n\n\tif !s.starting && !closing && s.lastView != nil && viewEquals(s.lastView, &view) && frameArea == s.cellbuf.Bounds() {\n\t\t// No changes, nothing to do.\n\t\treturn nil\n\t}\n\n\t// We're no longer starting.\n\ts.starting = false\n\n\tif frameArea != s.cellbuf.Bounds() {\n\t\ts.scr.Erase() // Force a full redraw to avoid artifacts.\n\n\t\t// We need to reset the touched lines buffer to match the new height.\n\t\ts.cellbuf.Touched = nil\n\n\t\t// Resize the screen buffer to match the frame area. This is necessary\n\t\t// to ensure that the screen buffer is the same size as the frame area\n\t\t// and to avoid rendering issues when the frame area is smaller than\n\t\t// the screen buffer.\n\t\ts.cellbuf.Resize(frameArea.Dx(), frameArea.Dy())\n\t}\n\n\t// Clear our screen buffer before copying the new frame into it to ensure\n\t// we erase any old content.\n\ts.cellbuf.Clear()\n\tcontent.Draw(s.cellbuf, s.cellbuf.Bounds())\n\n\t// If the frame height is greater than the screen height, we drop the\n\t// lines from the top of the buffer.\n\tif frameHeight := frameArea.Dy(); frameHeight > s.height {\n\t\ts.cellbuf.Lines = s.cellbuf.Lines[frameHeight-s.height:]\n\t}\n\n\t// Alt screen mode.\n\tshouldUpdateAltScreen := (s.lastView == nil && view.AltScreen) || (s.lastView != nil && s.lastView.AltScreen != view.AltScreen)\n\tif shouldUpdateAltScreen {\n\t\t// We want to enter/exit altscreen mode but defer writing the actual\n\t\t// sequences until we flush the rest of the updates. This is because we\n\t\t// control the cursor visibility and we need to ensure that happens\n\t\t// after entering/exiting alt screen mode. Some terminals have\n\t\t// different cursor visibility states for main and alt screen modes and\n\t\t// this ensures we handle that correctly.\n\t\tenableAltScreen(s, view.AltScreen, false)\n\t}\n\n\t// bracketed paste mode.\n\tif s.lastView == nil || view.DisableBracketedPasteMode != s.lastView.DisableBracketedPasteMode {\n\t\tif !view.DisableBracketedPasteMode {\n\t\t\t_, _ = s.scr.WriteString(ansi.SetModeBracketedPaste)\n\t\t} else if s.lastView != nil {\n\t\t\t_, _ = s.scr.WriteString(ansi.ResetModeBracketedPaste)\n\t\t}\n\t}\n\n\t// report focus events mode.\n\tif s.lastView == nil || s.lastView.ReportFocus != view.ReportFocus {\n\t\tif view.ReportFocus {\n\t\t\t_, _ = s.scr.WriteString(ansi.SetModeFocusEvent)\n\t\t} else if s.lastView != nil {\n\t\t\t_, _ = s.scr.WriteString(ansi.ResetModeFocusEvent)\n\t\t}\n\t}\n\n\t// mouse events mode.\n\tif s.lastView == nil || view.MouseMode != s.lastView.MouseMode {\n\t\tswitch view.MouseMode {\n\t\tcase MouseModeNone:\n\t\t\tif s.lastView != nil && s.lastView.MouseMode != MouseModeNone {\n\t\t\t\t_, _ = s.scr.WriteString(ansi.ResetModeMouseButtonEvent +\n\t\t\t\t\tansi.ResetModeMouseAnyEvent +\n\t\t\t\t\tansi.ResetModeMouseExtSgr)\n\t\t\t}\n\t\tcase MouseModeCellMotion:\n\t\t\tif s.lastView != nil && s.lastView.MouseMode == MouseModeAllMotion {\n\t\t\t\t_, _ = s.scr.WriteString(ansi.ResetModeMouseAnyEvent)\n\t\t\t}\n\t\t\t_, _ = s.scr.WriteString(ansi.SetModeMouseButtonEvent + ansi.SetModeMouseExtSgr)\n\t\tcase MouseModeAllMotion:\n\t\t\tif s.lastView != nil && s.lastView.MouseMode == MouseModeCellMotion {\n\t\t\t\t_, _ = s.scr.WriteString(ansi.ResetModeMouseButtonEvent)\n\t\t\t}\n\t\t\t_, _ = s.scr.WriteString(ansi.SetModeMouseAnyEvent + ansi.SetModeMouseExtSgr)\n\t\t}\n\t}\n\n\t// Set window title.\n\tif s.lastView == nil || view.WindowTitle != s.lastView.WindowTitle {\n\t\tif s.lastView != nil || view.WindowTitle != \"\" {\n\t\t\t_, _ = s.scr.WriteString(ansi.SetWindowTitle(view.WindowTitle))\n\t\t}\n\t}\n\n\t// kitty keyboard protocol\n\tif s.lastView == nil || view.KeyboardEnhancements != s.lastView.KeyboardEnhancements ||\n\t\tview.AltScreen != s.lastView.AltScreen {\n\t\t// NOTE: We need to reset the keyboard protocol when switching\n\t\t// between main and alt screen. This is because the specs specify\n\t\t// two different states for the main and alt screen.\n\n\t\t// Enable modifyOtherKeys and Kitty keyboard protocol.\n\t\t_, _ = s.scr.WriteString(ansi.SetModifyOtherKeys2)\n\n\t\tkittyFlags := ansi.KittyDisambiguateEscapeCodes // always enable basic key disambiguation\n\t\tif view.KeyboardEnhancements.ReportEventTypes {\n\t\t\tkittyFlags |= ansi.KittyReportEventTypes\n\t\t}\n\t\t_, _ = s.scr.WriteString(ansi.KittyKeyboard(kittyFlags, 1))\n\t\tif !closing {\n\t\t\t// Request keyboard enhancements when they change\n\t\t\t_, _ = s.scr.WriteString(ansi.RequestKittyKeyboard)\n\t\t}\n\t}\n\n\t// Set terminal colors.\n\tvar (\n\t\tcc, lcc  color.Color\n\t\tlfg, lbg color.Color\n\t)\n\tif view.Cursor != nil {\n\t\tcc = view.Cursor.Color\n\t}\n\tif s.lastView != nil {\n\t\tif s.lastView.Cursor != nil {\n\t\t\tlcc = s.lastView.Cursor.Color\n\t\t}\n\t\tlfg = s.lastView.ForegroundColor\n\t\tlbg = s.lastView.BackgroundColor\n\t}\n\tfor _, c := range []struct {\n\t\tnewColor color.Color\n\t\toldColor color.Color\n\t\treset    string\n\t\tsetter   func(string) string\n\t}{\n\t\t{newColor: cc, oldColor: lcc, reset: ansi.ResetCursorColor, setter: ansi.SetCursorColor},\n\t\t{newColor: view.ForegroundColor, oldColor: lfg, reset: ansi.ResetForegroundColor, setter: ansi.SetForegroundColor},\n\t\t{newColor: view.BackgroundColor, oldColor: lbg, reset: ansi.ResetBackgroundColor, setter: ansi.SetBackgroundColor},\n\t} {\n\t\tif c.newColor != c.oldColor {\n\t\t\tif c.newColor == nil {\n\t\t\t\t// Reset the color if it was set to nil.\n\t\t\t\t_, _ = s.scr.WriteString(c.reset)\n\t\t\t} else {\n\t\t\t\t// Set the color.\n\t\t\t\tcol, ok := colorful.MakeColor(c.newColor)\n\t\t\t\tif ok {\n\t\t\t\t\t_, _ = s.scr.WriteString(c.setter(col.Hex()))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set cursor shape and blink if set.\n\tvar ccStyle, lcStyle int\n\tvar lcur *Cursor\n\tccur := view.Cursor\n\tif lv := s.lastView; lv != nil {\n\t\tlcur = lv.Cursor\n\t}\n\tif ccur != nil {\n\t\tccStyle = encodeCursorStyle(ccur.Shape, ccur.Blink)\n\t}\n\tif lcur != nil {\n\t\tlcStyle = encodeCursorStyle(lcur.Shape, lcur.Blink)\n\t}\n\tif ccStyle != lcStyle {\n\t\t_, _ = s.scr.WriteString(ansi.SetCursorStyle(ccStyle))\n\t}\n\n\t// Render progress bar if it's changed.\n\tif (s.lastView == nil && view.ProgressBar != nil && view.ProgressBar.State != ProgressBarNone) ||\n\t\t(s.lastView != nil && (s.lastView.ProgressBar == nil) != (view.ProgressBar == nil)) ||\n\t\t(s.lastView != nil && s.lastView.ProgressBar != nil && view.ProgressBar != nil && *s.lastView.ProgressBar != *view.ProgressBar) {\n\t\t// Render or clear the progress bar if it was added or removed.\n\t\tsetProgressBar(s, view.ProgressBar)\n\t}\n\n\t// Render and queue changes to the screen buffer.\n\ts.scr.Render(s.cellbuf.RenderBuffer)\n\n\tif cur := view.Cursor; cur != nil {\n\t\t// MoveTo must come after [uv.TerminalRenderer.Render] because the\n\t\t// cursor position might get updated during rendering.\n\t\ts.scr.MoveTo(view.Cursor.X, view.Cursor.Y)\n\t} else if !view.AltScreen {\n\t\t// We don't want the cursor to be dangling at the end of the line in\n\t\t// inline mode because it can cause unwanted line wraps in some\n\t\t// terminals. So we move it to the beginning of the next line if\n\t\t// necessary.\n\t\t// This is only needed when the cursor is hidden because when it's\n\t\t// visible, we already set its position above.\n\t\tx, y := s.scr.Position()\n\t\tif x >= s.width-1 {\n\t\t\ts.scr.MoveTo(0, y)\n\t\t}\n\t}\n\n\tif err := s.scr.Flush(); err != nil {\n\t\treturn fmt.Errorf(\"bubbletea: error flushing screen writer: %w\", err)\n\t}\n\n\t// Check if we have any render updates to flush.\n\thasUpdates := s.buf.Len() > 0\n\n\t// Cursor visibility.\n\tdidShowCursor := s.lastView != nil && s.lastView.Cursor != nil\n\tshowCursor := view.Cursor != nil\n\thideCursor := !showCursor\n\tshouldUpdateCursorVis := (s.lastView == nil || didShowCursor != showCursor) || shouldUpdateAltScreen\n\n\t// Build final output buffer with synchronized output or hide/show cursor\n\t// updates. But first, enter/exit alt screen mode if needed.\n\t//\n\t// Here, we have two scenarios:\n\t// 1. Synchronized output updates are supported. In this case, we want to\n\t//    wrap all updates, unless it's just a cursor visibility change, in\n\t//    synchronized output mode. This is because synchronized output mode\n\t//    takes care of rendering the updates atomically. In the case of\n\t//    just a cursor visibility change, we don't need to enter\n\t//    synchronized output mode because it's just a single sequence to\n\t//    flush out to the terminal.\n\t//\n\t// 2. We don't have synchronized output updates support. In this case, and\n\t//    if the cursor is visible or should be visible, we wrap the updates\n\t//    with hide/show cursor sequences to try and mitigate cursor\n\t//    flickering. This is terminal dependent and may still result in\n\t//    flickering in some terminals. It's the best effort we can do instead\n\t//    of showing the cursor flying around the screen during updates.\n\n\tvar buf bytes.Buffer\n\tif shouldUpdateAltScreen {\n\t\t// We always disable keyboard enhancements when switching screens\n\t\t// because the terminal is expected to have two different keyboard\n\t\t// registries for main and alt screens.\n\t\t_, _ = buf.WriteString(ansi.ResetModifyOtherKeys)\n\t\t_, _ = buf.WriteString(ansi.KittyKeyboard(0, 1))\n\t\tif view.AltScreen {\n\t\t\t// Entering alt screen mode.\n\t\t\tbuf.WriteString(ansi.SetModeAltScreenSaveCursor)\n\t\t} else {\n\t\t\t// Exiting alt screen mode.\n\t\t\tbuf.WriteString(ansi.ResetModeAltScreenSaveCursor)\n\t\t}\n\t}\n\n\tif s.syncdUpdates {\n\t\tif hasUpdates {\n\t\t\t// We have synchronized output updates enabled.\n\t\t\tbuf.WriteString(ansi.SetModeSynchronizedOutput)\n\t\t}\n\t\tif shouldUpdateCursorVis && hideCursor {\n\t\t\t// Do we need to update the cursor visibility to hidden? If so, do\n\t\t\t// it here before writing any updates to the buffer.\n\t\t\t_, _ = buf.WriteString(ansi.ResetModeTextCursorEnable)\n\t\t}\n\t} else if (shouldUpdateCursorVis && hideCursor) || (hasUpdates && showCursor && didShowCursor) {\n\t\t_, _ = buf.WriteString(ansi.ResetModeTextCursorEnable)\n\t}\n\n\tif hasUpdates {\n\t\tbuf.Write(s.buf.Bytes())\n\t}\n\n\tif s.syncdUpdates {\n\t\tif shouldUpdateCursorVis && showCursor {\n\t\t\t// Do we need to update the cursor visibility to visible? If so, do\n\t\t\t// it here after writing any updates to the buffer.\n\t\t\t_, _ = buf.WriteString(ansi.SetModeTextCursorEnable)\n\t\t}\n\t\tif hasUpdates {\n\t\t\t// Close synchronized output mode.\n\t\t\tbuf.WriteString(ansi.ResetModeSynchronizedOutput)\n\t\t}\n\t} else if (shouldUpdateCursorVis && showCursor) || (hasUpdates && showCursor && didShowCursor) {\n\t\t_, _ = buf.WriteString(ansi.SetModeTextCursorEnable)\n\t}\n\n\t// Reset internal screen renderer buffer.\n\ts.buf.Reset()\n\n\t// If our updates flush buffer has content, write it to the output writer.\n\tif buf.Len() > 0 {\n\t\tif s.logger != nil {\n\t\t\ts.logger.Printf(\"output: %q\", buf.String())\n\t\t}\n\t\tif _, err := io.Copy(s.w, &buf); err != nil {\n\t\t\treturn fmt.Errorf(\"bubbletea: error flushing update to the writer: %w\", err)\n\t\t}\n\t}\n\n\ts.lastView = &view\n\n\treturn nil\n}\n\n// render implements renderer.\nfunc (s *cursedRenderer) render(v View) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\ts.view = v\n}\n\n// reset implements renderer.\nfunc (s *cursedRenderer) reset() {\n\ts.mu.Lock()\n\treset(s)\n\ts.mu.Unlock()\n}\n\nfunc reset(s *cursedRenderer) {\n\ts.buf.Reset()\n\tscr := uv.NewTerminalRenderer(&s.buf, s.env)\n\tscr.SetColorProfile(s.profile)\n\tscr.SetRelativeCursor(true) // Always start in inline mode\n\tscr.SetFullscreen(false)    // Always start in inline mode\n\tscr.SetTabStops(s.width)\n\tscr.SetBackspace(s.backspace)\n\tscr.SetMapNewline(s.mapnl)\n\tscr.SetScrollOptim(runtime.GOOS != \"windows\") // disable scroll optimization on Windows due to bugs in some terminals\n\ts.scr = scr\n}\n\n// setColorProfile implements renderer.\nfunc (s *cursedRenderer) setColorProfile(p colorprofile.Profile) {\n\ts.mu.Lock()\n\ts.profile = p\n\ts.scr.SetColorProfile(p)\n\ts.mu.Unlock()\n}\n\n// resize implements renderer.\nfunc (s *cursedRenderer) resize(w, h int) {\n\ts.mu.Lock()\n\t// We need to mark the screen for clear to force a redraw. However, we\n\t// only do so if we're using alt screen or the width has changed.\n\t// That's because redrawing is expensive and we can avoid it if the\n\t// width hasn't changed in inline mode. On the other hand, when using\n\t// alt screen mode, we always want to redraw because some terminals\n\t// would scroll the screen and our content would be lost.\n\ts.scr.Erase()\n\ts.width, s.height = w, h\n\ts.scr.Resize(s.width, s.height)\n\ts.mu.Unlock()\n}\n\n// clearScreen implements renderer.\nfunc (s *cursedRenderer) clearScreen() {\n\ts.mu.Lock()\n\t// Move the cursor to the top left corner of the screen and trigger a full\n\t// screen redraw.\n\ts.scr.MoveTo(0, 0)\n\ts.scr.Erase()\n\ts.mu.Unlock()\n}\n\n// enableAltScreen sets the alt screen mode.\n// Note that this writes to the buffer directly if write is true.\nfunc enableAltScreen(s *cursedRenderer, enable bool, write bool) {\n\tif enable {\n\t\tenterAltScreen(s, write)\n\t} else {\n\t\texitAltScreen(s, write)\n\t}\n}\n\nfunc enterAltScreen(s *cursedRenderer, write bool) {\n\ts.scr.SaveCursor()\n\tif write {\n\t\ts.buf.WriteString(ansi.SetModeAltScreenSaveCursor)\n\t}\n\ts.scr.SetFullscreen(true)\n\ts.scr.SetRelativeCursor(false)\n\ts.scr.Erase()\n}\n\nfunc exitAltScreen(s *cursedRenderer, write bool) {\n\ts.scr.Erase()\n\ts.scr.SetRelativeCursor(true)\n\ts.scr.SetFullscreen(false)\n\tif write {\n\t\ts.buf.WriteString(ansi.ResetModeAltScreenSaveCursor)\n\t}\n\ts.scr.RestoreCursor()\n}\n\n// enableTextCursor sets the text cursor mode.\nfunc enableTextCursor(s *cursedRenderer, enable bool) {\n\tif enable {\n\t\t_, _ = s.scr.WriteString(ansi.SetModeTextCursorEnable)\n\t} else {\n\t\t_, _ = s.scr.WriteString(ansi.ResetModeTextCursorEnable)\n\t}\n}\n\n// setSyncdUpdates implements renderer.\nfunc (s *cursedRenderer) setSyncdUpdates(syncd bool) {\n\ts.mu.Lock()\n\ts.syncdUpdates = syncd\n\ts.mu.Unlock()\n}\n\n// setWidthMethod implements renderer.\nfunc (s *cursedRenderer) setWidthMethod(method ansi.Method) {\n\ts.mu.Lock()\n\tif method == ansi.GraphemeWidth {\n\t\t// Turn on Unicode mode (2027) for accurate grapheme width calculation.\n\t\t// This is needed for proper rendering of wide characters and emojis.\n\t\t_, _ = s.scr.WriteString(ansi.SetModeUnicodeCore)\n\t} else if s.cellbuf.Method == ansi.GraphemeWidth {\n\t\t// Turn off Unicode mode if we're switching away from grapheme width\n\t\t// calculation to avoid issues with some terminals that might still be\n\t\t// in Unicode mode and render characters incorrectly.\n\t\t_, _ = s.scr.WriteString(ansi.ResetModeUnicodeCore)\n\t}\n\ts.cellbuf.Method = method\n\ts.mu.Unlock()\n}\n\n// insertAbove implements renderer.\nfunc (s *cursedRenderer) insertAbove(str string) error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tif len(str) == 0 {\n\t\treturn nil\n\t}\n\n\tvar sb strings.Builder\n\tw, h := s.cellbuf.Width(), s.cellbuf.Height()\n\t_, y := s.scr.Position()\n\n\t// We need to scroll the screen up by the number of lines in the queue.\n\tsb.WriteByte('\\r')\n\tdown := h - y - 1\n\tif down > 0 {\n\t\tsb.WriteString(ansi.CursorDown(down))\n\t}\n\n\tlines := strings.Split(str, \"\\n\")\n\toffset := len(lines)\n\tfor _, line := range lines {\n\t\tlineWidth := ansi.StringWidth(line)\n\t\tif w > 0 && lineWidth > w {\n\t\t\toffset += (lineWidth / w)\n\t\t}\n\t}\n\n\t// Scroll the screen up by the offset to make room for the new lines.\n\tsb.WriteString(strings.Repeat(\"\\n\", offset))\n\n\t// XXX: Now go to the top of the screen, insert new lines, and write\n\t// the queued strings. It is important to use [Screen.moveCursor]\n\t// instead of [Screen.move] because we don't want to perform any checks\n\t// on the cursor position.\n\tup := offset + h - 1\n\tsb.WriteString(ansi.CursorUp(up))\n\tsb.WriteString(ansi.InsertLine(offset))\n\tfor _, line := range lines {\n\t\tsb.WriteString(line)\n\t\tsb.WriteString(ansi.EraseLineRight)\n\t\tsb.WriteString(\"\\r\\n\")\n\t}\n\n\ts.scr.SetPosition(0, 0)\n\n\tif s.logger != nil {\n\t\ts.logger.Printf(\"insert above: %q\", sb.String())\n\t}\n\n\t_, err := io.WriteString(s.w, sb.String())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"bubbletea: error writing insert above to the writer: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// onMouse implements renderer.\nfunc (s *cursedRenderer) onMouse(m MouseMsg) Cmd {\n\tif s.lastView != nil && s.lastView.OnMouse != nil {\n\t\treturn s.lastView.OnMouse(m)\n\t}\n\treturn nil\n}\n\nfunc setProgressBar(s *cursedRenderer, pb *ProgressBar) {\n\tif pb == nil {\n\t\t_, _ = s.scr.WriteString(ansi.ResetProgressBar)\n\t\treturn\n\t}\n\n\tvar seq string\n\tswitch pb.State {\n\tcase ProgressBarNone:\n\t\tseq = ansi.ResetProgressBar\n\tcase ProgressBarDefault:\n\t\tseq = ansi.SetProgressBar(pb.Value)\n\tcase ProgressBarError:\n\t\tseq = ansi.SetErrorProgressBar(pb.Value)\n\tcase ProgressBarIndeterminate:\n\t\tseq = ansi.SetIndeterminateProgressBar\n\tcase ProgressBarWarning:\n\t\tseq = ansi.SetWarningProgressBar(pb.Value)\n\t}\n\tif seq != \"\" {\n\t\t_, _ = s.scr.WriteString(seq)\n\t}\n}\n\nfunc viewEquals(a, b *View) bool {\n\tif a == nil || b == nil {\n\t\treturn false\n\t}\n\n\tif a.Content != b.Content ||\n\t\ta.AltScreen != b.AltScreen ||\n\t\ta.DisableBracketedPasteMode != b.DisableBracketedPasteMode ||\n\t\ta.ReportFocus != b.ReportFocus ||\n\t\ta.MouseMode != b.MouseMode ||\n\t\ta.WindowTitle != b.WindowTitle ||\n\t\ta.ForegroundColor != b.ForegroundColor ||\n\t\ta.BackgroundColor != b.BackgroundColor ||\n\t\ta.KeyboardEnhancements != b.KeyboardEnhancements {\n\t\treturn false\n\t}\n\n\tif (a.Cursor == nil) != (b.Cursor == nil) {\n\t\treturn false\n\t}\n\tif a.Cursor != nil && b.Cursor != nil {\n\t\tif a.Cursor.X != b.Cursor.X ||\n\t\t\ta.Cursor.Y != b.Cursor.Y ||\n\t\t\ta.Cursor.Shape != b.Cursor.Shape ||\n\t\t\ta.Cursor.Blink != b.Cursor.Blink ||\n\t\t\ta.Cursor.Color != b.Cursor.Color {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif (a.ProgressBar == nil) != (b.ProgressBar == nil) {\n\t\treturn false\n\t}\n\tif a.ProgressBar != nil && b.ProgressBar != nil {\n\t\tif *a.ProgressBar != *b.ProgressBar {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "cursor.go",
    "content": "package tea\n\n// Position represents a position in the terminal.\ntype Position struct{ X, Y int }\n\n// CursorPositionMsg is a message that represents the terminal cursor position.\ntype CursorPositionMsg struct {\n\tX, Y int\n}\n\n// CursorShape represents a terminal cursor shape.\ntype CursorShape int\n\n// Cursor shapes.\nconst (\n\tCursorBlock CursorShape = iota\n\tCursorUnderline\n\tCursorBar\n)\n\n// requestCursorPosMsg is a message that requests the cursor position.\ntype requestCursorPosMsg struct{}\n\n// RequestCursorPosition is a command that requests the cursor position.\n// The cursor position will be sent as a [CursorPositionMsg] message.\nfunc RequestCursorPosition() Msg {\n\treturn requestCursorPosMsg{}\n}\n"
  },
  {
    "path": "environ.go",
    "content": "package tea\n\nimport uv \"github.com/charmbracelet/ultraviolet\"\n\n// EnvMsg is a message that represents the environment variables of the\n// program. This is useful for getting the environment variables of programs\n// running in a remote session like SSH. In that case, using [os.Getenv] would\n// return the server's environment variables, not the client's.\n//\n// This message is sent to the program when it starts.\n//\n// Example:\n//\n//\tswitch msg := msg.(type) {\n//\tcase EnvMsg:\n//\t  // What terminal type is being used?\n//\t  term := msg.Getenv(\"TERM\")\n//\t}\ntype EnvMsg uv.Environ\n\n// Getenv returns the value of the environment variable named by the key. If\n// the variable is not present in the environment, the value returned will be\n// the empty string.\nfunc (msg EnvMsg) Getenv(key string) (v string) {\n\treturn uv.Environ(msg).Getenv(key)\n}\n\n// LookupEnv retrieves the value of the environment variable named by the key.\n// If the variable is present in the environment the value (which may be empty)\n// is returned and the boolean is true. Otherwise the returned value will be\n// empty and the boolean will be false.\nfunc (msg EnvMsg) LookupEnv(key string) (s string, v bool) {\n\treturn uv.Environ(msg).LookupEnv(key)\n}\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Examples\n\n### Alt Screen Toggle\n\nThe `altscreen-toggle` example shows how to transition between the alternative\nscreen buffer and the normal screen buffer using Bubble Tea.\n\n<a href=\"./altscreen-toggle/main.go\">\n  <img width=\"750\" src=\"./altscreen-toggle/altscreen-toggle.gif\" />\n</a>\n\n### Chat\n\nThe `chat` examples shows a basic chat application with a multi-line `textarea`\ninput.\n\n<a href=\"./chat/main.go\">\n  <img width=\"750\" src=\"./chat/chat.gif\" />\n</a>\n\n### Composable Views\n\nThe `composable-views` example shows how to compose two bubble models (spinner\nand timer) together in a single application and switch between them.\n\n<a href=\"./composable-views/main.go\">\n  <img width=\"750\" src=\"./composable-views/composable-views.gif\" />\n</a>\n\n### ISBN Book Form\n\nThe `isbn-form` example demonstrates how to build a multi-step form with\n`textinput` bubbles and validation on the inputs.\n\n<a href=\"./isbn-form/main.go\">\n  <img width=\"750\" src=\"./isbn-form/isbn-form.gif\" />\n</a>\n\n### Debounce\n\nThe `debounce` example shows how to throttle key presses to avoid overloading\nyour Bubble Tea application.\n\n<a href=\"./debounce/main.go\">\n  <img width=\"750\" src=\"./debounce/debounce.gif\" />\n</a>\n\n### Exec\n\nThe `exec` example shows how to execute a running command during the execution\nof a Bubble Tea application such as launching an `EDITOR`.\n \n<a href=\"./exec/main.go\">\n  <img width=\"750\" src=\"./exec/exec.gif\" />\n</a>\n\n### Full Screen\n\nThe `fullscreen` example shows how to make a Bubble Tea application fullscreen.\n\n<a href=\"./fullscreen/main.go\">\n  <img width=\"750\" src=\"./fullscreen/fullscreen.gif\" />\n</a>\n\n### Glamour\n\nThe `glamour` example shows how to use [Glamour](https://github.com/charmbracelet/glamour) inside a viewport bubble.\n\n<a href=\"./glamour/main.go\">\n  <img width=\"750\" src=\"./glamour/glamour.gif\" />\n</a>\n\n### Help\n\nThe `help` example shows how to use the `help` bubble to display help to the\nuser of your application.\n\n<a href=\"./help/main.go\">\n  <img width=\"750\" src=\"./help/help.gif\" />\n</a>\n\n### Http\n\nThe `http` example shows how to make an `http` call within your Bubble Tea\napplication.\n\n<a href=\"./http/main.go\">\n  <img width=\"750\" src=\"./http/http.gif\" />\n</a>\n\n### Default List\n\nThe `list-default` example shows how to use the list bubble.\n\n<a href=\"./list-default/main.go\">\n  <img width=\"750\" src=\"./list-default/list-default.gif\" />\n</a>\n\n### Fancy List\n\nThe `list-fancy` example shows how to use the list bubble with extra customizations.\n\n<a href=\"./list-fancy/main.go\">\n  <img width=\"750\" src=\"./list-fancy/list-fancy.gif\" />\n</a>\n\n### Simple List\n\nThe `list-simple` example shows how to use the list and customize it to have a simpler, more compact, appearance.\n\n<a href=\"./list-simple/main.go\">\n  <img width=\"750\" src=\"./list-simple/list-simple.gif\" />\n</a>\n\n### Mouse\n\nThe `mouse` example shows how to receive mouse events in a Bubble Tea\napplication.\n\n<a href=\"./mouse/main.go\">\n  Code\n</a>\n\n### Package Manager\n\nThe `package-manager` example shows how to build an interface for a package\nmanager using the `tea.Println` feature.\n\n<a href=\"./package-manager/main.go\">\n  <img width=\"750\" src=\"./package-manager/package-manager.gif\" />\n</a>\n\n### Pager\n\nThe `pager` example shows how to build a simple pager application similar to\n`less`.\n\n<a href=\"./pager/main.go\">\n  <img width=\"750\" src=\"./pager/pager.gif\" />\n</a>\n\n### Paginator\n\nThe `paginator` example shows how to build a simple paginated list.\n\n<a href=\"./paginator/main.go\">\n  <img width=\"750\" src=\"./paginator/paginator.gif\" />\n</a>\n\n### Pipe\n\nThe `pipe` example demonstrates using shell pipes to communicate with Bubble\nTea applications.\n\n<a href=\"./pipe/main.go\">\n  <img width=\"750\" src=\"./pipe/pipe.gif\" />\n</a>\n\n### Animated Progress\n\nThe `progress-animated` example shows how to build a progress bar with an\nanimated progression.\n\n<a href=\"./progress-animated/main.go\">\n  <img width=\"750\" src=\"./progress-animated/progress-animated.gif\" />\n</a>\n\n### Download Progress\n\nThe `progress-download` example demonstrates how to download a file while\nindicating download progress through Bubble Tea.\n\n<a href=\"./progress-download/main.go\">\n  Code\n</a>\n\n### Static Progress\n\nThe `progress-static` example shows a progress bar with static incrementation\nof progress.\n\n<a href=\"./progress-static/main.go\">\n  <img width=\"750\" src=\"./progress-static/progress-static.gif\" />\n</a>\n\n### Real Time\n\nThe `realtime` example demonstrates the use of go channels to perform realtime\ncommunication with a Bubble Tea application.\n\n<a href=\"./realtime/main.go\">\n  <img width=\"750\" src=\"./realtime/realtime.gif\" />\n</a>\n\n### Result\n\nThe `result` example shows a choice menu with the ability to select an option.\n\n<a href=\"./result/main.go\">\n  <img width=\"750\" src=\"./result/result.gif\" />\n</a>\n\n### Send Msg\n\nThe `send-msg` example demonstrates the usage of custom `tea.Msg`s.\n\n<a href=\"./send-msg/main.go\">\n  <img width=\"750\" src=\"./send-msg/send-msg.gif\" />\n</a>\n\n### Sequence\n\nThe `sequence` example demonstrates the `tea.Sequence` command.\n\n<a href=\"./sequence/main.go\">\n  <img width=\"750\" src=\"./sequence/sequence.gif\" />\n</a>\n\n### Simple\n\nThe `simple` example shows a very simple Bubble Tea application.\n\n<a href=\"./simple/main.go\">\n  <img width=\"750\" src=\"./simple/simple.gif\" />\n</a>\n\n### Spinner\n\nThe `spinner` example demonstrates a spinner bubble being used to indicate loading.\n\n<a href=\"./spinner/main.go\">\n  <img width=\"750\" src=\"./spinner/spinner.gif\" />\n</a>\n\n### Spinners\n\nThe `spinner` example shows various spinner types that are available.\n\n<a href=\"./spinners/main.go\">\n  <img width=\"750\" src=\"./spinners/spinners.gif\" />\n</a>\n\n### Split Editors\n\nThe `split-editors` example shows multiple `textarea`s being used in a single\napplication and being able to switch focus between them.\n\n<a href=\"./split-editors/main.go\">\n  <img width=\"750\" src=\"./split-editors/split-editors.gif\" />\n</a>\n\n### Stop Watch\n\nThe `stopwatch` example shows a sample stop watch built with Bubble Tea.\n\n<a href=\"./stopwatch/main.go\">\n  <img width=\"750\" src=\"./stopwatch/stopwatch.gif\" />\n</a>\n\n### Table\n\nThe `table` example demonstrates the table bubble being used to display tabular\ndata.\n\n<a href=\"./table/main.go\">\n  <img width=\"750\" src=\"./table/table.gif\" />\n</a>\n\n### Tabs\n\nThe `tabs` example demonstrates tabbed navigation styled with [Lip Gloss](https://github.com/charmbracelet/lipgloss).\n\n<a href=\"./tabs/main.go\">\n  <img width=\"750\" src=\"./tabs/tabs.gif\" />\n</a>\n\n### Text Area\n\nThe `textarea` example demonstrates a simple Bubble Tea application using a\n`textarea` bubble.\n\n<a href=\"./textarea/main.go\">\n  <img width=\"750\" src=\"./textarea/textarea.gif\" />\n</a>\n\n### Text Input\n\nThe `textinput` example demonstrates a simple Bubble Tea application using a `textinput` bubble.\n\n<a href=\"./textinput/main.go\">\n  <img width=\"750\" src=\"./textinput/textinput.gif\" />\n</a>\n\n### Multiple Text Inputs\n\nThe `textinputs` example shows multiple `textinputs` and being able to switch\nfocus between them as well as changing the cursor mode.\n\n<a href=\"./textinputs/main.go\">\n  <img width=\"750\" src=\"./textinputs/textinputs.gif\" />\n</a>\n\n### Timer\n\nThe `timer` example shows a simple timer built with Bubble Tea.\n\n<a href=\"./timer/main.go\">\n  <img width=\"750\" src=\"./timer/timer.gif\" />\n</a>\n\n### TUI Daemon\n\nThe `tui-daemon-combo` demonstrates building a text-user interface along with a\ndaemon mode using Bubble Tea.\n\n<a href=\"./tui-daemon-combo/main.go\">\n  <img width=\"750\" src=\"./tui-daemon-combo/tui-daemon-combo.gif\" />\n</a>\n\n### Views\n\nThe `views` example demonstrates how to build a Bubble Tea application with\nmultiple views and switch between them.\n\n<a href=\"./views/main.go\">\n  <img width=\"750\" src=\"./views/views.gif\" />\n</a>\n\n"
  },
  {
    "path": "examples/altscreen-toggle/README.md",
    "content": "# Alt Screen Toggle\n\n<img width=\"800\" src=\"./altscreen-toggle.gif\" />\n"
  },
  {
    "path": "examples/altscreen-toggle/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar (\n\tkeywordStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"204\")).Background(lipgloss.Color(\"235\"))\n\thelpStyle    = lipgloss.NewStyle().Foreground(lipgloss.Color(\"241\"))\n)\n\ntype model struct {\n\taltscreen  bool\n\tquitting   bool\n\tsuspending bool\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.ResumeMsg:\n\t\tm.suspending = false\n\t\treturn m, nil\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\", \"esc\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"ctrl+z\":\n\t\t\tm.suspending = true\n\t\t\treturn m, tea.Suspend\n\t\tcase \"space\":\n\t\t\tvar cmd tea.Cmd\n\t\t\tm.altscreen = !m.altscreen\n\t\t\treturn m, cmd\n\t\t}\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tif m.suspending {\n\t\tv := tea.NewView(\"\")\n\t\tv.AltScreen = m.altscreen\n\t\treturn v\n\t}\n\n\tif m.quitting {\n\t\tv := tea.NewView(\"Bye!\\n\")\n\t\tv.AltScreen = m.altscreen\n\t\treturn v\n\t}\n\n\tconst (\n\t\taltscreenMode = \" altscreen mode \"\n\t\tinlineMode    = \" inline mode \"\n\t)\n\n\tvar mode string\n\tif m.altscreen {\n\t\tmode = altscreenMode\n\t} else {\n\t\tmode = inlineMode\n\t}\n\n\tv := tea.NewView(fmt.Sprintf(\"\\n\\n  You're in %s\\n\\n\\n\", keywordStyle.Render(mode)) +\n\t\thelpStyle.Render(\"  space: switch modes • ctrl-z: suspend • q: exit\\n\"))\n\tv.AltScreen = m.altscreen\n\treturn v\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(model{}).Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/autocomplete/main.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"charm.land/bubbles/v2/help\"\n\t\"charm.land/bubbles/v2/key\"\n\t\"charm.land/bubbles/v2/textinput\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nfunc main() {\n\tp := tea.NewProgram(initialModel())\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\ntype (\n\tgotReposSuccessMsg []repo\n\tgotReposErrMsg     error\n)\n\ntype repo struct {\n\tName string `json:\"name\"`\n}\n\nconst reposURL = \"https://api.github.com/orgs/charmbracelet/repos\"\n\nfunc getRepos() tea.Msg {\n\treq, err := http.NewRequest(http.MethodGet, reposURL, nil)\n\tif err != nil {\n\t\treturn gotReposErrMsg(err)\n\t}\n\n\treq.Header.Add(\"Accept\", \"application/vnd.github+json\")\n\treq.Header.Add(\"X-GitHub-Api-Version\", \"2022-11-28\")\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn gotReposErrMsg(err)\n\t}\n\tdefer resp.Body.Close() // nolint: errcheck\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn gotReposErrMsg(err)\n\t}\n\n\tvar repos []repo\n\n\terr = json.Unmarshal(data, &repos)\n\tif err != nil {\n\t\treturn gotReposErrMsg(err)\n\t}\n\n\treturn gotReposSuccessMsg(repos)\n}\n\ntype model struct {\n\ttextInput textinput.Model\n\thelp      help.Model\n\tkeymap    keymap\n}\n\ntype keymap struct {\n\tcomplete, next, prev, quit key.Binding\n}\n\nfunc (k keymap) ShortHelp() []key.Binding {\n\treturn []key.Binding{\n\t\tk.complete,\n\t\tk.next,\n\t\tk.prev,\n\t\tk.quit,\n\t}\n}\n\nfunc (k keymap) FullHelp() [][]key.Binding {\n\treturn [][]key.Binding{k.ShortHelp()}\n}\n\nfunc initialModel() model {\n\tti := textinput.New()\n\tti.Prompt = \"charmbracelet/\"\n\n\ts := ti.Styles()\n\ts.Focused.Prompt = lipgloss.NewStyle().Foreground(lipgloss.Color(\"63\")).MarginLeft(2)\n\ts.Cursor.Color = lipgloss.Color(\"63\")\n\tti.SetStyles(s)\n\n\tti.SetVirtualCursor(false)\n\tti.Focus()\n\tti.CharLimit = 50\n\tti.SetWidth(20)\n\tti.ShowSuggestions = true\n\n\tkm := keymap{\n\t\t// XXX: we should be using the keybindings on the textinput model.\n\t\tcomplete: key.NewBinding(key.WithKeys(\"tab\"), key.WithHelp(\"tab\", \"complete\"), key.WithDisabled()),\n\t\tnext:     key.NewBinding(key.WithKeys(\"ctrl+n\"), key.WithHelp(\"ctrl+n\", \"next\"), key.WithDisabled()),\n\t\tprev:     key.NewBinding(key.WithKeys(\"ctrl+p\"), key.WithHelp(\"ctrl+p\", \"prev\"), key.WithDisabled()),\n\n\t\tquit: key.NewBinding(key.WithKeys(\"enter\", \"ctrl+c\", \"esc\"), key.WithHelp(\"esc\", \"quit\")),\n\t}\n\n\treturn model{\n\t\ttextInput: ti,\n\t\tkeymap:    km,\n\t\thelp:      help.New(),\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tea.Batch(getRepos, textinput.Blink)\n}\n\nfunc (m model) Cursor() *tea.Cursor {\n\tc := m.textInput.Cursor()\n\tif c != nil {\n\t\tc.Y += lipgloss.Height(m.headerView())\n\t}\n\treturn c\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\n\tcase gotReposSuccessMsg:\n\t\tvar suggestions []string\n\t\tfor _, r := range msg {\n\t\t\tsuggestions = append(suggestions, r.Name)\n\t\t}\n\t\tm.textInput.SetSuggestions(suggestions)\n\n\tcase tea.KeyPressMsg:\n\t\tswitch {\n\t\tcase key.Matches(msg, m.keymap.quit):\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\tvar cmd tea.Cmd\n\tm.textInput, cmd = m.textInput.Update(msg)\n\n\t// Determine whether to show completion keybindings.\n\t//\n\t// XXX: we should be using the keybindings on the textinput model.\n\thasChoices := len(m.textInput.MatchedSuggestions()) > 1\n\tm.keymap.complete.SetEnabled(hasChoices)\n\tm.keymap.next.SetEnabled(hasChoices)\n\tm.keymap.prev.SetEnabled(hasChoices)\n\n\treturn m, cmd\n}\n\nfunc (m model) View() tea.View {\n\tif len(m.textInput.AvailableSuggestions()) < 1 {\n\t\treturn tea.NewView(\"One sec, we're fetching completions...\")\n\t}\n\n\tv := tea.NewView(lipgloss.JoinVertical(\n\t\tlipgloss.Left,\n\t\tm.headerView(),\n\t\tm.textInput.View(),\n\t\tm.footerView(),\n\t))\n\n\tc := m.textInput.Cursor()\n\tif c != nil {\n\t\tc.Y += lipgloss.Height(m.headerView())\n\t}\n\tv.Cursor = c\n\treturn v\n}\n\nfunc (m model) headerView() string { return \"Enter a Charm™ repo:\\n\" }\nfunc (m model) footerView() string { return \"\\n\" + m.help.View(m.keymap) }\n"
  },
  {
    "path": "examples/canvas/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/charmbracelet/x/exp/charmtone\"\n)\n\ntype model struct {\n\twidth    int\n\tflip     bool\n\tquitting bool\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tm.width = msg.Width\n\t\treturn m, nil\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\", \"esc\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tdefault:\n\t\t\tm.flip = !m.flip\n\t\t}\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tvar view tea.View\n\tif m.quitting {\n\t\treturn view\n\t}\n\n\tz := []int{0, 1}\n\tif m.flip {\n\t\tz = reverse(z)\n\t}\n\n\tfooter := lipgloss.NewStyle().\n\t\tHeight(13).\n\t\tForeground(charmtone.Oyster).\n\t\tAlignVertical(lipgloss.Bottom).\n\t\tRender(\"Press any key to swap the cards, or q to quit.\")\n\n\tcardA := newCard(\"Hello\").Z(z[0])\n\tcardB := newCard(\"Goodbye\").Z(z[1])\n\tcomp := lipgloss.NewCompositor(\n\t\tlipgloss.NewLayer(footer),\n\t\tcardA,\n\t\tcardB.X(10).Y(2),\n\t)\n\tview.SetContent(comp.Render())\n\n\treturn view\n}\n\nfunc newCard(str string) *lipgloss.Layer {\n\treturn lipgloss.NewLayer(\n\t\tlipgloss.NewStyle().\n\t\t\tWidth(20).\n\t\t\tHeight(10).\n\t\t\tBorder(lipgloss.RoundedBorder()).\n\t\t\tBorderForeground(charmtone.Charple).\n\t\t\tAlign(lipgloss.Center, lipgloss.Center).\n\t\t\tRender(str),\n\t)\n}\n\n// Reverse a slice, returning a new slice.\nfunc reverse[T any](s []T) []T {\n\tn := len(s)\n\tr := make([]T, n)\n\tfor i, v := range s {\n\t\tr[n-1-i] = v\n\t}\n\treturn r\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(model{}).Run(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, \"Urgh:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/capability/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"charm.land/bubbles/v2/textinput\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\ntype model struct {\n\tinput textinput.Model\n\twidth int\n}\n\nvar _ tea.Model = model{}\n\n// Init implements tea.Model.\nfunc (m model) Init() tea.Cmd {\n\treturn m.input.Focus()\n}\n\n// Update implements tea.Model.\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd tea.Cmd\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tm.width = msg.Width\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"esc\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"enter\":\n\t\t\tinput := m.input.Value()\n\t\t\tm.input.Reset()\n\t\t\treturn m, tea.RequestCapability(input)\n\t\t}\n\tcase tea.CapabilityMsg:\n\t\treturn m, tea.Printf(\"Got capability: %s\", msg)\n\t}\n\tm.input, cmd = m.input.Update(msg)\n\treturn m, cmd\n}\n\n// View implements tea.Model.\nfunc (m model) View() tea.View {\n\tw := min(m.width, 60)\n\n\tinstructions := lipgloss.NewStyle().\n\t\tWidth(w).\n\t\tRender(\"Query for terminal capabilities. You can enter things like 'TN', 'RGB', 'cols', and so on. This will not work in all terminals and multiplexers.\")\n\n\treturn tea.NewView(\"\\n\" + instructions + \"\\n\\n\" +\n\t\tm.input.View() +\n\t\t\"\\n\\nPress enter to request capability, or ctrl+c to quit.\")\n}\n\nfunc main() {\n\tm := model{}\n\tm.input = textinput.New()\n\tm.input.Placeholder = \"Enter capability name to request\"\n\tm.input.Focus()\n\n\tif _, err := tea.NewProgram(m).Run(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, \"Uh oh:\", err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc min(a, b int) int {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "examples/cellbuffer/main.go",
    "content": "package main\n\n// A simple example demonstrating how to draw and animate on a cellular grid.\n// Note that the cellbuffer implementation in this example does not support\n// double-width runes.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"github.com/charmbracelet/harmonica\"\n)\n\nconst (\n\tfps       = 60\n\tfrequency = 7.5\n\tdamping   = 0.15\n\tasterisk  = \"*\"\n)\n\nfunc drawEllipse(cb *cellbuffer, xc, yc, rx, ry float64) {\n\tvar (\n\t\tdx, dy, d1, d2 float64\n\t\tx              float64\n\t\ty              = ry\n\t)\n\n\td1 = ry*ry - rx*rx*ry + 0.25*rx*rx\n\tdx = 2 * ry * ry * x\n\tdy = 2 * rx * rx * y\n\n\tfor dx < dy {\n\t\tcb.set(int(x+xc), int(y+yc))\n\t\tcb.set(int(-x+xc), int(y+yc))\n\t\tcb.set(int(x+xc), int(-y+yc))\n\t\tcb.set(int(-x+xc), int(-y+yc))\n\t\tif d1 < 0 {\n\t\t\tx++\n\t\t\tdx = dx + (2 * ry * ry)\n\t\t\td1 = d1 + dx + (ry * ry)\n\t\t} else {\n\t\t\tx++\n\t\t\ty--\n\t\t\tdx = dx + (2 * ry * ry)\n\t\t\tdy = dy - (2 * rx * rx)\n\t\t\td1 = d1 + dx - dy + (ry * ry)\n\t\t}\n\t}\n\n\td2 = ((ry * ry) * ((x + 0.5) * (x + 0.5))) + ((rx * rx) * ((y - 1) * (y - 1))) - (rx * rx * ry * ry)\n\n\tfor y >= 0 {\n\t\tcb.set(int(x+xc), int(y+yc))\n\t\tcb.set(int(-x+xc), int(y+yc))\n\t\tcb.set(int(x+xc), int(-y+yc))\n\t\tcb.set(int(-x+xc), int(-y+yc))\n\t\tif d2 > 0 {\n\t\t\ty--\n\t\t\tdy = dy - (2 * rx * rx)\n\t\t\td2 = d2 + (rx * rx) - dy\n\t\t} else {\n\t\t\ty--\n\t\t\tx++\n\t\t\tdx = dx + (2 * ry * ry)\n\t\t\tdy = dy - (2 * rx * rx)\n\t\t\td2 = d2 + dx - dy + (rx * rx)\n\t\t}\n\t}\n}\n\ntype cellbuffer struct {\n\tcells  []string\n\tstride int\n}\n\nfunc (c *cellbuffer) init(w, h int) {\n\tif w == 0 {\n\t\treturn\n\t}\n\tc.stride = w\n\tc.cells = make([]string, w*h)\n\tc.wipe()\n}\n\nfunc (c cellbuffer) set(x, y int) {\n\ti := y*c.stride + x\n\tif i > len(c.cells)-1 || x < 0 || y < 0 || x >= c.width() || y >= c.height() {\n\t\treturn\n\t}\n\tc.cells[i] = asterisk\n}\n\nfunc (c *cellbuffer) wipe() {\n\tfor i := range c.cells {\n\t\tc.cells[i] = \" \"\n\t}\n}\n\nfunc (c cellbuffer) width() int {\n\treturn c.stride\n}\n\nfunc (c cellbuffer) height() int {\n\th := len(c.cells) / c.stride\n\tif len(c.cells)%c.stride != 0 {\n\t\th++\n\t}\n\treturn h\n}\n\nfunc (c cellbuffer) ready() bool {\n\treturn len(c.cells) > 0\n}\n\nfunc (c cellbuffer) String() string {\n\tvar b strings.Builder\n\tfor i := range c.cells {\n\t\tif i > 0 && i%c.stride == 0 && i < len(c.cells)-1 {\n\t\t\tb.WriteRune('\\n')\n\t\t}\n\t\tb.WriteString(c.cells[i])\n\t}\n\treturn b.String()\n}\n\ntype frameMsg struct{}\n\nfunc animate() tea.Cmd {\n\treturn tea.Tick(time.Second/fps, func(_ time.Time) tea.Msg {\n\t\treturn frameMsg{}\n\t})\n}\n\ntype model struct {\n\tcells                cellbuffer\n\tspring               harmonica.Spring\n\ttargetX, targetY     float64\n\tx, y                 float64\n\txVelocity, yVelocity float64\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn animate()\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\treturn m, tea.Quit\n\tcase tea.WindowSizeMsg:\n\t\tif !m.cells.ready() {\n\t\t\tm.targetX, m.targetY = float64(msg.Width)/2, float64(msg.Height)/2\n\t\t}\n\t\tm.cells.init(msg.Width, msg.Height)\n\t\treturn m, nil\n\tcase tea.MouseMsg:\n\t\tswitch msg.(type) {\n\t\tcase tea.MouseClickMsg, tea.MouseMotionMsg:\n\t\tdefault:\n\t\t\tbreak\n\t\t}\n\t\tif !m.cells.ready() {\n\t\t\treturn m, nil\n\t\t}\n\t\tmouse := msg.Mouse()\n\t\tm.targetX, m.targetY = float64(mouse.X), float64(mouse.Y)\n\t\treturn m, nil\n\n\tcase frameMsg:\n\t\tif !m.cells.ready() {\n\t\t\treturn m, nil\n\t\t}\n\n\t\tm.cells.wipe()\n\t\tm.x, m.xVelocity = m.spring.Update(m.x, m.xVelocity, m.targetX)\n\t\tm.y, m.yVelocity = m.spring.Update(m.y, m.yVelocity, m.targetY)\n\t\tdrawEllipse(&m.cells, m.x, m.y, 16, 8)\n\t\treturn m, animate()\n\tdefault:\n\t\treturn m, nil\n\t}\n}\n\nfunc (m model) View() tea.View {\n\tv := tea.NewView(m.cells.String())\n\tv.AltScreen = true\n\tv.MouseMode = tea.MouseModeCellMotion\n\treturn v\n}\n\nfunc main() {\n\tm := model{\n\t\tspring: harmonica.NewSpring(harmonica.FPS(fps), frequency, damping),\n\t}\n\n\tp := tea.NewProgram(m)\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, \"Uh oh:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/chat/README.md",
    "content": "# Chat\n\n<img width=\"800\" src=\"./chat.gif\" />\n"
  },
  {
    "path": "examples/chat/main.go",
    "content": "package main\n\n// A simple program demonstrating the text area component from the Bubbles\n// component library.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"charm.land/bubbles/v2/cursor\"\n\t\"charm.land/bubbles/v2/textarea\"\n\t\"charm.land/bubbles/v2/viewport\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nfunc main() {\n\tp := tea.NewProgram(initialModel())\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Oof: %v\\n\", err)\n\t}\n}\n\ntype model struct {\n\tviewport    viewport.Model\n\tmessages    []string\n\ttextarea    textarea.Model\n\tsenderStyle lipgloss.Style\n\terr         error\n}\n\nfunc initialModel() model {\n\tta := textarea.New()\n\tta.Placeholder = \"Send a message...\"\n\tta.SetVirtualCursor(false)\n\tta.Focus()\n\n\tta.Prompt = \"┃ \"\n\tta.CharLimit = 280\n\n\tta.SetWidth(30)\n\tta.SetHeight(3)\n\n\t// Remove cursor line styling\n\ts := ta.Styles()\n\ts.Focused.CursorLine = lipgloss.NewStyle()\n\tta.SetStyles(s)\n\n\tta.ShowLineNumbers = false\n\n\tvp := viewport.New(viewport.WithWidth(30), viewport.WithHeight(5))\n\tvp.SetContent(`Welcome to the chat room!\nType a message and press Enter to send.`)\n\tvp.KeyMap.Left.SetEnabled(false)\n\tvp.KeyMap.Right.SetEnabled(false)\n\n\tta.KeyMap.InsertNewline.SetEnabled(false)\n\n\treturn model{\n\t\ttextarea:    ta,\n\t\tmessages:    []string{},\n\t\tviewport:    vp,\n\t\tsenderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color(\"5\")),\n\t\terr:         nil,\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn textarea.Blink\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tm.viewport.SetWidth(msg.Width)\n\t\tm.textarea.SetWidth(msg.Width)\n\t\tm.viewport.SetHeight(msg.Height - m.textarea.Height())\n\n\t\tif len(m.messages) > 0 {\n\t\t\t// Wrap content before setting it.\n\t\t\tm.viewport.SetContent(lipgloss.NewStyle().Width(m.viewport.Width()).Render(strings.Join(m.messages, \"\\n\")))\n\t\t}\n\t\tm.viewport.GotoBottom()\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"esc\":\n\t\t\tfmt.Println(m.textarea.Value())\n\t\t\treturn m, tea.Quit\n\t\tcase \"enter\":\n\t\t\tm.messages = append(m.messages, m.senderStyle.Render(\"You: \")+m.textarea.Value())\n\t\t\tm.viewport.SetContent(lipgloss.NewStyle().Width(m.viewport.Width()).Render(strings.Join(m.messages, \"\\n\")))\n\t\t\tm.textarea.Reset()\n\t\t\tm.viewport.GotoBottom()\n\t\t\treturn m, nil\n\t\tdefault:\n\t\t\t// Send all other keypresses to the textarea.\n\t\t\tvar cmd tea.Cmd\n\t\t\tm.textarea, cmd = m.textarea.Update(msg)\n\t\t\treturn m, cmd\n\t\t}\n\n\tcase cursor.BlinkMsg:\n\t\t// Textarea should also process cursor blinks.\n\t\tvar cmd tea.Cmd\n\t\tm.textarea, cmd = m.textarea.Update(msg)\n\t\treturn m, cmd\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tviewportView := m.viewport.View()\n\tv := tea.NewView(viewportView + \"\\n\" + m.textarea.View())\n\tc := m.textarea.Cursor()\n\tif c != nil {\n\t\tc.Y += lipgloss.Height(viewportView)\n\t}\n\tv.Cursor = c\n\tv.AltScreen = true\n\treturn v\n}\n"
  },
  {
    "path": "examples/clickable/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/segmentio/ksuid\"\n)\n\n// LayerHitMsg is a message that is sent to the program when a layer is hit by\n// a mouse event. This is used to determine which layer in a compostable view\n// was hit by the mouse event. The layer is identified by its ID, which is a\n// string that is unique to the layer.\ntype LayerHitMsg struct {\n\tID    string\n\tMouse tea.MouseMsg\n}\n\nconst maxDialogs = 999\n\n// Styles\nvar (\n\tbgTextStyle = lipgloss.NewStyle().\n\t\t\tForeground(lipgloss.Color(\"239\")).\n\t\t\tPadding(1, 2)\n\n\tbgWhitespace = []lipgloss.WhitespaceOption{\n\t\tlipgloss.WithWhitespaceChars(\"/\"),\n\t\tlipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Foreground(lipgloss.Color(\"238\"))),\n\t}\n\n\tdialogWordStyle = lipgloss.NewStyle().\n\t\t\tForeground(lipgloss.Color(\"#E7E1CC\"))\n\n\tdialogStyle = dialogWordStyle.\n\t\t\tWidth(36).\n\t\t\tHeight(8).\n\t\t\tPadding(1, 3).\n\t\t\tBorder(lipgloss.RoundedBorder()).\n\t\t\tBorderForeground(lipgloss.Color(\"#874BFD\"))\n\n\thoveredDialogStyle = dialogStyle.\n\t\t\t\tBorderForeground(lipgloss.Color(\"#F25D94\"))\n\n\tspecialWordLightColor = lipgloss.Color(\"#43BF6D\")\n\tspecialWordDarkColor  = lipgloss.Color(\"#73F59F\")\n\n\tbuttonStyle = lipgloss.NewStyle().\n\t\t\tPadding(0, 3).\n\t\t\tForeground(lipgloss.Color(\"#FFF7DB\")).\n\t\t\tBackground(lipgloss.Color(\"#6124DF\"))\n\n\thoveredButtonStyle = buttonStyle.\n\t\t\t\tBackground(lipgloss.Color(\"#FF5F87\"))\n)\n\n// Model\n\ntype model struct {\n\tspecialWordStyle lipgloss.Style\n\twidth, height    int\n\tdialogs          []dialog\n\tmouseDown        bool\n\tpressID          string\n\tdragID           string\n\tdragOffsetX      int\n\tdragOffsetY      int\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tea.Batch(\n\t\ttea.RequestBackgroundColor,\n\t)\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tm.width, m.height = msg.Width, msg.Height\n\n\tcase tea.BackgroundColorMsg:\n\t\tif msg.IsDark() {\n\t\t\tm.specialWordStyle = m.specialWordStyle.Foreground(specialWordDarkColor)\n\t\t} else {\n\t\t\tm.specialWordStyle = m.specialWordStyle.Foreground(specialWordLightColor)\n\t\t}\n\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\", \"esc\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\n\tcase LayerHitMsg:\n\t\tmouse := msg.Mouse.Mouse()\n\n\t\tswitch msg.Mouse.(type) {\n\t\tcase tea.MouseClickMsg:\n\t\t\tif mouse.Button != tea.MouseLeft {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Initial press\n\t\t\tif !m.mouseDown {\n\t\t\t\tm.mouseDown = true\n\t\t\t\tm.pressID = msg.ID\n\n\t\t\t\t// Did we press on a dialog box?\n\t\t\t\tfor i, d := range m.dialogs {\n\t\t\t\t\tif d.id != msg.ID {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\t// Init drag\n\t\t\t\t\tm.dragID = msg.ID\n\t\t\t\t\tm.dragOffsetX = mouse.X - d.x\n\t\t\t\t\tm.dragOffsetY = mouse.Y - d.y\n\n\t\t\t\t\tif len(m.dialogs) < 2 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\t// Move the one we're going to drag to the end of the slice\n\t\t\t\t\t// so that it gets the highest z-index when we do\n\t\t\t\t\t// compositing later. There are, of course, lots of other\n\t\t\t\t\t// ways you could manage the z-index, too.\n\t\t\t\t\tm.dialogs = m.removeDialog(i)\n\t\t\t\t\tm.dialogs = append(m.dialogs, d)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t// MouseMotion events are send when the mouse has moved and a mouse\n\t\t// button is not pressed.\n\t\tcase tea.MouseMotionMsg:\n\t\t\t// Dragging\n\t\t\tif m.mouseDown && m.dragID != \"\" {\n\t\t\t\t// Find the dialog box we're dragging\n\t\t\t\tfor i := range m.dialogs {\n\t\t\t\t\td := &m.dialogs[i]\n\t\t\t\t\tif d.id != m.dragID {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\t// Move the dialog box with the cursor\n\t\t\t\t\tif m.dragID == d.id {\n\t\t\t\t\t\td.x = clamp(mouse.X-(m.dragOffsetX), 0, m.width-lipgloss.Width(d.windowView()))\n\t\t\t\t\t\td.y = clamp(mouse.Y-(m.dragOffsetY), 0, m.height-lipgloss.Height(d.windowView()))\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Are we hoving over a dialog box?\n\t\t\tfor i := range m.dialogs {\n\n\t\t\t\td := &m.dialogs[i]\n\t\t\t\td.hovering = false\n\t\t\t\td.hoveringButton = false\n\n\t\t\t\tif d.id == msg.ID {\n\t\t\t\t\td.hovering = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif d.buttonID == msg.ID {\n\t\t\t\t\td.hovering = true\n\t\t\t\t\td.hoveringButton = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase tea.MouseReleaseMsg:\n\n\t\t\t// Make sure we're releasing on something with an ID. A successful\n\t\t\t// click is a press and release.\n\t\t\tif m.pressID == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Did we click a button?\n\t\t\tfor i, d := range m.dialogs {\n\t\t\t\tif msg.ID == d.buttonID && m.pressID == d.buttonID {\n\t\t\t\t\t// \"Close\" the window\n\t\t\t\t\tm.dialogs = m.removeDialog(i)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Clicking the background spawns a new dialog\n\t\t\tif msg.ID == \"bg\" && m.pressID == \"bg\" {\n\t\t\t\tif len(m.dialogs) < maxDialogs {\n\t\t\t\t\tm.dialogs = append(m.dialogs, m.newDialog(mouse.X, mouse.Y))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tm.mouseDown = false\n\t\t\tm.dragID = \"\"\n\t\t\tm.pressID = \"\"\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tvar v tea.View\n\tvar body string\n\n\tn := len(m.dialogs)\n\tif n > 0 {\n\t\tbody += \"Drag to move. \"\n\t}\n\tif n == 0 && n < maxDialogs {\n\t\tbody += \"Click to spawn.\"\n\t} else if n >= 1 && n < maxDialogs {\n\t\tbody += fmt.Sprintf(\"Click to spawn up to %d more.\", maxDialogs-len(m.dialogs))\n\t}\n\tbody += \"\\n\\nPress q to quit.\"\n\n\tbg := lipgloss.Place(\n\t\tm.width,\n\t\tm.height,\n\t\tlipgloss.Top,\n\t\tlipgloss.Left,\n\t\tbgTextStyle.Render(body),\n\t\tbgWhitespace...,\n\t)\n\n\troot := lipgloss.NewLayer(bg).ID(\"bg\")\n\tfor i, d := range m.dialogs {\n\t\troot.AddLayers(d.view().Z(i + 1))\n\t}\n\n\tcomp := lipgloss.NewCompositor(root)\n\n\tv.MouseMode = tea.MouseModeAllMotion\n\tv.AltScreen = true\n\tv.OnMouse = func(msg tea.MouseMsg) tea.Cmd {\n\t\treturn func() tea.Msg {\n\t\t\tmouse := msg.Mouse()\n\t\t\tx, y := mouse.X, mouse.Y\n\t\t\tif id := comp.Hit(x, y).ID(); id != \"\" {\n\t\t\t\treturn LayerHitMsg{\n\t\t\t\t\tID:    id,\n\t\t\t\t\tMouse: msg,\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\tv.SetContent(comp.Render())\n\n\treturn v\n}\n\nfunc (m *model) newDialog(x, y int) (d dialog) {\n\td.specialWordStyle = &m.specialWordStyle\n\tdummyView := d.windowView()\n\tw := lipgloss.Width(dummyView)\n\th := lipgloss.Height(dummyView)\n\td.x = clamp(x-w/2, 0, m.width-w)\n\td.y = clamp(y-h/2, 0, m.height-h)\n\td.text = nextRandomWord()\n\td.id = ksuid.New().String()\n\td.buttonID = ksuid.New().String()\n\treturn d\n}\n\nfunc (m model) removeDialog(index int) []dialog {\n\td := m.dialogs\n\n\tif len(d) <= index {\n\t\treturn m.dialogs\n\t}\n\n\tcopy(d[index:], d[index+1:]) // shift\n\td[len(d)-1] = dialog{}       // nullify\n\treturn d[:len(d)-1]          // truncate\n}\n\n// Dialog Windows\n\ntype dialog struct {\n\tspecialWordStyle *lipgloss.Style\n\tid               string\n\tbuttonID         string\n\tx, y             int\n\ttext             string\n\thovering         bool\n\thoveringButton   bool\n}\n\nfunc (d dialog) buttonView() string {\n\tconst label = \"Run Away\"\n\n\tif d.hoveringButton {\n\t\treturn hoveredButtonStyle.Render(label)\n\t}\n\treturn buttonStyle.Render(label)\n}\n\nfunc (d dialog) windowView() string {\n\tvar style lipgloss.Style\n\tif d.hovering {\n\t\tstyle = hoveredDialogStyle\n\t} else {\n\t\tstyle = dialogStyle\n\t}\n\n\ts := d.specialWordStyle.Render(d.text) + dialogWordStyle.Render(\" draws near. Command?\")\n\treturn style.Render(s)\n}\n\nfunc (d dialog) view() *lipgloss.Layer {\n\tconst hGap, vGap = 3, 1\n\n\twindow := d.windowView()\n\tbutton := d.buttonView()\n\n\tbuttonX := lipgloss.Width(window) - lipgloss.Width(button) - 1 - hGap\n\tbuttonY := lipgloss.Height(window) - lipgloss.Height(button) - 1 - vGap\n\n\tbuttonLayer := lipgloss.NewLayer(button).\n\t\tID(d.buttonID).\n\t\tX(buttonX).\n\t\tY(buttonY)\n\n\treturn lipgloss.NewLayer(window).\n\t\tID(d.id).\n\t\tX(d.x).\n\t\tY(d.y).\n\t\tAddLayers(buttonLayer)\n}\n\n// Main\n\nfunc main() {\n\tksuid.SetRand(ksuid.FastRander)\n\n\tpath := os.Getenv(\"TEA_LOGFILE\")\n\tif path != \"\" {\n\t\tf, err := tea.LogToFile(path, \"layers\")\n\t\tif err != nil {\n\t\t\tfmt.Println(\"could not open logfile:\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tdefer f.Close()\n\t}\n\n\tif _, err := tea.NewProgram(model{}).Run(); err != nil {\n\t\tfmt.Println(\"Error while running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc clamp(n, min, max int) int {\n\tif n < min {\n\t\treturn min\n\t}\n\tif n > max {\n\t\treturn max\n\t}\n\treturn n\n}\n"
  },
  {
    "path": "examples/clickable/words.go",
    "content": "package main\n\nimport (\n\t\"math/rand\"\n\t\"strings\"\n\t\"sync\"\n)\n\nconst uncapitalized = \" of a an and ’n’ \"\n\nvar (\n\tadjectives = []string{\n\t\t\"a hot\", \"a cute\", \"a fresh\", \"a nice\", \"a lovely\",\n\t\t\"an eager\", \"a soft\", \"an expensive\", \"a new\", \"an old\", \"a happy\",\n\t\t\"a messy\", \"a good\", \"a bad\", \"a cheesy\", \"a friendly\", \"a free\",\n\t\t\"a cold\", \"a gorgeous\", \"a glamorous\", \"a handsome\", \"an exquisite\",\n\t\t\"a tantalizing\", \"a suspicious\", \"an american\", \"a wooden\", \"a golden\",\n\t\t\"a dirty\", \"a hairy\", \"a lukewarm\", \"a burning hot\", \"a shiny\",\n\t\t\"a rogue\", \"a green\", \"a late night\", \"a mass produced\", \"a handmade\",\n\t\t\"a wild\", \"a clean\", \"a rugged\", \"the #1\", \"the best\", \"the worst\",\n\t\t\"a famous\", \"an infamous\", \"a clever\", \"a microwaved\", \"a 3D printed\",\n\t\t\"your favorite\", \"your least favorite\", \"someone’s\", \"a precious\",\n\t\t\"a fake\", \"a genuine\", \"a bejeweled\", \"a good-smelling\",\n\t}\n\n\tnouns = []string{\n\t\t\"pear\", \"banana\", \"bowl of ramen\", \"currywurst\", \"quince\",\n\t\t\"pie\", \"cake\", \"burrito\", \"sushi\", \"basket of fish ’n’ chips\", \"burger\",\n\t\t\"kohlrabi\", \"pineapple\", \"cantaloupe\", \"sausage roll\", \"yuzu\",\n\t\t\"grapefruit\", \"espresso shot\", \"sandwich\", \"bowl of chow mein\", \"lemon\",\n\t\t\"cup of coffee\", \"bottle of hot sauce\", \"can of beer\", \"glass of wine\",\n\t\t\"muffin\", \"bagel\", \"glass of champagne\", \"bottle of rosé\", \"pengu\",\n\t\t\"badger\", \"mango\", \"okonomiyaki\", \"meatball\", \"box of wine\",\n\t\t\"artichoke\", \"TUI\", \"linux distro\", \"dotfile\", \"weißwurst\", \"computer\",\n\t}\n\n\tshuffle     sync.Once\n\tnextWordMtx sync.Mutex\n)\n\nfunc nextRandomWord() string {\n\tshuffle.Do(shuffleWords)\n\n\tnextWordMtx.Lock()\n\tdefer nextWordMtx.Unlock()\n\n\tadjectives = cycle(adjectives)\n\tnouns = cycle(nouns)\n\n\treturn capitalize(adjectives[0] + \" \" + nouns[0])\n}\n\nfunc shuffleWords() {\n\tshuf := func(x []string) {\n\t\trand.Shuffle(len(x), func(i, j int) { x[i], x[j] = x[j], x[i] })\n\t}\n\tshuf(adjectives)\n\tshuf(nouns)\n}\n\nfunc capitalize(s string) string {\n\twords := strings.Fields(s)\n\n\tfor i, w := range words {\n\t\tif i > 0 && strings.Contains(uncapitalized, \" \"+w+\" \") {\n\t\t\twords[i] = w\n\t\t} else {\n\t\t\twords[i] = strings.Title(w)\n\t\t}\n\t}\n\n\treturn strings.Join(words, \" \")\n}\n\nfunc cycle(stack []string) []string {\n\treturn append(stack[1:], stack[0])\n}\n"
  },
  {
    "path": "examples/colorprofile/main.go",
    "content": "package main\n\nimport (\n\t\"image/color\"\n\t\"log\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"github.com/charmbracelet/colorprofile\"\n\t\"github.com/charmbracelet/x/ansi\"\n\t\"github.com/lucasb-eyer/go-colorful\"\n)\n\nvar myFancyColor color.Color\n\ntype model struct{}\n\nvar _ tea.Model = model{}\n\n// Init implements tea.Model.\nfunc (m model) Init() tea.Cmd {\n\treturn tea.Batch(\n\t\ttea.RequestCapability(\"RGB\"),\n\t\ttea.RequestCapability(\"Tc\"),\n\t)\n}\n\n// Update implements tea.Model.\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyMsg:\n\t\treturn m, tea.Quit\n\tcase tea.ColorProfileMsg:\n\t\treturn m, tea.Println(\"Color profile manually set to \", msg)\n\t}\n\treturn m, nil\n}\n\n// View implements tea.Model.\nfunc (m model) View() tea.View {\n\treturn tea.NewView(\"This will produce the wrong colors on Apple Terminal :)\\n\\n\" +\n\t\tansi.Style{}.ForegroundColor(myFancyColor).Styled(\"Howdy!\") +\n\t\t\"\\n\\n\" +\n\t\t\"Press any key to exit.\")\n}\n\nfunc main() {\n\tmyFancyColor, _ = colorful.Hex(\"#6b50ff\")\n\n\tp := tea.NewProgram(model{}, tea.WithColorProfile(colorprofile.TrueColor))\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/composable-views/README.md",
    "content": "# Composable Views\n\n<img width=\"800\" src=\"./composable-views.gif\" />\n"
  },
  {
    "path": "examples/composable-views/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/spinner\"\n\t\"charm.land/bubbles/v2/timer\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\n/*\nThis example assumes an existing understanding of commands and messages. If you\nhaven't already read our tutorials on the basics of Bubble Tea and working with\ncommands, we recommend reading those first.\n\nFind them at:\nhttps://github.com/charmbracelet/bubbletea/tree/master/tutorials/commands\nhttps://github.com/charmbracelet/bubbletea/tree/master/tutorials/basics\n*/\n\n// sessionState is used to track which model is focused\ntype sessionState uint\n\nconst (\n\tdefaultTime              = time.Minute\n\ttimerView   sessionState = iota\n\tspinnerView\n)\n\nvar (\n\t// Available spinners\n\tspinners = []spinner.Spinner{\n\t\tspinner.Line,\n\t\tspinner.Dot,\n\t\tspinner.MiniDot,\n\t\tspinner.Jump,\n\t\tspinner.Pulse,\n\t\tspinner.Points,\n\t\tspinner.Globe,\n\t\tspinner.Moon,\n\t\tspinner.Monkey,\n\t}\n\tmodelStyle = lipgloss.NewStyle().\n\t\t\tWidth(15).\n\t\t\tHeight(5).\n\t\t\tAlign(lipgloss.Center, lipgloss.Center).\n\t\t\tBorderStyle(lipgloss.HiddenBorder())\n\tfocusedModelStyle = lipgloss.NewStyle().\n\t\t\t\tWidth(15).\n\t\t\t\tHeight(5).\n\t\t\t\tAlign(lipgloss.Center, lipgloss.Center).\n\t\t\t\tBorderStyle(lipgloss.NormalBorder()).\n\t\t\t\tBorderForeground(lipgloss.Color(\"69\"))\n\tspinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"69\"))\n\thelpStyle    = lipgloss.NewStyle().Foreground(lipgloss.Color(\"241\"))\n)\n\ntype mainModel struct {\n\tstate   sessionState\n\ttimer   timer.Model\n\tspinner spinner.Model\n\tindex   int\n}\n\nfunc newModel(timeout time.Duration) mainModel {\n\tm := mainModel{state: timerView}\n\tm.timer = timer.New(timeout)\n\tm.spinner = spinner.New()\n\treturn m\n}\n\nfunc (m mainModel) Init() tea.Cmd {\n\t// start the timer and spinner on program start\n\treturn tea.Batch(m.timer.Init(), m.spinner.Tick)\n}\n\nfunc (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd tea.Cmd\n\tvar cmds []tea.Cmd\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"tab\":\n\t\t\tif m.state == timerView {\n\t\t\t\tm.state = spinnerView\n\t\t\t} else {\n\t\t\t\tm.state = timerView\n\t\t\t}\n\t\tcase \"n\":\n\t\t\tif m.state == timerView {\n\t\t\t\tm.timer = timer.New(defaultTime)\n\t\t\t\tcmds = append(cmds, m.timer.Init())\n\t\t\t} else {\n\t\t\t\tm.Next()\n\t\t\t\tm.resetSpinner()\n\t\t\t\tcmds = append(cmds, m.spinner.Tick)\n\t\t\t}\n\t\t}\n\t\tswitch m.state {\n\t\t// update whichever model is focused\n\t\tcase spinnerView:\n\t\t\tm.spinner, cmd = m.spinner.Update(msg)\n\t\t\tcmds = append(cmds, cmd)\n\t\tdefault:\n\t\t\tm.timer, cmd = m.timer.Update(msg)\n\t\t\tcmds = append(cmds, cmd)\n\t\t}\n\tcase spinner.TickMsg:\n\t\tm.spinner, cmd = m.spinner.Update(msg)\n\t\tcmds = append(cmds, cmd)\n\tcase timer.TickMsg:\n\t\tm.timer, cmd = m.timer.Update(msg)\n\t\tcmds = append(cmds, cmd)\n\t}\n\treturn m, tea.Batch(cmds...)\n}\n\nfunc (m mainModel) View() tea.View {\n\tvar s strings.Builder\n\tmodel := m.currentFocusedModel()\n\tif m.state == timerView {\n\t\ts.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, focusedModelStyle.Render(fmt.Sprintf(\"%4s\", m.timer.View())), modelStyle.Render(m.spinner.View())))\n\t} else {\n\t\ts.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, modelStyle.Render(fmt.Sprintf(\"%4s\", m.timer.View())), focusedModelStyle.Render(m.spinner.View())))\n\t}\n\ts.WriteString(helpStyle.Render(fmt.Sprintf(\"\\ntab: focus next • n: new %s • q: exit\\n\", model)))\n\treturn tea.NewView(s.String())\n}\n\nfunc (m mainModel) currentFocusedModel() string {\n\tif m.state == timerView {\n\t\treturn \"timer\"\n\t}\n\treturn \"spinner\"\n}\n\nfunc (m *mainModel) Next() {\n\tif m.index == len(spinners)-1 {\n\t\tm.index = 0\n\t} else {\n\t\tm.index++\n\t}\n}\n\nfunc (m *mainModel) resetSpinner() {\n\tm.spinner = spinner.New()\n\tm.spinner.Style = spinnerStyle\n\tm.spinner.Spinner = spinners[m.index]\n}\n\nfunc main() {\n\tp := tea.NewProgram(newModel(defaultTime))\n\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/cursor-style/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype model struct {\n\tcursor tea.Cursor\n\tblink  bool\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"h\", \"left\":\n\t\t\tm.cursor.Shape--\n\t\t\tif m.cursor.Shape < tea.CursorBlock {\n\t\t\t\tm.cursor.Shape = tea.CursorBar\n\t\t\t}\n\t\tcase \"l\", \"right\":\n\t\t\tm.cursor.Shape++\n\t\t\tif m.cursor.Shape > tea.CursorBar {\n\t\t\t\tm.cursor.Shape = tea.CursorBlock\n\t\t\t}\n\t\t}\n\t}\n\tm.blink = !m.blink\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tv := tea.NewView(\"Press left/right to change the cursor style, q or ctrl+c to quit.\" +\n\t\t\"\\n\\n\" +\n\t\t\"  <- This is the cursor (a \" + m.describeCursor() + \")\")\n\tc := tea.NewCursor(0, 2)\n\tc.Shape = m.cursor.Shape\n\tc.Blink = m.blink\n\tv.Cursor = c\n\treturn v\n}\n\nfunc (m model) describeCursor() string {\n\tvar adj, noun string\n\n\tif m.blink {\n\t\tadj = \"blinking\"\n\t} else {\n\t\tadj = \"steady\"\n\t}\n\n\tswitch m.cursor.Shape {\n\tcase tea.CursorBlock:\n\t\tnoun = \"block\"\n\tcase tea.CursorUnderline:\n\t\tnoun = \"underline\"\n\tcase tea.CursorBar:\n\t\tnoun = \"bar\"\n\t}\n\n\treturn fmt.Sprintf(\"%s %s\", adj, noun)\n}\n\nfunc main() {\n\tp := tea.NewProgram(model{blink: true})\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Error: %v\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/debounce/README.md",
    "content": "# Debounce\n\n<img width=\"800\" src=\"./debounce.gif\" />\n"
  },
  {
    "path": "examples/debounce/main.go",
    "content": "package main\n\n// This example illustrates how to debounce commands.\n//\n// When the user presses a key we increment the \"tag\" value on the model and,\n// after a short delay, we include that tag value in the message produced\n// by the Tick command.\n//\n// In a subsequent Update, if the tag in the Msg matches current tag on the\n// model's state we know that the debouncing is complete and we can proceed as\n// normal. If not, we simply ignore the inbound message.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nconst debounceDuration = time.Second\n\ntype exitMsg int\n\ntype model struct {\n\ttag int\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\t// Increment the tag on the model...\n\t\tm.tag++\n\t\treturn m, tea.Tick(debounceDuration, func(_ time.Time) tea.Msg {\n\t\t\t// ...and include a copy of that tag value in the message.\n\t\t\treturn exitMsg(m.tag)\n\t\t})\n\tcase exitMsg:\n\t\t// If the tag in the message doesn't match the tag on the model then we\n\t\t// know that this message was not the last one sent and another is on\n\t\t// the way. If that's the case we know, we can ignore this message.\n\t\t// Otherwise, the debounce timeout has passed and this message is a\n\t\t// valid debounced one.\n\t\tif int(msg) == m.tag {\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\treturn tea.NewView(fmt.Sprintf(\"Key presses: %d\", m.tag) +\n\t\t\"\\nTo exit press any key, then wait for one second without pressing anything.\")\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(model{}).Run(); err != nil {\n\t\tfmt.Println(\"uh oh:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/doom-fire/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\n// This Doom Fire implementation was ported from @const-void's Node version.\n// See https://github.com/const-void/DOOM-fire-node\n\nvar whiteFg = lipgloss.NewStyle().Foreground(lipgloss.White)\n\ntype model struct {\n\tscreenBuf   []int\n\twidth       int\n\theight      int\n\tfirePalette []int\n\tstartTime   time.Time\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tick\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tif msg.String() == \"q\" || msg.String() == \"ctrl+c\" {\n\t\t\treturn m, tea.Quit\n\t\t}\n\tcase tickMsg:\n\t\tm.spreadFire()\n\t\treturn m, tick\n\tcase tea.WindowSizeMsg:\n\t\tm.width = msg.Width\n\t\tm.height = msg.Height * 2 // Double height for half-block characters\n\t\tm.screenBuf = make([]int, m.width*m.height)\n\t\t// Initialize the bottom row with white (maximum intensity)\n\t\tfor i := range m.width {\n\t\t\tm.screenBuf[(m.height-1)*m.width+i] = len(m.firePalette) - 1\n\t\t}\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tif m.width == 0 {\n\t\treturn tea.NewView(\"Initializing...\")\n\t}\n\n\tvar s strings.Builder\n\n\tfor y := 0; y < m.height-2; y += 2 {\n\t\tfor x := range m.width {\n\t\t\tpixelHi := m.screenBuf[y*m.width+x]\n\t\t\tpixelLo := m.screenBuf[(y+1)*m.width+x]\n\n\t\t\thiColor := m.firePalette[pixelHi]\n\t\t\tloColor := m.firePalette[pixelLo]\n\n\t\t\ts.WriteString(lipgloss.NewStyle().\n\t\t\t\tForeground(lipgloss.ANSIColor(hiColor)).\n\t\t\t\tBackground(lipgloss.ANSIColor(loColor)).\n\t\t\t\tRender(\"▀\"))\n\t\t}\n\t\tif y < m.height-2 {\n\t\t\ts.WriteByte('\\n')\n\t\t}\n\t}\n\n\telapsed := time.Since(m.startTime)\n\ts.WriteString(whiteFg.Render(\"Press q or ctrl+c to quit. \" + fmt.Sprintf(\"Elapsed: %s\", elapsed.Round(time.Second))))\n\tv := tea.NewView(s.String())\n\tv.AltScreen = true\n\treturn v\n}\n\nfunc (m *model) spreadFire() {\n\tfor x := range m.width {\n\t\tfor y := range m.height {\n\t\t\tm.spreadPixel(y*m.width + x)\n\t\t}\n\t}\n}\n\nfunc (m *model) spreadPixel(idx int) {\n\tif idx < m.width {\n\t\treturn\n\t}\n\n\tpixel := m.screenBuf[idx]\n\tif pixel == 0 {\n\t\tm.screenBuf[idx-m.width] = 0\n\t\treturn\n\t}\n\n\trnd := rand.Intn(3)\n\tdst := idx - rnd + 1\n\tif dst-m.width >= 0 && dst-m.width < len(m.screenBuf) {\n\t\tdecay := rnd & 1\n\t\tnewValue := max(pixel-decay, 0)\n\t\tm.screenBuf[dst-m.width] = newValue\n\t}\n}\n\ntype tickMsg time.Time\n\nfunc tick() tea.Msg {\n\ttime.Sleep(time.Millisecond * 50)\n\treturn tickMsg(time.Now())\n}\n\nfunc initialModel() model {\n\t// Same color palette as the original\n\tpalette := []int{0, 233, 234, 52, 53, 88, 89, 94, 95, 96, 130, 131, 132, 133, 172, 214, 215, 220, 220, 221, 3, 226, 227, 230, 231, 7}\n\n\treturn model{\n\t\tfirePalette: palette,\n\t\tstartTime:   time.Now(),\n\t}\n}\n\nfunc main() {\n\tp := tea.NewProgram(initialModel())\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Printf(\"Error running program: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "examples/exec/README.md",
    "content": "# Exec\n\n<img width=\"800\" src=\"./exec.gif\" />\n"
  },
  {
    "path": "examples/exec/main.go",
    "content": "package main\n\nimport (\n\t\"cmp\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype editorFinishedMsg struct{ err error }\n\nfunc openEditor() tea.Cmd {\n\teditor := cmp.Or(os.Getenv(\"EDITOR\"), \"vim\")\n\tc := exec.Command(editor) //nolint:gosec\n\treturn tea.ExecProcess(c, func(err error) tea.Msg {\n\t\treturn editorFinishedMsg{err}\n\t})\n}\n\ntype model struct {\n\taltscreenActive bool\n\terr             error\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"a\":\n\t\t\tm.altscreenActive = !m.altscreenActive\n\t\t\treturn m, nil\n\t\tcase \"e\":\n\t\t\treturn m, openEditor()\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\tcase editorFinishedMsg:\n\t\tif msg.err != nil {\n\t\t\tm.err = msg.err\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tif m.err != nil {\n\t\tv := tea.NewView(\"Error: \" + m.err.Error() + \"\\n\")\n\t\tv.AltScreen = m.altscreenActive\n\t\treturn v\n\t}\n\tv := tea.NewView(\"Press 'e' to open your EDITOR.\\nPress 'a' to toggle the altscreen\\nPress 'q' to quit.\\n\")\n\tv.AltScreen = m.altscreenActive\n\treturn v\n}\n\nfunc main() {\n\tm := model{}\n\tif _, err := tea.NewProgram(m).Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/eyes/main.go",
    "content": "// roughly converted to Go from https://github.com/dmtrKovalenko/esp32-smooth-eye-blinking/blob/main/src/main.cpp\npackage main\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nconst (\n\t// Eye dimensions (corresponding to original EYE_WIDTH and EYE_HEIGHT)\n\teyeWidth   = 15\n\teyeHeight  = 12 // Increased height for taller eyes\n\teyeSpacing = 40\n\n\t// Blink animation timing (matching original constants)\n\tblinkFrames = 20\n\topenTimeMin = 1000\n\topenTimeMax = 4000\n)\n\n// Characters for drawing the eyes\nconst (\n\teyeChar = \"●\"\n\tbgChar  = \" \"\n)\n\ntype model struct {\n\twidth        int\n\theight       int\n\teyePositions [2]int\n\teyeY         int\n\tisBlinking   bool\n\tblinkState   int\n\tlastBlink    time.Time\n\topenTime     time.Duration\n}\n\ntype tickMsg time.Time\n\nfunc main() {\n\tp := tea.NewProgram(initialModel())\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Printf(\"Error running program: %v\\n\", err)\n\t}\n}\n\nfunc initialModel() model {\n\tm := model{\n\t\twidth:      80,\n\t\theight:     24,\n\t\tisBlinking: false,\n\t\tblinkState: 0,\n\t\tlastBlink:  time.Now(),\n\t\topenTime:   time.Duration(rand.Intn(openTimeMax-openTimeMin)+openTimeMin) * time.Millisecond,\n\t}\n\n\tm.updateEyePositions()\n\treturn m\n}\n\nfunc (m *model) updateEyePositions() {\n\tstartX := (m.width - eyeSpacing) / 2\n\tm.eyeY = m.height / 2\n\n\tm.eyePositions[0] = startX\n\tm.eyePositions[1] = startX + eyeSpacing\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tickCmd()\n}\n\nfunc tickCmd() tea.Cmd {\n\treturn tea.Tick(50*time.Millisecond, func(t time.Time) tea.Msg {\n\t\treturn tickMsg(t)\n\t})\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"esc\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\n\tcase tea.WindowSizeMsg:\n\t\tm.width = msg.Width\n\t\tm.height = msg.Height\n\t\tm.updateEyePositions()\n\n\tcase tickMsg:\n\t\tcurrentTime := time.Now()\n\n\t\tif !m.isBlinking && currentTime.Sub(m.lastBlink) >= m.openTime {\n\t\t\tm.isBlinking = true\n\t\t\tm.blinkState = 0\n\t\t}\n\n\t\tif m.isBlinking {\n\t\t\tm.blinkState++\n\n\t\t\tif m.blinkState >= blinkFrames {\n\t\t\t\tm.isBlinking = false\n\t\t\t\tm.lastBlink = currentTime\n\t\t\t\tm.openTime = time.Duration(rand.Intn(openTimeMax-openTimeMin)+openTimeMin) * time.Millisecond\n\n\t\t\t\t// 10% chance of double blink (matching original logic)\n\t\t\t\tif rand.Intn(10) == 0 {\n\t\t\t\t\tm.openTime = 300 * time.Millisecond\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn m, tickCmd()\n}\n\nfunc (m model) View() tea.View {\n\tvar v tea.View\n\tv.AltScreen = true // Use alternate screen buffer\n\n\t// Create empty canvas\n\tcanvas := make([][]string, m.height)\n\tfor y := range canvas {\n\t\tcanvas[y] = make([]string, m.width)\n\t\tfor x := range canvas[y] {\n\t\t\tcanvas[y][x] = bgChar\n\t\t}\n\t}\n\n\t// Calculate current eye height based on blink state\n\tcurrentHeight := eyeHeight\n\tif m.isBlinking {\n\t\tvar blinkProgress float64\n\n\t\tif m.blinkState < blinkFrames/2 {\n\t\t\t// Closing eyes (with easing function from original)\n\t\t\tblinkProgress = float64(m.blinkState) / float64(blinkFrames/2)\n\t\t\tblinkProgress = 1.0 - (blinkProgress * blinkProgress)\n\t\t} else {\n\t\t\t// Opening eyes (with easing function from original)\n\t\t\tblinkProgress = float64(m.blinkState-blinkFrames/2) / float64(blinkFrames/2)\n\t\t\tblinkProgress = blinkProgress * (2.0 - blinkProgress)\n\t\t}\n\n\t\tcurrentHeight = int(math.Max(1, float64(eyeHeight)*blinkProgress))\n\t}\n\n\t// Draw both eyes\n\tfor i := 0; i < 2; i++ {\n\t\tdrawEllipse(canvas, m.eyePositions[i], m.eyeY, eyeWidth, currentHeight)\n\t}\n\n\t// Convert canvas to string\n\tvar s strings.Builder\n\tfor _, row := range canvas {\n\t\tfor _, cell := range row {\n\t\t\ts.WriteString(cell)\n\t\t}\n\t\ts.WriteString(\"\\n\")\n\t}\n\n\t// Style output\n\tstyle := lipgloss.NewStyle().\n\t\tForeground(lipgloss.Color(\"#F0F0F0\"))\n\n\tv.SetContent(style.Render(s.String()))\n\n\treturn v\n}\n\nfunc drawEllipse(canvas [][]string, x0, y0, rx, ry int) {\n\t// Improved ellipse drawing algorithm with better angles\n\tfor y := -ry; y <= ry; y++ {\n\t\t// Calculate the width at this y position for a smoother ellipse\n\t\t// Use a slightly modified formula to improve the angles\n\t\twidth := int(float64(rx) * math.Sqrt(1.0-math.Pow(float64(y)/float64(ry), 2.0)))\n\n\t\tfor x := -width; x <= width; x++ {\n\t\t\t// Calculate canvas position\n\t\t\tcanvasX := x0 + x\n\t\t\tcanvasY := y0 + y\n\n\t\t\t// Make sure we're within canvas bounds\n\t\t\tif canvasX >= 0 && canvasX < len(canvas[0]) && canvasY >= 0 && canvasY < len(canvas) {\n\t\t\t\tcanvas[canvasY][canvasX] = eyeChar\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/file-picker/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/filepicker\"\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype model struct {\n\tfilepicker   filepicker.Model\n\tselectedFile string\n\tquitting     bool\n\terr          error\n}\n\ntype clearErrorMsg struct{}\n\nfunc clearErrorAfter(t time.Duration) tea.Cmd {\n\treturn tea.Tick(t, func(_ time.Time) tea.Msg {\n\t\treturn clearErrorMsg{}\n\t})\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn m.filepicker.Init()\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\t}\n\tcase clearErrorMsg:\n\t\tm.err = nil\n\t}\n\n\tvar cmd tea.Cmd\n\tm.filepicker, cmd = m.filepicker.Update(msg)\n\n\t// Did the user select a file?\n\tif didSelect, path := m.filepicker.DidSelectFile(msg); didSelect {\n\t\t// Get the path of the selected file.\n\t\tm.selectedFile = path\n\t}\n\n\t// Did the user select a disabled file?\n\t// This is only necessary to display an error to the user.\n\tif didSelect, path := m.filepicker.DidSelectDisabledFile(msg); didSelect {\n\t\t// Let's clear the selectedFile and display an error.\n\t\tm.err = errors.New(path + \" is not valid.\")\n\t\tm.selectedFile = \"\"\n\t\treturn m, tea.Batch(cmd, clearErrorAfter(2*time.Second))\n\t}\n\n\treturn m, cmd\n}\n\nfunc (m model) View() tea.View {\n\tif m.quitting {\n\t\treturn tea.NewView(\"\")\n\t}\n\tvar s strings.Builder\n\ts.WriteString(\"\\n  \")\n\tif m.err != nil {\n\t\ts.WriteString(m.filepicker.Styles.DisabledFile.Render(m.err.Error()))\n\t} else if m.selectedFile == \"\" {\n\t\ts.WriteString(\"Pick a file:\")\n\t} else {\n\t\ts.WriteString(\"Selected file: \" + m.filepicker.Styles.Selected.Render(m.selectedFile))\n\t}\n\ts.WriteString(\"\\n\\n\" + m.filepicker.View() + \"\\n\")\n\tv := tea.NewView(s.String())\n\tv.AltScreen = true\n\treturn v\n}\n\nfunc main() {\n\tfp := filepicker.New()\n\tfp.AllowedTypes = []string{\".mod\", \".sum\", \".go\", \".txt\", \".md\"}\n\tfp.CurrentDirectory, _ = os.UserHomeDir()\n\n\tm := model{filepicker: fp}\n\ttm, _ := tea.NewProgram(m).Run()\n\tmm := tm.(model)\n\tfmt.Println(\"\\n  You selected: \" + m.filepicker.Styles.Selected.Render(mm.selectedFile) + \"\\n\")\n}\n"
  },
  {
    "path": "examples/focus-blur/main.go",
    "content": "package main\n\n// A simple program that handled losing and acquiring focus.\n\nimport (\n\t\"log\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nfunc main() {\n\tp := tea.NewProgram(model{\n\t\tfocused:   true,\n\t\treporting: true,\n\t})\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\ntype model struct {\n\tfocused   bool\n\treporting bool\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.FocusMsg:\n\t\tm.focused = true\n\tcase tea.BlurMsg:\n\t\tm.focused = false\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"t\":\n\t\t\tm.reporting = !m.reporting\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\ts := \"Hi. Focus report is currently \"\n\tif m.reporting {\n\t\ts += \"enabled\"\n\t} else {\n\t\ts += \"disabled\"\n\t}\n\ts += \".\\n\\n\"\n\n\tif m.reporting {\n\t\tif m.focused {\n\t\t\ts += \"This program is currently focused!\"\n\t\t} else {\n\t\t\ts += \"This program is currently blurred!\"\n\t\t}\n\t}\n\tv := tea.NewView(s + \"\\n\\nTo quit sooner press ctrl-c, or t to toggle focus reporting...\\n\")\n\tv.ReportFocus = m.reporting\n\treturn v\n}\n"
  },
  {
    "path": "examples/fullscreen/README.md",
    "content": "# Full Screen\n\n<img width=\"800\" src=\"./fullscreen.gif\" />\n"
  },
  {
    "path": "examples/fullscreen/main.go",
    "content": "package main\n\n// A simple program that opens the alternate screen buffer then counts down\n// from 5 and then exits.\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype model int\n\ntype tickMsg time.Time\n\nfunc main() {\n\tp := tea.NewProgram(model(5))\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tick()\n}\n\nfunc (m model) Update(message tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := message.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"esc\", \"ctrl+c\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\n\tcase tickMsg:\n\t\tm--\n\t\tif m <= 0 {\n\t\t\treturn m, tea.Quit\n\t\t}\n\t\treturn m, tick()\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tv := tea.NewView(fmt.Sprintf(\"\\n\\n     Hi. This program will exit in %d seconds...\", m))\n\tv.AltScreen = true\n\treturn v\n}\n\nfunc tick() tea.Cmd {\n\treturn tea.Tick(time.Second, func(t time.Time) tea.Msg {\n\t\treturn tickMsg(t)\n\t})\n}\n"
  },
  {
    "path": "examples/glamour/README.md",
    "content": "# Glamour\n\n<img width=\"800\" src=\"./glamour.gif\" />\n"
  },
  {
    "path": "examples/glamour/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"charm.land/bubbles/v2/viewport\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"github.com/charmbracelet/glamour/v2\"\n\t\"github.com/charmbracelet/glamour/v2/styles\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nconst content = `\n# Today’s Menu\n\n## Appetizers\n\n| Name        | Price | Notes                           |\n| ---         | ---   | ---                             |\n| Tsukemono   | $2    | Just an appetizer               |\n| Tomato Soup | $4    | Made with San Marzano tomatoes  |\n| Okonomiyaki | $4    | Takes a few minutes to make     |\n| Curry       | $3    | We can add squash if you’d like |\n\n## Seasonal Dishes\n\n| Name                 | Price | Notes              |\n| ---                  | ---   | ---                |\n| Steamed bitter melon | $2    | Not so bitter      |\n| Takoyaki             | $3    | Fun to eat         |\n| Winter squash        | $3    | Today it's pumpkin |\n\n## Desserts\n\n| Name         | Price | Notes                 |\n| ---          | ---   | ---                   |\n| Dorayaki     | $4    | Looks good on rabbits |\n| Banana Split | $5    | A classic             |\n| Cream Puff   | $3    | Pretty creamy!        |\n\nAll our dishes are made in-house by Karen, our chef. Most of our ingredients are from our garden or the fish market down the street.\n\nSome famous people that have eaten here lately:\n\n* [x] René Redzepi\n* [x] David Chang\n* [ ] Jiro Ono (maybe some day)\n\nBon appétit!\n`\n\nvar helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"241\")).Render\n\ntype example struct {\n\tviewport viewport.Model\n}\n\nfunc newExample(isDark bool) (*example, error) {\n\tconst (\n\t\twidth  = 78\n\t\theight = 20\n\t)\n\n\tvp := viewport.New()\n\tvp.SetWidth(width)\n\tvp.SetHeight(height)\n\tvp.Style = lipgloss.NewStyle().\n\t\tBorderStyle(lipgloss.RoundedBorder()).\n\t\tBorderForeground(lipgloss.Color(\"62\")).\n\t\tPaddingRight(2)\n\n\t// We need to adjust the width of the glamour render from our main width\n\t// to account for a few things:\n\t//\n\t//  * The viewport border width\n\t//  * The viewport padding\n\t//  * The viewport margins\n\t//  * The gutter glamour applies to the left side of the content\n\t//\n\tconst glamourGutter = 3\n\tglamourRenderWidth := width - vp.Style.GetHorizontalFrameSize() - glamourGutter\n\n\tstyle := styles.DarkStyleConfig\n\tif !isDark {\n\t\tstyle = styles.LightStyleConfig\n\t}\n\trenderer, err := glamour.NewTermRenderer(\n\t\tglamour.WithStyles(style),\n\t\tglamour.WithWordWrap(glamourRenderWidth),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstr, err := renderer.Render(content)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvp.SetContent(str)\n\n\treturn &example{\n\t\tviewport: vp,\n\t}, nil\n}\n\nfunc (e example) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (e example) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\", \"esc\":\n\t\t\treturn e, tea.Quit\n\t\tdefault:\n\t\t\tvar cmd tea.Cmd\n\t\t\te.viewport, cmd = e.viewport.Update(msg)\n\t\t\treturn e, cmd\n\t\t}\n\tdefault:\n\t\treturn e, nil\n\t}\n}\n\nfunc (e example) View() tea.View {\n\treturn tea.NewView(e.viewport.View() + e.helpView())\n}\n\nfunc (e example) helpView() string {\n\treturn helpStyle(\"\\n  ↑/↓: Navigate • q: Quit\\n\")\n}\n\nfunc main() {\n\thasDarkBg := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n\tmodel, err := newExample(hasDarkBg)\n\tif err != nil {\n\t\tfmt.Println(\"Could not initialize Bubble Tea model:\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif _, err := tea.NewProgram(model).Run(); err != nil {\n\t\tfmt.Println(\"Bummer, there's been an error:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/go.mod",
    "content": "module examples\n\ngo 1.25.2\n\nreplace charm.land/bubbletea/v2 => ../\n\nrequire (\n\tcharm.land/bubbles/v2 v2.0.0\n\tcharm.land/bubbletea/v2 v2.0.0\n\tcharm.land/lipgloss/v2 v2.0.2\n\tgithub.com/charmbracelet/colorprofile v0.4.3\n\tgithub.com/charmbracelet/glamour/v2 v2.0.0-20251106195642-800eb8175930\n\tgithub.com/charmbracelet/harmonica v0.2.0\n\tgithub.com/charmbracelet/x/ansi v0.11.6\n\tgithub.com/charmbracelet/x/exp/charmtone v0.0.0-20250602192518-9e722df69bbb\n\tgithub.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776\n\tgithub.com/lucasb-eyer/go-colorful v1.3.0\n\tgithub.com/mattn/go-isatty v0.0.20\n\tgithub.com/segmentio/ksuid v1.0.4\n)\n\nrequire (\n\tgithub.com/alecthomas/chroma/v2 v2.14.0 // indirect\n\tgithub.com/atotto/clipboard v0.1.4 // indirect\n\tgithub.com/aymerick/douceur v0.2.0 // indirect\n\tgithub.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect\n\tgithub.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect\n\tgithub.com/charmbracelet/x/term v0.2.2 // indirect\n\tgithub.com/charmbracelet/x/termios v0.1.1 // indirect\n\tgithub.com/charmbracelet/x/windows v0.2.2 // indirect\n\tgithub.com/clipperhouse/displaywidth v0.11.0 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.7.0 // indirect\n\tgithub.com/dlclark/regexp2 v1.11.0 // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/gorilla/css v1.0.1 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.20 // indirect\n\tgithub.com/microcosm-cc/bluemonday v1.0.27 // indirect\n\tgithub.com/muesli/cancelreader v0.2.2 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/sahilm/fuzzy v0.1.1 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgithub.com/yuin/goldmark v1.7.8 // indirect\n\tgithub.com/yuin/goldmark-emoji v1.0.5 // indirect\n\tgolang.org/x/net v0.39.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgolang.org/x/text v0.24.0 // indirect\n)\n"
  },
  {
    "path": "examples/go.sum",
    "content": "charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=\ncharm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=\ncharm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs=\ncharm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=\ngithub.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=\ngithub.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=\ngithub.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=\ngithub.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=\ngithub.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=\ngithub.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=\ngithub.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=\ngithub.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=\ngithub.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=\ngithub.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=\ngithub.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=\ngithub.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=\ngithub.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=\ngithub.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=\ngithub.com/charmbracelet/glamour/v2 v2.0.0-20251106195642-800eb8175930 h1:+47Z2jVAWPSLGjPRbfZizW3OpcAYsu7EUk2DR+66FyM=\ngithub.com/charmbracelet/glamour/v2 v2.0.0-20251106195642-800eb8175930/go.mod h1:izs11tnkYaT3DTEH2E0V/lCb18VGZ7k9HLYEGuvgXGA=\ngithub.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=\ngithub.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=\ngithub.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=\ngithub.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=\ngithub.com/charmbracelet/x/exp/charmtone v0.0.0-20250602192518-9e722df69bbb h1:oTM8tZxV7FY0ehvYjFuICouuhzE08UZYNqUIp/lDQdY=\ngithub.com/charmbracelet/x/exp/charmtone v0.0.0-20250602192518-9e722df69bbb/go.mod h1:T9jr8CzFpjhFVHjNjKwbAD7KwBNyFnj2pntAO7F2zw0=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=\ngithub.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=\ngithub.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=\ngithub.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=\ngithub.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=\ngithub.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=\ngithub.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=\ngithub.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=\ngithub.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=\ngithub.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=\ngithub.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=\ngithub.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=\ngithub.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=\ngithub.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=\ngithub.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 h1:VRIbnDWRmAh5yBdz+J6yFMF5vso1It6vn+WmM/5l7MA=\ngithub.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776/go.mod h1:9wvnDu3YOfxzWM9Cst40msBF1C2UdQgDv962oTxSuMs=\ngithub.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=\ngithub.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=\ngithub.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=\ngithub.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=\ngithub.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=\ngithub.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=\ngithub.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=\ngithub.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=\ngithub.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=\ngithub.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=\ngithub.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=\ngithub.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=\ngithub.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=\ngithub.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=\ngithub.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=\ngolang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=\ngolang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=\ngolang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=\ngolang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=\ngolang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=\n"
  },
  {
    "path": "examples/help/README.md",
    "content": "# Help\n\n<img width=\"800\" src=\"./help.gif\" />\n"
  },
  {
    "path": "examples/help/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"charm.land/bubbles/v2/help\"\n\t\"charm.land/bubbles/v2/key\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\n// keyMap defines a set of keybindings. To work for help it must satisfy\n// key.Map. It could also very easily be a map[string]key.Binding.\ntype keyMap struct {\n\tUp    key.Binding\n\tDown  key.Binding\n\tLeft  key.Binding\n\tRight key.Binding\n\tHelp  key.Binding\n\tQuit  key.Binding\n}\n\n// ShortHelp returns keybindings to be shown in the mini help view. It's part\n// of the key.Map interface.\nfunc (k keyMap) ShortHelp() []key.Binding {\n\treturn []key.Binding{k.Help, k.Quit}\n}\n\n// FullHelp returns keybindings for the expanded help view. It's part of the\n// key.Map interface.\nfunc (k keyMap) FullHelp() [][]key.Binding {\n\treturn [][]key.Binding{\n\t\t{k.Up, k.Down, k.Left, k.Right}, // first column\n\t\t{k.Help, k.Quit},                // second column\n\t}\n}\n\nvar keys = keyMap{\n\tUp: key.NewBinding(\n\t\tkey.WithKeys(\"up\", \"k\"),\n\t\tkey.WithHelp(\"↑/k\", \"move up\"),\n\t),\n\tDown: key.NewBinding(\n\t\tkey.WithKeys(\"down\", \"j\"),\n\t\tkey.WithHelp(\"↓/j\", \"move down\"),\n\t),\n\tLeft: key.NewBinding(\n\t\tkey.WithKeys(\"left\", \"h\"),\n\t\tkey.WithHelp(\"←/h\", \"move left\"),\n\t),\n\tRight: key.NewBinding(\n\t\tkey.WithKeys(\"right\", \"l\"),\n\t\tkey.WithHelp(\"→/l\", \"move right\"),\n\t),\n\tHelp: key.NewBinding(\n\t\tkey.WithKeys(\"?\"),\n\t\tkey.WithHelp(\"?\", \"toggle help\"),\n\t),\n\tQuit: key.NewBinding(\n\t\tkey.WithKeys(\"q\", \"esc\", \"ctrl+c\"),\n\t\tkey.WithHelp(\"q\", \"quit\"),\n\t),\n}\n\ntype model struct {\n\tkeys       keyMap\n\thelp       help.Model\n\tinputStyle lipgloss.Style\n\tlastKey    string\n\tquitting   bool\n}\n\nfunc newModel() model {\n\treturn model{\n\t\tkeys:       keys,\n\t\thelp:       help.New(),\n\t\tinputStyle: lipgloss.NewStyle().Foreground(lipgloss.Color(\"#FF75B7\")),\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\t// If we set a width on the help menu it can gracefully truncate\n\t\t// its view as needed.\n\t\tm.help.SetWidth(msg.Width)\n\n\tcase tea.KeyPressMsg:\n\t\tswitch {\n\t\tcase key.Matches(msg, m.keys.Up):\n\t\t\tm.lastKey = \"↑\"\n\t\tcase key.Matches(msg, m.keys.Down):\n\t\t\tm.lastKey = \"↓\"\n\t\tcase key.Matches(msg, m.keys.Left):\n\t\t\tm.lastKey = \"←\"\n\t\tcase key.Matches(msg, m.keys.Right):\n\t\t\tm.lastKey = \"→\"\n\t\tcase key.Matches(msg, m.keys.Help):\n\t\t\tm.help.ShowAll = !m.help.ShowAll\n\t\tcase key.Matches(msg, m.keys.Quit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tif m.quitting {\n\t\treturn tea.NewView(\"Bye!\\n\")\n\t}\n\n\tvar status string\n\tif m.lastKey == \"\" {\n\t\tstatus = \"Waiting for input...\"\n\t} else {\n\t\tstatus = \"You chose: \" + m.inputStyle.Render(m.lastKey)\n\t}\n\n\thelpView := m.help.View(m.keys)\n\theight := 8 - strings.Count(status, \"\\n\") - strings.Count(helpView, \"\\n\")\n\n\treturn tea.NewView(status + strings.Repeat(\"\\n\", height) + helpView)\n}\n\nfunc main() {\n\tif os.Getenv(\"HELP_DEBUG\") != \"\" {\n\t\tf, err := tea.LogToFile(\"debug.log\", \"help\")\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Couldn't open a file for logging:\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tdefer f.Close() // nolint:errcheck\n\t}\n\n\tif _, err := tea.NewProgram(newModel()).Run(); err != nil {\n\t\tfmt.Printf(\"Could not start program :(\\n%v\\n\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/http/README.md",
    "content": "# HTTP\n\n<img width=\"800\" src=\"./http.gif\" />\n"
  },
  {
    "path": "examples/http/main.go",
    "content": "package main\n\n// A simple program that makes a GET request and prints the response status.\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nconst url = \"https://charm.sh/\"\n\ntype model struct {\n\tstatus int\n\terr    error\n}\n\ntype statusMsg int\n\ntype errMsg struct{ error }\n\nfunc (e errMsg) Error() string { return e.error.Error() }\n\nfunc main() {\n\tp := tea.NewProgram(model{})\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn checkServer\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\", \"esc\":\n\t\t\treturn m, tea.Quit\n\t\tdefault:\n\t\t\treturn m, nil\n\t\t}\n\n\tcase statusMsg:\n\t\tm.status = int(msg)\n\t\treturn m, tea.Quit\n\n\tcase errMsg:\n\t\tm.err = msg\n\t\treturn m, nil\n\n\tdefault:\n\t\treturn m, nil\n\t}\n}\n\nfunc (m model) View() tea.View {\n\ts := fmt.Sprintf(\"Checking %s...\", url)\n\tif m.err != nil {\n\t\ts += fmt.Sprintf(\"something went wrong: %s\", m.err)\n\t} else if m.status != 0 {\n\t\ts += fmt.Sprintf(\"%d %s\", m.status, http.StatusText(m.status))\n\t}\n\treturn tea.NewView(s + \"\\n\")\n}\n\nfunc checkServer() tea.Msg {\n\tc := &http.Client{\n\t\tTimeout: 10 * time.Second,\n\t}\n\tres, err := c.Get(url)\n\tif err != nil {\n\t\treturn errMsg{err}\n\t}\n\tdefer res.Body.Close() // nolint:errcheck\n\n\treturn statusMsg(res.StatusCode)\n}\n"
  },
  {
    "path": "examples/isbn-form/isbn-form.tape",
    "content": "Type \"go run ./isbn-form\"\nEnter\nSleep 2s\nType \"9783548372570\"\nSleep 500ms\nSleep 1s\nDown\nSleep 500ms\nType \"my book title\"\nSleep 500ms\nBackspace 10\nType \"title\"\nSleep 500ms\nEnter\nSleep 3s\nOutput isbn-form.gif\n"
  },
  {
    "path": "examples/isbn-form/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"charm.land/bubbles/v2/textinput\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/charmbracelet/x/exp/charmtone\"\n)\n\nfunc main() {\n\tp := tea.NewProgram(initialModel())\n\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\ntype (\n\terrMsg error\n)\n\nvar (\n\tinputStyle    = lipgloss.NewStyle().Foreground(lipgloss.Color(charmtone.Tang.Hex()))\n\tcontinueStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(charmtone.Anchovy.Hex()))\n\tvalidStyle    = lipgloss.NewStyle().Foreground(lipgloss.Color(charmtone.Guac.Hex()))\n\terrStyle      = lipgloss.NewStyle().Foreground(lipgloss.Color(charmtone.Cherry.Hex()))\n)\n\ntype model struct {\n\tisbnInput    textinput.Model\n\ttitleInput   textinput.Model\n\tfocusedInput int\n\terr          error\n}\n\n// canFindBook returns whether the find button is to be pressed\nfunc (m model) canFindBook() bool {\n\tcorrectIsbnGiven := m.isbnInput.Err == nil && len(m.isbnInput.Value()) != 0\n\tcorrectTitleGiven := m.titleInput.Err == nil && len(m.titleInput.Value()) != 0\n\n\treturn correctIsbnGiven && correctTitleGiven\n}\n\n// Validator function to ensure valid input\nfunc isbn13Validator(s string) error {\n\t// A valid ISBN looks like this:\n\t// 978-3-548-37257-0 or\n\t// 9783548372570 without any spaces\n\n\t// Remove dashes\n\ts = strings.ReplaceAll(s, \"-\", \"\")\n\tif len(s) != 13 {\n\t\treturn fmt.Errorf(\"ISBN is of wrong length\")\n\t}\n\n\tfor _, c := range s {\n\t\tif !unicode.IsDigit(c) {\n\t\t\treturn fmt.Errorf(\"ISBN contains invalid characters\")\n\t\t}\n\t}\n\n\tgs1Prefix := s[:3]\n\tswitch gs1Prefix {\n\tcase \"978\", \"979\":\n\t\tbreak\n\tdefault:\n\t\treturn fmt.Errorf(\"ISBN has invalid GS1 prefix\")\n\t}\n\n\t// The last digit, the check digit,\n\t// must make the checksum a multiple of 10.\n\t// All digits are added up after being multiplied\n\t// by either 1 or 3 alternately.\n\t// So 9x1 + 7x3 + 8x1 + ... + 0 must be a multiple of 10.\n\n\tsum := 0\n\tfor i, c := range s {\n\t\t// Convert rune to int\n\t\tn := int(c - '0')\n\n\t\t// Multiply the uneven indices by 3\n\t\tif i%2 != 0 {\n\t\t\tn *= 3\n\t\t}\n\n\t\tsum += n\n\t}\n\n\tif sum%10 != 0 {\n\t\treturn fmt.Errorf(\"ISBN has invalid check digit\")\n\t}\n\n\treturn nil\n}\n\nvar bannedTitleWords = []string{\n\t\"very\",\n\t\"bad\",\n\t\"words\",\n\t\"that\",\n\t\"should\",\n\t\"not\",\n\t\"appear\",\n\t\"in\",\n\t\"book\",\n\t\"titles\",\n}\n\nfunc bookTitleValidator(s string) error {\n\ts = strings.TrimSpace(s)\n\n\tif len(s) == 0 {\n\t\treturn fmt.Errorf(\"Book title is empty\")\n\t}\n\n\tfor _, bannedWord := range bannedTitleWords {\n\t\tif strings.Contains(s, bannedWord) {\n\t\t\treturn fmt.Errorf(\"Book title contains banned word %q\", bannedWord)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc initialModel() model {\n\tisbnInput := textinput.New()\n\tisbnInput.Focus()\n\tisbnInput.Placeholder = \"978-X-XXX-XXXXX-X\"\n\tisbnInput.CharLimit = 17\n\tisbnInput.SetWidth(30)\n\tisbnInput.Prompt = \"\"\n\tisbnInput.Validate = isbn13Validator\n\n\ttitleInput := textinput.New()\n\ttitleInput.Blur()\n\ttitleInput.Placeholder = \"Title\"\n\ttitleInput.CharLimit = 100\n\ttitleInput.SetWidth(100)\n\ttitleInput.Prompt = \"\"\n\ttitleInput.Validate = bookTitleValidator\n\n\treturn model{\n\t\tisbnInput:    isbnInput,\n\t\ttitleInput:   titleInput,\n\t\tfocusedInput: 0,\n\t\terr:          nil,\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn textinput.Blink\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"up\", \"down\":\n\t\t\t// Switch between text inputs\n\t\t\tswitch m.focusedInput {\n\t\t\tcase 0:\n\t\t\t\tm.focusedInput = 1\n\t\t\t\tm.titleInput.Focus()\n\t\t\t\tm.isbnInput.Blur()\n\t\t\tcase 1:\n\t\t\t\tm.focusedInput = 0\n\t\t\t\tm.isbnInput.Focus()\n\t\t\t\tm.titleInput.Blur()\n\t\t\t}\n\t\tcase \"enter\":\n\t\t\t// Enter is blocked until all inputs are ok\n\t\t\tif m.canFindBook() {\n\t\t\t\treturn m, tea.Quit\n\t\t\t}\n\t\tcase \"ctrl+c\", \"esc\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\n\t// We handle errors just like any other message\n\tcase errMsg:\n\t\tm.err = msg\n\t\treturn m, nil\n\t}\n\n\tvar isbnCommand tea.Cmd\n\tm.isbnInput, isbnCommand = m.isbnInput.Update(msg)\n\n\tvar titleCommand tea.Cmd\n\tm.titleInput, titleCommand = m.titleInput.Update(msg)\n\n\treturn m, tea.Batch(isbnCommand, titleCommand)\n}\n\nfunc (m model) View() tea.View {\n\tvar continueText string\n\tif m.canFindBook() {\n\t\tcontinueText = continueStyle.Render(\"Find ->\")\n\t}\n\n\tvar isbnErrorText string\n\tif m.isbnInput.Value() != \"\" {\n\t\tif m.isbnInput.Err != nil {\n\t\t\tisbnErrorText = errStyle.Render(m.isbnInput.Err.Error())\n\t\t} else {\n\t\t\tisbnErrorText = validStyle.Render(\"Valid ISBN\")\n\t\t}\n\t}\n\n\tvar titleErrorText string\n\tif m.titleInput.Value() != \"\" {\n\t\tif m.titleInput.Err != nil {\n\t\t\ttitleErrorText = errStyle.Render(m.titleInput.Err.Error())\n\t\t} else {\n\t\t\ttitleErrorText = validStyle.Render(\"Valid title\")\n\t\t}\n\t}\n\n\treturn tea.NewView(fmt.Sprintf(\n\t\t` Search book:\n %s\n %s\n %s\n\n %s\n %s\n %s\n\n %s\n`,\n\t\tinputStyle.Width(30).Render(\"ISBN\"),\n\t\tm.isbnInput.View(),\n\t\tisbnErrorText,\n\n\t\tinputStyle.Width(30).Render(\"Title\"),\n\t\tm.titleInput.View(),\n\t\ttitleErrorText,\n\n\t\tcontinueText,\n\t) + \"\\n\")\n}\n"
  },
  {
    "path": "examples/keyboard-enhancements/main.go",
    "content": "package main\n\n// This is a simple example illustrating how to enable enhanced keyboard\n// support.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\ntype styles struct {\n\tui lipgloss.Style\n}\n\ntype model struct {\n\tsupportsDisambiguation bool\n\tsupportsEventTypes     bool\n\tstyles                 styles\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tea.RequestBackgroundColor\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\t// Bubble Tea will send a [tea.KeyboardEnhancementsMsg] on startup if the\n\t// terminal supports keyboard enhancements features.\n\t//\n\t// These features extend the capabilities of keyboard input beyond the basic legacy\n\t// support found in most terminals. This includes features like:\n\t//  - Key disambiguation: Improved ability to distinguish between certain key presses\n\t//     like \"enter\" and \"shift+enter\" or \"tab\" and \"ctrl+i\".\n\t//  - Key event types: The ability to report different types of key events such as\n\t//   key presses and key releases.\n\t//\n\t// This allows for more nuanced input handling in terminal applications.\n\t// You can ask Bubble Tea to request additional keyboard enhancements\n\t// features by setting fields on the [tea.View.KeyboardEnhancements] struct\n\t// in your [tea.View] method.\n\tcase tea.KeyboardEnhancementsMsg:\n\t\t// Check which features were able to be enabled.\n\t\tm.supportsDisambiguation = true // This is always enabled when this msg is received.\n\t\tm.supportsEventTypes = msg.SupportsEventTypes()\n\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\":\n\t\t\treturn m, tea.Quit\n\t\tdefault:\n\t\t\treturn m, tea.Println(\"  press: \" + msg.String())\n\t\t}\n\n\tcase tea.KeyReleaseMsg:\n\t\treturn m, tea.Printf(\"release: %s\", msg.String())\n\n\tcase tea.BackgroundColorMsg:\n\t\t// Initialize styles.\n\t\tm.updateStyles(msg.IsDark())\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tvar v tea.View\n\tvar b strings.Builder\n\tfmt.Fprintf(&b, \"Terminal supports key releases: %v\\n\", m.supportsEventTypes)\n\tfmt.Fprintf(&b, \"Terminal supports key disambiguation: %v\\n\", m.supportsDisambiguation)\n\tfmt.Fprint(&b, \"This demo logs key events. Press ctrl+c to quit.\")\n\tv.SetContent(b.String() + \"\\n\")\n\n\t// Attempt to enable reporting key event types (key presses and key\n\t// releases). By default, only key disambiguation is enabled which improves\n\t// the ability to distinguish between certain key presses like \"enter\" and\n\t// \"shift+enter\" or \"tab\" and \"ctrl+i\".\n\tv.KeyboardEnhancements.ReportEventTypes = true\n\n\treturn v\n}\n\nfunc (m *model) updateStyles(isDark bool) {\n\t// Initialize styles.\n\tlightDark := lipgloss.LightDark(isDark)\n\tgrey := lightDark(lipgloss.Color(\"239\"), lipgloss.Color(\"245\"))\n\tdarkGray := lightDark(lipgloss.Color(\"245\"), lipgloss.Color(\"239\"))\n\n\tm.styles.ui = lipgloss.NewStyle().\n\t\tForeground(grey).\n\t\tBorder(lipgloss.NormalBorder(), true, false, false, false).\n\t\tBorderForeground(darkGray)\n}\n\nfunc initialModel() model {\n\tm := model{}\n\tm.updateStyles(true) // default to dark styles.\n\treturn m\n}\n\nfunc main() {\n\tp := tea.NewProgram(initialModel())\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Urgh: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/list-default/README.md",
    "content": "# Default List\n\n<img width=\"800\" src=\"./list-default.gif\" />\n"
  },
  {
    "path": "examples/list-default/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"charm.land/bubbles/v2/list\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar docStyle = lipgloss.NewStyle().Margin(1, 2)\n\ntype item struct {\n\ttitle, desc string\n}\n\nfunc (i item) Title() string       { return i.title }\nfunc (i item) Description() string { return i.desc }\nfunc (i item) FilterValue() string { return i.title }\n\ntype model struct {\n\tlist list.Model\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tif msg.String() == \"ctrl+c\" {\n\t\t\treturn m, tea.Quit\n\t\t}\n\tcase tea.WindowSizeMsg:\n\t\th, v := docStyle.GetFrameSize()\n\t\tm.list.SetSize(msg.Width-h, msg.Height-v)\n\t}\n\n\tvar cmd tea.Cmd\n\tm.list, cmd = m.list.Update(msg)\n\treturn m, cmd\n}\n\nfunc (m model) View() tea.View {\n\tv := tea.NewView(docStyle.Render(m.list.View()))\n\tv.AltScreen = true\n\treturn v\n}\n\nfunc main() {\n\titems := []list.Item{\n\t\titem{title: \"Raspberry Pi’s\", desc: \"I have ’em all over my house\"},\n\t\titem{title: \"Nutella\", desc: \"It's good on toast\"},\n\t\titem{title: \"Bitter melon\", desc: \"It cools you down\"},\n\t\titem{title: \"Nice socks\", desc: \"And by that I mean socks without holes\"},\n\t\titem{title: \"Eight hours of sleep\", desc: \"I had this once\"},\n\t\titem{title: \"Cats\", desc: \"Usually\"},\n\t\titem{title: \"Plantasia, the album\", desc: \"My plants love it too\"},\n\t\titem{title: \"Pour over coffee\", desc: \"It takes forever to make though\"},\n\t\titem{title: \"VR\", desc: \"Virtual reality...what is there to say?\"},\n\t\titem{title: \"Noguchi Lamps\", desc: \"Such pleasing organic forms\"},\n\t\titem{title: \"Linux\", desc: \"Pretty much the best OS\"},\n\t\titem{title: \"Business school\", desc: \"Just kidding\"},\n\t\titem{title: \"Pottery\", desc: \"Wet clay is a great feeling\"},\n\t\titem{title: \"Shampoo\", desc: \"Nothing like clean hair\"},\n\t\titem{title: \"Table tennis\", desc: \"It’s surprisingly exhausting\"},\n\t\titem{title: \"Milk crates\", desc: \"Great for packing in your extra stuff\"},\n\t\titem{title: \"Afternoon tea\", desc: \"Especially the tea sandwich part\"},\n\t\titem{title: \"Stickers\", desc: \"The thicker the vinyl the better\"},\n\t\titem{title: \"20° Weather\", desc: \"Celsius, not Fahrenheit\"},\n\t\titem{title: \"Warm light\", desc: \"Like around 2700 Kelvin\"},\n\t\titem{title: \"The vernal equinox\", desc: \"The autumnal equinox is pretty good too\"},\n\t\titem{title: \"Gaffer’s tape\", desc: \"Basically sticky fabric\"},\n\t\titem{title: \"Terrycloth\", desc: \"In other words, towel fabric\"},\n\t}\n\n\tm := model{list: list.New(items, list.NewDefaultDelegate(), 0, 0)}\n\tm.list.Title = \"My Fave Things\"\n\n\tp := tea.NewProgram(m)\n\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/list-fancy/README.md",
    "content": "# Fancy List\n\n<img width=\"800\" src=\"./list-fancy.gif\" />\n"
  },
  {
    "path": "examples/list-fancy/delegate.go",
    "content": "package main\n\nimport (\n\t\"charm.land/bubbles/v2/key\"\n\t\"charm.land/bubbles/v2/list\"\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nfunc newItemDelegate(keys *delegateKeyMap, styles *styles) list.DefaultDelegate {\n\td := list.NewDefaultDelegate()\n\n\td.UpdateFunc = func(msg tea.Msg, m *list.Model) tea.Cmd {\n\t\tvar title string\n\n\t\tif i, ok := m.SelectedItem().(item); ok {\n\t\t\ttitle = i.Title()\n\t\t} else {\n\t\t\treturn nil\n\t\t}\n\n\t\tswitch msg := msg.(type) {\n\t\tcase tea.KeyPressMsg:\n\t\t\tswitch {\n\t\t\tcase key.Matches(msg, keys.choose):\n\t\t\t\treturn m.NewStatusMessage(styles.statusMessage.Render(\"You chose \" + title))\n\n\t\t\tcase key.Matches(msg, keys.remove):\n\t\t\t\tindex := m.Index()\n\t\t\t\tm.RemoveItem(index)\n\t\t\t\tif len(m.Items()) == 0 {\n\t\t\t\t\tkeys.remove.SetEnabled(false)\n\t\t\t\t}\n\t\t\t\treturn m.NewStatusMessage(styles.statusMessage.Render(\"Deleted \" + title))\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\thelp := []key.Binding{keys.choose, keys.remove}\n\n\td.ShortHelpFunc = func() []key.Binding {\n\t\treturn help\n\t}\n\n\td.FullHelpFunc = func() [][]key.Binding {\n\t\treturn [][]key.Binding{help}\n\t}\n\n\treturn d\n}\n\ntype delegateKeyMap struct {\n\tchoose key.Binding\n\tremove key.Binding\n}\n\n// Additional short help entries. This satisfies the help.KeyMap interface and\n// is entirely optional.\nfunc (d delegateKeyMap) ShortHelp() []key.Binding {\n\treturn []key.Binding{\n\t\td.choose,\n\t\td.remove,\n\t}\n}\n\n// Additional full help entries. This satisfies the help.KeyMap interface and\n// is entirely optional.\nfunc (d delegateKeyMap) FullHelp() [][]key.Binding {\n\treturn [][]key.Binding{\n\t\t{\n\t\t\td.choose,\n\t\t\td.remove,\n\t\t},\n\t}\n}\n\nfunc newDelegateKeyMap() *delegateKeyMap {\n\treturn &delegateKeyMap{\n\t\tchoose: key.NewBinding(\n\t\t\tkey.WithKeys(\"enter\"),\n\t\t\tkey.WithHelp(\"enter\", \"choose\"),\n\t\t),\n\t\tremove: key.NewBinding(\n\t\t\tkey.WithKeys(\"x\", \"backspace\"),\n\t\t\tkey.WithHelp(\"x\", \"delete\"),\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "examples/list-fancy/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\t\"charm.land/bubbles/v2/key\"\n\t\"charm.land/bubbles/v2/list\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\ntype styles struct {\n\tapp           lipgloss.Style\n\ttitle         lipgloss.Style\n\tstatusMessage lipgloss.Style\n}\n\nfunc newStyles(darkBG bool) styles {\n\tlightDark := lipgloss.LightDark(darkBG)\n\n\treturn styles{\n\t\tapp: lipgloss.NewStyle().\n\t\t\tPadding(1, 2),\n\t\ttitle: lipgloss.NewStyle().\n\t\t\tForeground(lipgloss.Color(\"#FFFDF5\")).\n\t\t\tBackground(lipgloss.Color(\"#25A065\")).\n\t\t\tPadding(0, 1),\n\t\tstatusMessage: lipgloss.NewStyle().\n\t\t\tForeground(lightDark(lipgloss.Color(\"#04B575\"), lipgloss.Color(\"#04B575\"))),\n\t}\n}\n\ntype item struct {\n\ttitle       string\n\tdescription string\n}\n\nfunc (i item) Title() string       { return i.title }\nfunc (i item) Description() string { return i.description }\nfunc (i item) FilterValue() string { return i.title }\n\ntype listKeyMap struct {\n\ttoggleSpinner    key.Binding\n\ttoggleTitleBar   key.Binding\n\ttoggleStatusBar  key.Binding\n\ttogglePagination key.Binding\n\ttoggleHelpMenu   key.Binding\n\tinsertItem       key.Binding\n}\n\nfunc newListKeyMap() *listKeyMap {\n\treturn &listKeyMap{\n\t\tinsertItem: key.NewBinding(\n\t\t\tkey.WithKeys(\"a\"),\n\t\t\tkey.WithHelp(\"a\", \"add item\"),\n\t\t),\n\t\ttoggleSpinner: key.NewBinding(\n\t\t\tkey.WithKeys(\"s\"),\n\t\t\tkey.WithHelp(\"s\", \"toggle spinner\"),\n\t\t),\n\t\ttoggleTitleBar: key.NewBinding(\n\t\t\tkey.WithKeys(\"T\"),\n\t\t\tkey.WithHelp(\"T\", \"toggle title\"),\n\t\t),\n\t\ttoggleStatusBar: key.NewBinding(\n\t\t\tkey.WithKeys(\"S\"),\n\t\t\tkey.WithHelp(\"S\", \"toggle status\"),\n\t\t),\n\t\ttogglePagination: key.NewBinding(\n\t\t\tkey.WithKeys(\"P\"),\n\t\t\tkey.WithHelp(\"P\", \"toggle pagination\"),\n\t\t),\n\t\ttoggleHelpMenu: key.NewBinding(\n\t\t\tkey.WithKeys(\"H\"),\n\t\t\tkey.WithHelp(\"H\", \"toggle help\"),\n\t\t),\n\t}\n}\n\ntype model struct {\n\tstyles        styles\n\tdarkBG        bool\n\twidth, height int\n\tonce          *sync.Once\n\tlist          list.Model\n\titemGenerator *randomItemGenerator\n\tkeys          *listKeyMap\n\tdelegateKeys  *delegateKeyMap\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tea.Batch(\n\t\ttea.RequestBackgroundColor,\n\t)\n}\n\nfunc (m *model) updateListProperties() {\n\t// Update list size.\n\th, v := m.styles.app.GetFrameSize()\n\tm.list.SetSize(m.width-h, m.height-v)\n\n\t// Update the model and list styles.\n\tm.styles = newStyles(m.darkBG)\n\tm.list.Styles.Title = m.styles.title\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmds []tea.Cmd\n\n\tswitch msg := msg.(type) {\n\tcase tea.BackgroundColorMsg:\n\t\tm.darkBG = msg.IsDark()\n\t\tm.updateListProperties()\n\t\treturn m, nil\n\n\tcase tea.WindowSizeMsg:\n\t\tm.width, m.height = msg.Width, msg.Height\n\t\tm.updateListProperties()\n\t\treturn m, nil\n\t}\n\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\t// Don't match any of the keys below if we're actively filtering.\n\t\tif m.list.FilterState() == list.Filtering {\n\t\t\tbreak\n\t\t}\n\n\t\tswitch {\n\t\tcase key.Matches(msg, m.keys.toggleSpinner):\n\t\t\tcmd := m.list.ToggleSpinner()\n\t\t\treturn m, cmd\n\n\t\tcase key.Matches(msg, m.keys.toggleTitleBar):\n\t\t\tv := !m.list.ShowTitle()\n\t\t\tm.list.SetShowTitle(v)\n\t\t\tm.list.SetShowFilter(v)\n\t\t\tm.list.SetFilteringEnabled(v)\n\t\t\treturn m, nil\n\n\t\tcase key.Matches(msg, m.keys.toggleStatusBar):\n\t\t\tm.list.SetShowStatusBar(!m.list.ShowStatusBar())\n\t\t\treturn m, nil\n\n\t\tcase key.Matches(msg, m.keys.togglePagination):\n\t\t\tm.list.SetShowPagination(!m.list.ShowPagination())\n\t\t\treturn m, nil\n\n\t\tcase key.Matches(msg, m.keys.toggleHelpMenu):\n\t\t\tm.list.SetShowHelp(!m.list.ShowHelp())\n\t\t\treturn m, nil\n\n\t\tcase key.Matches(msg, m.keys.insertItem):\n\t\t\tm.delegateKeys.remove.SetEnabled(true)\n\t\t\tnewItem := m.itemGenerator.next()\n\t\t\tinsCmd := m.list.InsertItem(0, newItem)\n\t\t\tstatusCmd := m.list.NewStatusMessage(m.styles.statusMessage.Render(\"Added \" + newItem.Title()))\n\t\t\treturn m, tea.Batch(insCmd, statusCmd)\n\t\t}\n\t}\n\n\t// This will also call our delegate's update function.\n\tnewListModel, cmd := m.list.Update(msg)\n\tm.list = newListModel\n\tcmds = append(cmds, cmd)\n\n\treturn m, tea.Batch(cmds...)\n}\n\nfunc (m model) View() tea.View {\n\tv := tea.NewView(m.styles.app.Render(m.list.View()))\n\tv.AltScreen = true\n\treturn v\n}\n\nfunc initialModel() model {\n\t// Initialize the model and list.\n\tm := model{}\n\tm.styles = newStyles(false) // default to dark background styles\n\n\tdelegateKeys := newDelegateKeyMap()\n\tlistKeys := newListKeyMap()\n\n\t// Make initial list of items.\n\tvar itemGenerator randomItemGenerator\n\tconst numItems = 24\n\titems := make([]list.Item, numItems)\n\tfor i := range numItems {\n\t\titems[i] = itemGenerator.next()\n\t}\n\n\t// Setup list.\n\tdelegate := newItemDelegate(delegateKeys, &m.styles)\n\tgroceryList := list.New(items, delegate, 0, 0)\n\tgroceryList.Title = \"Groceries\"\n\tgroceryList.Styles.Title = m.styles.title\n\tgroceryList.AdditionalFullHelpKeys = func() []key.Binding {\n\t\treturn []key.Binding{\n\t\t\tlistKeys.toggleSpinner,\n\t\t\tlistKeys.insertItem,\n\t\t\tlistKeys.toggleTitleBar,\n\t\t\tlistKeys.toggleStatusBar,\n\t\t\tlistKeys.togglePagination,\n\t\t\tlistKeys.toggleHelpMenu,\n\t\t}\n\t}\n\n\tm.list = groceryList\n\tm.keys = listKeys\n\tm.delegateKeys = delegateKeys\n\tm.itemGenerator = &itemGenerator\n\n\treturn m\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(initialModel()).Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/list-fancy/randomitems.go",
    "content": "package main\n\nimport (\n\t\"math/rand\"\n\t\"sync\"\n)\n\ntype randomItemGenerator struct {\n\ttitles     []string\n\tdescs      []string\n\ttitleIndex int\n\tdescIndex  int\n\tmtx        *sync.Mutex\n\tshuffle    *sync.Once\n}\n\nfunc (r *randomItemGenerator) reset() {\n\tr.mtx = &sync.Mutex{}\n\tr.shuffle = &sync.Once{}\n\n\tr.titles = []string{\n\t\t\"Artichoke\",\n\t\t\"Baking Flour\",\n\t\t\"Bananas\",\n\t\t\"Barley\",\n\t\t\"Bean Sprouts\",\n\t\t\"Bitter Melon\",\n\t\t\"Black Cod\",\n\t\t\"Blood Orange\",\n\t\t\"Brown Sugar\",\n\t\t\"Cashew Apple\",\n\t\t\"Cashews\",\n\t\t\"Cat Food\",\n\t\t\"Coconut Milk\",\n\t\t\"Cucumber\",\n\t\t\"Curry Paste\",\n\t\t\"Currywurst\",\n\t\t\"Dill\",\n\t\t\"Dragonfruit\",\n\t\t\"Dried Shrimp\",\n\t\t\"Eggs\",\n\t\t\"Fish Cake\",\n\t\t\"Furikake\",\n\t\t\"Garlic\",\n\t\t\"Gherkin\",\n\t\t\"Ginger\",\n\t\t\"Granulated Sugar\",\n\t\t\"Grapefruit\",\n\t\t\"Green Onion\",\n\t\t\"Hazelnuts\",\n\t\t\"Heavy whipping cream\",\n\t\t\"Honey Dew\",\n\t\t\"Horseradish\",\n\t\t\"Jicama\",\n\t\t\"Kohlrabi\",\n\t\t\"Leeks\",\n\t\t\"Lentils\",\n\t\t\"Licorice Root\",\n\t\t\"Meyer Lemons\",\n\t\t\"Milk\",\n\t\t\"Molasses\",\n\t\t\"Muesli\",\n\t\t\"Nectarine\",\n\t\t\"Niagamo Root\",\n\t\t\"Nopal\",\n\t\t\"Nutella\",\n\t\t\"Oat Milk\",\n\t\t\"Oatmeal\",\n\t\t\"Olives\",\n\t\t\"Papaya\",\n\t\t\"Party Gherkin\",\n\t\t\"Peppers\",\n\t\t\"Persian Lemons\",\n\t\t\"Pickle\",\n\t\t\"Pineapple\",\n\t\t\"Plantains\",\n\t\t\"Pocky\",\n\t\t\"Powdered Sugar\",\n\t\t\"Quince\",\n\t\t\"Radish\",\n\t\t\"Ramps\",\n\t\t\"Star Anise\",\n\t\t\"Sweet Potato\",\n\t\t\"Tamarind\",\n\t\t\"Unsalted Butter\",\n\t\t\"Watermelon\",\n\t\t\"Weißwurst\",\n\t\t\"Yams\",\n\t\t\"Yeast\",\n\t\t\"Yuzu\",\n\t\t\"Snow Peas\",\n\t}\n\n\tr.descs = []string{\n\t\t\"A little weird\",\n\t\t\"Bold flavor\",\n\t\t\"Can’t get enough\",\n\t\t\"Delectable\",\n\t\t\"Expensive\",\n\t\t\"Expired\",\n\t\t\"Exquisite\",\n\t\t\"Fresh\",\n\t\t\"Gimme\",\n\t\t\"In season\",\n\t\t\"Kind of spicy\",\n\t\t\"Looks fresh\",\n\t\t\"Looks good to me\",\n\t\t\"Maybe not\",\n\t\t\"My favorite\",\n\t\t\"Oh my\",\n\t\t\"On sale\",\n\t\t\"Organic\",\n\t\t\"Questionable\",\n\t\t\"Really fresh\",\n\t\t\"Refreshing\",\n\t\t\"Salty\",\n\t\t\"Scrumptious\",\n\t\t\"Delectable\",\n\t\t\"Slightly sweet\",\n\t\t\"Smells great\",\n\t\t\"Tasty\",\n\t\t\"Too ripe\",\n\t\t\"At last\",\n\t\t\"What?\",\n\t\t\"Wow\",\n\t\t\"Yum\",\n\t\t\"Maybe\",\n\t\t\"Sure, why not?\",\n\t}\n\n\tr.shuffle.Do(func() {\n\t\tshuf := func(x []string) {\n\t\t\trand.Shuffle(len(x), func(i, j int) { x[i], x[j] = x[j], x[i] })\n\t\t}\n\t\tshuf(r.titles)\n\t\tshuf(r.descs)\n\t})\n}\n\nfunc (r *randomItemGenerator) next() item {\n\tif r.mtx == nil {\n\t\tr.reset()\n\t}\n\n\tr.mtx.Lock()\n\tdefer r.mtx.Unlock()\n\n\ti := item{\n\t\ttitle:       r.titles[r.titleIndex],\n\t\tdescription: r.descs[r.descIndex],\n\t}\n\n\tr.titleIndex++\n\tif r.titleIndex >= len(r.titles) {\n\t\tr.titleIndex = 0\n\t}\n\n\tr.descIndex++\n\tif r.descIndex >= len(r.descs) {\n\t\tr.descIndex = 0\n\t}\n\n\treturn i\n}\n"
  },
  {
    "path": "examples/list-simple/README.md",
    "content": "# Simple List\n\n<img width=\"800\" src=\"./list-simple.gif\" />\n"
  },
  {
    "path": "examples/list-simple/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"charm.land/bubbles/v2/list\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nconst listHeight = 14\n\ntype styles struct {\n\ttitle        lipgloss.Style\n\titem         lipgloss.Style\n\tselectedItem lipgloss.Style\n\tpagination   lipgloss.Style\n\thelp         lipgloss.Style\n\tquitText     lipgloss.Style\n}\n\nfunc newStyles(darkBG bool) styles {\n\tvar s styles\n\ts.title = lipgloss.NewStyle().MarginLeft(2)\n\ts.item = lipgloss.NewStyle().PaddingLeft(4)\n\ts.selectedItem = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color(\"170\"))\n\ts.pagination = list.DefaultStyles(darkBG).PaginationStyle.PaddingLeft(4)\n\ts.help = list.DefaultStyles(darkBG).HelpStyle.PaddingLeft(4).PaddingBottom(1)\n\ts.quitText = lipgloss.NewStyle().Margin(1, 0, 2, 4)\n\treturn s\n}\n\ntype item string\n\nfunc (i item) FilterValue() string { return \"\" }\n\ntype itemDelegate struct {\n\tstyles *styles\n}\n\nfunc (d itemDelegate) Height() int                             { return 1 }\nfunc (d itemDelegate) Spacing() int                            { return 0 }\nfunc (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }\nfunc (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {\n\ti, ok := listItem.(item)\n\tif !ok {\n\t\treturn\n\t}\n\n\tstr := fmt.Sprintf(\"%d. %s\", index+1, i)\n\n\tfn := d.styles.item.Render\n\tif index == m.Index() {\n\t\tfn = func(s ...string) string {\n\t\t\treturn d.styles.selectedItem.Render(\"> \" + strings.Join(s, \" \"))\n\t\t}\n\t}\n\n\tfmt.Fprint(w, fn(str))\n}\n\ntype model struct {\n\tlist     list.Model\n\tchoice   string\n\tstyles   styles\n\tquitting bool\n}\n\nfunc initialModel() model {\n\titems := []list.Item{\n\t\titem(\"Ramen\"),\n\t\titem(\"Tomato Soup\"),\n\t\titem(\"Hamburgers\"),\n\t\titem(\"Cheeseburgers\"),\n\t\titem(\"Currywurst\"),\n\t\titem(\"Okonomiyaki\"),\n\t\titem(\"Pasta\"),\n\t\titem(\"Fillet Mignon\"),\n\t\titem(\"Caviar\"),\n\t\titem(\"Just Wine\"),\n\t}\n\n\tconst defaultWidth = 20\n\n\tl := list.New(items, itemDelegate{}, defaultWidth, listHeight)\n\tl.Title = \"What do you want for dinner?\"\n\tl.SetShowStatusBar(false)\n\tl.SetFilteringEnabled(false)\n\n\tm := model{list: l}\n\tm.updateStyles(true) // default to dark styles.\n\treturn m\n}\n\nfunc (m *model) updateStyles(isDark bool) {\n\tm.styles = newStyles(isDark)\n\tm.list.Styles.Title = m.styles.title\n\tm.list.Styles.PaginationStyle = m.styles.pagination\n\tm.list.Styles.HelpStyle = m.styles.help\n\tm.list.SetDelegate(itemDelegate{styles: &m.styles})\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tm.list.SetWidth(msg.Width)\n\t\treturn m, nil\n\n\tcase tea.KeyPressMsg:\n\t\tswitch keypress := msg.String(); keypress {\n\t\tcase \"q\", \"ctrl+c\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\n\t\tcase \"enter\":\n\t\t\ti, ok := m.list.SelectedItem().(item)\n\t\t\tif ok {\n\t\t\t\tm.choice = string(i)\n\t\t\t}\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\tvar cmd tea.Cmd\n\tm.list, cmd = m.list.Update(msg)\n\treturn m, cmd\n}\n\nfunc (m model) View() tea.View {\n\tif m.choice != \"\" {\n\t\treturn tea.NewView(m.styles.quitText.Render(fmt.Sprintf(\"%s? Sounds good to me.\", m.choice)))\n\t}\n\tif m.quitting {\n\t\treturn tea.NewView(m.styles.quitText.Render(\"Not hungry? That’s cool.\"))\n\t}\n\treturn tea.NewView(\"\\n\" + m.list.View())\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(initialModel()).Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/mouse/main.go",
    "content": "package main\n\n// A simple program that opens the alternate screen buffer and displays mouse\n// coordinates and events.\n\nimport (\n\t\"log\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nfunc main() {\n\tp := tea.NewProgram(model{})\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\ntype model struct{}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tif s := msg.String(); s == \"ctrl+c\" || s == \"q\" || s == \"esc\" {\n\t\t\treturn m, tea.Quit\n\t\t}\n\n\tcase tea.MouseMsg:\n\t\tmouse := msg.Mouse()\n\t\treturn m, tea.Printf(\"(X: %d, Y: %d) %s\", mouse.X, mouse.Y, mouse)\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tv := tea.NewView(\"Do mouse stuff. When you're done press q to quit.\\n\")\n\tv.MouseMode = tea.MouseModeAllMotion\n\treturn v\n}\n"
  },
  {
    "path": "examples/package-manager/README.md",
    "content": "# Package Manager\n\n<img width=\"800\" src=\"./package-manager.gif\" />\n"
  },
  {
    "path": "examples/package-manager/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/progress\"\n\t\"charm.land/bubbles/v2/spinner\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\ntype model struct {\n\tpackages []string\n\tindex    int\n\twidth    int\n\theight   int\n\tspinner  spinner.Model\n\tprogress progress.Model\n\tdone     bool\n}\n\nvar (\n\tcurrentPkgNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"211\"))\n\tdoneStyle           = lipgloss.NewStyle().Margin(1, 2)\n\tcheckMark           = lipgloss.NewStyle().Foreground(lipgloss.Color(\"42\")).SetString(\"✓\")\n)\n\nfunc newModel() model {\n\tp := progress.New(\n\t\tprogress.WithDefaultBlend(),\n\t\tprogress.WithWidth(40),\n\t\tprogress.WithoutPercentage(),\n\t)\n\ts := spinner.New()\n\ts.Style = lipgloss.NewStyle().Foreground(lipgloss.Color(\"63\"))\n\treturn model{\n\t\tpackages: getPackages(),\n\t\tspinner:  s,\n\t\tprogress: p,\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tea.Batch(downloadAndInstall(m.packages[m.index]), m.spinner.Tick)\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tm.width, m.height = msg.Width, msg.Height\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"esc\", \"q\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\tcase installedPkgMsg:\n\t\tpkg := m.packages[m.index]\n\t\tif m.index >= len(m.packages)-1 {\n\t\t\t// Everything's been installed. We're done!\n\t\t\tm.done = true\n\t\t\treturn m, tea.Sequence(\n\t\t\t\ttea.Printf(\"%s %s\", checkMark, pkg), // print the last success message\n\t\t\t\ttea.Quit,                            // exit the program\n\t\t\t)\n\t\t}\n\n\t\t// Update progress bar\n\t\tm.index++\n\t\tprogressCmd := m.progress.SetPercent(float64(m.index) / float64(len(m.packages)))\n\n\t\treturn m, tea.Batch(\n\t\t\tprogressCmd,\n\t\t\ttea.Printf(\"%s %s\", checkMark, pkg),     // print success message above our program\n\t\t\tdownloadAndInstall(m.packages[m.index]), // download the next package\n\t\t)\n\tcase spinner.TickMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.spinner, cmd = m.spinner.Update(msg)\n\t\treturn m, cmd\n\tcase progress.FrameMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.progress, cmd = m.progress.Update(msg)\n\t\treturn m, cmd\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tn := len(m.packages)\n\tw := lipgloss.Width(fmt.Sprintf(\"%d\", n))\n\n\tif m.done {\n\t\treturn tea.NewView(doneStyle.Render(fmt.Sprintf(\"Done! Installed %d packages.\\n\", n)))\n\t}\n\n\tpkgCount := fmt.Sprintf(\" %*d/%*d\", w, m.index, w, n)\n\n\tspin := m.spinner.View() + \" \"\n\tprog := m.progress.View()\n\tcellsAvail := max(0, m.width-lipgloss.Width(spin+prog+pkgCount))\n\n\tpkgName := currentPkgNameStyle.Render(m.packages[m.index])\n\tinfo := lipgloss.NewStyle().MaxWidth(cellsAvail).Render(\"Installing \" + pkgName)\n\n\tcellsRemaining := max(0, m.width-lipgloss.Width(spin+info+prog+pkgCount))\n\tgap := strings.Repeat(\" \", cellsRemaining)\n\n\treturn tea.NewView(spin + info + gap + prog + pkgCount)\n}\n\ntype installedPkgMsg string\n\nfunc downloadAndInstall(pkg string) tea.Cmd {\n\t// This is where you'd do i/o stuff to download and install packages. In\n\t// our case we're just pausing for a moment to simulate the process.\n\td := time.Millisecond * time.Duration(rand.Intn(500)) //nolint:gosec\n\treturn tea.Tick(d, func(t time.Time) tea.Msg {\n\t\treturn installedPkgMsg(pkg)\n\t})\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(newModel()).Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/package-manager/packages.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n)\n\nvar packages = []string{\n\t\"vegeutils\",\n\t\"libgardening\",\n\t\"currykit\",\n\t\"spicerack\",\n\t\"fullenglish\",\n\t\"eggy\",\n\t\"bad-kitty\",\n\t\"chai\",\n\t\"hojicha\",\n\t\"libtacos\",\n\t\"babys-monads\",\n\t\"libpurring\",\n\t\"currywurst-devel\",\n\t\"xmodmeow\",\n\t\"licorice-utils\",\n\t\"cashew-apple\",\n\t\"rock-lobster\",\n\t\"standmixer\",\n\t\"coffee-CUPS\",\n\t\"libesszet\",\n\t\"zeichenorientierte-benutzerschnittstellen\",\n\t\"schnurrkit\",\n\t\"old-socks-devel\",\n\t\"jalapeño\",\n\t\"molasses-utils\",\n\t\"xkohlrabi\",\n\t\"party-gherkin\",\n\t\"snow-peas\",\n\t\"libyuzu\",\n}\n\nfunc getPackages() []string {\n\tpkgs := packages\n\tcopy(pkgs, packages)\n\n\trand.Shuffle(len(pkgs), func(i, j int) {\n\t\tpkgs[i], pkgs[j] = pkgs[j], pkgs[i]\n\t})\n\n\tfor k := range pkgs {\n\t\tpkgs[k] += fmt.Sprintf(\"-%d.%d.%d\", rand.Intn(10), rand.Intn(10), rand.Intn(10)) //nolint:gosec\n\t}\n\treturn pkgs\n}\n"
  },
  {
    "path": "examples/pager/README.md",
    "content": "# Pager\n\n<img width=\"800\" src=\"./pager.gif\" />\n"
  },
  {
    "path": "examples/pager/artichoke.md",
    "content": "Glow\n====\n\nA casual introduction. 你好世界!\n\n## Let’s talk about artichokes\n\nThe _artichoke_ is mentioned as a garden plant in the 8th century BC by Homer\n**and** Hesiod. The naturally occurring variant of the artichoke, the cardoon,\nwhich is native to the Mediterranean area, also has records of use as a food\namong the ancient Greeks and Romans. Pliny the Elder mentioned growing of\n_carduus_ in Carthage and Cordoba.\n\n> He holds him with a skinny hand,\n> ‘There was a ship,’ quoth he.\n> ‘Hold off! unhand me, grey-beard loon!’\n> An artichoke, dropt he.\n\n--Samuel Taylor Coleridge, [The Rime of the Ancient Mariner][rime]\n\n[rime]: https://poetryfoundation.org/poems/43997/\n\n## Other foods worth mentioning\n\n1. Carrots\n1. Celery\n1. Tacos\n    * Soft\n    * Hard\n1. Cucumber\n\n## Things to eat today\n\n* [x] Carrots\n* [x] Ramen\n* [ ] Currywurst\n\n### Power levels of the aforementioned foods\n\n| Name       | Power | Comment          |\n| ---        | ---   | ---              |\n| Carrots    | 9001  | It’s over 9000?! |\n| Ramen      | 9002  | Also over 9000?! |\n| Currywurst | 10000 | What?!           |\n\n## Currying Artichokes\n\nHere’s a bit of code in [Haskell](https://haskell.org), because we are fancy.\nRemember that to compile Haskell you’ll need `ghc`.\n\n```haskell\nmodule Main where\n\nimport Data.Function ( (&) )\nimport Data.List ( intercalculate )\n\nhello :: String -> String\nhello s =\n    \"Hello, \" ++ s ++ \".\"\n\nmain :: IO ()\nmain =\n    map hello [ \"artichoke\", \"alcachofa\" ] & intercalculate \"\\n\" & putStrLn\n```\n\n***\n\n_Alcachofa_, if you were wondering, is artichoke in Spanish.\n"
  },
  {
    "path": "examples/pager/main.go",
    "content": "package main\n\n// An example program demonstrating the pager component from the Bubbles\n// component library.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"charm.land/bubbles/v2/viewport\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar (\n\ttitleStyle = func() lipgloss.Style {\n\t\tb := lipgloss.RoundedBorder()\n\t\tb.Right = \"├\"\n\t\treturn lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)\n\t}()\n\n\tinfoStyle = func() lipgloss.Style {\n\t\tb := lipgloss.RoundedBorder()\n\t\tb.Left = \"┤\"\n\t\treturn titleStyle.BorderStyle(b)\n\t}()\n)\n\ntype model struct {\n\tcontent  string\n\tready    bool\n\tviewport viewport.Model\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar (\n\t\tcmd  tea.Cmd\n\t\tcmds []tea.Cmd\n\t)\n\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tif k := msg.String(); k == \"ctrl+c\" || k == \"q\" || k == \"esc\" {\n\t\t\treturn m, tea.Quit\n\t\t}\n\n\tcase tea.WindowSizeMsg:\n\t\theaderHeight := lipgloss.Height(m.headerView())\n\t\tfooterHeight := lipgloss.Height(m.footerView())\n\t\tverticalMarginHeight := headerHeight + footerHeight\n\n\t\tif !m.ready {\n\t\t\t// Since this program is using the full size of the viewport we\n\t\t\t// need to wait until we've received the window dimensions before\n\t\t\t// we can initialize the viewport. The initial dimensions come in\n\t\t\t// quickly, though asynchronously, which is why we wait for them\n\t\t\t// here.\n\t\t\tm.viewport = viewport.New(viewport.WithWidth(msg.Width), viewport.WithHeight(msg.Height-verticalMarginHeight))\n\t\t\tm.viewport.YPosition = headerHeight\n\t\t\tm.viewport.LeftGutterFunc = func(info viewport.GutterContext) string {\n\t\t\t\tif info.Soft {\n\t\t\t\t\treturn \"     │ \"\n\t\t\t\t}\n\t\t\t\tif info.Index >= info.TotalLines {\n\t\t\t\t\treturn \"   ~ │ \"\n\t\t\t\t}\n\t\t\t\treturn fmt.Sprintf(\"%4d │ \", info.Index+1)\n\t\t\t}\n\t\t\tm.viewport.HighlightStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"238\")).Background(lipgloss.Color(\"34\"))\n\t\t\tm.viewport.SelectedHighlightStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"238\")).Background(lipgloss.Color(\"47\"))\n\t\t\tm.viewport.SetContent(m.content)\n\t\t\tm.viewport.SetHighlights(regexp.MustCompile(\"artichoke\").FindAllStringIndex(m.content, -1))\n\t\t\tm.viewport.HighlightNext()\n\t\t\tm.ready = true\n\t\t} else {\n\t\t\tm.viewport.SetWidth(msg.Width)\n\t\t\tm.viewport.SetHeight(msg.Height - verticalMarginHeight)\n\t\t}\n\t}\n\n\t// Handle keyboard and mouse events in the viewport\n\tm.viewport, cmd = m.viewport.Update(msg)\n\tcmds = append(cmds, cmd)\n\n\treturn m, tea.Batch(cmds...)\n}\n\nfunc (m model) View() tea.View {\n\tvar v tea.View\n\tv.AltScreen = true                    // use the full size of the terminal in its \"alternate screen buffer\"\n\tv.MouseMode = tea.MouseModeCellMotion // turn on mouse support so we can track the mouse wheel\n\tif !m.ready {\n\t\tv.SetContent(\"\\n  Initializing...\")\n\t} else {\n\t\tv.SetContent(fmt.Sprintf(\"%s\\n%s\\n%s\", m.headerView(), m.viewport.View(), m.footerView()))\n\t}\n\treturn v\n}\n\nfunc (m model) headerView() string {\n\ttitle := titleStyle.Render(\"Mr. Pager\")\n\tline := strings.Repeat(\"─\", max(0, m.viewport.Width()-lipgloss.Width(title)))\n\treturn lipgloss.JoinHorizontal(lipgloss.Center, title, line)\n}\n\nfunc (m model) footerView() string {\n\tinfo := infoStyle.Render(fmt.Sprintf(\"%3.f%%:%3.f%%\", m.viewport.ScrollPercent()*100, m.viewport.HorizontalScrollPercent()*100))\n\tline := strings.Repeat(\"─\", max(0, m.viewport.Width()-lipgloss.Width(info)))\n\treturn lipgloss.JoinHorizontal(lipgloss.Center, line, info)\n}\n\nfunc main() {\n\t// Load some text for our viewport\n\tcontent, err := os.ReadFile(\"artichoke.md\")\n\tif err != nil {\n\t\tfmt.Println(\"could not load file:\", err)\n\t\tos.Exit(1)\n\t}\n\n\tp := tea.NewProgram(\n\t\tmodel{content: string(content)},\n\t)\n\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Println(\"could not run program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/paginator/README.md",
    "content": "# Paginator\n\n<img width=\"800\" src=\"./paginator.gif\" />\n"
  },
  {
    "path": "examples/paginator/main.go",
    "content": "package main\n\n// A simple program demonstrating the paginator component from the Bubbles\n// component library.\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\n\t\"charm.land/bubbles/v2/paginator\"\n\t\"charm.land/lipgloss/v2\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype styles struct {\n\tactiveDot   lipgloss.Style\n\tinactiveDot lipgloss.Style\n}\n\nfunc newStyles(bgIsDark bool) (s styles) {\n\tlightDark := lipgloss.LightDark(bgIsDark)\n\n\ts.activeDot = lipgloss.NewStyle().Foreground(lightDark(lipgloss.Color(\"235\"), lipgloss.Color(\"252\"))).SetString(\"•\")\n\ts.inactiveDot = s.activeDot.Foreground(lightDark(lipgloss.Color(\"250\"), lipgloss.Color(\"238\"))).SetString(\"•\")\n\treturn s\n}\n\ntype model struct {\n\titems     []string\n\tpaginator paginator.Model\n}\n\nfunc newModel() model {\n\tvar items []string\n\tfor i := 1; i < 101; i++ {\n\t\ttext := fmt.Sprintf(\"Item %d\", i)\n\t\titems = append(items, text)\n\t}\n\n\tp := paginator.New()\n\tp.Type = paginator.Dots\n\tp.PerPage = 10\n\tp.SetTotalPages(len(items))\n\n\tm := model{\n\t\tpaginator: p,\n\t\titems:     items,\n\t}\n\n\tm.updateStyles(true) // default to dark styles\n\treturn m\n}\n\nfunc (m *model) updateStyles(isDark bool) {\n\tstyles := newStyles(isDark)\n\tm.paginator.ActiveDot = styles.activeDot.String()\n\tm.paginator.InactiveDot = styles.inactiveDot.String()\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tea.RequestBackgroundColor\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd tea.Cmd\n\tswitch msg := msg.(type) {\n\tcase tea.BackgroundColorMsg:\n\t\tm.updateStyles(msg.IsDark())\n\t\treturn m, nil\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"esc\", \"ctrl+c\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\tm.paginator, cmd = m.paginator.Update(msg)\n\treturn m, cmd\n}\n\nfunc (m model) View() tea.View {\n\tvar b strings.Builder\n\tb.WriteString(\"\\n  Paginator Example\\n\\n\")\n\tstart, end := m.paginator.GetSliceBounds(len(m.items))\n\tfor _, item := range m.items[start:end] {\n\t\tb.WriteString(\"  • \" + item + \"\\n\\n\")\n\t}\n\tb.WriteString(\"  \" + m.paginator.View())\n\tb.WriteString(\"\\n\\n  h/l ←/→ page • q: quit\\n\")\n\treturn tea.NewView(b.String())\n}\n\nfunc main() {\n\tp := tea.NewProgram(newModel())\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/pipe/README.md",
    "content": "# Pipe\n\n<img width=\"800\" src=\"./pipe.gif\" />\n"
  },
  {
    "path": "examples/pipe/main.go",
    "content": "package main\n\n// An example illustrating how to pipe in data to a Bubble Tea application.\n// More so, this serves as proof that Bubble Tea will automatically listen for\n// keystrokes when input is not a TTY, such as when data is piped or redirected\n// in.\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"charm.land/bubbles/v2/textinput\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nfunc main() {\n\tstat, err := os.Stdin.Stat()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tif stat.Mode()&os.ModeNamedPipe == 0 && stat.Size() == 0 {\n\t\tfmt.Println(\"Try piping in some text.\")\n\t\tos.Exit(1)\n\t}\n\n\treader := bufio.NewReader(os.Stdin)\n\tvar b strings.Builder\n\n\tfor {\n\t\tr, _, err := reader.ReadRune()\n\t\tif err != nil && err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\t_, err = b.WriteRune(r)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Error getting input:\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\tmodel := newModel(strings.TrimSpace(b.String()))\n\n\tif _, err := tea.NewProgram(model).Run(); err != nil {\n\t\tfmt.Println(\"Couldn't start program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n\ntype model struct {\n\tuserInput textinput.Model\n}\n\nfunc newModel(initialValue string) (m model) {\n\ti := textinput.New()\n\ti.Prompt = \"\"\n\n\ts := i.Styles()\n\ts.Cursor.Color = lipgloss.Color(\"63\")\n\ti.SetStyles(s)\n\n\ti.SetWidth(48)\n\ti.SetValue(initialValue)\n\ti.CursorEnd()\n\ti.Focus()\n\n\tm.userInput = i\n\treturn m\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn textinput.Blink\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tif key, ok := msg.(tea.KeyMsg); ok {\n\t\tswitch key.String() {\n\t\tcase \"ctrl+c\", \"esc\", \"enter\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\tvar cmd tea.Cmd\n\tm.userInput, cmd = m.userInput.Update(msg)\n\treturn m, cmd\n}\n\nfunc (m model) View() tea.View {\n\treturn tea.NewView(fmt.Sprintf(\n\t\t\"\\nYou piped in: %s\\n\\nPress ^C to exit\",\n\t\tm.userInput.View(),\n\t))\n}\n"
  },
  {
    "path": "examples/prevent-quit/main.go",
    "content": "package main\n\n// A program demonstrating how to use the WithFilter option to intercept events.\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\t\"charm.land/bubbles/v2/help\"\n\t\"charm.land/bubbles/v2/key\"\n\t\"charm.land/bubbles/v2/textarea\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar (\n\tchoiceStyle   = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color(\"241\"))\n\tsaveTextStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"170\"))\n\tquitViewStyle = lipgloss.NewStyle().Padding(1, 3).Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color(\"170\"))\n)\n\nfunc main() {\n\tp := tea.NewProgram(initialModel(), tea.WithFilter(filter))\n\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc filter(teaModel tea.Model, msg tea.Msg) tea.Msg {\n\tif _, ok := msg.(tea.QuitMsg); !ok {\n\t\treturn msg\n\t}\n\n\tm := teaModel.(model)\n\tif m.hasChanges {\n\t\treturn nil\n\t}\n\n\treturn msg\n}\n\ntype model struct {\n\ttextarea   textarea.Model\n\thelp       help.Model\n\tkeymap     keymap\n\tsaveText   string\n\thasChanges bool\n\tquitting   bool\n}\n\ntype keymap struct {\n\tsave key.Binding\n\tquit key.Binding\n}\n\nfunc initialModel() model {\n\tti := textarea.New()\n\tti.Placeholder = \"Only the best words\"\n\tti.Focus()\n\n\treturn model{\n\t\ttextarea: ti,\n\t\thelp:     help.New(),\n\t\tkeymap: keymap{\n\t\t\tsave: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"ctrl+s\"),\n\t\t\t\tkey.WithHelp(\"ctrl+s\", \"save\"),\n\t\t\t),\n\t\t\tquit: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"esc\", \"ctrl+c\"),\n\t\t\t\tkey.WithHelp(\"esc\", \"quit\"),\n\t\t\t),\n\t\t},\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn textarea.Blink\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tif m.quitting {\n\t\treturn m.updatePromptView(msg)\n\t}\n\n\treturn m.updateTextView(msg)\n}\n\nfunc (m model) updateTextView(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmds []tea.Cmd\n\tvar cmd tea.Cmd\n\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tm.saveText = \"\"\n\t\tswitch {\n\t\tcase key.Matches(msg, m.keymap.save):\n\t\t\tm.saveText = \"Changes saved!\"\n\t\t\tm.hasChanges = false\n\t\tcase key.Matches(msg, m.keymap.quit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase len(msg.Text) > 0:\n\t\t\tm.saveText = \"\"\n\t\t\tm.hasChanges = true\n\t\t\tfallthrough\n\t\tdefault:\n\t\t\tif !m.textarea.Focused() {\n\t\t\t\tcmd = m.textarea.Focus()\n\t\t\t\tcmds = append(cmds, cmd)\n\t\t\t}\n\t\t}\n\t}\n\tm.textarea, cmd = m.textarea.Update(msg)\n\tcmds = append(cmds, cmd)\n\treturn m, tea.Batch(cmds...)\n}\n\nfunc (m model) updatePromptView(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\t// For simplicity's sake, we'll treat any key besides \"y\" as \"no\"\n\t\tif key.Matches(msg, m.keymap.quit) || msg.String() == \"y\" {\n\t\t\tm.hasChanges = false\n\t\t\treturn m, tea.Quit\n\t\t}\n\t\tm.quitting = false\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tif m.quitting {\n\t\tif m.hasChanges {\n\t\t\ttext := lipgloss.JoinHorizontal(lipgloss.Top, \"You have unsaved changes. Quit without saving?\", choiceStyle.Render(\"[yN]\"))\n\t\t\treturn tea.NewView(quitViewStyle.Render(text))\n\t\t}\n\t\treturn tea.NewView(\"Very important. Thank you.\\n\")\n\t}\n\n\thelpView := m.help.ShortHelpView([]key.Binding{\n\t\tm.keymap.save,\n\t\tm.keymap.quit,\n\t})\n\n\treturn tea.NewView(fmt.Sprintf(\n\t\t\"Type some important things.\\n%s\\n %s\\n %s\",\n\t\tm.textarea.View(),\n\t\tsaveTextStyle.Render(m.saveText),\n\t\thelpView,\n\t) + \"\\n\\n\")\n}\n"
  },
  {
    "path": "examples/print-key/main.go",
    "content": "package main\n\nimport (\n\t\"log\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype model struct{}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyboardEnhancementsMsg:\n\t\treturn m, tea.Printf(\"Keyboard enhancements: EventTypes: %v\\n\",\n\t\t\tmsg.SupportsEventTypes())\n\tcase tea.KeyMsg:\n\t\tkey := msg.Key()\n\t\tswitch msg := msg.(type) {\n\t\tcase tea.KeyPressMsg:\n\t\t\tswitch msg.String() {\n\t\t\tcase \"ctrl+c\":\n\t\t\t\treturn m, tea.Quit\n\t\t\t}\n\t\t}\n\t\tformat := \"(%T) You pressed: %s\"\n\t\targs := []any{msg, msg.String()}\n\t\tif len(key.Text) > 0 {\n\t\t\tformat += \" (text: %q)\"\n\t\t\targs = append(args, key.Text)\n\t\t}\n\t\treturn m, tea.Printf(format, args...)\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tv := tea.NewView(\"Press any key to see its details printed to the terminal. Press 'ctrl+c' to quit.\")\n\tv.KeyboardEnhancements.ReportEventTypes = true\n\treturn v\n}\n\nfunc main() {\n\tp := tea.NewProgram(model{})\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Printf(\"Error running program: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "examples/progress-animated/README.md",
    "content": "# Animated Progress\n\n<img width=\"800\" src=\"./progress-animated.gif\" />\n"
  },
  {
    "path": "examples/progress-animated/main.go",
    "content": "package main\n\n// A simple example that shows how to render an animated progress bar. In this\n// example we bump the progress by 25% every two seconds, animating our\n// progress bar to its new target state.\n//\n// It's also possible to render a progress bar in a more static fashion without\n// transitions. For details on that approach see the progress-static example.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/progress\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nconst (\n\tpadding  = 2\n\tmaxWidth = 80\n)\n\nvar helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"#626262\")).Render\n\nfunc main() {\n\tm := model{\n\t\tprogress: progress.New(progress.WithDefaultBlend()),\n\t}\n\n\tif _, err := tea.NewProgram(m).Run(); err != nil {\n\t\tfmt.Println(\"Oh no!\", err)\n\t\tos.Exit(1)\n\t}\n}\n\ntype tickMsg time.Time\n\ntype model struct {\n\tprogress progress.Model\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tickCmd()\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\treturn m, tea.Quit\n\n\tcase tea.WindowSizeMsg:\n\t\tm.progress.SetWidth(msg.Width - padding*2 - 4)\n\t\tif m.progress.Width() > maxWidth {\n\t\t\tm.progress.SetWidth(maxWidth)\n\t\t}\n\t\treturn m, nil\n\n\tcase tickMsg:\n\t\tif m.progress.Percent() == 1.0 {\n\t\t\treturn m, tea.Quit\n\t\t}\n\n\t\t// Note that you can also use progress.Model.SetPercent to set the\n\t\t// percentage value explicitly, too.\n\t\tcmd := m.progress.IncrPercent(0.25)\n\t\treturn m, tea.Batch(tickCmd(), cmd)\n\n\t// FrameMsg is sent when the progress bar wants to animate itself\n\tcase progress.FrameMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.progress, cmd = m.progress.Update(msg)\n\t\treturn m, cmd\n\n\tdefault:\n\t\treturn m, nil\n\t}\n}\n\nfunc (m model) View() tea.View {\n\tpad := strings.Repeat(\" \", padding)\n\treturn tea.NewView(\"\\n\" +\n\t\tpad + m.progress.View() + \"\\n\\n\" +\n\t\tpad + helpStyle(\"Press any key to quit\"))\n}\n\nfunc tickCmd() tea.Cmd {\n\treturn tea.Tick(time.Second*1, func(t time.Time) tea.Msg {\n\t\treturn tickMsg(t)\n\t})\n}\n"
  },
  {
    "path": "examples/progress-bar/main.go",
    "content": "package main\n\nimport (\n\t\"log\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar body = lipgloss.NewStyle().Padding(1, 2)\n\ntype model struct {\n\tvalue int\n\twidth int\n\tstate tea.ProgressBarState\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tm.width = msg.Width\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"up\", \"k\":\n\t\t\tif m.value < 100 {\n\t\t\t\tm.value += 10\n\t\t\t}\n\t\tcase \"down\", \"j\":\n\t\t\tif m.value > 0 {\n\t\t\t\tm.value -= 10\n\t\t\t}\n\t\tcase \"left\", \"h\":\n\t\t\tif m.state > 0 {\n\t\t\t\tm.state--\n\t\t\t}\n\t\tcase \"right\", \"l\":\n\t\t\tif m.state < 4 {\n\t\t\t\tm.state++\n\t\t\t}\n\t\t}\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\ts := body.Width(m.width - body.GetHorizontalPadding()).Render(\n\t\t\"This demo requires a terminal emulator that supports an indeterminate progress bar, such a Windows Terminal or Ghostty. In other terminals (including tmux in a supporting terminal) nothing will happen.\\n\\nPress up/down to change value, left/right to change state, q to quit.\",\n\t)\n\tv := tea.NewView(s)\n\tv.ProgressBar = tea.NewProgressBar(m.state, m.value)\n\treturn v\n}\n\nfunc main() {\n\tp := tea.NewProgram(model{value: 50, state: tea.ProgressBarIndeterminate})\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatalf(\"Error: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "examples/progress-download/README.md",
    "content": "# Download Progress\n\nThis example demonstrates how to download a file from a URL and show its\nprogress with a [Progress Bubble][progress].\n\nIn this case we're getting download progress with an [`io.TeeReader`][tee] and\nsending progress `Msg`s to the `Program` with `Program.Send()`.\n\n## How to Run\n\nBuild the application with `go build .`, then run with a `--url` argument\nspecifying the URL of the file to download. For example:\n\n```\n./progress-download --url=\"https://download.blender.org/demo/color_vortex.blend\"\n```\n\nNote that in this example a TUI will not be shown for URLs that do not respond\nwith a ContentLength header.\n\n* * *\n\nThis example originally came from [this discussion][discussion].\n\n* * *\n\n<a href=\"https://charm.sh/\"><img alt=\"The Charm logo\" src=\"https://stuff.charm.sh/charm-badge.jpg\" width=\"400\"></a>\n\nCharm热爱开源 • Charm loves open source\n\n\n[progress]: https://github.com/charmbracelet/bubbles/\n[tee]: https://pkg.go.dev/io#TeeReader\n[discussion]: https://github.com/charmbracelet/bubbles/discussions/127\n"
  },
  {
    "path": "examples/progress-download/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"charm.land/bubbles/v2/progress\"\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nvar p *tea.Program\n\ntype progressWriter struct {\n\ttotal      int\n\tdownloaded int\n\tfile       *os.File\n\treader     io.Reader\n\tonProgress func(float64)\n}\n\nfunc (pw *progressWriter) Start() {\n\t// TeeReader calls pw.Write() each time a new response is received\n\t_, err := io.Copy(pw.file, io.TeeReader(pw.reader, pw))\n\tif err != nil {\n\t\tp.Send(progressErrMsg{err})\n\t}\n}\n\nfunc (pw *progressWriter) Write(p []byte) (int, error) {\n\tpw.downloaded += len(p)\n\tif pw.total > 0 && pw.onProgress != nil {\n\t\tpw.onProgress(float64(pw.downloaded) / float64(pw.total))\n\t}\n\treturn len(p), nil\n}\n\nfunc getResponse(url string) (*http.Response, error) {\n\tresp, err := http.Get(url) // nolint:gosec\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"receiving status of %d for url: %s\", resp.StatusCode, url)\n\t}\n\treturn resp, nil\n}\n\nfunc main() {\n\turl := flag.String(\"url\", \"\", \"url for the file to download\")\n\tflag.Parse()\n\n\tif *url == \"\" {\n\t\tflag.Usage()\n\t\tos.Exit(1)\n\t}\n\n\tresp, err := getResponse(*url)\n\tif err != nil {\n\t\tfmt.Println(\"could not get response\", err)\n\t\tos.Exit(1)\n\t}\n\tdefer resp.Body.Close() // nolint:errcheck\n\n\t// Don't add TUI if the header doesn't include content size\n\t// it's impossible see progress without total\n\tif resp.ContentLength <= 0 {\n\t\tfmt.Println(\"can't parse content length, aborting download\")\n\t\tos.Exit(1)\n\t}\n\n\tfilename := filepath.Base(*url)\n\tfile, err := os.Create(filename)\n\tif err != nil {\n\t\tfmt.Println(\"could not create file:\", err)\n\t\tos.Exit(1)\n\t}\n\tdefer file.Close() // nolint:errcheck\n\n\tpw := &progressWriter{\n\t\ttotal:  int(resp.ContentLength),\n\t\tfile:   file,\n\t\treader: resp.Body,\n\t\tonProgress: func(ratio float64) {\n\t\t\tp.Send(progressMsg(ratio))\n\t\t},\n\t}\n\n\tm := model{\n\t\tpw:       pw,\n\t\tprogress: progress.New(progress.WithDefaultBlend()),\n\t}\n\t// Start Bubble Tea\n\tp = tea.NewProgram(m)\n\n\t// Start the download\n\tgo pw.Start()\n\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Println(\"error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/progress-download/tui.go",
    "content": "package main\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/progress\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"#626262\")).Render\n\nconst (\n\tpadding  = 2\n\tmaxWidth = 80\n)\n\ntype progressMsg float64\n\ntype progressErrMsg struct{ err error }\n\nfunc finalPause() tea.Cmd {\n\treturn tea.Tick(time.Millisecond*750, func(_ time.Time) tea.Msg {\n\t\treturn nil\n\t})\n}\n\ntype model struct {\n\tpw       *progressWriter\n\tprogress progress.Model\n\terr      error\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\treturn m, tea.Quit\n\n\tcase tea.WindowSizeMsg:\n\t\tm.progress.SetWidth(msg.Width - padding*2 - 4)\n\t\tif m.progress.Width() > maxWidth {\n\t\t\tm.progress.SetWidth(maxWidth)\n\t\t}\n\t\treturn m, nil\n\n\tcase progressErrMsg:\n\t\tm.err = msg.err\n\t\treturn m, tea.Quit\n\n\tcase progressMsg:\n\t\tvar cmds []tea.Cmd\n\n\t\tif msg >= 1.0 {\n\t\t\tcmds = append(cmds, tea.Sequence(finalPause(), tea.Quit))\n\t\t}\n\n\t\tcmds = append(cmds, m.progress.SetPercent(float64(msg)))\n\t\treturn m, tea.Batch(cmds...)\n\n\t// FrameMsg is sent when the progress bar wants to animate itself\n\tcase progress.FrameMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.progress, cmd = m.progress.Update(msg)\n\t\treturn m, cmd\n\n\tdefault:\n\t\treturn m, nil\n\t}\n}\n\nfunc (m model) View() tea.View {\n\tif m.err != nil {\n\t\treturn tea.NewView(\"Error downloading: \" + m.err.Error() + \"\\n\")\n\t}\n\n\tpad := strings.Repeat(\" \", padding)\n\treturn tea.NewView(\"\\n\" +\n\t\tpad + m.progress.View() + \"\\n\\n\" +\n\t\tpad + helpStyle(\"Press any key to quit\"))\n}\n"
  },
  {
    "path": "examples/progress-static/README.md",
    "content": "# Static Progress\n\n<img width=\"800\" src=\"./progress-static.gif\" />\n"
  },
  {
    "path": "examples/progress-static/main.go",
    "content": "package main\n\n// A simple example that shows how to render a progress bar in a \"pure\"\n// fashion. In this example we bump the progress by 25% every second,\n// maintaining the progress state on our top level model using the progress bar\n// model's ViewAs method only for rendering.\n//\n// The signature for ViewAs is:\n//\n//     func (m Model) ViewAs(percent float64) string\n//\n// So it takes a float between 0 and 1, and renders the progress bar\n// accordingly. When using the progress bar in this \"pure\" fashion and there's\n// no need to call an Update method.\n//\n// The progress bar is also able to animate itself, however. For details see\n// the progress-animated example.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/progress\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nconst (\n\tpadding  = 2\n\tmaxWidth = 80\n)\n\nvar (\n\thelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"#626262\")).Render\n\tyellow    = lipgloss.Color(\"#FDFF8C\")\n\tpink      = lipgloss.Color(\"#FF7CCB\")\n)\n\nfunc main() {\n\tprog := progress.New(progress.WithScaled(true), progress.WithColors(pink, yellow))\n\n\tif _, err := tea.NewProgram(model{progress: prog}).Run(); err != nil {\n\t\tfmt.Println(\"Oh no!\", err)\n\t\tos.Exit(1)\n\t}\n}\n\ntype tickMsg time.Time\n\ntype model struct {\n\tpercent  float64\n\tprogress progress.Model\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tickCmd()\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\treturn m, tea.Quit\n\n\tcase tea.WindowSizeMsg:\n\t\tm.progress.SetWidth(msg.Width - padding*2 - 4)\n\t\tif m.progress.Width() > maxWidth {\n\t\t\tm.progress.SetWidth(maxWidth)\n\t\t}\n\t\treturn m, nil\n\n\tcase tickMsg:\n\t\tm.percent += 0.25\n\t\tif m.percent > 1.0 {\n\t\t\tm.percent = 1.0\n\t\t\treturn m, tea.Quit\n\t\t}\n\t\treturn m, tickCmd()\n\n\tdefault:\n\t\treturn m, nil\n\t}\n}\n\nfunc (m model) View() tea.View {\n\tpad := strings.Repeat(\" \", padding)\n\treturn tea.NewView(\"\\n\" +\n\t\tpad + m.progress.ViewAs(m.percent) + \"\\n\\n\" +\n\t\tpad + helpStyle(\"Press any key to quit\"))\n}\n\nfunc tickCmd() tea.Cmd {\n\treturn tea.Tick(time.Second, func(t time.Time) tea.Msg {\n\t\treturn tickMsg(t)\n\t})\n}\n"
  },
  {
    "path": "examples/query-term/main.go",
    "content": "// This example uses a textinput to send the terminal ANSI sequences to query\n// it for capabilities.\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"charm.land/bubbles/v2/textinput\"\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nfunc newModel() model {\n\tti := textinput.New()\n\tti.Focus()\n\tti.CharLimit = 156\n\tti.SetWidth(20)\n\tti.SetVirtualCursor(false)\n\treturn model{input: ti}\n}\n\ntype model struct {\n\tinput textinput.Model\n\terr   error\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmds []tea.Cmd\n\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tm.err = nil\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"enter\":\n\t\t\t// Write the sequence to the terminal.\n\t\t\tval := m.input.Value()\n\t\t\tval = \"\\\"\" + val + \"\\\"\"\n\n\t\t\t// Unescape the sequence.\n\t\t\tseq, err := strconv.Unquote(val)\n\t\t\tif err != nil {\n\t\t\t\tm.err = err\n\t\t\t\treturn m, nil\n\t\t\t}\n\n\t\t\tif !strings.HasPrefix(seq, \"\\x1b\") {\n\t\t\t\tm.err = fmt.Errorf(\"sequence is not an ANSI escape sequence\")\n\t\t\t\treturn m, nil\n\t\t\t}\n\n\t\t\tm.input.SetValue(\"\")\n\n\t\t\t// Write the sequence to the terminal.\n\t\t\treturn m, func() tea.Msg {\n\t\t\t\tio.WriteString(os.Stdout, seq)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\tdefault:\n\t\t_, typ, ok := strings.Cut(fmt.Sprintf(\"%T\", msg), \".\")\n\t\tif ok && unicode.IsUpper(rune(typ[0])) {\n\t\t\t// Only log messages that are exported types.\n\t\t\tcmds = append(cmds, tea.Printf(\"Received message: %T %+v\", msg, msg))\n\t\t}\n\t}\n\n\tvar cmd tea.Cmd\n\tm.input, cmd = m.input.Update(msg)\n\tcmds = append(cmds, cmd)\n\n\treturn m, tea.Batch(cmds...)\n}\n\nfunc (m model) View() tea.View {\n\tvar s strings.Builder\n\ts.WriteString(m.input.View())\n\tif m.err != nil {\n\t\ts.WriteString(\"\\n\\nError: \" + m.err.Error())\n\t}\n\ts.WriteString(\"\\n\\nPress ctrl+c to quit, enter to write the sequence to terminal\")\n\tv := tea.NewView(s.String())\n\tv.Cursor = m.input.Cursor()\n\treturn v\n}\n\nfunc main() {\n\tp := tea.NewProgram(newModel())\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/realtime/README.md",
    "content": "# Real Time\n\n<img width=\"800\" src=\"./realtime.gif\" />\n"
  },
  {
    "path": "examples/realtime/main.go",
    "content": "package main\n\n// A simple example that shows how to send activity to Bubble Tea in real-time\n// through a channel.\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/spinner\"\n\ttea \"charm.land/bubbletea/v2\"\n)\n\n// A message used to indicate that activity has occurred. In the real world (for\n// example, chat) this would contain actual data.\ntype responseMsg struct{}\n\n// Simulate a process that sends events at an irregular interval in real time.\n// In this case, we'll send events on the channel at a random interval between\n// 100 to 1000 milliseconds. As a command, Bubble Tea will run this\n// asynchronously.\nfunc listenForActivity(sub chan struct{}) tea.Cmd {\n\treturn func() tea.Msg {\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond * time.Duration(rand.Int63n(900)+100)) // nolint:gosec\n\t\t\tsub <- struct{}{}\n\t\t}\n\t}\n}\n\n// A command that waits for the activity on a channel.\nfunc waitForActivity(sub chan struct{}) tea.Cmd {\n\treturn func() tea.Msg {\n\t\treturn responseMsg(<-sub)\n\t}\n}\n\ntype model struct {\n\tsub       chan struct{} // where we'll receive activity notifications\n\tresponses int           // how many responses we've received\n\tspinner   spinner.Model\n\tquitting  bool\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tea.Batch(\n\t\tm.spinner.Tick,\n\t\tlistenForActivity(m.sub), // generate activity\n\t\twaitForActivity(m.sub),   // wait for activity\n\t)\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tm.quitting = true\n\t\treturn m, tea.Quit\n\tcase responseMsg:\n\t\tm.responses++                    // record external activity\n\t\treturn m, waitForActivity(m.sub) // wait for next event\n\tcase spinner.TickMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.spinner, cmd = m.spinner.Update(msg)\n\t\treturn m, cmd\n\tdefault:\n\t\treturn m, nil\n\t}\n}\n\nfunc (m model) View() tea.View {\n\ts := fmt.Sprintf(\"\\n %s Events received: %d\\n\\n Press any key to exit\\n\", m.spinner.View(), m.responses)\n\tif m.quitting {\n\t\ts += \"\\n\"\n\t}\n\treturn tea.NewView(s)\n}\n\nfunc main() {\n\tp := tea.NewProgram(model{\n\t\tsub:     make(chan struct{}),\n\t\tspinner: spinner.New(),\n\t})\n\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Println(\"could not start program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/result/README.md",
    "content": "# Result\n\n<img width=\"800\" src=\"./result.gif\" />\n"
  },
  {
    "path": "examples/result/main.go",
    "content": "package main\n\n// A simple example that shows how to retrieve a value from a Bubble Tea\n// program after the Bubble Tea has exited.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nvar choices = []string{\"Taro\", \"Coffee\", \"Lychee\"}\n\ntype model struct {\n\tcursor int\n\tchoice string\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"q\", \"esc\":\n\t\t\treturn m, tea.Quit\n\n\t\tcase \"enter\":\n\t\t\t// Send the choice on the channel and exit.\n\t\t\tm.choice = choices[m.cursor]\n\t\t\treturn m, tea.Quit\n\n\t\tcase \"down\", \"j\":\n\t\t\tm.cursor++\n\t\t\tif m.cursor >= len(choices) {\n\t\t\t\tm.cursor = 0\n\t\t\t}\n\n\t\tcase \"up\", \"k\":\n\t\t\tm.cursor--\n\t\t\tif m.cursor < 0 {\n\t\t\t\tm.cursor = len(choices) - 1\n\t\t\t}\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\ts := strings.Builder{}\n\ts.WriteString(\"What kind of Bubble Tea would you like to order?\\n\\n\")\n\n\tfor i := range choices {\n\t\tif m.cursor == i {\n\t\t\ts.WriteString(\"(•) \")\n\t\t} else {\n\t\t\ts.WriteString(\"( ) \")\n\t\t}\n\t\ts.WriteString(choices[i])\n\t\ts.WriteString(\"\\n\")\n\t}\n\ts.WriteString(\"\\n(press q to quit)\\n\")\n\n\treturn tea.NewView(s.String())\n}\n\nfunc main() {\n\tp := tea.NewProgram(model{})\n\n\t// Run returns the model as a tea.Model.\n\tm, err := p.Run()\n\tif err != nil {\n\t\tfmt.Println(\"Oh no:\", err)\n\t\tos.Exit(1)\n\t}\n\n\t// Assert the final tea.Model to our local model and print the choice.\n\tif m, ok := m.(model); ok && m.choice != \"\" {\n\t\tfmt.Printf(\"\\n---\\nYou chose %s!\\n\", m.choice)\n\t}\n}\n"
  },
  {
    "path": "examples/send-msg/README.md",
    "content": "# Send Msg\n\n<img width=\"800\" src=\"./send-msg.gif\" />\n"
  },
  {
    "path": "examples/send-msg/main.go",
    "content": "package main\n\n// A simple example that shows how to send messages to a Bubble Tea program\n// from outside the program using Program.Send(Msg).\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/spinner\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar (\n\tspinnerStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color(\"63\"))\n\thelpStyle     = lipgloss.NewStyle().Foreground(lipgloss.Color(\"241\")).Margin(1, 0)\n\tdotStyle      = helpStyle.UnsetMargins()\n\tdurationStyle = dotStyle\n\tappStyle      = lipgloss.NewStyle().Margin(1, 2, 0, 2)\n)\n\ntype resultMsg struct {\n\tduration time.Duration\n\tfood     string\n}\n\nfunc (r resultMsg) String() string {\n\tif r.duration == 0 {\n\t\treturn dotStyle.Render(strings.Repeat(\".\", 30))\n\t}\n\treturn fmt.Sprintf(\"🍔 Ate %s %s\", r.food,\n\t\tdurationStyle.Render(r.duration.String()))\n}\n\ntype model struct {\n\tspinner  spinner.Model\n\tresults  []resultMsg\n\tquitting bool\n}\n\nfunc newModel() model {\n\tconst numLastResults = 5\n\ts := spinner.New()\n\ts.Style = spinnerStyle\n\treturn model{\n\t\tspinner: s,\n\t\tresults: make([]resultMsg, numLastResults),\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn m.spinner.Tick\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tm.quitting = true\n\t\treturn m, tea.Quit\n\tcase resultMsg:\n\t\tm.results = append(m.results[1:], msg)\n\t\treturn m, nil\n\tcase spinner.TickMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.spinner, cmd = m.spinner.Update(msg)\n\t\treturn m, cmd\n\tdefault:\n\t\treturn m, nil\n\t}\n}\n\nfunc (m model) View() tea.View {\n\tvar b strings.Builder\n\n\tif m.quitting {\n\t\tb.WriteString(\"That's all for today!\")\n\t} else {\n\t\tb.WriteString(m.spinner.View())\n\t\tb.WriteString(\" Eating food...\")\n\t}\n\n\tb.WriteString(\"\\n\\n\")\n\n\tfor _, res := range m.results {\n\t\tb.WriteString(res.String())\n\t\tb.WriteString(\"\\n\")\n\t}\n\n\tif !m.quitting {\n\t\tb.WriteString(helpStyle.Render(\"Press any key to exit\"))\n\t}\n\n\tif m.quitting {\n\t\tb.WriteString(\"\\n\")\n\t}\n\n\treturn tea.NewView(appStyle.Render(b.String()))\n}\n\nfunc main() {\n\tp := tea.NewProgram(newModel())\n\n\t// Simulate activity\n\tgo func() {\n\t\tfor {\n\t\t\tpause := time.Duration(rand.Int63n(899)+100) * time.Millisecond // nolint:gosec\n\t\t\ttime.Sleep(pause)\n\n\t\t\t// Send the Bubble Tea program a message from outside the\n\t\t\t// tea.Program. This will block until it is ready to receive\n\t\t\t// messages.\n\t\t\tp.Send(resultMsg{food: randomFood(), duration: pause})\n\t\t}\n\t}()\n\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc randomFood() string {\n\tfood := []string{\n\t\t\"an apple\", \"a pear\", \"a gherkin\", \"a party gherkin\",\n\t\t\"a kohlrabi\", \"some spaghetti\", \"tacos\", \"a currywurst\", \"some curry\",\n\t\t\"a sandwich\", \"some peanut butter\", \"some cashews\", \"some ramen\",\n\t}\n\treturn food[rand.Intn(len(food))] // nolint:gosec\n}\n"
  },
  {
    "path": "examples/sequence/README.md",
    "content": "# Sequence\n\n<img width=\"800\" src=\"./sequence.gif\" />\n"
  },
  {
    "path": "examples/sequence/main.go",
    "content": "package main\n\n// A simple example illustrating how to run a series of commands in order.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype model struct{}\n\nfunc (m model) Init() tea.Cmd {\n\t// A tea.Sequence is a command that runs a series of commands in\n\t// order. Contrast this with tea.Batch, which runs a series of commands\n\t// concurrently, with no order guarantees.\n\treturn tea.Sequence(\n\t\ttea.Batch(\n\t\t\ttea.Sequence(\n\t\t\t\tSleepPrintln(\"1-1-1\", 1000),\n\t\t\t\tSleepPrintln(\"1-1-2\", 1000),\n\t\t\t),\n\t\t\ttea.Batch(\n\t\t\t\tSleepPrintln(\"1-2-1\", 1500),\n\t\t\t\tSleepPrintln(\"1-2-2\", 1250),\n\t\t\t),\n\t\t),\n\t\ttea.Println(\"2\"),\n\t\ttea.Sequence(\n\t\t\ttea.Batch(\n\t\t\t\tSleepPrintln(\"3-1-1\", 500),\n\t\t\t\tSleepPrintln(\"3-1-2\", 1000),\n\t\t\t),\n\t\t\ttea.Sequence(\n\t\t\t\tSleepPrintln(\"3-2-1\", 750),\n\t\t\t\tSleepPrintln(\"3-2-2\", 500),\n\t\t\t),\n\t\t),\n\t\ttea.Quit,\n\t)\n}\n\n// print string after stopping for a certain period of time\nfunc SleepPrintln(s string, milisecond int) tea.Cmd {\n\tprintCmd := tea.Println(s)\n\treturn func() tea.Msg {\n\t\ttime.Sleep(time.Duration(milisecond) * time.Millisecond)\n\t\treturn printCmd()\n\t}\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\treturn m, tea.Quit\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\treturn tea.NewView(\"\")\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(model{}).Run(); err != nil {\n\t\tfmt.Println(\"Uh oh:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/set-terminal-color/main.go",
    "content": "package main\n\nimport (\n\t\"image/color\"\n\t\"log\"\n\t\"strings\"\n\n\t\"charm.land/bubbles/v2/textinput\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/lucasb-eyer/go-colorful\"\n)\n\ntype colorType int\n\nconst (\n\tforeground colorType = iota + 1\n\tbackground\n\tcursor\n)\n\nfunc (c colorType) String() string {\n\tswitch c {\n\tcase foreground:\n\t\treturn \"Foreground\"\n\tcase background:\n\t\treturn \"Background\"\n\tcase cursor:\n\t\treturn \"Cursor\"\n\tdefault:\n\t\treturn \"Unknown\"\n\t}\n}\n\ntype state int\n\nconst (\n\tchooseState state = iota\n\tinputState\n)\n\ntype model struct {\n\tti          textinput.Model\n\tchoice      colorType\n\tstate       state\n\tchoiceIndex int\n\terr         error\n\tfg, bg, cc  color.Color\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn textinput.Blink\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\n\t\tswitch m.state {\n\t\tcase chooseState:\n\t\t\tm.ti.Blur()\n\t\t\tswitch msg.String() {\n\t\t\tcase \"j\", \"down\":\n\t\t\t\tm.choiceIndex++\n\t\t\t\tif m.choiceIndex > 2 {\n\t\t\t\t\tm.choiceIndex = 0\n\t\t\t\t}\n\t\t\tcase \"k\", \"up\":\n\t\t\t\tm.choiceIndex--\n\t\t\t\tif m.choiceIndex < 0 {\n\t\t\t\t\tm.choiceIndex = 2\n\t\t\t\t}\n\t\t\tcase \"enter\":\n\t\t\t\tm.state = inputState\n\t\t\t\tm.ti.Focus()\n\t\t\t\tswitch m.choiceIndex {\n\t\t\t\tcase 0:\n\t\t\t\t\tm.choice = foreground\n\t\t\t\tcase 1:\n\t\t\t\t\tm.choice = background\n\t\t\t\tcase 2:\n\t\t\t\t\tm.choice = cursor\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase inputState:\n\t\t\tm.ti.Focus()\n\t\t\tswitch msg.String() {\n\t\t\tcase \"esc\":\n\t\t\t\tm.choice = 0\n\t\t\t\tm.choiceIndex = 0\n\t\t\t\tm.state = chooseState\n\t\t\t\tm.err = nil\n\t\t\t\tm.ti.Blur()\n\t\t\tcase \"enter\":\n\t\t\t\tval := m.ti.Value()\n\t\t\t\tcol, err := colorful.Hex(val)\n\t\t\t\tif err != nil {\n\t\t\t\t\tm.err = err\n\t\t\t\t} else {\n\t\t\t\t\tm.err = nil\n\t\t\t\t\tchoice := m.choice\n\t\t\t\t\tm.choice = 0\n\t\t\t\t\tm.choiceIndex = 0\n\t\t\t\t\tm.state = chooseState\n\n\t\t\t\t\t// Reset the text input\n\t\t\t\t\tm.ti.Reset()\n\n\t\t\t\t\tswitch choice {\n\t\t\t\t\tcase foreground:\n\t\t\t\t\t\tm.fg = col\n\t\t\t\t\tcase background:\n\t\t\t\t\t\tm.bg = col\n\t\t\t\t\tcase cursor:\n\t\t\t\t\t\tm.cc = col\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tm.ti.Blur()\n\n\t\t\tdefault:\n\t\t\t\tvar cmd tea.Cmd\n\t\t\t\tm.ti, cmd = m.ti.Update(msg)\n\t\t\t\treturn m, cmd\n\t\t\t}\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tvar s strings.Builder\n\tinstructions := lipgloss.NewStyle().Width(40).Render(\"Choose a terminal-wide color to set. All settings will be cleared on exit.\")\n\n\tswitch m.state {\n\tcase chooseState:\n\t\ts.WriteString(instructions + \"\\n\\n\")\n\t\tfor i, c := range []colorType{foreground, background, cursor} {\n\t\t\tif i == m.choiceIndex {\n\t\t\t\ts.WriteString(\" > \")\n\t\t\t} else {\n\t\t\t\ts.WriteString(\"   \")\n\t\t\t}\n\t\t\ts.WriteString(c.String())\n\t\t\ts.WriteString(\"\\n\")\n\t\t}\n\tcase inputState:\n\t\ts.WriteString(\"Enter a color in hex format:\\n\\n\")\n\t\ts.WriteString(m.ti.View())\n\t\ts.WriteString(\"\\n\")\n\t}\n\n\tif m.err != nil {\n\t\ts.WriteString(\"\\nError: \")\n\t\ts.WriteString(m.err.Error())\n\t}\n\n\ts.WriteString(\"\\nPress q to quit\")\n\n\tswitch m.state {\n\tcase chooseState:\n\t\ts.WriteString(\", j/k to move, and enter to select\")\n\tcase inputState:\n\t\ts.WriteString(\", and enter to submit, esc to go back\")\n\t}\n\n\ts.WriteString(\"\\n\")\n\n\tv := tea.NewView(s.String())\n\tif m.ti.Focused() {\n\t\tv.Cursor = m.ti.Cursor()\n\t\tv.Cursor.Y += 2 // account for the prompt\n\t\tv.Cursor.Color = m.cc\n\t}\n\tv.BackgroundColor = m.bg\n\tv.ForegroundColor = m.fg\n\n\treturn v\n}\n\nfunc main() {\n\tti := textinput.New()\n\tti.Placeholder = \"#ff00ff\"\n\tti.CharLimit = 156\n\tti.SetWidth(20)\n\tti.SetVirtualCursor(false)\n\tp := tea.NewProgram(model{\n\t\tti: ti,\n\t})\n\n\t_, err := p.Run()\n\tif err != nil {\n\t\tlog.Fatalf(\"Error running program: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "examples/set-window-title/main.go",
    "content": "package main\n\n// A simple example illustrating how to set a window title.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nconst windowTitle = \"Hello, Bubble Tea\"\n\ntype model struct{}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\treturn m, tea.Quit\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\twrap := lipgloss.NewStyle().Width(78).Render\n\tv := tea.NewView(wrap(\"The window title has been set to '\"+windowTitle+\"'. It will be cleared on exit.\") +\n\t\t\"\\n\\nPress any key to quit.\")\n\tv.WindowTitle = windowTitle\n\treturn v\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(model{}).Run(); err != nil {\n\t\tfmt.Println(\"Uh oh:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/simple/README.md",
    "content": "# Simple\n\n<img width=\"800\" src=\"./simple.gif\" />\n"
  },
  {
    "path": "examples/simple/main.go",
    "content": "package main\n\n// A simple program that counts down from 5 and then exits.\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nfunc main() {\n\t// Log to a file. Useful in debugging since you can't really log to stdout.\n\t// Not required.\n\tlogfilePath := os.Getenv(\"BUBBLETEA_LOG\")\n\tif logfilePath != \"\" {\n\t\tif _, err := tea.LogToFile(logfilePath, \"simple\"); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\n\t// Initialize our program\n\tp := tea.NewProgram(model(5))\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\n// A model can be more or less any type of data. It holds all the data for a\n// program, so often it's a struct. For this simple example, however, all\n// we'll need is a simple integer.\ntype model int\n\n// Init optionally returns an initial command we should run. In this case we\n// want to start the timer.\nfunc (m model) Init() tea.Cmd {\n\treturn tick\n}\n\n// Update is called when messages are received. The idea is that you inspect the\n// message and send back an updated model accordingly. You can also return\n// a command, which is a function that performs I/O and returns a message.\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"ctrl+z\":\n\t\t\treturn m, tea.Suspend\n\t\t}\n\n\tcase tickMsg:\n\t\tm--\n\t\tif m <= 0 {\n\t\t\treturn m, tea.Quit\n\t\t}\n\t\treturn m, tick\n\t}\n\treturn m, nil\n}\n\n// View returns a string based on data in the model. That string which will be\n// rendered to the terminal.\nfunc (m model) View() tea.View {\n\treturn tea.NewView(fmt.Sprintf(\"Hi. This program will exit in %d seconds.\\n\\nTo quit sooner press ctrl-c, or press ctrl-z to suspend...\\n\", m))\n}\n\n// Messages are events that we respond to in our Update function. This\n// particular one indicates that the timer has ticked.\ntype tickMsg time.Time\n\nfunc tick() tea.Msg {\n\ttime.Sleep(time.Second)\n\treturn tickMsg{}\n}\n"
  },
  {
    "path": "examples/simple/main_test.go",
    "content": "package main\n\n/*\nfunc TestApp(t *testing.T) {\n\t// TODO: Enable this test again\n\t// Since we added colorprofile.Writer to standard_renderer.go, this test\n\t// keeps failing. This is because the output is colored and has escape\n\t// sequences but the test runs against a buffer output and not a terminal,\n\t// tty, or pty. One way to fix this is to pass a color profile to the test\n\t// program using [tea.WithColorProfile(Ascii)].\n\tt.Skip(\"this test is currently disabled\")\n\n\tm := model(10)\n\ttm := teatest.NewTestModel(\n\t\tt, m,\n\t\tteatest.WithInitialTermSize(70, 30),\n\t)\n\tt.Cleanup(func() {\n\t\tif err := tm.Quit(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\n\ttime.Sleep(time.Second + time.Millisecond*200)\n\ttm.Type(\"I'm typing things, but it'll be ignored by my program\")\n\ttm.Send(\"ignored msg\")\n\ttm.Send(tea.KeyPressMsg{\n\t\tCode: tea.KeyEnter,\n\t})\n\n\tif err := tm.Quit(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tout := readBts(t, tm.FinalOutput(t))\n\tif !regexp.MustCompile(`This program will exit in \\d+ seconds`).Match(out) {\n\t\tt.Fatalf(\"output does not match the given regular expression: %s\", string(out))\n\t}\n\tteatest.RequireEqualOutput(t, out)\n\n\tif tm.FinalModel(t).(model) != 9 {\n\t\tt.Errorf(\"expected model to be 10, was %d\", m)\n\t}\n}\n\nfunc TestAppInteractive(t *testing.T) {\n\tt.Skip(\"This test is flaky and needs to be fixed.\\n\" +\n\t\t\"We need a more concrete way to set the initial terminal size\")\n\n\tm := model(10)\n\ttm := teatest.NewTestModel(\n\t\tt, m,\n\t\tteatest.WithInitialTermSize(70, 30),\n\t)\n\n\ttime.Sleep(time.Second + time.Millisecond*200)\n\ttm.Send(\"ignored msg\")\n\n\tif bts := readBts(t, tm.Output()); !bytes.Contains(bts, []byte(\"This program will exit in 9 seconds\")) {\n\t\tt.Fatalf(\"output does not match: expected %q\", string(bts))\n\t}\n\n\tteatest.WaitFor(t, tm.Output(), func(out []byte) bool {\n\t\treturn bytes.Contains(out, []byte(\"This program will exit in 7 seconds\"))\n\t}, teatest.WithDuration(5*time.Second))\n\n\ttm.Send(tea.KeyPressMsg{\n\t\tCode: tea.KeyEnter,\n\t})\n\n\tif err := tm.Quit(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif tm.FinalModel(t).(model) != 7 {\n\t\tt.Errorf(\"expected model to be 7, was %d\", m)\n\t}\n}\n\nfunc readBts(tb testing.TB, r io.Reader) []byte {\n\ttb.Helper()\n\tbts, err := io.ReadAll(r)\n\tif err != nil {\n\t\ttb.Fatal(err)\n\t}\n\treturn bts\n}\n*/\n"
  },
  {
    "path": "examples/simple/testdata/TestApp.golden",
    "content": "\u001b[?2004h\u001b[?25l\u001b[=1;1u\u001b[?u\rHi. This program will exit in 10 seconds.\n\nTo quit sooner press ctrl-c, or press ctrl-z to suspend...\n\u001b[3A\u001b[30C9 \u001b[P\u001b[?25l\n\n\n\r\u001b[J\u001b[?25h\u001b[0 q\u001b[?25h\u001b[?2004l"
  },
  {
    "path": "examples/space/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"math/rand\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\n// An example to show the FPS count of a moving space-like background.\n//\n// This was ported from the talented Orhun Parmaksız (@orhun)'s space example\n// from his blog post \"Why stdout is faster than stderr?\".\n\ntype model struct {\n\tcolors     [][]color.Color\n\tlastWidth  int\n\tlastHeight int\n\tframeCount int\n\twidth      int\n\theight     int\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tea.Batch(\n\t\ttickCmd(),\n\t)\n}\n\nfunc tickCmd() tea.Cmd {\n\treturn tea.Tick(time.Second/60, func(time.Time) tea.Msg {\n\t\treturn tickMsg{}\n\t})\n}\n\ntype tickMsg struct{}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\":\n\t\t\treturn m, tea.Quit\n\t\t}\n\n\tcase tea.WindowSizeMsg:\n\t\tm.width = msg.Width\n\t\tm.height = msg.Height\n\t\tif m.width != m.lastWidth || m.height != m.lastHeight {\n\t\t\tm.setupColors()\n\t\t\tm.lastWidth = m.width\n\t\t\tm.lastHeight = m.height\n\t\t}\n\n\tcase tickMsg:\n\t\tm.frameCount++\n\t\treturn m, tickCmd()\n\t}\n\n\treturn m, nil\n}\n\nfunc (m *model) setupColors() {\n\theight := m.height * 2 // double height for half blocks\n\tm.colors = make([][]color.Color, height)\n\n\tfor y := range height {\n\t\tm.colors[y] = make([]color.Color, m.width)\n\t\trandomnessFactor := float64(height-y) / float64(height)\n\n\t\tfor x := range m.width {\n\t\t\tbaseValue := randomnessFactor * (float64(height-y) / float64(height))\n\t\t\trandomOffset := (rand.Float64() * 0.2) - 0.1\n\t\t\tvalue := clamp(baseValue+randomOffset, 0, 1)\n\n\t\t\t// Convert value to grayscale color (0-255)\n\t\t\tgray := uint8(value * 255)\n\t\t\tm.colors[y][x] = lipgloss.Color(fmt.Sprintf(\"#%02x%02x%02x\", gray, gray, gray))\n\t\t}\n\t}\n}\n\nfunc clamp(value, min, max float64) float64 {\n\tif value < min {\n\t\treturn min\n\t}\n\tif value > max {\n\t\treturn max\n\t}\n\treturn value\n}\n\nfunc (m model) View() tea.View {\n\t// Title\n\ttitle := lipgloss.NewStyle().Bold(true).Render(\"Space\")\n\n\t// Color display\n\tvar s strings.Builder\n\theight := m.height - 1 // leave one line for title\n\tfor y := range height {\n\t\tfor x := range m.width {\n\t\t\txi := (x + m.frameCount) % m.width\n\t\t\tfg := m.colors[y*2][xi]\n\t\t\tbg := m.colors[y*2+1][xi]\n\t\t\tst := lipgloss.NewStyle().Foreground(fg).Background(bg)\n\t\t\ts.WriteString(st.Render(\"▀\"))\n\t\t}\n\t\tif y < height-1 {\n\t\t\ts.WriteString(\"\\n\")\n\t\t}\n\t}\n\n\tv := tea.NewView(strings.Join([]string{\n\t\ttitle,\n\t\ts.String(),\n\t}, \"\\n\"))\n\tv.AltScreen = true\n\treturn v\n}\n\nfunc main() {\n\tp := tea.NewProgram(model{}, tea.WithFPS(120))\n\n\t_, err := p.Run()\n\tif err != nil {\n\t\tfmt.Printf(\"Error running program: %v\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/spinner/README.md",
    "content": "# Spinner\n\n<img width=\"800\" src=\"./spinner.gif\" />\n"
  },
  {
    "path": "examples/spinner/main.go",
    "content": "package main\n\n// A simple program demonstrating the spinner component from the Bubbles\n// component library.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"charm.land/bubbles/v2/spinner\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\ntype errMsg error\n\ntype model struct {\n\tspinner  spinner.Model\n\tquitting bool\n\terr      error\n}\n\nfunc initialModel() model {\n\ts := spinner.New()\n\ts.Spinner = spinner.Dot\n\ts.Style = lipgloss.NewStyle().Foreground(lipgloss.Color(\"205\"))\n\treturn model{spinner: s}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn m.spinner.Tick\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"esc\", \"ctrl+c\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tdefault:\n\t\t\treturn m, nil\n\t\t}\n\n\tcase errMsg:\n\t\tm.err = msg\n\t\treturn m, nil\n\n\tdefault:\n\t\tvar cmd tea.Cmd\n\t\tm.spinner, cmd = m.spinner.Update(msg)\n\t\treturn m, cmd\n\t}\n}\n\nfunc (m model) View() tea.View {\n\tif m.err != nil {\n\t\treturn tea.NewView(m.err.Error())\n\t}\n\tstr := fmt.Sprintf(\"\\n\\n   %s Loading forever...press q to quit\\n\\n\", m.spinner.View())\n\tif m.quitting {\n\t\treturn tea.NewView(str + \"\\n\")\n\t}\n\treturn tea.NewView(str)\n}\n\nfunc main() {\n\tp := tea.NewProgram(initialModel())\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/spinners/README.md",
    "content": "# Spinners\n\n<img width=\"800\" src=\"./spinners.gif\" />\n"
  },
  {
    "path": "examples/spinners/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"charm.land/bubbles/v2/spinner\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar (\n\t// Available spinners\n\tspinners = []spinner.Spinner{\n\t\tspinner.Line,\n\t\tspinner.Dot,\n\t\tspinner.MiniDot,\n\t\tspinner.Jump,\n\t\tspinner.Pulse,\n\t\tspinner.Points,\n\t\tspinner.Globe,\n\t\tspinner.Moon,\n\t\tspinner.Monkey,\n\t}\n\n\ttextStyle    = lipgloss.NewStyle().Foreground(lipgloss.Color(\"252\")).Render\n\tspinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"69\"))\n\thelpStyle    = lipgloss.NewStyle().Foreground(lipgloss.Color(\"241\")).Render\n)\n\nfunc main() {\n\tm := model{}\n\tm.resetSpinner()\n\n\tif _, err := tea.NewProgram(m).Run(); err != nil {\n\t\tfmt.Println(\"could not run program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n\ntype model struct {\n\tindex   int\n\tspinner spinner.Model\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn m.spinner.Tick\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"q\", \"esc\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"h\", \"left\":\n\t\t\tm.index--\n\t\t\tif m.index < 0 {\n\t\t\t\tm.index = len(spinners) - 1\n\t\t\t}\n\t\t\tm.resetSpinner()\n\t\t\treturn m, m.spinner.Tick\n\t\tcase \"l\", \"right\":\n\t\t\tm.index++\n\t\t\tif m.index >= len(spinners) {\n\t\t\t\tm.index = 0\n\t\t\t}\n\t\t\tm.resetSpinner()\n\t\t\treturn m, m.spinner.Tick\n\t\tdefault:\n\t\t\treturn m, nil\n\t\t}\n\tcase spinner.TickMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.spinner, cmd = m.spinner.Update(msg)\n\t\treturn m, cmd\n\tdefault:\n\t\treturn m, nil\n\t}\n}\n\nfunc (m *model) resetSpinner() {\n\tm.spinner = spinner.New()\n\tm.spinner.Style = spinnerStyle\n\tm.spinner.Spinner = spinners[m.index]\n}\n\nfunc (m model) View() tea.View {\n\tvar gap string\n\tswitch m.index {\n\tcase 1:\n\t\tgap = \"\"\n\tdefault:\n\t\tgap = \" \"\n\t}\n\n\tvar s string\n\ts += fmt.Sprintf(\"\\n %s%s%s\\n\\n\", m.spinner.View(), gap, textStyle(\"Spinning...\"))\n\ts += helpStyle(\"h/l, ←/→: change spinner • q: exit\\n\")\n\treturn tea.NewView(s)\n}\n"
  },
  {
    "path": "examples/splash/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"math\"\n\t\"strings\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\n// This example was ported from the awesome Textualize project by @willmcgugan.\n// Check it out here:\n// https://github.com/Textualize/textual/blob/main/examples/splash.py\n\n// Color gradient\nvar colors = []color.Color{\n\tlipgloss.Color(\"#881177\"),\n\tlipgloss.Color(\"#aa3355\"),\n\tlipgloss.Color(\"#cc6666\"),\n\tlipgloss.Color(\"#ee9944\"),\n\tlipgloss.Color(\"#eedd00\"),\n\tlipgloss.Color(\"#99dd55\"),\n\tlipgloss.Color(\"#44dd88\"),\n\tlipgloss.Color(\"#22ccbb\"),\n\tlipgloss.Color(\"#00bbcc\"),\n\tlipgloss.Color(\"#0099cc\"),\n\tlipgloss.Color(\"#3366bb\"),\n\tlipgloss.Color(\"#663399\"),\n}\n\ntype model struct {\n\twidth  int\n\theight int\n\trate   int64\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tick\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\treturn m, tea.Quit\n\tcase tea.WindowSizeMsg:\n\t\tm.width = msg.Width\n\t\tm.height = msg.Height\n\tcase tickMsg:\n\t\treturn m, tick\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tvar v tea.View\n\tv.AltScreen = true\n\tif m.width == 0 {\n\t\tv.SetContent(\"Initializing...\")\n\t\treturn v\n\t}\n\n\tv.SetContent(m.gradient())\n\treturn v\n}\n\nfunc (m model) gradient() string {\n\t// Time-based angle for animation\n\tt := float64(time.Now().UnixNano()*m.rate) / float64(time.Second)\n\tangleRadians := -t * math.Pi / 180.0\n\tsinAngle := math.Sin(angleRadians)\n\tcosAngle := math.Cos(angleRadians)\n\n\tcenterX := float64(m.width) / 2\n\tcenterY := float64(m.height)\n\n\tvar output strings.Builder\n\n\tfor lineY := range m.height {\n\t\tpointY := float64(lineY)*2 - centerY\n\t\tpointX := 0.0 - centerX\n\n\t\tx1 := (centerX + (pointX*cosAngle - pointY*sinAngle)) / float64(m.width)\n\t\tx2 := (centerX + (pointX*cosAngle - (pointY+1.0)*sinAngle)) / float64(m.width)\n\t\tpointX = float64(m.width) - centerX\n\t\tendX1 := (centerX + (pointX*cosAngle - pointY*sinAngle)) / float64(m.width)\n\t\tdeltaX := (endX1 - x1) / float64(m.width)\n\n\t\tif math.Abs(deltaX) < 0.0001 {\n\t\t\t// Special case for verticals\n\t\t\tcolor1 := getGradientColor(x1)\n\t\t\tcolor2 := getGradientColor(x2)\n\t\t\tstyle := lipgloss.NewStyle().\n\t\t\t\tForeground(color1).\n\t\t\t\tBackground(color2)\n\t\t\toutput.WriteString(style.Render(strings.Repeat(\"▀\", m.width)))\n\t\t} else {\n\t\t\t// Render each column in the row\n\t\t\tfor x := range m.width {\n\t\t\t\tpos1 := x1 + float64(x)*deltaX\n\t\t\t\tpos2 := x2 + float64(x)*deltaX\n\t\t\t\tcolor1 := getGradientColor(pos1)\n\t\t\t\tcolor2 := getGradientColor(pos2)\n\t\t\t\tstyle := lipgloss.NewStyle().\n\t\t\t\t\tForeground(color1).\n\t\t\t\t\tBackground(color2)\n\t\t\t\toutput.WriteString(style.Render(\"▀\"))\n\t\t\t}\n\t\t}\n\t\tif lineY < m.height-1 {\n\t\t\toutput.WriteString(\"\\n\")\n\t\t}\n\t}\n\n\treturn output.String()\n}\n\nfunc getGradientColor(position float64) color.Color {\n\t// Normalize position to [0,1]\n\tif position <= 0 {\n\t\tposition = 0\n\t}\n\tif position >= 1 {\n\t\tposition = 1\n\t}\n\n\t// Calculate the color index\n\tidx := position * float64(len(colors)-1)\n\ti1 := int(math.Floor(idx))\n\ti2 := int(math.Ceil(idx))\n\n\t// Ensure indices are within bounds\n\ti1 = i1 % len(colors)\n\ti2 = i2 % len(colors)\n\tif i1 < 0 {\n\t\ti1 += len(colors)\n\t}\n\tif i2 < 0 {\n\t\ti2 += len(colors)\n\t}\n\n\t// Interpolate between colors\n\tt := idx - float64(i1)\n\treturn interpolateColors(colors[i1], colors[i2], t)\n}\n\nfunc interpolateColors(color1, color2 color.Color, t float64) color.Color {\n\t// Parse hex colors\n\tr1, g1, b1, _ := color1.RGBA()\n\tr1, g1, b1 = r1>>8, g1>>8, b1>>8\n\tr2, g2, b2, _ := color2.RGBA()\n\tr2, g2, b2 = r2>>8, g2>>8, b2>>8\n\n\t// Interpolate\n\tr := int(float64(r1)*(1-t) + float64(r2)*t)\n\tg := int(float64(g1)*(1-t) + float64(g2)*t)\n\tb := int(float64(b1)*(1-t) + float64(b2)*t)\n\n\treturn color.RGBA{uint8(r), uint8(g), uint8(b), 255}\n}\n\ntype tickMsg time.Time\n\nfunc tick() tea.Msg {\n\treturn tickMsg(time.Now())\n}\n\nfunc main() {\n\tp := tea.NewProgram(\n\t\tmodel{rate: 90},\n\t)\n\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Printf(\"Error running program: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "examples/split-editors/README.md",
    "content": "# Split Editors\n\n<img width=\"800\" src=\"./split-editors.gif\" />\n"
  },
  {
    "path": "examples/split-editors/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"charm.land/bubbles/v2/help\"\n\t\"charm.land/bubbles/v2/key\"\n\t\"charm.land/bubbles/v2/textarea\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nconst (\n\tinitialInputs = 2\n\tmaxInputs     = 6\n\tminInputs     = 1\n\thelpHeight    = 5\n)\n\nvar (\n\tcursorColor = lipgloss.Color(\"212\")\n\n\tcursorLineStyle = lipgloss.NewStyle().\n\t\t\tBackground(lipgloss.Color(\"57\")).\n\t\t\tForeground(lipgloss.Color(\"230\"))\n\n\tplaceholderStyle = lipgloss.NewStyle().\n\t\t\t\tForeground(lipgloss.Color(\"238\"))\n\n\tfocusedPlaceholderStyle = lipgloss.NewStyle().\n\t\t\t\tForeground(lipgloss.Color(\"99\"))\n\n\tfocusedBorderStyle = lipgloss.NewStyle().\n\t\t\t\tBorder(lipgloss.RoundedBorder()).\n\t\t\t\tBorderForeground(lipgloss.Color(\"238\"))\n\n\tblurredBorderStyle = lipgloss.NewStyle().\n\t\t\t\tBorder(lipgloss.HiddenBorder())\n\n\tendOfBufferStyle = lipgloss.NewStyle().\n\t\t\t\tForeground(lipgloss.Color(\"235\"))\n)\n\ntype keymap = struct {\n\tnext, prev, add, remove, quit key.Binding\n}\n\nfunc newTextarea() textarea.Model {\n\tt := textarea.New()\n\tt.Prompt = \"\"\n\tt.Placeholder = \"Type something\"\n\tt.ShowLineNumbers = true\n\tt.SetVirtualCursor(true)\n\n\ts := t.Styles()\n\ts.Cursor.Color = cursorColor\n\ts.Focused.Placeholder = focusedPlaceholderStyle\n\ts.Blurred.Placeholder = placeholderStyle\n\ts.Focused.CursorLine = cursorLineStyle\n\ts.Focused.CursorLineNumber = cursorLineStyle\n\ts.Focused.Base = focusedBorderStyle\n\ts.Blurred.Base = blurredBorderStyle\n\ts.Focused.EndOfBuffer = endOfBufferStyle\n\ts.Blurred.EndOfBuffer = endOfBufferStyle\n\tt.SetStyles(s)\n\n\tt.KeyMap.DeleteWordBackward.SetEnabled(false)\n\tt.KeyMap.LineNext = key.NewBinding(key.WithKeys(\"down\"))\n\tt.KeyMap.LinePrevious = key.NewBinding(key.WithKeys(\"up\"))\n\tt.Blur()\n\treturn t\n}\n\ntype model struct {\n\twidth  int\n\theight int\n\tkeymap keymap\n\thelp   help.Model\n\tinputs []textarea.Model\n\tfocus  int\n}\n\nfunc newModel() model {\n\tm := model{\n\t\tinputs: make([]textarea.Model, initialInputs),\n\t\thelp:   help.New(),\n\t\tkeymap: keymap{\n\t\t\tnext: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"tab\"),\n\t\t\t\tkey.WithHelp(\"tab\", \"next\"),\n\t\t\t),\n\t\t\tprev: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"shift+tab\"),\n\t\t\t\tkey.WithHelp(\"shift+tab\", \"prev\"),\n\t\t\t),\n\t\t\tadd: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"ctrl+n\"),\n\t\t\t\tkey.WithHelp(\"ctrl+n\", \"add an editor\"),\n\t\t\t),\n\t\t\tremove: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"ctrl+w\"),\n\t\t\t\tkey.WithHelp(\"ctrl+w\", \"remove an editor\"),\n\t\t\t),\n\t\t\tquit: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"esc\", \"ctrl+c\"),\n\t\t\t\tkey.WithHelp(\"esc\", \"quit\"),\n\t\t\t),\n\t\t},\n\t}\n\tfor i := range initialInputs {\n\t\tm.inputs[i] = newTextarea()\n\t}\n\tm.inputs[m.focus].Focus()\n\tm.updateKeybindings()\n\treturn m\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn textarea.Blink\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmds []tea.Cmd\n\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch {\n\t\tcase key.Matches(msg, m.keymap.quit):\n\t\t\tfor i := range m.inputs {\n\t\t\t\tm.inputs[i].Blur()\n\t\t\t}\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, m.keymap.next):\n\t\t\tm.inputs[m.focus].Blur()\n\t\t\tm.focus++\n\t\t\tif m.focus > len(m.inputs)-1 {\n\t\t\t\tm.focus = 0\n\t\t\t}\n\t\t\tcmd := m.inputs[m.focus].Focus()\n\t\t\tcmds = append(cmds, cmd)\n\t\tcase key.Matches(msg, m.keymap.prev):\n\t\t\tm.inputs[m.focus].Blur()\n\t\t\tm.focus--\n\t\t\tif m.focus < 0 {\n\t\t\t\tm.focus = len(m.inputs) - 1\n\t\t\t}\n\t\t\tcmd := m.inputs[m.focus].Focus()\n\t\t\tcmds = append(cmds, cmd)\n\t\tcase key.Matches(msg, m.keymap.add):\n\t\t\tm.inputs = append(m.inputs, newTextarea())\n\t\tcase key.Matches(msg, m.keymap.remove):\n\t\t\tm.inputs = m.inputs[:len(m.inputs)-1]\n\t\t\tif m.focus > len(m.inputs)-1 {\n\t\t\t\tm.focus = len(m.inputs) - 1\n\t\t\t}\n\t\t}\n\tcase tea.WindowSizeMsg:\n\t\tm.height = msg.Height\n\t\tm.width = msg.Width\n\t}\n\n\tm.updateKeybindings()\n\tm.sizeInputs()\n\n\t// Update all textareas\n\tfor i := range m.inputs {\n\t\tnewModel, cmd := m.inputs[i].Update(msg)\n\t\tm.inputs[i] = newModel\n\t\tcmds = append(cmds, cmd)\n\t}\n\n\treturn m, tea.Batch(cmds...)\n}\n\nfunc (m *model) sizeInputs() {\n\tfor i := range m.inputs {\n\t\tm.inputs[i].SetWidth(m.width / len(m.inputs))\n\t\tm.inputs[i].SetHeight(m.height - helpHeight)\n\t}\n}\n\nfunc (m *model) updateKeybindings() {\n\tm.keymap.add.SetEnabled(len(m.inputs) < maxInputs)\n\tm.keymap.remove.SetEnabled(len(m.inputs) > minInputs)\n}\n\nfunc (m model) inputViews() []string {\n\tvar views []string\n\tfor i := range m.inputs {\n\t\tviews = append(views, m.inputs[i].View())\n\t}\n\treturn views\n}\n\nfunc (m model) View() tea.View {\n\thelp := m.help.ShortHelpView([]key.Binding{\n\t\tm.keymap.next,\n\t\tm.keymap.prev,\n\t\tm.keymap.add,\n\t\tm.keymap.remove,\n\t\tm.keymap.quit,\n\t})\n\n\tv := tea.NewView(lipgloss.JoinHorizontal(lipgloss.Top, m.inputViews()...) + \"\\n\\n\" + help)\n\tv.AltScreen = true\n\treturn v\n}\n\nfunc (m model) Cursor() *tea.Cursor {\n\tfocusedInput := m.inputs[m.focus]\n\tif focusedInput.VirtualCursor() {\n\t\treturn nil\n\t}\n\n\tviews := m.inputViews()\n\tc := focusedInput.Cursor()\n\n\t// Find textrea offset to position real cursor.\n\t//\n\t// To do this we calculate the width of all textareas to the left of\n\t// the focused one.\n\tfor i := range m.focus {\n\t\tc.X += lipgloss.Width(views[i])\n\t}\n\n\treturn c\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(newModel()).Run(); err != nil {\n\t\tfmt.Println(\"Error while running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/stopwatch/README.md",
    "content": "# Stopwatch\n\n<img width=\"800\" src=\"./stopwatch.gif\" />\n"
  },
  {
    "path": "examples/stopwatch/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/help\"\n\t\"charm.land/bubbles/v2/key\"\n\t\"charm.land/bubbles/v2/stopwatch\"\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype model struct {\n\tstopwatch stopwatch.Model\n\tkeymap    keymap\n\thelp      help.Model\n\tquitting  bool\n}\n\ntype keymap struct {\n\tstart key.Binding\n\tstop  key.Binding\n\treset key.Binding\n\tquit  key.Binding\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn m.stopwatch.Init()\n}\n\nfunc (m model) View() tea.View {\n\t// Note: you could further customize the time output by getting the\n\t// duration from m.stopwatch.Elapsed(), which returns a time.Duration, and\n\t// skip m.stopwatch.View() altogether.\n\ts := m.stopwatch.View() + \"\\n\"\n\tif !m.quitting {\n\t\ts = \"Elapsed: \" + s\n\t\ts += m.helpView()\n\t}\n\treturn tea.NewView(s)\n}\n\nfunc (m model) helpView() string {\n\treturn \"\\n\" + m.help.ShortHelpView([]key.Binding{\n\t\tm.keymap.start,\n\t\tm.keymap.stop,\n\t\tm.keymap.reset,\n\t\tm.keymap.quit,\n\t})\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch {\n\t\tcase key.Matches(msg, m.keymap.quit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, m.keymap.reset):\n\t\t\treturn m, m.stopwatch.Reset()\n\t\tcase key.Matches(msg, m.keymap.start, m.keymap.stop):\n\t\t\tm.keymap.stop.SetEnabled(!m.stopwatch.Running())\n\t\t\tm.keymap.start.SetEnabled(m.stopwatch.Running())\n\t\t\treturn m, m.stopwatch.Toggle()\n\t\t}\n\t}\n\tvar cmd tea.Cmd\n\tm.stopwatch, cmd = m.stopwatch.Update(msg)\n\treturn m, cmd\n}\n\nfunc main() {\n\tm := model{\n\t\tstopwatch: stopwatch.New(stopwatch.WithInterval(time.Millisecond)),\n\t\tkeymap: keymap{\n\t\t\tstart: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"s\"),\n\t\t\t\tkey.WithHelp(\"s\", \"start\"),\n\t\t\t),\n\t\t\tstop: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"s\"),\n\t\t\t\tkey.WithHelp(\"s\", \"stop\"),\n\t\t\t),\n\t\t\treset: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"r\"),\n\t\t\t\tkey.WithHelp(\"r\", \"reset\"),\n\t\t\t),\n\t\t\tquit: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"ctrl+c\", \"q\"),\n\t\t\t\tkey.WithHelp(\"q\", \"quit\"),\n\t\t\t),\n\t\t},\n\t\thelp: help.New(),\n\t}\n\n\tm.keymap.start.SetEnabled(false)\n\n\tif _, err := tea.NewProgram(m).Run(); err != nil {\n\t\tfmt.Println(\"Oh no, it didn't work:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/suspend/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype model struct {\n\tquitting   bool\n\tsuspending bool\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.ResumeMsg:\n\t\tm.suspending = false\n\t\treturn m, nil\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"esc\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"ctrl+c\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Interrupt\n\t\tcase \"ctrl+z\":\n\t\t\tm.suspending = true\n\t\t\treturn m, tea.Suspend\n\t\t}\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tif m.suspending || m.quitting {\n\t\treturn tea.NewView(\"\")\n\t}\n\n\treturn tea.NewView(\"\\nPress ctrl-z to suspend, ctrl+c to interrupt, q, or esc to exit\\n\")\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(model{}).Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tif errors.Is(err, tea.ErrInterrupted) {\n\t\t\tos.Exit(130)\n\t\t}\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/table/README.md",
    "content": "# Table\n\n<img width=\"800\" src=\"./table.gif\" />\n"
  },
  {
    "path": "examples/table/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"charm.land/bubbles/v2/table\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar baseStyle = lipgloss.NewStyle().\n\tBorderStyle(lipgloss.NormalBorder()).\n\tBorderForeground(lipgloss.Color(\"240\"))\n\ntype model struct {\n\ttable table.Model\n}\n\nfunc (m model) Init() tea.Cmd { return nil }\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd tea.Cmd\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"esc\":\n\t\t\tif m.table.Focused() {\n\t\t\t\tm.table.Blur()\n\t\t\t} else {\n\t\t\t\tm.table.Focus()\n\t\t\t}\n\t\tcase \"q\", \"ctrl+c\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"enter\":\n\t\t\treturn m, tea.Batch(\n\t\t\t\ttea.Printf(\"Let's go to %s!\", m.table.SelectedRow()[1]),\n\t\t\t)\n\t\t}\n\t}\n\tm.table, cmd = m.table.Update(msg)\n\treturn m, cmd\n}\n\nfunc (m model) View() tea.View {\n\treturn tea.NewView(baseStyle.Render(m.table.View()) + \"\\n  \" + m.table.HelpView() + \"\\n\")\n}\n\nfunc main() {\n\tcolumns := []table.Column{\n\t\t{Title: \"Rank\", Width: 4},\n\t\t{Title: \"City\", Width: 10},\n\t\t{Title: \"Country\", Width: 10},\n\t\t{Title: \"Population\", Width: 10},\n\t}\n\n\trows := []table.Row{\n\t\t{\"1\", \"Tokyo\", \"Japan\", \"37,274,000\"},\n\t\t{\"2\", \"Delhi\", \"India\", \"32,065,760\"},\n\t\t{\"3\", \"Shanghai\", \"China\", \"28,516,904\"},\n\t\t{\"4\", \"Dhaka\", \"Bangladesh\", \"22,478,116\"},\n\t\t{\"5\", \"São Paulo\", \"Brazil\", \"22,429,800\"},\n\t\t{\"6\", \"Mexico City\", \"Mexico\", \"22,085,140\"},\n\t\t{\"7\", \"Cairo\", \"Egypt\", \"21,750,020\"},\n\t\t{\"8\", \"Beijing\", \"China\", \"21,333,332\"},\n\t\t{\"9\", \"Mumbai\", \"India\", \"20,961,472\"},\n\t\t{\"10\", \"Osaka\", \"Japan\", \"19,059,856\"},\n\t\t{\"11\", \"Chongqing\", \"China\", \"16,874,740\"},\n\t\t{\"12\", \"Karachi\", \"Pakistan\", \"16,839,950\"},\n\t\t{\"13\", \"Istanbul\", \"Turkey\", \"15,636,243\"},\n\t\t{\"14\", \"Kinshasa\", \"DR Congo\", \"15,628,085\"},\n\t\t{\"15\", \"Lagos\", \"Nigeria\", \"15,387,639\"},\n\t\t{\"16\", \"Buenos Aires\", \"Argentina\", \"15,369,919\"},\n\t\t{\"17\", \"Kolkata\", \"India\", \"15,133,888\"},\n\t\t{\"18\", \"Manila\", \"Philippines\", \"14,406,059\"},\n\t\t{\"19\", \"Tianjin\", \"China\", \"14,011,828\"},\n\t\t{\"20\", \"Guangzhou\", \"China\", \"13,964,637\"},\n\t\t{\"21\", \"Rio De Janeiro\", \"Brazil\", \"13,634,274\"},\n\t\t{\"22\", \"Lahore\", \"Pakistan\", \"13,541,764\"},\n\t\t{\"23\", \"Bangalore\", \"India\", \"13,193,035\"},\n\t\t{\"24\", \"Shenzhen\", \"China\", \"12,831,330\"},\n\t\t{\"25\", \"Moscow\", \"Russia\", \"12,640,818\"},\n\t\t{\"26\", \"Chennai\", \"India\", \"11,503,293\"},\n\t\t{\"27\", \"Bogota\", \"Colombia\", \"11,344,312\"},\n\t\t{\"28\", \"Paris\", \"France\", \"11,142,303\"},\n\t\t{\"29\", \"Jakarta\", \"Indonesia\", \"11,074,811\"},\n\t\t{\"30\", \"Lima\", \"Peru\", \"11,044,607\"},\n\t\t{\"31\", \"Bangkok\", \"Thailand\", \"10,899,698\"},\n\t\t{\"32\", \"Hyderabad\", \"India\", \"10,534,418\"},\n\t\t{\"33\", \"Seoul\", \"South Korea\", \"9,975,709\"},\n\t\t{\"34\", \"Nagoya\", \"Japan\", \"9,571,596\"},\n\t\t{\"35\", \"London\", \"United Kingdom\", \"9,540,576\"},\n\t\t{\"36\", \"Chengdu\", \"China\", \"9,478,521\"},\n\t\t{\"37\", \"Nanjing\", \"China\", \"9,429,381\"},\n\t\t{\"38\", \"Tehran\", \"Iran\", \"9,381,546\"},\n\t\t{\"39\", \"Ho Chi Minh City\", \"Vietnam\", \"9,077,158\"},\n\t\t{\"40\", \"Luanda\", \"Angola\", \"8,952,496\"},\n\t\t{\"41\", \"Wuhan\", \"China\", \"8,591,611\"},\n\t\t{\"42\", \"Xi An Shaanxi\", \"China\", \"8,537,646\"},\n\t\t{\"43\", \"Ahmedabad\", \"India\", \"8,450,228\"},\n\t\t{\"44\", \"Kuala Lumpur\", \"Malaysia\", \"8,419,566\"},\n\t\t{\"45\", \"New York City\", \"United States\", \"8,177,020\"},\n\t\t{\"46\", \"Hangzhou\", \"China\", \"8,044,878\"},\n\t\t{\"47\", \"Surat\", \"India\", \"7,784,276\"},\n\t\t{\"48\", \"Suzhou\", \"China\", \"7,764,499\"},\n\t\t{\"49\", \"Hong Kong\", \"Hong Kong\", \"7,643,256\"},\n\t\t{\"50\", \"Riyadh\", \"Saudi Arabia\", \"7,538,200\"},\n\t\t{\"51\", \"Shenyang\", \"China\", \"7,527,975\"},\n\t\t{\"52\", \"Baghdad\", \"Iraq\", \"7,511,920\"},\n\t\t{\"53\", \"Dongguan\", \"China\", \"7,511,851\"},\n\t\t{\"54\", \"Foshan\", \"China\", \"7,497,263\"},\n\t\t{\"55\", \"Dar Es Salaam\", \"Tanzania\", \"7,404,689\"},\n\t\t{\"56\", \"Pune\", \"India\", \"6,987,077\"},\n\t\t{\"57\", \"Santiago\", \"Chile\", \"6,856,939\"},\n\t\t{\"58\", \"Madrid\", \"Spain\", \"6,713,557\"},\n\t\t{\"59\", \"Haerbin\", \"China\", \"6,665,951\"},\n\t\t{\"60\", \"Toronto\", \"Canada\", \"6,312,974\"},\n\t\t{\"61\", \"Belo Horizonte\", \"Brazil\", \"6,194,292\"},\n\t\t{\"62\", \"Khartoum\", \"Sudan\", \"6,160,327\"},\n\t\t{\"63\", \"Johannesburg\", \"South Africa\", \"6,065,354\"},\n\t\t{\"64\", \"Singapore\", \"Singapore\", \"6,039,577\"},\n\t\t{\"65\", \"Dalian\", \"China\", \"5,930,140\"},\n\t\t{\"66\", \"Qingdao\", \"China\", \"5,865,232\"},\n\t\t{\"67\", \"Zhengzhou\", \"China\", \"5,690,312\"},\n\t\t{\"68\", \"Ji Nan Shandong\", \"China\", \"5,663,015\"},\n\t\t{\"69\", \"Barcelona\", \"Spain\", \"5,658,472\"},\n\t\t{\"70\", \"Saint Petersburg\", \"Russia\", \"5,535,556\"},\n\t\t{\"71\", \"Abidjan\", \"Ivory Coast\", \"5,515,790\"},\n\t\t{\"72\", \"Yangon\", \"Myanmar\", \"5,514,454\"},\n\t\t{\"73\", \"Fukuoka\", \"Japan\", \"5,502,591\"},\n\t\t{\"74\", \"Alexandria\", \"Egypt\", \"5,483,605\"},\n\t\t{\"75\", \"Guadalajara\", \"Mexico\", \"5,339,583\"},\n\t\t{\"76\", \"Ankara\", \"Turkey\", \"5,309,690\"},\n\t\t{\"77\", \"Chittagong\", \"Bangladesh\", \"5,252,842\"},\n\t\t{\"78\", \"Addis Ababa\", \"Ethiopia\", \"5,227,794\"},\n\t\t{\"79\", \"Melbourne\", \"Australia\", \"5,150,766\"},\n\t\t{\"80\", \"Nairobi\", \"Kenya\", \"5,118,844\"},\n\t\t{\"81\", \"Hanoi\", \"Vietnam\", \"5,067,352\"},\n\t\t{\"82\", \"Sydney\", \"Australia\", \"5,056,571\"},\n\t\t{\"83\", \"Monterrey\", \"Mexico\", \"5,036,535\"},\n\t\t{\"84\", \"Changsha\", \"China\", \"4,809,887\"},\n\t\t{\"85\", \"Brasilia\", \"Brazil\", \"4,803,877\"},\n\t\t{\"86\", \"Cape Town\", \"South Africa\", \"4,800,954\"},\n\t\t{\"87\", \"Jiddah\", \"Saudi Arabia\", \"4,780,740\"},\n\t\t{\"88\", \"Urumqi\", \"China\", \"4,710,203\"},\n\t\t{\"89\", \"Kunming\", \"China\", \"4,657,381\"},\n\t\t{\"90\", \"Changchun\", \"China\", \"4,616,002\"},\n\t\t{\"91\", \"Hefei\", \"China\", \"4,496,456\"},\n\t\t{\"92\", \"Shantou\", \"China\", \"4,490,411\"},\n\t\t{\"93\", \"Xinbei\", \"Taiwan\", \"4,470,672\"},\n\t\t{\"94\", \"Kabul\", \"Afghanistan\", \"4,457,882\"},\n\t\t{\"95\", \"Ningbo\", \"China\", \"4,405,292\"},\n\t\t{\"96\", \"Tel Aviv\", \"Israel\", \"4,343,584\"},\n\t\t{\"97\", \"Yaounde\", \"Cameroon\", \"4,336,670\"},\n\t\t{\"98\", \"Rome\", \"Italy\", \"4,297,877\"},\n\t\t{\"99\", \"Shijiazhuang\", \"China\", \"4,285,135\"},\n\t\t{\"100\", \"Montreal\", \"Canada\", \"4,276,526\"},\n\t}\n\n\tt := table.New(\n\t\ttable.WithColumns(columns),\n\t\ttable.WithRows(rows),\n\t\ttable.WithFocused(true),\n\t\ttable.WithHeight(7),\n\t\ttable.WithWidth(42),\n\t)\n\n\ts := table.DefaultStyles()\n\ts.Header = s.Header.\n\t\tBorderStyle(lipgloss.NormalBorder()).\n\t\tBorderForeground(lipgloss.Color(\"240\")).\n\t\tBorderBottom(true).\n\t\tBold(false)\n\ts.Selected = s.Selected.\n\t\tForeground(lipgloss.Color(\"229\")).\n\t\tBackground(lipgloss.Color(\"57\")).\n\t\tBold(false)\n\tt.SetStyles(s)\n\n\tm := model{t}\n\tif _, err := tea.NewProgram(m).Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/table-resize/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"os\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/table\"\n)\n\n// Pokemon types.\nconst (\n\tNone     = \"\"\n\tBug      = \"Bug\"\n\tElectric = \"Electric\"\n\tFire     = \"Fire\"\n\tFlying   = \"Flying\"\n\tGrass    = \"Grass\"\n\tGround   = \"Ground\"\n\tNormal   = \"Normal\"\n\tPoison   = \"Poison\"\n\tWater    = \"Water\"\n)\n\ntype model struct {\n\ttable *table.Table\n}\n\nfunc (m model) Init() tea.Cmd { return nil }\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd tea.Cmd\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tm.table = m.table.Width(msg.Width)\n\t\tm.table = m.table.Height(msg.Height)\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"enter\":\n\t\t}\n\t}\n\treturn m, cmd\n}\n\nfunc (m model) View() tea.View {\n\tv := tea.NewView(\"\\n\" + m.table.String() + \"\\n\")\n\tv.AltScreen = true\n\treturn v\n}\n\nfunc main() {\n\tbaseStyle := lipgloss.NewStyle().Padding(0, 1)\n\theaderStyle := baseStyle.Foreground(lipgloss.Color(\"252\")).Bold(true)\n\tselectedStyle := baseStyle.Foreground(lipgloss.Color(\"#01BE85\")).Background(lipgloss.Color(\"#00432F\"))\n\ttypeColors := map[string]color.Color{\n\t\tBug:      lipgloss.Color(\"#D7FF87\"),\n\t\tElectric: lipgloss.Color(\"#FDFF90\"),\n\t\tFire:     lipgloss.Color(\"#FF7698\"),\n\t\tFlying:   lipgloss.Color(\"#FF87D7\"),\n\t\tGrass:    lipgloss.Color(\"#75FBAB\"),\n\t\tGround:   lipgloss.Color(\"#FF875F\"),\n\t\tNormal:   lipgloss.Color(\"#929292\"),\n\t\tPoison:   lipgloss.Color(\"#7D5AFC\"),\n\t\tWater:    lipgloss.Color(\"#00E2C7\"),\n\t}\n\tdimTypeColors := map[string]color.Color{\n\t\tBug:      lipgloss.Color(\"#97AD64\"),\n\t\tElectric: lipgloss.Color(\"#FCFF5F\"),\n\t\tFire:     lipgloss.Color(\"#BA5F75\"),\n\t\tFlying:   lipgloss.Color(\"#C97AB2\"),\n\t\tGrass:    lipgloss.Color(\"#59B980\"),\n\t\tGround:   lipgloss.Color(\"#C77252\"),\n\t\tNormal:   lipgloss.Color(\"#727272\"),\n\t\tPoison:   lipgloss.Color(\"#634BD0\"),\n\t\tWater:    lipgloss.Color(\"#439F8E\"),\n\t}\n\theaders := []string{\"#\", \"NAME\", \"TYPE 1\", \"TYPE 2\", \"JAPANESE\", \"OFFICIAL ROM.\"}\n\trows := [][]string{\n\t\t{\"1\", \"Bulbasaur\", Grass, Poison, \"フシギダネ\", \"Bulbasaur\"},\n\t\t{\"2\", \"Ivysaur\", Grass, Poison, \"フシギソウ\", \"Ivysaur\"},\n\t\t{\"3\", \"Venusaur\", Grass, Poison, \"フシギバナ\", \"Venusaur\"},\n\t\t{\"4\", \"Charmander\", Fire, None, \"ヒトカゲ\", \"Hitokage\"},\n\t\t{\"5\", \"Charmeleon\", Fire, None, \"リザード\", \"Lizardo\"},\n\t\t{\"6\", \"Charizard\", Fire, Flying, \"リザードン\", \"Lizardon\"},\n\t\t{\"7\", \"Squirtle\", Water, None, \"ゼニガメ\", \"Zenigame\"},\n\t\t{\"8\", \"Wartortle\", Water, None, \"カメール\", \"Kameil\"},\n\t\t{\"9\", \"Blastoise\", Water, None, \"カメックス\", \"Kamex\"},\n\t\t{\"10\", \"Caterpie\", Bug, None, \"キャタピー\", \"Caterpie\"},\n\t\t{\"11\", \"Metapod\", Bug, None, \"トランセル\", \"Trancell\"},\n\t\t{\"12\", \"Butterfree\", Bug, Flying, \"バタフリー\", \"Butterfree\"},\n\t\t{\"13\", \"Weedle\", Bug, Poison, \"ビードル\", \"Beedle\"},\n\t\t{\"14\", \"Kakuna\", Bug, Poison, \"コクーン\", \"Cocoon\"},\n\t\t{\"15\", \"Beedrill\", Bug, Poison, \"スピアー\", \"Spear\"},\n\t\t{\"16\", \"Pidgey\", Normal, Flying, \"ポッポ\", \"Poppo\"},\n\t\t{\"17\", \"Pidgeotto\", Normal, Flying, \"ピジョン\", \"Pigeon\"},\n\t\t{\"18\", \"Pidgeot\", Normal, Flying, \"ピジョット\", \"Pigeot\"},\n\t\t{\"19\", \"Rattata\", Normal, None, \"コラッタ\", \"Koratta\"},\n\t\t{\"20\", \"Raticate\", Normal, None, \"ラッタ\", \"Ratta\"},\n\t\t{\"21\", \"Spearow\", Normal, Flying, \"オニスズメ\", \"Onisuzume\"},\n\t\t{\"22\", \"Fearow\", Normal, Flying, \"オニドリル\", \"Onidrill\"},\n\t\t{\"23\", \"Ekans\", Poison, None, \"アーボ\", \"Arbo\"},\n\t\t{\"24\", \"Arbok\", Poison, None, \"アーボック\", \"Arbok\"},\n\t\t{\"25\", \"Pikachu\", Electric, None, \"ピカチュウ\", \"Pikachu\"},\n\t\t{\"26\", \"Raichu\", Electric, None, \"ライチュウ\", \"Raichu\"},\n\t\t{\"27\", \"Sandshrew\", Ground, None, \"サンド\", \"Sand\"},\n\t\t{\"28\", \"Sandslash\", Ground, None, \"サンドパン\", \"Sandpan\"},\n\t}\n\n\tt := table.New().\n\t\tHeaders(headers...).\n\t\tRows(rows...).\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tBorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color(\"238\"))).\n\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\tif row == 0 {\n\t\t\t\treturn headerStyle\n\t\t\t}\n\n\t\t\trowIndex := row - 1\n\t\t\tif rowIndex < 0 || rowIndex >= len(rows) {\n\t\t\t\treturn baseStyle\n\t\t\t}\n\n\t\t\tif rows[rowIndex][1] == \"Pikachu\" {\n\t\t\t\treturn selectedStyle\n\t\t\t}\n\n\t\t\teven := row%2 == 0\n\n\t\t\tswitch col {\n\t\t\tcase 2, 3: // Type 1 + 2\n\t\t\t\tc := typeColors\n\t\t\t\tif even {\n\t\t\t\t\tc = dimTypeColors\n\t\t\t\t}\n\n\t\t\t\tif col >= len(rows[rowIndex]) {\n\t\t\t\t\treturn baseStyle\n\t\t\t\t}\n\n\t\t\t\tcolor, ok := c[rows[rowIndex][col]]\n\t\t\t\tif !ok {\n\t\t\t\t\treturn baseStyle\n\t\t\t\t}\n\t\t\t\treturn baseStyle.Foreground(color)\n\t\t\t}\n\n\t\t\tif even {\n\t\t\t\treturn baseStyle.Foreground(lipgloss.Color(\"245\"))\n\t\t\t}\n\t\t\treturn baseStyle.Foreground(lipgloss.Color(\"252\"))\n\t\t}).\n\t\tBorder(lipgloss.ThickBorder())\n\n\tif _, err := tea.NewProgram(model{t}).Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/tabs/README.md",
    "content": "# Tabs\n\n<img width=\"800\" src=\"./tabs.gif\" />\n"
  },
  {
    "path": "examples/tabs/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\ntype styles struct {\n\tdoc         lipgloss.Style\n\thighlight   lipgloss.Style\n\tinactiveTab lipgloss.Style\n\tactiveTab   lipgloss.Style\n\twindow      lipgloss.Style\n}\n\nfunc newStyles(bgIsDark bool) *styles {\n\tlightDark := lipgloss.LightDark(bgIsDark)\n\n\tinactiveTabBorder := tabBorderWithBottom(\"┴\", \"─\", \"┴\")\n\tactiveTabBorder := tabBorderWithBottom(\"┘\", \" \", \"└\")\n\thighlightColor := lightDark(lipgloss.Color(\"#874BFD\"), lipgloss.Color(\"#7D56F4\"))\n\n\ts := new(styles)\n\ts.doc = lipgloss.NewStyle().\n\t\tPadding(1, 2, 1, 2)\n\ts.inactiveTab = lipgloss.NewStyle().\n\t\tBorder(inactiveTabBorder, true).\n\t\tBorderForeground(highlightColor).\n\t\tPadding(0, 1)\n\ts.activeTab = s.inactiveTab.\n\t\tBorder(activeTabBorder, true)\n\ts.window = lipgloss.NewStyle().\n\t\tBorderForeground(highlightColor).\n\t\tPadding(2, 0).\n\t\tAlign(lipgloss.Center).\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tUnsetBorderTop()\n\treturn s\n}\n\ntype model struct {\n\tTabs       []string\n\tTabContent []string\n\tstyles     *styles\n\tactiveTab  int\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch keypress := msg.String(); keypress {\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"right\", \"l\", \"n\", \"tab\":\n\t\t\tm.activeTab = min(m.activeTab+1, len(m.Tabs)-1)\n\t\t\treturn m, nil\n\t\tcase \"left\", \"h\", \"p\", \"shift+tab\":\n\t\t\tm.activeTab = max(m.activeTab-1, 0)\n\t\t\treturn m, nil\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc tabBorderWithBottom(left, middle, right string) lipgloss.Border {\n\tborder := lipgloss.RoundedBorder()\n\tborder.BottomLeft = left\n\tborder.Bottom = middle\n\tborder.BottomRight = right\n\treturn border\n}\n\nfunc (m model) View() tea.View {\n\tif m.styles == nil {\n\t\treturn tea.NewView(\"\")\n\t}\n\n\tdoc := strings.Builder{}\n\ts := m.styles\n\n\tvar renderedTabs []string\n\n\tfor i, t := range m.Tabs {\n\t\tvar style lipgloss.Style\n\t\tisFirst, isLast, isActive := i == 0, i == len(m.Tabs)-1, i == m.activeTab\n\t\tif isActive {\n\t\t\tstyle = s.activeTab\n\t\t} else {\n\t\t\tstyle = s.inactiveTab\n\t\t}\n\t\tborder, _, _, _, _ := style.GetBorder()\n\t\tif isFirst && isActive {\n\t\t\tborder.BottomLeft = \"│\"\n\t\t} else if isFirst && !isActive {\n\t\t\tborder.BottomLeft = \"├\"\n\t\t} else if isLast && isActive {\n\t\t\tborder.BottomRight = \"│\"\n\t\t} else if isLast && !isActive {\n\t\t\tborder.BottomRight = \"┤\"\n\t\t}\n\t\tstyle = style.Border(border)\n\t\trenderedTabs = append(renderedTabs, style.Render(t))\n\t}\n\n\trow := lipgloss.JoinHorizontal(lipgloss.Top, renderedTabs...)\n\tdoc.WriteString(row)\n\tdoc.WriteString(\"\\n\")\n\tdoc.WriteString(s.window.Width((lipgloss.Width(row))).Render(m.TabContent[m.activeTab]))\n\treturn tea.NewView(s.doc.Render(doc.String()))\n}\n\nfunc main() {\n\ttabs := []string{\"Lip Gloss\", \"Blush\", \"Eye Shadow\", \"Mascara\", \"Foundation\"}\n\ttabContent := []string{\"Lip Gloss Tab\", \"Blush Tab\", \"Eye Shadow Tab\", \"Mascara Tab\", \"Foundation Tab\"}\n\tm := model{Tabs: tabs, TabContent: tabContent, styles: newStyles(true)} // default to dark styles.\n\tif _, err := tea.NewProgram(m).Run(); err != nil {\n\t\tfmt.Println(\"Error running program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/textarea/README.md",
    "content": "# Text Area\n\n<img width=\"800\" src=\"./textarea.gif\" />\n"
  },
  {
    "path": "examples/textarea/main.go",
    "content": "package main\n\n// A simple program demonstrating the textarea component from the Bubbles\n// component library.\n\nimport (\n\t\"log\"\n\t\"strings\"\n\n\t\"charm.land/bubbles/v2/textarea\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nfunc main() {\n\tp := tea.NewProgram(initialModel())\n\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\ntype errMsg error\n\ntype model struct {\n\ttextarea textarea.Model\n\terr      error\n}\n\nfunc initialModel() model {\n\tti := textarea.New()\n\tti.Placeholder = \"Once upon a time...\"\n\tti.SetVirtualCursor(false)\n\tti.SetStyles(textarea.DefaultStyles(true)) // default to dark styles.\n\tti.Focus()\n\n\treturn model{\n\t\ttextarea: ti,\n\t\terr:      nil,\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tea.Batch(textarea.Blink, tea.RequestBackgroundColor)\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmds []tea.Cmd\n\tvar cmd tea.Cmd\n\n\tswitch msg := msg.(type) {\n\tcase tea.BackgroundColorMsg:\n\t\t// Update styling now that we know the background color.\n\t\tm.textarea.SetStyles(textarea.DefaultStyles(msg.IsDark()))\n\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"esc\":\n\t\t\tif m.textarea.Focused() {\n\t\t\t\tm.textarea.Blur()\n\t\t\t}\n\t\tcase \"ctrl+c\":\n\t\t\treturn m, tea.Quit\n\t\tdefault:\n\t\t\tif !m.textarea.Focused() {\n\t\t\t\tcmd = m.textarea.Focus()\n\t\t\t\tcmds = append(cmds, cmd)\n\t\t\t}\n\t\t}\n\n\t\t// We handle errors just like any other message\n\tcase errMsg:\n\t\tm.err = msg\n\t\treturn m, nil\n\t}\n\n\tm.textarea, cmd = m.textarea.Update(msg)\n\tcmds = append(cmds, cmd)\n\treturn m, tea.Batch(cmds...)\n}\n\nfunc (m model) headerView() string {\n\treturn \"Tell me a story.\\n\"\n}\n\nfunc (m model) View() tea.View {\n\tconst (\n\t\tfooter = \"\\n(ctrl+c to quit)\\n\"\n\t)\n\n\tvar c *tea.Cursor\n\tif !m.textarea.VirtualCursor() {\n\t\tc = m.textarea.Cursor()\n\n\t\t// Set the y offset of the cursor based on the position of the textarea\n\t\t// in the application.\n\t\toffset := lipgloss.Height(m.headerView())\n\t\tc.Y += offset\n\t}\n\n\tf := strings.Join([]string{\n\t\tm.headerView(),\n\t\tm.textarea.View(),\n\t\tfooter,\n\t}, \"\\n\")\n\n\tv := tea.NewView(f)\n\tv.Cursor = c\n\treturn v\n}\n"
  },
  {
    "path": "examples/textinput/README.md",
    "content": "# Text Input\n\n<img width=\"800\" src=\"./textinput.gif\" />\n"
  },
  {
    "path": "examples/textinput/main.go",
    "content": "package main\n\n// A simple program demonstrating the text input component from the Bubbles\n// component library.\n\nimport (\n\t\"log\"\n\n\t\"charm.land/bubbles/v2/textinput\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nfunc main() {\n\tp := tea.NewProgram(initialModel())\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\ntype (\n\terrMsg error\n)\n\ntype model struct {\n\ttextInput textinput.Model\n\terr       error\n\tquitting  bool\n}\n\nfunc initialModel() model {\n\tti := textinput.New()\n\tti.Placeholder = \"Pikachu\"\n\tti.SetVirtualCursor(false)\n\tti.Focus()\n\tti.CharLimit = 156\n\tti.SetWidth(20)\n\n\treturn model{textInput: ti}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn textinput.Blink\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd tea.Cmd\n\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"enter\", \"ctrl+c\", \"esc\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\tm.textInput, cmd = m.textInput.Update(msg)\n\treturn m, cmd\n}\n\nfunc (m model) View() tea.View {\n\tvar c *tea.Cursor\n\tif !m.textInput.VirtualCursor() {\n\t\tc = m.textInput.Cursor()\n\t\tc.Y += lipgloss.Height(m.headerView())\n\t}\n\n\tstr := lipgloss.JoinVertical(lipgloss.Top, m.headerView(), m.textInput.View(), m.footerView())\n\tif m.quitting {\n\t\tstr += \"\\n\"\n\t}\n\n\tv := tea.NewView(str)\n\tv.Cursor = c\n\treturn v\n}\n\nfunc (m model) headerView() string { return \"What’s your favorite Pokémon?\\n\" }\nfunc (m model) footerView() string { return \"\\n(esc to quit)\" }\n"
  },
  {
    "path": "examples/textinputs/README.md",
    "content": "# Text Inputs\n\n<img width=\"800\" src=\"./textinputs.gif\" />\n"
  },
  {
    "path": "examples/textinputs/main.go",
    "content": "package main\n\n// A simple example demonstrating the use of multiple text input components\n// from the Bubbles component library.\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"charm.land/bubbles/v2/cursor\"\n\t\"charm.land/bubbles/v2/textinput\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar (\n\tfocusedStyle        = lipgloss.NewStyle().Foreground(lipgloss.Color(\"205\"))\n\tblurredStyle        = lipgloss.NewStyle().Foreground(lipgloss.Color(\"240\"))\n\tcursorStyle         = focusedStyle\n\tnoStyle             = lipgloss.NewStyle()\n\thelpStyle           = blurredStyle\n\tcursorModeHelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"244\"))\n\n\tfocusedButton = focusedStyle.Render(\"[ Submit ]\")\n\tblurredButton = fmt.Sprintf(\"[ %s ]\", blurredStyle.Render(\"Submit\"))\n)\n\ntype model struct {\n\tfocusIndex int\n\tinputs     []textinput.Model\n\tcursorMode cursor.Mode\n\tquitting   bool\n}\n\nfunc initialModel() model {\n\tm := model{\n\t\tinputs: make([]textinput.Model, 3),\n\t}\n\n\tvar t textinput.Model\n\tfor i := range m.inputs {\n\t\tt = textinput.New()\n\t\tt.CharLimit = 32\n\n\t\ts := t.Styles()\n\t\ts.Cursor.Color = lipgloss.Color(\"205\")\n\t\ts.Focused.Prompt = focusedStyle\n\t\ts.Focused.Text = focusedStyle\n\t\ts.Blurred.Prompt = blurredStyle\n\t\ts.Focused.Text = focusedStyle\n\t\tt.SetStyles(s)\n\n\t\tswitch i {\n\t\tcase 0:\n\t\t\tt.Placeholder = \"Nickname\"\n\t\t\tt.Focus()\n\t\tcase 1:\n\t\t\tt.Placeholder = \"Email\"\n\t\t\tt.CharLimit = 64\n\t\tcase 2:\n\t\t\tt.Placeholder = \"Password\"\n\t\t\tt.EchoMode = textinput.EchoPassword\n\t\t\tt.EchoCharacter = '•'\n\t\t}\n\n\t\tm.inputs[i] = t\n\t}\n\n\treturn m\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn textinput.Blink\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"esc\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\n\t\t// Change cursor mode\n\t\tcase \"ctrl+r\":\n\t\t\tm.cursorMode++\n\t\t\tif m.cursorMode > cursor.CursorHide {\n\t\t\t\tm.cursorMode = cursor.CursorBlink\n\t\t\t}\n\t\t\tcmds := make([]tea.Cmd, len(m.inputs))\n\t\t\tfor i := range m.inputs {\n\t\t\t\ts := m.inputs[i].Styles()\n\t\t\t\ts.Cursor.Blink = m.cursorMode == cursor.CursorBlink\n\t\t\t\tm.inputs[i].SetStyles(s)\n\t\t\t}\n\t\t\treturn m, tea.Batch(cmds...)\n\n\t\t// Set focus to next input\n\t\tcase \"tab\", \"shift+tab\", \"enter\", \"up\", \"down\":\n\t\t\ts := msg.String()\n\n\t\t\t// Did the user press enter while the submit button was focused?\n\t\t\t// If so, exit.\n\t\t\tif s == \"enter\" && m.focusIndex == len(m.inputs) {\n\t\t\t\treturn m, tea.Quit\n\t\t\t}\n\n\t\t\t// Cycle indexes\n\t\t\tif s == \"up\" || s == \"shift+tab\" {\n\t\t\t\tm.focusIndex--\n\t\t\t} else {\n\t\t\t\tm.focusIndex++\n\t\t\t}\n\n\t\t\tif m.focusIndex > len(m.inputs) {\n\t\t\t\tm.focusIndex = 0\n\t\t\t} else if m.focusIndex < 0 {\n\t\t\t\tm.focusIndex = len(m.inputs)\n\t\t\t}\n\n\t\t\tcmds := make([]tea.Cmd, len(m.inputs))\n\t\t\tfor i := 0; i <= len(m.inputs)-1; i++ {\n\t\t\t\tif i == m.focusIndex {\n\t\t\t\t\t// Set focused state\n\t\t\t\t\tcmds[i] = m.inputs[i].Focus()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Remove focused state\n\t\t\t\tm.inputs[i].Blur()\n\t\t\t}\n\n\t\t\treturn m, tea.Batch(cmds...)\n\t\t}\n\t}\n\n\t// Handle character input and blinking\n\tcmd := m.updateInputs(msg)\n\n\treturn m, cmd\n}\n\nfunc (m *model) updateInputs(msg tea.Msg) tea.Cmd {\n\tcmds := make([]tea.Cmd, len(m.inputs))\n\n\t// Only text inputs with Focus() set will respond, so it's safe to simply\n\t// update all of them here without any further logic.\n\tfor i := range m.inputs {\n\t\tm.inputs[i], cmds[i] = m.inputs[i].Update(msg)\n\t}\n\n\treturn tea.Batch(cmds...)\n}\n\nfunc (m model) View() tea.View {\n\tvar b strings.Builder\n\tvar c *tea.Cursor\n\n\tfor i, in := range m.inputs {\n\t\tb.WriteString(m.inputs[i].View())\n\t\tif i < len(m.inputs)-1 {\n\t\t\tb.WriteRune('\\n')\n\t\t}\n\t\tif m.cursorMode != cursor.CursorHide && in.Focused() {\n\t\t\tc = in.Cursor()\n\t\t\tif c != nil {\n\t\t\t\tc.Y += i\n\t\t\t}\n\t\t}\n\t}\n\n\tbutton := &blurredButton\n\tif m.focusIndex == len(m.inputs) {\n\t\tbutton = &focusedButton\n\t}\n\tfmt.Fprintf(&b, \"\\n\\n%s\\n\\n\", *button)\n\n\tb.WriteString(helpStyle.Render(\"cursor mode is \"))\n\tb.WriteString(cursorModeHelpStyle.Render(m.cursorMode.String()))\n\tb.WriteString(helpStyle.Render(\" (ctrl+r to change style)\"))\n\n\tif m.quitting {\n\t\tb.WriteRune('\\n')\n\t}\n\n\tv := tea.NewView(b.String())\n\tv.Cursor = c\n\treturn v\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(initialModel()).Run(); err != nil {\n\t\tfmt.Printf(\"could not start program: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/timer/README.md",
    "content": "# Timer\n\n<img width=\"800\" src=\"./timer.gif\" />\n"
  },
  {
    "path": "examples/timer/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/help\"\n\t\"charm.land/bubbles/v2/key\"\n\t\"charm.land/bubbles/v2/timer\"\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nconst timeout = time.Second * 5\n\ntype model struct {\n\ttimer    timer.Model\n\tkeymap   keymap\n\thelp     help.Model\n\tquitting bool\n}\n\ntype keymap struct {\n\tstart key.Binding\n\tstop  key.Binding\n\treset key.Binding\n\tquit  key.Binding\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn m.timer.Init()\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase timer.TickMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.timer, cmd = m.timer.Update(msg)\n\t\treturn m, cmd\n\n\tcase timer.StartStopMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.timer, cmd = m.timer.Update(msg)\n\t\tm.keymap.stop.SetEnabled(m.timer.Running())\n\t\tm.keymap.start.SetEnabled(!m.timer.Running())\n\t\treturn m, cmd\n\n\tcase timer.TimeoutMsg:\n\t\tm.quitting = true\n\t\treturn m, tea.Quit\n\n\tcase tea.KeyPressMsg:\n\t\tswitch {\n\t\tcase key.Matches(msg, m.keymap.quit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, m.keymap.reset):\n\t\t\tm.timer.Timeout = timeout\n\t\tcase key.Matches(msg, m.keymap.start, m.keymap.stop):\n\t\t\treturn m, m.timer.Toggle()\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) helpView() string {\n\treturn \"\\n\" + m.help.ShortHelpView([]key.Binding{\n\t\tm.keymap.start,\n\t\tm.keymap.stop,\n\t\tm.keymap.reset,\n\t\tm.keymap.quit,\n\t})\n}\n\nfunc (m model) View() tea.View {\n\t// For a more detailed timer view you could read m.timer.Timeout to get\n\t// the remaining time as a time.Duration and skip calling m.timer.View()\n\t// entirely.\n\ts := m.timer.View()\n\n\tif m.timer.Timedout() {\n\t\ts = \"All done!\"\n\t}\n\ts += \"\\n\"\n\tif !m.quitting {\n\t\ts = \"Exiting in \" + s\n\t\ts += m.helpView()\n\t}\n\treturn tea.NewView(s)\n}\n\nfunc main() {\n\tm := model{\n\t\ttimer: timer.New(timeout, timer.WithInterval(time.Millisecond)),\n\t\tkeymap: keymap{\n\t\t\tstart: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"s\"),\n\t\t\t\tkey.WithHelp(\"s\", \"start\"),\n\t\t\t),\n\t\t\tstop: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"s\"),\n\t\t\t\tkey.WithHelp(\"s\", \"stop\"),\n\t\t\t),\n\t\t\treset: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"r\"),\n\t\t\t\tkey.WithHelp(\"r\", \"reset\"),\n\t\t\t),\n\t\t\tquit: key.NewBinding(\n\t\t\t\tkey.WithKeys(\"q\", \"ctrl+c\"),\n\t\t\t\tkey.WithHelp(\"q\", \"quit\"),\n\t\t\t),\n\t\t},\n\t\thelp: help.New(),\n\t}\n\tm.keymap.start.SetEnabled(false)\n\n\tif _, err := tea.NewProgram(m).Run(); err != nil {\n\t\tfmt.Println(\"Uh oh, we encountered an error:\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/tui-daemon-combo/README.md",
    "content": "# TUI Daemon\n\n<img width=\"800\" src=\"./tui-daemon-combo.gif\" />\n"
  },
  {
    "path": "examples/tui-daemon-combo/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\t\"time\"\n\n\t\"charm.land/bubbles/v2/spinner\"\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/mattn/go-isatty\"\n)\n\nvar (\n\thelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"241\")).Render\n\tmainStyle = lipgloss.NewStyle().MarginLeft(1)\n)\n\nfunc main() {\n\tvar (\n\t\tdaemonMode bool\n\t\tshowHelp   bool\n\t)\n\n\tflag.BoolVar(&daemonMode, \"d\", false, \"run as a daemon\")\n\tflag.BoolVar(&showHelp, \"h\", false, \"show help\")\n\tflag.Parse()\n\n\tif showHelp {\n\t\tflag.Usage()\n\t\tos.Exit(0)\n\t}\n\n\topts := []tea.ProgramOption{}\n\tif daemonMode || !isatty.IsTerminal(os.Stdout.Fd()) {\n\t\t// If we're in daemon mode don't render the TUI\n\t\topts = append(opts, tea.WithoutRenderer())\n\t} else {\n\t\t// If we're in TUI mode, discard log output\n\t\tlog.SetOutput(io.Discard)\n\t}\n\n\tp := tea.NewProgram(newModel(), opts...)\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Println(\"Error starting Bubble Tea program:\", err)\n\t\tos.Exit(1)\n\t}\n}\n\ntype result struct {\n\tduration time.Duration\n\temoji    string\n}\n\ntype model struct {\n\tspinner  spinner.Model\n\tresults  []result\n\tquitting bool\n}\n\nfunc newModel() model {\n\tconst showLastResults = 5\n\n\tsp := spinner.New()\n\tsp.Style = lipgloss.NewStyle().Foreground(lipgloss.Color(\"206\"))\n\n\treturn model{\n\t\tspinner: sp,\n\t\tresults: make([]result, showLastResults),\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\tlog.Println(\"Starting work...\")\n\treturn tea.Batch(\n\t\tm.spinner.Tick,\n\t\trunPretendProcess,\n\t)\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tm.quitting = true\n\t\treturn m, tea.Quit\n\tcase spinner.TickMsg:\n\t\tvar cmd tea.Cmd\n\t\tm.spinner, cmd = m.spinner.Update(msg)\n\t\treturn m, cmd\n\tcase processFinishedMsg:\n\t\td := time.Duration(msg)\n\t\tres := result{emoji: randomEmoji(), duration: d}\n\t\tlog.Printf(\"%s Job finished in %s\", res.emoji, res.duration)\n\t\tm.results = append(m.results[1:], res)\n\t\treturn m, runPretendProcess\n\tdefault:\n\t\treturn m, nil\n\t}\n}\n\nfunc (m model) View() tea.View {\n\ts := \"\\n\" +\n\t\tm.spinner.View() + \" Doing some work...\\n\\n\"\n\n\tfor _, res := range m.results {\n\t\tif res.duration == 0 {\n\t\t\ts += \"........................\\n\"\n\t\t} else {\n\t\t\ts += fmt.Sprintf(\"%s Job finished in %s\\n\", res.emoji, res.duration)\n\t\t}\n\t}\n\n\ts += helpStyle(\"\\nPress any key to exit\\n\")\n\n\tif m.quitting {\n\t\ts += \"\\n\"\n\t}\n\n\treturn tea.NewView(mainStyle.Render(s))\n}\n\n// processFinishedMsg is sent when a pretend process completes.\ntype processFinishedMsg time.Duration\n\n// pretendProcess simulates a long-running process.\nfunc runPretendProcess() tea.Msg {\n\tpause := time.Duration(rand.Int63n(899)+100) * time.Millisecond // nolint:gosec\n\ttime.Sleep(pause)\n\treturn processFinishedMsg(pause)\n}\n\nfunc randomEmoji() string {\n\temojis := []rune(\"🍦🧋🍡🤠👾😭🦊🐯🦆🥨🎏🍔🍒🍥🎮📦🦁🐶🐸🍕🥐🧲🚒🥇🏆🌽\")\n\treturn string(emojis[rand.Intn(len(emojis))]) // nolint:gosec\n}\n"
  },
  {
    "path": "examples/vanish/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype model bool\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tif _, ok := msg.(tea.KeyPressMsg); ok {\n\t\tm = true\n\t\treturn m, tea.Quit\n\t}\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tif m {\n\t\treturn tea.NewView(\"\")\n\t}\n\treturn tea.NewView(\"Press any key to quit.\\n(When this program quits, it will vanish without a trace.)\")\n}\n\nfunc main() {\n\tp := tea.NewProgram(model(false))\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, \"Oh no:\", err)\n\t}\n}\n"
  },
  {
    "path": "examples/views/README.md",
    "content": "# Views\n\n<img width=\"800\" src=\"./views.gif\" />\n"
  },
  {
    "path": "examples/views/main.go",
    "content": "package main\n\n// An example demonstrating an application with multiple views.\n//\n// Note that this example was produced before the Bubbles progress component\n// was available (github.com/charmbracelet/bubbles/progress) and thus, we're\n// implementing a progress bar from scratch here.\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/fogleman/ease\"\n\t\"github.com/lucasb-eyer/go-colorful\"\n)\n\nconst (\n\tprogressBarWidth  = 71\n\tprogressFullChar  = \"█\"\n\tprogressEmptyChar = \"░\"\n\tdotChar           = \" • \"\n)\n\n// General stuff for styling the view\nvar (\n\tkeywordStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color(\"211\"))\n\tsubtleStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color(\"241\"))\n\tticksStyle    = lipgloss.NewStyle().Foreground(lipgloss.Color(\"79\"))\n\tcheckboxStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"212\"))\n\tprogressEmpty = subtleStyle.Render(progressEmptyChar)\n\tdotStyle      = lipgloss.NewStyle().Foreground(lipgloss.Color(\"236\")).Render(dotChar)\n\tmainStyle     = lipgloss.NewStyle().MarginLeft(2)\n\n\t// Gradient colors we'll use for the progress bar\n\tramp = makeRampStyles(\"#B14FFF\", \"#00FFA3\", progressBarWidth)\n)\n\nfunc main() {\n\tinitialModel := model{0, false, 10, 0, 0, false, false}\n\tp := tea.NewProgram(initialModel)\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Println(\"could not start program:\", err)\n\t}\n}\n\ntype (\n\ttickMsg  struct{}\n\tframeMsg struct{}\n)\n\nfunc tick() tea.Cmd {\n\treturn tea.Tick(time.Second, func(time.Time) tea.Msg {\n\t\treturn tickMsg{}\n\t})\n}\n\nfunc frame() tea.Cmd {\n\treturn tea.Tick(time.Second/60, func(time.Time) tea.Msg {\n\t\treturn frameMsg{}\n\t})\n}\n\ntype model struct {\n\tChoice   int\n\tChosen   bool\n\tTicks    int\n\tFrames   int\n\tProgress float64\n\tLoaded   bool\n\tQuitting bool\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn tick()\n}\n\n// Main update function.\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\t// Make sure these keys always quit\n\tif msg, ok := msg.(tea.KeyMsg); ok {\n\t\tk := msg.String()\n\t\tif k == \"q\" || k == \"esc\" || k == \"ctrl+c\" {\n\t\t\tm.Quitting = true\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\t// Hand off the message and model to the appropriate update function for the\n\t// appropriate view based on the current state.\n\tif !m.Chosen {\n\t\treturn updateChoices(msg, m)\n\t}\n\treturn updateChosen(msg, m)\n}\n\n// The main view, which just calls the appropriate sub-view\nfunc (m model) View() tea.View {\n\tvar s string\n\tif m.Quitting {\n\t\treturn tea.NewView(\"\\n  See you later!\\n\\n\")\n\t}\n\tif !m.Chosen {\n\t\ts = choicesView(m)\n\t} else {\n\t\ts = chosenView(m)\n\t}\n\treturn tea.NewView(mainStyle.Render(\"\\n\" + s + \"\\n\"))\n}\n\n// Sub-update functions\n\n// Update loop for the first view where you're choosing a task.\nfunc updateChoices(msg tea.Msg, m model) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"j\", \"down\":\n\t\t\tm.Choice++\n\t\t\tif m.Choice > 3 {\n\t\t\t\tm.Choice = 3\n\t\t\t}\n\t\tcase \"k\", \"up\":\n\t\t\tm.Choice--\n\t\t\tif m.Choice < 0 {\n\t\t\t\tm.Choice = 0\n\t\t\t}\n\t\tcase \"enter\":\n\t\t\tm.Chosen = true\n\t\t\treturn m, frame()\n\t\t}\n\n\tcase tickMsg:\n\t\tif m.Ticks == 0 {\n\t\t\tm.Quitting = true\n\t\t\treturn m, tea.Quit\n\t\t}\n\t\tm.Ticks--\n\t\treturn m, tick()\n\t}\n\n\treturn m, nil\n}\n\n// Update loop for the second view after a choice has been made\nfunc updateChosen(msg tea.Msg, m model) (tea.Model, tea.Cmd) {\n\tswitch msg.(type) {\n\tcase frameMsg:\n\t\tif !m.Loaded {\n\t\t\tm.Frames++\n\t\t\tm.Progress = ease.OutBounce(float64(m.Frames) / float64(100))\n\t\t\tif m.Progress >= 1 {\n\t\t\t\tm.Progress = 1\n\t\t\t\tm.Loaded = true\n\t\t\t\tm.Ticks = 3\n\t\t\t\treturn m, tick()\n\t\t\t}\n\t\t\treturn m, frame()\n\t\t}\n\n\tcase tickMsg:\n\t\tif m.Loaded {\n\t\t\tif m.Ticks == 0 {\n\t\t\t\tm.Quitting = true\n\t\t\t\treturn m, tea.Quit\n\t\t\t}\n\t\t\tm.Ticks--\n\t\t\treturn m, tick()\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\n// Sub-views\n\n// The first view, where you're choosing a task\nfunc choicesView(m model) string {\n\tc := m.Choice\n\n\ttpl := \"What to do today?\\n\\n\"\n\ttpl += \"%s\\n\\n\"\n\ttpl += \"Program quits in %s seconds\\n\\n\"\n\ttpl += subtleStyle.Render(\"j/k, up/down: select\") + dotStyle +\n\t\tsubtleStyle.Render(\"enter: choose\") + dotStyle +\n\t\tsubtleStyle.Render(\"q, esc: quit\")\n\n\tchoices := fmt.Sprintf(\n\t\t\"%s\\n%s\\n%s\\n%s\",\n\t\tcheckbox(\"Plant carrots\", c == 0),\n\t\tcheckbox(\"Go to the market\", c == 1),\n\t\tcheckbox(\"Read something\", c == 2),\n\t\tcheckbox(\"See friends\", c == 3),\n\t)\n\n\treturn fmt.Sprintf(tpl, choices, ticksStyle.Render(strconv.Itoa(m.Ticks)))\n}\n\n// The second view, after a task has been chosen\nfunc chosenView(m model) string {\n\tvar msg string\n\n\tswitch m.Choice {\n\tcase 0:\n\t\tmsg = fmt.Sprintf(\"Carrot planting?\\n\\nCool, we'll need %s and %s...\", keywordStyle.Render(\"libgarden\"), keywordStyle.Render(\"vegeutils\"))\n\tcase 1:\n\t\tmsg = fmt.Sprintf(\"A trip to the market?\\n\\nOkay, then we should install %s and %s...\", keywordStyle.Render(\"marketkit\"), keywordStyle.Render(\"libshopping\"))\n\tcase 2:\n\t\tmsg = fmt.Sprintf(\"Reading time?\\n\\nOkay, cool, then we’ll need a library. Yes, an %s.\", keywordStyle.Render(\"actual library\"))\n\tdefault:\n\t\tmsg = fmt.Sprintf(\"It’s always good to see friends.\\n\\nFetching %s and %s...\", keywordStyle.Render(\"social-skills\"), keywordStyle.Render(\"conversationutils\"))\n\t}\n\n\tlabel := \"Downloading...\"\n\tif m.Loaded {\n\t\tlabel = fmt.Sprintf(\"Downloaded. Exiting in %s seconds...\", ticksStyle.Render(strconv.Itoa(m.Ticks)))\n\t}\n\n\treturn msg + \"\\n\\n\" + label + \"\\n\" + progressbar(m.Progress) + \"%\"\n}\n\nfunc checkbox(label string, checked bool) string {\n\tif checked {\n\t\treturn checkboxStyle.Render(\"[x] \" + label)\n\t}\n\treturn fmt.Sprintf(\"[ ] %s\", label)\n}\n\nfunc progressbar(percent float64) string {\n\tw := float64(progressBarWidth)\n\n\tfullSize := int(math.Round(w * percent))\n\tvar fullCells string\n\tfor i := range fullSize {\n\t\tfullCells += ramp[i].Render(progressFullChar)\n\t}\n\n\temptySize := int(w) - fullSize\n\temptyCells := strings.Repeat(progressEmpty, emptySize)\n\n\treturn fmt.Sprintf(\"%s%s %3.0f\", fullCells, emptyCells, math.Round(percent*100))\n}\n\n// Utils\n\n// Generate a blend of colors.\nfunc makeRampStyles(colorA, colorB string, steps float64) (s []lipgloss.Style) {\n\tcA, _ := colorful.Hex(colorA)\n\tcB, _ := colorful.Hex(colorB)\n\n\tfor i := 0.0; i < steps; i++ {\n\t\tc := cA.BlendLuv(cB, i/steps)\n\t\ts = append(s, lipgloss.NewStyle().Foreground(lipgloss.Color(colorToHex(c))))\n\t}\n\treturn s\n}\n\n// Convert a colorful.Color to a hexadecimal format.\nfunc colorToHex(c colorful.Color) string {\n\treturn fmt.Sprintf(\"#%s%s%s\", colorFloatToHex(c.R), colorFloatToHex(c.G), colorFloatToHex(c.B))\n}\n\n// Helper function for converting colors to hex. Assumes a value between 0 and\n// 1.\nfunc colorFloatToHex(f float64) (s string) {\n\ts = strconv.FormatInt(int64(f*255), 16)\n\tif len(s) == 1 {\n\t\ts = \"0\" + s\n\t}\n\treturn s\n}\n"
  },
  {
    "path": "examples/window-size/main.go",
    "content": "package main\n\n// A simple program that queries and displays the window-size.\n\nimport (\n\t\"log\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nfunc main() {\n\tp := tea.NewProgram(model{})\n\tif _, err := p.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\ntype model struct{}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tif s := msg.String(); s == \"ctrl+c\" || s == \"q\" || s == \"esc\" {\n\t\t\treturn m, tea.Quit\n\t\t}\n\t\treturn m, tea.RequestWindowSize\n\n\tcase tea.WindowSizeMsg:\n\t\treturn m, tea.Printf(\"The window size is: %dx%d\", msg.Width, msg.Height)\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\treturn tea.NewView(\"\\nWhen you're done press q to quit.\\nPress any other key to query the window-size.\\n\")\n}\n"
  },
  {
    "path": "exec.go",
    "content": "package tea\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n)\n\n// execMsg is used internally to run an ExecCommand sent with Exec.\ntype execMsg struct {\n\tcmd ExecCommand\n\tfn  ExecCallback\n}\n\n// Exec is used to perform arbitrary I/O in a blocking fashion, effectively\n// pausing the Program while execution is running and resuming it when\n// execution has completed.\n//\n// Most of the time you'll want to use ExecProcess, which runs an exec.Cmd.\n//\n// For non-interactive i/o you should use a Cmd (that is, a tea.Cmd).\nfunc Exec(c ExecCommand, fn ExecCallback) Cmd {\n\treturn func() Msg {\n\t\treturn execMsg{cmd: c, fn: fn}\n\t}\n}\n\n// ExecProcess runs the given *exec.Cmd in a blocking fashion, effectively\n// pausing the Program while the command is running. After the *exec.Cmd exists\n// the Program resumes. It's useful for spawning other interactive applications\n// such as editors and shells from within a Program.\n//\n// To produce the command, pass an *exec.Cmd and a function which returns\n// a message containing the error which may have occurred when running the\n// ExecCommand.\n//\n//\ttype VimFinishedMsg struct { err error }\n//\n//\tc := exec.Command(\"vim\", \"file.txt\")\n//\n//\tcmd := ExecProcess(c, func(err error) Msg {\n//\t    return VimFinishedMsg{err: err}\n//\t})\n//\n// Or, if you don't care about errors, you could simply:\n//\n//\tcmd := ExecProcess(exec.Command(\"vim\", \"file.txt\"), nil)\n//\n// For non-interactive i/o you should use a Cmd (that is, a tea.Cmd).\nfunc ExecProcess(c *exec.Cmd, fn ExecCallback) Cmd {\n\treturn Exec(wrapExecCommand(c), fn)\n}\n\n// ExecCallback is used when executing an *exec.Command to return a message\n// with an error, which may or may not be nil.\ntype ExecCallback func(error) Msg\n\n// ExecCommand can be implemented to execute things in a blocking fashion in\n// the current terminal.\ntype ExecCommand interface {\n\tRun() error\n\tSetStdin(io.Reader)\n\tSetStdout(io.Writer)\n\tSetStderr(io.Writer)\n}\n\n// wrapExecCommand wraps an exec.Cmd so that it satisfies the ExecCommand\n// interface so it can be used with Exec.\nfunc wrapExecCommand(c *exec.Cmd) ExecCommand {\n\treturn &osExecCommand{Cmd: c}\n}\n\n// osExecCommand is a layer over an exec.Cmd that satisfies the ExecCommand\n// interface.\ntype osExecCommand struct{ *exec.Cmd }\n\n// SetStdin sets stdin on underlying exec.Cmd to the given io.Reader.\nfunc (c *osExecCommand) SetStdin(r io.Reader) {\n\t// If unset, have the command use the same input as the terminal.\n\tif c.Stdin == nil {\n\t\tc.Stdin = r\n\t}\n}\n\n// SetStdout sets stdout on underlying exec.Cmd to the given io.Writer.\nfunc (c *osExecCommand) SetStdout(w io.Writer) {\n\t// If unset, have the command use the same output as the terminal.\n\tif c.Stdout == nil {\n\t\tc.Stdout = w\n\t}\n}\n\n// SetStderr sets stderr on the underlying exec.Cmd to the given io.Writer.\nfunc (c *osExecCommand) SetStderr(w io.Writer) {\n\t// If unset, use stderr for the command's stderr\n\tif c.Stderr == nil {\n\t\tc.Stderr = w\n\t}\n}\n\n// exec runs an ExecCommand and delivers the results to the program as a Msg.\nfunc (p *Program) exec(c ExecCommand, fn ExecCallback) {\n\tif err := p.releaseTerminal(false); err != nil {\n\t\t// If we can't release input, abort.\n\t\tif fn != nil {\n\t\t\tgo p.Send(fn(err))\n\t\t}\n\t\treturn\n\t}\n\n\tc.SetStdin(p.input)\n\tc.SetStdout(p.output)\n\tc.SetStderr(os.Stderr)\n\n\t// Execute system command.\n\tif err := c.Run(); err != nil {\n\t\t_ = p.RestoreTerminal() // also try to restore the terminal.\n\t\tif fn != nil {\n\t\t\tgo p.Send(fn(err))\n\t\t}\n\t\treturn\n\t}\n\n\t// Have the program re-capture input.\n\terr := p.RestoreTerminal()\n\tif fn != nil {\n\t\tgo p.Send(fn(err))\n\t}\n}\n"
  },
  {
    "path": "exec_test.go",
    "content": "package tea\n\nimport (\n\t\"bytes\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"testing\"\n)\n\ntype execFinishedMsg struct{ err error }\n\ntype testExecModel struct {\n\tcmd string\n\terr error\n}\n\nfunc (m *testExecModel) Init() Cmd {\n\tc := exec.Command(m.cmd) //nolint:gosec\n\treturn ExecProcess(c, func(err error) Msg {\n\t\treturn execFinishedMsg{err}\n\t})\n}\n\nfunc (m *testExecModel) Update(msg Msg) (Model, Cmd) {\n\tswitch msg := msg.(type) {\n\tcase execFinishedMsg:\n\t\tif msg.err != nil {\n\t\t\tm.err = msg.err\n\t\t}\n\t\treturn m, Quit\n\t}\n\n\treturn m, nil\n}\n\nfunc (m *testExecModel) View() View {\n\treturn NewView(\"\\n\")\n}\n\ntype spyRenderer struct {\n\trenderer\n\tcalledReset bool\n}\n\nfunc TestTeaExec(t *testing.T) {\n\ttype test struct {\n\t\tname      string\n\t\tcmd       string\n\t\texpectErr bool\n\t}\n\n\t// TODO: add more tests for windows\n\ttests := []test{\n\t\t{\n\t\t\tname:      \"invalid command\",\n\t\t\tcmd:       \"invalid\",\n\t\t\texpectErr: true,\n\t\t},\n\t}\n\n\tif runtime.GOOS != \"windows\" {\n\t\ttests = append(tests, []test{\n\t\t\t{\n\t\t\t\tname:      \"true\",\n\t\t\t\tcmd:       \"true\",\n\t\t\t\texpectErr: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:      \"false\",\n\t\t\t\tcmd:       \"false\",\n\t\t\t\texpectErr: true,\n\t\t\t},\n\t\t}...)\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\tvar in bytes.Buffer\n\n\t\t\tm := &testExecModel{cmd: test.cmd}\n\t\t\tp := NewProgram(m,\n\t\t\t\tWithInput(&in),\n\t\t\t\tWithOutput(&buf),\n\t\t\t)\n\t\t\tif _, err := p.Run(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tp.renderer = &spyRenderer{renderer: p.renderer}\n\n\t\t\tif m.err != nil && !test.expectErr {\n\t\t\t\tt.Errorf(\"expected no error, got %v\", m.err)\n\n\t\t\t\tif !p.renderer.(*spyRenderer).calledReset {\n\t\t\t\t\tt.Error(\"expected renderer to be reset\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tif m.err == nil && test.expectErr {\n\t\t\t\tt.Error(\"expected error, got nil\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "focus.go",
    "content": "package tea\n\n// FocusMsg represents a terminal focus message.\n// This occurs when the terminal gains focus.\ntype FocusMsg struct{}\n\n// BlurMsg represents a terminal blur message.\n// This occurs when the terminal loses focus.\ntype BlurMsg struct{}\n"
  },
  {
    "path": "go.mod",
    "content": "module charm.land/bubbletea/v2\n\nretract v2.0.0-beta1 // We add a \".\" after the \"beta\" in the version number.\n\ngo 1.24.6\n\nrequire (\n\tgithub.com/charmbracelet/colorprofile v0.4.2\n\tgithub.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8\n\tgithub.com/charmbracelet/x/ansi v0.11.6\n\tgithub.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f\n\tgithub.com/charmbracelet/x/term v0.2.2\n\tgithub.com/lucasb-eyer/go-colorful v1.3.0\n\tgithub.com/muesli/cancelreader v0.2.2\n\tgolang.org/x/sys v0.41.0\n)\n\nrequire (\n\tgithub.com/aymanbagabas/go-udiff v0.2.0 // indirect\n\tgithub.com/charmbracelet/x/termios v0.1.1 // indirect\n\tgithub.com/charmbracelet/x/windows v0.2.2 // indirect\n\tgithub.com/clipperhouse/displaywidth v0.9.0 // indirect\n\tgithub.com/clipperhouse/stringish v0.1.1 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.5.0 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.19 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=\ngithub.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=\ngithub.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=\ngithub.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=\ngithub.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=\ngithub.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=\ngithub.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=\ngithub.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=\ngithub.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=\ngithub.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=\ngithub.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=\ngithub.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=\ngithub.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=\ngithub.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=\ngithub.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=\ngithub.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=\ngithub.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=\ngithub.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=\ngithub.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=\ngithub.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=\ngithub.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=\ngithub.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=\ngithub.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngolang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=\ngolang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\n"
  },
  {
    "path": "input.go",
    "content": "package tea\n\nimport (\n\tuv \"github.com/charmbracelet/ultraviolet\"\n)\n\n// translateInputEvent translates an input event into a Bubble Tea Msg.\nfunc (p *Program) translateInputEvent(e uv.Event) Msg {\n\tswitch e := e.(type) {\n\tcase uv.ClipboardEvent:\n\t\treturn ClipboardMsg(e)\n\tcase uv.ForegroundColorEvent:\n\t\treturn ForegroundColorMsg(e)\n\tcase uv.BackgroundColorEvent:\n\t\treturn BackgroundColorMsg(e)\n\tcase uv.CursorColorEvent:\n\t\treturn CursorColorMsg(e)\n\tcase uv.CursorPositionEvent:\n\t\treturn CursorPositionMsg(e)\n\tcase uv.FocusEvent:\n\t\treturn FocusMsg(e)\n\tcase uv.BlurEvent:\n\t\treturn BlurMsg(e)\n\tcase uv.KeyPressEvent:\n\t\treturn KeyPressMsg(e)\n\tcase uv.KeyReleaseEvent:\n\t\treturn KeyReleaseMsg(e)\n\tcase uv.MouseClickEvent:\n\t\treturn MouseClickMsg(e)\n\tcase uv.MouseMotionEvent:\n\t\treturn MouseMotionMsg(e)\n\tcase uv.MouseReleaseEvent:\n\t\treturn MouseReleaseMsg(e)\n\tcase uv.MouseWheelEvent:\n\t\treturn MouseWheelMsg(e)\n\tcase uv.PasteEvent:\n\t\treturn PasteMsg(e)\n\tcase uv.PasteStartEvent:\n\t\treturn PasteStartMsg(e)\n\tcase uv.PasteEndEvent:\n\t\treturn PasteEndMsg(e)\n\tcase uv.WindowSizeEvent:\n\t\treturn WindowSizeMsg(e)\n\tcase uv.CapabilityEvent:\n\t\treturn CapabilityMsg(e)\n\tcase uv.TerminalVersionEvent:\n\t\treturn TerminalVersionMsg(e)\n\tcase uv.KeyboardEnhancementsEvent:\n\t\treturn KeyboardEnhancementsMsg(e)\n\tcase uv.ModeReportEvent:\n\t\treturn ModeReportMsg(e)\n\t}\n\treturn e\n}\n"
  },
  {
    "path": "key.go",
    "content": "package tea\n\nimport (\n\t\"fmt\"\n\n\tuv \"github.com/charmbracelet/ultraviolet\"\n)\n\nconst (\n\t// KeyExtended is a special key code used to signify that a key event\n\t// contains multiple runes.\n\tKeyExtended = uv.KeyExtended\n)\n\n// Special key symbols.\nconst (\n\n\t// Special keys.\n\n\tKeyUp     = uv.KeyUp\n\tKeyDown   = uv.KeyDown\n\tKeyRight  = uv.KeyRight\n\tKeyLeft   = uv.KeyLeft\n\tKeyBegin  = uv.KeyBegin\n\tKeyFind   = uv.KeyFind\n\tKeyInsert = uv.KeyInsert\n\tKeyDelete = uv.KeyDelete\n\tKeySelect = uv.KeySelect\n\tKeyPgUp   = uv.KeyPgUp\n\tKeyPgDown = uv.KeyPgDown\n\tKeyHome   = uv.KeyHome\n\tKeyEnd    = uv.KeyEnd\n\n\t// Keypad keys.\n\n\tKeyKpEnter    = uv.KeyKpEnter\n\tKeyKpEqual    = uv.KeyKpEqual\n\tKeyKpMultiply = uv.KeyKpMultiply\n\tKeyKpPlus     = uv.KeyKpPlus\n\tKeyKpComma    = uv.KeyKpComma\n\tKeyKpMinus    = uv.KeyKpMinus\n\tKeyKpDecimal  = uv.KeyKpDecimal\n\tKeyKpDivide   = uv.KeyKpDivide\n\tKeyKp0        = uv.KeyKp0\n\tKeyKp1        = uv.KeyKp1\n\tKeyKp2        = uv.KeyKp2\n\tKeyKp3        = uv.KeyKp3\n\tKeyKp4        = uv.KeyKp4\n\tKeyKp5        = uv.KeyKp5\n\tKeyKp6        = uv.KeyKp6\n\tKeyKp7        = uv.KeyKp7\n\tKeyKp8        = uv.KeyKp8\n\tKeyKp9        = uv.KeyKp9\n\n\t// The following are keys defined in the Kitty keyboard protocol.\n\t// XXX: Investigate the names of these keys.\n\tKeyKpSep    = uv.KeyKpSep\n\tKeyKpUp     = uv.KeyKpUp\n\tKeyKpDown   = uv.KeyKpDown\n\tKeyKpLeft   = uv.KeyKpLeft\n\tKeyKpRight  = uv.KeyKpRight\n\tKeyKpPgUp   = uv.KeyKpPgUp\n\tKeyKpPgDown = uv.KeyKpPgDown\n\tKeyKpHome   = uv.KeyKpHome\n\tKeyKpEnd    = uv.KeyKpEnd\n\tKeyKpInsert = uv.KeyKpInsert\n\tKeyKpDelete = uv.KeyKpDelete\n\tKeyKpBegin  = uv.KeyKpBegin\n\n\t// Function keys.\n\n\tKeyF1  = uv.KeyF1\n\tKeyF2  = uv.KeyF2\n\tKeyF3  = uv.KeyF3\n\tKeyF4  = uv.KeyF4\n\tKeyF5  = uv.KeyF5\n\tKeyF6  = uv.KeyF6\n\tKeyF7  = uv.KeyF7\n\tKeyF8  = uv.KeyF8\n\tKeyF9  = uv.KeyF9\n\tKeyF10 = uv.KeyF10\n\tKeyF11 = uv.KeyF11\n\tKeyF12 = uv.KeyF12\n\tKeyF13 = uv.KeyF13\n\tKeyF14 = uv.KeyF14\n\tKeyF15 = uv.KeyF15\n\tKeyF16 = uv.KeyF16\n\tKeyF17 = uv.KeyF17\n\tKeyF18 = uv.KeyF18\n\tKeyF19 = uv.KeyF19\n\tKeyF20 = uv.KeyF20\n\tKeyF21 = uv.KeyF21\n\tKeyF22 = uv.KeyF22\n\tKeyF23 = uv.KeyF23\n\tKeyF24 = uv.KeyF24\n\tKeyF25 = uv.KeyF25\n\tKeyF26 = uv.KeyF26\n\tKeyF27 = uv.KeyF27\n\tKeyF28 = uv.KeyF28\n\tKeyF29 = uv.KeyF29\n\tKeyF30 = uv.KeyF30\n\tKeyF31 = uv.KeyF31\n\tKeyF32 = uv.KeyF32\n\tKeyF33 = uv.KeyF33\n\tKeyF34 = uv.KeyF34\n\tKeyF35 = uv.KeyF35\n\tKeyF36 = uv.KeyF36\n\tKeyF37 = uv.KeyF37\n\tKeyF38 = uv.KeyF38\n\tKeyF39 = uv.KeyF39\n\tKeyF40 = uv.KeyF40\n\tKeyF41 = uv.KeyF41\n\tKeyF42 = uv.KeyF42\n\tKeyF43 = uv.KeyF43\n\tKeyF44 = uv.KeyF44\n\tKeyF45 = uv.KeyF45\n\tKeyF46 = uv.KeyF46\n\tKeyF47 = uv.KeyF47\n\tKeyF48 = uv.KeyF48\n\tKeyF49 = uv.KeyF49\n\tKeyF50 = uv.KeyF50\n\tKeyF51 = uv.KeyF51\n\tKeyF52 = uv.KeyF52\n\tKeyF53 = uv.KeyF53\n\tKeyF54 = uv.KeyF54\n\tKeyF55 = uv.KeyF55\n\tKeyF56 = uv.KeyF56\n\tKeyF57 = uv.KeyF57\n\tKeyF58 = uv.KeyF58\n\tKeyF59 = uv.KeyF59\n\tKeyF60 = uv.KeyF60\n\tKeyF61 = uv.KeyF61\n\tKeyF62 = uv.KeyF62\n\tKeyF63 = uv.KeyF63\n\n\t// The following are keys defined in the Kitty keyboard protocol.\n\t// XXX: Investigate the names of these keys.\n\n\tKeyCapsLock    = uv.KeyCapsLock\n\tKeyScrollLock  = uv.KeyScrollLock\n\tKeyNumLock     = uv.KeyNumLock\n\tKeyPrintScreen = uv.KeyPrintScreen\n\tKeyPause       = uv.KeyPause\n\tKeyMenu        = uv.KeyMenu\n\n\tKeyMediaPlay        = uv.KeyMediaPlay\n\tKeyMediaPause       = uv.KeyMediaPause\n\tKeyMediaPlayPause   = uv.KeyMediaPlayPause\n\tKeyMediaReverse     = uv.KeyMediaReverse\n\tKeyMediaStop        = uv.KeyMediaStop\n\tKeyMediaFastForward = uv.KeyMediaFastForward\n\tKeyMediaRewind      = uv.KeyMediaRewind\n\tKeyMediaNext        = uv.KeyMediaNext\n\tKeyMediaPrev        = uv.KeyMediaPrev\n\tKeyMediaRecord\n\n\tKeyLowerVol = uv.KeyLowerVol\n\tKeyRaiseVol = uv.KeyRaiseVol\n\tKeyMute     = uv.KeyMute\n\n\tKeyLeftShift      = uv.KeyLeftShift\n\tKeyLeftAlt        = uv.KeyLeftAlt\n\tKeyLeftCtrl       = uv.KeyLeftCtrl\n\tKeyLeftSuper      = uv.KeyLeftSuper\n\tKeyLeftHyper      = uv.KeyLeftHyper\n\tKeyLeftMeta       = uv.KeyLeftMeta\n\tKeyRightShift     = uv.KeyRightShift\n\tKeyRightAlt       = uv.KeyRightAlt\n\tKeyRightCtrl      = uv.KeyRightCtrl\n\tKeyRightSuper     = uv.KeyRightSuper\n\tKeyRightHyper     = uv.KeyRightHyper\n\tKeyRightMeta      = uv.KeyRightMeta\n\tKeyIsoLevel3Shift = uv.KeyIsoLevel3Shift\n\tKeyIsoLevel5Shift = uv.KeyIsoLevel5Shift\n\n\t// Special names in C0.\n\n\tKeyBackspace = uv.KeyBackspace\n\tKeyTab       = uv.KeyTab\n\tKeyEnter     = uv.KeyEnter\n\tKeyReturn    = uv.KeyReturn\n\tKeyEscape    = uv.KeyEscape\n\tKeyEsc       = uv.KeyEsc\n\n\t// Special names in G0.\n\n\tKeySpace = uv.KeySpace\n)\n\n// KeyPressMsg represents a key press message.\ntype KeyPressMsg Key\n\n// String implements [fmt.Stringer] and is quite useful for matching key\n// events. For details, on what this returns see [Key.String].\nfunc (k KeyPressMsg) String() string {\n\treturn Key(k).String()\n}\n\n// Keystroke returns the keystroke representation of the [Key]. While less type\n// safe than looking at the individual fields, it will usually be more\n// convenient and readable to use this method when matching against keys.\n//\n// Note that modifier keys are always printed in the following order:\n//   - ctrl\n//   - alt\n//   - shift\n//   - meta\n//   - hyper\n//   - super\n//\n// For example, you'll always see \"ctrl+shift+alt+a\" and never\n// \"shift+ctrl+alt+a\".\nfunc (k KeyPressMsg) Keystroke() string {\n\treturn uv.Key(k).Keystroke()\n}\n\n// Key returns the underlying key event. This is a syntactic sugar for casting\n// the key event to a [Key].\nfunc (k KeyPressMsg) Key() Key {\n\treturn Key(k)\n}\n\n// KeyReleaseMsg represents a key release message.\ntype KeyReleaseMsg Key\n\n// String implements [fmt.Stringer] and is quite useful for matching key\n// events. For details, on what this returns see [Key.String].\nfunc (k KeyReleaseMsg) String() string {\n\treturn Key(k).String()\n}\n\n// Keystroke returns the keystroke representation of the [Key]. While less type\n// safe than looking at the individual fields, it will usually be more\n// convenient and readable to use this method when matching against keys.\n//\n// Note that modifier keys are always printed in the following order:\n//   - ctrl\n//   - alt\n//   - shift\n//   - meta\n//   - hyper\n//   - super\n//\n// For example, you'll always see \"ctrl+shift+alt+a\" and never\n// \"shift+ctrl+alt+a\".\nfunc (k KeyReleaseMsg) Keystroke() string {\n\treturn uv.Key(k).Keystroke()\n}\n\n// Key returns the underlying key event. This is a convenience method and\n// syntactic sugar to satisfy the [KeyMsg] interface, and cast the key event to\n// [Key].\nfunc (k KeyReleaseMsg) Key() Key {\n\treturn Key(k)\n}\n\n// KeyMsg represents a key event. This can be either a key press or a key\n// release event.\ntype KeyMsg interface {\n\tfmt.Stringer\n\n\t// Key returns the underlying key event.\n\tKey() Key\n}\n\n// Key represents a Key press or release event. It contains information about\n// the Key pressed, like the runes, the type of Key, and the modifiers pressed.\n// There are a couple general patterns you could use to check for key presses\n// or releases:\n//\n//\t// Switch on the string representation of the key (shorter)\n//\tswitch msg := msg.(type) {\n//\tcase KeyPressMsg:\n//\t    switch msg.String() {\n//\t    case \"enter\":\n//\t        fmt.Println(\"you pressed enter!\")\n//\t    case \"a\":\n//\t        fmt.Println(\"you pressed a!\")\n//\t    }\n//\t}\n//\n//\t// Switch on the key type (more foolproof)\n//\tswitch msg := msg.(type) {\n//\tcase KeyMsg:\n//\t    // catch both KeyPressMsg and KeyReleaseMsg\n//\t    switch key := msg.Key(); key.Code {\n//\t    case KeyEnter:\n//\t        fmt.Println(\"you pressed enter!\")\n//\t    default:\n//\t        switch key.Text {\n//\t        case \"a\":\n//\t            fmt.Println(\"you pressed a!\")\n//\t        }\n//\t    }\n//\t}\n//\n// Note that [Key.Text] will be empty for special keys like [KeyEnter],\n// [KeyTab], and for keys that don't represent printable characters like key\n// combos with modifier keys. In other words, [Key.Text] is populated only for\n// keys that represent printable characters shifted or unshifted (like 'a',\n// 'A', '1', '!', etc.).\ntype Key struct {\n\t// Text contains the actual characters received. This usually the same as\n\t// [Key.Code]. When [Key.Text] is non-empty, it indicates that the key\n\t// pressed represents printable character(s).\n\tText string\n\n\t// Mod represents modifier keys, like [ModCtrl], [ModAlt], and so on.\n\tMod KeyMod\n\n\t// Code represents the key pressed. This is usually a special key like\n\t// [KeyTab], [KeyEnter], [KeyF1], or a printable character like 'a'.\n\tCode rune\n\n\t// ShiftedCode is the actual, shifted key pressed by the user. For example,\n\t// if the user presses shift+a, or caps lock is on, [Key.ShiftedCode] will\n\t// be 'A' and [Key.Code] will be 'a'.\n\t//\n\t// In the case of non-latin keyboards, like Arabic, [Key.ShiftedCode] is the\n\t// unshifted key on the keyboard.\n\t//\n\t// This is only available with the Kitty Keyboard Protocol or the Windows\n\t// Console API.\n\tShiftedCode rune\n\n\t// BaseCode is the key pressed according to the standard PC-101 key layout.\n\t// On international keyboards, this is the key that would be pressed if the\n\t// keyboard was set to US PC-101 layout.\n\t//\n\t// For example, if the user presses 'q' on a French AZERTY keyboard,\n\t// [Key.BaseCode] will be 'q'.\n\t//\n\t// This is only available with the Kitty Keyboard Protocol or the Windows\n\t// Console API.\n\tBaseCode rune\n\n\t// IsRepeat indicates whether the key is being held down and sending events\n\t// repeatedly.\n\t//\n\t// This is only available with the Kitty Keyboard Protocol or the Windows\n\t// Console API.\n\tIsRepeat bool\n}\n\n// String implements [fmt.Stringer] and is quite useful for matching key\n// events. It will return the textual representation of the [Key] if there is\n// one, otherwise, it will fallback to [Key.Keystroke].\n//\n// For example, you'll always get \"?\" and instead of \"shift+/\" on a US ANSI\n// keyboard.\nfunc (k Key) String() string {\n\treturn uv.Key(k).String()\n}\n\n// Keystroke returns the keystroke representation of the [Key]. While less type\n// safe than looking at the individual fields, it will usually be more\n// convenient and readable to use this method when matching against keys.\n//\n// Note that modifier keys are always printed in the following order:\n//   - ctrl\n//   - alt\n//   - shift\n//   - meta\n//   - hyper\n//   - super\n//\n// For example, you'll always see \"ctrl+shift+alt+a\" and never\n// \"shift+ctrl+alt+a\".\nfunc (k Key) Keystroke() string {\n\treturn uv.Key(k).Keystroke()\n}\n"
  },
  {
    "path": "keyboard.go",
    "content": "package tea\n\nimport (\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// KeyboardEnhancementsMsg is a message that gets sent when the terminal\n// supports keyboard enhancements.\ntype KeyboardEnhancementsMsg struct {\n\t// Flags is a bitmask of enabled keyboard enhancement features. A non-zero\n\t// value indicates that at least we have key disambiguation support.\n\t//\n\t// See [ansi.KittyReportEventTypes] and other constants for details.\n\t//\n\t// Example:\n\t//\n\t//  ```go\n\t//  // The hard way\n\t//  if msg.Flags&ansi.KittyReportEventTypes != 0 {\n\t//     // Terminal supports reporting different key event types\n\t//  }\n\t//\n\t//  // The easy way\n\t//  if msg.SupportsEventTypes() {\n\t//     // Terminal supports reporting different key event types\n\t//  }\n\t//  ```\n\tFlags int\n}\n\n// SupportsKeyDisambiguation returns whether the terminal supports key\n// disambiguation (e.g., distinguishing between different modifier keys).\nfunc (k KeyboardEnhancementsMsg) SupportsKeyDisambiguation() bool {\n\treturn k.Flags > 0\n}\n\n// SupportsEventTypes returns whether the terminal supports reporting\n// different types of key events (press, release, and repeat).\nfunc (k KeyboardEnhancementsMsg) SupportsEventTypes() bool {\n\treturn k.Flags&ansi.KittyReportEventTypes != 0\n}\n"
  },
  {
    "path": "logging.go",
    "content": "package tea\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"unicode\"\n)\n\n// LogToFile sets up default logging to log to a file. This is helpful as we\n// can't print to the terminal since our TUI is occupying it. If the file\n// doesn't exist it will be created.\n//\n// Don't forget to close the file when you're done with it.\n//\n//\t  f, err := LogToFile(\"debug.log\", \"debug\")\n//\t  if err != nil {\n//\t\t\tfmt.Println(\"fatal:\", err)\n//\t\t\tos.Exit(1)\n//\t  }\n//\t  defer f.Close()\nfunc LogToFile(path string, prefix string) (*os.File, error) {\n\treturn LogToFileWith(path, prefix, log.Default())\n}\n\n// LogOptionsSetter is an interface implemented by stdlib's log and charm's log\n// libraries.\ntype LogOptionsSetter interface {\n\tSetOutput(io.Writer)\n\tSetPrefix(string)\n}\n\n// LogToFileWith does allows to call LogToFile with a custom LogOptionsSetter.\nfunc LogToFileWith(path string, prefix string, log LogOptionsSetter) (*os.File, error) {\n\tf, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) //nolint:mnd\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error opening file for logging: %w\", err)\n\t}\n\tlog.SetOutput(f)\n\n\t// Add a space after the prefix if a prefix is being specified and it\n\t// doesn't already have a trailing space.\n\tif len(prefix) > 0 {\n\t\tfinalChar := prefix[len(prefix)-1]\n\t\tif !unicode.IsSpace(rune(finalChar)) {\n\t\t\tprefix += \" \"\n\t\t}\n\t}\n\tlog.SetPrefix(prefix)\n\n\treturn f, nil\n}\n"
  },
  {
    "path": "logging_test.go",
    "content": "package tea\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestLogToFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"log.txt\")\n\tprefix := \"logprefix\"\n\tf, err := LogToFile(path, prefix)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tlog.SetFlags(log.Lmsgprefix)\n\tlog.Println(\"some test log\")\n\tif closeErr := f.Close(); closeErr != nil {\n\t\tt.Error(closeErr)\n\t}\n\tout, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif string(out) != prefix+\" some test log\\n\" {\n\t\tt.Fatalf(\"wrong log msg: %q\", string(out))\n\t}\n}\n"
  },
  {
    "path": "mod.go",
    "content": "package tea\n\nimport uv \"github.com/charmbracelet/ultraviolet\"\n\n// KeyMod represents modifier keys.\ntype KeyMod = uv.KeyMod\n\n// Modifier keys.\nconst (\n\tModShift = uv.ModShift\n\tModAlt   = uv.ModAlt\n\tModCtrl  = uv.ModCtrl\n\tModMeta  = uv.ModMeta\n\n\t// These modifiers are used with the Kitty protocol.\n\t// XXX: Meta and Super are swapped in the Kitty protocol,\n\t// this is to preserve compatibility with XTerm modifiers.\n\n\tModHyper = uv.ModHyper\n\tModSuper = uv.ModSuper // Windows/Command keys\n\n\t// These are key lock states.\n\n\tModCapsLock   = uv.ModCapsLock\n\tModNumLock    = uv.ModNumLock\n\tModScrollLock = uv.ModScrollLock // Defined in Windows API only\n)\n"
  },
  {
    "path": "mouse.go",
    "content": "package tea\n\nimport (\n\t\"fmt\"\n\n\tuv \"github.com/charmbracelet/ultraviolet\"\n)\n\n// MouseButton represents the button that was pressed during a mouse message.\ntype MouseButton = uv.MouseButton\n\n// Mouse event buttons\n//\n// This is based on X11 mouse button codes.\n//\n//\t1 = left button\n//\t2 = middle button (pressing the scroll wheel)\n//\t3 = right button\n//\t4 = turn scroll wheel up\n//\t5 = turn scroll wheel down\n//\t6 = push scroll wheel left\n//\t7 = push scroll wheel right\n//\t8 = 4th button (aka browser backward button)\n//\t9 = 5th button (aka browser forward button)\n//\t10\n//\t11\n//\n// Other buttons are not supported.\nconst (\n\tMouseNone       = uv.MouseNone\n\tMouseLeft       = uv.MouseLeft\n\tMouseMiddle     = uv.MouseMiddle\n\tMouseRight      = uv.MouseRight\n\tMouseWheelUp    = uv.MouseWheelUp\n\tMouseWheelDown  = uv.MouseWheelDown\n\tMouseWheelLeft  = uv.MouseWheelLeft\n\tMouseWheelRight = uv.MouseWheelRight\n\tMouseBackward   = uv.MouseBackward\n\tMouseForward    = uv.MouseForward\n\tMouseButton10   = uv.MouseButton10\n\tMouseButton11\n)\n\n// MouseMsg represents a mouse message. This is a generic mouse message that\n// can represent any kind of mouse event.\ntype MouseMsg interface {\n\tfmt.Stringer\n\n\t// Mouse returns the underlying mouse event.\n\tMouse() Mouse\n}\n\n// Mouse represents a Mouse message. Use [MouseMsg] to represent all mouse\n// messages.\n//\n// The X and Y coordinates are zero-based, with (0,0) being the upper left\n// corner of the terminal.\n//\n//\t// Catch all mouse events\n//\tswitch msg := msg.(type) {\n//\tcase MouseMsg:\n//\t    m := msg.Mouse()\n//\t    fmt.Println(\"Mouse event:\", m.X, m.Y, m)\n//\t}\n//\n//\t// Only catch mouse click events\n//\tswitch msg := msg.(type) {\n//\tcase MouseClickMsg:\n//\t    fmt.Println(\"Mouse click event:\", msg.X, msg.Y, msg)\n//\t}\ntype Mouse struct {\n\tX, Y   int\n\tButton MouseButton\n\tMod    KeyMod\n}\n\n// String returns a string representation of the mouse message.\nfunc (m Mouse) String() (s string) {\n\treturn uv.Mouse(m).String()\n}\n\n// MouseClickMsg represents a mouse button click message.\ntype MouseClickMsg Mouse\n\n// String returns a string representation of the mouse click message.\nfunc (e MouseClickMsg) String() string {\n\treturn Mouse(e).String()\n}\n\n// Mouse returns the underlying mouse event. This is a convenience method and\n// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse\n// event to [Mouse].\nfunc (e MouseClickMsg) Mouse() Mouse {\n\treturn Mouse(e)\n}\n\n// MouseReleaseMsg represents a mouse button release message.\ntype MouseReleaseMsg Mouse\n\n// String returns a string representation of the mouse release message.\nfunc (e MouseReleaseMsg) String() string {\n\treturn Mouse(e).String()\n}\n\n// Mouse returns the underlying mouse event. This is a convenience method and\n// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse\n// event to [Mouse].\nfunc (e MouseReleaseMsg) Mouse() Mouse {\n\treturn Mouse(e)\n}\n\n// MouseWheelMsg represents a mouse wheel message event.\ntype MouseWheelMsg Mouse\n\n// String returns a string representation of the mouse wheel message.\nfunc (e MouseWheelMsg) String() string {\n\treturn Mouse(e).String()\n}\n\n// Mouse returns the underlying mouse event. This is a convenience method and\n// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse\n// event to [Mouse].\nfunc (e MouseWheelMsg) Mouse() Mouse {\n\treturn Mouse(e)\n}\n\n// MouseMotionMsg represents a mouse motion message.\ntype MouseMotionMsg Mouse\n\n// String returns a string representation of the mouse motion message.\nfunc (e MouseMotionMsg) String() string {\n\tm := Mouse(e)\n\tif m.Button != 0 {\n\t\treturn m.String() + \"+motion\"\n\t}\n\treturn m.String() + \"motion\"\n}\n\n// Mouse returns the underlying mouse event. This is a convenience method and\n// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse\n// event to [Mouse].\nfunc (e MouseMotionMsg) Mouse() Mouse {\n\treturn Mouse(e)\n}\n"
  },
  {
    "path": "nil_renderer.go",
    "content": "package tea\n\nimport (\n\t\"github.com/charmbracelet/colorprofile\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// nilRenderer is a no-op renderer. It implements the Renderer interface but\n// doesn't render anything to the terminal.\ntype nilRenderer struct{}\n\nvar _ renderer = nilRenderer{}\n\n// start implements renderer.\nfunc (n nilRenderer) start() {}\n\n// clearScreen implements renderer.\nfunc (n nilRenderer) clearScreen() {}\n\n// insertAbove implements renderer.\nfunc (n nilRenderer) insertAbove(string) error { return nil }\n\n// resize implements renderer.\nfunc (n nilRenderer) resize(int, int) {}\n\n// setColorProfile implements renderer.\nfunc (n nilRenderer) setColorProfile(colorprofile.Profile) {}\n\n// flush implements the Renderer interface.\nfunc (nilRenderer) flush(bool) error { return nil }\n\n// close implements the Renderer interface.\nfunc (nilRenderer) close() error { return nil }\n\n// render implements the Renderer interface.\nfunc (nilRenderer) render(View) {}\n\n// reset implements the Renderer interface.\nfunc (nilRenderer) reset() {}\n\n// writeString implements the Renderer interface.\nfunc (nilRenderer) writeString(string) (int, error) { return 0, nil }\n\n// setSyncdUpdates implements the Renderer interface.\nfunc (n nilRenderer) setSyncdUpdates(bool) {}\n\n// setWidthMethod implements the Renderer interface.\nfunc (n nilRenderer) setWidthMethod(ansi.Method) {}\n\n// onMouse implements the Renderer interface.\nfunc (n nilRenderer) onMouse(MouseMsg) Cmd {\n\treturn nil\n}\n"
  },
  {
    "path": "options.go",
    "content": "package tea\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"sync/atomic\"\n\n\t\"github.com/charmbracelet/colorprofile\"\n)\n\n// ProgramOption is used to set options when initializing a Program. Program can\n// accept a variable number of options.\n//\n// Example usage:\n//\n//\tp := NewProgram(model, WithInput(someInput), WithOutput(someOutput))\ntype ProgramOption func(*Program)\n\n// WithContext lets you specify a context in which to run the Program. This is\n// useful if you want to cancel the execution from outside. When a Program gets\n// cancelled it will exit with an error ErrProgramKilled.\nfunc WithContext(ctx context.Context) ProgramOption {\n\treturn func(p *Program) {\n\t\tp.externalCtx = ctx\n\t}\n}\n\n// WithOutput sets the output which, by default, is stdout. In most cases you\n// won't need to use this.\nfunc WithOutput(output io.Writer) ProgramOption {\n\treturn func(p *Program) {\n\t\tp.output = output\n\t}\n}\n\n// WithInput sets the input which, by default, is stdin. In most cases you\n// won't need to use this. To disable input entirely pass nil.\n//\n//\tp := NewProgram(model, WithInput(nil))\nfunc WithInput(input io.Reader) ProgramOption {\n\treturn func(p *Program) {\n\t\tp.input = input\n\t\tp.disableInput = input == nil\n\t}\n}\n\n// WithEnvironment sets the environment variables that the program will use.\n// This useful when the program is running in a remote session (e.g. SSH) and\n// you want to pass the environment variables from the remote session to the\n// program.\n//\n// Example:\n//\n//\tvar sess ssh.Session // ssh.Session is a type from the github.com/charmbracelet/ssh package\n//\tpty, _, _ := sess.Pty()\n//\tenviron := append(sess.Environ(), \"TERM=\"+pty.Term)\n//\tp := tea.NewProgram(model, tea.WithEnvironment(environ)\nfunc WithEnvironment(env []string) ProgramOption {\n\treturn func(p *Program) {\n\t\tp.environ = env\n\t}\n}\n\n// WithoutSignalHandler disables the signal handler that Bubble Tea sets up for\n// Programs. This is useful if you want to handle signals yourself.\nfunc WithoutSignalHandler() ProgramOption {\n\treturn func(p *Program) {\n\t\tp.disableSignalHandler = true\n\t}\n}\n\n// WithoutCatchPanics disables the panic catching that Bubble Tea does by\n// default. If panic catching is disabled the terminal will be in a fairly\n// unusable state after a panic because Bubble Tea will not perform its usual\n// cleanup on exit.\nfunc WithoutCatchPanics() ProgramOption {\n\treturn func(p *Program) {\n\t\tp.disableCatchPanics = true\n\t}\n}\n\n// WithoutSignals will ignore OS signals.\n// This is mainly useful for testing.\nfunc WithoutSignals() ProgramOption {\n\treturn func(p *Program) {\n\t\tatomic.StoreUint32(&p.ignoreSignals, 1)\n\t}\n}\n\n// WithoutRenderer disables the renderer. When this is set output and log\n// statements will be plainly sent to stdout (or another output if one is set)\n// without any rendering and redrawing logic. In other words, printing and\n// logging will behave the same way it would in a non-TUI commandline tool.\n// This can be useful if you want to use the Bubble Tea framework for a non-TUI\n// application, or to provide an additional non-TUI mode to your Bubble Tea\n// programs. For example, your program could behave like a daemon if output is\n// not a TTY.\nfunc WithoutRenderer() ProgramOption {\n\treturn func(p *Program) {\n\t\tp.disableRenderer = true\n\t}\n}\n\n// WithFilter supplies an event filter that will be invoked before Bubble Tea\n// processes a tea.Msg. The event filter can return any tea.Msg which will then\n// get handled by Bubble Tea instead of the original event. If the event filter\n// returns nil, the event will be ignored and Bubble Tea will not process it.\n//\n// As an example, this could be used to prevent a program from shutting down if\n// there are unsaved changes.\n//\n// Example:\n//\n//\tfunc filter(m tea.Model, msg tea.Msg) tea.Msg {\n//\t\tif _, ok := msg.(tea.QuitMsg); !ok {\n//\t\t\treturn msg\n//\t\t}\n//\n//\t\tmodel := m.(myModel)\n//\t\tif model.hasChanges {\n//\t\t\treturn nil\n//\t\t}\n//\n//\t\treturn msg\n//\t}\n//\n//\tp := tea.NewProgram(Model{}, tea.WithFilter(filter));\n//\n//\tif _,err := p.Run(); err != nil {\n//\t\tfmt.Println(\"Error running program:\", err)\n//\t\tos.Exit(1)\n//\t}\nfunc WithFilter(filter func(Model, Msg) Msg) ProgramOption {\n\treturn func(p *Program) {\n\t\tp.filter = filter\n\t}\n}\n\n// WithFPS sets a custom maximum FPS at which the renderer should run. If\n// less than 1, the default value of 60 will be used. If over 120, the FPS\n// will be capped at 120.\nfunc WithFPS(fps int) ProgramOption {\n\treturn func(p *Program) {\n\t\tp.fps = fps\n\t}\n}\n\n// WithColorProfile sets the color profile that the program will use. This is\n// useful when you want to force a specific color profile. By default, Bubble\n// Tea will try to detect the terminal's color profile from environment\n// variables and terminfo capabilities. Use [tea.WithEnvironment] to set custom\n// environment variables.\nfunc WithColorProfile(profile colorprofile.Profile) ProgramOption {\n\treturn func(p *Program) {\n\t\tp.profile = &profile\n\t}\n}\n\n// WithWindowSize sets the initial size of the terminal window. This is useful\n// when you need to set the initial size of the terminal window, for example\n// during testing or when you want to run your program in a non-interactive\n// environment.\nfunc WithWindowSize(width, height int) ProgramOption {\n\treturn func(p *Program) {\n\t\tp.width = width\n\t\tp.height = height\n\t}\n}\n"
  },
  {
    "path": "options_test.go",
    "content": "package tea\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"os\"\n\t\"sync/atomic\"\n\t\"testing\"\n)\n\nfunc TestOptions(t *testing.T) {\n\tt.Run(\"output\", func(t *testing.T) {\n\t\tvar b bytes.Buffer\n\t\tp := NewProgram(nil, WithOutput(&b))\n\t\tif f, ok := p.output.(*os.File); ok {\n\t\t\tt.Errorf(\"expected output to custom, got %v\", f.Fd())\n\t\t}\n\t})\n\n\tt.Run(\"renderer\", func(t *testing.T) {\n\t\tp := NewProgram(nil, WithoutRenderer())\n\t\tif !p.disableRenderer {\n\t\t\tt.Errorf(\"expected renderer to be a nilRenderer, got %v\", p.renderer)\n\t\t}\n\t})\n\n\tt.Run(\"without signals\", func(t *testing.T) {\n\t\tp := NewProgram(nil, WithoutSignals())\n\t\tif atomic.LoadUint32(&p.ignoreSignals) == 0 {\n\t\t\tt.Errorf(\"ignore signals should have been set\")\n\t\t}\n\t})\n\n\tt.Run(\"filter\", func(t *testing.T) {\n\t\tp := NewProgram(nil, WithFilter(func(_ Model, msg Msg) Msg { return msg }))\n\t\tif p.filter == nil {\n\t\t\tt.Errorf(\"expected filter to be set\")\n\t\t}\n\t})\n\n\tt.Run(\"external context\", func(t *testing.T) {\n\t\textCtx, extCancel := context.WithCancel(context.Background())\n\t\tdefer extCancel()\n\n\t\tp := NewProgram(nil, WithContext(extCtx))\n\t\tif p.externalCtx != extCtx || p.externalCtx == context.Background() {\n\t\t\tt.Errorf(\"expected passed in external context, got default\")\n\t\t}\n\t})\n\n\tt.Run(\"input options\", func(t *testing.T) {\n\t\texercise := func(t *testing.T, opt ProgramOption, fn func(*Program)) {\n\t\t\tp := NewProgram(nil, opt)\n\t\t\tfn(p)\n\t\t}\n\n\t\tt.Run(\"nil input\", func(t *testing.T) {\n\t\t\texercise(t, WithInput(nil), func(p *Program) {\n\t\t\t\tif !p.disableInput || p.input != nil {\n\t\t\t\t\tt.Errorf(\"expected input to be disabled, got %v\", p.input)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tt.Run(\"custom input\", func(t *testing.T) {\n\t\t\tvar b bytes.Buffer\n\t\t\texercise(t, WithInput(&b), func(p *Program) {\n\t\t\t\tif p.input != &b {\n\t\t\t\t\tt.Errorf(\"expected input to be custom, got %v\", p.input)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"startup options\", func(t *testing.T) {\n\t\texercise := func(t *testing.T, opt ProgramOption, fn func(*Program)) {\n\t\t\tp := NewProgram(nil, opt)\n\t\t\tfn(p)\n\t\t}\n\n\t\tt.Run(\"without catch panics\", func(t *testing.T) {\n\t\t\texercise(t, WithoutCatchPanics(), func(p *Program) {\n\t\t\t\tif !p.disableCatchPanics {\n\t\t\t\t\tt.Errorf(\"expected catch panics to be disabled\")\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tt.Run(\"without signal handler\", func(t *testing.T) {\n\t\t\texercise(t, WithoutSignalHandler(), func(p *Program) {\n\t\t\t\tif !p.disableSignalHandler {\n\t\t\t\t\tt.Errorf(\"expected signal handler to be disabled\")\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "paste.go",
    "content": "package tea\n\n// PasteMsg is an message that is emitted when a terminal receives pasted text\n// using bracketed-paste.\ntype PasteMsg struct {\n\tContent string\n}\n\n// String returns the pasted content as a string.\nfunc (p PasteMsg) String() string {\n\treturn p.Content\n}\n\n// PasteStartMsg is an message that is emitted when the terminal starts the\n// bracketed-paste text.\ntype PasteStartMsg struct{}\n\n// PasteEndMsg is an message that is emitted when the terminal ends the\n// bracketed-paste text.\ntype PasteEndMsg struct{}\n"
  },
  {
    "path": "profile.go",
    "content": "package tea\n\nimport \"github.com/charmbracelet/colorprofile\"\n\n// ColorProfileMsg is a message that describes the terminal's color profile.\n// This message is send to the program's update function when the program is\n// started.\n//\n// To upgrade the terminal color profile, use the `tea.RequestCapability`\n// command to request the `RGB` and `Tc` terminfo capabilities. Bubble Tea will\n// then cache the terminal's color profile and send a `ColorProfileMsg` to the\n// program's update function.\ntype ColorProfileMsg struct {\n\tcolorprofile.Profile\n}\n"
  },
  {
    "path": "raw.go",
    "content": "package tea\n\n// RawMsg is a message that contains a string to be printed to the terminal\n// without any intermediate processing.\ntype RawMsg struct {\n\tMsg any\n}\n\n// Raw is a command that prints the given string to the terminal without any\n// formatting.\n//\n// This is intended for advanced use cases where you need to query the terminal\n// or send escape sequences directly. Don't use this unless you know what\n// you're doing :)\n//\n// Example:\n//\n//\tfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n//\t  switch msg := msg.(type) {\n//\t  case input.PrimaryDeviceAttributesEvent:\n//\t    for _, attr := range msg {\n//\t      if attr == 4 {\n//\t        // We have Sixel graphics support!\n//\t        break\n//\t      }\n//\t    }\n//\t  }\n//\n//\t  // Request the terminal primary device attributes to detect Sixel graphics\n//\t  // support.\n//\t  return m, tea.Raw(ansi.RequestPrimaryDeviceAttributes)\n//\t}\nfunc Raw(r any) Cmd {\n\treturn func() Msg {\n\t\treturn RawMsg{r}\n\t}\n}\n"
  },
  {
    "path": "renderer.go",
    "content": "package tea\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/charmbracelet/colorprofile\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\nconst (\n\t// defaultFramerate specifies the maximum interval at which we should\n\t// update the view.\n\tdefaultFPS = 60\n\tmaxFPS     = 120\n)\n\n// renderer is the interface for Bubble Tea renderers.\ntype renderer interface {\n\t// start starts the renderer.\n\tstart()\n\n\t// close closes the renderer and flushes any remaining data.\n\tclose() error\n\n\t// render renders a frame to the output.\n\trender(View)\n\n\t// flush flushes the renderer's buffer to the output.\n\tflush(closing bool) error\n\n\t// reset resets the renderer's state to its initial state.\n\treset()\n\n\t// insertAbove inserts unmanaged lines above the renderer.\n\tinsertAbove(string) error\n\n\t// setSyncdUpdates sets whether to use synchronized updates.\n\tsetSyncdUpdates(bool)\n\n\t// setWidthMethod sets the method for calculating the width of the terminal.\n\tsetWidthMethod(ansi.Method)\n\n\t// resize notify the renderer of a terminal resize.\n\tresize(int, int)\n\n\t// setColorProfile sets the color profile.\n\tsetColorProfile(colorprofile.Profile)\n\n\t// clearScreen clears the screen.\n\tclearScreen()\n\n\t// writeString writes a string to the renderer's output.\n\twriteString(string) (int, error)\n\n\t// onMouse handles a mouse event.\n\tonMouse(MouseMsg) Cmd\n}\n\ntype printLineMessage struct {\n\tmessageBody string\n}\n\n// Println prints above the Program. This output is unmanaged by the program and\n// will persist across renders by the Program.\n//\n// Unlike fmt.Println (but similar to log.Println) the message will be print on\n// its own line.\n//\n// If the altscreen is active no output will be printed.\nfunc Println(args ...any) Cmd {\n\treturn func() Msg {\n\t\treturn printLineMessage{\n\t\t\tmessageBody: fmt.Sprint(args...),\n\t\t}\n\t}\n}\n\n// Printf prints above the Program. It takes a format template followed by\n// values similar to fmt.Printf. This output is unmanaged by the program and\n// will persist across renders by the Program.\n//\n// Unlike fmt.Printf (but similar to log.Printf) the message will be print on\n// its own line.\n//\n// If the altscreen is active no output will be printed.\nfunc Printf(template string, args ...any) Cmd {\n\treturn func() Msg {\n\t\treturn printLineMessage{\n\t\t\tmessageBody: fmt.Sprintf(template, args...),\n\t\t}\n\t}\n}\n\n// encodeCursorStyle returns the integer value for the given cursor style and\n// blink state.\nfunc encodeCursorStyle(style CursorShape, blink bool) int {\n\t// We're using the ANSI escape sequence values for cursor styles.\n\t// We need to map both [style] and [steady] to the correct value.\n\tstyle = (style * 2) + 1 //nolint:mnd\n\tif !blink {\n\t\tstyle++\n\t}\n\treturn int(style)\n}\n"
  },
  {
    "path": "screen.go",
    "content": "package tea\n\nimport \"github.com/charmbracelet/x/ansi\"\n\n// WindowSizeMsg is used to report the terminal size. It's sent to Update once\n// initially and then on every terminal resize. Note that Windows does not\n// have support for reporting when resizes occur as it does not support the\n// SIGWINCH signal.\ntype WindowSizeMsg struct {\n\tWidth  int\n\tHeight int\n}\n\n// ClearScreen is a special command that tells the program to clear the screen\n// before the next update. This can be used to move the cursor to the top left\n// of the screen and clear visual clutter when the alt screen is not in use.\n//\n// Note that it should never be necessary to call ClearScreen() for regular\n// redraws.\nfunc ClearScreen() Msg {\n\treturn clearScreenMsg{}\n}\n\n// clearScreenMsg is an internal message that signals to clear the screen.\n// You can send a clearScreenMsg with ClearScreen.\ntype clearScreenMsg struct{}\n\n// ModeReportMsg is a message that represents a mode report event (DECRPM).\n//\n// This is sent by the terminal in response to a request for a terminal mode\n// report (DECRQM). It indicates the current setting of a specific terminal\n// mode like cursor visibility, mouse tracking, etc.\n//\n// Example:\n//\n//\t```go\n//\tfunc (m model) Init() tea.Cmd {\n//\t  // Does my terminal support reporting focus events?\n//\t  return tea.Raw(ansi.RequestModeFocusEvent)\n//\t}\n//\n//\tfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n//\t  switch msg := msg.(type) {\n//\t  case tea.ModeReportMsg:\n//\t    if msg.Mode == ansi.ModeFocusEvent && !msg.Value.IsNotRecognized() {\n//\t      // Terminal supports focus events\n//\t      m.supportsFocus = true\n//\t    }\n//\t  }\n//\t  return m, nil\n//\t}\n//\n//\tfunc (m model) View() tea.View {\n//\t  var view tea.View\n//\t  view.ReportFocus = m.supportsFocus\n//\t  view.SetContent(fmt.Sprintf(\"Terminal supports focus events: %v\", m.supportsFocus))\n//\t  return view\n//\t}\n//\t```\n//\n// See: https://vt100.net/docs/vt510-rm/DECRPM.html\ntype ModeReportMsg struct {\n\t// Mode is the mode number.\n\tMode ansi.Mode\n\n\t// Value is the mode value.\n\tValue ansi.ModeSetting\n}\n"
  },
  {
    "path": "screen_test.go",
    "content": "package tea\n\nimport (\n\t\"bytes\"\n\t\"image/color\"\n\t\"testing\"\n\n\t\"github.com/charmbracelet/colorprofile\"\n\t\"github.com/charmbracelet/x/exp/golden\"\n)\n\ntype testViewOpts struct {\n\taltScreen   bool\n\tmouseMode   MouseMode\n\tshowCursor  bool\n\tdisableBp   bool\n\tkeyReleases bool\n\tbgColor     color.Color\n}\n\nfunc testViewOptsCmds(opts ...testViewOpts) []Cmd {\n\tcmds := make([]Cmd, len(opts))\n\tfor i, o := range opts {\n\t\to := o\n\t\tcmds[i] = func() Msg {\n\t\t\treturn o\n\t\t}\n\t}\n\treturn cmds\n}\n\ntype testViewModel struct {\n\t*testModel\n\topts testViewOpts\n}\n\nfunc (m *testViewModel) Update(msg Msg) (Model, Cmd) {\n\tswitch msg := msg.(type) {\n\tcase testViewOpts:\n\t\tm.opts = msg\n\t\treturn m, nil\n\t}\n\ttm, cmd := m.testModel.Update(msg)\n\tm.testModel = tm.(*testModel)\n\treturn m, cmd\n}\n\nfunc (m *testViewModel) View() View {\n\tv := m.testModel.View()\n\tv.AltScreen = m.opts.altScreen\n\tv.MouseMode = m.opts.mouseMode\n\tv.DisableBracketedPasteMode = m.opts.disableBp\n\tv.KeyboardEnhancements.ReportEventTypes = m.opts.keyReleases\n\tv.BackgroundColor = m.opts.bgColor\n\tif m.opts.showCursor {\n\t\tv.Cursor = NewCursor(0, 0)\n\t}\n\treturn v\n}\n\nfunc TestViewModel(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\topts []testViewOpts\n\t}{\n\t\t{\n\t\t\tname: \"altscreen\",\n\t\t\topts: []testViewOpts{\n\t\t\t\t{altScreen: true},\n\t\t\t\t{altScreen: false},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"altscreen_autoexit\",\n\t\t\topts: []testViewOpts{\n\t\t\t\t{altScreen: true},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"mouse_cellmotion\",\n\t\t\topts: []testViewOpts{\n\t\t\t\t{mouseMode: MouseModeCellMotion},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"mouse_allmotion\",\n\t\t\topts: []testViewOpts{\n\t\t\t\t{mouseMode: MouseModeAllMotion},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"mouse_disable\",\n\t\t\topts: []testViewOpts{\n\t\t\t\t{mouseMode: MouseModeAllMotion},\n\t\t\t\t{mouseMode: MouseModeNone},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"cursor_hide\",\n\t\t\topts: []testViewOpts{\n\t\t\t\t{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"cursor_hideshow\",\n\t\t\topts: []testViewOpts{\n\t\t\t\t{showCursor: false},\n\t\t\t\t{showCursor: true},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"bp_stop_start\",\n\t\t\topts: []testViewOpts{\n\t\t\t\t{disableBp: true},\n\t\t\t\t{disableBp: false},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"kitty_stop_startreleases\",\n\t\t\topts: []testViewOpts{\n\t\t\t\t{},\n\t\t\t\t{keyReleases: true},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"bg_set_color\",\n\t\t\topts: []testViewOpts{\n\t\t\t\t{bgColor: color.RGBA{255, 255, 255, 255}},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\tvar in bytes.Buffer\n\n\t\t\tm := &testViewModel{testModel: &testModel{}}\n\t\t\tp := NewProgram(m,\n\t\t\t\t// Set the initial window size for the program.\n\t\t\t\tWithWindowSize(80, 24),\n\t\t\t\t// Use ANSI256 to increase test coverage.\n\t\t\t\tWithColorProfile(colorprofile.ANSI256),\n\t\t\t\t// always use xterm and 256 colors for tests\n\t\t\t\tWithEnvironment([]string{\"TERM=xterm-256color\"}),\n\t\t\t\tWithInput(&in),\n\t\t\t\tWithOutput(&buf),\n\t\t\t)\n\n\t\t\tgo p.Send(append(sequenceMsg(testViewOptsCmds(test.opts...)), Quit))\n\n\t\t\tif _, err := p.Run(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tgolden.RequireEqual(t, buf.Bytes())\n\t\t})\n\t}\n}\n\nfunc TestClearMsg(t *testing.T) {\n\ttype test struct {\n\t\tname string\n\t\tcmds sequenceMsg\n\t}\n\ttests := []test{\n\t\t{\n\t\t\tname: \"clear_screen\",\n\t\t\tcmds: []Cmd{ClearScreen},\n\t\t},\n\t\t{\n\t\t\tname: \"read_set_clipboard\",\n\t\t\tcmds: []Cmd{ReadClipboard, SetClipboard(\"success\")},\n\t\t},\n\t\t{\n\t\t\tname: \"bg_fg_cur_color\",\n\t\t\tcmds: []Cmd{RequestForegroundColor, RequestBackgroundColor, RequestCursorColor},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\tvar in bytes.Buffer\n\n\t\t\tm := &testModel{}\n\t\t\tp := NewProgram(m,\n\t\t\t\t// Set the initial window size for the program.\n\t\t\t\tWithWindowSize(80, 24),\n\t\t\t\t// Use ANSI256 to increase test coverage.\n\t\t\t\tWithColorProfile(colorprofile.ANSI256),\n\t\t\t\t// always use xterm and 256 colors for tests\n\t\t\t\tWithEnvironment([]string{\"TERM=xterm-256color\"}),\n\t\t\t\tWithInput(&in),\n\t\t\t\tWithOutput(&buf),\n\t\t\t)\n\n\t\t\tgo p.Send(append(test.cmds, Quit))\n\n\t\t\tif _, err := p.Run(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tgolden.RequireEqual(t, buf.Bytes())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "signals_unix.go",
    "content": "//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || aix || zos\n// +build darwin dragonfly freebsd linux netbsd openbsd solaris aix zos\n\npackage tea\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\n// listenForResize sends messages (or errors) when the terminal resizes.\n// Argument output should be the file descriptor for the terminal; usually\n// os.Stdout.\nfunc (p *Program) listenForResize(done chan struct{}) {\n\tsig := make(chan os.Signal, 1)\n\tsignal.Notify(sig, syscall.SIGWINCH)\n\n\tdefer func() {\n\t\tsignal.Stop(sig)\n\t\tclose(done)\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase <-p.ctx.Done():\n\t\t\treturn\n\t\tcase <-sig:\n\t\t}\n\n\t\tp.checkResize()\n\t}\n}\n"
  },
  {
    "path": "signals_windows.go",
    "content": "//go:build windows\n// +build windows\n\npackage tea\n\n// listenForResize is not available on windows because windows does not\n// implement syscall.SIGWINCH.\nfunc (p *Program) listenForResize(done chan struct{}) {\n\tclose(done)\n}\n"
  },
  {
    "path": "tea.go",
    "content": "// Package tea provides a framework for building rich terminal user interfaces\n// based on the paradigms of The Elm Architecture. It's well-suited for simple\n// and complex terminal applications, either inline, full-window, or a mix of\n// both. It's been battle-tested in several large projects and is\n// production-ready.\n//\n// A tutorial is available at https://github.com/charmbracelet/bubbletea/tree/master/tutorials\n//\n// Example programs can be found at https://github.com/charmbracelet/bubbletea/tree/master/examples\npackage tea\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"image/color\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/charmbracelet/colorprofile\"\n\tuv \"github.com/charmbracelet/ultraviolet\"\n\t\"github.com/charmbracelet/x/ansi\"\n\t\"github.com/charmbracelet/x/term\"\n\t\"github.com/muesli/cancelreader\"\n)\n\n// ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic.\nvar ErrProgramPanic = errors.New(\"program experienced a panic\")\n\n// ErrProgramKilled is returned by [Program.Run] when the program gets killed.\nvar ErrProgramKilled = errors.New(\"program was killed\")\n\n// ErrInterrupted is returned by [Program.Run] when the program get a SIGINT\n// signal, or when it receives a [InterruptMsg].\nvar ErrInterrupted = errors.New(\"program was interrupted\")\n\n// Msg contain data from the result of a IO operation. Msgs trigger the update\n// function and, henceforth, the UI.\ntype Msg = uv.Event\n\n// Model contains the program's state as well as its core functions.\ntype Model interface {\n\t// Init is the first function that will be called. It returns an optional\n\t// initial command. To not perform an initial command return nil.\n\tInit() Cmd\n\n\t// Update is called when a message is received. Use it to inspect messages\n\t// and, in response, update the model and/or send a command.\n\tUpdate(Msg) (Model, Cmd)\n\n\t// View renders the program's UI, which can be a string or a [Layer]. The\n\t// view is rendered after every Update.\n\tView() View\n}\n\n// NewView is a helper function to create a new [View] with the given styled\n// string. A styled string represents text with styles and hyperlinks encoded\n// as ANSI escape codes.\n//\n// Example:\n//\n//\t```go\n//\tv := tea.NewView(\"Hello, World!\")\n//\t```\nfunc NewView(s string) View {\n\tvar view View\n\tview.SetContent(s)\n\treturn view\n}\n\n// View represents a terminal view that can be composed of multiple layers.\n// It can also contain a cursor that will be rendered on top of the layers.\ntype View struct {\n\t// Content is the screen content of the view. It holds styled strings that\n\t// will be rendered to the terminal when the view is rendered.\n\t//\n\t// A styled string represents text with styles and hyperlinks encoded as\n\t// ANSI escape codes.\n\t//\n\t// Example:\n\t//\n\t//  ```go\n\t//  v := tea.NewView(\"Hello, World!\")\n\t//  ```\n\tContent string\n\n\t// OnMouse is an optional mouse message handler that can be used to\n\t// intercept mouse messages that depends on view content from last render.\n\t// It can be useful for implementing view-specific behavior without\n\t// breaking the unidirectional data flow of Bubble Tea.\n\t//\n\t// Example:\n\t//\n\t//  ```go\n\t//  content := \"Hello, World!\"\n\t//  v := tea.NewView(content)\n\t//  v.OnMouse = func(msg tea.MouseMsg) tea.Cmd {\n\t//      return func() tea.Msg {\n\t//        m := msg.Mouse()\n\t//        // Check if the mouse is within the bounds of \"World!\"\n\t//        start := strings.Index(content, \"World!\")\n\t//        end := start + len(\"World!\")\n\t//        if m.Y == 0 && m.X >= start && m.X < end {\n\t//          // Mouse is over \"World!\"\n\t//          return MyCustomMsg{\n\t//            MouseMsg: msg,\n\t//          }\n\t//\t\t  }\n\t//      }\n\t//    }\n\t//    return nil\n\t//  }\n\t//  return v\n\t//  ```\n\tOnMouse func(msg MouseMsg) Cmd\n\n\t// Cursor represents the cursor position, style, and visibility on the\n\t// screen. When not nil, the cursor will be shown at the specified\n\t// position.\n\tCursor *Cursor\n\n\t// BackgroundColor when not nil, sets the terminal background color. Use\n\t// nil to reset to the terminal's default background color.\n\tBackgroundColor color.Color\n\n\t// ForegroundColor when not nil, sets the terminal foreground color. Use\n\t// nil to reset to the terminal's default foreground color.\n\tForegroundColor color.Color\n\n\t// WindowTitle sets the terminal window title. Support depends on the\n\t// terminal.\n\tWindowTitle string\n\n\t// ProgressBar when not nil, shows a progress bar in the terminal's\n\t// progress bar section. Support depends on the terminal.\n\tProgressBar *ProgressBar\n\n\t// AltScreen puts the program in the alternate screen buffer\n\t// (i.e. the program goes into full window mode). Note that the altscreen will\n\t// be automatically exited when the program quits.\n\t//\n\t// Example:\n\t//\n\t//\tfunc (m model) View() tea.View {\n\t//\t    v := tea.NewView(\"Hello, World!\")\n\t//\t    v.AltScreen = true\n\t//\t    return v\n\t//\t}\n\t//\n\tAltScreen bool\n\n\t// ReportFocus enables reporting when the terminal gains and loses focus.\n\t// When this is enabled [FocusMsg] and [BlurMsg] messages will be sent to\n\t// your Update method.\n\t//\n\t// Note that while most terminals and multiplexers support focus reporting,\n\t// some do not. Also note that tmux needs to be configured to report focus\n\t// events.\n\tReportFocus bool\n\n\t// DisableBracketedPasteMode disables bracketed paste mode for this view.\n\tDisableBracketedPasteMode bool\n\n\t// MouseMode sets the mouse mode for this view. It can be one of\n\t// [MouseModeNone], [MouseModeCellMotion], or [MouseModeAllMotion].\n\tMouseMode MouseMode\n\n\t// KeyboardEnhancements describes what keyboard enhancement features Bubble\n\t// Tea should request from the terminal.\n\t//\n\t// Bubble Tea supports requesting the following keyboard enhancement features:\n\t//   - ReportEventTypes: requests the terminal to report key repeat and\n\t//     release events.\n\t//\n\t// If the terminal supports any of these features, your program will\n\t// receive  a [KeyboardEnhancementsMsg] that indicates which features are\n\t// available.\n\tKeyboardEnhancements KeyboardEnhancements\n}\n\n// KeyboardEnhancements describes the requested keyboard enhancement features.\n// If the terminal supports any of them, it will respond with a\n// [KeyboardEnhancementsMsg] that indicates which features are supported.\n\n// KeyboardEnhancements defines different keyboard enhancement features that\n// can be requested from the terminal.\n\n// KeyboardEnhancements defines different keyboard enhancement features that\n// can be requested from the terminal.\n//\n// By default, Bubble Tea requests basic key disambiguation features from the\n// terminal. If the terminal supports keyboard enhancements, or any of its\n// additional features, it will respond with a [KeyboardEnhancementsMsg] that\n// indicates which features are supported.\n//\n// Example:\n//\n//\t```go\n//\tfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n//\t  switch msg := msg.(type) {\n//\t  case tea.KeyboardEnhancementsMsg:\n//\t    // We have basic key disambiguation support.\n//\t    // We can handle \"shift+enter\", \"ctrl+i\", etc.\n//\t\tm.keyboardEnhancements = msg\n//\t\tif msg.ReportEventTypes {\n//\t\t  // Even better! We can now handle key repeat and release events.\n//\t\t}\n//\t  case tea.KeyPressMsg:\n//\t    switch msg.String() {\n//\t    case \"shift+enter\":\n//\t      // Handle shift+enter\n//\t      // This would not be possible without keyboard enhancements.\n//\t    case \"ctrl+j\":\n//\t      // Handle ctrl+j\n//\t    }\n//\t  case tea.KeyReleaseMsg:\n//\t    // Whoa! A key was released!\n//\t  }\n//\n//\t  return m, nil\n//\t}\n//\n//\tfunc (m model) View() tea.View {\n//\t  v := tea.NewView(\"Press some keys!\")\n//\t  // Request reporting key repeat and release events.\n//\t  v.KeyboardEnhancements.ReportEventTypes = true\n//\t  return v\n//\t}\n//\t```\ntype KeyboardEnhancements struct {\n\t// ReportEventTypes requests the terminal to report key repeat and release\n\t// events.\n\t// If supported, your program will receive [KeyReleaseMsg]s and\n\t// [KeyPressMsg] with the [Key.IsRepeat] field set indicating that this is\n\t// a it's part of a key repeat sequence.\n\tReportEventTypes bool\n}\n\n// SetContent is a helper method to set the content of a [View] with a styled\n// string. A styled string represents text with styles and hyperlinks encoded\n// as ANSI escape codes.\n//\n// Example:\n//\n//\t```go\n//\tvar v tea.View\n//\tv.SetContent(\"Hello, World!\")\n//\t```\nfunc (v *View) SetContent(s string) {\n\tv.Content = s\n}\n\n// MouseMode represents the mouse mode of a view.\ntype MouseMode int\n\nconst (\n\t// MouseModeNone disables mouse events.\n\tMouseModeNone MouseMode = iota\n\n\t// MouseModeCellMotion enables mouse click, release, and wheel events.\n\t// Mouse movement events are also captured if a mouse button is pressed\n\t// (i.e., drag events). Cell motion mode is better supported than all\n\t// motion mode.\n\t//\n\t// This will try to enable the mouse in extended mode (SGR), if that is not\n\t// supported by the terminal it will fall back to normal mode (X10).\n\tMouseModeCellMotion\n\n\t// MouseModeAllMotion enables all mouse events, including click, release,\n\t// wheel, and movement events. You will receive mouse movement events even\n\t// when no buttons are pressed.\n\t//\n\t// This will try to enable the mouse in extended mode (SGR), if that is not\n\t// supported by the terminal it will fall back to normal mode (X10).\n\tMouseModeAllMotion\n)\n\n// ProgressBarState represents the state of the progress bar.\ntype ProgressBarState int\n\n// Progress bar states.\nconst (\n\tProgressBarNone ProgressBarState = iota\n\tProgressBarDefault\n\tProgressBarError\n\tProgressBarIndeterminate\n\tProgressBarWarning\n)\n\n// String return a human-readable value for the given [ProgressBarState].\nfunc (s ProgressBarState) String() string {\n\treturn [...]string{\n\t\t\"None\",\n\t\t\"Default\",\n\t\t\"Error\",\n\t\t\"Indeterminate\",\n\t\t\"Warning\",\n\t}[s]\n}\n\n// ProgressBar represents the terminal progress bar.\n//\n// Support depends on the terminal.\n//\n// See https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences\ntype ProgressBar struct {\n\t// State is the current state of the progress bar. It can be one of\n\t// [ProgressBarNone], [ProgressBarDefault], [ProgressBarError],\n\t// [ProgressBarIndeterminate], and [ProgressBarWarn].\n\tState ProgressBarState\n\t// Value is the current value of the progress bar. It should be between\n\t// 0 and 100.\n\tValue int\n}\n\n// NewProgressBar returns a new progress bar with the given state and value.\n// The value is ignored if the state is [ProgressBarNone] or\n// [ProgressBarIndeterminate].\nfunc NewProgressBar(state ProgressBarState, value int) *ProgressBar {\n\treturn &ProgressBar{\n\t\tState: state,\n\t\tValue: min(max(value, 0), 100),\n\t}\n}\n\n// Cursor represents a cursor on the terminal screen.\ntype Cursor struct {\n\t// Position is a [Position] that determines the cursor's position on the\n\t// screen relative to the top left corner of the frame.\n\tPosition\n\n\t// Color is a [color.Color] that determines the cursor's color.\n\tColor color.Color\n\n\t// Shape is a [CursorShape] that determines the cursor's shape.\n\tShape CursorShape\n\n\t// Blink is a boolean that determines whether the cursor should blink.\n\tBlink bool\n}\n\n// NewCursor returns a new cursor with the default settings and the given\n// position.\nfunc NewCursor(x, y int) *Cursor {\n\treturn &Cursor{\n\t\tPosition: Position{X: x, Y: y},\n\t\tColor:    nil,\n\t\tShape:    CursorBlock,\n\t\tBlink:    true,\n\t}\n}\n\n// Cmd is an IO operation that returns a message when it's complete. If it's\n// nil it's considered a no-op. Use it for things like HTTP requests, timers,\n// saving and loading from disk, and so on.\n//\n// Note that there's almost never a reason to use a command to send a message\n// to another part of your program. That can almost always be done in the\n// update function.\ntype Cmd func() Msg\n\n// channelHandlers manages the series of channels returned by various processes.\n// It allows us to wait for those processes to terminate before exiting the\n// program.\ntype channelHandlers struct {\n\thandlers []chan struct{}\n\tmu       sync.RWMutex\n}\n\n// Adds a channel to the list of handlers. We wait for all handlers to terminate\n// gracefully on shutdown.\nfunc (h *channelHandlers) add(ch chan struct{}) {\n\th.mu.Lock()\n\th.handlers = append(h.handlers, ch)\n\th.mu.Unlock()\n}\n\n// shutdown waits for all handlers to terminate.\nfunc (h *channelHandlers) shutdown() {\n\tvar wg sync.WaitGroup\n\n\th.mu.RLock()\n\tdefer h.mu.RUnlock()\n\n\tfor _, ch := range h.handlers {\n\t\twg.Add(1)\n\t\tgo func(ch chan struct{}) {\n\t\t\t<-ch\n\t\t\twg.Done()\n\t\t}(ch)\n\t}\n\twg.Wait()\n}\n\n// Program is a terminal user interface.\ntype Program struct {\n\t// disableInput disables all input. This is useful for programs that\n\t// don't need input, like a progress bar or a spinner.\n\tdisableInput bool\n\n\t// disableSignalHandler disables the signal handler that Bubble Tea sets up\n\t// for Programs. This is useful if you want to handle signals yourself.\n\tdisableSignalHandler bool\n\n\t// disableCatchPanics disables the panic catching that Bubble Tea does by\n\t// default. If panic catching is disabled the terminal will be in a fairly\n\t// unusable state after a panic because Bubble Tea will not perform its usual\n\t// cleanup on exit.\n\tdisableCatchPanics bool\n\n\t// filter supplies an event filter that will be invoked before Bubble Tea\n\t// processes a tea.Msg. The event filter can return any tea.Msg which will\n\t// then get handled by Bubble Tea instead of the original event. If the\n\t// event filter returns nil, the event will be ignored and Bubble Tea will\n\t// not process it.\n\t//\n\t// As an example, this could be used to prevent a program from shutting\n\t// down if there are unsaved changes.\n\t//\n\t// Example:\n\t//\n\t//\tfunc filter(m tea.Model, msg tea.Msg) tea.Msg {\n\t//\t\tif _, ok := msg.(tea.QuitMsg); !ok {\n\t//\t\t\treturn msg\n\t//\t\t}\n\t//\n\t//\t\tmodel := m.(myModel)\n\t//\t\tif model.hasChanges {\n\t//\t\t\treturn nil\n\t//\t\t}\n\t//\n\t//\t\treturn msg\n\t//\t}\n\t//\n\t//\tp := tea.NewProgram(Model{});\n\t//\tp.filter = filter\n\t//\n\t//\tif _,err := p.Run(context.Background()); err != nil {\n\t//\t\tfmt.Println(\"Error running program:\", err)\n\t//\t\tos.Exit(1)\n\t//\t}\n\tfilter func(Model, Msg) Msg\n\n\t// fps sets a custom maximum fps at which the renderer should run. If less\n\t// than 1, the default value of 60 will be used. If over 120, the fps will\n\t// be capped at 120.\n\tfps int\n\n\t// initialModel is the initial model for the program and is the only\n\t// required field when creating a new program.\n\tinitialModel Model\n\n\t// disableRenderer prevents the program from rendering to the terminal.\n\t// This can be useful for running daemon-like programs that don't require a\n\t// UI but still want to take advantage of Bubble Tea's architecture.\n\tdisableRenderer bool\n\n\t// handlers is a list of channels that need to be waited on before the\n\t// program can exit.\n\thandlers channelHandlers\n\n\t// ctx is the programs's internal context for signalling internal teardown.\n\t// It is built and derived from the externalCtx in NewProgram().\n\tctx    context.Context\n\tcancel context.CancelFunc\n\n\t// externalCtx is a context that was passed in via WithContext, otherwise defaulting\n\t// to ctx.Background() (in case it was not), the internal context is derived from it.\n\texternalCtx context.Context\n\n\tmsgs         chan Msg\n\terrs         chan error\n\tfinished     chan struct{}\n\tshutdownOnce sync.Once\n\n\tprofile *colorprofile.Profile // the terminal color profile\n\n\t// where to send output, this will usually be os.Stdout.\n\toutput    io.Writer\n\toutputBuf bytes.Buffer // buffer used to queue commands to be sent to the output\n\n\t// ttyOutput is null if output is not a TTY.\n\tttyOutput           term.File\n\tpreviousOutputState *term.State\n\trenderer            renderer\n\n\t// the environment variables for the program, defaults to os.Environ().\n\tenviron uv.Environ\n\t// the program's logger for debugging.\n\tlogger uv.Logger\n\n\t// where to read inputs from, this will usually be os.Stdin.\n\tinput io.Reader\n\t// ttyInput is null if input is not a TTY.\n\tttyInput              term.File\n\tpreviousTtyInputState *term.State\n\tcancelReader          cancelreader.CancelReader\n\tinputScanner          *uv.TerminalReader\n\treadLoopDone          chan struct{}\n\n\t// modes keeps track of terminal modes that have been enabled or disabled.\n\tignoreSignals uint32\n\n\t// ticker is the ticker that will be used to write to the renderer.\n\tticker *time.Ticker\n\n\t// once is used to stop the renderer.\n\tonce sync.Once\n\n\t// rendererDone is used to stop the renderer.\n\trendererDone chan struct{}\n\n\t// Initial window size. Mainly used for testing.\n\twidth, height int\n\n\t// whether to use hard tabs to optimize cursor movements\n\tuseHardTabs bool\n\t// whether to use backspace to optimize cursor movements\n\tuseBackspace bool\n\n\tmu sync.Mutex\n}\n\n// Quit is a special command that tells the Bubble Tea program to exit.\nfunc Quit() Msg {\n\treturn QuitMsg{}\n}\n\n// QuitMsg signals that the program should quit. You can send a [QuitMsg] with\n// [Quit].\ntype QuitMsg struct{}\n\n// Suspend is a special command that tells the Bubble Tea program to suspend.\nfunc Suspend() Msg {\n\treturn SuspendMsg{}\n}\n\n// SuspendMsg signals the program should suspend.\n// This usually happens when ctrl+z is pressed on common programs, but since\n// bubbletea puts the terminal in raw mode, we need to handle it in a\n// per-program basis.\n//\n// You can send this message with [Suspend()].\ntype SuspendMsg struct{}\n\n// ResumeMsg can be listen to do something once a program is resumed back\n// from a suspend state.\ntype ResumeMsg struct{}\n\n// InterruptMsg signals the program should suspend.\n// This usually happens when ctrl+c is pressed on common programs, but since\n// bubbletea puts the terminal in raw mode, we need to handle it in a\n// per-program basis.\n//\n// You can send this message with [Interrupt()].\ntype InterruptMsg struct{}\n\n// Interrupt is a special command that tells the Bubble Tea program to\n// interrupt.\nfunc Interrupt() Msg {\n\treturn InterruptMsg{}\n}\n\n// NewProgram creates a new [Program].\nfunc NewProgram(model Model, opts ...ProgramOption) *Program {\n\tp := &Program{\n\t\tinitialModel: model,\n\t\tmsgs:         make(chan Msg),\n\t\terrs:         make(chan error, 1),\n\t\trendererDone: make(chan struct{}),\n\t}\n\n\t// Apply all options to the program.\n\tfor _, opt := range opts {\n\t\topt(p)\n\t}\n\n\t// A context can be provided with a ProgramOption, but if none was provided\n\t// we'll use the default background context.\n\tif p.externalCtx == nil {\n\t\tp.externalCtx = context.Background()\n\t}\n\t// Initialize context and teardown channel.\n\tp.ctx, p.cancel = context.WithCancel(p.externalCtx)\n\n\t// if no output was set, set it to stdout\n\tif p.output == nil {\n\t\tp.output = os.Stdout\n\t}\n\n\t// if no environment was set, set it to os.Environ()\n\tif p.environ == nil {\n\t\tp.environ = os.Environ()\n\t}\n\n\tif p.fps < 1 {\n\t\tp.fps = defaultFPS\n\t} else if p.fps > maxFPS {\n\t\tp.fps = maxFPS\n\t}\n\n\ttracePath, traceOk := os.LookupEnv(\"TEA_TRACE\")\n\tif traceOk && len(tracePath) > 0 {\n\t\t// We have a trace filepath.\n\t\tif f, err := os.OpenFile(tracePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600); err == nil {\n\t\t\tp.logger = log.New(f, \"bubbletea: \", log.LstdFlags|log.Lshortfile)\n\t\t}\n\t}\n\n\treturn p\n}\n\nfunc (p *Program) handleSignals() chan struct{} {\n\tch := make(chan struct{})\n\n\t// Listen for SIGINT and SIGTERM.\n\t//\n\t// In most cases ^C will not send an interrupt because the terminal will be\n\t// in raw mode and ^C will be captured as a keystroke and sent along to\n\t// Program.Update as a KeyMsg. When input is not a TTY, however, ^C will be\n\t// caught here.\n\t//\n\t// SIGTERM is sent by unix utilities (like kill) to terminate a process.\n\tgo func() {\n\t\tsig := make(chan os.Signal, 1)\n\t\tsignal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)\n\t\tdefer func() {\n\t\t\tsignal.Stop(sig)\n\t\t\tclose(ch)\n\t\t}()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-p.ctx.Done():\n\t\t\t\treturn\n\n\t\t\tcase s := <-sig:\n\t\t\t\tif atomic.LoadUint32(&p.ignoreSignals) == 0 {\n\t\t\t\t\tswitch s {\n\t\t\t\t\tcase syscall.SIGINT:\n\t\t\t\t\t\tp.msgs <- InterruptMsg{}\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tp.msgs <- QuitMsg{}\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn ch\n}\n\n// handleResize handles terminal resize events.\nfunc (p *Program) handleResize() chan struct{} {\n\tch := make(chan struct{})\n\n\tif p.ttyOutput != nil {\n\t\t// Listen for window resizes.\n\t\tgo p.listenForResize(ch)\n\t} else {\n\t\tclose(ch)\n\t}\n\n\treturn ch\n}\n\n// handleCommands runs commands in a goroutine and sends the result to the\n// program's message channel.\nfunc (p *Program) handleCommands(cmds chan Cmd) chan struct{} {\n\tch := make(chan struct{})\n\n\tgo func() {\n\t\tdefer close(ch)\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-p.ctx.Done():\n\t\t\t\treturn\n\n\t\t\tcase cmd := <-cmds:\n\t\t\t\tif cmd == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Don't wait on these goroutines, otherwise the shutdown\n\t\t\t\t// latency would get too large as a Cmd can run for some time\n\t\t\t\t// (e.g. tick commands that sleep for half a second). It's not\n\t\t\t\t// possible to cancel them so we'll have to leak the goroutine\n\t\t\t\t// until Cmd returns.\n\t\t\t\tgo func() {\n\t\t\t\t\t// Recover from panics.\n\t\t\t\t\tif !p.disableCatchPanics {\n\t\t\t\t\t\tdefer func() {\n\t\t\t\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\t\t\t\tp.recoverFromPanic(r)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}()\n\t\t\t\t\t}\n\n\t\t\t\t\tmsg := cmd() // this can be long.\n\t\t\t\t\tp.Send(msg)\n\t\t\t\t}()\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn ch\n}\n\n// eventLoop is the central message loop. It receives and handles the default\n// Bubble Tea messages, update the model and triggers redraws.\nfunc (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {\n\tfor {\n\t\tselect {\n\t\tcase <-p.ctx.Done():\n\t\t\treturn model, nil\n\n\t\tcase err := <-p.errs:\n\t\t\treturn model, err\n\n\t\tcase msg := <-p.msgs:\n\t\t\tmsg = p.translateInputEvent(msg)\n\n\t\t\t// Filter messages.\n\t\t\tif p.filter != nil {\n\t\t\t\tmsg = p.filter(model, msg)\n\t\t\t}\n\t\t\tif msg == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Handle special internal messages.\n\t\t\tswitch msg := msg.(type) {\n\t\t\tcase QuitMsg:\n\t\t\t\treturn model, nil\n\n\t\t\tcase InterruptMsg:\n\t\t\t\treturn model, ErrInterrupted\n\n\t\t\tcase SuspendMsg:\n\t\t\t\tif suspendSupported {\n\t\t\t\t\tp.suspend()\n\t\t\t\t}\n\n\t\t\tcase CapabilityMsg:\n\t\t\t\tswitch msg.Content {\n\t\t\t\tcase \"RGB\", \"Tc\":\n\t\t\t\t\tif *p.profile != colorprofile.TrueColor {\n\t\t\t\t\t\ttc := colorprofile.TrueColor\n\t\t\t\t\t\tp.profile = &tc\n\t\t\t\t\t\tgo p.Send(ColorProfileMsg{*p.profile})\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase ModeReportMsg:\n\t\t\t\tswitch msg.Mode {\n\t\t\t\tcase ansi.ModeSynchronizedOutput:\n\t\t\t\t\tif msg.Value == ansi.ModeReset {\n\t\t\t\t\t\t// The terminal supports synchronized output and it's\n\t\t\t\t\t\t// currently disabled, so we can enable it on the renderer.\n\t\t\t\t\t\tp.renderer.setSyncdUpdates(true)\n\t\t\t\t\t}\n\t\t\t\tcase ansi.ModeUnicodeCore:\n\t\t\t\t\tif msg.Value == ansi.ModeReset || msg.Value == ansi.ModeSet || msg.Value == ansi.ModePermanentlySet {\n\t\t\t\t\t\tp.renderer.setWidthMethod(ansi.GraphemeWidth)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase MouseMsg:\n\t\t\t\tswitch msg.(type) {\n\t\t\t\tcase MouseClickMsg, MouseReleaseMsg, MouseWheelMsg, MouseMotionMsg:\n\t\t\t\t\t// Only send mouse messages to the renderer if they are an\n\t\t\t\t\t// actual mouse event.\n\t\t\t\t\tif cmd := p.renderer.onMouse(msg); cmd != nil {\n\t\t\t\t\t\tgo p.Send(cmd())\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase readClipboardMsg:\n\t\t\t\tp.execute(ansi.RequestSystemClipboard)\n\n\t\t\tcase setClipboardMsg:\n\t\t\t\tp.execute(ansi.SetSystemClipboard(string(msg)))\n\n\t\t\tcase readPrimaryClipboardMsg:\n\t\t\t\tp.execute(ansi.RequestPrimaryClipboard)\n\n\t\t\tcase setPrimaryClipboardMsg:\n\t\t\t\tp.execute(ansi.SetPrimaryClipboard(string(msg)))\n\n\t\t\tcase backgroundColorMsg:\n\t\t\t\tp.execute(ansi.RequestBackgroundColor)\n\n\t\t\tcase foregroundColorMsg:\n\t\t\t\tp.execute(ansi.RequestForegroundColor)\n\n\t\t\tcase cursorColorMsg:\n\t\t\t\tp.execute(ansi.RequestCursorColor)\n\n\t\t\tcase execMsg:\n\t\t\t\t// NB: this blocks.\n\t\t\t\tp.exec(msg.cmd, msg.fn)\n\n\t\t\tcase terminalVersion:\n\t\t\t\tp.execute(ansi.RequestNameVersion)\n\n\t\t\tcase requestCapabilityMsg:\n\t\t\t\tp.execute(ansi.RequestTermcap(string(msg)))\n\n\t\t\tcase BatchMsg:\n\t\t\t\tgo p.execBatchMsg(msg)\n\t\t\t\tcontinue\n\n\t\t\tcase sequenceMsg:\n\t\t\t\tgo p.execSequenceMsg(msg)\n\t\t\t\tcontinue\n\n\t\t\tcase WindowSizeMsg:\n\t\t\t\tp.renderer.resize(msg.Width, msg.Height)\n\n\t\t\tcase windowSizeMsg:\n\t\t\t\tgo p.checkResize()\n\n\t\t\tcase requestCursorPosMsg:\n\t\t\t\tp.execute(ansi.RequestCursorPositionReport)\n\n\t\t\tcase RawMsg:\n\t\t\t\tp.execute(fmt.Sprint(msg.Msg))\n\n\t\t\tcase printLineMessage:\n\t\t\t\tp.renderer.insertAbove(msg.messageBody) //nolint:errcheck,gosec\n\n\t\t\tcase clearScreenMsg:\n\t\t\t\tp.renderer.clearScreen()\n\n\t\t\tcase ColorProfileMsg:\n\t\t\t\tp.renderer.setColorProfile(msg.Profile)\n\t\t\t}\n\n\t\t\tvar cmd Cmd\n\t\t\tmodel, cmd = model.Update(msg) // run update\n\n\t\t\tselect {\n\t\t\tcase <-p.ctx.Done():\n\t\t\t\treturn model, nil\n\t\t\tcase cmds <- cmd: // process command (if any)\n\t\t\t}\n\n\t\t\tp.render(model) // render view\n\t\t}\n\t}\n}\n\n// render renders the given view to the renderer.\nfunc (p *Program) render(model Model) {\n\tif p.renderer != nil {\n\t\tp.renderer.render(model.View()) // send view to renderer\n\t}\n}\n\nfunc (p *Program) execSequenceMsg(msg sequenceMsg) {\n\tif !p.disableCatchPanics {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tp.recoverFromGoPanic(r)\n\t\t\t}\n\t\t}()\n\t}\n\n\t// Execute commands one at a time, in order.\n\tfor _, cmd := range msg {\n\t\tif cmd == nil {\n\t\t\tcontinue\n\t\t}\n\t\tmsg := cmd()\n\t\tswitch msg := msg.(type) {\n\t\tcase BatchMsg:\n\t\t\tp.execBatchMsg(msg)\n\t\tcase sequenceMsg:\n\t\t\tp.execSequenceMsg(msg)\n\t\tdefault:\n\t\t\tp.Send(msg)\n\t\t}\n\t}\n}\n\nfunc (p *Program) execBatchMsg(msg BatchMsg) {\n\tif !p.disableCatchPanics {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tp.recoverFromGoPanic(r)\n\t\t\t}\n\t\t}()\n\t}\n\n\t// Execute commands one at a time.\n\tvar wg sync.WaitGroup\n\tfor _, cmd := range msg {\n\t\tif cmd == nil {\n\t\t\tcontinue\n\t\t}\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\tif !p.disableCatchPanics {\n\t\t\t\tdefer func() {\n\t\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\t\tp.recoverFromGoPanic(r)\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\n\t\t\tmsg := cmd()\n\t\t\tswitch msg := msg.(type) {\n\t\t\tcase BatchMsg:\n\t\t\t\tp.execBatchMsg(msg)\n\t\t\tcase sequenceMsg:\n\t\t\t\tp.execSequenceMsg(msg)\n\t\t\tdefault:\n\t\t\t\tp.Send(msg)\n\t\t\t}\n\t\t}()\n\t}\n\n\twg.Wait() // wait for all commands from batch msg to finish\n}\n\n// shouldQuerySynchronizedOutput determines whether the terminal should be\n// queried for various capabilities.\n//\n// This function checks for terminals that are known to support mode 2026,\n// while excluding SSH sessions which may be unreliable, unless it's a\n// known-good terminal like Windows Terminal.\n//\n// The function returns true for:\n//   - Terminals without TERM_PROGRAM set and not in SSH sessions\n//   - Windows Terminal (WT_SESSION is set)\n//   - Terminals with TERM_PROGRAM set (except Apple Terminal) and not in SSH sessions\n//   - Specific terminal types: ghostty, wezterm, alacritty, kitty, rio\nfunc shouldQuerySynchronizedOutput(environ uv.Environ) bool {\n\ttermType := environ.Getenv(\"TERM\")\n\ttermProg, okTermProg := environ.LookupEnv(\"TERM_PROGRAM\")\n\t_, okSSHTTY := environ.LookupEnv(\"SSH_TTY\")\n\t_, okWTSession := environ.LookupEnv(\"WT_SESSION\")\n\n\treturn (!okTermProg && !okSSHTTY) ||\n\t\tokWTSession ||\n\t\t(okTermProg && !strings.Contains(termProg, \"Apple\") && !okSSHTTY) ||\n\t\tstrings.Contains(termType, \"ghostty\") ||\n\t\tstrings.Contains(termType, \"wezterm\") ||\n\t\tstrings.Contains(termType, \"alacritty\") ||\n\t\tstrings.Contains(termType, \"kitty\") ||\n\t\tstrings.Contains(termType, \"rio\")\n}\n\n// Run initializes the program and runs its event loops, blocking until it gets\n// terminated by either [Program.Quit], [Program.Kill], or its signal handler.\n// Returns the final model.\nfunc (p *Program) Run() (returnModel Model, returnErr error) {\n\tif p.initialModel == nil {\n\t\treturn nil, errors.New(\"bubbletea: InitialModel cannot be nil\")\n\t}\n\n\t// Initialize context and teardown channel.\n\tp.handlers = channelHandlers{}\n\tcmds := make(chan Cmd)\n\n\tp.finished = make(chan struct{})\n\tdefer func() {\n\t\tclose(p.finished)\n\t}()\n\n\tdefer p.cancel()\n\n\tif p.disableInput {\n\t\tp.input = nil\n\t} else if p.input == nil {\n\t\tp.input = os.Stdin\n\t\tif !term.IsTerminal(os.Stdin.Fd()) {\n\t\t\tttyIn, _, err := OpenTTY()\n\t\t\tif err != nil {\n\t\t\t\treturn p.initialModel, fmt.Errorf(\"bubbletea: error opening TTY: %w\", err)\n\t\t\t}\n\t\t\tp.input = ttyIn\n\t\t}\n\t}\n\n\t// Handle signals.\n\tif !p.disableSignalHandler {\n\t\tp.handlers.add(p.handleSignals())\n\t}\n\n\t// Recover from panics.\n\tif !p.disableCatchPanics {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\treturnErr = fmt.Errorf(\"%w: %w\", ErrProgramKilled, ErrProgramPanic)\n\t\t\t\tp.recoverFromPanic(r)\n\t\t\t}\n\t\t}()\n\t}\n\n\t// Check if output is a TTY before entering raw mode, hiding the cursor and\n\t// so on.\n\tif err := p.initTerminal(); err != nil {\n\t\treturn p.initialModel, err\n\t}\n\n\t// Get the initial window size.\n\twidth, height := p.width, p.height\n\tif p.ttyOutput != nil {\n\t\t// Set the initial size of the terminal.\n\t\tw, h, err := term.GetSize(p.ttyOutput.Fd())\n\t\tif err != nil {\n\t\t\treturn p.initialModel, fmt.Errorf(\"bubbletea: error getting terminal size: %w\", err)\n\t\t}\n\n\t\twidth, height = w, h\n\t}\n\n\tp.width, p.height = width, height\n\tresizeMsg := WindowSizeMsg{Width: p.width, Height: p.height}\n\n\tif p.renderer == nil {\n\t\tif p.disableRenderer {\n\t\t\tp.renderer = &nilRenderer{}\n\t\t} else {\n\t\t\t// If no renderer is set use the cursed one.\n\t\t\tr := newCursedRenderer(\n\t\t\t\tp.output,\n\t\t\t\tp.environ,\n\t\t\t\tp.width,\n\t\t\t\tp.height,\n\t\t\t)\n\t\t\tr.setLogger(p.logger)\n\t\t\t// XXX: This breaks many things especially when we want the output\n\t\t\t// to be compatible with terminals that are not necessary a TTY.\n\t\t\t// This was originally done to work around a Wish emulated-pty\n\t\t\t// issue where when a PTY session is detected, and we don't\n\t\t\t// allocate a real PTY, the terminal settings (Termios and WinCon)\n\t\t\t// don't change and the we end up working in cooked mode instead of\n\t\t\t// raw mode. See issue #1572.\n\t\t\tmapNl := runtime.GOOS != \"windows\" && p.ttyInput == nil\n\t\t\tr.setOptimizations(p.useHardTabs, p.useBackspace, mapNl)\n\t\t\tp.renderer = r\n\t\t}\n\t}\n\n\t// Get the color profile and send it to the program.\n\tif p.profile == nil {\n\t\tcp := colorprofile.Detect(p.output, p.environ)\n\t\tp.profile = &cp\n\t}\n\n\t// Set the color profile on the renderer and send it to the program.\n\tp.renderer.setColorProfile(*p.profile)\n\tgo p.Send(ColorProfileMsg{*p.profile})\n\n\t// Send the initial size to the program.\n\tgo p.Send(resizeMsg)\n\tp.renderer.resize(resizeMsg.Width, resizeMsg.Height)\n\n\t// Send the environment variables used by the program.\n\tgo p.Send(EnvMsg(p.environ))\n\n\t// Init the input reader and initial model.\n\tmodel := p.initialModel\n\tif p.input != nil {\n\t\tif err := p.initInputReader(false); err != nil {\n\t\t\treturn model, err\n\t\t}\n\t}\n\n\t// Start the renderer.\n\tp.startRenderer()\n\n\tif !p.disableRenderer && shouldQuerySynchronizedOutput(p.environ) {\n\t\t// Query for synchronized updates support (mode 2026) and unicode core\n\t\t// (mode 2027). If the terminal supports it, the renderer will enable\n\t\t// it once we get the response.\n\t\tp.execute(ansi.RequestModeSynchronizedOutput +\n\t\t\tansi.RequestModeUnicodeCore)\n\t}\n\n\t// Initialize the program.\n\tinitCmd := model.Init()\n\tif initCmd != nil {\n\t\tch := make(chan struct{})\n\t\tp.handlers.add(ch)\n\n\t\tgo func() {\n\t\t\tdefer close(ch)\n\n\t\t\tselect {\n\t\t\tcase cmds <- initCmd:\n\t\t\tcase <-p.ctx.Done():\n\t\t\t}\n\t\t}()\n\t}\n\n\t// Render the initial view.\n\tp.render(model)\n\n\t// Handle resize events.\n\tp.handlers.add(p.handleResize())\n\n\t// Process commands.\n\tp.handlers.add(p.handleCommands(cmds))\n\n\t// Run event loop, handle updates and draw.\n\tvar err error\n\tmodel, err = p.eventLoop(model, cmds)\n\n\tif err == nil && len(p.errs) > 0 {\n\t\terr = <-p.errs // Drain a leftover error in case eventLoop crashed.\n\t}\n\n\tkilled := p.externalCtx.Err() != nil || p.ctx.Err() != nil || err != nil\n\tif killed {\n\t\tif err == nil && p.externalCtx.Err() != nil {\n\t\t\t// Return also as context error the cancellation of an external context.\n\t\t\t// This is the context the user knows about and should be able to act on.\n\t\t\terr = fmt.Errorf(\"%w: %w\", ErrProgramKilled, p.externalCtx.Err())\n\t\t} else if err == nil && p.ctx.Err() != nil {\n\t\t\t// Return only that the program was killed (not the internal mechanism).\n\t\t\t// The user does not know or need to care about the internal program context.\n\t\t\terr = ErrProgramKilled\n\t\t} else {\n\t\t\t// Return that the program was killed and also the error that caused it.\n\t\t\terr = fmt.Errorf(\"%w: %w\", ErrProgramKilled, err)\n\t\t}\n\t} else {\n\t\t// Graceful shutdown of the program (not killed):\n\t\t// Ensure we rendered the final state of the model.\n\t\tp.render(model)\n\t}\n\n\t// Restore terminal state.\n\tp.shutdown(killed)\n\n\treturn model, err\n}\n\n// Send sends a message to the main update function, effectively allowing\n// messages to be injected from outside the program for interoperability\n// purposes.\n//\n// If the program hasn't started yet this will be a blocking operation.\n// If the program has already been terminated this will be a no-op, so it's safe\n// to send messages after the program has exited.\nfunc (p *Program) Send(msg Msg) {\n\tselect {\n\tcase <-p.ctx.Done():\n\tcase p.msgs <- msg:\n\t}\n}\n\n// Quit is a convenience function for quitting Bubble Tea programs. Use it\n// when you need to shut down a Bubble Tea program from the outside.\n//\n// If you wish to quit from within a Bubble Tea program use the Quit command.\n//\n// If the program is not running this will be a no-op, so it's safe to call\n// if the program is unstarted or has already exited.\nfunc (p *Program) Quit() {\n\tp.Send(Quit())\n}\n\n// Kill stops the program immediately and restores the former terminal state.\n// The final render that you would normally see when quitting will be skipped.\n// [program.Run] returns a [ErrProgramKilled] error.\nfunc (p *Program) Kill() {\n\tp.shutdown(true)\n}\n\n// Wait waits/blocks until the underlying Program finished shutting down.\nfunc (p *Program) Wait() {\n\t<-p.finished\n}\n\n// execute writes the given sequence to the program output.\nfunc (p *Program) execute(seq string) {\n\tp.mu.Lock()\n\t_, _ = p.outputBuf.WriteString(seq)\n\tp.mu.Unlock()\n}\n\n// flush flushes the output buffer to the program output.\nfunc (p *Program) flush() error {\n\tp.mu.Lock()\n\tdefer p.mu.Unlock()\n\n\tif p.outputBuf.Len() == 0 {\n\t\treturn nil\n\t}\n\tif p.logger != nil {\n\t\tp.logger.Printf(\"output: %q\", p.outputBuf.String())\n\t}\n\t_, err := p.output.Write(p.outputBuf.Bytes())\n\tp.outputBuf.Reset()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error writing to output: %w\", err)\n\t}\n\treturn nil\n}\n\n// shutdown performs operations to free up resources and restore the terminal\n// to its original state.\nfunc (p *Program) shutdown(kill bool) {\n\tp.shutdownOnce.Do(func() {\n\t\tp.cancel()\n\n\t\t// Wait for all handlers to finish.\n\t\tp.handlers.shutdown()\n\n\t\t// Check if the cancel reader has been setup before waiting and closing.\n\t\tif p.cancelReader != nil {\n\t\t\t// Wait for input loop to finish.\n\t\t\tif p.cancelReader.Cancel() {\n\t\t\t\tif !kill {\n\t\t\t\t\tp.waitForReadLoop()\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ = p.cancelReader.Close()\n\t\t}\n\n\t\tif p.renderer != nil {\n\t\t\tp.stopRenderer(kill)\n\t\t}\n\n\t\t_ = p.restoreTerminalState()\n\t})\n}\n\n// recoverFromPanic recovers from a panic, prints the stack trace, and restores\n// the terminal to a usable state.\nfunc (p *Program) recoverFromPanic(r interface{}) {\n\tselect {\n\tcase p.errs <- ErrProgramPanic:\n\tdefault:\n\t}\n\tp.shutdown(true) // Ok to call here, p.Run() cannot do it anymore.\n\t// We use \"\\r\\n\" to ensure the output is formatted even when restoring the\n\t// terminal does not work or when raw mode is still active.\n\trec := strings.ReplaceAll(fmt.Sprintf(\"%s\", r), \"\\n\", \"\\r\\n\")\n\tfmt.Fprintf(os.Stderr, \"Caught panic:\\r\\n\\r\\n%s\\r\\n\\r\\nRestoring terminal...\\r\\n\\r\\n\", rec)\n\tstack := strings.ReplaceAll(fmt.Sprintf(\"%s\\n\", debug.Stack()), \"\\n\", \"\\r\\n\")\n\tfmt.Fprint(os.Stderr, stack)\n\tif v, err := strconv.ParseBool(os.Getenv(\"TEA_DEBUG\")); err == nil && v {\n\t\tf, err := os.Create(fmt.Sprintf(\"bubbletea-panic-%d.log\", time.Now().Unix()))\n\t\tif err == nil {\n\t\t\tdefer f.Close()        //nolint:errcheck\n\t\t\tfmt.Fprintln(f, rec)   //nolint:errcheck\n\t\t\tfmt.Fprintln(f)        //nolint:errcheck\n\t\t\tfmt.Fprintln(f, stack) //nolint:errcheck\n\t\t}\n\t}\n}\n\n// recoverFromGoPanic recovers from a goroutine panic, prints a stack trace and\n// signals for the program to be killed and terminal restored to a usable state.\nfunc (p *Program) recoverFromGoPanic(r interface{}) {\n\tselect {\n\tcase p.errs <- ErrProgramPanic:\n\tdefault:\n\t}\n\tp.cancel()\n\t// We use \"\\r\\n\" to ensure the output is formatted even when restoring the\n\t// terminal does not work or when raw mode is still active.\n\trec := strings.ReplaceAll(fmt.Sprintf(\"%s\", r), \"\\n\", \"\\r\\n\")\n\tfmt.Fprintf(os.Stderr, \"Caught panic:\\r\\n\\r\\n%s\\r\\n\\r\\nRestoring terminal...\\r\\n\\r\\n\", rec)\n\tstack := strings.ReplaceAll(fmt.Sprintf(\"%s\\n\", debug.Stack()), \"\\n\", \"\\r\\n\")\n\tfmt.Fprint(os.Stderr, stack)\n\tif v, err := strconv.ParseBool(os.Getenv(\"TEA_DEBUG\")); err == nil && v {\n\t\tf, err := os.Create(fmt.Sprintf(\"bubbletea-panic-%d.log\", time.Now().Unix()))\n\t\tif err == nil {\n\t\t\tdefer f.Close()        //nolint:errcheck\n\t\t\tfmt.Fprintln(f, rec)   //nolint:errcheck\n\t\t\tfmt.Fprintln(f)        //nolint:errcheck\n\t\t\tfmt.Fprintln(f, stack) //nolint:errcheck\n\t\t}\n\t}\n}\n\n// ReleaseTerminal restores the original terminal state and cancels the input\n// reader. You can return control to the Program with RestoreTerminal.\nfunc (p *Program) ReleaseTerminal() error {\n\treturn p.releaseTerminal(false)\n}\n\nfunc (p *Program) releaseTerminal(reset bool) error {\n\tatomic.StoreUint32(&p.ignoreSignals, 1)\n\tif p.cancelReader != nil {\n\t\tp.cancelReader.Cancel()\n\t}\n\n\tp.waitForReadLoop()\n\n\tif p.renderer != nil {\n\t\tp.stopRenderer(false)\n\t\tif reset {\n\t\t\tp.renderer.reset()\n\t\t}\n\t}\n\n\treturn p.restoreTerminalState()\n}\n\n// RestoreTerminal reinitializes the Program's input reader, restores the\n// terminal to the former state when the program was running, and repaints.\n// Use it to reinitialize a Program after running ReleaseTerminal.\nfunc (p *Program) RestoreTerminal() error {\n\tatomic.StoreUint32(&p.ignoreSignals, 0)\n\n\tif err := p.initTerminal(); err != nil {\n\t\treturn err\n\t}\n\tif err := p.initInputReader(false); err != nil {\n\t\treturn err\n\t}\n\n\tp.startRenderer()\n\n\t// If the output is a terminal, it may have been resized while another\n\t// process was at the foreground, in which case we may not have received\n\t// SIGWINCH. Detect any size change now and propagate the new size as\n\t// needed.\n\tgo p.checkResize()\n\n\t// Flush queued commands.\n\treturn p.flush()\n}\n\n// Println prints above the Program. This output is unmanaged by the program\n// and will persist across renders by the Program.\n//\n// If the altscreen is active no output will be printed.\nfunc (p *Program) Println(args ...any) {\n\tp.msgs <- printLineMessage{\n\t\tmessageBody: fmt.Sprint(args...),\n\t}\n}\n\n// Printf prints above the Program. It takes a format template followed by\n// values similar to fmt.Printf. This output is unmanaged by the program and\n// will persist across renders by the Program.\n//\n// Unlike fmt.Printf (but similar to log.Printf) the message will be print on\n// its own line.\n//\n// If the altscreen is active no output will be printed.\nfunc (p *Program) Printf(template string, args ...any) {\n\tp.msgs <- printLineMessage{\n\t\tmessageBody: fmt.Sprintf(template, args...),\n\t}\n}\n\n// startRenderer starts the renderer.\nfunc (p *Program) startRenderer() {\n\tframerate := time.Second / time.Duration(p.fps)\n\tif p.ticker == nil {\n\t\tp.ticker = time.NewTicker(framerate)\n\t} else {\n\t\t// If the ticker already exists, it has been stopped and we need to\n\t\t// reset it.\n\t\tp.ticker.Reset(framerate)\n\t}\n\n\t// Since the renderer can be restarted after a stop, we need to reset\n\t// the done channel and its corresponding sync.Once.\n\tp.once = sync.Once{}\n\n\t// Start the renderer.\n\tp.renderer.start()\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-p.rendererDone:\n\t\t\t\tp.ticker.Stop()\n\t\t\t\treturn\n\n\t\t\tcase <-p.ticker.C:\n\t\t\t\t_ = p.flush()\n\t\t\t\t_ = p.renderer.flush(false)\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// stopRenderer stops the renderer.\n// If kill is true, the renderer will be stopped immediately without flushing\n// the last frame.\nfunc (p *Program) stopRenderer(kill bool) {\n\t// Stop the renderer before acquiring the mutex to avoid a deadlock.\n\tp.once.Do(func() {\n\t\tp.rendererDone <- struct{}{}\n\t})\n\n\tif !kill {\n\t\t// flush locks the mutex\n\t\t_ = p.renderer.flush(true)\n\t}\n\n\t_ = p.renderer.close()\n}\n"
  },
  {
    "path": "tea_test.go",
    "content": "package tea\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n)\n\ntype ctxImplodeMsg struct {\n\tcancel context.CancelFunc\n}\n\ntype incrementMsg struct{}\n\ntype panicMsg struct{}\n\nfunc panicCmd() Msg {\n\tpanic(\"testing goroutine panic behavior\")\n}\n\ntype testModel struct {\n\texecuted atomic.Value\n\tcounter  atomic.Value\n}\n\nfunc (m *testModel) Init() Cmd {\n\treturn nil\n}\n\nfunc (m *testModel) Update(msg Msg) (Model, Cmd) {\n\tswitch msg := msg.(type) {\n\tcase ctxImplodeMsg:\n\t\tmsg.cancel()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\tcase incrementMsg:\n\t\ti := m.counter.Load()\n\t\tif i == nil {\n\t\t\tm.counter.Store(1)\n\t\t} else {\n\t\t\tm.counter.Store(i.(int) + 1)\n\t\t}\n\n\tcase KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\":\n\t\t\treturn m, Quit\n\t\t}\n\n\tcase panicMsg:\n\t\tpanic(\"testing panic behavior\")\n\t}\n\n\treturn m, nil\n}\n\nfunc (m *testModel) View() View {\n\tm.executed.Store(true)\n\treturn NewView(\"success\")\n}\n\nfunc TestTeaModel(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\tin.Write([]byte(\"q\"))\n\n\tctx, cancel := context.WithTimeout(t.Context(), 3*time.Second)\n\tdefer cancel()\n\n\tp := NewProgram(&testModel{},\n\t\tWithContext(ctx),\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tif _, err := p.Run(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif buf.Len() == 0 {\n\t\tt.Fatal(\"no output\")\n\t}\n}\n\nfunc TestTeaQuit(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tif m.executed.Load() != nil {\n\t\t\t\tp.Quit()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tif _, err := p.Run(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestTeaWaitQuit(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tprogStarted := make(chan struct{})\n\twaitStarted := make(chan struct{})\n\terrChan := make(chan error, 1)\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\n\tgo func() {\n\t\t_, err := p.Run()\n\t\terrChan <- err\n\t}()\n\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tif m.executed.Load() != nil {\n\t\t\t\tclose(progStarted)\n\n\t\t\t\t<-waitStarted\n\t\t\t\ttime.Sleep(50 * time.Millisecond)\n\t\t\t\tp.Quit()\n\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t<-progStarted\n\n\tvar wg sync.WaitGroup\n\tfor range 5 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tp.Wait()\n\t\t\twg.Done()\n\t\t}()\n\t}\n\tclose(waitStarted)\n\twg.Wait()\n\n\terr := <-errChan\n\tif err != nil {\n\t\tt.Fatalf(\"Expected nil, got %v\", err)\n\t}\n}\n\nfunc TestTeaWaitKill(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tprogStarted := make(chan struct{})\n\twaitStarted := make(chan struct{})\n\terrChan := make(chan error, 1)\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\n\tgo func() {\n\t\t_, err := p.Run()\n\t\terrChan <- err\n\t}()\n\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tif m.executed.Load() != nil {\n\t\t\t\tclose(progStarted)\n\n\t\t\t\t<-waitStarted\n\t\t\t\ttime.Sleep(50 * time.Millisecond)\n\t\t\t\tp.Kill()\n\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t<-progStarted\n\n\tvar wg sync.WaitGroup\n\tfor range 5 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tp.Wait()\n\t\t\twg.Done()\n\t\t}()\n\t}\n\tclose(waitStarted)\n\twg.Wait()\n\n\terr := <-errChan\n\tif !errors.Is(err, ErrProgramKilled) {\n\t\tt.Fatalf(\"Expected %v, got %v\", ErrProgramKilled, err)\n\t}\n}\n\nfunc TestTeaWithFilter(t *testing.T) {\n\ttestTeaWithFilter(t, 0)\n\ttestTeaWithFilter(t, 1)\n\ttestTeaWithFilter(t, 2)\n}\n\nfunc testTeaWithFilter(t *testing.T, preventCount uint32) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tm := &testModel{}\n\tshutdowns := uint32(0)\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tp.filter = func(_ Model, msg Msg) Msg {\n\t\tif _, ok := msg.(QuitMsg); !ok {\n\t\t\treturn msg\n\t\t}\n\t\tif shutdowns < preventCount {\n\t\t\tatomic.AddUint32(&shutdowns, 1)\n\t\t\treturn nil\n\t\t}\n\t\treturn msg\n\t}\n\n\tgo func() {\n\t\tfor atomic.LoadUint32(&shutdowns) <= preventCount {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tp.Quit()\n\t\t}\n\t}()\n\n\tif _, err := p.Run(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif shutdowns != preventCount {\n\t\tt.Errorf(\"Expected %d prevented shutdowns, got %d\", preventCount, shutdowns)\n\t}\n}\n\nfunc TestTeaKill(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tif m.executed.Load() != nil {\n\t\t\t\tp.Kill()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t_, err := p.Run()\n\n\tif !errors.Is(err, ErrProgramKilled) {\n\t\tt.Fatalf(\"Expected %v, got %v\", ErrProgramKilled, err)\n\t}\n\n\tif errors.Is(err, context.Canceled) {\n\t\t// The end user should not know about the program's internal context state.\n\t\t// The program should only report external context cancellation as a context error.\n\t\tt.Fatalf(\"Internal context cancellation was reported as context error!\")\n\t}\n}\n\nfunc TestTeaContext(t *testing.T) {\n\tctx, cancel := context.WithCancel(t.Context())\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithContext(ctx),\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tif m.executed.Load() != nil {\n\t\t\t\tcancel()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t_, err := p.Run()\n\n\tif !errors.Is(err, ErrProgramKilled) {\n\t\tt.Fatalf(\"Expected %v, got %v\", ErrProgramKilled, err)\n\t}\n\n\tif !errors.Is(err, context.Canceled) {\n\t\t// The end user should know that their passed in context caused the kill.\n\t\tt.Fatalf(\"Expected %v, got %v\", context.Canceled, err)\n\t}\n}\n\nfunc TestTeaContextImplodeDeadlock(t *testing.T) {\n\tctx, cancel := context.WithCancel(t.Context())\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithContext(ctx),\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tif m.executed.Load() != nil {\n\t\t\t\tp.Send(ctxImplodeMsg{cancel: cancel})\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tif _, err := p.Run(); !errors.Is(err, ErrProgramKilled) {\n\t\tt.Fatalf(\"Expected %v, got %v\", ErrProgramKilled, err)\n\t}\n}\n\nfunc TestTeaContextBatchDeadlock(t *testing.T) {\n\tctx, cancel := context.WithCancel(t.Context())\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tinc := func() Msg {\n\t\tcancel()\n\t\treturn incrementMsg{}\n\t}\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithContext(ctx),\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tif m.executed.Load() != nil {\n\t\t\t\tbatch := make(BatchMsg, 100)\n\t\t\t\tfor i := range batch {\n\t\t\t\t\tbatch[i] = inc\n\t\t\t\t}\n\t\t\t\tp.Send(batch)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tif _, err := p.Run(); !errors.Is(err, ErrProgramKilled) {\n\t\tt.Fatalf(\"Expected %v, got %v\", ErrProgramKilled, err)\n\t}\n}\n\nfunc TestTeaBatchMsg(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tinc := func() Msg {\n\t\treturn incrementMsg{}\n\t}\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo func() {\n\t\tp.Send(BatchMsg{inc, inc})\n\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\ti := m.counter.Load()\n\t\t\tif i != nil && i.(int) >= 2 {\n\t\t\t\tp.Quit()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tif _, err := p.Run(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif m.counter.Load() != 2 {\n\t\tt.Fatalf(\"counter should be 2, got %d\", m.counter.Load())\n\t}\n}\n\nfunc TestTeaSequenceMsg(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tinc := func() Msg {\n\t\treturn incrementMsg{}\n\t}\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo p.Send(sequenceMsg{inc, inc, Quit})\n\n\tif _, err := p.Run(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif m.counter.Load() != 2 {\n\t\tt.Fatalf(\"counter should be 2, got %d\", m.counter.Load())\n\t}\n}\n\nfunc TestTeaSequenceMsgWithBatchMsg(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tinc := func() Msg {\n\t\treturn incrementMsg{}\n\t}\n\tbatch := func() Msg {\n\t\treturn BatchMsg{inc, inc}\n\t}\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo p.Send(sequenceMsg{batch, inc, Quit})\n\n\tif _, err := p.Run(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif m.counter.Load() != 3 {\n\t\tt.Fatalf(\"counter should be 3, got %d\", m.counter.Load())\n\t}\n}\n\nfunc TestTeaNestedSequenceMsg(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tinc := func() Msg {\n\t\treturn incrementMsg{}\n\t}\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo p.Send(sequenceMsg{inc, Sequence(inc, inc, Batch(inc, inc)), Quit})\n\n\tif _, err := p.Run(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif m.counter.Load() != 5 {\n\t\tt.Fatalf(\"counter should be 5, got %d\", m.counter.Load())\n\t}\n}\n\nfunc TestTeaSend(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\n\t// sending before the program is started is a blocking operation\n\tgo p.Send(Quit())\n\n\tif _, err := p.Run(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// sending a message after program has quit is a no-op\n\tp.Send(Quit())\n}\n\nfunc TestTeaNoRun(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tm := &testModel{}\n\tNewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n}\n\nfunc TestTeaPanic(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tif m.executed.Load() != nil {\n\t\t\t\tp.Send(panicMsg{})\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t_, err := p.Run()\n\n\tif !errors.Is(err, ErrProgramPanic) {\n\t\tt.Fatalf(\"Expected %v, got %v\", ErrProgramPanic, err)\n\t}\n\n\tif !errors.Is(err, ErrProgramKilled) {\n\t\tt.Fatalf(\"Expected %v, got %v\", ErrProgramKilled, err)\n\t}\n}\n\nfunc TestTeaGoroutinePanic(t *testing.T) {\n\tvar buf bytes.Buffer\n\tvar in bytes.Buffer\n\n\tm := &testModel{}\n\tp := NewProgram(m,\n\t\tWithInput(&in),\n\t\tWithOutput(&buf),\n\t)\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tif m.executed.Load() != nil {\n\t\t\t\tbatch := make(BatchMsg, 10)\n\t\t\t\tfor i := 0; i < len(batch); i += 2 {\n\t\t\t\t\tbatch[i] = Sequence(panicCmd)\n\t\t\t\t\tbatch[i+1] = Batch(panicCmd)\n\t\t\t\t}\n\t\t\t\tp.Send(batch)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t_, err := p.Run()\n\n\tif !errors.Is(err, ErrProgramPanic) {\n\t\tt.Fatalf(\"Expected %v, got %v\", ErrProgramPanic, err)\n\t}\n\n\tif !errors.Is(err, ErrProgramKilled) {\n\t\tt.Fatalf(\"Expected %v, got %v\", ErrProgramKilled, err)\n\t}\n}\n\ntype benchModel struct {\n\tt testing.TB\n}\n\nfunc (m benchModel) Init() Cmd {\n\treturn nil\n}\n\nfunc (m benchModel) Update(msg Msg) (Model, Cmd) {\n\tswitch msg := msg.(type) {\n\tcase KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\":\n\t\t\treturn m, Quit\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc (m benchModel) View() View {\n\tview := strings.Join([]string{\n\t\t\" \\x1b[38;5;63m╭─────────────────────────╮\\x1b[m\",\n\t\t\" \\x1b[38;5;63m│\\x1b[m\\x1b[25X\\x1b[28G\\x1b[38;5;63m│\\x1b[m\",\n\t\t\" \\x1b[38;5;63m│\\x1b[m    \\x1b[38;5;231mHello There!\\x1b[m    \\x1b[38;5;63m│\\x1b[m\",\n\t\t\" \\x1b[38;5;63m│\\x1b[m\\x1b[25X\\x1b[28G\\x1b[38;5;63m│\\x1b[m\",\n\t\t\" \\x1b[38;5;63m╰─────────────────────────╯\\x1b[m\",\n\t}, \"\\n\")\n\n\treturn NewView(view)\n}\n\nfunc BenchmarkTeaRun(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tvar buf bytes.Buffer\n\n\t\tm := benchModel{b}\n\t\tr, w := io.Pipe()\n\t\tp := NewProgram(m,\n\t\t\tWithInput(r),\n\t\t\tWithOutput(&buf),\n\t\t)\n\n\t\tgo func() {\n\t\t\tfor _, input := range \"abcdefghijklmnopq\" {\n\t\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t\tw.Write([]byte(string(input)))\n\t\t\t}\n\t\t}()\n\n\t\tif _, err := p.Run(); err != nil {\n\t\t\tb.Fatalf(\"Run failed: %v\", err)\n\t\t}\n\n\t\t_ = r.CloseWithError(io.EOF)\n\t}\n}\n"
  },
  {
    "path": "termcap.go",
    "content": "package tea\n\n// requestCapabilityMsg is an internal message that requests the terminal to\n// send its Termcap/Terminfo response.\ntype requestCapabilityMsg string\n\n// RequestCapability is a command that requests the terminal to send its\n// Termcap/Terminfo response for the given capability.\n//\n// Bubble Tea recognizes the following capabilities and will use them to\n// upgrade the program's color profile:\n//   - \"RGB\" Xterm direct color\n//   - \"Tc\" True color support\n//\n// Note: that some terminal's like Apple's Terminal.app do not support this and\n// will send the wrong response to the terminal breaking the program's output.\n//\n// When the Bubble Tea advertises a non-TrueColor profile, you can use this\n// command to query the terminal for its color capabilities. Example:\n//\n//\tswitch msg := msg.(type) {\n//\tcase tea.ColorProfileMsg:\n//\t  if msg.Profile != colorprofile.TrueColor {\n//\t    return m, tea.Batch(\n//\t      tea.RequestCapability(\"RGB\"),\n//\t      tea.RequestCapability(\"Tc\"),\n//\t    )\n//\t  }\n//\t}\nfunc RequestCapability(s string) Cmd {\n\treturn func() Msg {\n\t\treturn requestCapabilityMsg(s)\n\t}\n}\n\n// CapabilityMsg represents a Termcap/Terminfo response event. Termcap\n// responses are generated by the terminal in response to RequestTermcap\n// (XTGETTCAP) requests.\n//\n// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands\ntype CapabilityMsg struct {\n\tContent string\n}\n\n// String returns the capability content as a string.\nfunc (c CapabilityMsg) String() string {\n\treturn c.Content\n}\n"
  },
  {
    "path": "termios_bsd.go",
    "content": "//go:build dragonfly || freebsd\n// +build dragonfly freebsd\n\npackage tea\n\nimport (\n\t\"github.com/charmbracelet/x/term\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc (p *Program) checkOptimizedMovements(s *term.State) {\n\tp.useHardTabs = s.Oflag&unix.TABDLY == unix.TAB0\n}\n"
  },
  {
    "path": "termios_other.go",
    "content": "//go:build !windows && !darwin && !dragonfly && !freebsd && !linux && !solaris && !aix\n// +build !windows,!darwin,!dragonfly,!freebsd,!linux,!solaris,!aix\n\npackage tea\n\nimport \"github.com/charmbracelet/x/term\"\n\nfunc (*Program) checkOptimizedMovements(*term.State) {}\n"
  },
  {
    "path": "termios_unix.go",
    "content": "//go:build darwin || linux || solaris || aix\n// +build darwin linux solaris aix\n\npackage tea\n\nimport (\n\t\"github.com/charmbracelet/x/term\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc (p *Program) checkOptimizedMovements(s *term.State) {\n\tp.useHardTabs = s.Oflag&unix.TABDLY == unix.TAB0\n\tp.useBackspace = s.Lflag&unix.BSDLY == unix.BS0\n}\n"
  },
  {
    "path": "termios_windows.go",
    "content": "//go:build windows\n// +build windows\n\npackage tea\n\nimport \"github.com/charmbracelet/x/term\"\n\nfunc (p *Program) checkOptimizedMovements(*term.State) {\n\tp.useHardTabs = true\n\tp.useBackspace = true\n}\n"
  },
  {
    "path": "testdata/TestClearMsg/bg_fg_cur_color.golden",
    "content": "\u001b[?25l\u001b[?2004h\u001b[>4;2m\u001b[=1;1u\r\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b[?2026$p\u001b[?2027$p\u001b]10;?\u0007\u001b]11;?\u0007\u001b]12;?\u0007"
  },
  {
    "path": "testdata/TestClearMsg/clear_screen.golden",
    "content": "\u001b[?25l\r\u001b[?2004h\u001b[>4;2m\u001b[=1;1u\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "testdata/TestClearMsg/read_set_clipboard.golden",
    "content": "\u001b[?25l\u001b[?2004h\u001b[>4;2m\u001b[=1;1u\r\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b[?2026$p\u001b[?2027$p\u001b]52;c;?\u0007\u001b]52;c;c3VjY2Vzcw==\u0007"
  },
  {
    "path": "testdata/TestViewModel/altscreen.golden",
    "content": "\u001b[?25l\u001b[?2004h\u001b[>4;2m\u001b[=1;1u\r\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "testdata/TestViewModel/altscreen_autoexit.golden",
    "content": "\u001b[>4m\u001b[=0;1u\u001b[?1049h\u001b[?25l\u001b[?2004h\u001b[>4;2m\u001b[=1;1u\u001b[H\u001b[2Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[24d\u001b[?1049l\u001b[?25h\u001b[?2004l\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "testdata/TestViewModel/bg_set_color.golden",
    "content": "\u001b[?25l\u001b[?2004h\u001b[>4;2m\u001b[=1;1u\u001b]11;#ffffff\u0007\r\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b]111\u0007\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "testdata/TestViewModel/bp_stop_start.golden",
    "content": "\u001b[?25l\u001b[?2004h\u001b[>4;2m\u001b[=1;1u\r\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "testdata/TestViewModel/cursor_hide.golden",
    "content": "\u001b[?25l\u001b[?2004h\u001b[>4;2m\u001b[=1;1u\r\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "testdata/TestViewModel/cursor_hideshow.golden",
    "content": "\u001b[?2004h\u001b[>4;2m\u001b[=1;1u\u001b[1 q\r\u001b[Jsuccess\r\u001b[?25h\u001b[>4m\u001b[=0;1u\u001b[J\u001b[?2004l\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "testdata/TestViewModel/kitty_stop_startreleases.golden",
    "content": "\u001b[?25l\u001b[?2004h\u001b[>4;2m\u001b[=3;1u\r\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "testdata/TestViewModel/mouse_allmotion.golden",
    "content": "\u001b[?25l\u001b[?2004h\u001b[?1003h\u001b[?1006h\u001b[>4;2m\u001b[=1;1u\r\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b[?1002l\u001b[?1003l\u001b[?1006l\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "testdata/TestViewModel/mouse_cellmotion.golden",
    "content": "\u001b[?25l\u001b[?2004h\u001b[?1002h\u001b[?1006h\u001b[>4;2m\u001b[=1;1u\r\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b[?1002l\u001b[?1003l\u001b[?1006l\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "testdata/TestViewModel/mouse_disable.golden",
    "content": "\u001b[?25l\u001b[?2004h\u001b[>4;2m\u001b[=1;1u\r\u001b[Jsuccess\u001b[>4m\u001b[=0;1u\r\u001b[J\u001b[?25h\u001b[?2004l\u001b[?2026$p\u001b[?2027$p"
  },
  {
    "path": "tty.go",
    "content": "package tea\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\tuv \"github.com/charmbracelet/ultraviolet\"\n\t\"github.com/charmbracelet/x/term\"\n)\n\nfunc (p *Program) suspend() {\n\tif err := p.releaseTerminal(true); err != nil {\n\t\t// If we can't release input, abort.\n\t\treturn\n\t}\n\n\tsuspendProcess()\n\n\t_ = p.RestoreTerminal()\n\tgo p.Send(ResumeMsg{})\n}\n\nfunc (p *Program) initTerminal() error {\n\tif p.disableRenderer {\n\t\treturn nil\n\t}\n\treturn p.initInput()\n}\n\n// restoreTerminalState restores the terminal to the state prior to running the\n// Bubble Tea program.\nfunc (p *Program) restoreTerminalState() error {\n\t// Flush queued commands.\n\t_ = p.flush()\n\n\treturn p.restoreInput()\n}\n\n// restoreInput restores the tty input to its original state.\nfunc (p *Program) restoreInput() error {\n\tif p.ttyInput != nil && p.previousTtyInputState != nil {\n\t\tif err := term.Restore(p.ttyInput.Fd(), p.previousTtyInputState); err != nil {\n\t\t\treturn fmt.Errorf(\"bubbletea: error restoring console: %w\", err)\n\t\t}\n\t}\n\tif p.ttyOutput != nil && p.previousOutputState != nil {\n\t\tif err := term.Restore(p.ttyOutput.Fd(), p.previousOutputState); err != nil {\n\t\t\treturn fmt.Errorf(\"bubbletea: error restoring console: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// initInputReader (re)commences reading inputs.\nfunc (p *Program) initInputReader(cancel bool) error {\n\tif cancel && p.cancelReader != nil {\n\t\tp.cancelReader.Cancel()\n\t\tp.waitForReadLoop()\n\t}\n\n\tterm := p.environ.Getenv(\"TERM\")\n\n\t// Initialize the input reader.\n\t// This need to be done after the terminal has been initialized and set to\n\t// raw mode.\n\n\tvar err error\n\tp.cancelReader, err = uv.NewCancelReader(p.input)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"bubbletea: could not create cancelable reader: %w\", err)\n\t}\n\n\tdrv := uv.NewTerminalReader(p.cancelReader, term)\n\tdrv.SetLogger(p.logger)\n\tp.inputScanner = drv\n\tp.readLoopDone = make(chan struct{})\n\n\tgo p.readLoop()\n\n\treturn nil\n}\n\nfunc (p *Program) readLoop() {\n\tdefer close(p.readLoopDone)\n\n\tif err := p.inputScanner.StreamEvents(p.ctx, p.msgs); err != nil {\n\t\tselect {\n\t\tcase <-p.ctx.Done():\n\t\t\treturn\n\t\tcase p.errs <- err:\n\t\t}\n\t}\n}\n\n// waitForReadLoop waits for the cancelReader to finish its read loop.\nfunc (p *Program) waitForReadLoop() {\n\tselect {\n\tcase <-p.readLoopDone:\n\tcase <-time.After(500 * time.Millisecond): //nolint:mnd\n\t\t// The read loop hangs, which means the input\n\t\t// cancelReader's cancel function has returned true even\n\t\t// though it was not able to cancel the read.\n\t}\n}\n\n// checkResize detects the current size of the output and informs the program\n// via a WindowSizeMsg.\nfunc (p *Program) checkResize() {\n\tif p.ttyOutput == nil {\n\t\t// can't query window size\n\t\treturn\n\t}\n\n\tw, h, err := term.GetSize(p.ttyOutput.Fd())\n\tif err != nil {\n\t\tselect {\n\t\tcase <-p.ctx.Done():\n\t\tcase p.errs <- err:\n\t\t}\n\n\t\treturn\n\t}\n\n\tp.width, p.height = w, h\n\tp.Send(WindowSizeMsg{Width: w, Height: h})\n}\n\n// OpenTTY opens the running terminal's TTY for reading and writing.\nfunc OpenTTY() (*os.File, *os.File, error) {\n\tin, out, err := uv.OpenTTY()\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"bubbletea: could not open TTY: %w\", err)\n\t}\n\treturn in, out, nil\n}\n"
  },
  {
    "path": "tty_unix.go",
    "content": "//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || aix || zos\n// +build darwin dragonfly freebsd linux netbsd openbsd solaris aix zos\n\npackage tea\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/charmbracelet/x/term\"\n)\n\nfunc (p *Program) initInput() (err error) {\n\t// Check if input is a terminal\n\tif f, ok := p.input.(term.File); ok && term.IsTerminal(f.Fd()) {\n\t\tp.ttyInput = f\n\t\tp.previousTtyInputState, err = term.MakeRaw(p.ttyInput.Fd())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error entering raw mode: %w\", err)\n\t\t}\n\n\t\t// OPTIM: We can use hard tabs and backspaces to optimize cursor\n\t\t// movements. This is based on termios settings support and whether\n\t\t// they exist and enabled.\n\t\tp.checkOptimizedMovements(p.previousTtyInputState)\n\t}\n\n\tif f, ok := p.output.(term.File); ok && term.IsTerminal(f.Fd()) {\n\t\tp.ttyOutput = f\n\t}\n\n\treturn nil\n}\n\nconst suspendSupported = true\n\n// Send SIGTSTP to the entire process group.\nfunc suspendProcess() {\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, syscall.SIGCONT)\n\t_ = syscall.Kill(0, syscall.SIGTSTP)\n\t// blocks until a CONT happens...\n\t<-c\n}\n"
  },
  {
    "path": "tty_windows.go",
    "content": "//go:build windows\n// +build windows\n\npackage tea\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/charmbracelet/x/term\"\n\t\"golang.org/x/sys/windows\"\n)\n\nfunc (p *Program) initInput() (err error) {\n\t// Save stdin state and enable VT input\n\t// We also need to enable VT\n\t// input here.\n\tif f, ok := p.input.(term.File); ok && term.IsTerminal(f.Fd()) {\n\t\tp.ttyInput = f\n\t\tp.previousTtyInputState, err = term.MakeRaw(p.ttyInput.Fd())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error making terminal raw: %w\", err)\n\t\t}\n\n\t\t// Enable VT input\n\t\tvar mode uint32\n\t\tif err := windows.GetConsoleMode(windows.Handle(p.ttyInput.Fd()), &mode); err != nil {\n\t\t\treturn fmt.Errorf(\"error getting console mode: %w\", err)\n\t\t}\n\n\t\tif err := windows.SetConsoleMode(windows.Handle(p.ttyInput.Fd()), mode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err != nil {\n\t\t\treturn fmt.Errorf(\"error setting console mode: %w\", err)\n\t\t}\n\t}\n\n\t// Save output screen buffer state and enable VT processing.\n\tif f, ok := p.output.(term.File); ok && term.IsTerminal(f.Fd()) {\n\t\tp.ttyOutput = f\n\t\tp.previousOutputState, err = term.GetState(f.Fd())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error getting terminal state: %w\", err)\n\t\t}\n\n\t\tvar mode uint32\n\t\tif err := windows.GetConsoleMode(windows.Handle(p.ttyOutput.Fd()), &mode); err != nil {\n\t\t\treturn fmt.Errorf(\"error getting console mode: %w\", err)\n\t\t}\n\n\t\tif err := windows.SetConsoleMode(windows.Handle(p.ttyOutput.Fd()),\n\t\t\tmode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING|\n\t\t\t\twindows.DISABLE_NEWLINE_AUTO_RETURN); err != nil {\n\t\t\treturn fmt.Errorf(\"error setting console mode: %w\", err)\n\t\t}\n\n\t\t//nolint:godox\n\t\t// TODO: check if we can optimize cursor movements on Windows.\n\t\tp.checkOptimizedMovements(p.previousOutputState)\n\t}\n\n\treturn //nolint:nakedret\n}\n\nconst suspendSupported = false\n\nfunc suspendProcess() {}\n"
  },
  {
    "path": "tutorials/basics/README.md",
    "content": "# Bubble Tea Basics\n\nBubble Tea is based on the functional design paradigms of [The Elm\nArchitecture][elm], which happens to work nicely with Go. It’s a delightful way\nto build applications.\n\nThis tutorial assumes you have a working knowledge of Go.\n\nBy the way, the non-annotated source code for this program is available\n[on GitHub][tut-source].\n\n[elm]: https://guide.elm-lang.org/architecture/\n[tut-source]: https://github.com/charmbracelet/bubbletea/tree/master/tutorials/basics\n\n## Enough! Let’s get to it.\n\nFor this tutorial, we’re making a shopping list.\n\nTo start we’ll define our package and import some libraries. Our only external\nimport will be the Bubble Tea library, which we’ll call `tea` for short.\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"os\"\n\n    tea \"charm.land/bubbletea/v2\"\n)\n```\n\nBubble Tea programs are comprised of a **model** that describes the application\nstate and three simple methods on that model:\n\n- **Init**, a function that returns an initial command for the application to run.\n- **Update**, a function that handles incoming events and updates the model accordingly.\n- **View**, a function that renders the UI based on the data in the model.\n\n## The Model\n\nSo let’s start by defining our model which will store our application’s state.\nIt can be any type, but a `struct` usually makes the most sense.\n\n```go\ntype model struct {\n    choices  []string           // items on the to-do list\n    cursor   int                // which to-do list item our cursor is pointing at\n    selected map[int]struct{}   // which to-do items are selected\n}\n```\n\n## Initialization\n\nNext, we’ll define our application’s initial state. In this case, we’re defining\na function to return our initial model, however, we could just as easily define\nthe initial model as a variable elsewhere, too.\n\n```go\nfunc initialModel() model {\n\treturn model{\n\t\t// Our to-do list is a grocery list\n\t\tchoices:  []string{\"Buy carrots\", \"Buy celery\", \"Buy kohlrabi\"},\n\n\t\t// A map which indicates which choices are selected. We're using\n\t\t// the map like a mathematical set. The keys refer to the indexes\n\t\t// of the `choices` slice, above.\n\t\tselected: make(map[int]struct{}),\n\t}\n}\n```\n\nAfter that, we’ll define the `Init` method. `Init` can return a `Cmd` that\ncould perform some initial I/O. For now, we don’t need to do any I/O, so for\nthe command, we’ll just return `nil`, which translates to \"no command.\"\n\n```go\nfunc (m model) Init() tea.Cmd {\n    return nil\n}\n```\n\n## The Update Method\n\nNext up is the update method. The update function is called when “things\nhappen.” Its job is to look at what has happened and return an updated model in\nresponse. It can also return a `Cmd` to make more things happen, but for now\ndon’t worry about that part.\n\nIn our case, when a user presses the down arrow, `Update`’s job is to notice\nthat the down arrow was pressed and move the cursor accordingly (or not).\n\nThe “something happened” comes in the form of a `Msg`, which can be any type.\nMessages are the result of some I/O that took place, such as a keypress, timer\ntick, or a response from a server.\n\nWe usually figure out which type of `Msg` we received with a type switch, but\nyou could also use a type assertion.\n\nFor now, we’ll just deal with `tea.KeyPressMsg` messages, which are\nautomatically sent to the update function when keys are pressed.\n\n```go\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    switch msg := msg.(type) {\n\n    // Is it a key press?\n    case tea.KeyPressMsg:\n\n        // Cool, what was the actual key pressed?\n        switch msg.String() {\n\n        // These keys should exit the program.\n        case \"ctrl+c\", \"q\":\n            return m, tea.Quit\n\n        // The \"up\" and \"k\" keys move the cursor up\n        case \"up\", \"k\":\n            if m.cursor > 0 {\n                m.cursor--\n            }\n\n        // The \"down\" and \"j\" keys move the cursor down\n        case \"down\", \"j\":\n            if m.cursor < len(m.choices)-1 {\n                m.cursor++\n            }\n\n        // The enter key and the space bar toggle the selected state for the\n        // item that the cursor is pointing at.\n        case \"enter\", \"space\":\n            _, ok := m.selected[m.cursor]\n            if ok {\n                delete(m.selected, m.cursor)\n            } else {\n                m.selected[m.cursor] = struct{}{}\n            }\n        }\n    }\n\n    // Return the updated model to the Bubble Tea runtime for processing.\n    // Note that we're not returning a command.\n    return m, nil\n}\n```\n\nYou may have noticed that <kbd>ctrl+c</kbd> and <kbd>q</kbd> above return\na `tea.Quit` command with the model. That’s a special command which instructs\nthe Bubble Tea runtime to quit, exiting the program.\n\n## The View Method\n\nAt last, it’s time to render our UI. Of all the methods, the view is the\nsimplest. We look at the model in its current state and use it to return\na `tea.View`. The view declares our UI content and, optionally, terminal\nfeatures like full-window mode (aka, altscreen mode), mouse tracking, cursor\nposition, and more.\n\nBecause the view describes the entire UI of your application, you don’t have to\nworry about redrawing logic and stuff like that. Bubble Tea takes care of it\nfor you.\n\n```go\nfunc (m model) View() tea.View {\n    // The header\n    s := \"What should we buy at the market?\\n\\n\"\n\n    // Iterate over our choices\n    for i, choice := range m.choices {\n\n        // Is the cursor pointing at this choice?\n        cursor := \" \" // no cursor\n        if m.cursor == i {\n            cursor = \">\" // cursor!\n        }\n\n        // Is this choice selected?\n        checked := \" \" // not selected\n        if _, ok := m.selected[i]; ok {\n            checked = \"x\" // selected!\n        }\n\n        // Render the row\n        s += fmt.Sprintf(\"%s [%s] %s\\n\", cursor, checked, choice)\n    }\n\n    // The footer\n    s += \"\\nPress q to quit.\\n\"\n\n    // Send the UI for rendering\n    return tea.NewView(s)\n}\n```\n\n## All Together Now\n\nThe last step is to simply run our program. We pass our initial model to\n`tea.NewProgram` and let it rip:\n\n```go\nfunc main() {\n    p := tea.NewProgram(initialModel())\n    if _, err := p.Run(); err != nil {\n        fmt.Printf(\"Alas, there's been an error: %v\", err)\n        os.Exit(1)\n    }\n}\n```\n\n## What’s Next?\n\nThis tutorial covers the basics of building an interactive terminal UI, but\nin the real world you’ll also need to perform I/O. To learn about that have a\nlook at the [Command Tutorial][cmd]. It’s pretty simple.\n\nThere are also several [Bubble Tea examples][examples] available and, of course,\nthere are [Go Docs][docs].\n\n[cmd]: http://github.com/charmbracelet/bubbletea/tree/master/tutorials/commands/\n[examples]: http://github.com/charmbracelet/bubbletea/tree/master/examples\n[docs]: https://pkg.go.dev/charm.land/bubbletea/v2?tab=doc\n\n## Additional Resources\n\n- [Libraries we use with Bubble Tea](https://github.com/charmbracelet/bubbletea/#libraries-we-use-with-bubble-tea)\n- [Bubble Tea in the Wild](https://github.com/charmbracelet/bubbletea/#bubble-tea-in-the-wild)\n\n### Feedback\n\nWe’d love to hear your thoughts on this tutorial. Feel free to drop us a note!\n\n- [Twitter](https://twitter.com/charmcli)\n- [The Fediverse](https://mastodon.social/@charmcli)\n- [Discord](https://charm.sh/chat)\n\n---\n\nPart of [Charm](https://charm.sh).\n\n<a href=\"https://charm.sh/\"><img alt=\"The Charm logo\" src=\"https://stuff.charm.sh/charm-badge.jpg\" width=\"400\"></a>\n\nCharm热爱开源 • Charm loves open source\n"
  },
  {
    "path": "tutorials/basics/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\ntype model struct {\n\tcursor   int\n\tchoices  []string\n\tselected map[int]struct{}\n}\n\nfunc initialModel() model {\n\treturn model{\n\t\t// Our to-do list is a grocery list\n\t\tchoices: []string{\"Buy carrots\", \"Buy celery\", \"Buy kohlrabi\"},\n\n\t\t// A map which indicates which choices are selected. We're using\n\t\t// the  map like a mathematical set. The keys refer to the indexes\n\t\t// of the `choices` slice, above.\n\t\tselected: make(map[int]struct{}),\n\t}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"up\", \"k\":\n\t\t\tif m.cursor > 0 {\n\t\t\t\tm.cursor--\n\t\t\t}\n\t\tcase \"down\", \"j\":\n\t\t\tif m.cursor < len(m.choices)-1 {\n\t\t\t\tm.cursor++\n\t\t\t}\n\t\tcase \"enter\", \"space\":\n\t\t\t_, ok := m.selected[m.cursor]\n\t\t\tif ok {\n\t\t\t\tdelete(m.selected, m.cursor)\n\t\t\t} else {\n\t\t\t\tm.selected[m.cursor] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\ts := \"What should we buy at the market?\\n\\n\"\n\n\tfor i, choice := range m.choices {\n\t\tcursor := \" \"\n\t\tif m.cursor == i {\n\t\t\tcursor = \">\"\n\t\t}\n\n\t\tchecked := \" \"\n\t\tif _, ok := m.selected[i]; ok {\n\t\t\tchecked = \"x\"\n\t\t}\n\n\t\ts += fmt.Sprintf(\"%s [%s] %s\\n\", cursor, checked, choice)\n\t}\n\n\ts += \"\\nPress q to quit.\\n\"\n\n\tv := tea.NewView(s)\n\tv.WindowTitle = \"Grocery List\"\n\n\treturn v\n}\n\nfunc main() {\n\tp := tea.NewProgram(initialModel())\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Printf(\"Alas, there's been an error: %v\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "tutorials/commands/README.md",
    "content": "# Commands in Bubble Tea\n\nThis is the second tutorial for Bubble Tea covering commands, which deal with\nI/O. The tutorial assumes you have a working knowledge of Go and a decent\nunderstanding of [the first tutorial][basics].\n\nYou can find the non-annotated version of this program [on GitHub][source].\n\n[basics]: https://github.com/charmbracelet/bubbletea/tree/master/tutorials/basics\n[source]: https://github.com/charmbracelet/bubbletea/blob/master/tutorials/commands/main.go\n\n## Let’s Go!\n\nFor this tutorial we’re building a very simple program that makes an HTTP\nrequest to a server and reports the status code of the response.\n\nWe’ll import a few necessary packages and put the URL we’re going to check in\na `const`.\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"net/http\"\n    \"os\"\n    \"time\"\n\n    tea \"charm.land/bubbletea/v2\"\n)\n\nconst url = \"https://charm.sh/\"\n```\n\n## The Model\n\nNext we’ll define our model. The only things we need to store are the status\ncode of the HTTP response and a possible error.\n\n```go\ntype model struct {\n    status int\n    err    error\n}\n```\n\n## Commands and Messages\n\n`Cmd`s are functions that perform some I/O and then return a `Msg`. Checking the\ntime, ticking a timer, reading from the disk, and network stuff are all I/O and\nshould be run through commands. That might sound harsh, but it will keep your\nBubble Tea program straightforward and simple.\n\nAnyway, let’s write a `Cmd` that makes a request to a server and returns the\nresult as a `Msg`.\n\n```go\nfunc checkServer() tea.Msg {\n\n    // Create an HTTP client and make a GET request.\n    c := &http.Client{Timeout: 10 * time.Second}\n    res, err := c.Get(url)\n\n    if err != nil {\n        // There was an error making our request. Wrap the error we received\n        // in a message and return it.\n        return errMsg{err}\n    }\n    // We received a response from the server. Return the HTTP status code\n    // as a message.\n    return statusMsg(res.StatusCode)\n}\n\ntype statusMsg int\n\ntype errMsg struct{ err error }\n\n// For messages that contain errors it's often handy to also implement the\n// error interface on the message.\nfunc (e errMsg) Error() string { return e.err.Error() }\n```\n\nAnd notice that we’ve defined two new `Msg` types. They can be any type, even\nan empty struct. We’ll come back to them later in our update function.\nFirst, let’s write our initialization function.\n\n## The Initialization Method\n\nThe initialization method is very simple: we return the `Cmd` we made earlier.\nNote that we don’t call the function; the Bubble Tea runtime will do that when\nthe time is right.\n\n```go\nfunc (m model) Init() tea.Cmd {\n    return checkServer\n}\n```\n\n## The Update Method\n\nInternally, `Cmd`s run asynchronously in a goroutine. The `Msg` they return is\ncollected and sent to our update function for handling. Remember those message\ntypes we made earlier when we were making the `checkServer` command? We handle\nthem here. This makes dealing with many asynchronous operations very easy.\n\n```go\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    switch msg := msg.(type) {\n\n    case statusMsg:\n        // The server returned a status message. Save it to our model. Also\n        // tell the Bubble Tea runtime we want to exit because we have nothing\n        // else to do. We'll still be able to render a final view with our\n        // status message.\n        m.status = int(msg)\n        return m, tea.Quit\n\n    case errMsg:\n        // There was an error. Note it in the model. And tell the runtime\n        // we're done and want to quit.\n        m.err = msg\n        return m, tea.Quit\n\n    case tea.KeyPressMsg:\n        // Ctrl+c exits. Even with short running programs it's good to have\n        // a quit key, just in case your logic is off. Users will be very\n        // annoyed if they can't exit.\n\t\tif msg.Mod == tea.ModCtrl && msg.Code == 'c' {\n\t\t\treturn m, tea.Quit\n\t\t}\n    }\n\n    // If we happen to get any other messages, don't do anything.\n    return m, nil\n}\n```\n\n## The View Function\n\nOur view is very straightforward. We look at the current model and build a\n`tea.View` accordingly:\n\n```go\nfunc (m model) View() tea.View {\n    // If there's an error, print it out and don't do anything else.\n    if m.err != nil {\n        return tea.NewView(fmt.Sprintf(\"\\nWe had some trouble: %v\\n\\n\", m.err))\n    }\n\n    // Tell the user we're doing something.\n    s := fmt.Sprintf(\"Checking %s ... \", url)\n\n    // When the server responds with a status, add it to the current line.\n    if m.status > 0 {\n        s += fmt.Sprintf(\"%d %s!\", m.status, http.StatusText(m.status))\n    }\n\n    // Send off whatever we came up with above for rendering.\n    return tea.NewView(\"\\n\" + s + \"\\n\\n\")\n}\n```\n\n## Run the program\n\nThe only thing left to do is run the program, so let’s do that! Our initial\nmodel doesn’t need any data at all in this case, we just initialize it with\na `model` struct with default values.\n\n```go\nfunc main() {\n    if _, err := tea.NewProgram(model{}).Run(); err != nil {\n        fmt.Printf(\"Uh oh, there was an error: %v\\n\", err)\n        os.Exit(1)\n    }\n}\n```\n\nAnd that’s that. There’s one more thing that is helpful to know about\n`Cmd`s, though.\n\n## One More Thing About Commands\n\n`Cmd`s are defined in Bubble Tea as `type Cmd func() Msg`. So they’re just\nfunctions that don’t take any arguments and return a `Msg`, which can be\nany type. If you need to pass arguments to a command, you just make a function\nthat returns a command. For example:\n\n```go\nfunc cmdWithArg(id int) tea.Cmd {\n    return func() tea.Msg {\n        return someMsg{id: id}\n    }\n}\n```\n\nA more real-world example looks like:\n\n```go\nfunc checkSomeUrl(url string) tea.Cmd {\n    return func() tea.Msg {\n        c := &http.Client{Timeout: 10 * time.Second}\n        res, err := c.Get(url)\n        if err != nil {\n            return errMsg{err}\n        }\n        return statusMsg(res.StatusCode)\n    }\n}\n```\n\nAnyway, just make sure you do as much stuff as you can in the innermost\nfunction, because that’s the one that runs asynchronously.\n\n## Now What?\n\nAfter doing this tutorial and [the previous one][basics] you should be ready to\nbuild a Bubble Tea program of your own. We also recommend that you look at the\nBubble Tea [example programs][examples] as well as [Bubbles][bubbles],\na component library for Bubble Tea.\n\nAnd, of course, check out the [Go Docs][docs].\n\n[bubbles]: https://github.com/charmbracelet/bubbles\n[docs]: https://pkg.go.dev/charm.land/bubbletea/v2?tab=doc\n[examples]: https://github.com/charmbracelet/bubbletea/tree/master/examples\n\n## Additional Resources\n\n- [Libraries we use with Bubble Tea](https://github.com/charmbracelet/bubbletea/#libraries-we-use-with-bubble-tea)\n- [Bubble Tea in the Wild](https://github.com/charmbracelet/bubbletea/#bubble-tea-in-the-wild)\n\n### Feedback\n\nWe’d love to hear your thoughts on this tutorial. Feel free to drop us a note!\n\n- [Twitter](https://twitter.com/charmcli)\n- [The Fediverse](https://mastodon.social/@charmcli)\n- [Discord](https://charm.sh/chat)\n\n---\n\nPart of [Charm](https://charm.sh).\n\n<a href=\"https://charm.sh/\"><img alt=\"The Charm logo\" src=\"https://stuff.charm.sh/charm-badge.jpg\" width=\"400\"></a>\n\nCharm热爱开源 • Charm loves open source\n"
  },
  {
    "path": "tutorials/commands/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nconst url = \"https://charm.sh/\"\n\ntype model struct {\n\tstatus int\n\terr    error\n}\n\nfunc checkServer() tea.Msg {\n\tc := &http.Client{Timeout: 10 * time.Second}\n\tres, err := c.Get(url)\n\tif err != nil {\n\t\treturn errMsg{err}\n\t}\n\tdefer res.Body.Close() // nolint:errcheck\n\n\treturn statusMsg(res.StatusCode)\n}\n\ntype statusMsg int\n\ntype errMsg struct{ err error }\n\n// For messages that contain errors it's often handy to also implement the\n// error interface on the message.\nfunc (e errMsg) Error() string { return e.err.Error() }\n\nfunc (m model) Init() tea.Cmd {\n\treturn checkServer\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase statusMsg:\n\t\tm.status = int(msg)\n\t\treturn m, tea.Quit\n\n\tcase errMsg:\n\t\tm.err = msg\n\t\treturn m, tea.Quit\n\n\tcase tea.KeyPressMsg:\n\t\tif msg.Mod == tea.ModCtrl && msg.Code == 'c' {\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tif m.err != nil {\n\t\treturn tea.NewView(fmt.Sprintf(\"\\nWe had some trouble: %v\\n\\n\", m.err))\n\t}\n\n\ts := fmt.Sprintf(\"Checking %s ... \", url)\n\tif m.status > 0 {\n\t\ts += fmt.Sprintf(\"%d %s!\", m.status, http.StatusText(m.status))\n\t}\n\treturn tea.NewView(\"\\n\" + s + \"\\n\\n\")\n}\n\nfunc main() {\n\tif _, err := tea.NewProgram(model{}).Run(); err != nil {\n\t\tfmt.Printf(\"Uh oh, there was an error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "tutorials/go.mod",
    "content": "module tutorial\n\ngo 1.24.2\n\ntoolchain go1.24.10\n\nrequire charm.land/bubbletea/v2 v2.0.0-00010101000000-000000000000\n\nrequire (\n\tgithub.com/charmbracelet/colorprofile v0.4.1 // indirect\n\tgithub.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect\n\tgithub.com/charmbracelet/x/ansi v0.11.6 // indirect\n\tgithub.com/charmbracelet/x/term v0.2.2 // indirect\n\tgithub.com/charmbracelet/x/termios v0.1.1 // indirect\n\tgithub.com/charmbracelet/x/windows v0.2.2 // indirect\n\tgithub.com/clipperhouse/displaywidth v0.9.0 // indirect\n\tgithub.com/clipperhouse/stringish v0.1.1 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.5.0 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.3.0 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.19 // indirect\n\tgithub.com/muesli/cancelreader v0.2.2 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgolang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.40.0 // indirect\n)\n\nreplace charm.land/bubbletea/v2 => ../\n"
  },
  {
    "path": "tutorials/go.sum",
    "content": "github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=\ngithub.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=\ngithub.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=\ngithub.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=\ngithub.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=\ngithub.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=\ngithub.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=\ngithub.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=\ngithub.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=\ngithub.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=\ngithub.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=\ngithub.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=\ngithub.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=\ngithub.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=\ngithub.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=\ngithub.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=\ngithub.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=\ngithub.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=\ngithub.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=\ngithub.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=\ngithub.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=\ngithub.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=\ngithub.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngolang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=\ngolang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=\ngolang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\n"
  },
  {
    "path": "xterm.go",
    "content": "package tea\n\n// TerminalVersionMsg is a message that represents the terminal version.\ntype TerminalVersionMsg struct {\n\tName string\n}\n\n// String returns the terminal name as a string.\nfunc (t TerminalVersionMsg) String() string {\n\treturn t.Name\n}\n\n// terminalVersion is an internal message that queries the terminal for its\n// version using XTVERSION.\ntype terminalVersion struct{}\n\n// RequestTerminalVersion is a command that queries the terminal for its\n// version using XTVERSION. Note that some terminals may not support this\n// command.\nfunc RequestTerminalVersion() Msg {\n\treturn terminalVersion{}\n}\n"
  }
]