[
  {
    "path": ".editorconfig",
    "content": "# https://editorconfig.org/\n\nroot = true\n\n[*]\ncharset = utf-8\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nindent_style = space\nindent_size = 2\n\n[*.go]\nindent_style = tab\nindent_size = 8\n\n[*.golden]\ninsert_final_newline = false\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.golden linguist-generated=true -text\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "*  @meowgorithm @aymanbagabas\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 - Locale [e.g. en_US.UTF-8, zh_CN.UTF-8, etc.]\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: \"/example\"\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\n\non:\n  push:\n    branches:\n      - \"master\"\n  pull_request:\n\njobs:\n  build:\n    uses: charmbracelet/meta/.github/workflows/build.yml@main\n    secrets:\n      gh_pat: ${{ secrets.PERSONAL_ACCESS_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/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      - run: |\n          git config --global url.\"https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/charmbracelet\".insteadOf \"https://github.com/charmbracelet\"\n          git config --global url.\"https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/charmcli\".insteadOf \"https://github.com/charmcli\"\n\n      - name: Coverage\n        env:\n          COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          go test -race -covermode atomic -coverprofile=profile.cov ./...\n          go install github.com/mattn/goveralls@latest\n          goveralls -coverprofile=profile.cov -service=github\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/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": "ssh_example_ed25519*\n/tmp\n**/.crush/**\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  tests: false\nlinters:\n  enable:\n    - bodyclose\n    - exhaustive\n    - goconst\n    - godot\n    - gomoddirectives\n    - goprintffuncname\n    - gosec\n    - misspell\n    - nakedret\n    - nestif\n    - nilerr\n    - noctx\n    - nolintlint\n    - prealloc\n    - revive\n    - rowserrcheck\n    - sqlclosecheck\n    - tparallel\n    - unconvert\n    - unparam\n    - whitespace\n    - wrapcheck\n  exclusions:\n    rules:\n      - text: '(slog|log)\\.\\w+'\n        linters:\n          - noctx\n    generated: lax\n    presets:\n      - common-false-positives\n  settings:\n    exhaustive:\n      default-signifies-exhaustive: true\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\nformatters:\n  enable:\n    - gofumpt\n    - goimports\n  exclusions:\n    generated: lax\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "includes:\n  - from_url:\n      url: charmbracelet/meta/main/goreleaser-lib.yaml\n# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021-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": "# Lip Gloss\n\n<p >\n    <img src=\"https://github.com/user-attachments/assets/d13bbe1a-d2b2-4d18-9302-419a0bc3f579\" width=\"350\"><br>\n    <a href=\"https://github.com/charmbracelet/lipgloss/releases\"><img src=\"https://img.shields.io/github/release/charmbracelet/lipgloss.svg\" alt=\"Latest Release\"></a>\n    <a href=\"https://pkg.go.dev/charm.land/lipgloss/v2?tab=doc\"><img src=\"https://godoc.org/github.com/golang/gddo?status.svg\" alt=\"GoDoc\"></a>\n    <a href=\"https://github.com/charmbracelet/lipgloss/actions\"><img src=\"https://github.com/charmbracelet/lipgloss/workflows/build/badge.svg\" alt=\"Build Status\"></a>\n</p>\n\n<p>Style definitions for nice terminal layouts. Built with TUIs in mind.</p>\n\n![Lip Gloss example](https://github.com/user-attachments/assets/92560e60-d70e-4ce0-b39e-a60bb933356b)\n\nLip Gloss takes an expressive, declarative approach to terminal rendering.\nUsers familiar with CSS will feel at home with Lip Gloss.\n\n```go\nimport \"charm.land/lipgloss/v2\"\n\nvar style = lipgloss.NewStyle().\n    Bold(true).\n    Foreground(lipgloss.Color(\"#FAFAFA\")).\n    Background(lipgloss.Color(\"#7D56F4\")).\n    PaddingTop(2).\n    PaddingLeft(4).\n    Width(22)\n\nlipgloss.Println(style.Render(\"Hello, kitty\"))\n```\n\n## Installation\n\n```bash\ngo get charm.land/lipgloss/v2\n```\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## Colors\n\nLip Gloss supports the following color profiles:\n\n### ANSI 16 colors (4-bit)\n\n```go\nlipgloss.Color(\"5\")  // magenta\nlipgloss.Color(\"9\")  // red\nlipgloss.Color(\"12\") // light blue\n```\n\n### ANSI 256 Colors (8-bit)\n\n```go\nlipgloss.Color(\"86\")  // aqua\nlipgloss.Color(\"201\") // hot pink\nlipgloss.Color(\"202\") // orange\n```\n\n### True Color (16,777,216 colors; 24-bit)\n\n```go\nlipgloss.Color(\"#0000FF\") // good ol' 100% blue\nlipgloss.Color(\"#04B575\") // a green\nlipgloss.Color(\"#3C3C3C\") // a dark gray\n```\n\n...as well as a 1-bit ASCII profile, which is black and white only.\n\nThere are also named constants for the 16 standard ANSI colors:\n\n```go\nlipgloss.Black\nlipgloss.Red\nlipgloss.Green\nlipgloss.Yellow\nlipgloss.Blue\nlipgloss.Magenta\nlipgloss.Cyan\nlipgloss.White\nlipgloss.BrightBlack\nlipgloss.BrightRed\nlipgloss.BrightGreen\nlipgloss.BrightYellow\nlipgloss.BrightBlue\nlipgloss.BrightMagenta\nlipgloss.BrightCyan\nlipgloss.BrightWhite\n```\n\n### Automatically Downsampling Colors\n\nSome users don't have Truecolor terminals. Other times, output might not\nsupport color at all (for example, in logs). Lip Gloss was designed to handle\nthis gracefully by automatically downsampling colors to the best available\nprofile.\n\nIf you're using Lip Gloss with Bubble Tea, there’s nothing to do. If you're\nusing Lip Gloss standalone, just use `lipgloss.Println` or `lipgloss.Sprint`\n(and their variants).\n\nFor more, see [advanced color usage](#advanced-color-usage).\n\n### Color Utilities\n\nLip Gloss ships with a handful of handy tools for working with colors:\n\n```go\nc := lipgloss.Color(\"#EB4268\")      // Sriracha sauce color\ndark := lipgloss.Darken(c, 0.5)     // dark Sriracha sauce\nlight := lipgloss.Lighten(c, 0.35)  // light Sriracha sauce\ngreen := lipgloss.Complementary(c)  // greenish Sriracha sauce\nwithAlpha := lipgloss.Alpha(c, 0.2) // watered down Sriracha sauce\n```\n\n### Advanced Color Tooling\n\nLip Gloss also supports color blending, automatically choosing light or dark\nvariants of colors at runtime, and a lot more. For details, see [Advanced Color\nUsage](#advanced-color-usage) and [the docs][docs].\n\n## Inline Formatting\n\nLip Gloss supports the usual ANSI text formatting options:\n\n```go\nvar style = lipgloss.NewStyle().\n    Bold(true).\n    Italic(true).\n    Faint(true).\n    Blink(true).\n    Strikethrough(true).\n    Underline(true).\n    Reverse(true)\n```\n\n### Underline Styles\n\nBeyond simple on/off, underlines support multiple styles and custom colors:\n\n```go\ns := lipgloss.NewStyle().\n    UnderlineStyle(lipgloss.UnderlineCurly).\n    UnderlineColor(lipgloss.Color(\"#FF0000\"))\n```\n\nAvailable styles: `UnderlineNone`, `UnderlineSingle`, `UnderlineDouble`,\n`UnderlineCurly`, `UnderlineDotted`, `UnderlineDashed`.\n\n### Hyperlinks\n\nStyles can render clickable hyperlinks in supporting terminals:\n\n```go\ns := lipgloss.NewStyle().\n    Foreground(lipgloss.Color(\"#7B2FBE\")).\n    Hyperlink(\"https://charm.land\")\n\nlipgloss.Println(s.Render(\"Visit Charm\"))\n```\n\nIn unsupported terminals this will degrade gracefully and hyperlinks will\nsimply not render.\n\n## Block-Level Formatting\n\nLip Gloss also supports rules for block-level formatting:\n\n```go\n// Padding\nvar style = lipgloss.NewStyle().\n    PaddingTop(2).\n    PaddingRight(4).\n    PaddingBottom(2).\n    PaddingLeft(4)\n\n// Margins\nvar style = lipgloss.NewStyle().\n    MarginTop(2).\n    MarginRight(4).\n    MarginBottom(2).\n    MarginLeft(4)\n```\n\nThere is also shorthand syntax for margins and padding, which follows the same\nformat as CSS:\n\n```go\n// 2 cells on all sides\nlipgloss.NewStyle().Padding(2)\n\n// 2 cells on the top and bottom, 4 cells on the left and right\nlipgloss.NewStyle().Margin(2, 4)\n\n// 1 cell on the top, 4 cells on the sides, 2 cells on the bottom\nlipgloss.NewStyle().Padding(1, 4, 2)\n\n// Clockwise, starting from the top: 2 cells on the top, 4 on the right, 3 on\n// the bottom, and 1 on the left\nlipgloss.NewStyle().Margin(2, 4, 3, 1)\n```\n\nYou can also customize the characters used for padding and margin fill:\n\n```go\ns := lipgloss.NewStyle().\n    Padding(1, 2).\n    PaddingChar('·').\n    Margin(1, 2).\n    MarginChar('░')\n```\n\n## Aligning Text\n\nYou can align paragraphs of text to the left, right, or center.\n\n```go\nvar style = lipgloss.NewStyle().\n    Width(24).\n    Align(lipgloss.Left).  // align it left\n    Align(lipgloss.Right). // no wait, align it right\n    Align(lipgloss.Center) // just kidding, align it in the center\n```\n\n## Width and Height\n\nSetting a minimum width and height is simple and straightforward.\n\n```go\nvar style = lipgloss.NewStyle().\n    SetString(\"What’s for lunch?\").\n    Width(24).\n    Height(32).\n    Foreground(lipgloss.Color(\"63\"))\n```\n\n## Borders\n\nAdding borders is easy:\n\n```go\n// Add a purple, rectangular border\nvar style = lipgloss.NewStyle().\n    BorderStyle(lipgloss.NormalBorder()).\n    BorderForeground(lipgloss.Color(\"63\"))\n\n// Set a rounded, yellow-on-purple border to the top and left\nvar anotherStyle = lipgloss.NewStyle().\n    BorderStyle(lipgloss.RoundedBorder()).\n    BorderForeground(lipgloss.Color(\"228\")).\n    BorderBackground(lipgloss.Color(\"63\")).\n    BorderTop(true).\n    BorderLeft(true)\n\n// Make your own border\nvar myCuteBorder = lipgloss.Border{\n    Top:         \"._.:*:\",\n    Bottom:      \"._.:*:\",\n    Left:        \"|*\",\n    Right:       \"|*\",\n    TopLeft:     \"*\",\n    TopRight:    \"*\",\n    BottomLeft:  \"*\",\n    BottomRight: \"*\",\n}\n```\n\nThere are also shorthand functions for defining borders, which follow a similar\npattern to the margin and padding shorthand functions.\n\n```go\n// Add a thick border to the top and bottom\nlipgloss.NewStyle().\n    Border(lipgloss.ThickBorder(), true, false)\n\n// Add a double border to the top and left sides. Rules are set clockwise\n// from top.\nlipgloss.NewStyle().\n    Border(lipgloss.DoubleBorder(), true, false, false, true)\n```\n\nYou can also pass multiple colors to a border for a gradient effect:\n\n```go\ns := lipgloss.NewStyle().\n    Border(lipgloss.RoundedBorder()).\n    BorderForegroundBlend(lipgloss.Color(\"#FF0000\"), lipgloss.Color(\"#0000FF\"))\n```\n\nFor more on borders see [the docs](https://pkg.go.dev/charm.land/lipgloss/v2#Border).\n\n## Copying Styles\n\nJust use assignment:\n\n```go\nstyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"219\"))\n\ncopiedStyle := style // this is a true copy\n\nwildStyle := style.Blink(true) // this is also true copy, with blink added\n```\n\nSince `Style` is a pure value type, assigning a style to another effectively\ncreates a new copy of the style without mutating the original.\n\n## Inheritance\n\nStyles can inherit rules from other styles. When inheriting, only unset rules\non the receiver are inherited.\n\n```go\nvar styleA = lipgloss.NewStyle().\n    Foreground(lipgloss.Color(\"229\")).\n    Background(lipgloss.Color(\"63\"))\n\n// Only the background color will be inherited here, because the foreground\n// color will have been already set:\nvar styleB = lipgloss.NewStyle().\n    Foreground(lipgloss.Color(\"201\")).\n    Inherit(styleA)\n```\n\n## Unsetting Rules\n\nAll rules can be unset:\n\n```go\nvar style = lipgloss.NewStyle().\n    Bold(true).                        // make it bold\n    UnsetBold().                       // jk don't make it bold\n    Background(lipgloss.Color(\"227\")). // yellow background\n    UnsetBackground()                  // never mind\n```\n\nWhen a rule is unset, it won’t be inherited or copied.\n\n## Enforcing Rules\n\nSometimes, such as when developing a component, you want to make sure style\ndefinitions respect their intended purpose in the UI. This is where `Inline`\nand `MaxWidth`, and `MaxHeight` come in:\n\n```go\n// Force rendering onto a single line, ignoring margins, padding, and borders.\nsomeStyle.Inline(true).Render(\"yadda yadda\")\n\n// Also limit rendering to five cells\nsomeStyle.Inline(true).MaxWidth(5).Render(\"yadda yadda\")\n\n// Limit rendering to a 5x5 cell block\nsomeStyle.MaxWidth(5).MaxHeight(5).Render(\"yadda yadda\")\n```\n\n## Tabs\n\nThe tab character (`\\t`) is rendered differently in different terminals (often\nas 8 spaces, sometimes 4). Because of this inconsistency, Lip Gloss converts\ntabs to 4 spaces at render time. This behavior can be changed on a per-style\nbasis, however:\n\n```go\nstyle := lipgloss.NewStyle() // tabs will render as 4 spaces, the default\nstyle = style.TabWidth(2)    // render tabs as 2 spaces\nstyle = style.TabWidth(0)    // remove tabs entirely\nstyle = style.TabWidth(lipgloss.NoTabConversion) // leave tabs intact\n```\n\n## Wrapping\n\nThe `Wrap` function wraps text while preserving ANSI styles and hyperlinks\nacross line boundaries:\n\n```go\nwrapped := lipgloss.Wrap(styledText, 40, \" \")\n```\n\n## Rendering\n\nGenerally, you just call the `Render(string...)` method on a `lipgloss.Style`:\n\n```go\nstyle := lipgloss.NewStyle().Bold(true).SetString(\"Hello,\")\nlipgloss.Println(style.Render(\"kitty.\")) // Hello, kitty.\nlipgloss.Println(style.Render(\"puppy.\")) // Hello, puppy.\n```\n\nBut you could also use the Stringer interface:\n\n```go\nvar style = lipgloss.NewStyle().SetString(\"你好，猫咪。\").Bold(true)\nlipgloss.Println(style) // 你好，猫咪。\n```\n\n## Utilities\n\nIn addition to pure styling, Lip Gloss also ships with some utilities to help\nassemble your layouts.\n\n### Compositing\n\n<p><img width=\"350\" alt=\"xx\" src=\"https://github.com/user-attachments/assets/1921bac6-2408-436a-9d9e-7930fe4c6ec9\" /></p>\n\nLip Gloss includes a powerful, cell-based compositor for rendering layered\ncontent:\n\n```go\n// Create some layers.\na := lipgloss.NewLayer(pickles).X(4).Y(2).Z(1)\nb := lipgloss.NewLayer(bitterMelon).X(22).Y(1)\nc := lipgloss.NewLayer(sriracha).X(11).Y(7)\n\n// Composite 'em and render.\noutput := compositor.Compose(a, b, c).Render()\n```\n\nFor a more thorough example, see [the canvas\nexample](./examples/canvas/main.go). For reference, including how to detect\nmouse clicks on layers, see [the docs][docs].\n\n### Joining Paragraphs\n\nHorizontally and vertically joining paragraphs is a cinch.\n\n```go\n// Horizontally join three paragraphs along their bottom edges\nlipgloss.JoinHorizontal(lipgloss.Bottom, paragraphA, paragraphB, paragraphC)\n\n// Vertically join two paragraphs along their center axes\nlipgloss.JoinVertical(lipgloss.Center, paragraphA, paragraphB)\n\n// Horizontally join three paragraphs, with the shorter ones aligning 20%\n// from the top of the tallest\nlipgloss.JoinHorizontal(0.2, paragraphA, paragraphB, paragraphC)\n```\n\n### Measuring Width and Height\n\nSometimes you’ll want to know the width and height of text blocks when building\nyour layouts.\n\n```go\n// Render a block of text.\nvar style = lipgloss.NewStyle().\n    Width(40).\n    Padding(2)\nvar block string = style.Render(someLongString)\n\n// Get the actual, physical dimensions of the text block.\nwidth := lipgloss.Width(block)\nheight := lipgloss.Height(block)\n\n// Here's a shorthand function.\nw, h := lipgloss.Size(block)\n```\n\n### Blending Colors\n\nYou can blend colors in one or two dimensions for gradient effects:\n\n```go\n// 1-dimentinoal gradient\ncolors := lipgloss.Blend1D(10, lipgloss.Color(\"#FF0000\"), lipgloss.Color(\"#0000FF\"))\n\n// 2-dimensional gradient with rotation\ncolors := lipgloss.Blend2D(80, 24, 45.0, color1, color2, color3)\n```\n\n### Placing Text in Whitespace\n\nSometimes you’ll simply want to place a block of text in whitespace. This is\na lightweight alternative to compositing.\n\n```go\n// Center a paragraph horizontally in a space 80 cells wide. The height of\n// the block returned will be as tall as the input paragraph.\nblock := lipgloss.PlaceHorizontal(80, lipgloss.Center, fancyStyledParagraph)\n\n// Place a paragraph at the bottom of a space 30 cells tall. The width of\n// the text block returned will be as wide as the input paragraph.\nblock := lipgloss.PlaceVertical(30, lipgloss.Bottom, fancyStyledParagraph)\n\n// Place a paragraph in the bottom right corner of a 30x80 cell space.\nblock := lipgloss.Place(30, 80, lipgloss.Right, lipgloss.Bottom, fancyStyledParagraph)\n```\n\nYou can also style the whitespace. For details, see [the docs][docs].\n\n## Rendering Tables\n\nLip Gloss ships with a table rendering sub-package.\n\n```go\nimport \"charm.land/lipgloss/v2/table\"\n```\n\nDefine some rows of data.\n\n```go\nrows := [][]string{\n    {\"Chinese\", \"您好\", \"你好\"},\n    {\"Japanese\", \"こんにちは\", \"やあ\"},\n    {\"Arabic\", \"أهلين\", \"أهلا\"},\n    {\"Russian\", \"Здравствуйте\", \"Привет\"},\n    {\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n}\n```\n\nUse the table package to style and render the table.\n\n```go\nvar (\n    purple    = lipgloss.Color(\"99\")\n    gray      = lipgloss.Color(\"245\")\n    lightGray = lipgloss.Color(\"241\")\n\n    headerStyle  = lipgloss.NewStyle().Foreground(purple).Bold(true).Align(lipgloss.Center)\n    cellStyle    = lipgloss.NewStyle().Padding(0, 1).Width(14)\n    oddRowStyle  = cellStyle.Foreground(gray)\n    evenRowStyle = cellStyle.Foreground(lightGray)\n)\n\nt := table.New().\n    Border(lipgloss.NormalBorder()).\n    BorderStyle(lipgloss.NewStyle().Foreground(purple)).\n    StyleFunc(func(row, col int) lipgloss.Style {\n        switch {\n        case row == table.HeaderRow:\n            return headerStyle\n        case row%2 == 0:\n            return evenRowStyle\n        default:\n            return oddRowStyle\n        }\n    }).\n    Headers(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n    Rows(rows...)\n\n// You can also add tables row-by-row\nt.Row(\"English\", \"You look absolutely fabulous.\", \"How's it going?\")\n```\n\nPrint the table.\n\n```go\nlipgloss.Println(t)\n```\n\n![Table Example](https://github.com/charmbracelet/lipgloss/assets/42545625/6e4b70c4-f494-45da-a467-bdd27df30d5d)\n\n### Table Borders\n\nThere are helpers to generate tables in markdown or ASCII style:\n\n#### Markdown Table\n\n```go\ntable.New().Border(lipgloss.MarkdownBorder()).BorderTop(false).BorderBottom(false)\n```\n\n```\n| LANGUAGE |    FORMAL    | INFORMAL  |\n|----------|--------------|-----------|\n| Chinese  | Nǐn hǎo      | Nǐ hǎo    |\n| French   | Bonjour      | Salut     |\n| Russian  | Zdravstvuyte | Privet    |\n| Spanish  | Hola         | ¿Qué tal? |\n```\n\n#### ASCII Table\n\n```go\ntable.New().Border(lipgloss.ASCIIBorder())\n```\n\n```\n+----------+--------------+-----------+\n| LANGUAGE |    FORMAL    | INFORMAL  |\n+----------+--------------+-----------+\n| Chinese  | Nǐn hǎo      | Nǐ hǎo    |\n| French   | Bonjour      | Salut     |\n| Russian  | Zdravstvuyte | Privet    |\n| Spanish  | Hola         | ¿Qué tal? |\n+----------+--------------+-----------+\n```\n\nFor more on tables see [the docs][docs] and [examples](https://github.com/charmbracelet/lipgloss/tree/master/examples/table).\n\n## Rendering Lists\n\nLip Gloss ships with a list rendering sub-package.\n\n```go\nimport \"charm.land/lipgloss/v2/list\"\n```\n\nDefine a new list.\n\n```go\nl := list.New(\"A\", \"B\", \"C\")\n```\n\nPrint the list.\n\n```go\nlipgloss.Println(l)\n\n// • A\n// • B\n// • C\n```\n\nLists have the ability to nest.\n\n```go\nl := list.New(\n    \"A\", list.New(\"Artichoke\"),\n    \"B\", list.New(\"Baking Flour\", \"Bananas\", \"Barley\", \"Bean Sprouts\"),\n    \"C\", list.New(\"Cashew Apple\", \"Cashews\", \"Coconut Milk\", \"Curry Paste\", \"Currywurst\"),\n    \"D\", list.New(\"Dill\", \"Dragonfruit\", \"Dried Shrimp\"),\n    \"E\", list.New(\"Eggs\"),\n    \"F\", list.New(\"Fish Cake\", \"Furikake\"),\n    \"J\", list.New(\"Jicama\"),\n    \"K\", list.New(\"Kohlrabi\"),\n    \"L\", list.New(\"Leeks\", \"Lentils\", \"Licorice Root\"),\n)\n```\n\nPrint the list.\n\n```go\nlipgloss.Println(l)\n```\n\n<p align=\"center\">\n<img width=\"600\" alt=\"image\" src=\"https://github.com/charmbracelet/lipgloss/assets/42545625/0dc9f440-0748-4151-a3b0-7dcf29dfcdb0\">\n</p>\n\nLists can be customized via their enumeration function as well as using\n`lipgloss.Style`s.\n\n```go\nenumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"99\")).MarginRight(1)\nitemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"212\")).MarginRight(1)\n\nl := list.New(\n    \"Glossier\",\n    \"Claire's Boutique\",\n    \"Nyx\",\n    \"Mac\",\n    \"Milk\",\n    ).\n    Enumerator(list.Roman).\n    EnumeratorStyle(enumeratorStyle).\n    ItemStyle(itemStyle)\n```\n\nPrint the list.\n\n<p align=\"center\">\n<img width=\"600\" alt=\"List example\" src=\"https://github.com/charmbracelet/lipgloss/assets/42545625/360494f1-57fb-4e13-bc19-0006efe01561\">\n</p>\n\nIn addition to the predefined enumerators (`Arabic`, `Alphabet`, `Roman`, `Bullet`, `Tree`),\nyou may also define your own custom enumerator:\n\n```go\nl := list.New(\"Duck\", \"Duck\", \"Duck\", \"Duck\", \"Goose\", \"Duck\", \"Duck\")\n\nfunc DuckDuckGooseEnumerator(l list.Items, i int) string {\n    if l.At(i).Value() == \"Goose\" {\n        return \"Honk →\"\n    }\n    return \"\"\n}\n\nl = l.Enumerator(DuckDuckGooseEnumerator)\n```\n\nPrint the list:\n\n<p align=\"center\">\n<img width=\"600\" alt=\"image\" src=\"https://github.com/charmbracelet/lipgloss/assets/42545625/157aaf30-140d-4948-9bb4-dfba46e5b87e\">\n</p>\n\nIf you need, you can also build lists incrementally:\n\n```go\nl := list.New()\n\nfor i := 0; i < repeat; i++ {\n    l.Item(\"Lip Gloss\")\n}\n```\n\n## Rendering Trees\n\nLip Gloss ships with a tree rendering sub-package.\n\n```go\nimport \"charm.land/lipgloss/v2/tree\"\n```\n\nDefine a new tree.\n\n```go\nt := tree.Root(\".\").\n    Child(\"A\", \"B\", \"C\")\n```\n\nPrint the tree.\n\n```go\nlipgloss.Println(t)\n\n// .\n// ├── A\n// ├── B\n// └── C\n```\n\nTrees have the ability to nest.\n\n```go\nt := tree.Root(\".\").\n    Child(\"macOS\").\n    Child(\n        tree.New().\n            Root(\"Linux\").\n            Child(\"NixOS\").\n            Child(\"Arch Linux (btw)\").\n            Child(\"Void Linux\"),\n        ).\n    Child(\n        tree.New().\n            Root(\"BSD\").\n            Child(\"FreeBSD\").\n            Child(\"OpenBSD\"),\n    )\n```\n\nPrint the tree.\n\n```go\nlipgloss.Println(t)\n```\n\n<p align=\"center\">\n<img width=\"663\" alt=\"Tree Example (simple)\" src=\"https://github.com/user-attachments/assets/5ef14eb8-a5d4-4f94-8834-e15d1e714f89\">\n</p>\n\nTrees can be customized via their enumeration function as well as using\n`lipgloss.Style`s.\n\n```go\nenumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"63\")).MarginRight(1)\nrootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"35\"))\nitemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"212\"))\n\nt := tree.\n    Root(\"⁜ Makeup\").\n    Child(\n        \"Glossier\",\n        \"Fenty Beauty\",\n        tree.New().Child(\n            \"Gloss Bomb Universal Lip Luminizer\",\n            \"Hot Cheeks Velour Blushlighter\",\n        ),\n        \"Nyx\",\n        \"Mac\",\n        \"Milk\",\n    ).\n    Enumerator(tree.RoundedEnumerator).\n    EnumeratorStyle(enumeratorStyle).\n    RootStyle(rootStyle).\n    ItemStyle(itemStyle)\n```\n\nPrint the tree.\n\n<p align=\"center\">\n<img width=\"663\" alt=\"Tree Example (makeup)\" src=\"https://github.com/user-attachments/assets/06d12d87-744a-4c89-bd98-45de9094a97e\">\n</p>\n\nThe predefined enumerators for trees are `DefaultEnumerator` and `RoundedEnumerator`.\n\nIf you need, you can also build trees incrementally:\n\n```go\nt := tree.New()\n\nfor i := 0; i < repeat; i++ {\n    t.Child(\"Lip Gloss\")\n}\n```\n\n## Advanced Color Usage\n\nOne of the most powerful features of Lip Gloss is the ability to render\ndifferent colors at runtime depending on the user's terminal and environment,\nallowing you to present the best possible user experience.\n\nThis section shows you how to do exactly that.\n\n<details>\n<summary>Migrating from v1?</summary>\n\nThe `compat` package provides `AdaptiveColor`, `CompleteColor`, and\n`CompleteAdaptiveColor` for a quicker migration from v1. These work by\nlooking at `stdin` and `stdout` on a global basis:\n\n```go\nimport \"charm.land/lipgloss/v2/compat\"\n\ncolor := compat.AdaptiveColor{\n    Light: lipgloss.Color(\"#f1f1f1\"),\n    Dark:  lipgloss.Color(\"#cccccc\"),\n}\n```\n\nNote that we don't recommend this for new code as it removes the purity from\nLip Gloss, computationally speaking, as it removes transparency around when\nI/O happens, which could cause Lip Gloss to compete for resources (like stdin)\nwith other tools.\n\n</details>\n\n### Adaptive Colors\n\nYou can render different colors at runtime depending on whether the terminal\nhas a light or dark background:\n\n```go\nhasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\nlightDark := lipgloss.LightDark(hasDarkBG)\n\nmyColor := lightDark(lipgloss.Color(\"#D7FFAE\"), lipgloss.Color(\"#D75FEE\"))\n```\n\n#### With Bubble Tea\n\nIn Bubble Tea, request the background color, listen for a\n`BackgroundColorMsg`, and respond accordingly:\n\n```go\nfunc (m model) Init() tea.Cmd {\n    // First, send a Cmd to request the terminal background color.\n    return tea.RequestBackgroundColor\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    switch msg := msg.(type) {\n    case tea.BackgroundColorMsg:\n        // Great, we have the background color. Now we can set up our styles\n        // against the color.\n        m.styles = newStyles(msg.IsDark())\n        return m, nil\n    }\n}\n\nfunc newStyles(bgIsDark bool) styles {\n    // A little ternary function that will return the appropriate color\n    // based on the background color.\n    lightDark := lipgloss.LightDark(bgIsDark)\n\n    return styles{\n        myHotStyle: lipgloss.NewStyle().Foreground(lightDark(\n            lipgloss.Color(\"#f1f1f1\"),\n            lipgloss.Color(\"#333333\"),\n        )),\n    }\n}\n```\n\n#### Standalone\n\nIf you’re not using Bubble Tea you can perform the query manually:\n\n```go\n// What's the background color?\nhasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stderr)\n\n// A helper function that will return the appropriate color based on the\n// background.\nlightDark := lipgloss.LightDark(hasDarkBG)\n\n// A couple colors with light and dark variants.\nthisColor := lightDark(lipgloss.Color(\"#C5ADF9\"), lipgloss.Color(\"#864EFF\"))\nthatColor := lightDark(lipgloss.Color(\"#37CD96\"), lipgloss.Color(\"#22C78A\"))\n\na := lipgloss.NewStyle().Foreground(thisColor).Render(\"this\")\nb := lipgloss.NewStyle().Foreground(thatColor).Render(\"that\")\n\n// Render the appropriate colors at runtime:\nlipgloss.Fprintf(os.Stderr, \"my fave colors are %s and %s\", a, b)\n```\n\n### Complete Colors\n\nIn some cases where you may want to specify exact values for each color profile\n(ANSI 16, ANSI 156, and TrueColor). For these cases, use the `Complete` helper:\n\n```go\n// You'll need the colorprofile package.\nimport \"github.com/charmbracelet/colorprofile\"\n\n// Get the color profile.\nprofile := colorprofile.Detect(os.Stdout, os.Environ())\n\n// Create a function for rendering the appropriate color based on the profile.\nvar completeColor := lipgloss.Complete(profile)\n\n// Now we'll choose the appropriate color at runtime.\nmyColor := completeColor(ansiColor, ansi256Color, trueColor)\n```\n\n### Color Downsampling\n\nOne of the best things about Lip Gloss is that it can automatically downsample\ncolors to the best available profile, stripping colors (and ANSI) entirely when\noutput is not a TTY.\n\nIf you’re using Lip Gloss with Bubble Tea there’s nothing to do here:\ndownsampling is built into Bubble Tea v2. If you’re not using Bubble Tea, use\nthe Lip Gloss writer functions, which are a drop-in replacement for the `fmt`\npackage:\n\n```go\ns := lipgloss.NewStyle()\n    .Foreground(lipgloss.Color(\"#EB4268\"))\n    .Render(\"Hello!\")\n\n// Downsample if needed and print to stdout.\nlipgloss.Println(s)\n\n// Render to a variable.\ndownsampled := lipgloss.Sprint(s)\n\n// Print to stderr.\nlipgloss.Fprint(os.Stderr, s)\n```\n\nThe full set: `Print`, `Println`, `Printf`, `Fprint`, `Fprintln`, `Fprintf`,\n`Sprint`, `Sprintln`, `Sprintf`.\n\nNeed more control? Check out\n[Colorprofile](https://github.com/charmbracelet/colorprofile), which Lip Gloss\nuses under the hood.\n\n## What about [Bubble Tea][tea]?\n\nLip Gloss doesn’t replace Bubble Tea. Rather, it is an excellent Bubble Tea\ncompanion. It was designed to make assembling terminal user interface views as\nsimple and fun as possible so that you can focus on building your application\ninstead of concerning yourself with low-level layout details.\n\nIn simple terms, you can use Lip Gloss to help build your Bubble Tea views.\n\n[tea]: https://github.com/charmbracelet/bubbletea\n\n## Rendering Markdown\n\nFor a more document-centric rendering solution with support for things like\nlists, tables, and syntax-highlighted code have a look at [Glamour][glamour],\nthe stylesheet-based Markdown renderer.\n\n[glamour]: https://github.com/charmbracelet/glamour\n\n## Contributing\n\nSee [contributing][contribute].\n\n[contribute]: https://github.com/charmbracelet/lipgloss/contribute\n\n## Feedback\n\nWe’d love to hear your thoughts on this project. Feel free to drop us a note!\n\n- [Discord](https://charm.land/chat)\n- [Matrix](https://charm.land/matrix)\n\n## License\n\n[MIT](https://github.com/charmbracelet/lipgloss/raw/master/LICENSE)\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-banner-next.jpg\" width=\"400\"></a>\n\nCharm热爱开源 • Charm loves open source\n\n[docs]: https://pkg.go.dev/charm.land/lipgloss/v2?tab=doc\n"
  },
  {
    "path": "Taskfile.yaml",
    "content": "# https://taskfile.dev\n\nversion: \"3\"\n\ntasks:\n  lint:\n    desc: Run base linters\n    cmds:\n      - golangci-lint run\n\n  test:\n    desc: Run tests\n    cmds:\n      - go test ./... {{.CLI_ARGS}}\n\n  test:table:\n    desc: Run table tests\n    cmds:\n      - go test ./table {{.CLI_ARGS}}\n\n  test:tree:\n    desc: Run tree tests\n    cmds:\n      - go test ./tree {{.CLI_ARGS}}\n"
  },
  {
    "path": "UPGRADE_GUIDE_V2.md",
    "content": "# Lip Gloss v2 Upgrade Guide\n\nThis guide covers migrating from Lip Gloss v1 (`github.com/charmbracelet/lipgloss`)\nto Lip Gloss v2 (`charm.land/lipgloss/v2`). It is written for both humans and\nLLMs performing automated migrations.\n\n---\n\n## Table of Contents\n\n1. [Quick Start](#quick-start)\n2. [Module Path](#module-path)\n3. [Color System](#color-system)\n4. [Renderer Removal](#renderer-removal)\n5. [Printing and Color Downsampling](#printing-and-color-downsampling)\n6. [Background Detection and Adaptive Colors](#background-detection-and-adaptive-colors)\n7. [Whitespace Options](#whitespace-options)\n8. [Underline](#underline)\n9. [Style API Changes](#style-api-changes)\n10. [Tree Subpackage](#tree-subpackage)\n11. [Removed APIs](#removed-apis)\n12. [Quick Reference Table](#quick-reference-table)\n\n---\n\n## Quick Start\n\nFor the fastest possible upgrade, do these two things:\n\n### 1. Use the `compat` package for adaptive/complete colors\n\n```go\nimport \"charm.land/lipgloss/v2/compat\"\n\n// v1\ncolor := lipgloss.AdaptiveColor{Light: \"#f1f1f1\", Dark: \"#cccccc\"}\n\n// v2\ncolor := compat.AdaptiveColor{Light: lipgloss.Color(\"#f1f1f1\"), Dark: lipgloss.Color(\"#cccccc\")}\n```\n\nThe `compat` package reads `stdin`/`stdout` globally, just like v1. To\ncustomize:\n\n```go\nimport (\n    \"charm.land/lipgloss/v2/compat\"\n    \"github.com/charmbracelet/colorprofile\"\n)\n\nfunc init() {\n    compat.HasDarkBackground = lipgloss.HasDarkBackground(os.Stdin, os.Stderr)\n    compat.Profile = colorprofile.Detect(os.Stderr, os.Environ())\n}\n```\n\n### 2. Use Lip Gloss writers for output\n\n```go\n// v1\nfmt.Println(s)\n\n// v2\nlipgloss.Println(s)\n```\n\nThis ensures colors are automatically downsampled. If you're using Bubble Tea\nv2, this step is unnecessary — Bubble Tea handles it for you.\n\n**That's the quick path.** Read on for the full migration details.\n\n---\n\n## Module Path\n\nThe import path has changed.\n\n```go\n// v1\nimport \"github.com/charmbracelet/lipgloss\"\n\n// v2\nimport \"charm.land/lipgloss/v2\"\n```\n\n**Install:**\n\n```bash\ngo get charm.land/lipgloss/v2\n```\n\nAll subpackages follow the same pattern:\n\n```go\n// v1\nimport \"github.com/charmbracelet/lipgloss/table\"\nimport \"github.com/charmbracelet/lipgloss/tree\"\nimport \"github.com/charmbracelet/lipgloss/list\"\n\n// v2\nimport \"charm.land/lipgloss/v2/table\"\nimport \"charm.land/lipgloss/v2/tree\"\nimport \"charm.land/lipgloss/v2/list\"\n```\n\n**Search-and-replace pattern:**\n\n```\ngithub.com/charmbracelet/lipgloss → charm.land/lipgloss/v2\n```\n\n---\n\n## Color System\n\nThis is the most significant API change.\n\n### `Color` is now a function, not a type\n\n```go\n// v1 — Color is a string type\nvar c lipgloss.Color = \"21\"\nvar c lipgloss.Color = \"#ff00ff\"\n\n// v2 — Color is a function returning color.Color\nvar c color.Color = lipgloss.Color(\"21\")\nvar c color.Color = lipgloss.Color(\"#ff00ff\")\n```\n\nThe return type is `image/color.Color` (from the standard library).\n\n### `TerminalColor` interface is removed\n\nAll methods that accepted `lipgloss.TerminalColor` now accept\n`image/color.Color`:\n\n```go\n// v1\nfunc (s Style) Foreground(c TerminalColor) Style\nfunc (s Style) Background(c TerminalColor) Style\nfunc (s Style) BorderForeground(c ...TerminalColor) Style\n\n// v2\nfunc (s Style) Foreground(c color.Color) Style\nfunc (s Style) Background(c color.Color) Style\nfunc (s Style) BorderForeground(c ...color.Color) Style\n```\n\n**Migration:** Replace every `lipgloss.TerminalColor` with `color.Color` and\nadd `import \"image/color\"`.\n\n### `ANSIColor` is now an alias\n\n```go\n// v1 — custom uint type\ntype ANSIColor uint\n\n// v2 — alias for ansi.IndexedColor\ntype ANSIColor = ansi.IndexedColor\n```\n\nv2 also exports named constants for the 16 basic ANSI colors:\n\n```go\nlipgloss.Black, lipgloss.Red, lipgloss.Green, lipgloss.Yellow,\nlipgloss.Blue, lipgloss.Magenta, lipgloss.Cyan, lipgloss.White,\nlipgloss.BrightBlack, lipgloss.BrightRed, lipgloss.BrightGreen,\nlipgloss.BrightYellow, lipgloss.BrightBlue, lipgloss.BrightMagenta,\nlipgloss.BrightCyan, lipgloss.BrightWhite\n```\n\n### `AdaptiveColor`, `CompleteColor`, `CompleteAdaptiveColor`\n\nThese types have been moved out of the root package. Use the `compat` package\nfor a drop-in replacement, or use the new `LightDark` and `Complete` helpers\nfor explicit control:\n\n```go\n// v1\ncolor := lipgloss.AdaptiveColor{Light: \"#0000ff\", Dark: \"#000099\"}\n\n// v2 — using compat (quick path)\ncolor := compat.AdaptiveColor{\n    Light: lipgloss.Color(\"#0000ff\"),\n    Dark:  lipgloss.Color(\"#000099\"),\n}\n\n// v2 — using LightDark (recommended)\nhasDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\nlightDark := lipgloss.LightDark(hasDark)\ncolor := lightDark(lipgloss.Color(\"#0000ff\"), lipgloss.Color(\"#000099\"))\n```\n\n```go\n// v1\ncolor := lipgloss.CompleteColor{TrueColor: \"#ff00ff\", ANSI256: \"200\", ANSI: \"5\"}\n\n// v2 — using compat\ncolor := compat.CompleteColor{\n    TrueColor: lipgloss.Color(\"#ff00ff\"),\n    ANSI256:   lipgloss.Color(\"200\"),\n    ANSI:      lipgloss.Color(\"5\"),\n}\n\n// v2 — using Complete (recommended)\nprofile := colorprofile.Detect(os.Stdout, os.Environ())\ncomplete := lipgloss.Complete(profile)\ncolor := complete(lipgloss.Color(\"5\"), lipgloss.Color(\"200\"), lipgloss.Color(\"#ff00ff\"))\n```\n\nNote that `compat.AdaptiveColor` and friends take `color.Color` values for\ntheir fields, not strings.\n\n---\n\n## Renderer Removal\n\nThe `Renderer` type and all associated functions are removed. In v1, every\n`Style` carried a `*Renderer` pointer and the package maintained a global\ndefault renderer.\n\n```go\n// v1 — these no longer exist\nlipgloss.DefaultRenderer()\nlipgloss.SetDefaultRenderer(r)\nlipgloss.NewRenderer(w, opts...)\nlipgloss.ColorProfile()\nlipgloss.SetColorProfile(p)\nrenderer.NewStyle()\n```\n\n**In v2, `Style` is a plain value type.** There is no renderer. Color\ndownsampling is handled at the output layer (see next section).\n\n**Migration:**\n\n- Replace `lipgloss.DefaultRenderer().NewStyle()` with `lipgloss.NewStyle()`.\n- Replace `renderer.NewStyle()` with `lipgloss.NewStyle()`.\n- Remove any `*Renderer` fields from your types.\n- Remove calls to `SetColorProfile` — use `colorprofile.Detect` at the output\n  layer instead.\n\n---\n\n## Printing and Color Downsampling\n\nIn v1, color downsampling happened inside `Style.Render()` via the renderer. In\nv2, `Render()` always emits full-fidelity ANSI. Downsampling happens when you\nprint.\n\n### Standalone Usage\n\nUse the Lip Gloss writer functions:\n\n```go\ns := someStyle.Render(\"Hello!\")\n\n// Print to stdout with automatic downsampling\nlipgloss.Println(s)\n\n// Print to stderr\nlipgloss.Fprintln(os.Stderr, s)\n\n// Render to a string (downsampled for stdout's profile)\nstr := lipgloss.Sprint(s)\n```\n\nThe default writer targets `stdout`. To customize:\n\n```go\nlipgloss.Writer = colorprofile.NewWriter(os.Stderr, os.Environ())\n```\n\n### With Bubble Tea\n\nNo changes needed. Bubble Tea v2 handles downsampling internally.\n\n---\n\n## Background Detection and Adaptive Colors\n\n### Standalone\n\nv1 detected the background color automatically via the global renderer. v2\nrequires explicit queries:\n\n```go\n// v1\nhasDark := lipgloss.HasDarkBackground()\n\n// v2 — specify the input and output\nhasDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n```\n\nThen use `LightDark` to pick colors:\n\n```go\nlightDark := lipgloss.LightDark(hasDark)\nfg := lightDark(lipgloss.Color(\"#333333\"), lipgloss.Color(\"#f1f1f1\"))\n\ns := lipgloss.NewStyle().Foreground(fg)\n```\n\n### With Bubble Tea\n\nRequest the background color in `Init` and listen for the response:\n\n```go\nfunc (m model) Init() tea.Cmd {\n    return tea.RequestBackgroundColor\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    switch msg := msg.(type) {\n    case tea.BackgroundColorMsg:\n        m.styles = newStyles(msg.IsDark())\n    }\n    // ...\n}\n\nfunc newStyles(bgIsDark bool) styles {\n    lightDark := lipgloss.LightDark(bgIsDark)\n    return styles{\n        title: lipgloss.NewStyle().Foreground(lightDark(\n            lipgloss.Color(\"#333333\"),\n            lipgloss.Color(\"#f1f1f1\"),\n        )),\n    }\n}\n```\n\n---\n\n## Whitespace Options\n\nThe separate foreground/background whitespace options have been replaced by a\nsingle style option:\n\n```go\n// v1\nlipgloss.Place(width, height, hPos, vPos, str,\n    lipgloss.WithWhitespaceForeground(lipgloss.Color(\"#333\")),\n    lipgloss.WithWhitespaceBackground(lipgloss.Color(\"#000\")),\n)\n\n// v2\nlipgloss.Place(width, height, hPos, vPos, str,\n    lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().\n        Foreground(lipgloss.Color(\"#333\")).\n        Background(lipgloss.Color(\"#000\")),\n    ),\n)\n```\n\n---\n\n## Underline\n\n`Underline(bool)` still works for basic on/off. v2 adds fine-grained control:\n\n```go\n// v1\ns := lipgloss.NewStyle().Underline(true)\n\n// v2 — still works\ns := lipgloss.NewStyle().Underline(true)\n\n// v2 — new: specific styles\ns := lipgloss.NewStyle().UnderlineStyle(lipgloss.UnderlineCurly)\n\n// v2 — new: colored underlines\ns := lipgloss.NewStyle().\n    UnderlineStyle(lipgloss.UnderlineSingle).\n    UnderlineColor(lipgloss.Color(\"#FF0000\"))\n```\n\nInternally, `Underline(true)` is equivalent to `UnderlineStyle(UnderlineSingle)`\nand `Underline(false)` is equivalent to `UnderlineStyle(UnderlineNone)`.\n\n---\n\n## Style API Changes\n\n### `NewStyle()` is no longer tied to a Renderer\n\n```go\n// v1\ns := lipgloss.NewStyle()         // uses global renderer\ns := renderer.NewStyle()          // uses specific renderer\n\n// v2\ns := lipgloss.NewStyle()         // pure value, no renderer\n```\n\n### Color getters return `color.Color`\n\n```go\n// v1\nfg := s.GetForeground() // returns TerminalColor\n\n// v2\nfg := s.GetForeground() // returns color.Color\n```\n\n### New style methods\n\n| Method | Description |\n|---|---|\n| `UnderlineStyle(Underline)` | Set underline style (single, double, curly, etc.) |\n| `UnderlineColor(color.Color)` | Set underline color |\n| `PaddingChar(rune)` | Set the character used for padding fill |\n| `MarginChar(rune)` | Set the character used for margin fill |\n| `Hyperlink(link, params...)` | Set a clickable hyperlink |\n| `BorderForegroundBlend(...color.Color)` | Apply gradient colors to borders |\n| `BorderForegroundBlendOffset(int)` | Set the offset for border gradient |\n\nEach has a corresponding `Get*`, `Unset*`, and where applicable `Get*`\naccessor.\n\n---\n\n## Tree Subpackage\n\nThe import path changes and there are new styling options:\n\n```go\n// v1\nimport \"github.com/charmbracelet/lipgloss/tree\"\n\n// v2\nimport \"charm.land/lipgloss/v2/tree\"\n```\n\nNew methods:\n\n- `IndenterStyle(lipgloss.Style)` — set a static style for tree indentation.\n- `IndenterStyleFunc(func(Children, int) lipgloss.Style)` — conditionally style\n  indentation.\n- `Width(int)` — set tree width for padding.\n\n---\n\n## Removed APIs\n\nThe following types and functions no longer exist in v2. This table shows each\nremoved symbol and its replacement.\n\n| v1 Symbol | v2 Replacement |\n|---|---|\n| `type Renderer` | Removed entirely |\n| `DefaultRenderer()` | Not needed |\n| `SetDefaultRenderer(r)` | Not needed |\n| `NewRenderer(w, opts...)` | Not needed |\n| `ColorProfile()` | `colorprofile.Detect(w, env)` |\n| `SetColorProfile(p)` | Set `lipgloss.Writer.Profile` |\n| `HasDarkBackground()` (no args) | `lipgloss.HasDarkBackground(in, out)` |\n| `SetHasDarkBackground(b)` | Not needed — pass bool to `LightDark` |\n| `type TerminalColor` | `image/color.Color` |\n| `type Color string` | `func Color(string) color.Color` |\n| `type ANSIColor uint` | `type ANSIColor = ansi.IndexedColor` |\n| `type AdaptiveColor` | `compat.AdaptiveColor` or `LightDark` |\n| `type CompleteColor` | `compat.CompleteColor` or `Complete` |\n| `type CompleteAdaptiveColor` | `compat.CompleteAdaptiveColor` |\n| `WithWhitespaceForeground(c)` | `WithWhitespaceStyle(s)` |\n| `WithWhitespaceBackground(c)` | `WithWhitespaceStyle(s)` |\n| `renderer.NewStyle()` | `lipgloss.NewStyle()` |\n\n---\n\n## Quick Reference Table\n\nA side-by-side summary for common patterns:\n\n| Task | v1 | v2 |\n|---|---|---|\n| Import | `\"github.com/charmbracelet/lipgloss\"` | `\"charm.land/lipgloss/v2\"` |\n| Create style | `lipgloss.NewStyle()` | `lipgloss.NewStyle()` |\n| Hex color | `lipgloss.Color(\"#ff00ff\")` | `lipgloss.Color(\"#ff00ff\")` |\n| ANSI color | `lipgloss.Color(\"5\")` | `lipgloss.Color(\"5\")` or `lipgloss.Magenta` |\n| Adaptive color | `lipgloss.AdaptiveColor{Light: \"#fff\", Dark: \"#000\"}` | `compat.AdaptiveColor{Light: lipgloss.Color(\"#fff\"), Dark: lipgloss.Color(\"#000\")}` |\n| Set foreground | `s.Foreground(lipgloss.Color(\"5\"))` | `s.Foreground(lipgloss.Color(\"5\"))` |\n| Print with downsampling | `fmt.Println(s.Render(\"hi\"))` | `lipgloss.Println(s.Render(\"hi\"))` |\n| Detect dark bg | `lipgloss.HasDarkBackground()` | `lipgloss.HasDarkBackground(os.Stdin, os.Stdout)` |\n| Light/dark color | `lipgloss.AdaptiveColor{...}` | `lipgloss.LightDark(isDark)(light, dark)` |\n| Whitespace styling | `WithWhitespaceForeground(c)` | `WithWhitespaceStyle(lipgloss.NewStyle().Foreground(c))` |\n| Underline | `s.Underline(true)` | `s.Underline(true)` or `s.UnderlineStyle(lipgloss.UnderlineCurly)` |\n\n---\n\n## Feedback\n\nQuestions, issues, or feedback:\n\n- [Discord](https://charm.land/discord)\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": "align.go",
    "content": "package lipgloss\n\nimport (\n\t\"strings\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// Perform text alignment. If the string is multi-lined, we also make all lines\n// the same width by padding them with spaces. If a style is passed, use that\n// to style the spaces added.\nfunc alignTextHorizontal(str string, pos Position, width int, style *ansi.Style) string {\n\tlines, widestLine := getLines(str)\n\tvar b strings.Builder\n\n\tfor i, l := range lines {\n\t\tlineWidth := ansi.StringWidth(l)\n\n\t\tshortAmount := widestLine - lineWidth                // difference from the widest line\n\t\tshortAmount += max(0, width-(shortAmount+lineWidth)) // difference from the total width, if set\n\n\t\tif shortAmount > 0 {\n\t\t\tswitch pos {\n\t\t\tcase Right:\n\t\t\t\ts := strings.Repeat(\" \", shortAmount)\n\t\t\t\tif style != nil {\n\t\t\t\t\ts = style.Styled(s)\n\t\t\t\t}\n\t\t\t\tl = s + l\n\t\t\tcase Center:\n\t\t\t\t// Note: remainder goes on the right.\n\t\t\t\tleft := shortAmount / 2       //nolint:mnd\n\t\t\t\tright := left + shortAmount%2 //nolint:mnd\n\n\t\t\t\tleftSpaces := strings.Repeat(\" \", left)\n\t\t\t\trightSpaces := strings.Repeat(\" \", right)\n\n\t\t\t\tif style != nil {\n\t\t\t\t\tleftSpaces = style.Styled(leftSpaces)\n\t\t\t\t\trightSpaces = style.Styled(rightSpaces)\n\t\t\t\t}\n\t\t\t\tl = leftSpaces + l + rightSpaces\n\t\t\tdefault: // Left\n\t\t\t\ts := strings.Repeat(\" \", shortAmount)\n\t\t\t\tif style != nil {\n\t\t\t\t\ts = style.Styled(s)\n\t\t\t\t}\n\t\t\t\tl += s\n\t\t\t}\n\t\t}\n\n\t\tb.WriteString(l)\n\t\tif i < len(lines)-1 {\n\t\t\tb.WriteRune('\\n')\n\t\t}\n\t}\n\n\treturn b.String()\n}\n\nfunc alignTextVertical(str string, pos Position, height int, _ *ansi.Style) string {\n\tstrHeight := strings.Count(str, \"\\n\") + 1\n\tif height < strHeight {\n\t\treturn str\n\t}\n\n\tswitch pos {\n\tcase Top:\n\t\treturn str + strings.Repeat(\"\\n\", height-strHeight)\n\tcase Center:\n\t\ttopPadding, bottomPadding := (height-strHeight)/2, (height-strHeight)/2 //nolint:mnd\n\t\tif strHeight+topPadding+bottomPadding > height {\n\t\t\ttopPadding--\n\t\t} else if strHeight+topPadding+bottomPadding < height {\n\t\t\tbottomPadding++\n\t\t}\n\t\treturn strings.Repeat(\"\\n\", topPadding) + str + strings.Repeat(\"\\n\", bottomPadding)\n\tcase Bottom:\n\t\treturn strings.Repeat(\"\\n\", height-strHeight) + str\n\t}\n\treturn str\n}\n"
  },
  {
    "path": "align_test.go",
    "content": "package lipgloss\n\nimport \"testing\"\n\nfunc TestAlignTextVertical(t *testing.T) {\n\ttests := []struct {\n\t\tstr    string\n\t\tpos    Position\n\t\theight int\n\t\twant   string\n\t}{\n\t\t{str: \"Foo\", pos: Top, height: 2, want: \"Foo\\n\"},\n\t\t{str: \"Foo\", pos: Center, height: 5, want: \"\\n\\nFoo\\n\\n\"},\n\t\t{str: \"Foo\", pos: Bottom, height: 5, want: \"\\n\\n\\n\\nFoo\"},\n\n\t\t{str: \"Foo\\nBar\", pos: Bottom, height: 5, want: \"\\n\\n\\nFoo\\nBar\"},\n\t\t{str: \"Foo\\nBar\", pos: Center, height: 5, want: \"\\nFoo\\nBar\\n\\n\"},\n\t\t{str: \"Foo\\nBar\", pos: Top, height: 5, want: \"Foo\\nBar\\n\\n\\n\"},\n\n\t\t{str: \"Foo\\nBar\\nBaz\", pos: Bottom, height: 5, want: \"\\n\\nFoo\\nBar\\nBaz\"},\n\t\t{str: \"Foo\\nBar\\nBaz\", pos: Center, height: 5, want: \"\\nFoo\\nBar\\nBaz\\n\"},\n\n\t\t{str: \"Foo\\nBar\\nBaz\", pos: Bottom, height: 3, want: \"Foo\\nBar\\nBaz\"},\n\t\t{str: \"Foo\\nBar\\nBaz\", pos: Center, height: 3, want: \"Foo\\nBar\\nBaz\"},\n\t\t{str: \"Foo\\nBar\\nBaz\", pos: Top, height: 3, want: \"Foo\\nBar\\nBaz\"},\n\n\t\t{str: \"Foo\\n\\n\\n\\nBar\", pos: Bottom, height: 5, want: \"Foo\\n\\n\\n\\nBar\"},\n\t\t{str: \"Foo\\n\\n\\n\\nBar\", pos: Center, height: 5, want: \"Foo\\n\\n\\n\\nBar\"},\n\t\t{str: \"Foo\\n\\n\\n\\nBar\", pos: Top, height: 5, want: \"Foo\\n\\n\\n\\nBar\"},\n\n\t\t{str: \"Foo\\nBar\\nBaz\", pos: Center, height: 9, want: \"\\n\\n\\nFoo\\nBar\\nBaz\\n\\n\\n\"},\n\t\t{str: \"Foo\\nBar\\nBaz\", pos: Center, height: 10, want: \"\\n\\n\\nFoo\\nBar\\nBaz\\n\\n\\n\\n\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tgot := alignTextVertical(test.str, test.pos, test.height, nil)\n\t\tif got != test.want {\n\t\t\tt.Errorf(\"alignTextVertical(%q, %v, %d) = %q, want %q\", test.str, test.pos, test.height, got, test.want)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "ansi_unix.go",
    "content": "//go:build !windows\n\npackage lipgloss\n\nimport \"os\"\n\n// EnableLegacyWindowsANSI is only needed on Windows.\nfunc EnableLegacyWindowsANSI(*os.File) {}\n"
  },
  {
    "path": "ansi_windows.go",
    "content": "//go:build windows\n\npackage lipgloss\n\nimport (\n\t\"os\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\n// EnableLegacyWindowsANSI enables support for ANSI color sequences in the\n// Windows default console (cmd.exe and the PowerShell application). Note that\n// this only works with Windows 10 and greater. Also note that Windows Terminal\n// supports colors by default.\nfunc EnableLegacyWindowsANSI(f *os.File) {\n\tvar mode uint32\n\thandle := windows.Handle(f.Fd())\n\terr := windows.GetConsoleMode(handle, &mode)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// See https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences\n\tif mode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING {\n\t\tvtpmode := mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING\n\t\tif err := windows.SetConsoleMode(handle, vtpmode); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "blending.go",
    "content": "package lipgloss\n\nimport (\n\t\"image/color\"\n\t\"math\"\n\t\"slices\"\n\n\t\"github.com/lucasb-eyer/go-colorful\"\n)\n\n// Blend1D blends a series of colors together in one linear dimension using multiple\n// stops, into the provided number of steps. Uses the \"CIE L*, a*, b*\" (CIELAB) color-space.\n//\n// Note that if any of the provided colors are completely transparent, we will\n// assume that the alpha value was lost in conversion from RGB -> RGBA, and we\n// will set the alpha to opaque, as it's not possible to blend something completely\n// transparent.\nfunc Blend1D(steps int, stops ...color.Color) []color.Color {\n\tif steps < 0 {\n\t\tsteps = 0\n\t}\n\n\tif steps <= len(stops) {\n\t\treturn stops[:steps]\n\t}\n\n\t// Ensure they didn't provide any nil colors.\n\tstops = slices.DeleteFunc(stops, func(c color.Color) bool {\n\t\treturn c == nil\n\t})\n\n\tif len(stops) == 0 {\n\t\treturn nil // We can't safely fallback.\n\t}\n\n\t// If they only provided one valid color (or some nil colors), we will just return\n\t// an array of that color, for the amount of steps they requested.\n\tif len(stops) == 1 {\n\t\tsingleColor := stops[0]\n\t\tresult := make([]color.Color, steps)\n\t\tfor i := range result {\n\t\t\tresult[i] = singleColor\n\t\t}\n\t\treturn result\n\t}\n\n\tblended := make([]color.Color, steps)\n\n\t// Convert stops to colorful.Color once\n\tcstops := make([]colorful.Color, len(stops))\n\tfor i, k := range stops {\n\t\tcstops[i], _ = colorful.MakeColor(ensureNotTransparent(k))\n\t}\n\n\tnumSegments := len(cstops) - 1\n\tdefaultSize := steps / numSegments\n\tremainingSteps := steps % numSegments\n\n\tresultIndex := 0\n\tfor i := range numSegments {\n\t\tfrom := cstops[i]\n\t\tto := cstops[i+1]\n\n\t\t// Calculate segment size.\n\t\tsegmentSize := defaultSize\n\t\tif i < remainingSteps {\n\t\t\tsegmentSize++\n\t\t}\n\n\t\tdivisor := float64(segmentSize - 1)\n\n\t\t// Generate colors for this segment.\n\t\tfor j := 0; j < segmentSize; j++ {\n\t\t\tvar blendingFactor float64\n\t\t\tif segmentSize > 1 {\n\t\t\t\tblendingFactor = float64(j) / divisor\n\t\t\t}\n\t\t\tblended[resultIndex] = from.BlendLab(to, blendingFactor).Clamped()\n\t\t\tresultIndex++\n\t\t}\n\t}\n\n\treturn blended\n}\n\n// Blend2D blends a series of colors together in two linear dimensions using\n// multiple stops, into the provided width/height. Uses the \"CIE L*, a*, b*\" (CIELAB)\n// color-space. The angle parameter controls the rotation of the gradient (0-360°),\n// where 0° is left-to-right, 45° is bottom-left to top-right (diagonal). The function\n// returns colors in a 1D row-major order ([row1, row2, row3, ...]).\n//\n// Example of how to iterate over the result:\n//\n//\tgradient := colors.Blend2D(width, height, 180, color1, color2, color3, ...)\n//\tgradientContent := strings.Builder{}\n//\tfor y := range height {\n//\t\tfor x := range width {\n//\t\t\tindex := y*width + x\n//\t\t\tgradientContent.WriteString(\n//\t\t\t\tlipgloss.NewStyle().\n//\t\t\t\t\tBackground(gradient[index]).\n//\t\t\t\t\tRender(\" \"),\n//\t\t\t)\n//\t\t}\n//\t\tif y < height-1 { // End of row.\n//\t\t\tgradientContent.WriteString(\"\\n\")\n//\t\t}\n//\t}\n//\n// Note that if any of the provided colors are completely transparent, we will\n// assume that the alpha value was lost in conversion from RGB -> RGBA, and we\n// will set the alpha to opaque, as it's not possible to blend something completely\n// transparent.\nfunc Blend2D(width, height int, angle float64, stops ...color.Color) []color.Color {\n\tif width < 1 {\n\t\twidth = 1\n\t}\n\tif height < 1 {\n\t\theight = 1\n\t}\n\n\t// Normalize angle to 0-360.\n\tangle = math.Mod(angle, 360)\n\tif angle < 0 {\n\t\tangle += 360\n\t}\n\n\t// Ensure they didn't provide any nil colors.\n\tstops = slices.DeleteFunc(stops, func(c color.Color) bool {\n\t\treturn c == nil\n\t})\n\n\tif len(stops) == 0 {\n\t\treturn nil // We can't safely fallback.\n\t}\n\n\t// If they only provided one valid color (or some nil colors), we will just return\n\t// an array of that color, for the amount of pixels they requested.\n\tif len(stops) == 1 {\n\t\tsingleColor := stops[0]\n\t\tresult := make([]color.Color, width*height)\n\t\tfor i := range result {\n\t\t\tresult[i] = singleColor\n\t\t}\n\t\treturn result\n\t}\n\n\t// For 2D blending, we'll create a gradient along the diagonal and then sample\n\t// from it based on the angle. We'll use the maximum dimension to ensure we have\n\t// enough resolution for the gradient.\n\tdiagonalGradient := Blend1D(max(width, height), stops...)\n\n\tresult := make([]color.Color, width*height)\n\n\t// Calculate center point for rotation.\n\tcenterX := float64(width-1) / 2.0\n\tcenterY := float64(height-1) / 2.0\n\n\tangleRad := angle * math.Pi / 180.0 // -> radians.\n\n\t// Pre-calculate sin and cos.\n\tcosAngle := math.Cos(angleRad)\n\tsinAngle := math.Sin(angleRad)\n\n\t// Calculate diagonal length for proper gradient mapping.\n\tdiagonalLength := math.Sqrt(float64(width*width + height*height))\n\n\t// Pre-calculate gradient length for index calculation.\n\tgradientLen := float64(len(diagonalGradient) - 1)\n\n\tfor y := range height {\n\t\t// Calculate the distance from center along the gradient direction.\n\t\tdy := float64(y) - centerY\n\n\t\tfor x := 0; x < width; x++ {\n\t\t\t// Calculate the distance from center along the gradient direction.\n\t\t\tdx := float64(x) - centerX\n\n\t\t\trotX := dx*cosAngle - dy*sinAngle // Rotate the point by the angle.\n\n\t\t\t// Map the rotated position to the gradient. Normalize to 0-1 range based on\n\t\t\t// the diagonal length.\n\t\t\tgradientPos := clamp((rotX+diagonalLength/2.0)/diagonalLength, 0, 1)\n\n\t\t\t// Calculate the index in the gradient.\n\t\t\tgradientIndex := int(gradientPos * gradientLen)\n\t\t\tif gradientIndex >= len(diagonalGradient) {\n\t\t\t\tgradientIndex = len(diagonalGradient) - 1\n\t\t\t}\n\n\t\t\tresult[y*width+x] = diagonalGradient[gradientIndex] // -> row-major order.\n\t\t}\n\t}\n\n\treturn result\n}\n"
  },
  {
    "path": "blending_test.go",
    "content": "package lipgloss\n\nimport (\n\t\"image/color\"\n\t\"testing\"\n)\n\nfunc TestBlend1D(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tsteps    int\n\t\tstops    []color.Color\n\t\texpected []color.Color\n\t}{\n\t\t{\n\t\t\tname:  \"2-colors-10-steps\",\n\t\t\tsteps: 10,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpected: []color.Color{\n\t\t\t\t&color.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 246, G: 0, B: 45, A: 255},\n\t\t\t\t&color.RGBA{R: 235, G: 0, B: 73, A: 255},\n\t\t\t\t&color.RGBA{R: 223, G: 0, B: 99, A: 255},\n\t\t\t\t&color.RGBA{R: 210, G: 0, B: 124, A: 255},\n\t\t\t\t&color.RGBA{R: 193, G: 0, B: 149, A: 255},\n\t\t\t\t&color.RGBA{R: 173, G: 0, B: 175, A: 255},\n\t\t\t\t&color.RGBA{R: 147, G: 0, B: 201, A: 255},\n\t\t\t\t&color.RGBA{R: 109, G: 0, B: 228, A: 255},\n\t\t\t\t&color.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"3-colors-4-steps\",\n\t\t\tsteps: 4,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 255, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpected: []color.Color{\n\t\t\t\t&color.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 0, G: 255, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 0, G: 255, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"black-to-white-5-steps\",\n\t\t\tsteps: 5,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 255, G: 255, B: 255, A: 255},\n\t\t\t},\n\t\t\texpected: []color.Color{\n\t\t\t\t&color.RGBA{R: 0, G: 0, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 59, G: 59, B: 59, A: 255},\n\t\t\t\t&color.RGBA{R: 119, G: 119, B: 119, A: 255},\n\t\t\t\t&color.RGBA{R: 185, G: 185, B: 185, A: 255},\n\t\t\t\t&color.RGBA{R: 255, G: 255, B: 255, A: 255},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"4-colors-6-steps\",\n\t\t\tsteps: 6,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 255, G: 255, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 255, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpected: []color.Color{\n\t\t\t\t&color.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 255, G: 255, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 255, G: 255, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 0, G: 255, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 0, G: 255, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"2-steps-5-stops\",\n\t\t\tsteps: 2,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 255, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t\tcolor.RGBA{R: 255, G: 255, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 0, A: 255},\n\t\t\t},\n\t\t\texpected: []color.Color{\n\t\t\t\t&color.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 0, G: 255, B: 0, A: 255},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"3-steps-1-stop\",\n\t\t\tsteps: 3,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t},\n\t\t\texpected: []color.Color{\n\t\t\t\t&color.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\t&color.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"1-step-2-stops\",\n\t\t\tsteps: 1,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpected: []color.Color{\n\t\t\t\t&color.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"0-steps-0-stops\",\n\t\t\tsteps:    0,\n\t\t\tstops:    []color.Color{},\n\t\t\texpected: []color.Color{},\n\t\t},\n\t\t{\n\t\t\tname:  \"0-steps-1-stop\",\n\t\t\tsteps: 0,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t},\n\t\t\texpected: []color.Color{},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tgot := Blend1D(tt.steps, tt.stops...)\n\n\t\t\tif len(got) != len(tt.expected) {\n\t\t\t\tt.Errorf(\"Blend() = %v length, want %v length\", len(got), len(tt.expected))\n\t\t\t}\n\n\t\t\tfor i := range tt.expected {\n\t\t\t\texpectColorMatches(t, got[i], tt.expected[i])\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBlend2D(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\twidth, height  int\n\t\tangle          float64\n\t\tstops          []color.Color\n\t\texpectedLength int\n\t}{\n\t\t{\n\t\t\tname:   \"2x2-red-to-blue-0deg\",\n\t\t\twidth:  2,\n\t\t\theight: 2,\n\t\t\tangle:  0,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpectedLength: 4,\n\t\t},\n\t\t{\n\t\t\tname:   \"3x2-red-to-blue-90deg\",\n\t\t\twidth:  3,\n\t\t\theight: 2,\n\t\t\tangle:  90,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpectedLength: 6,\n\t\t},\n\t\t{\n\t\t\tname:   \"2x3-red-to-blue-180deg\",\n\t\t\twidth:  2,\n\t\t\theight: 3,\n\t\t\tangle:  180,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpectedLength: 6,\n\t\t},\n\t\t{\n\t\t\tname:   \"2x2-red-to-blue-270deg\",\n\t\t\twidth:  2,\n\t\t\theight: 2,\n\t\t\tangle:  270,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpectedLength: 4,\n\t\t},\n\t\t{\n\t\t\tname:   \"1x1-single-color\",\n\t\t\twidth:  1,\n\t\t\theight: 1,\n\t\t\tangle:  0,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t},\n\t\t\texpectedLength: 1,\n\t\t},\n\t\t{\n\t\t\tname:   \"3-colors-2x2-0deg\",\n\t\t\twidth:  2,\n\t\t\theight: 2,\n\t\t\tangle:  0,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 255, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpectedLength: 4,\n\t\t},\n\t\t{\n\t\t\tname:   \"invalid-dimensions-fallback\",\n\t\t\twidth:  0,\n\t\t\theight: -1,\n\t\t\tangle:  0,\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t},\n\t\t\texpectedLength: 1,\n\t\t},\n\t\t{\n\t\t\tname:   \"angle-normalization-450\",\n\t\t\twidth:  2,\n\t\t\theight: 2,\n\t\t\tangle:  450, // Should normalize to 90\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpectedLength: 4,\n\t\t},\n\t\t{\n\t\t\tname:   \"negative-angle-normalization\",\n\t\t\twidth:  2,\n\t\t\theight: 2,\n\t\t\tangle:  -90, // Should normalize to 270\n\t\t\tstops: []color.Color{\n\t\t\t\tcolor.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\t\tcolor.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\t},\n\t\t\texpectedLength: 4,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tgot := Blend2D(tt.width, tt.height, tt.angle, tt.stops...)\n\n\t\t\tif len(got) != tt.expectedLength {\n\t\t\t\tt.Errorf(\"Blend2D() = %v length, want %v length\", len(got), tt.expectedLength)\n\t\t\t}\n\n\t\t\t// Verify row-major order by checking that the width matches.\n\t\t\tif tt.width > 0 && tt.height > 0 {\n\t\t\t\texpectedTotal := max(tt.width, 1) * max(tt.height, 1)\n\t\t\t\tif len(got) != expectedTotal {\n\t\t\t\t\tt.Errorf(\"Blend2D() total pixels = %v, want %v\", len(got), expectedTotal)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Verify that we have valid colors (not nil).\n\t\t\tfor i, color := range got {\n\t\t\t\tif color == nil {\n\t\t\t\t\tt.Errorf(\"Blend2D() color at index %d is nil\", i)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// For single color tests, verify all colors are the same.\n\t\t\tif len(tt.stops) == 1 && len(got) > 0 {\n\t\t\t\tfirstColor := got[0]\n\t\t\t\tfor _, color := range got {\n\t\t\t\t\texpectColorMatches(t, color, firstColor)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBlend2DEdgeCases(t *testing.T) {\n\tt.Run(\"nil-stops\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tgot := Blend2D(2, 2, 0, nil, nil)\n\t\tif got != nil {\n\t\t\tt.Errorf(\"Blend2D() with nil stops = %v, want nil\", got)\n\t\t}\n\t})\n\n\tt.Run(\"empty-stops\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tgot := Blend2D(2, 2, 0)\n\t\tif got != nil {\n\t\t\tt.Errorf(\"Blend2D() with empty stops = %v, want nil\", got)\n\t\t}\n\t})\n\n\tt.Run(\"nil-color-in-stops\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tgot := Blend2D(2, 2, 0, color.RGBA{R: 255, G: 0, B: 0, A: 255}, nil, color.RGBA{R: 0, G: 0, B: 255, A: 255})\n\t\tif len(got) != 4 {\n\t\t\tt.Errorf(\"Blend2D() with nil color in stops = %v length, want 4\", len(got))\n\t\t}\n\t\t// Should still work with the non-nil colors and produce valid colors\n\t\tfor i, color := range got {\n\t\t\tif color == nil {\n\t\t\t\tt.Errorf(\"Blend2D() color at index %d is nil\", i)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc BenchmarkBlend1D(b *testing.B) {\n\tstops := []color.Color{\n\t\thex(\"#FF0000\"), // Red\n\t\thex(\"#00FF00\"), // Green\n\t\thex(\"#0000FF\"), // Blue\n\t\thex(\"#FFFF00\"), // Yellow\n\t\thex(\"#FF00FF\"), // Magenta\n\t}\n\n\tfor b.Loop() {\n\t\tBlend1D(100, stops...)\n\t}\n}\n\nfunc BenchmarkBlend2D(b *testing.B) {\n\tstops := []color.Color{\n\t\thex(\"#FF0000\"), // Red\n\t\thex(\"#00FF00\"), // Green\n\t\thex(\"#0000FF\"), // Blue\n\t\thex(\"#FFFF00\"), // Yellow\n\t\thex(\"#FF00FF\"), // Magenta\n\t}\n\n\tfor b.Loop() {\n\t\tBlend2D(100, 50, 45, stops...)\n\t}\n}\n"
  },
  {
    "path": "borders.go",
    "content": "package lipgloss\n\nimport (\n\t\"image/color\"\n\t\"slices\"\n\t\"strings\"\n\t\"unicode/utf8\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n\t\"github.com/clipperhouse/displaywidth\"\n\t\"github.com/rivo/uniseg\"\n)\n\n// Border contains a series of values which comprise the various parts of a\n// border.\ntype Border struct {\n\tTop          string\n\tBottom       string\n\tLeft         string\n\tRight        string\n\tTopLeft      string\n\tTopRight     string\n\tBottomLeft   string\n\tBottomRight  string\n\tMiddleLeft   string\n\tMiddleRight  string\n\tMiddle       string\n\tMiddleTop    string\n\tMiddleBottom string\n}\n\n// GetTopSize returns the width of the top border. If borders contain runes of\n// varying widths, the widest rune is returned. If no border exists on the top\n// edge, 0 is returned.\nfunc (b Border) GetTopSize() int {\n\treturn getBorderEdgeWidth(b.TopLeft, b.Top, b.TopRight)\n}\n\n// GetRightSize returns the width of the right border. If borders contain\n// runes of varying widths, the widest rune is returned. If no border exists on\n// the right edge, 0 is returned.\nfunc (b Border) GetRightSize() int {\n\treturn getBorderEdgeWidth(b.TopRight, b.Right, b.BottomRight)\n}\n\n// GetBottomSize returns the width of the bottom border. If borders contain\n// runes of varying widths, the widest rune is returned. If no border exists on\n// the bottom edge, 0 is returned.\nfunc (b Border) GetBottomSize() int {\n\treturn getBorderEdgeWidth(b.BottomLeft, b.Bottom, b.BottomRight)\n}\n\n// GetLeftSize returns the width of the left border. If borders contain runes\n// of varying widths, the widest rune is returned. If no border exists on the\n// left edge, 0 is returned.\nfunc (b Border) GetLeftSize() int {\n\treturn getBorderEdgeWidth(b.TopLeft, b.Left, b.BottomLeft)\n}\n\nfunc getBorderEdgeWidth(borderParts ...string) (maxWidth int) {\n\tfor _, piece := range borderParts {\n\t\tmaxWidth = max(maxWidth, maxRuneWidth(piece))\n\t}\n\treturn maxWidth\n}\n\nvar (\n\tnoBorder = Border{}\n\n\tnormalBorder = Border{\n\t\tTop:          \"─\",\n\t\tBottom:       \"─\",\n\t\tLeft:         \"│\",\n\t\tRight:        \"│\",\n\t\tTopLeft:      \"┌\",\n\t\tTopRight:     \"┐\",\n\t\tBottomLeft:   \"└\",\n\t\tBottomRight:  \"┘\",\n\t\tMiddleLeft:   \"├\",\n\t\tMiddleRight:  \"┤\",\n\t\tMiddle:       \"┼\",\n\t\tMiddleTop:    \"┬\",\n\t\tMiddleBottom: \"┴\",\n\t}\n\n\troundedBorder = Border{\n\t\tTop:          \"─\",\n\t\tBottom:       \"─\",\n\t\tLeft:         \"│\",\n\t\tRight:        \"│\",\n\t\tTopLeft:      \"╭\",\n\t\tTopRight:     \"╮\",\n\t\tBottomLeft:   \"╰\",\n\t\tBottomRight:  \"╯\",\n\t\tMiddleLeft:   \"├\",\n\t\tMiddleRight:  \"┤\",\n\t\tMiddle:       \"┼\",\n\t\tMiddleTop:    \"┬\",\n\t\tMiddleBottom: \"┴\",\n\t}\n\n\tblockBorder = Border{\n\t\tTop:          \"█\",\n\t\tBottom:       \"█\",\n\t\tLeft:         \"█\",\n\t\tRight:        \"█\",\n\t\tTopLeft:      \"█\",\n\t\tTopRight:     \"█\",\n\t\tBottomLeft:   \"█\",\n\t\tBottomRight:  \"█\",\n\t\tMiddleLeft:   \"█\",\n\t\tMiddleRight:  \"█\",\n\t\tMiddle:       \"█\",\n\t\tMiddleTop:    \"█\",\n\t\tMiddleBottom: \"█\",\n\t}\n\n\touterHalfBlockBorder = Border{\n\t\tTop:         \"▀\",\n\t\tBottom:      \"▄\",\n\t\tLeft:        \"▌\",\n\t\tRight:       \"▐\",\n\t\tTopLeft:     \"▛\",\n\t\tTopRight:    \"▜\",\n\t\tBottomLeft:  \"▙\",\n\t\tBottomRight: \"▟\",\n\t}\n\n\tinnerHalfBlockBorder = Border{\n\t\tTop:         \"▄\",\n\t\tBottom:      \"▀\",\n\t\tLeft:        \"▐\",\n\t\tRight:       \"▌\",\n\t\tTopLeft:     \"▗\",\n\t\tTopRight:    \"▖\",\n\t\tBottomLeft:  \"▝\",\n\t\tBottomRight: \"▘\",\n\t}\n\n\tthickBorder = Border{\n\t\tTop:          \"━\",\n\t\tBottom:       \"━\",\n\t\tLeft:         \"┃\",\n\t\tRight:        \"┃\",\n\t\tTopLeft:      \"┏\",\n\t\tTopRight:     \"┓\",\n\t\tBottomLeft:   \"┗\",\n\t\tBottomRight:  \"┛\",\n\t\tMiddleLeft:   \"┣\",\n\t\tMiddleRight:  \"┫\",\n\t\tMiddle:       \"╋\",\n\t\tMiddleTop:    \"┳\",\n\t\tMiddleBottom: \"┻\",\n\t}\n\n\tdoubleBorder = Border{\n\t\tTop:          \"═\",\n\t\tBottom:       \"═\",\n\t\tLeft:         \"║\",\n\t\tRight:        \"║\",\n\t\tTopLeft:      \"╔\",\n\t\tTopRight:     \"╗\",\n\t\tBottomLeft:   \"╚\",\n\t\tBottomRight:  \"╝\",\n\t\tMiddleLeft:   \"╠\",\n\t\tMiddleRight:  \"╣\",\n\t\tMiddle:       \"╬\",\n\t\tMiddleTop:    \"╦\",\n\t\tMiddleBottom: \"╩\",\n\t}\n\n\thiddenBorder = Border{\n\t\tTop:          \" \",\n\t\tBottom:       \" \",\n\t\tLeft:         \" \",\n\t\tRight:        \" \",\n\t\tTopLeft:      \" \",\n\t\tTopRight:     \" \",\n\t\tBottomLeft:   \" \",\n\t\tBottomRight:  \" \",\n\t\tMiddleLeft:   \" \",\n\t\tMiddleRight:  \" \",\n\t\tMiddle:       \" \",\n\t\tMiddleTop:    \" \",\n\t\tMiddleBottom: \" \",\n\t}\n\n\tmarkdownBorder = Border{\n\t\tTop:          \"-\",\n\t\tBottom:       \"-\",\n\t\tLeft:         \"|\",\n\t\tRight:        \"|\",\n\t\tTopLeft:      \"|\",\n\t\tTopRight:     \"|\",\n\t\tBottomLeft:   \"|\",\n\t\tBottomRight:  \"|\",\n\t\tMiddleLeft:   \"|\",\n\t\tMiddleRight:  \"|\",\n\t\tMiddle:       \"|\",\n\t\tMiddleTop:    \"|\",\n\t\tMiddleBottom: \"|\",\n\t}\n\n\tasciiBorder = Border{\n\t\tTop:          \"-\",\n\t\tBottom:       \"-\",\n\t\tLeft:         \"|\",\n\t\tRight:        \"|\",\n\t\tTopLeft:      \"+\",\n\t\tTopRight:     \"+\",\n\t\tBottomLeft:   \"+\",\n\t\tBottomRight:  \"+\",\n\t\tMiddleLeft:   \"+\",\n\t\tMiddleRight:  \"+\",\n\t\tMiddle:       \"+\",\n\t\tMiddleTop:    \"+\",\n\t\tMiddleBottom: \"+\",\n\t}\n)\n\n// NormalBorder returns a standard-type border with a normal weight and 90\n// degree corners.\nfunc NormalBorder() Border {\n\treturn normalBorder\n}\n\n// RoundedBorder returns a border with rounded corners.\nfunc RoundedBorder() Border {\n\treturn roundedBorder\n}\n\n// BlockBorder returns a border that takes the whole block.\nfunc BlockBorder() Border {\n\treturn blockBorder\n}\n\n// OuterHalfBlockBorder returns a half-block border that sits outside the frame.\nfunc OuterHalfBlockBorder() Border {\n\treturn outerHalfBlockBorder\n}\n\n// InnerHalfBlockBorder returns a half-block border that sits inside the frame.\nfunc InnerHalfBlockBorder() Border {\n\treturn innerHalfBlockBorder\n}\n\n// ThickBorder returns a border that's thicker than the one returned by\n// NormalBorder.\nfunc ThickBorder() Border {\n\treturn thickBorder\n}\n\n// DoubleBorder returns a border comprised of two thin strokes.\nfunc DoubleBorder() Border {\n\treturn doubleBorder\n}\n\n// HiddenBorder returns a border that renders as a series of single-cell\n// spaces. It's useful for cases when you want to remove a standard border but\n// maintain layout positioning. This said, you can still apply a background\n// color to a hidden border.\nfunc HiddenBorder() Border {\n\treturn hiddenBorder\n}\n\n// MarkdownBorder return a table border in markdown style.\n//\n// Make sure to disable top and bottom border for the best result. This will\n// ensure that the output is valid markdown.\n//\n//\ttable.New().Border(lipgloss.MarkdownBorder()).BorderTop(false).BorderBottom(false)\nfunc MarkdownBorder() Border {\n\treturn markdownBorder\n}\n\n// ASCIIBorder returns a table border with ASCII characters.\nfunc ASCIIBorder() Border {\n\treturn asciiBorder\n}\n\ntype borderBlend struct {\n\ttopGradient    []color.Color\n\trightGradient  []color.Color\n\tbottomGradient []color.Color\n\tleftGradient   []color.Color\n}\n\nfunc (s Style) borderBlend(width, height int, colors ...color.Color) *borderBlend {\n\tgradient := Blend1D(\n\t\t(height+width+2)*2,\n\t\tcolors...,\n\t)\n\n\t// Rotate array forward or reverse based on the offset if provided.\n\tif r := -s.getAsInt(borderForegroundBlendOffsetKey); r != 0 {\n\t\tn := len(gradient)\n\t\tr %= n\n\t\tif r < 0 {\n\t\t\tr += n\n\t\t}\n\t\tslices.Reverse(gradient[:r])\n\t\tslices.Reverse(gradient[r:])\n\t\tslices.Reverse(gradient)\n\t}\n\n\toffset := 0\n\tgetFromOffset := func(size int) (s []color.Color) {\n\t\ts = gradient[offset : offset+size]\n\t\toffset += size\n\t\treturn s\n\t}\n\n\tblend := &borderBlend{\n\t\ttopGradient:    getFromOffset(width + 2),\n\t\trightGradient:  getFromOffset(height),\n\t\tbottomGradient: getFromOffset(width + 2),\n\t\tleftGradient:   getFromOffset(height),\n\t}\n\n\t// bottom and left gradients are reversed because they are drawn in reverse order.\n\tslices.Reverse(blend.bottomGradient)\n\tslices.Reverse(blend.leftGradient)\n\n\treturn blend\n}\n\nfunc (s Style) applyBorder(str string) string {\n\tvar (\n\t\tborder    = s.getBorderStyle()\n\t\thasTop    = s.getAsBool(borderTopKey, false)\n\t\thasRight  = s.getAsBool(borderRightKey, false)\n\t\thasBottom = s.getAsBool(borderBottomKey, false)\n\t\thasLeft   = s.getAsBool(borderLeftKey, false)\n\t)\n\n\t// If a border is set and no sides have been specifically turned on or off\n\t// render borders on all sides.\n\tif s.isBorderStyleSetWithoutSides() {\n\t\thasTop = true\n\t\thasRight = true\n\t\thasBottom = true\n\t\thasLeft = true\n\t}\n\n\t// If no border is set or all borders are been disabled, abort.\n\tif border == noBorder || (!hasTop && !hasRight && !hasBottom && !hasLeft) {\n\t\treturn str\n\t}\n\n\tlines, width := getLines(str)\n\n\tif hasLeft {\n\t\tif border.Left == \"\" {\n\t\t\tborder.Left = \" \"\n\t\t}\n\t\twidth += maxRuneWidth(border.Left)\n\t}\n\n\tif hasRight {\n\t\tif border.Right == \"\" {\n\t\t\tborder.Right = \" \"\n\t\t}\n\t\twidth += maxRuneWidth(border.Right)\n\t}\n\n\t// If corners should be rendered but are set with the empty string, fill them\n\t// with a single space.\n\tif hasTop && hasLeft && border.TopLeft == \"\" {\n\t\tborder.TopLeft = \" \"\n\t}\n\tif hasTop && hasRight && border.TopRight == \"\" {\n\t\tborder.TopRight = \" \"\n\t}\n\tif hasBottom && hasLeft && border.BottomLeft == \"\" {\n\t\tborder.BottomLeft = \" \"\n\t}\n\tif hasBottom && hasRight && border.BottomRight == \"\" {\n\t\tborder.BottomRight = \" \"\n\t}\n\n\t// Figure out which corners we should actually be using based on which\n\t// sides are set to show.\n\tif hasTop {\n\t\tswitch {\n\t\tcase !hasLeft && !hasRight:\n\t\t\tborder.TopLeft = \"\"\n\t\t\tborder.TopRight = \"\"\n\t\tcase !hasLeft:\n\t\t\tborder.TopLeft = \"\"\n\t\tcase !hasRight:\n\t\t\tborder.TopRight = \"\"\n\t\t}\n\t}\n\tif hasBottom {\n\t\tswitch {\n\t\tcase !hasLeft && !hasRight:\n\t\t\tborder.BottomLeft = \"\"\n\t\t\tborder.BottomRight = \"\"\n\t\tcase !hasLeft:\n\t\t\tborder.BottomLeft = \"\"\n\t\tcase !hasRight:\n\t\t\tborder.BottomRight = \"\"\n\t\t}\n\t}\n\n\t// For now, limit corners to one rune.\n\tborder.TopLeft = getFirstRuneAsString(border.TopLeft)\n\tborder.TopRight = getFirstRuneAsString(border.TopRight)\n\tborder.BottomRight = getFirstRuneAsString(border.BottomRight)\n\tborder.BottomLeft = getFirstRuneAsString(border.BottomLeft)\n\n\tvar topFG, rightFG, bottomFG, leftFG color.Color\n\tvar (\n\t\tblendFG  = s.getAsColors(borderForegroundBlendKey)\n\t\ttopBG    = s.getAsColor(borderTopBackgroundKey)\n\t\trightBG  = s.getAsColor(borderRightBackgroundKey)\n\t\tbottomBG = s.getAsColor(borderBottomBackgroundKey)\n\t\tleftBG   = s.getAsColor(borderLeftBackgroundKey)\n\t)\n\n\tvar blend *borderBlend\n\tif len(blendFG) > 0 {\n\t\tblend = s.borderBlend(width, len(lines), blendFG...)\n\t} else {\n\t\ttopFG = s.getAsColor(borderTopForegroundKey)\n\t\trightFG = s.getAsColor(borderRightForegroundKey)\n\t\tbottomFG = s.getAsColor(borderBottomForegroundKey)\n\t\tleftFG = s.getAsColor(borderLeftForegroundKey)\n\t}\n\n\tvar out strings.Builder\n\n\t// Render top\n\tif hasTop {\n\t\ttop := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width)\n\t\tif blend != nil {\n\t\t\tout.WriteString(s.styleBorderBlend(top, blend.topGradient, topBG))\n\t\t} else {\n\t\t\tout.WriteString(s.styleBorder(top, topFG, topBG))\n\t\t}\n\t\tout.WriteRune('\\n')\n\t}\n\n\tleftRunes := []rune(border.Left)\n\tleftIndex := 0\n\n\trightRunes := []rune(border.Right)\n\trightIndex := 0\n\n\t// Render sides\n\tvar r string\n\tfor i, l := range lines {\n\t\tif hasLeft {\n\t\t\tr = string(leftRunes[leftIndex])\n\t\t\tleftIndex++\n\t\t\tif leftIndex >= len(leftRunes) {\n\t\t\t\tleftIndex = 0\n\t\t\t}\n\t\t\tif blend != nil {\n\t\t\t\tout.WriteString(s.styleBorder(r, blend.leftGradient[i], leftBG))\n\t\t\t} else {\n\t\t\t\tout.WriteString(s.styleBorder(r, leftFG, leftBG))\n\t\t\t}\n\t\t}\n\t\tout.WriteString(l)\n\t\tif hasRight {\n\t\t\tr = string(rightRunes[rightIndex])\n\t\t\trightIndex++\n\t\t\tif rightIndex >= len(rightRunes) {\n\t\t\t\trightIndex = 0\n\t\t\t}\n\t\t\tif blend != nil {\n\t\t\t\tout.WriteString(s.styleBorder(r, blend.rightGradient[i], rightBG))\n\t\t\t} else {\n\t\t\t\tout.WriteString(s.styleBorder(r, rightFG, rightBG))\n\t\t\t}\n\t\t}\n\t\tif i < len(lines)-1 {\n\t\t\tout.WriteRune('\\n')\n\t\t}\n\t}\n\n\t// Render bottom\n\tif hasBottom {\n\t\tbottom := renderHorizontalEdge(border.BottomLeft, border.Bottom, border.BottomRight, width)\n\t\tout.WriteRune('\\n')\n\t\tif blend != nil {\n\t\t\tout.WriteString(s.styleBorderBlend(bottom, blend.bottomGradient, bottomBG))\n\t\t} else {\n\t\t\tout.WriteString(s.styleBorder(bottom, bottomFG, bottomBG))\n\t\t}\n\t}\n\n\treturn out.String()\n}\n\n// Render the horizontal (top or bottom) portion of a border.\nfunc renderHorizontalEdge(left, middle, right string, width int) string {\n\tif middle == \"\" {\n\t\tmiddle = \" \"\n\t}\n\n\tleftWidth := ansi.StringWidth(left)\n\trightWidth := ansi.StringWidth(right)\n\n\trunes := []rune(middle)\n\tj := 0\n\n\tout := strings.Builder{}\n\tout.WriteString(left)\n\n\tfor i := 0; i < width-leftWidth-rightWidth; {\n\t\tr := runes[j]\n\t\tout.WriteRune(r)\n\t\ti += ansi.StringWidth(string(r))\n\t\tj++\n\t\tif j >= len(runes) {\n\t\t\tj = 0\n\t\t}\n\t}\n\n\tout.WriteString(right)\n\treturn out.String()\n}\n\n// styleBorder applies foreground and background styling to a border.\nfunc (s Style) styleBorder(border string, fg, bg color.Color) string {\n\tif fg == noColor && bg == noColor {\n\t\treturn border\n\t}\n\tvar style ansi.Style\n\tif fg != noColor {\n\t\tstyle = style.ForegroundColor(fg)\n\t}\n\tif bg != noColor {\n\t\tstyle = style.BackgroundColor(bg)\n\t}\n\treturn style.Styled(border)\n}\n\n// styleBorderBlend applies foreground and background styling to a border, using blending.\nfunc (s Style) styleBorderBlend(border string, fg []color.Color, bg color.Color) string {\n\tvar out strings.Builder\n\tvar style ansi.Style\n\tvar i int\n\n\tgr := uniseg.NewGraphemes(border)\n\tfor gr.Next() {\n\t\tstyle = style[:0]\n\t\tif fg[i] != noColor {\n\t\t\tstyle = style.ForegroundColor(fg[i])\n\t\t}\n\t\tif bg != noColor {\n\t\t\tstyle = style.BackgroundColor(bg)\n\t\t}\n\t\t_, _ = out.WriteString(style.String())\n\t\t_, _ = out.Write(gr.Bytes())\n\t\ti++\n\t}\n\t_, _ = out.WriteString(ansi.ResetStyle)\n\treturn out.String()\n}\n\nfunc maxRuneWidth(str string) int {\n\tswitch len(str) {\n\tcase 0:\n\t\treturn 0\n\tcase 1:\n\t\treturn displaywidth.String(str)\n\t}\n\n\tvar width int\n\n\tg := displaywidth.StringGraphemes(str)\n\tfor g.Next() {\n\t\twidth = max(width, g.Width())\n\t}\n\treturn width\n}\n\nfunc getFirstRuneAsString(str string) string {\n\tif str == \"\" {\n\t\treturn str\n\t}\n\t_, size := utf8.DecodeRuneInString(str)\n\treturn str[:size]\n}\n"
  },
  {
    "path": "borders_test.go",
    "content": "package lipgloss\n\nimport (\n\t\"testing\"\n\n\t\"github.com/rivo/uniseg\"\n)\n\nfunc BenchmarkBorderRendering(b *testing.B) {\n\tdimensions := []struct {\n\t\tname   string\n\t\twidth  int\n\t\theight int\n\t}{\n\t\t{\"10x5\", 10, 5},\n\t\t{\"20x10\", 20, 10},\n\t\t{\"40x20\", 40, 15},\n\t\t{\"80x40\", 80, 20},\n\t\t{\"120x60\", 120, 25},\n\t\t{\"160x80\", 160, 30},\n\t}\n\n\tfor _, dim := range dimensions {\n\t\tb.Run(dim.name, func(b *testing.B) {\n\t\t\tstyle := NewStyle().\n\t\t\t\tBorder(RoundedBorder(), true).\n\t\t\t\tForeground(Color(\"#ffffff\")).\n\t\t\t\tBackground(Color(\"#000000\")).\n\t\t\t\tWidth(dim.width).\n\t\t\t\tHeight(dim.height)\n\n\t\t\tb.ResetTimer()\n\t\t\tfor b.Loop() {\n\t\t\t\t_ = style.Render(\"\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkBorderBlend(b *testing.B) {\n\tdimensions := []struct {\n\t\tname   string\n\t\twidth  int\n\t\theight int\n\t}{\n\t\t{\"10x5\", 10, 5},\n\t\t{\"20x10\", 20, 10},\n\t\t{\"40x20\", 40, 15},\n\t\t{\"80x40\", 80, 20},\n\t\t{\"120x60\", 120, 25},\n\t\t{\"160x80\", 160, 30},\n\t}\n\n\tfor _, dim := range dimensions {\n\t\tb.Run(dim.name, func(b *testing.B) {\n\t\t\tstyle := NewStyle().\n\t\t\t\tBorder(RoundedBorder(), true).\n\t\t\t\tBorderForegroundBlend(\n\t\t\t\t\tColor(\"#00FA68\"),\n\t\t\t\t\tColor(\"#9900FF\"),\n\t\t\t\t\tColor(\"#ED5353\"),\n\t\t\t\t).\n\t\t\t\tWidth(dim.width).\n\t\t\t\tHeight(dim.height)\n\n\t\t\tb.ResetTimer()\n\t\t\tfor b.Loop() {\n\t\t\t\t_ = style.Render(\"\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkBorderRenderingNoColors(b *testing.B) {\n\tdimensions := []struct {\n\t\tname   string\n\t\twidth  int\n\t\theight int\n\t}{\n\t\t{\"10x5\", 10, 5},\n\t\t{\"20x10\", 20, 10},\n\t\t{\"40x20\", 40, 15},\n\t\t{\"80x40\", 80, 20},\n\t\t{\"120x60\", 120, 25},\n\t\t{\"160x80\", 160, 30},\n\t}\n\n\tfor _, dim := range dimensions {\n\t\tb.Run(dim.name, func(b *testing.B) {\n\t\t\tstyle := NewStyle().\n\t\t\t\tBorder(RoundedBorder(), true).\n\t\t\t\tWidth(dim.width).\n\t\t\t\tHeight(dim.height)\n\n\t\t\tb.ResetTimer()\n\t\t\tfor b.Loop() {\n\t\t\t\t_ = style.Render(\"\")\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Old implementation using rune slice conversion\nfunc getFirstRuneAsStringOld(str string) string {\n\tif str == \"\" {\n\t\treturn str\n\t}\n\tr := []rune(str)\n\treturn string(r[0])\n}\n\nfunc TestGetFirstRuneAsString(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  string\n\t}{\n\t\t{\"Empty\", \"\", \"\"},\n\t\t{\"SingleASCII\", \"A\", \"A\"},\n\t\t{\"SingleUnicode\", \"世\", \"世\"},\n\t\t{\"ASCIIString\", \"Hello\", \"H\"},\n\t\t{\"UnicodeString\", \"你好世界\", \"你\"},\n\t\t{\"MixedASCIIFirst\", \"Hello世界\", \"H\"},\n\t\t{\"MixedUnicodeFirst\", \"世界Hello\", \"世\"},\n\t\t{\"Emoji\", \"😀Happy\", \"😀\"},\n\t\t{\"MultiByteFirst\", \"ñoño\", \"ñ\"},\n\t\t{\"LongString\", \"The quick brown fox jumps over the lazy dog\", \"T\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := getFirstRuneAsString(tt.input)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"getFirstRuneAsString(%q) = %q, want %q\", tt.input, got, tt.want)\n\t\t\t}\n\n\t\t\t// Verify new implementation matches old implementation\n\t\t\told := getFirstRuneAsStringOld(tt.input)\n\t\t\tif got != old {\n\t\t\t\tt.Errorf(\"getFirstRuneAsString(%q) = %q, but old implementation returns %q\", tt.input, got, old)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkGetFirstRuneAsString(b *testing.B) {\n\ttestCases := []struct {\n\t\tname string\n\t\tstr  string\n\t}{\n\t\t{\"ASCII\", \"Hello, World!\"},\n\t\t{\"Unicode\", \"你好世界\"},\n\t\t{\"Single\", \"A\"},\n\t\t{\"Empty\", \"\"},\n\t}\n\n\tb.Run(\"Old\", func(b *testing.B) {\n\t\tfor _, tc := range testCases {\n\t\t\tb.Run(tc.name, func(b *testing.B) {\n\t\t\t\tb.ReportAllocs()\n\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\t_ = getFirstRuneAsStringOld(tc.str)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n\n\tb.Run(\"New\", func(b *testing.B) {\n\t\tfor _, tc := range testCases {\n\t\t\tb.Run(tc.name, func(b *testing.B) {\n\t\t\t\tb.ReportAllocs()\n\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\t_ = getFirstRuneAsString(tc.str)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc BenchmarkMaxRuneWidth(b *testing.B) {\n\ttestCases := []struct {\n\t\tname string\n\t\tstr  string\n\t}{\n\t\t{\"Blank\", \" \"},\n\t\t{\"ASCII\", \"+\"},\n\t\t{\"Markdown\", \"|\"},\n\t\t{\"Normal\", \"├\"},\n\t\t{\"Rounded\", \"╭\"},\n\t\t{\"Block\", \"█\"},\n\t\t{\"Emoji\", \"😀\"},\n\t}\n\tfor _, tc := range testCases {\n\t\tb.Run(tc.name, func(b *testing.B) {\n\t\t\tb.Run(\"Before\", func(b *testing.B) {\n\t\t\t\tb.ReportAllocs()\n\t\t\t\tfor b.Loop() {\n\t\t\t\t\t_ = maxRuneWidthOld(tc.str)\n\t\t\t\t}\n\t\t\t})\n\t\t\tb.Run(\"After\", func(b *testing.B) {\n\t\t\t\tb.ReportAllocs()\n\t\t\t\tfor b.Loop() {\n\t\t\t\t\t_ = maxRuneWidth(tc.str)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc maxRuneWidthOld(str string) int {\n\tvar width int\n\n\tstate := -1\n\tfor len(str) > 0 {\n\t\tvar w int\n\t\t_, str, w, state = uniseg.FirstGraphemeClusterInString(str, state)\n\t\tif w > width {\n\t\t\twidth = w\n\t\t}\n\t}\n\n\treturn width\n}\n"
  },
  {
    "path": "canvas.go",
    "content": "package lipgloss\n\nimport (\n\tuv \"github.com/charmbracelet/ultraviolet\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// Canvas is a cell-buffer that can be used to compose and draw [uv.Drawable]s\n// like [Layer]s.\n//\n// Composed drawables are drawn onto the canvas in the order they were\n// composed, meaning later drawables will appear \"on top\" of earlier ones.\n//\n// A canvas can read, modify, and render its cell contents.\n//\n// It implements [uv.Screen] and [uv.Drawable].\ntype Canvas struct {\n\tscr uv.ScreenBuffer\n}\n\nvar _ uv.Screen = (*Canvas)(nil)\n\n// NewCanvas creates a new [Canvas] with the given size.\nfunc NewCanvas(width, height int) *Canvas {\n\tc := new(Canvas)\n\tc.scr = uv.NewScreenBuffer(width, height)\n\tc.scr.Method = ansi.GraphemeWidth\n\treturn c\n}\n\n// Resize resizes the canvas to the given width and height.\nfunc (c *Canvas) Resize(width, height int) {\n\tc.scr.Resize(width, height)\n}\n\n// Clear clears the canvas.\nfunc (c *Canvas) Clear() {\n\tc.scr.Clear()\n}\n\n// Bounds implements [uv.Screen].\nfunc (c *Canvas) Bounds() uv.Rectangle {\n\treturn c.scr.Bounds()\n}\n\n// Width returns the width of the canvas.\nfunc (c *Canvas) Width() int {\n\treturn c.scr.Width()\n}\n\n// Height returns the height of the canvas.\nfunc (c *Canvas) Height() int {\n\treturn c.scr.Height()\n}\n\n// CellAt implements [uv.Screen].\nfunc (c *Canvas) CellAt(x int, y int) *uv.Cell {\n\treturn c.scr.CellAt(x, y)\n}\n\n// SetCell implements [uv.Screen].\nfunc (c *Canvas) SetCell(x int, y int, cell *uv.Cell) {\n\tc.scr.SetCell(x, y, cell)\n}\n\n// WidthMethod implements [uv.Screen].\nfunc (c *Canvas) WidthMethod() uv.WidthMethod {\n\treturn c.scr.WidthMethod()\n}\n\n// Compose composes a [Layer] or any [uv.Drawable] onto the [Canvas].\nfunc (c *Canvas) Compose(drawer uv.Drawable) *Canvas {\n\tdrawer.Draw(c, c.Bounds())\n\treturn c\n}\n\n// Draw draws the [Canvas] onto the given [uv.Screen] within the specified\n// area.\n//\n// It implements [uv.Drawable].\nfunc (c *Canvas) Draw(scr uv.Screen, area uv.Rectangle) {\n\tc.scr.Draw(scr, area)\n}\n\n// Render renders the canvas into a styled string.\nfunc (c *Canvas) Render() string {\n\treturn c.scr.Render()\n}\n"
  },
  {
    "path": "canvas_test.go",
    "content": "package lipgloss\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestCanvasRender(t *testing.T) {\n\tc := NewCanvas(5, 3)\n\n\t// Fill the canvas with dots\n\tfor y := 0; y < c.Height(); y++ {\n\t\tfor x := 0; x < c.Width(); x++ {\n\t\t\tcell := c.CellAt(x, y)\n\t\t\tcell.Content = \".\"\n\t\t}\n\t}\n\n\t// Draw a rectangle\n\tfor y := 1; y < 2; y++ {\n\t\tfor x := 1; x < 4; x++ {\n\t\t\tcell := c.CellAt(x, y)\n\t\t\tcell.Content = \"#\"\n\t\t}\n\t}\n\n\texpected := strings.Join([]string{\n\t\t\".....\",\n\t\t\".###.\",\n\t\t\".....\",\n\t}, \"\\n\")\n\n\tif rendered := c.Render(); rendered != expected {\n\t\tt.Errorf(\"expected:\\n%q\\ngot:\\n%q\", expected, rendered)\n\t}\n}\n\nfunc TestCanvasRenderWithTrailingSpaces(t *testing.T) {\n\tc := NewCanvas(5, 2)\n\n\t// Fill the canvas with spaces and some trailing spaces\n\tfor y := 0; y < c.Height(); y++ {\n\t\tfor x := 0; x < c.Width(); x++ {\n\t\t\tcell := c.CellAt(x, y)\n\t\t\tif x < 3 {\n\t\t\t\tcell.Content = \"A\"\n\t\t\t} else {\n\t\t\t\tcell.Content = \" \"\n\t\t\t}\n\t\t}\n\t}\n\n\texpected := strings.Join([]string{\n\t\t\"AAA\",\n\t\t\"AAA\",\n\t}, \"\\n\")\n\n\tif rendered := c.Render(); rendered != expected {\n\t\tt.Errorf(\"expected:\\n%q\\ngot:\\n%q\", expected, rendered)\n\t}\n}\n"
  },
  {
    "path": "color.go",
    "content": "package lipgloss\n\nimport (\n\t\"cmp\"\n\t\"errors\"\n\t\"image/color\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/colorprofile\"\n\t\"github.com/charmbracelet/x/ansi\"\n\t\"github.com/lucasb-eyer/go-colorful\"\n)\n\nfunc clamp[T cmp.Ordered](v, low, high T) T {\n\tif high < low {\n\t\thigh, low = low, high\n\t}\n\treturn min(high, max(low, v))\n}\n\n// 4-bit color constants.\nconst (\n\tBlack ansi.BasicColor = iota\n\tRed\n\tGreen\n\tYellow\n\tBlue\n\tMagenta\n\tCyan\n\tWhite\n\n\tBrightBlack\n\tBrightRed\n\tBrightGreen\n\tBrightYellow\n\tBrightBlue\n\tBrightMagenta\n\tBrightCyan\n\tBrightWhite\n)\n\nvar noColor = NoColor{}\n\n// NoColor is used to specify the absence of color styling. When this is active\n// foreground colors will be rendered with the terminal's default text color,\n// and background colors will not be drawn at all.\n//\n// Example usage:\n//\n//\tvar style = someStyle.Background(lipgloss.NoColor{})\ntype NoColor struct{}\n\n// RGBA returns the RGBA value of this color. Because we have to return\n// something, despite this color being the absence of color, we're returning\n// black with 100% opacity.\n//\n// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.\nfunc (n NoColor) RGBA() (r, g, b, a uint32) {\n\treturn 0x0, 0x0, 0x0, 0xFFFF //nolint:mnd\n}\n\n// Color specifies a color by hex or ANSI256 value. For example:\n//\n//\tansiColor := lipgloss.Color(\"1\") // The same as lipgloss.Red\n//\tansi256Color := lipgloss.Color(\"21\")\n//\thexColor := lipgloss.Color(\"#0000ff\")\nfunc Color(s string) color.Color {\n\tif strings.HasPrefix(s, \"#\") {\n\t\tc, err := parseHex(s)\n\t\tif err != nil {\n\t\t\treturn noColor\n\t\t}\n\t\treturn c\n\t}\n\n\ti, err := strconv.Atoi(s)\n\tif err != nil {\n\t\treturn noColor\n\t}\n\n\tif i < 0 {\n\t\t// Only positive numbers\n\t\ti = -i\n\t}\n\n\tif i < 16 {\n\t\treturn ansi.BasicColor(i) //nolint:gosec\n\t} else if i < 256 {\n\t\treturn ANSIColor(i) //nolint:gosec\n\t}\n\n\tr, g, b := uint8((i>>16)&0xff), uint8(i>>8&0xff), uint8(i&0xff) //nolint:gosec\n\treturn color.RGBA{R: r, G: g, B: b, A: 0xff}\n}\n\nvar errInvalidFormat = errors.New(\"invalid hex format\") // pre-allocated.\n\n// parseHex parses a hex color string and returns a color.RGBA. The string can be\n// in the format #RRGGBB or #RGB. This is a more performant implementation of\n// [colorful.Hex].\nfunc parseHex(s string) (c color.RGBA, err error) {\n\tc.A = 0xff\n\n\tif len(s) == 0 || s[0] != '#' {\n\t\treturn c, errInvalidFormat\n\t}\n\n\thexToByte := func(b byte) byte {\n\t\tswitch {\n\t\tcase b >= '0' && b <= '9':\n\t\t\treturn b - '0'\n\t\tcase b >= 'a' && b <= 'f':\n\t\t\treturn b - 'a' + 10\n\t\tcase b >= 'A' && b <= 'F':\n\t\t\treturn b - 'A' + 10\n\t\t}\n\t\terr = errInvalidFormat\n\t\treturn 0\n\t}\n\n\tswitch len(s) {\n\tcase 7:\n\t\tc.R = hexToByte(s[1])<<4 + hexToByte(s[2])\n\t\tc.G = hexToByte(s[3])<<4 + hexToByte(s[4])\n\t\tc.B = hexToByte(s[5])<<4 + hexToByte(s[6])\n\tcase 4:\n\t\tc.R = hexToByte(s[1]) * 17\n\t\tc.G = hexToByte(s[2]) * 17\n\t\tc.B = hexToByte(s[3]) * 17\n\tdefault:\n\t\terr = errInvalidFormat\n\t}\n\treturn c, err\n}\n\n// RGBColor is a color specified by red, green, and blue values.\ntype RGBColor struct {\n\tR uint8\n\tG uint8\n\tB uint8\n}\n\n// RGBA returns the RGBA value of this color. This satisfies the Go Color\n// interface.\nfunc (c RGBColor) RGBA() (r, g, b, a uint32) {\n\tconst shift = 8\n\tr |= uint32(c.R) << shift\n\tg |= uint32(c.G) << shift\n\tb |= uint32(c.B) << shift\n\ta = 0xFFFF\n\treturn\n}\n\n// ANSIColor is a color specified by an ANSI256 color value.\n//\n// Example usage:\n//\n//\tcolorA := lipgloss.ANSIColor(8)\n//\tcolorB := lipgloss.ANSIColor(134)\ntype ANSIColor = ansi.IndexedColor\n\n// LightDarkFunc is a function that returns a color based on whether the\n// terminal has a light or dark background. You can create one of these with\n// [LightDark].\n//\n// Example:\n//\n//\tlightDark := lipgloss.LightDark(hasDarkBackground)\n//\tred, blue := lipgloss.Color(\"#ff0000\"), lipgloss.Color(\"#0000ff\")\n//\tmyHotColor := lightDark(red, blue)\n//\n// For more info see [LightDark].\ntype LightDarkFunc func(light, dark color.Color) color.Color\n\n// LightDark is a simple helper type that can be used to choose the appropriate\n// color based on whether the terminal has a light or dark background.\n//\n//\tlightDark := lipgloss.LightDark(hasDarkBackground)\n//\tred, blue := lipgloss.Color(\"#ff0000\"), lipgloss.Color(\"#0000ff\")\n//\tmyHotColor := lightDark(red, blue)\n//\n// In practice, there are slightly different workflows between Bubble Tea and\n// Lip Gloss standalone.\n//\n// In Bubble Tea, listen for tea.BackgroundColorMsg, which automatically\n// flows through Update on start. This message will be received whenever the\n// background color changes:\n//\n//\tcase tea.BackgroundColorMsg:\n//\t    m.hasDarkBackground = msg.IsDark()\n//\n// Later, when you're rendering use:\n//\n//\tlightDark := lipgloss.LightDark(m.hasDarkBackground)\n//\tred, blue := lipgloss.Color(\"#ff0000\"), lipgloss.Color(\"#0000ff\")\n//\tmyHotColor := lightDark(red, blue)\n//\n// In standalone Lip Gloss, the workflow is simpler:\n//\n//\thasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n//\tlightDark := lipgloss.LightDark(hasDarkBG)\n//\tred, blue := lipgloss.Color(\"#ff0000\"), lipgloss.Color(\"#0000ff\")\n//\tmyHotColor := lightDark(red, blue)\nfunc LightDark(isDark bool) LightDarkFunc {\n\treturn func(light, dark color.Color) color.Color {\n\t\tif isDark {\n\t\t\treturn dark\n\t\t}\n\t\treturn light\n\t}\n}\n\n// isDarkColor returns whether the given color is dark (based on the luminance\n// portion of the color as interpreted as HSL).\n//\n// Example usage:\n//\n//\tcolor := lipgloss.Color(\"#0000ff\")\n//\tif lipgloss.isDarkColor(color) {\n//\t\tfmt.Println(\"It's dark! I love darkness!\")\n//\t} else {\n//\t\tfmt.Println(\"It's light! Cover your eyes!\")\n//\t}\nfunc isDarkColor(c color.Color) bool {\n\tcol, ok := colorful.MakeColor(c)\n\tif !ok {\n\t\treturn true\n\t}\n\n\t_, _, l := col.Hsl()\n\treturn l < 0.5 //nolint:mnd\n}\n\n// CompleteFunc is a function that returns the appropriate color based on the\n// given color profile.\n//\n// Example usage:\n//\n//\tp := colorprofile.Detect(os.Stderr, os.Environ())\n//\tcomplete := lipgloss.Complete(p)\n//\tcolor := complete(\n//\t\tlipgloss.Color(1), // ANSI\n//\t\tlipgloss.Color(124), // ANSI256\n//\t\tlipgloss.Color(\"#ff34ac\"), // TrueColor\n//\t)\n//\tfmt.Println(\"Ooh, pretty color: \", color)\n//\n// For more info see [Complete].\ntype CompleteFunc func(ansi, ansi256, truecolor color.Color) color.Color\n\n// Complete returns a function that will return the appropriate color based on\n// the given color profile.\n//\n// Example usage:\n//\n//\tp := colorprofile.Detect(os.Stderr, os.Environ())\n//\tcomplete := lipgloss.Complete(p)\n//\tcolor := complete(\n//\t    lipgloss.Color(1), // ANSI\n//\t    lipgloss.Color(124), // ANSI256\n//\t    lipgloss.Color(\"#ff34ac\"), // TrueColor\n//\t)\n//\tfmt.Println(\"Ooh, pretty color: \", color)\nfunc Complete(p colorprofile.Profile) CompleteFunc {\n\treturn func(ansi, ansi256, truecolor color.Color) color.Color {\n\t\tswitch p { //nolint:exhaustive\n\t\tcase colorprofile.ANSI:\n\t\t\treturn ansi\n\t\tcase colorprofile.ANSI256:\n\t\t\treturn ansi256\n\t\tcase colorprofile.TrueColor:\n\t\t\treturn truecolor\n\t\t}\n\t\treturn noColor\n\t}\n}\n\n// ensureNotTransparent ensures that the alpha value of a color is not 0, and if\n// it is, we will set it to 1. This is useful for when we are converting from\n// RGB -> RGBA, and the alpha value is lost in the conversion for gradient purposes.\nfunc ensureNotTransparent(c color.Color) color.Color {\n\t_, _, _, a := c.RGBA()\n\tif a == 0 {\n\t\treturn Alpha(c, 1)\n\t}\n\treturn c\n}\n\n// Alpha adjusts the alpha value of a color using a 0-1 (clamped) float scale\n// 0 = transparent, 1 = opaque.\nfunc Alpha(c color.Color, alpha float64) color.Color {\n\tif c == nil {\n\t\treturn nil\n\t}\n\n\tr, g, b, _ := c.RGBA()\n\treturn color.RGBA{\n\t\tR: uint8(min(255, float64(r>>8))),\n\t\tG: uint8(min(255, float64(g>>8))),\n\t\tB: uint8(min(255, float64(b>>8))),\n\t\tA: uint8(clamp(alpha, 0, 1) * 255),\n\t}\n}\n\n// Complementary returns the complementary color (180° away on color wheel) of\n// the given color. This is useful for creating a contrasting color.\nfunc Complementary(c color.Color) color.Color {\n\tif c == nil {\n\t\treturn nil\n\t}\n\n\t// Offset hue by 180°.\n\tcf, _ := colorful.MakeColor(ensureNotTransparent(c))\n\n\th, s, v := cf.Hsv()\n\th += 180\n\tif h >= 360 {\n\t\th -= 360\n\t} else if h < 0 {\n\t\th += 360\n\t}\n\n\treturn colorful.Hsv(h, s, v).Clamped()\n}\n\n// Darken takes a color and makes it darker by a specific percentage (0-1, clamped).\nfunc Darken(c color.Color, percent float64) color.Color {\n\tif c == nil {\n\t\treturn nil\n\t}\n\n\tmult := 1.0 - clamp(percent, 0, 1)\n\n\tr, g, b, a := c.RGBA()\n\treturn color.RGBA{\n\t\tR: uint8(float64(r>>8) * mult),\n\t\tG: uint8(float64(g>>8) * mult),\n\t\tB: uint8(float64(b>>8) * mult),\n\t\tA: uint8(min(255, float64(a>>8))),\n\t}\n}\n\n// Lighten makes a color lighter by a specific percentage (0-1, clamped).\nfunc Lighten(c color.Color, percent float64) color.Color {\n\tif c == nil {\n\t\treturn nil\n\t}\n\n\tadd := 255 * (clamp(percent, 0, 1))\n\n\tr, g, b, a := c.RGBA()\n\treturn color.RGBA{\n\t\tR: uint8(min(255, float64(r>>8)+add)),\n\t\tG: uint8(min(255, float64(g>>8)+add)),\n\t\tB: uint8(min(255, float64(b>>8)+add)),\n\t\tA: uint8(min(255, float64(a>>8))),\n\t}\n}\n"
  },
  {
    "path": "color_test.go",
    "content": "package lipgloss\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"testing\"\n)\n\n// hex converts a color to a hex string or panics if invalid.\nfunc hex(hex string) color.Color {\n\tcf, err := parseHex(hex)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn cf\n}\n\nfunc expectColorMatches(t *testing.T, got, want color.Color) {\n\tt.Helper()\n\n\tif (got == nil) != (want == nil) {\n\t\tt.Errorf(\"expectColorMatches() = %s, want %s\", rgbaString(t, got), rgbaString(t, want))\n\t}\n\n\tif got == nil {\n\t\treturn\n\t}\n\n\tgr, gg, gb, ga := got.RGBA()\n\twr, wg, wb, wa := want.RGBA()\n\n\tgru, ggu, gbu, gau := uint8(gr>>8), uint8(gg>>8), uint8(gb>>8), uint8(ga>>8)\n\twru, wgu, wbu, wau := uint8(wr>>8), uint8(wg>>8), uint8(wb>>8), uint8(wa>>8)\n\n\tif gru != wru || ggu != wgu || gbu != wbu || gau != wau {\n\t\tt.Errorf(\"expectColorMatches() = %s, want %s\", rgbaString(t, got), rgbaString(t, want))\n\t}\n}\n\nfunc rgbaString(t *testing.T, c color.Color) string {\n\tt.Helper()\n\n\tif c == nil {\n\t\treturn \"nil\"\n\t}\n\n\tr, g, b, a := c.RGBA()\n\treturn fmt.Sprintf(\"rgba(%d,%d,%d,%d)\", uint8(r>>8), uint8(g>>8), uint8(b>>8), uint8(a>>8))\n}\n\nfunc TestHexToColor(t *testing.T) {\n\tt.Parallel()\n\n\ttt := []struct {\n\t\tinput    string\n\t\texpected uint\n\t}{\n\t\t{\n\t\t\t\"#FF0000\",\n\t\t\t0xFF0000,\n\t\t},\n\t\t{\n\t\t\t\"#00F\",\n\t\t\t0x0000FF,\n\t\t},\n\t\t{\n\t\t\t\"#6B50FF\",\n\t\t\t0x6B50FF,\n\t\t},\n\t\t{\n\t\t\t\"invalid color\",\n\t\t\t0x0,\n\t\t},\n\t}\n\n\tfor i, tc := range tt {\n\t\tr, g, b, _ := Color(tc.input).RGBA()\n\t\to := uint(r>>8)<<16 + uint(g>>8)<<8 + uint(b>>8)\n\t\tif o != tc.expected {\n\t\t\tt.Errorf(\"expected %X, got %X (test #%d)\", tc.expected, o, i+1)\n\t\t}\n\t}\n}\n\nfunc TestRGBA(t *testing.T) {\n\ttt := []struct {\n\t\tinput    string\n\t\texpected uint\n\t}{\n\t\t// lipgloss.Color\n\t\t{\n\t\t\t\"#FF0000\",\n\t\t\t0xFF0000,\n\t\t},\n\t\t{\n\t\t\t\"9\",\n\t\t\t0xFF0000,\n\t\t},\n\t\t{\n\t\t\t\"21\",\n\t\t\t0x0000FF,\n\t\t},\n\t\t{\n\t\t\t\"16711680\", // #FF0000\n\t\t\t0xFF0000,\n\t\t},\n\t}\n\n\tfor i, tc := range tt {\n\t\tr, g, b, _ := Color(tc.input).RGBA()\n\t\to := uint(r/256)<<16 + uint(g/256)<<8 + uint(b/256)\n\n\t\tif o != tc.expected {\n\t\t\tt.Errorf(\"expected %X, got %X (test #%d)\", tc.expected, o, i+1)\n\t\t}\n\t}\n}\n\nfunc TestParseHex(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tinput       string\n\t\texpected    color.Color\n\t\texpectError bool\n\t}{\n\t\t{name: \"valid-6-red\", input: \"#FF0000\", expected: hex(\"#FF0000\")},\n\t\t{name: \"valid-6-green\", input: \"#00FF00\", expected: hex(\"#00FF00\")},\n\t\t{name: \"valid-6-blue\", input: \"#0000FF\", expected: hex(\"#0000FF\")},\n\t\t{name: \"valid-6-white\", input: \"#FFFFFF\", expected: hex(\"#FFFFFF\")},\n\t\t{name: \"valid-6-black\", input: \"#000000\", expected: hex(\"#000000\")},\n\t\t{name: \"valid-6-gray\", input: \"#808080\", expected: hex(\"#808080\")},\n\t\t{name: \"valid-3-red\", input: \"#F00\", expected: hex(\"#FF0000\")},\n\t\t{name: \"valid-3-green\", input: \"#0F0\", expected: hex(\"#00FF00\")},\n\t\t{name: \"valid-3-blue\", input: \"#00F\", expected: hex(\"#0000FF\")},\n\t\t{name: \"valid-3-white\", input: \"#FFF\", expected: hex(\"#FFFFFF\")},\n\t\t{name: \"valid-3-black\", input: \"#000\", expected: hex(\"#000000\")},\n\t\t{name: \"valid-6-lowercase\", input: \"#ff0000\", expected: hex(\"#FF0000\")},\n\t\t{name: \"valid-6-mixed-case\", input: \"#Ff0000\", expected: hex(\"#FF0000\")},\n\t\t{name: \"valid-3-lowercase\", input: \"#f00\", expected: hex(\"#FF0000\")},\n\t\t{name: \"missing-hash-prefix\", input: \"FF0000\", expectError: true},\n\t\t{name: \"empty-string\", input: \"\", expectError: true},\n\t\t{name: \"only-hash\", input: \"#\", expectError: true},\n\t\t{name: \"too-short-3\", input: \"#F0\", expectError: true},\n\t\t{name: \"too-long-6\", input: \"#FF00000\", expectError: true},\n\t\t{name: \"invalid-char\", input: \"#FG0000\", expectError: true},\n\t\t{name: \"invalid-char-3\", input: \"#FG0\", expectError: true},\n\t\t{name: \"invalid-char-lowercase\", input: \"#fg0000\", expectError: true},\n\t\t{name: \"invalid-char-mixed\", input: \"#Fg0000\", expectError: true},\n\t\t{name: \"wrong-len-5\", input: \"#FF000\", expectError: true},\n\t\t{name: \"wrong-len-8\", input: \"#FF000000\", expectError: true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tresult, err := parseHex(tt.input)\n\n\t\t\tif tt.expectError {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"FromHex() expected error but got none for input %q\", tt.input)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"FromHex() unexpected error for input %q: %v\", tt.input, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\texpectColorMatches(t, result, tt.expected)\n\t\t})\n\t}\n}\n\nfunc TestAlpha(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tcolor    color.Color\n\t\talpha    float64\n\t\texpected color.Color\n\t}{\n\t\t{\n\t\t\tname:     \"alpha-full-opacity\",\n\t\t\tcolor:    color.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t\talpha:    1.0,\n\t\t\texpected: color.RGBA{R: 255, G: 0, B: 0, A: 255},\n\t\t},\n\t\t{\n\t\t\tname:     \"alpha-half-opacity\",\n\t\t\tcolor:    color.RGBA{R: 0, G: 255, B: 0, A: 255},\n\t\t\talpha:    0.5,\n\t\t\texpected: color.RGBA{R: 0, G: 255, B: 0, A: 127},\n\t\t},\n\t\t{\n\t\t\tname:     \"alpha-quarter-opacity\",\n\t\t\tcolor:    color.RGBA{R: 0, G: 0, B: 255, A: 255},\n\t\t\talpha:    0.25,\n\t\t\texpected: color.RGBA{R: 0, G: 0, B: 255, A: 63},\n\t\t},\n\t\t{\n\t\t\tname:     \"alpha-zero-opacity\",\n\t\t\tcolor:    color.RGBA{R: 255, G: 255, B: 255, A: 255},\n\t\t\talpha:    0.0,\n\t\t\texpected: color.RGBA{R: 255, G: 255, B: 255, A: 0},\n\t\t},\n\t\t{\n\t\t\tname:     \"alpha-clamp-above-max\",\n\t\t\tcolor:    color.RGBA{R: 255, G: 0, B: 255, A: 255},\n\t\t\talpha:    1.5,\n\t\t\texpected: color.RGBA{R: 255, G: 0, B: 255, A: 255},\n\t\t},\n\t\t{\n\t\t\tname:     \"alpha-clamp-below-min\",\n\t\t\tcolor:    color.RGBA{R: 255, G: 255, B: 0, A: 255},\n\t\t\talpha:    -0.5,\n\t\t\texpected: color.RGBA{R: 255, G: 255, B: 0, A: 0},\n\t\t},\n\t\t{\n\t\t\tname:     \"alpha-complex-color\",\n\t\t\tcolor:    color.RGBA{R: 18, G: 52, B: 86, A: 255},\n\t\t\talpha:    0.75,\n\t\t\texpected: color.RGBA{R: 18, G: 52, B: 86, A: 191},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\texpectColorMatches(t, Alpha(tt.color, tt.alpha), tt.expected)\n\t\t})\n\t}\n}\n\nfunc TestComplementary(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tcolor    color.Color\n\t\texpected color.Color\n\t}{\n\t\t{name: \"red\", color: hex(\"#FF0000\"), expected: hex(\"#00FFFF\")},\n\t\t{name: \"green\", color: hex(\"#00FF00\"), expected: hex(\"#FF00FF\")},\n\t\t{name: \"blue\", color: hex(\"#0000FF\"), expected: hex(\"#FFFF00\")},\n\t\t{name: \"yellow\", color: hex(\"#FFFF00\"), expected: hex(\"#0000FF\")},\n\t\t{name: \"cyan\", color: hex(\"#00FFFF\"), expected: hex(\"#FF0000\")},\n\t\t{name: \"magenta\", color: hex(\"#FF00FF\"), expected: hex(\"#00FF00\")},\n\t\t// Black has no hue to complement\n\t\t{name: \"black\", color: hex(\"#000000\"), expected: hex(\"#000000\")},\n\t\t// White has no hue to complement\n\t\t{name: \"white\", color: hex(\"#FFFFFF\"), expected: hex(\"#FFFFFF\")},\n\t\t// Gray has no hue to complement\n\t\t{name: \"gray\", color: hex(\"#808080\"), expected: hex(\"#808080\")},\n\t\t{name: \"orange\", color: hex(\"#FF8000\"), expected: hex(\"#007FFF\")},\n\t\t{name: \"purple\", color: hex(\"#8000FF\"), expected: hex(\"#7FFF00\")},\n\t\t{name: \"nil-color\", color: nil, expected: nil},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\texpectColorMatches(t, Complementary(tt.color), tt.expected)\n\t\t})\n\t}\n}\n\nfunc TestDarken(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tcolor    color.Color\n\t\tpercent  float64\n\t\texpected color.Color\n\t}{\n\t\t{name: \"darken-white-50-percent\", color: hex(\"#FFFFFF\"), percent: 0.5, expected: hex(\"#7F7F7F\")},\n\t\t{name: \"darken-red-25-percent\", color: hex(\"#FF0000\"), percent: 0.25, expected: hex(\"#BF0000\")},\n\t\t{name: \"darken-blue-75-percent\", color: hex(\"#0000FF\"), percent: 0.75, expected: hex(\"#00003F\")},\n\t\t{name: \"darken-black-10-percent\", color: hex(\"#000000\"), percent: 0.1, expected: hex(\"#000000\")},\n\t\t{name: \"darken-with-clamp-min\", color: hex(\"#FFFFFF\"), percent: 0, expected: hex(\"#FFFFFF\")},\n\t\t{name: \"darken-with-clamp-max\", color: hex(\"#FFFFFF\"), percent: 1, expected: hex(\"#000000\")},\n\t\t{name: \"darken-nil-color\", color: nil, percent: 0.5, expected: nil},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\texpectColorMatches(t, Darken(tt.color, tt.percent), tt.expected)\n\t\t})\n\t}\n}\n\nfunc TestLighten(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tcolor    color.Color\n\t\tpercent  float64\n\t\texpected color.Color\n\t}{\n\t\t{name: \"lighten-black-50-percent\", color: hex(\"#000000\"), percent: 0.5, expected: hex(\"#7F7F7F\")},\n\t\t{name: \"lighten-red-25-percent\", color: hex(\"#800000\"), percent: 0.25, expected: hex(\"#BF3F3F\")},\n\t\t{name: \"lighten-blue-75-percent\", color: hex(\"#000080\"), percent: 0.75, expected: hex(\"#BFBFFF\")},\n\t\t{name: \"lighten-white-10-percent\", color: hex(\"#FFFFFF\"), percent: 0.1, expected: hex(\"#FFFFFF\")},\n\t\t{name: \"lighten-with-clamp-min\", color: hex(\"#000000\"), percent: 0, expected: hex(\"#000000\")},\n\t\t{name: \"lighten-with-clamp-max\", color: hex(\"#000000\"), percent: 1, expected: hex(\"#FFFFFF\")},\n\t\t{name: \"lighten-nil-color\", color: nil, percent: 0.5, expected: nil},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\texpectColorMatches(t, Lighten(tt.color, tt.percent), tt.expected)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "compat/color.go",
    "content": "package compat\n\nimport (\n\t\"image/color\"\n\t\"os\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/charmbracelet/colorprofile\"\n)\n\nvar (\n\t// HasDarkBackground is true if the terminal has a dark background.\n\tHasDarkBackground = lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n\n\t// Profile is the color profile of the terminal.\n\tProfile = colorprofile.Detect(os.Stdout, os.Environ())\n)\n\n// AdaptiveColor provides color options for light and dark backgrounds. The\n// appropriate color will be returned at runtime based on the darkness of the\n// terminal background color.\n//\n// Example usage:\n//\n//\tcolor := lipgloss.AdaptiveColor{Light: \"#0000ff\", Dark: \"#000099\"}\ntype AdaptiveColor struct {\n\tLight color.Color\n\tDark  color.Color\n}\n\n// RGBA returns the RGBA value of this color. This satisfies the Go Color\n// interface.\nfunc (c AdaptiveColor) RGBA() (uint32, uint32, uint32, uint32) {\n\tif HasDarkBackground {\n\t\treturn c.Dark.RGBA()\n\t}\n\treturn c.Light.RGBA()\n}\n\n// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color\n// profiles. Automatic color degradation will not be performed.\ntype CompleteColor struct {\n\tTrueColor color.Color\n\tANSI256   color.Color\n\tANSI      color.Color\n}\n\n// RGBA returns the RGBA value of this color. This satisfies the Go Color\n// interface.\nfunc (c CompleteColor) RGBA() (uint32, uint32, uint32, uint32) {\n\tswitch Profile { //nolint:exhaustive\n\tcase colorprofile.TrueColor:\n\t\treturn c.TrueColor.RGBA()\n\tcase colorprofile.ANSI256:\n\t\treturn c.ANSI256.RGBA()\n\tcase colorprofile.ANSI:\n\t\treturn c.ANSI.RGBA()\n\t}\n\treturn lipgloss.NoColor{}.RGBA()\n}\n\n// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color\n// profiles, with separate options for light and dark backgrounds. Automatic\n// color degradation will not be performed.\ntype CompleteAdaptiveColor struct {\n\tLight CompleteColor\n\tDark  CompleteColor\n}\n\n// RGBA returns the RGBA value of this color. This satisfies the Go Color\n// interface.\nfunc (c CompleteAdaptiveColor) RGBA() (uint32, uint32, uint32, uint32) {\n\tif HasDarkBackground {\n\t\treturn c.Dark.RGBA()\n\t}\n\treturn c.Light.RGBA()\n}\n"
  },
  {
    "path": "compat/doc.go",
    "content": "// Package compat is a compatibility layer for Lip Gloss that provides a way to\n// deal with the hassle of setting up a writer. It's impure because it uses\n// global variables, is not thread-safe, and only works with the default\n// standard I/O streams.\n//\n// In case you want [os.Stderr] to be used as the default writer, you can set\n// both [Writer] and [HasDarkBackground] to use [os.Stderr] with\n// the following code:\n//\n//\timport (\n//\t\t\"os\"\n//\n//\t\t\"github.com/charmbracelet/colorprofile\"\n//\t\t\"charm.land/lipgloss/v2/impure\"\n//\t)\n//\n//\tfunc init() {\n//\t\timpure.Writer = colorprofile.NewWriter(os.Stderr, os.Environ())\n//\t\timpure.HasDarkBackground, _ = lipgloss.HasDarkBackground(os.Stdin, os.Stderr)\n//\t}\npackage compat\n"
  },
  {
    "path": "examples/blending/border-blend-rotation/bubbletea/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nconst (\n\tborderRotationFPS   = 15\n\tborderRotationSteps = 5\n)\n\ntype borderRotationTickMsg struct {\n\tValue int\n}\n\nfunc borderRotationTick(current int) tea.Cmd {\n\treturn tea.Tick(time.Second/time.Duration(borderRotationFPS), func(_ time.Time) tea.Msg {\n\t\treturn borderRotationTickMsg{Value: current + borderRotationSteps}\n\t})\n}\n\ntype model struct {\n\tborderRotation int\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn borderRotationTick(0)\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\treturn m, tea.Quit\n\t\t}\n\tcase borderRotationTickMsg:\n\t\tm.borderRotation = msg.Value\n\t\treturn m, borderRotationTick(msg.Value)\n\t}\n\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tv := tea.NewView(lipgloss.NewStyle().\n\t\tBorder(lipgloss.RoundedBorder()).\n\t\tBorderForegroundBlend(\n\t\t\tlipgloss.Color(\"#00FA68\"),\n\t\t\tlipgloss.Color(\"#9900FF\"),\n\t\t\tlipgloss.Color(\"#ED5353\"),\n\t\t\tlipgloss.Color(\"#9900FF\"),\n\t\t\tlipgloss.Color(\"#00FA68\"),\n\t\t).\n\t\tBorderForegroundBlendOffset(m.borderRotation).\n\t\tWidth(60).\n\t\tHeight(15).\n\t\tRender(\"Hello, world!\"))\n\tv.AltScreen = true\n\treturn v\n}\n\nfunc main() {\n\t_, err := tea.NewProgram(model{}).Run()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Uh oh: %v\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/blending/linear-1d/bubbletea/main.go",
    "content": "// This example demonstrates how to use the colors.Blend1D function to create\n// beautiful color gradients in a Bubble Tea application.\npackage main\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"os\"\n\t\"strings\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar gradients = []gradientData{\n\t{\n\t\tname: \"Sunset\",\n\t\tstops: []color.Color{\n\t\t\tlipgloss.Color(\"#FF6B6B\"), // Coral\n\t\t\tlipgloss.Color(\"#FFB74D\"), // Orange\n\t\t\tlipgloss.Color(\"#FFDFBA\"), // Peach\n\t\t},\n\t},\n\t{\n\t\tname: \"Ocean\",\n\t\tstops: []color.Color{\n\t\t\tlipgloss.Color(\"#0077B6\"), // Deep Blue\n\t\t\tlipgloss.Color(\"#48CAE4\"), // Sky Blue\n\t\t\tlipgloss.Color(\"#ADE8F4\"), // Light Blue\n\t\t},\n\t},\n\t{\n\t\tname: \"Forest\",\n\t\tstops: []color.Color{\n\t\t\tlipgloss.Color(\"#228B22\"), // Forest Green\n\t\t\tlipgloss.Color(\"#90EE90\"), // Light Green\n\t\t\tlipgloss.Color(\"#FFFFE0\"), // Cream\n\t\t},\n\t},\n\t{\n\t\tname: \"Purple Dream\",\n\t\tstops: []color.Color{\n\t\t\tlipgloss.Color(\"#9370DB\"), // Medium Purple\n\t\t\tlipgloss.Color(\"#DDA0DD\"), // Plum\n\t\t\tlipgloss.Color(\"#FFB6C1\"), // Light Pink\n\t\t},\n\t},\n\t{\n\t\tname: \"Fire\",\n\t\tstops: []color.Color{\n\t\t\tlipgloss.Color(\"#FF0000\"), // Red\n\t\t\tlipgloss.Color(\"#FFA500\"), // Orange\n\t\t\tlipgloss.Color(\"#FFFF00\"), // Yellow\n\t\t},\n\t},\n}\n\ntype gradientData struct {\n\tname  string\n\tstops []color.Color\n}\n\n// Style definitions.\ntype styles struct {\n\t// UI styles.\n\ttitle        lipgloss.Style\n\tgradientName lipgloss.Style\n\tinfo         lipgloss.Style\n}\n\nfunc newStyles(dark bool) (s *styles) {\n\ts = &styles{}\n\n\tlightDark := lipgloss.LightDark(dark)\n\n\ts.title = lipgloss.NewStyle().\n\t\tBold(true).\n\t\tForeground(lightDark(lipgloss.Color(\"#2D3748\"), lipgloss.Color(\"#E2E8F0\"))).\n\t\tMarginBottom(1).\n\t\tAlign(lipgloss.Center)\n\n\ts.gradientName = lipgloss.NewStyle().\n\t\tBold(true).\n\t\tForeground(lightDark(lipgloss.Color(\"#4A5568\"), lipgloss.Color(\"#CBD5E0\"))).\n\t\tPaddingRight(1)\n\n\ts.info = lipgloss.NewStyle().\n\t\tForeground(lightDark(lipgloss.Color(\"#718096\"), lipgloss.Color(\"#A0AEC0\"))).\n\t\tItalic(true)\n\n\treturn s\n}\n\ntype model struct {\n\twidth  int\n\theight int\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\tcase tea.WindowSizeMsg:\n\t\tm.width = msg.Width\n\t\tm.height = msg.Height\n\t\treturn m, nil\n\tcase tea.BackgroundColorMsg:\n\t\tm.styles = newStyles(msg.IsDark())\n\t\treturn m, nil\n\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\n\treturn m, nil\n}\n\nfunc (m model) View() tea.View {\n\tvar maxTitleWidth int\n\n\tfor _, gradient := range gradients {\n\t\tmaxTitleWidth = max(maxTitleWidth, lipgloss.Width(m.styles.gradientName.Render(gradient.name)))\n\t}\n\n\tvar content strings.Builder\n\n\tcontent.WriteString(m.styles.title.Render(\"Color Gradient Examples with Blend1D\"))\n\tcontent.WriteString(\"\\n\\n\")\n\n\tvar title string\n\n\tfor _, gradient := range gradients {\n\t\ttitle = m.styles.gradientName.Width(maxTitleWidth).Render(gradient.name)\n\t\tcontent.WriteString(title)\n\n\t\tblendedColors := lipgloss.Blend1D(m.width-maxTitleWidth, gradient.stops...)\n\n\t\tfor _, c := range blendedColors {\n\t\t\tcontent.WriteString(lipgloss.NewStyle().Background(c).Foreground(c).Render(\"█\"))\n\t\t}\n\n\t\tcontent.WriteString(\"\\n\")\n\t}\n\n\tcontent.WriteString(\"\\n\")\n\tcontent.WriteString(m.styles.info.Render(\"Press Q to exit\"))\n\n\tcursor := &tea.Cursor{}\n\tcursor.X = 0\n\tcursor.Y = 0\n\n\tv := tea.NewView(content.String())\n\tv.Cursor = cursor\n\tv.AltScreen = true\n\n\treturn v\n}\n\nfunc main() {\n\t_, err := tea.NewProgram(model{styles: newStyles(true)}).Run()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Uh oh: %v\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "examples/blending/linear-1d/standalone/main.go",
    "content": "// This example demonstrates how to use the colors.Blend1D function to create\n// beautiful color gradients in a standalone Lip Gloss application.\npackage main\n\nimport (\n\t\"image/color\"\n\t\"os\"\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n)\n\nvar gradients = [][]color.Color{\n\t{\n\t\tlipgloss.Color(\"#FF6B6B\"), // Coral\n\t\tlipgloss.Color(\"#FFB74D\"), // Orange\n\t\tlipgloss.Color(\"#FFDFBA\"), // Peach\n\t},\n\t{\n\t\tlipgloss.Color(\"#0077B6\"), // Deep Blue\n\t\tlipgloss.Color(\"#48CAE4\"), // Sky Blue\n\t\tlipgloss.Color(\"#ADE8F4\"), // Light Blue\n\t},\n\t{\n\t\tlipgloss.Color(\"#228B22\"), // Forest Green\n\t\tlipgloss.Color(\"#90EE90\"), // Light Green\n\t\tlipgloss.Color(\"#FFFFE0\"), // Cream\n\t},\n\t{\n\t\tlipgloss.Color(\"#9370DB\"), // Medium Purple\n\t\tlipgloss.Color(\"#DDA0DD\"), // Plum\n\t\tlipgloss.Color(\"#FFB6C1\"), // Light Pink\n\t},\n\t{\n\t\tlipgloss.Color(\"#9900FF\"), // Purple\n\t\tlipgloss.Color(\"#00FA68\"), // Lime\n\t\tlipgloss.Color(\"#ED5353\"), // Red\n\t},\n}\n\nfunc main() {\n\thasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n\tlightDark := lipgloss.LightDark(hasDarkBG)\n\n\t// Create styles.\n\ttitleStyle := lipgloss.NewStyle().\n\t\tBold(true).\n\t\tForeground(lightDark(lipgloss.Color(\"#2D3748\"), lipgloss.Color(\"#E2E8F0\"))).\n\t\tMarginBottom(1).\n\t\tAlign(lipgloss.Center)\n\n\tgradientStyle := lipgloss.NewStyle().\n\t\tBorder(lipgloss.RoundedBorder()).\n\t\tBorderForeground(lightDark(lipgloss.Color(\"#718096\"), lipgloss.Color(\"#A0AEC0\")))\n\n\tvar content strings.Builder\n\n\tcontent.WriteString(titleStyle.Render(\"Color Gradient Examples with Blend1D\"))\n\tcontent.WriteString(\"\\n\")\n\n\tfor _, gradient := range gradients {\n\t\tblendedColors := lipgloss.Blend1D(40, gradient...)\n\n\t\tvar gradientBar strings.Builder\n\t\tfor _, c := range blendedColors {\n\t\t\tblockStyle := lipgloss.NewStyle().Foreground(c)\n\t\t\tgradientBar.WriteString(blockStyle.Render(\"█\"))\n\t\t}\n\n\t\tcontent.WriteString(gradientStyle.Render(gradientBar.String()))\n\t\tcontent.WriteString(\"\\n\")\n\t}\n\n\tlipgloss.Println(content.String())\n}\n"
  },
  {
    "path": "examples/blending/linear-2d/bubbletea/main.go",
    "content": "// This example demonstrates how to use the colors.Blend2D function to create\n// beautiful 2D color gradients in a Bubble Tea application.\npackage main\n\nimport (\n\t\"cmp\"\n\t\"fmt\"\n\t\"image/color\"\n\t\"math\"\n\t\"os\"\n\t\"strings\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/charmbracelet/x/exp/charmtone\"\n)\n\nvar gradients = [][]color.Color{\n\t{\n\t\tlipgloss.Color(\"#FF6B6B\"), // Coral\n\t\tlipgloss.Color(\"#FFB74D\"), // Orange\n\t\tlipgloss.Color(\"#FFDFBA\"), // Peach\n\t},\n\t{\n\t\tlipgloss.Color(\"#0077B6\"), // Deep Blue\n\t\tlipgloss.Color(\"#48CAE4\"), // Sky Blue\n\t\tlipgloss.Color(\"#ADE8F4\"), // Light Blue\n\t},\n\t{\n\t\tlipgloss.Color(\"#228B22\"), // Forest Green\n\t\tlipgloss.Color(\"#90EE90\"), // Light Green\n\t\tlipgloss.Color(\"#FFFFE0\"), // Cream\n\t},\n\t{\n\t\tlipgloss.Color(\"#9370DB\"), // Medium Purple\n\t\tlipgloss.Color(\"#DDA0DD\"), // Plum\n\t\tlipgloss.Color(\"#FFB6C1\"), // Light Pink\n\t},\n\t{\n\t\tlipgloss.Color(\"#9900FF\"), // Purple\n\t\tlipgloss.Color(\"#00FA68\"), // Lime\n\t\tlipgloss.Color(\"#ED5353\"), // Red\n\t},\n}\n\nfunc main() {\n\tm := model{\n\t\tboxWidth:         20,\n\t\tboxHeight:        10,\n\t\tangle:            45,\n\t\tselectedGradient: 0,\n\n\t\tinfoStyle: lipgloss.NewStyle().\n\t\t\tForeground(lipgloss.Color(\"#888888\")).\n\t\t\tMarginTop(1),\n\t\tcontrolsStyle: lipgloss.NewStyle().\n\t\t\tForeground(lipgloss.Color(\"#666666\")).\n\t\t\tMarginTop(1),\n\t\tgradientBoxStyle: lipgloss.NewStyle().\n\t\t\tBorder(lipgloss.RoundedBorder()).\n\t\t\tBorderForegroundBlend(\n\t\t\t\tcharmtone.Cherry,\n\t\t\t\tcharmtone.Charple,\n\t\t\t\tcharmtone.Guac,\n\t\t\t\tcharmtone.Charple,\n\t\t\t\tcharmtone.Sriracha,\n\t\t\t),\n\t}\n\tp := tea.NewProgram(m)\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\ntype model struct {\n\t// UI state.\n\twindowWidth      int\n\twindowHeight     int\n\tboxWidth         int\n\tboxHeight        int\n\tangle            float64\n\tselectedGradient int\n\n\t// UI styles.\n\tinfoStyle        lipgloss.Style\n\tcontrolsStyle    lipgloss.Style\n\tgradientBoxStyle lipgloss.Style\n\tgradients        []color.Color\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.windowWidth = msg.Width\n\t\tm.windowHeight = msg.Height\n\t\tm.updateGradient()\n\tcase tea.KeyMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"ctrl+c\", \"esc\":\n\t\t\treturn m, tea.Quit\n\t\tcase \"a\":\n\t\t\tm.angle = math.Mod(m.angle+15, 360)\n\t\t\tm.updateGradient()\n\t\tcase \"d\":\n\t\t\tm.angle = math.Mod(m.angle-15+360, 360)\n\t\t\tm.updateGradient()\n\t\tcase \"left\":\n\t\t\tm.boxWidth -= 2\n\t\t\tm.updateGradient()\n\t\tcase \"right\":\n\t\t\tm.boxWidth += 2\n\t\t\tm.updateGradient()\n\t\tcase \"up\":\n\t\t\tm.boxHeight--\n\t\t\tm.updateGradient()\n\t\tcase \"down\":\n\t\t\tm.boxHeight++\n\t\t\tm.updateGradient()\n\t\tcase \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\":\n\t\t\tm.selectedGradient = max(0, min(int(msg.String()[0]-'1'), len(gradients)-1))\n\t\t\tm.updateGradient()\n\t\t}\n\tcase tea.MouseClickMsg:\n\t\tswitch msg.Mouse().Button {\n\t\tcase tea.MouseLeft:\n\t\t\tm.boxWidth = msg.Mouse().X\n\t\t\tm.boxHeight = msg.Mouse().Y\n\t\t\tm.updateGradient()\n\t\t}\n\t}\n\treturn m, nil\n}\n\nfunc (m *model) updateGradient() {\n\tm.boxWidth = clamp(m.boxWidth, 5, m.windowWidth-m.gradientBoxStyle.GetHorizontalFrameSize())\n\tm.boxHeight = clamp(m.boxHeight, 3, m.windowHeight-m.gradientBoxStyle.GetVerticalFrameSize()-m.infoStyle.GetVerticalFrameSize()-m.controlsStyle.GetVerticalFrameSize()-2)\n\n\t// Since gradients that might be large can take up more memory, only generate gradients when\n\t// the box size (potentially) changes. If you have much smaller gradients, this is less of\n\t// an issue.\n\tm.gradients = lipgloss.Blend2D(m.boxWidth, m.boxHeight, m.angle, gradients[m.selectedGradient]...)\n}\n\nfunc (m model) View() tea.View {\n\tvar v tea.View\n\tv.AltScreen = true\n\tv.MouseMode = tea.MouseModeCellMotion\n\n\tif len(m.gradients) == 0 || m.windowWidth == 0 || m.windowHeight == 0 {\n\t\treturn v // Wait until we generate the initial gradient/get window size.\n\t}\n\n\t// Build the gradient content.\n\tgradientContent := strings.Builder{}\n\tfor y := range m.boxHeight { // Uses 1D row-major order.\n\t\tfor x := range m.boxWidth {\n\t\t\tindex := y*m.boxWidth + x\n\t\t\tgradientContent.WriteString(\n\t\t\t\tlipgloss.NewStyle().\n\t\t\t\t\tBackground(m.gradients[index]).\n\t\t\t\t\tRender(\" \"),\n\t\t\t)\n\t\t}\n\t\tif y < m.boxHeight-1 { // End of row.\n\t\t\tgradientContent.WriteString(\"\\n\")\n\t\t}\n\t}\n\n\tgradient := m.gradientBoxStyle.Render(gradientContent.String())\n\n\tinfo := m.infoStyle.Width(m.windowWidth).Render(fmt.Sprintf(\n\t\t\"Size: %dx%d | Angle: %.1f° | Colors: %d\",\n\t\tm.boxWidth,\n\t\tm.boxHeight,\n\t\tm.angle,\n\t\tlen(gradients[m.selectedGradient]),\n\t))\n\n\tcontrols := m.controlsStyle.Width(m.windowWidth).Render(fmt.Sprintf(\n\t\t\"Controls: a/d (angle) | ←→ (width) | ↑↓ (height) | 1-%d (color scheme) | mouse click\",\n\t\tlen(gradients),\n\t))\n\n\tcontent := lipgloss.NewStyle().\n\t\tWidth(m.windowWidth).\n\t\tHeight(m.windowHeight).\n\t\tRender(lipgloss.JoinVertical(\n\t\t\tlipgloss.Top,\n\t\t\tlipgloss.NewStyle().\n\t\t\t\tWidth(m.windowWidth).\n\t\t\t\tHeight(m.windowHeight-lipgloss.Height(info)-lipgloss.Height(controls)).\n\t\t\t\tRender(gradient),\n\t\t\tinfo,\n\t\t\tcontrols,\n\t\t))\n\tv.SetContent(content)\n\treturn v\n}\n\nfunc clamp[T cmp.Ordered](v, low, high T) T {\n\treturn min(high, max(low, v))\n}\n"
  },
  {
    "path": "examples/blending/linear-2d/standalone/main.go",
    "content": "// This example demonstrates how to use the colors.Blend2D function to create\n// beautiful 2D color gradients in a standalone Lip Gloss application.\npackage main\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"os\"\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n)\n\nfunc main() {\n\thasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n\tlightDark := lipgloss.LightDark(hasDarkBG)\n\n\tgradients := []struct {\n\t\tname  string\n\t\tstops []color.Color\n\t\tangle float64\n\t}{\n\t\t{\n\t\t\tname: \"Sunset Diagonal\",\n\t\t\tstops: []color.Color{\n\t\t\t\tlipgloss.Color(\"#FF6B6B\"), // Coral\n\t\t\t\tlipgloss.Color(\"#FFB74D\"), // Orange\n\t\t\t\tlipgloss.Color(\"#FFDFBA\"), // Peach\n\t\t\t},\n\t\t\tangle: 45,\n\t\t},\n\t\t{\n\t\t\tname: \"Ocean Wave\",\n\t\t\tstops: []color.Color{\n\t\t\t\tlipgloss.Color(\"#0077B6\"), // Deep Blue\n\t\t\t\tlipgloss.Color(\"#48CAE4\"), // Sky Blue\n\t\t\t\tlipgloss.Color(\"#ADE8F4\"), // Light Blue\n\t\t\t},\n\t\t\tangle: 90,\n\t\t},\n\t\t{\n\t\t\tname: \"Forest Mist\",\n\t\t\tstops: []color.Color{\n\t\t\t\tlipgloss.Color(\"#228B22\"), // Forest Green\n\t\t\t\tlipgloss.Color(\"#90EE90\"), // Light Green\n\t\t\t\tlipgloss.Color(\"#FFFFE0\"), // Cream\n\t\t\t},\n\t\t\tangle: 135,\n\t\t},\n\t\t{\n\t\t\tname: \"Purple Dream\",\n\t\t\tstops: []color.Color{\n\t\t\t\tlipgloss.Color(\"#9370DB\"), // Medium Purple\n\t\t\t\tlipgloss.Color(\"#DDA0DD\"), // Plum\n\t\t\t\tlipgloss.Color(\"#FFB6C1\"), // Light Pink\n\t\t\t},\n\t\t\tangle: 180,\n\t\t},\n\t\t{\n\t\t\tname: \"Fire Gradient\",\n\t\t\tstops: []color.Color{\n\t\t\t\tlipgloss.Color(\"#FF0000\"), // Red\n\t\t\t\tlipgloss.Color(\"#FFA500\"), // Orange\n\t\t\t\tlipgloss.Color(\"#FFFF00\"), // Yellow\n\t\t\t},\n\t\t\tangle: 225,\n\t\t},\n\t}\n\n\t// Create styles.\n\ttitleStyle := lipgloss.NewStyle().\n\t\tBold(true).\n\t\tForeground(lightDark(lipgloss.Color(\"#2D3748\"), lipgloss.Color(\"#E2E8F0\"))).\n\t\tMarginBottom(1).\n\t\tAlign(lipgloss.Center)\n\n\tgradientStyle := lipgloss.NewStyle().\n\t\tBorder(lipgloss.RoundedBorder()).\n\t\tBorderForeground(lightDark(lipgloss.Color(\"#718096\"), lipgloss.Color(\"#A0AEC0\"))).\n\t\tMarginBottom(1)\n\n\tgradientNameStyle := lipgloss.NewStyle().\n\t\tBold(true).\n\t\tForeground(lightDark(lipgloss.Color(\"#4A5568\"), lipgloss.Color(\"#CBD5E0\"))).\n\t\tMarginBottom(1)\n\n\tvar content strings.Builder\n\n\tcontent.WriteString(titleStyle.Render(\"2D Color Gradient Examples with Blend2D\"))\n\tcontent.WriteString(\"\\n\\n\")\n\n\tfor _, gradient := range gradients {\n\t\t// Generate the gradient using Blend2D.\n\t\twidth, height := 30, 12\n\t\tblendedColors := lipgloss.Blend2D(width, height, gradient.angle, gradient.stops...)\n\n\t\t// Create the gradient box using individual character styling.\n\t\tvar gradientBox strings.Builder\n\t\tfor y := range height { // Uses 1D row-major order.\n\t\t\tfor x := range width {\n\t\t\t\tindex := y*width + x\n\t\t\t\tgradientBox.WriteString(\n\t\t\t\t\tlipgloss.NewStyle().\n\t\t\t\t\t\tForeground(blendedColors[index]).\n\t\t\t\t\t\tRender(\"█\"),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif y < height-1 { // End of row.\n\t\t\t\tgradientBox.WriteString(\"\\n\")\n\t\t\t}\n\t\t}\n\n\t\tcontent.WriteString(gradientNameStyle.Render(fmt.Sprintf(\"%s (Angle: %.0f°)\", gradient.name, gradient.angle)))\n\t\tcontent.WriteString(\"\\n\")\n\t\tcontent.WriteString(gradientStyle.Render(gradientBox.String()))\n\t\tcontent.WriteString(\"\\n\")\n\t}\n\n\tlipgloss.Println(content.String())\n}\n"
  },
  {
    "path": "examples/brightness/main.go",
    "content": "// This example demonstrates how to use the colors.Lighten and colors.Darken functions\n// to create progressive brightness variations in a standalone Lip Gloss application.\npackage main\n\nimport (\n\t\"image/color\"\n\t\"os\"\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n)\n\nfunc main() {\n\thasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n\tlightDark := lipgloss.LightDark(hasDarkBG)\n\n\t// Base colors to demonstrate lightening and darkening.\n\tbaseColors := map[string]color.Color{\n\t\t\"Red\":   lipgloss.Color(\"#FF0000\"),\n\t\t\"Blue\":  lipgloss.Color(\"#0066FF\"),\n\t\t\"Green\": lipgloss.Color(\"#00FF00\"),\n\t\t\"Gray\":  lipgloss.Color(\"#808080\"),\n\t}\n\n\t// Percentage to lighten/darken by.\n\tpercentage := 0.05 // 5%\n\n\t// Number of steps to generate.\n\tsteps := 20\n\n\tcolorNameStyle := lipgloss.NewStyle().\n\t\tBold(true).\n\t\tForeground(lightDark(lipgloss.Color(\"#2D3748\"), lipgloss.Color(\"#E2E8F0\")))\n\n\tvar content strings.Builder\n\n\tfor name, baseColor := range baseColors {\n\t\tcontent.WriteString(colorNameStyle.Render(name))\n\t\tcontent.WriteString(\"\\n\")\n\n\t\t// Create lightened variations.\n\t\tvar lightenedBox strings.Builder\n\t\tlightenedBox.WriteString(\"Lightened: \")\n\t\tfor i := range steps {\n\t\t\tlightenedBox.WriteString(\n\t\t\t\tlipgloss.NewStyle().\n\t\t\t\t\tForeground(lipgloss.Lighten(baseColor, percentage*(float64(i)+1))).\n\t\t\t\t\tRender(\"██\"),\n\t\t\t)\n\t\t}\n\t\tcontent.WriteString(lightenedBox.String())\n\t\tcontent.WriteString(\"\\n\")\n\n\t\t// Create darkened variations.\n\t\tvar darkenedBox strings.Builder\n\t\tdarkenedBox.WriteString(\"Darkened:  \")\n\t\tfor i := range steps {\n\t\t\tdarkenedBox.WriteString(\n\t\t\t\tlipgloss.NewStyle().\n\t\t\t\t\tForeground(lipgloss.Darken(baseColor, percentage*(float64(i)+1))).\n\t\t\t\t\tRender(\"██\"),\n\t\t\t)\n\t\t}\n\t\tcontent.WriteString(darkenedBox.String())\n\t\tcontent.WriteString(\"\\n\\n\")\n\t}\n\n\tlipgloss.Println(content.String())\n}\n"
  },
  {
    "path": "examples/canvas/main.go",
    "content": "package main\n\nimport (\n\t\"image/color\"\n\t\"os\"\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/charmbracelet/x/exp/charmtone\"\n)\n\n// newField fills a rectangular area with a given character in a given color.\nfunc newField(rows, cols int, color color.Color) string {\n\tfieldSetyle := lipgloss.NewStyle().Foreground(color)\n\tfieldBuilder := strings.Builder{}\n\tfor i := range rows {\n\t\tfor range cols {\n\t\t\tfieldBuilder.WriteString(\"/\")\n\t\t}\n\t\tif i < rows-1 {\n\t\t\tfieldBuilder.WriteString(\"\\n\")\n\t\t}\n\t}\n\treturn fieldSetyle.Render(fieldBuilder.String())\n}\n\n// newCard creates a little card with rounded borders and a text label.\nfunc newCard(darkMode bool, text string) string {\n\tlightDark := lipgloss.LightDark(darkMode)\n\n\treturn lipgloss.NewStyle().\n\t\tBorder(lipgloss.RoundedBorder()).\n\t\tBorderForegroundBlend(\n\t\t\tcharmtone.Cherry,\n\t\t\tcharmtone.Charple,\n\t\t\tcharmtone.Guac,\n\t\t\tcharmtone.Charple,\n\t\t\tcharmtone.Sriracha,\n\t\t).\n\t\tForeground(lightDark(charmtone.Iron, charmtone.Butter)).\n\t\tHeight(9).\n\t\tWidth(16).\n\t\tPaddingTop(3).\n\t\tAlign(lipgloss.Center).\n\t\tRender(text)\n}\n\nfunc main() {\n\tdarkMode := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n\tlightDark := lipgloss.LightDark(darkMode)\n\n\t// A few text blocks.\n\tlighterField := newField(17, 43, lightDark(charmtone.Smoke, charmtone.Pepper))\n\tdarkerField := newField(17, 43, lightDark(charmtone.Squid, charmtone.Charcoal))\n\n\t// A few layers. Layers are created from strings (or blocks of text).\n\tpickles := lipgloss.NewLayer(newCard(darkMode, \"Pickles\"))\n\tmelon := lipgloss.NewLayer(newCard(darkMode, \"Bitter Melon\"))\n\tsriracha := lipgloss.NewLayer(newCard(darkMode, \"Sriracha\"))\n\n\t// Let's create our layers.\n\tlayers := []*lipgloss.Layer{\n\t\t// Layers can have X, Y, and Z offsets. By default, X, Y, and\n\t\t// Z are all 0.\n\t\tlipgloss.NewLayer(lighterField).X(5).Y(2),\n\n\t\t// Layers can be nested.\n\t\tlipgloss.NewLayer(darkerField).AddLayers(\n\t\t\tpickles.X(4).Y(2).Z(1), // the Z index places this layer above the others\n\t\t\tmelon.X(22).Y(1),\n\t\t\tsriracha.X(11).Y(7),\n\t\t),\n\t}\n\n\t// A compositor takes multiple layers and composites them together into\n\t// a single output.\n\tcomp := lipgloss.NewCompositor(layers...)\n\n\tlipgloss.Println(comp.Render())\n}\n"
  },
  {
    "path": "examples/color/bubbletea/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\n// Style definitions.\ntype styles struct {\n\tframe,\n\tparagraph,\n\ttext,\n\tkeyword,\n\tactiveButton,\n\tinactiveButton lipgloss.Style\n}\n\n// Styles are initialized based on the background color of the terminal.\nfunc newStyles(backgroundIsDark bool) (s *styles) {\n\ts = new(styles)\n\n\t// Create a new helper function for choosing either a light or dark color\n\t// based on the detected background color.\n\tlightDark := lipgloss.LightDark(backgroundIsDark)\n\n\t// Define some styles. adaptive.Color() can be used to choose the\n\t// appropriate light or dark color based on the detected background color.\n\ts.frame = lipgloss.NewStyle().\n\t\tBorder(lipgloss.RoundedBorder()).\n\t\tBorderForeground(lightDark(\n\t\t\tlipgloss.Color(\"#C5ADF9\"),\n\t\t\tlipgloss.Color(\"#864EFF\"))).\n\t\tPadding(1, 3).\n\t\tMargin(1, 3)\n\ts.paragraph = lipgloss.NewStyle().\n\t\tWidth(40).\n\t\tMarginBottom(1).\n\t\tAlign(lipgloss.Center)\n\ts.text = lipgloss.NewStyle().\n\t\tForeground(lightDark(\n\t\t\tlipgloss.Color(\"#696969\"),\n\t\t\tlipgloss.Color(\"#bdbdbd\")))\n\ts.keyword = lipgloss.NewStyle().\n\t\tForeground(lightDark(\n\t\t\tlipgloss.Color(\"#37CD96\"),\n\t\t\tlipgloss.Color(\"#22C78A\"))).\n\t\tBold(true)\n\n\ts.activeButton = lipgloss.NewStyle().\n\t\tPadding(0, 3).\n\t\tBackground(lipgloss.Color(\"#FF6AD2\")).\n\t\tForeground(lipgloss.Color(\"#FFFCC2\"))\n\ts.inactiveButton = s.activeButton.\n\t\tBackground(lightDark(\n\t\t\tlipgloss.Color(\"#988F95\"),\n\t\t\tlipgloss.Color(\"#978692\"))).\n\t\tForeground(lightDark(\n\t\t\tlipgloss.Color(\"#FDFCE3\"),\n\t\t\tlipgloss.Color(\"#FBFAE7\")))\n\treturn s\n}\n\ntype model struct {\n\tstyles  *styles\n\tyes     bool\n\tchosen  bool\n\taborted bool\n}\n\nfunc (m model) Init() tea.Cmd {\n\t// Query for the background color on start.\n\tm.yes = true\n\treturn tea.RequestBackgroundColor\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\n\t// Bubble Tea automatically detects the background color on start. We\n\t// listen for the response here, then initialize our styles accordingly.\n\tcase tea.BackgroundColorMsg:\n\t\tm.styles = newStyles(msg.IsDark())\n\t\treturn m, nil\n\n\tcase tea.KeyPressMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"esc\", \"ctrl+c\":\n\t\t\tm.aborted = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"enter\":\n\t\t\tm.chosen = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"left\", \"right\", \"h\", \"l\":\n\t\t\tm.yes = !m.yes\n\t\tcase \"y\":\n\t\t\tm.yes = true\n\t\t\tm.chosen = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"n\":\n\t\t\tm.yes = false\n\t\t\tm.chosen = 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\tvar v tea.View\n\tif m.styles == nil {\n\t\t// We haven't received tea.BackgroundColorMsg yet. Don't worry, it'll\n\t\t// be here in a flash.\n\t\treturn v\n\t}\n\tif m.chosen || m.aborted {\n\t\t// We're about to exit, so wipe the UI.\n\t\treturn v\n\t}\n\n\tvar (\n\t\ts = m.styles\n\t\ty = \"Yes\"\n\t\tn = \"No\"\n\t)\n\n\tif m.yes {\n\t\ty = s.activeButton.Render(y)\n\t\tn = s.inactiveButton.Render(n)\n\t} else {\n\t\ty = s.inactiveButton.Render(y)\n\t\tn = s.activeButton.Render(n)\n\t}\n\n\tcontent := s.frame.Render(\n\t\tlipgloss.JoinVertical(lipgloss.Center,\n\t\t\ts.paragraph.Render(\n\t\t\t\ts.text.Render(\"Are you sure you want to eat that \")+\n\t\t\t\t\ts.keyword.Render(\"moderatly ripe\")+\n\t\t\t\t\ts.text.Render(\" banana?\"),\n\t\t\t),\n\t\t\ty+\"  \"+n,\n\t\t),\n\t)\n\tv.SetContent(content)\n\treturn v\n}\n\nfunc main() {\n\tm, err := tea.NewProgram(model{}).Run()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Uh oh: %v\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif m := m.(model); m.chosen {\n\t\tif m.yes {\n\t\t\tfmt.Println(\"Are you sure? It's not ripe yet.\")\n\t\t} else {\n\t\t\tfmt.Println(\"Well, alright. It was probably good, though.\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/color/standalone/main.go",
    "content": "// This example illustrates how to detect the terminal's background color and\n// choose either light or dark colors accordingly when using Lip Gloss in a.\n// standalone fashion, i.e. independent of Bubble Tea.\n//\n// For an example of how to do this in a Bubble Tea program, see the\n// 'bubbletea' example.\npackage main\n\nimport (\n\t\"os\"\n\n\t\"charm.land/lipgloss/v2\"\n)\n\nfunc main() {\n\t// Query for the background color. We only need to do this once, and only\n\t// when using Lip Gloss standalone.\n\t//\n\t// In Bubble Tea listen for tea.BackgroundColorMsg in your Update.\n\thasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n\n\t// Create a new helper function for choosing either a light or dark color\n\t// based on the detected background color.\n\tlightDark := lipgloss.LightDark(hasDarkBG)\n\n\t// Define some styles. adaptive.Color() can be used to choose the\n\t// appropriate light or dark color based on the detected background color.\n\tframeStyle := lipgloss.NewStyle().\n\t\tBorder(lipgloss.RoundedBorder()).\n\t\tBorderForeground(lightDark(lipgloss.Color(\"#C5ADF9\"), lipgloss.Color(\"#864EFF\"))).\n\t\tPadding(1, 3).\n\t\tMargin(1, 3)\n\tparagraphStyle := lipgloss.NewStyle().\n\t\tWidth(40).\n\t\tMarginBottom(1).\n\t\tAlign(lipgloss.Center)\n\ttextStyle := lipgloss.NewStyle().\n\t\tForeground(lightDark(lipgloss.Color(\"#696969\"), lipgloss.Color(\"#bdbdbd\")))\n\tkeywordStyle := lipgloss.NewStyle().\n\t\tForeground(lightDark(lipgloss.Color(\"#37CD96\"), lipgloss.Color(\"#22C78A\"))).\n\t\tBold(true)\n\n\tactiveButton := lipgloss.NewStyle().\n\t\tPadding(0, 3).\n\t\tBackground(lipgloss.Color(\"#FF6AD2\")).\n\t\tForeground(lipgloss.Color(\"#FFFCC2\"))\n\tinactiveButton := activeButton.\n\t\tBackground(lightDark(lipgloss.Color(\"#988F95\"), lipgloss.Color(\"#978692\"))).\n\t\tForeground(lightDark(lipgloss.Color(\"#FDFCE3\"), lipgloss.Color(\"#FBFAE7\")))\n\n\t// Build layout.\n\ttext := paragraphStyle.Render(\n\t\ttextStyle.Render(\"Are you sure you want to eat that \") +\n\t\t\tkeywordStyle.Render(\"moderatly ripe\") +\n\t\t\ttextStyle.Render(\" banana?\"),\n\t)\n\tbuttons := activeButton.Render(\"Yes\") + \"  \" + inactiveButton.Render(\"No\")\n\tblock := frameStyle.Render(\n\t\tlipgloss.JoinVertical(lipgloss.Center, text, buttons),\n\t)\n\n\t// Print the block to stdout. It's important to use Lip Gloss's print\n\t// functions to ensure that colors are downsampled correctly. If output\n\t// isn't a TTY (i.e. we're logging to a file) colors will be stripped\n\t// entirely.\n\t//\n\t// Note that in Bubble Tea downsampling happens automatically.\n\tlipgloss.Println(block)\n}\n"
  },
  {
    "path": "examples/compat/bubbletea/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\"charm.land/lipgloss/v2/compat\"\n)\n\nvar (\n\tframeColor      = compat.AdaptiveColor{Light: lipgloss.Color(\"#C5ADF9\"), Dark: lipgloss.Color(\"#864EFF\")}\n\ttextColor       = compat.AdaptiveColor{Light: lipgloss.Color(\"#696969\"), Dark: lipgloss.Color(\"#bdbdbd\")}\n\tkeywordColor    = compat.AdaptiveColor{Light: lipgloss.Color(\"#37CD96\"), Dark: lipgloss.Color(\"#22C78A\")}\n\tinactiveBgColor = compat.AdaptiveColor{Light: lipgloss.Color(\"#988F95\"), Dark: lipgloss.Color(\"#978692\")}\n\tinactiveFgColor = compat.AdaptiveColor{Light: lipgloss.Color(\"#FDFCE3\"), Dark: lipgloss.Color(\"#FBFAE7\")}\n)\n\n// Style definitions.\ntype styles struct {\n\tframe,\n\tparagraph,\n\ttext,\n\tkeyword,\n\tactiveButton,\n\tinactiveButton lipgloss.Style\n}\n\n// Styles are initialized based on the background color of the terminal.\nfunc newStyles() (s styles) {\n\t// Define some styles. adaptive.Color() can be used to choose the\n\t// appropriate light or dark color based on the detected background color.\n\ts.frame = lipgloss.NewStyle().\n\t\tBorder(lipgloss.RoundedBorder()).\n\t\tBorderForeground(frameColor).\n\t\tPadding(1, 3).\n\t\tMargin(1, 3)\n\ts.paragraph = lipgloss.NewStyle().\n\t\tWidth(40).\n\t\tMarginBottom(1).\n\t\tAlign(lipgloss.Center)\n\ts.text = lipgloss.NewStyle().\n\t\tForeground(textColor)\n\ts.keyword = lipgloss.NewStyle().\n\t\tForeground(keywordColor).\n\t\tBold(true)\n\n\ts.activeButton = lipgloss.NewStyle().\n\t\tPadding(0, 3).\n\t\tBackground(lipgloss.Color(\"#FF6AD2\")).\n\t\tForeground(lipgloss.Color(\"#FFFCC2\"))\n\ts.inactiveButton = s.activeButton.\n\t\tBackground(inactiveBgColor).\n\t\tForeground(inactiveFgColor)\n\treturn s\n}\n\ntype model struct {\n\tstyles  styles\n\tyes     bool\n\tchosen  bool\n\taborted 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 \"q\", \"esc\", \"ctrl+c\":\n\t\t\tm.aborted = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"enter\":\n\t\t\tm.chosen = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"left\", \"right\", \"h\", \"l\":\n\t\t\tm.yes = !m.yes\n\t\tcase \"y\":\n\t\t\tm.yes = true\n\t\t\tm.chosen = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"n\":\n\t\t\tm.yes = false\n\t\t\tm.chosen = 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\tvar v tea.View\n\tif m.chosen || m.aborted {\n\t\t// We're about to exit, so wipe the UI.\n\t\treturn v\n\t}\n\n\tvar (\n\t\ts = m.styles\n\t\ty = \"Yes\"\n\t\tn = \"No\"\n\t)\n\n\tif m.yes {\n\t\ty = s.activeButton.Render(y)\n\t\tn = s.inactiveButton.Render(n)\n\t} else {\n\t\ty = s.inactiveButton.Render(y)\n\t\tn = s.activeButton.Render(n)\n\t}\n\n\tcontent := s.frame.Render(\n\t\tlipgloss.JoinVertical(lipgloss.Center,\n\t\t\ts.paragraph.Render(\n\t\t\t\ts.text.Render(\"Are you sure you want to eat that \")+\n\t\t\t\t\ts.keyword.Render(\"moderatly ripe\")+\n\t\t\t\t\ts.text.Render(\" banana?\"),\n\t\t\t),\n\t\t\ty+\"  \"+n,\n\t\t),\n\t)\n\tv.SetContent(content)\n\treturn v\n}\n\nfunc main() {\n\tinitialModel := model{\n\t\tyes:    true,\n\t\tstyles: newStyles(),\n\t}\n\tm, err := tea.NewProgram(initialModel).Run()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Uh oh: %v\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif m := m.(model); m.chosen {\n\t\tif m.yes {\n\t\t\tfmt.Println(\"Are you sure? It's not ripe yet.\")\n\t\t} else {\n\t\t\tfmt.Println(\"Well, alright. It was probably good, though.\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/compat/standalone/main.go",
    "content": "// This example illustrates how to detect the terminal's background color and\n// choose either light or dark colors accordingly when using Lip Gloss in a.\n// standalone fashion, i.e. independent of Bubble Tea.\n//\n// For an example of how to do this in a Bubble Tea program, see the\n// 'bubbletea' example.\npackage main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/compat\"\n)\n\nvar (\n\tframeColor      = compat.AdaptiveColor{Light: lipgloss.Color(\"#C5ADF9\"), Dark: lipgloss.Color(\"#864EFF\")}\n\ttextColor       = compat.AdaptiveColor{Light: lipgloss.Color(\"#696969\"), Dark: lipgloss.Color(\"#bdbdbd\")}\n\tkeywordColor    = compat.AdaptiveColor{Light: lipgloss.Color(\"#37CD96\"), Dark: lipgloss.Color(\"#22C78A\")}\n\tinactiveBgColor = compat.AdaptiveColor{Light: lipgloss.Color(\"#988F95\"), Dark: lipgloss.Color(\"#978692\")}\n\tinactiveFgColor = compat.AdaptiveColor{Light: lipgloss.Color(\"#FDFCE3\"), Dark: lipgloss.Color(\"#FBFAE7\")}\n)\n\nfunc main() {\n\t// Define some styles. adaptive.Color() can be used to choose the\n\t// appropriate light or dark color based on the detected background color.\n\tframeStyle := lipgloss.NewStyle().\n\t\tBorder(lipgloss.RoundedBorder()).\n\t\tBorderForeground(frameColor).\n\t\tPadding(1, 3).\n\t\tMargin(1, 3)\n\tparagraphStyle := lipgloss.NewStyle().\n\t\tWidth(40).\n\t\tMarginBottom(1).\n\t\tAlign(lipgloss.Center)\n\ttextStyle := lipgloss.NewStyle().\n\t\tForeground(textColor)\n\tkeywordStyle := lipgloss.NewStyle().\n\t\tForeground(keywordColor).\n\t\tBold(true)\n\n\tactiveButton := lipgloss.NewStyle().\n\t\tPadding(0, 3).\n\t\tBackground(lipgloss.Color(\"#FF6AD2\")).\n\t\tForeground(lipgloss.Color(\"#FFFCC2\"))\n\tinactiveButton := activeButton.\n\t\tBackground(inactiveBgColor).\n\t\tForeground(inactiveFgColor)\n\n\t// Build layout.\n\ttext := paragraphStyle.Render(\n\t\ttextStyle.Render(\"Are you sure you want to eat that \") +\n\t\t\tkeywordStyle.Render(\"moderatly ripe\") +\n\t\t\ttextStyle.Render(\" banana?\"),\n\t)\n\tbuttons := activeButton.Render(\"Yes\") + \"  \" + inactiveButton.Render(\"No\")\n\tblock := frameStyle.Render(\n\t\tlipgloss.JoinVertical(lipgloss.Center, text, buttons),\n\t)\n\n\t// Print the block to stdout. It's important to use Lip Gloss's print\n\t// functions to ensure that colors are downsampled correctly. If output\n\t// isn't a TTY (i.e. we're logging to a file) colors will be stripped\n\t// entirely.\n\t//\n\t// Note that in Bubble Tea downsampling happens automatically.\n\tlipgloss.Println(block)\n}\n"
  },
  {
    "path": "examples/go.mod",
    "content": "module examples\n\ngo 1.24.3\n\ntoolchain go1.24.4\n\nreplace charm.land/lipgloss/v2 => ../\n\nrequire (\n\tcharm.land/bubbletea/v2 v2.0.0-rc.2.0.20251201184111-551c60ee5a5c\n\tcharm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7\n\tgithub.com/charmbracelet/colorprofile v0.4.2\n\tgithub.com/charmbracelet/ssh v0.0.0-20241211182756-4fe22b0f1b7c\n\tgithub.com/charmbracelet/wish/v2 v2.0.0-20251106193208-3cd15da8229f\n\tgithub.com/charmbracelet/x/exp/charmtone v0.0.0-20250627134340-c144409e381c\n\tgithub.com/charmbracelet/x/term v0.2.2\n\tgithub.com/rivo/uniseg v0.4.7\n)\n\nrequire (\n\tgithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect\n\tgithub.com/charmbracelet/keygen v0.5.1 // indirect\n\tgithub.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0 // indirect\n\tgithub.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect\n\tgithub.com/charmbracelet/x/ansi v0.11.6 // indirect\n\tgithub.com/charmbracelet/x/conpty v0.1.0 // indirect\n\tgithub.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 // 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/creack/pty v1.1.21 // indirect\n\tgithub.com/go-logfmt/logfmt v0.6.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/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgolang.org/x/crypto v0.36.0 // indirect\n\tgolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect\n\tgolang.org/x/sync v0.18.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n)\n\n// replace with log v2\nreplace github.com/charmbracelet/log => github.com/charmbracelet/log v0.4.1-0.20241010222913-47ce960d4847\n"
  },
  {
    "path": "examples/go.sum",
    "content": "charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251201184111-551c60ee5a5c h1:Jn9nugUf2ddyARHsA79zsWl7szy7dV7HpBj645Sp6DU=\ncharm.land/bubbletea/v2 v2.0.0-rc.2.0.20251201184111-551c60ee5a5c/go.mod h1:BLGnNsQA++rg5IEiTVeLix8AKte880DQWjc8Afs3Nw8=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=\ngithub.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=\ngithub.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=\ngithub.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=\ngithub.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=\ngithub.com/charmbracelet/keygen v0.5.1 h1:zBkkYPtmKDVTw+cwUyY6ZwGDhRxXkEp0Oxs9sqMLqxI=\ngithub.com/charmbracelet/keygen v0.5.1/go.mod h1:zznJVmK/GWB6dAtjluqn2qsttiCBhA5MZSiwb80fcHw=\ngithub.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0 h1:lxHzxsHd4P7o7+5D5OcEItYkQ1xY3ovNg8Dc5ftd3rI=\ngithub.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0/go.mod h1:Q7oMtlboDPnnrYiJDXNwdWmJblOmuOnycPKczlVju6I=\ngithub.com/charmbracelet/ssh v0.0.0-20241211182756-4fe22b0f1b7c h1:treQxMBdI2PaD4eOYfFux8stfCkUxhuUxaqGcxKqVpI=\ngithub.com/charmbracelet/ssh v0.0.0-20241211182756-4fe22b0f1b7c/go.mod h1:CY1xbl2z+ZeBmNWItKZyxx0zgDgnhmR57+DTsHOobJ4=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 h1:OqDqxQZliC7C8adA7KjelW3OjtAxREfeHkNcd66wpeI=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI=\ngithub.com/charmbracelet/wish/v2 v2.0.0-20251106193208-3cd15da8229f h1:yR3ru/zfVX4cnyhs5GPL1dxArAtxL/IZzJ9/mt1IoeI=\ngithub.com/charmbracelet/wish/v2 v2.0.0-20251106193208-3cd15da8229f/go.mod h1:YW+dfIwHgy7eKZqSffA+Fx9EEW2YyXKUGkAdijsvpGI=\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/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=\ngithub.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=\ngithub.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=\ngithub.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=\ngithub.com/charmbracelet/x/exp/charmtone v0.0.0-20250627134340-c144409e381c h1:2GELBLPgfSbHU53bsQhR9XIgNuVZ6w+Rz8RWV5Lq+A4=\ngithub.com/charmbracelet/x/exp/charmtone v0.0.0-20250627134340-c144409e381c/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/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/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=\ngithub.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=\ngithub.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=\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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngolang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=\ngolang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=\ngolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=\ngolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.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=\ngolang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=\ngolang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "examples/layout/main.go",
    "content": "package main\n\n// This example demonstrates various Lip Gloss style and layout features.\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"os\"\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/charmbracelet/x/term\"\n\t\"github.com/rivo/uniseg\"\n)\n\nconst (\n\t// In real life situations we'd adjust the document to fit the width we've\n\t// detected. In the case of this example we're hardcoding the width, and\n\t// later using the detected width only to truncate in order to avoid jaggy\n\t// wrapping.\n\twidth = 96\n\n\t// How wide to render various columns in the layout.\n\tcolumnWidth = 30\n)\n\nvar (\n\t// Whether the detected background color is dark. We detect this at app start.\n\thasDarkBG bool\n\n\t// A helper function for choosing either a light or dark color based on the\n\t// detected background color. We create this at app start.\n\tlightDark lipgloss.LightDarkFunc\n)\n\nfunc main() {\n\t// Detect the background color.\n\thasDarkBG = lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n\n\t// Create a new helper function for choosing either a light or dark color\n\t// based on the detected background color.\n\tlightDark = lipgloss.LightDark(hasDarkBG)\n\n\t// Style definitions.\n\tvar (\n\n\t\t// General.\n\n\t\tsubtle    = lightDark(lipgloss.Color(\"#D9DCCF\"), lipgloss.Color(\"#383838\"))\n\t\thighlight = lightDark(lipgloss.Color(\"#874BFD\"), lipgloss.Color(\"#7D56F4\"))\n\t\tspecial   = lightDark(lipgloss.Color(\"#43BF6D\"), lipgloss.Color(\"#73F59F\"))\n\n\t\tdivider = lipgloss.NewStyle().\n\t\t\tSetString(\"•\").\n\t\t\tPadding(0, 1).\n\t\t\tForeground(subtle).\n\t\t\tString()\n\n\t\turl = lipgloss.NewStyle().Foreground(special).Render\n\n\t\t// Tabs.\n\n\t\tactiveTabBorder = lipgloss.Border{\n\t\t\tTop:         \"─\",\n\t\t\tBottom:      \" \",\n\t\t\tLeft:        \"│\",\n\t\t\tRight:       \"│\",\n\t\t\tTopLeft:     \"╭\",\n\t\t\tTopRight:    \"╮\",\n\t\t\tBottomLeft:  \"┘\",\n\t\t\tBottomRight: \"└\",\n\t\t}\n\n\t\ttabBorder = lipgloss.Border{\n\t\t\tTop:         \"─\",\n\t\t\tBottom:      \"─\",\n\t\t\tLeft:        \"│\",\n\t\t\tRight:       \"│\",\n\t\t\tTopLeft:     \"╭\",\n\t\t\tTopRight:    \"╮\",\n\t\t\tBottomLeft:  \"┴\",\n\t\t\tBottomRight: \"┴\",\n\t\t}\n\n\t\ttab = lipgloss.NewStyle().\n\t\t\tBorder(tabBorder, true).\n\t\t\tBorderForeground(highlight).\n\t\t\tPadding(0, 1)\n\n\t\tactiveTab = tab.Border(activeTabBorder, true)\n\n\t\ttabGap = tab.\n\t\t\tBorderTop(false).\n\t\t\tBorderLeft(false).\n\t\t\tBorderRight(false)\n\n\t\t// Title.\n\n\t\ttitleStyle = lipgloss.NewStyle().\n\t\t\t\tMarginLeft(1).\n\t\t\t\tMarginRight(5).\n\t\t\t\tPadding(0, 1).\n\t\t\t\tItalic(true).\n\t\t\t\tForeground(lipgloss.Color(\"#FFF7DB\")).\n\t\t\t\tSetString(\"Lip Gloss\")\n\n\t\tdescStyle = lipgloss.NewStyle().MarginTop(1)\n\n\t\tinfoStyle = lipgloss.NewStyle().\n\t\t\t\tBorderStyle(lipgloss.NormalBorder()).\n\t\t\t\tBorderTop(true).\n\t\t\t\tBorderForeground(subtle)\n\n\t\t// Dialog.\n\n\t\tdialogBoxStyle = lipgloss.NewStyle().\n\t\t\t\tBorder(lipgloss.RoundedBorder()).\n\t\t\t\tBorderForeground(lipgloss.Color(\"#874BFD\")).\n\t\t\t\tPadding(1, 0).\n\t\t\t\tBorderTop(true).\n\t\t\t\tBorderLeft(true).\n\t\t\t\tBorderRight(true).\n\t\t\t\tBorderBottom(true)\n\n\t\tbuttonStyle = lipgloss.NewStyle().\n\t\t\t\tForeground(lipgloss.Color(\"#FFF7DB\")).\n\t\t\t\tBackground(lipgloss.Color(\"#888B7E\")).\n\t\t\t\tPadding(0, 3).\n\t\t\t\tMarginTop(1)\n\n\t\tactiveButtonStyle = buttonStyle.\n\t\t\t\t\tForeground(lipgloss.Color(\"#FFF7DB\")).\n\t\t\t\t\tBackground(lipgloss.Color(\"#F25D94\")).\n\t\t\t\t\tMarginRight(2).\n\t\t\t\t\tUnderline(true)\n\n\t\t// List.\n\n\t\tlist = lipgloss.NewStyle().\n\t\t\tBorder(lipgloss.NormalBorder(), false, true, false, false).\n\t\t\tBorderForeground(subtle).\n\t\t\tMarginRight(1).\n\t\t\tHeight(8).\n\t\t\tWidth(width / 3)\n\n\t\tlistHeader = lipgloss.NewStyle().\n\t\t\t\tBorderStyle(lipgloss.NormalBorder()).\n\t\t\t\tBorderBottom(true).\n\t\t\t\tBorderForeground(subtle).\n\t\t\t\tMarginRight(2).\n\t\t\t\tRender\n\n\t\tlistItem = lipgloss.NewStyle().PaddingLeft(2).Render\n\n\t\tcheckMark = lipgloss.NewStyle().SetString(\"✓\").\n\t\t\t\tForeground(special).\n\t\t\t\tPaddingRight(1).\n\t\t\t\tString()\n\n\t\tlistDone = func(s string) string {\n\t\t\treturn checkMark + lipgloss.NewStyle().\n\t\t\t\tStrikethrough(true).\n\t\t\t\tForeground(lightDark(lipgloss.Color(\"#969B86\"), lipgloss.Color(\"#696969\"))).\n\t\t\t\tRender(s)\n\t\t}\n\n\t\t// Paragraphs/History.\n\n\t\thistoryStyle = lipgloss.NewStyle().\n\t\t\t\tAlign(lipgloss.Left).\n\t\t\t\tForeground(lipgloss.Color(\"#FAFAFA\")).\n\t\t\t\tBackground(highlight).\n\t\t\t\tMargin(1, 3, 0, 0).\n\t\t\t\tPadding(1, 2).\n\t\t\t\tHeight(19).\n\t\t\t\tWidth(columnWidth)\n\n\t\t// Status Bar.\n\n\t\tstatusNugget = lipgloss.NewStyle().\n\t\t\t\tForeground(lipgloss.Color(\"#FFFDF5\")).\n\t\t\t\tPadding(0, 1)\n\n\t\tstatusBarStyle = lipgloss.NewStyle().\n\t\t\t\tForeground(lightDark(lipgloss.Color(\"#343433\"), lipgloss.Color(\"#C1C6B2\"))).\n\t\t\t\tBackground(lightDark(lipgloss.Color(\"#D9DCCF\"), lipgloss.Color(\"#353533\")))\n\n\t\tstatusStyle = lipgloss.NewStyle().\n\t\t\t\tInherit(statusBarStyle).\n\t\t\t\tForeground(lipgloss.Color(\"#FFFDF5\")).\n\t\t\t\tBackground(lipgloss.Color(\"#FF5F87\")).\n\t\t\t\tPadding(0, 1).\n\t\t\t\tMarginRight(1)\n\n\t\tencodingStyle = statusNugget.\n\t\t\t\tBackground(lipgloss.Color(\"#A550DF\")).\n\t\t\t\tAlign(lipgloss.Right)\n\n\t\tstatusText = lipgloss.NewStyle().Inherit(statusBarStyle)\n\n\t\tfishCakeStyle = statusNugget.Background(lipgloss.Color(\"#6124DF\"))\n\n\t\t// Floating thing.\n\n\t\tfloatingStyle = lipgloss.NewStyle().\n\t\t\t\tItalic(true).\n\t\t\t\tForeground(lipgloss.Color(\"#FFF7DB\")).\n\t\t\t\tBackground(lipgloss.Color(\"#F25D94\")).\n\t\t\t\tPadding(1, 6).\n\t\t\t\tAlign(lipgloss.Center)\n\n\t\t// Page.\n\n\t\tdocStyle = lipgloss.NewStyle().Padding(1, 2, 1, 2)\n\t)\n\n\tphysicalWidth, _, _ := term.GetSize(os.Stdout.Fd())\n\tdoc := strings.Builder{}\n\n\t// Tabs.\n\t{\n\t\trow := lipgloss.JoinHorizontal(\n\t\t\tlipgloss.Top,\n\t\t\tactiveTab.Render(\"Lip Gloss\"),\n\t\t\ttab.Render(\"Blush\"),\n\t\t\ttab.Render(\"Eye Shadow\"),\n\t\t\ttab.Render(\"Mascara\"),\n\t\t\ttab.Render(\"Foundation\"),\n\t\t)\n\t\tgap := tabGap.Render(strings.Repeat(\" \", max(0, width-lipgloss.Width(row)-2)))\n\t\trow = lipgloss.JoinHorizontal(lipgloss.Bottom, row, gap)\n\t\tdoc.WriteString(row + \"\\n\\n\")\n\t}\n\n\t// Title.\n\t{\n\t\tvar (\n\t\t\tcolors = colorGrid(1, 5)\n\t\t\ttitle  strings.Builder\n\t\t)\n\n\t\tfor i, v := range colors {\n\t\t\tconst offset = 2\n\t\t\tfmt.Fprint(&title, titleStyle.MarginLeft(i*offset).Background(v[0]))\n\t\t\tif i < len(colors)-1 {\n\t\t\t\ttitle.WriteRune('\\n')\n\t\t\t}\n\t\t}\n\n\t\tdesc := lipgloss.JoinVertical(lipgloss.Left,\n\t\t\tdescStyle.Render(\"Style Definitions for Nice Terminal Layouts\"),\n\t\t\tinfoStyle.Render(\"From Charm\"+divider+url(\"https://github.com/charmbracelet/lipgloss\")),\n\t\t)\n\n\t\trow := lipgloss.JoinHorizontal(lipgloss.Top, title.String(), desc)\n\t\tdoc.WriteString(row + \"\\n\\n\")\n\t}\n\n\t// Dialog.\n\t{\n\t\tokButton := activeButtonStyle.Render(\"Yes\")\n\t\tcancelButton := buttonStyle.Render(\"Maybe\")\n\n\t\tgrad := applyGradient(\n\t\t\tlipgloss.NewStyle(),\n\t\t\t\"Are you sure you want to eat marmalade?\",\n\t\t\tlipgloss.Color(\"#EDFF82\"),\n\t\t\tlipgloss.Color(\"#F25D94\"),\n\t\t)\n\n\t\tquestion := lipgloss.NewStyle().\n\t\t\tWidth(50).\n\t\t\tAlign(lipgloss.Center).\n\t\t\tRender(grad)\n\n\t\tbuttons := lipgloss.JoinHorizontal(lipgloss.Top, okButton, cancelButton)\n\t\tui := lipgloss.JoinVertical(lipgloss.Center, question, buttons)\n\n\t\tdialog := lipgloss.Place(width, 9,\n\t\t\tlipgloss.Center, lipgloss.Center,\n\t\t\tdialogBoxStyle.Render(ui),\n\t\t\tlipgloss.WithWhitespaceChars(\"猫咪\"),\n\t\t\tlipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Foreground(subtle)),\n\t\t)\n\n\t\tdoc.WriteString(dialog + \"\\n\\n\")\n\t}\n\n\t// Color grid.\n\tcolors := func() string {\n\t\tcolors := colorGrid(14, 8)\n\n\t\tb := strings.Builder{}\n\t\tfor _, x := range colors {\n\t\t\tfor _, y := range x {\n\t\t\t\ts := lipgloss.NewStyle().SetString(\"  \").Background(y)\n\t\t\t\tb.WriteString(s.String())\n\t\t\t}\n\t\t\tb.WriteRune('\\n')\n\t\t}\n\n\t\treturn b.String()\n\t}()\n\n\tlists := lipgloss.JoinHorizontal(lipgloss.Top,\n\t\tlist.Render(\n\t\t\tlipgloss.JoinVertical(lipgloss.Left,\n\t\t\t\tlistHeader(\"Citrus Fruits to Try\"),\n\t\t\t\tlistDone(\"Grapefruit\"),\n\t\t\t\tlistDone(\"Yuzu\"),\n\t\t\t\tlistItem(\"Citron\"),\n\t\t\t\tlistItem(\"Kumquat\"),\n\t\t\t\tlistItem(\"Pomelo\"),\n\t\t\t),\n\t\t),\n\t\tlist.Render(\n\t\t\tlipgloss.JoinVertical(lipgloss.Left,\n\t\t\t\tlistHeader(\"Actual Lip Gloss Vendors\"),\n\t\t\t\tlistItem(\"Glossier\"),\n\t\t\t\tlistItem(\"Claire‘s Boutique\"),\n\t\t\t\tlistDone(\"Nyx\"),\n\t\t\t\tlistItem(\"Mac\"),\n\t\t\t\tlistDone(\"Milk\"),\n\t\t\t),\n\t\t),\n\t)\n\n\tdoc.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, lists, lipgloss.NewStyle().MarginLeft(1).Render(colors)))\n\n\t// Marmalade history.\n\t{\n\t\tconst (\n\t\t\thistoryA = \"The Romans learned from the Greeks that quinces slowly cooked with honey would “set” when cool. The Apicius gives a recipe for preserving whole quinces, stems and leaves attached, in a bath of honey diluted with defrutum: Roman marmalade. Preserves of quince and lemon appear (along with rose, apple, plum and pear) in the Book of ceremonies of the Byzantine Emperor Constantine VII Porphyrogennetos.\"\n\t\t\thistoryB = \"Medieval quince preserves, which went by the French name cotignac, produced in a clear version and a fruit pulp version, began to lose their medieval seasoning of spices in the 16th century. In the 17th century, La Varenne provided recipes for both thick and clear cotignac.\"\n\t\t\thistoryC = \"In 1524, Henry VIII, King of England, received a “box of marmalade” from Mr. Hull of Exeter. This was probably marmelada, a solid quince paste from Portugal, still made and sold in southern Europe today. It became a favourite treat of Anne Boleyn and her ladies in waiting.\"\n\t\t)\n\n\t\tdoc.WriteString(lipgloss.JoinHorizontal(\n\t\t\tlipgloss.Top,\n\t\t\thistoryStyle.Align(lipgloss.Right).Render(historyA),\n\t\t\thistoryStyle.Align(lipgloss.Center).Render(historyB),\n\t\t\thistoryStyle.MarginRight(0).Render(historyC),\n\t\t))\n\n\t\tdoc.WriteString(\"\\n\\n\")\n\t}\n\n\t// Status bar.\n\t{\n\t\tw := lipgloss.Width\n\n\t\tlightDarkState := \"Light\"\n\t\tif hasDarkBG {\n\t\t\tlightDarkState = \"Dark\"\n\t\t}\n\n\t\tstatusKey := statusStyle.Render(\"STATUS\")\n\t\tencoding := encodingStyle.Render(\"UTF-8\")\n\t\tfishCake := fishCakeStyle.Render(\"🍥 Fish Cake\")\n\t\tstatusVal := statusText.\n\t\t\tWidth(width - w(statusKey) - w(encoding) - w(fishCake)).\n\t\t\tRender(\"Ravishingly \" + lightDarkState + \"!\")\n\n\t\tbar := lipgloss.JoinHorizontal(lipgloss.Top,\n\t\t\tstatusKey,\n\t\t\tstatusVal,\n\t\t\tencoding,\n\t\t\tfishCake,\n\t\t)\n\n\t\tdoc.WriteString(statusBarStyle.Width(width).Render(bar))\n\t}\n\n\tif physicalWidth > 0 {\n\t\tdocStyle = docStyle.MaxWidth(physicalWidth)\n\t}\n\n\t// Render the document.\n\tdocument := docStyle.Render(doc.String())\n\n\t// Surprise! Composite some bonus content on top of the document.\n\tmodal := floatingStyle.Render(\"Now with Compositing!\")\n\tlayers := []*lipgloss.Layer{\n\t\tlipgloss.NewLayer(document),\n\t\tlipgloss.NewLayer(modal).X(58).Y(44),\n\t}\n\n\tcomp := lipgloss.NewCompositor(layers...)\n\n\t// Okay, let's print it. We use a special Lipgloss writer to downsample\n\t// colors to the terminal's color palette. And, if output's not a TTY, we\n\t// will remove color entirely.\n\tlipgloss.Println(comp.Render())\n}\n\n// colorGrid blends colors from 4 corner quadrants, into a box region.\nfunc colorGrid(xSteps, ySteps int) [][]color.Color {\n\tleftColors := lipgloss.Blend1D(ySteps, lipgloss.Color(\"#F25D94\"), lipgloss.Color(\"#643AFF\"))\n\trightColors := lipgloss.Blend1D(ySteps, lipgloss.Color(\"#EDFF82\"), lipgloss.Color(\"#14F9D5\"))\n\n\tgrid := make([][]color.Color, ySteps)\n\tfor y := range ySteps {\n\t\trowColors := lipgloss.Blend1D(xSteps, leftColors[y], rightColors[y])\n\t\tgrid[y] = make([]color.Color, xSteps)\n\t\tfor x := range xSteps {\n\t\t\tgrid[y][x] = rowColors[x]\n\t\t}\n\t}\n\treturn grid\n}\n\n// applyGradient applies a gradient to the given string.\nfunc applyGradient(base lipgloss.Style, input string, from, to color.Color) string {\n\t// We want to get the graphemes of the input string, which is the number of\n\t// characters as a human would see them.\n\t//\n\t// We definitely don't want to use len(), because that returns the\n\t// bytes. The rune count would get us closer but there are times, like with\n\t// emojis, where the rune count is greater than the number of actual\n\t// characters.\n\tg := uniseg.NewGraphemes(input)\n\tvar chars []string\n\tfor g.Next() {\n\t\tchars = append(chars, g.Str())\n\t}\n\n\tgradient := lipgloss.Blend1D(len(chars), from, to)\n\tvar output strings.Builder\n\tfor i, char := range chars {\n\t\toutput.WriteString(base.Foreground(gradient[i]).Render(char))\n\t}\n\treturn output.String()\n}\n"
  },
  {
    "path": "examples/list/duckduckgoose/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/list\"\n)\n\nfunc duckDuckGooseEnumerator(items list.Items, i int) string {\n\tif items.At(i).Value() == \"Goose\" {\n\t\treturn \"Honk →\"\n\t}\n\treturn \" \"\n}\n\nfunc main() {\n\tenumStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"#00d787\")).MarginRight(1)\n\titemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"255\"))\n\n\tl := list.New(\"Duck\", \"Duck\", \"Duck\", \"Goose\", \"Duck\").\n\t\tItemStyle(itemStyle).\n\t\tEnumeratorStyle(enumStyle).\n\t\tEnumerator(duckDuckGooseEnumerator)\n\n\tlipgloss.Println(l)\n}\n"
  },
  {
    "path": "examples/list/glow/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/list\"\n)\n\ntype Document struct {\n\tName string\n\tTime string\n}\n\nvar faint = lipgloss.NewStyle().Faint(true)\n\nfunc (d Document) String() string {\n\treturn d.Name + \"\\n\" +\n\t\tfaint.Render(d.Time)\n}\n\nvar docs = []Document{\n\t{\"README.md\", \"2 minutes ago\"},\n\t{\"Example.md\", \"1 hour ago\"},\n\t{\"secrets.md\", \"1 week ago\"},\n}\n\nconst selected = 1\n\nfunc main() {\n\tbaseStyle := lipgloss.NewStyle().\n\t\tMarginBottom(1).\n\t\tMarginLeft(1)\n\tdimColor := lipgloss.Color(\"250\")\n\thightlightColor := lipgloss.Color(\"#EE6FF8\")\n\n\tl := list.New().\n\t\tEnumerator(func(_ list.Items, i int) string {\n\t\t\tif i == selected {\n\t\t\t\treturn \"│\\n│\"\n\t\t\t}\n\t\t\treturn \" \"\n\t\t}).\n\t\tItemStyleFunc(func(_ list.Items, i int) lipgloss.Style {\n\t\t\tst := baseStyle\n\t\t\tif selected == i {\n\t\t\t\treturn st.Foreground(hightlightColor)\n\t\t\t}\n\t\t\treturn st.Foreground(dimColor)\n\t\t}).\n\t\tEnumeratorStyleFunc(func(_ list.Items, i int) lipgloss.Style {\n\t\t\tif selected == i {\n\t\t\t\treturn lipgloss.NewStyle().Foreground(hightlightColor)\n\t\t\t}\n\t\t\treturn lipgloss.NewStyle().Foreground(dimColor)\n\t\t})\n\n\tfor _, d := range docs {\n\t\tl.Item(d.String())\n\t}\n\n\tlipgloss.Print(\"\\n\", l, \"\\n\")\n}\n"
  },
  {
    "path": "examples/list/grocery/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/list\"\n)\n\nvar purchased = []string{\n\t\"Bananas\",\n\t\"Barley\",\n\t\"Cashews\",\n\t\"Coconut Milk\",\n\t\"Dill\",\n\t\"Eggs\",\n\t\"Fish Cake\",\n\t\"Leeks\",\n\t\"Papaya\",\n}\n\nfunc groceryEnumerator(items list.Items, i int) string {\n\tfor _, p := range purchased {\n\t\tif items.At(i).Value() == p {\n\t\t\treturn \"✓\"\n\t\t}\n\t}\n\treturn \"•\"\n}\n\nvar dimEnumStyle = lipgloss.NewStyle().\n\tForeground(lipgloss.Color(\"240\")).\n\tMarginRight(1)\n\nvar highlightedEnumStyle = lipgloss.NewStyle().\n\tForeground(lipgloss.Color(\"10\")).\n\tMarginRight(1)\n\nfunc enumStyleFunc(items list.Items, i int) lipgloss.Style {\n\tfor _, p := range purchased {\n\t\tif items.At(i).Value() == p {\n\t\t\treturn highlightedEnumStyle\n\t\t}\n\t}\n\treturn dimEnumStyle\n}\n\nfunc itemStyleFunc(items list.Items, i int) lipgloss.Style {\n\titemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"255\"))\n\tfor _, p := range purchased {\n\t\tif items.At(i).Value() == p {\n\t\t\treturn itemStyle.Strikethrough(true)\n\t\t}\n\t}\n\treturn itemStyle\n}\n\nfunc main() {\n\tl := list.New(\n\t\t\"Artichoke\",\n\t\t\"Baking Flour\", \"Bananas\", \"Barley\", \"Bean Sprouts\",\n\t\t\"Cashew Apple\", \"Cashews\", \"Coconut Milk\", \"Curry Paste\", \"Currywurst\",\n\t\t\"Dill\", \"Dragonfruit\", \"Dried Shrimp\",\n\t\t\"Eggs\",\n\t\t\"Fish Cake\", \"Furikake\",\n\t\t\"Jicama\",\n\t\t\"Kohlrabi\",\n\t\t\"Leeks\", \"Lentils\", \"Licorice Root\",\n\t).\n\t\tEnumerator(groceryEnumerator).\n\t\tEnumeratorStyleFunc(enumStyleFunc).\n\t\tItemStyleFunc(itemStyleFunc)\n\n\tlipgloss.Println(l)\n}\n"
  },
  {
    "path": "examples/list/roman/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/list\"\n)\n\nfunc main() {\n\tenumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"99\")).MarginRight(1)\n\titemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"255\")).MarginRight(1)\n\n\tl := list.New(\n\t\t\"Glossier\",\n\t\t\"Claire’s Boutique\",\n\t\t\"Nyx\",\n\t\t\"Mac\",\n\t\t\"Milk\",\n\t).\n\t\tEnumerator(list.Roman).\n\t\tEnumeratorStyle(enumeratorStyle).\n\t\tItemStyle(itemStyle)\n\n\tlipgloss.Println(l)\n}\n"
  },
  {
    "path": "examples/list/simple/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/list\"\n)\n\nfunc main() {\n\tl := list.New(\n\t\t\"A\",\n\t\t\"B\",\n\t\t\"C\",\n\t\tlist.New(\n\t\t\t\"D\",\n\t\t\t\"E\",\n\t\t\t\"F\",\n\t\t).Enumerator(list.Roman),\n\t\t\"G\",\n\t)\n\tlipgloss.Println(l)\n}\n"
  },
  {
    "path": "examples/list/sublist/main.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/list\"\n\t\"charm.land/lipgloss/v2/table\"\n)\n\nfunc main() {\n\thasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)\n\tlightDark := lipgloss.LightDark(hasDarkBG)\n\n\tpurple := lipgloss.NewStyle().\n\t\tForeground(lipgloss.Color(\"99\")).\n\t\tMarginRight(1)\n\n\tpink := lipgloss.NewStyle().\n\t\tForeground(lipgloss.Color(\"212\")).\n\t\tMarginRight(1)\n\n\tbase := lipgloss.NewStyle().\n\t\tMarginBottom(1).\n\t\tMarginLeft(1)\n\n\tfaint := lipgloss.NewStyle().Faint(true)\n\n\tdim := lipgloss.Color(\"250\")\n\thighlight := lipgloss.Color(\"#EE6FF8\")\n\n\tspecial := lightDark(lipgloss.Color(\"#43BF6D\"), lipgloss.Color(\"#73F59F\"))\n\n\tchecklistEnumStyle := func(items list.Items, index int) lipgloss.Style {\n\t\tswitch index {\n\t\tcase 1, 2, 4:\n\t\t\treturn lipgloss.NewStyle().\n\t\t\t\tForeground(special).\n\t\t\t\tPaddingRight(1)\n\t\tdefault:\n\t\t\treturn lipgloss.NewStyle().PaddingRight(1)\n\t\t}\n\t}\n\n\tchecklistEnum := func(items list.Items, index int) string {\n\t\tswitch index {\n\t\tcase 1, 2, 4:\n\t\t\treturn \"✓\"\n\t\tdefault:\n\t\t\treturn \"•\"\n\t\t}\n\t}\n\n\tchecklistStyle := func(items list.Items, index int) lipgloss.Style {\n\t\tswitch index {\n\t\tcase 1, 2, 4:\n\t\t\treturn lipgloss.NewStyle().\n\t\t\t\tStrikethrough(true).\n\t\t\t\tForeground(lightDark(lipgloss.Color(\"#969B86\"), lipgloss.Color(\"#696969\")))\n\t\tdefault:\n\t\t\treturn lipgloss.NewStyle()\n\t\t}\n\t}\n\n\tgradient := lipgloss.Blend1D(5, lipgloss.Color(\"#F25D94\"), lipgloss.Color(\"#643AFF\"))\n\n\ttitleStyle := lipgloss.NewStyle().\n\t\tItalic(true).\n\t\tForeground(lipgloss.Color(\"#FFF7DB\"))\n\n\tlipglossStyleFunc := func(items list.Items, index int) lipgloss.Style {\n\t\tif index == items.Length()-1 {\n\t\t\treturn titleStyle.Padding(1, 2).Margin(0, 0, 1, 0).MaxWidth(20).Background(gradient[index])\n\t\t}\n\t\treturn titleStyle.Padding(0, 5-index, 0, index+2).MaxWidth(20).Background(gradient[index])\n\t}\n\n\thistory := \"Medieval quince preserves, which went by the French name cotignac, produced in a clear version and a fruit pulp version, began to lose their medieval seasoning of spices in the 16th century. In the 17th century, La Varenne provided recipes for both thick and clear cotignac.\"\n\n\tl := list.New().\n\t\tEnumeratorStyle(purple).\n\t\tItem(\"Lip Gloss\").\n\t\tItem(\"Blush\").\n\t\tItem(\"Eye Shadow\").\n\t\tItem(\"Mascara\").\n\t\tItem(\"Foundation\").\n\t\tItem(\n\t\t\tlist.New().\n\t\t\t\tEnumeratorStyle(pink).\n\t\t\t\tItem(\"Citrus Fruits to Try\").\n\t\t\t\tItem(\n\t\t\t\t\tlist.New().\n\t\t\t\t\t\tItemStyleFunc(checklistStyle).\n\t\t\t\t\t\tEnumeratorStyleFunc(checklistEnumStyle).\n\t\t\t\t\t\tEnumerator(checklistEnum).\n\t\t\t\t\t\tItem(\"Grapefruit\").\n\t\t\t\t\t\tItem(\"Yuzu\").\n\t\t\t\t\t\tItem(\"Citron\").\n\t\t\t\t\t\tItem(\"Kumquat\").\n\t\t\t\t\t\tItem(\"Pomelo\"),\n\t\t\t\t).\n\t\t\t\tItem(\"Actual Lip Gloss Vendors\").\n\t\t\t\tItem(\n\t\t\t\t\tlist.New().\n\t\t\t\t\t\tItemStyleFunc(checklistStyle).\n\t\t\t\t\t\tEnumeratorStyleFunc(checklistEnumStyle).\n\t\t\t\t\t\tEnumerator(checklistEnum).\n\t\t\t\t\t\tItem(\"Glossier\").\n\t\t\t\t\t\tItem(\"Claire‘s Boutique\").\n\t\t\t\t\t\tItem(\"Nyx\").\n\t\t\t\t\t\tItem(\"Mac\").\n\t\t\t\t\t\tItem(\"Milk\").\n\t\t\t\t\t\tItem(\n\t\t\t\t\t\t\tlist.New().\n\t\t\t\t\t\t\t\tEnumeratorStyle(purple).\n\t\t\t\t\t\t\t\tEnumerator(list.Dash).\n\t\t\t\t\t\t\t\tItemStyleFunc(lipglossStyleFunc).\n\t\t\t\t\t\t\t\tItem(\"Lip Gloss\").\n\t\t\t\t\t\t\t\tItem(\"Lip Gloss\").\n\t\t\t\t\t\t\t\tItem(\"Lip Gloss\").\n\t\t\t\t\t\t\t\tItem(\"Lip Gloss\").\n\t\t\t\t\t\t\t\tItem(\n\t\t\t\t\t\t\t\t\tlist.New().\n\t\t\t\t\t\t\t\t\t\tEnumeratorStyle(lipgloss.NewStyle().Foreground(gradient[4]).MarginRight(1)).\n\t\t\t\t\t\t\t\t\t\tItem(\"\\nStyle Definitions for Nice Terminal Layouts\\n─────\").\n\t\t\t\t\t\t\t\t\t\tItem(\"From Charm\").\n\t\t\t\t\t\t\t\t\t\tItem(\"https://github.com/charmbracelet/lipgloss\").\n\t\t\t\t\t\t\t\t\t\tItem(\n\t\t\t\t\t\t\t\t\t\t\tlist.New().\n\t\t\t\t\t\t\t\t\t\t\t\tEnumeratorStyle(lipgloss.NewStyle().Foreground(gradient[3]).MarginRight(1)).\n\t\t\t\t\t\t\t\t\t\t\t\tItem(\"Emperors: Julio-Claudian dynasty\").\n\t\t\t\t\t\t\t\t\t\t\t\tItem(\n\t\t\t\t\t\t\t\t\t\t\t\t\tlipgloss.NewStyle().Padding(1).Render(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlist.New(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Augustus\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Tiberius\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Caligula\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Claudius\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Nero\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t).Enumerator(list.Roman).String(),\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t).\n\t\t\t\t\t\t\t\t\t\t\t\tItem(\n\t\t\t\t\t\t\t\t\t\t\t\t\tlipgloss.NewStyle().\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tBold(true).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tForeground(lipgloss.Color(\"#FAFAFA\")).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tBackground(lipgloss.Color(\"#7D56F4\")).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tAlignHorizontal(lipgloss.Center).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tAlignVertical(lipgloss.Center).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tPadding(1, 3).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tMargin(0, 1, 1, 1).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tWidth(40).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tRender(history),\n\t\t\t\t\t\t\t\t\t\t\t\t).\n\t\t\t\t\t\t\t\t\t\t\t\tItem(\n\t\t\t\t\t\t\t\t\t\t\t\t\ttable.New().\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tWidth(30).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tBorderStyle(purple.MarginRight(0)).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstyle := lipgloss.NewStyle()\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif col == 0 {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstyle = style.Align(lipgloss.Center)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstyle = style.Align(lipgloss.Right).PaddingRight(2)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif row == 0 {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn style.Bold(true).Align(lipgloss.Center).PaddingRight(0)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn style.Faint(true)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tHeaders(\"ITEM\", \"QUANTITY\").\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tRow(\"Apple\", \"6\").\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tRow(\"Banana\", \"10\").\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tRow(\"Orange\", \"2\").\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tRow(\"Strawberry\", \"12\"),\n\t\t\t\t\t\t\t\t\t\t\t\t).\n\t\t\t\t\t\t\t\t\t\t\t\tItem(\"Documents\").\n\t\t\t\t\t\t\t\t\t\t\t\tItem(\n\t\t\t\t\t\t\t\t\t\t\t\t\tlist.New().\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tEnumerator(func(_ list.Items, i int) string {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif i == 1 {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn \"│\\n│\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn \" \"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tItemStyleFunc(func(_ list.Items, i int) lipgloss.Style {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif i == 1 {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn base.Foreground(highlight)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn base.Foreground(dim)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tEnumeratorStyleFunc(func(_ list.Items, i int) lipgloss.Style {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif i == 1 {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn lipgloss.NewStyle().Foreground(highlight)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn lipgloss.NewStyle().Foreground(dim)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tItem(\"Foo Document\\n\" + faint.Render(\"1 day ago\")).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tItem(\"Bar Document\\n\" + faint.Render(\"2 days ago\")).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tItem(\"Baz Document\\n\" + faint.Render(\"10 minutes ago\")).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tItem(\"Qux Document\\n\" + faint.Render(\"1 month ago\")),\n\t\t\t\t\t\t\t\t\t\t\t\t).\n\t\t\t\t\t\t\t\t\t\t\t\tItem(\"EOF\"),\n\t\t\t\t\t\t\t\t\t\t).\n\t\t\t\t\t\t\t\t\t\tItem(\"go get github.com/charmbracelet/lipgloss/list\\n\"),\n\t\t\t\t\t\t\t\t).\n\t\t\t\t\t\t\t\tItem(\"See ya later\"),\n\t\t\t\t\t\t),\n\t\t\t\t).\n\t\t\t\tItem(\"List\"),\n\t\t).\n\t\tItem(\"xoxo, Charm_™\")\n\n\tlipgloss.Println(l)\n}\n"
  },
  {
    "path": "examples/ssh/main.go",
    "content": "package main\n\n// This example demonstrates how to use a colorprofile to accurately detect\n// client terminal color capabilities for Lip Gloss rendering with Wish, a\n// package for building custom SSH servers.\n//\n// For details on wish see: https://github.com/charmbracelet/wish/\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/charmbracelet/colorprofile\"\n\t\"github.com/charmbracelet/ssh\"\n\t\"github.com/charmbracelet/wish/v2\"\n)\n\n// Available styles.\ntype styles struct {\n\tbold          lipgloss.Style\n\tfaint         lipgloss.Style\n\titalic        lipgloss.Style\n\tunderline     lipgloss.Style\n\tstrikethrough lipgloss.Style\n\tred           lipgloss.Style\n\tgreen         lipgloss.Style\n\tyellow        lipgloss.Style\n\tblue          lipgloss.Style\n\tmagenta       lipgloss.Style\n\tcyan          lipgloss.Style\n\tgray          lipgloss.Style\n}\n\n// Create new styles.\nfunc makeStyles() styles {\n\treturn styles{\n\t\tbold:          lipgloss.NewStyle().SetString(\"bold\").Bold(true),\n\t\tfaint:         lipgloss.NewStyle().SetString(\"faint\").Faint(true),\n\t\titalic:        lipgloss.NewStyle().SetString(\"italic\").Italic(true),\n\t\tunderline:     lipgloss.NewStyle().SetString(\"underline\").Underline(true),\n\t\tstrikethrough: lipgloss.NewStyle().SetString(\"strikethrough\").Strikethrough(true),\n\t\tred:           lipgloss.NewStyle().SetString(\"red\").Foreground(lipgloss.Color(\"#E88388\")),\n\t\tgreen:         lipgloss.NewStyle().SetString(\"green\").Foreground(lipgloss.Color(\"#A8CC8C\")),\n\t\tyellow:        lipgloss.NewStyle().SetString(\"yellow\").Foreground(lipgloss.Color(\"#DBAB79\")),\n\t\tblue:          lipgloss.NewStyle().SetString(\"blue\").Foreground(lipgloss.Color(\"#71BEF2\")),\n\t\tmagenta:       lipgloss.NewStyle().SetString(\"magenta\").Foreground(lipgloss.Color(\"#D290E4\")),\n\t\tcyan:          lipgloss.NewStyle().SetString(\"cyan\").Foreground(lipgloss.Color(\"#66C2CD\")),\n\t\tgray:          lipgloss.NewStyle().SetString(\"gray\").Foreground(lipgloss.Color(\"#B9BFCA\")),\n\t}\n}\n\n// Handle SSH requests.\nfunc handler(next ssh.Handler) ssh.Handler {\n\treturn func(sess ssh.Session) {\n\t\tpty, _, active := sess.Pty()\n\t\tif !active {\n\t\t\tnext(sess)\n\t\t\treturn\n\t\t}\n\n\t\tenviron := sess.Environ()\n\t\tenviron = append(environ, fmt.Sprintf(\"TERM=%s\", pty.Term))\n\t\toutput := colorprofile.NewWriter(pty.Slave, environ)\n\t\twidth := pty.Window.Width\n\n\t\t// Initialize new styles.\n\t\tstyles := makeStyles()\n\n\t\tstr := strings.Builder{}\n\n\t\tfmt.Fprintf(&str, \"\\n\\nProfile: %s\\n%s %s %s %s %s\",\n\t\t\tcolorprofile.Detect(pty.Slave, environ),\n\t\t\tstyles.bold,\n\t\t\tstyles.faint,\n\t\t\tstyles.italic,\n\t\t\tstyles.underline,\n\t\t\tstyles.strikethrough,\n\t\t)\n\n\t\tfmt.Fprintf(&str, \"\\n%s %s %s %s %s %s %s\",\n\t\t\tstyles.red,\n\t\t\tstyles.green,\n\t\t\tstyles.yellow,\n\t\t\tstyles.blue,\n\t\t\tstyles.magenta,\n\t\t\tstyles.cyan,\n\t\t\tstyles.gray,\n\t\t)\n\n\t\tfmt.Fprintf(&str, \"\\n%s %s %s %s %s %s %s\\n\\n\",\n\t\t\tstyles.red,\n\t\t\tstyles.green,\n\t\t\tstyles.yellow,\n\t\t\tstyles.blue,\n\t\t\tstyles.magenta,\n\t\t\tstyles.cyan,\n\t\t\tstyles.gray,\n\t\t)\n\n\t\thasDarkBG := lipgloss.HasDarkBackground(pty.Slave, pty.Slave)\n\t\tlightDark := lipgloss.LightDark(hasDarkBG)\n\n\t\tfmt.Fprintf(&str, \"%s %s\\n\\n\",\n\t\t\tstyles.bold.UnsetString().Render(\"Has dark background?\"),\n\t\t\tfunc() string {\n\t\t\t\tif hasDarkBG {\n\t\t\t\t\treturn \"Yep.\"\n\t\t\t\t}\n\t\t\t\treturn \"Nope!\"\n\t\t\t}(),\n\t\t)\n\n\t\tblock := lipgloss.Place(width,\n\t\t\tlipgloss.Height(str.String()), lipgloss.Center, lipgloss.Center, str.String(),\n\t\t\tlipgloss.WithWhitespaceChars(\"/\"),\n\t\t\tlipgloss.WithWhitespaceStyle(\n\t\t\t\tlipgloss.NewStyle().Foreground(lightDark(\n\t\t\t\t\tlipgloss.ANSIColor(250),\n\t\t\t\t\tlipgloss.ANSIColor(236),\n\t\t\t\t))),\n\t\t)\n\n\t\t// Render to client.\n\t\toutput.WriteString(block)\n\n\t\tnext(sess)\n\t}\n}\n\nfunc main() {\n\tport := 3456\n\ts, err := wish.NewServer(\n\t\tssh.AllocatePty(),\n\t\twish.WithAddress(fmt.Sprintf(\":%d\", port)),\n\t\twish.WithHostKeyPath(\"ssh_example\"),\n\t\twish.WithMiddleware(handler),\n\t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tlog.Printf(\"SSH server listening on port %d\", port)\n\tlog.Printf(\"To connect from your local machine run: ssh localhost -p %d\", port)\n\tif err := s.ListenAndServe(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/table/ansi/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/table\"\n)\n\nfunc main() {\n\ts := lipgloss.NewStyle().Foreground(lipgloss.Color(\"240\")).Render\n\n\tt := table.New()\n\tt.Row(\"Bubble Tea\", s(\"Milky\"))\n\tt.Row(\"Milk Tea\", s(\"Also milky\"))\n\tt.Row(\"Actual milk\", s(\"Milky as well\"))\n\tlipgloss.Println(t.Render())\n}\n"
  },
  {
    "path": "examples/table/chess/main.go",
    "content": "package main\n\nimport (\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/table\"\n)\n\nfunc main() {\n\tlabelStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"241\"))\n\n\tboard := [][]string{\n\t\t{\"♜\", \"♞\", \"♝\", \"♛\", \"♚\", \"♝\", \"♞\", \"♜\"},\n\t\t{\"♟\", \"♟\", \"♟\", \"♟\", \"♟\", \"♟\", \"♟\", \"♟\"},\n\t\t{\" \", \" \", \" \", \" \", \" \", \" \", \" \", \" \"},\n\t\t{\" \", \" \", \" \", \" \", \" \", \" \", \" \", \" \"},\n\t\t{\" \", \" \", \" \", \" \", \" \", \" \", \" \", \" \"},\n\t\t{\" \", \" \", \" \", \" \", \" \", \" \", \" \", \" \"},\n\t\t{\"♙\", \"♙\", \"♙\", \"♙\", \"♙\", \"♙\", \"♙\", \"♙\"},\n\t\t{\"♖\", \"♘\", \"♗\", \"♕\", \"♔\", \"♗\", \"♘\", \"♖\"},\n\t}\n\n\tt := table.New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tBorderRow(true).\n\t\tBorderColumn(true).\n\t\tRows(board...).\n\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\treturn lipgloss.NewStyle().Padding(0, 1)\n\t\t})\n\n\tranks := labelStyle.Render(strings.Join([]string{\" A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H  \"}, \"   \"))\n\tfiles := labelStyle.Render(strings.Join([]string{\" 1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8 \"}, \"\\n\\n \"))\n\n\tlipgloss.Println(\n\t\tlipgloss.JoinVertical(\n\t\t\tlipgloss.Right,\n\t\t\tlipgloss.JoinHorizontal(lipgloss.Center, files, t.Render()),\n\t\t\tranks,\n\t\t) + \"\\n\",\n\t)\n}\n"
  },
  {
    "path": "examples/table/demo.tape",
    "content": "Output table.gif\n\nSet Height 900\nSet Width 1600\nSet Padding 80\nSet FontSize 42\n\nHide\nType \"go build -o table\"\nEnter\nCtrl+L\nShow\n\nSleep 0.5s\nType \"clear && ./table\"\nSleep 0.5s\nEnter\nSleep 1s\n\nScreenshot \"table.png\"\n\nSleep 1s\n\nHide\nType \"rm table\"\nEnter\nShow\n\nSleep 1s\n"
  },
  {
    "path": "examples/table/languages/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/table\"\n)\n\nconst (\n\tpurple    = \"99\"\n\tgray      = \"245\"\n\tlightGray = \"241\"\n)\n\nfunc main() {\n\tvar (\n\t\t// HeaderStyle is the lipgloss style used for the table headers.\n\t\tHeaderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(purple)).Bold(true).Align(lipgloss.Center)\n\t\t// CellStyle is the base lipgloss style used for the table rows.\n\t\tCellStyle = lipgloss.NewStyle().Padding(0, 1).Width(14)\n\t\t// OddRowStyle is the lipgloss style used for odd-numbered table rows.\n\t\tOddRowStyle = CellStyle.Foreground(lipgloss.Color(gray))\n\t\t// EvenRowStyle is the lipgloss style used for even-numbered table rows.\n\t\tEvenRowStyle = CellStyle.Foreground(lipgloss.Color(lightGray))\n\t\t// BorderStyle is the lipgloss style used for the table border.\n\t\tBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(purple))\n\t)\n\n\trows := [][]string{\n\t\t{\"Chinese\", \"您好\", \"你好\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Arabic\", \"أهلين\", \"أهلا\"},\n\t\t{\"Russian\", \"Здравствуйте\", \"Привет\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\tt := table.New().\n\t\tBorder(lipgloss.ThickBorder()).\n\t\tBorderStyle(BorderStyle).\n\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\tvar style lipgloss.Style\n\n\t\t\tswitch {\n\t\t\tcase row == table.HeaderRow:\n\t\t\t\treturn HeaderStyle\n\t\t\tcase row%2 == 0:\n\t\t\t\tstyle = EvenRowStyle\n\t\t\tdefault:\n\t\t\t\tstyle = OddRowStyle\n\t\t\t}\n\n\t\t\t// Make the second column a little wider.\n\t\t\tif col == 1 {\n\t\t\t\tstyle = style.Width(22)\n\t\t\t}\n\n\t\t\t// Arabic is a right-to-left language, so right align the text.\n\t\t\tif row < len(rows) && rows[row][0] == \"Arabic\" && col != 0 {\n\t\t\t\tstyle = style.Align(lipgloss.Right)\n\t\t\t}\n\n\t\t\treturn style\n\t\t}).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...)\n\n\tt.Row(\"English\", \"You look absolutely fabulous.\", \"How's it going?\")\n\n\tlipgloss.Println(t)\n}\n"
  },
  {
    "path": "examples/table/mindy/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/table\"\n)\n\nfunc main() {\n\tlabelStyle := lipgloss.NewStyle().Width(3).Align(lipgloss.Right)\n\tswatchStyle := lipgloss.NewStyle().Width(6)\n\n\tdata := [][]string{}\n\tfor i := 0; i < 13; i += 8 {\n\t\tdata = append(data, makeRow(i, i+5))\n\t}\n\tdata = append(data, makeEmptyRow())\n\tfor i := 6; i < 15; i += 8 {\n\t\tdata = append(data, makeRow(i, i+1))\n\t}\n\tdata = append(data, makeEmptyRow())\n\tfor i := 16; i < 231; i += 6 {\n\t\tdata = append(data, makeRow(i, i+5))\n\t}\n\tdata = append(data, makeEmptyRow())\n\tfor i := 232; i < 256; i += 6 {\n\t\tdata = append(data, makeRow(i, i+5))\n\t}\n\n\tt := table.New().\n\t\tBorder(lipgloss.HiddenBorder()).\n\t\tRows(data...).\n\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\tcolor := lipgloss.Color(fmt.Sprint(data[row][col-col%2]))\n\t\t\tswitch col % 2 {\n\t\t\tcase 0:\n\t\t\t\treturn labelStyle.Foreground(color)\n\t\t\tdefault:\n\t\t\t\treturn swatchStyle.Background(color)\n\t\t\t}\n\t\t})\n\n\tlipgloss.Println(t)\n}\n\nconst rowLength = 12\n\nfunc makeRow(start, end int) []string {\n\tvar row []string\n\tfor i := start; i <= end; i++ {\n\t\trow = append(row, fmt.Sprint(i))\n\t\trow = append(row, \"\")\n\t}\n\tfor i := len(row); i < rowLength; i++ {\n\t\trow = append(row, \"\")\n\t}\n\treturn row\n}\n\nfunc makeEmptyRow() []string {\n\treturn makeRow(0, -1)\n}\n"
  },
  {
    "path": "examples/table/pokemon/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/table\"\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\t\"Bug\":      lipgloss.Color(\"#D7FF87\"),\n\t\t\"Electric\": lipgloss.Color(\"#FDFF90\"),\n\t\t\"Fire\":     lipgloss.Color(\"#FF7698\"),\n\t\t\"Flying\":   lipgloss.Color(\"#FF87D7\"),\n\t\t\"Grass\":    lipgloss.Color(\"#75FBAB\"),\n\t\t\"Ground\":   lipgloss.Color(\"#FF875F\"),\n\t\t\"Normal\":   lipgloss.Color(\"#929292\"),\n\t\t\"Poison\":   lipgloss.Color(\"#7D5AFC\"),\n\t\t\"Water\":    lipgloss.Color(\"#00E2C7\"),\n\t}\n\tdimTypeColors := map[string]color.Color{\n\t\t\"Bug\":      lipgloss.Color(\"#97AD64\"),\n\t\t\"Electric\": lipgloss.Color(\"#FCFF5F\"),\n\t\t\"Fire\":     lipgloss.Color(\"#BA5F75\"),\n\t\t\"Flying\":   lipgloss.Color(\"#C97AB2\"),\n\t\t\"Grass\":    lipgloss.Color(\"#59B980\"),\n\t\t\"Ground\":   lipgloss.Color(\"#C77252\"),\n\t\t\"Normal\":   lipgloss.Color(\"#727272\"),\n\t\t\"Poison\":   lipgloss.Color(\"#634BD0\"),\n\t\t\"Water\":    lipgloss.Color(\"#439F8E\"),\n\t}\n\n\theaders := []string{\"#\", \"Name\", \"Type 1\", \"Type 2\", \"Japanese\", \"Official Rom.\"}\n\tdata := [][]string{\n\t\t{\"1\", \"Bulbasaur\", \"Grass\", \"Poison\", \"フシギダネ\", \"Fushigidane\"},\n\t\t{\"2\", \"Ivysaur\", \"Grass\", \"Poison\", \"フシギソウ\", \"Fushigisou\"},\n\t\t{\"3\", \"Venusaur\", \"Grass\", \"Poison\", \"フシギバナ\", \"Fushigibana\"},\n\t\t{\"4\", \"Charmander\", \"Fire\", \"\", \"ヒトカゲ\", \"Hitokage\"},\n\t\t{\"5\", \"Charmeleon\", \"Fire\", \"\", \"リザード\", \"Lizardo\"},\n\t\t{\"6\", \"Charizard\", \"Fire\", \"Flying\", \"リザードン\", \"Lizardon\"},\n\t\t{\"7\", \"Squirtle\", \"Water\", \"\", \"ゼニガメ\", \"Zenigame\"},\n\t\t{\"8\", \"Wartortle\", \"Water\", \"\", \"カメール\", \"Kameil\"},\n\t\t{\"9\", \"Blastoise\", \"Water\", \"\", \"カメックス\", \"Kamex\"},\n\t\t{\"10\", \"Caterpie\", \"Bug\", \"\", \"キャタピー\", \"Caterpie\"},\n\t\t{\"11\", \"Metapod\", \"Bug\", \"\", \"トランセル\", \"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\", \"\", \"コラッタ\", \"Koratta\"},\n\t\t{\"20\", \"Raticate\", \"Normal\", \"\", \"ラッタ\", \"Ratta\"},\n\t\t{\"21\", \"Spearow\", \"Normal\", \"Flying\", \"オニスズメ\", \"Onisuzume\"},\n\t\t{\"22\", \"Fearow\", \"Normal\", \"Flying\", \"オニドリル\", \"Onidrill\"},\n\t\t{\"23\", \"Ekans\", \"Poison\", \"\", \"アーボ\", \"Arbo\"},\n\t\t{\"24\", \"Arbok\", \"Poison\", \"\", \"アーボック\", \"Arbok\"},\n\t\t{\"25\", \"Pikachu\", \"Electric\", \"\", \"ピカチュウ\", \"Pikachu\"},\n\t\t{\"26\", \"Raichu\", \"Electric\", \"\", \"ライチュウ\", \"Raichu\"},\n\t\t{\"27\", \"Sandshrew\", \"Ground\", \"\", \"サンド\", \"Sand\"},\n\t\t{\"28\", \"Sandslash\", \"Ground\", \"\", \"サンドパン\", \"Sandpan\"},\n\t}\n\n\tCapitalizeHeaders := func(data []string) []string {\n\t\tfor i := range data {\n\t\t\tdata[i] = strings.ToUpper(data[i])\n\t\t}\n\t\treturn data\n\t}\n\n\tt := table.New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tBorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color(\"238\"))).\n\t\tHeaders(CapitalizeHeaders(headers)...).\n\t\tWidth(80).\n\t\tRows(data...).\n\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\tif row == table.HeaderRow {\n\t\t\t\treturn headerStyle\n\t\t\t}\n\n\t\t\tif data[row][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\tcolor := c[fmt.Sprint(data[row][col])]\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\n\tlipgloss.Println(t)\n}\n"
  },
  {
    "path": "examples/tree/background/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/tree\"\n)\n\nfunc main() {\n\tdarkBg := lipgloss.NewStyle().\n\t\tBackground(lipgloss.Color(\"0\")).\n\t\tPadding(0, 1)\n\n\theaderItemStyle := lipgloss.NewStyle().\n\t\tBackground(lipgloss.Color(\"#ee6ff8\")).\n\t\tForeground(lipgloss.Color(\"#ecfe65\")).\n\t\tBold(true).\n\t\tPadding(0, 1)\n\n\titemStyle := headerItemStyle.Background(lipgloss.Color(\"0\"))\n\n\tt := tree.Root(\"# Table of Contents\").\n\t\tRootStyle(itemStyle).\n\t\tItemStyle(itemStyle).\n\t\tEnumeratorStyle(darkBg).\n\t\tIndenterStyle(darkBg).\n\t\tChild(\n\t\t\ttree.Root(\"## Chapter 1\").\n\t\t\t\tChild(\"Chapter 1.1\").\n\t\t\t\tChild(\"Chapter 1.2\"),\n\t\t).\n\t\tChild(\n\t\t\ttree.Root(\"## Chapter 2\").\n\t\t\t\tChild(\"Chapter 2.1\").\n\t\t\t\tChild(\"Chapter 2.2\"),\n\t\t)\n\n\tlipgloss.Println(t)\n}\n"
  },
  {
    "path": "examples/tree/files/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/tree\"\n)\n\nfunc addBranches(root *tree.Tree, path string) error {\n\titems, err := os.ReadDir(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, item := range items {\n\t\tif item.IsDir() {\n\t\t\t// It's a directory.\n\n\t\t\t// Skip directories that start with a dot.\n\t\t\tif strings.HasPrefix(item.Name(), \".\") {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttreeBranch := tree.Root(item.Name())\n\t\t\troot.Child(treeBranch)\n\n\t\t\t// Recurse.\n\t\t\tbranchPath := filepath.Join(path, item.Name())\n\t\t\tif err := addBranches(treeBranch, branchPath); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\t// It's a file.\n\n\t\t\t// Skip files that start with a dot.\n\t\t\tif strings.HasPrefix(item.Name(), \".\") {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\troot.Child(item.Name())\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc main() {\n\tenumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"240\")).PaddingRight(1)\n\titemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"99\")).Bold(true).PaddingRight(1)\n\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Error getting current working directory: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tt := tree.Root(pwd).\n\t\tIndenterStyle(enumeratorStyle).\n\t\tEnumeratorStyle(enumeratorStyle).\n\t\tRootStyle(itemStyle).\n\t\tItemStyle(itemStyle)\n\n\tif err := addBranches(t, \".\"); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Error building tree: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tlipgloss.Println(t)\n}\n"
  },
  {
    "path": "examples/tree/makeup/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/tree\"\n)\n\nfunc main() {\n\tenumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"63\")).MarginRight(1)\n\trootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"35\"))\n\titemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"212\"))\n\n\tt := tree.\n\t\tRoot(\"⁜ Makeup\").\n\t\tChild(\n\t\t\t\"Glossier\",\n\t\t\t\"Fenty Beauty\",\n\t\t\ttree.New().Child(\n\t\t\t\t\"Gloss Bomb Universal Lip Luminizer\",\n\t\t\t\t\"Hot Cheeks Velour Blushlighter\",\n\t\t\t),\n\t\t\t\"Nyx\",\n\t\t\t\"Mac\",\n\t\t\t\"Milk\",\n\t\t).\n\t\tEnumerator(tree.RoundedEnumerator).\n\t\tEnumeratorStyle(enumeratorStyle).\n\t\tIndenterStyle(enumeratorStyle).\n\t\tRootStyle(rootStyle).\n\t\tItemStyle(itemStyle)\n\n\tlipgloss.Println(t)\n}\n"
  },
  {
    "path": "examples/tree/rounded/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/tree\"\n)\n\nfunc main() {\n\titemStyle := lipgloss.NewStyle().MarginRight(1)\n\tenumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"8\")).MarginRight(1)\n\n\tt := tree.Root(\"Groceries\").\n\t\tChild(\n\t\t\ttree.Root(\"Fruits\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Blood Orange\",\n\t\t\t\t\t\"Papaya\",\n\t\t\t\t\t\"Dragonfruit\",\n\t\t\t\t\t\"Yuzu\",\n\t\t\t\t),\n\t\t\ttree.Root(\"Items\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Cat Food\",\n\t\t\t\t\t\"Nutella\",\n\t\t\t\t\t\"Powdered Sugar\",\n\t\t\t\t),\n\t\t\ttree.Root(\"Veggies\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Leek\",\n\t\t\t\t\t\"Artichoke\",\n\t\t\t\t),\n\t\t).ItemStyle(itemStyle).\n\t\tEnumeratorStyle(enumeratorStyle).\n\t\tEnumerator(tree.RoundedEnumerator).\n\t\tIndenterStyle(enumeratorStyle)\n\n\tlipgloss.Println(t)\n}\n"
  },
  {
    "path": "examples/tree/selection/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"path\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/tree\"\n)\n\nconst selected = \"/Users/bash/.config/doom-emacs\"\n\ntype styles struct {\n\tbase,\n\tcontainer,\n\tdir,\n\tselected,\n\tdimmed,\n\ttoggle lipgloss.Style\n}\n\nfunc defaultStyles() styles {\n\tvar s styles\n\ts.base = lipgloss.NewStyle()\n\ts.container = s.base.\n\t\tMargin(1, 2).\n\t\tPadding(1, 0)\n\ts.dir = s.base.\n\t\tInline(true)\n\ts.toggle = s.base.\n\t\tForeground(lipgloss.Color(\"5\")).\n\t\tPaddingRight(1)\n\ts.selected = s.base.\n\t\tBackground(lipgloss.Color(\"8\")).\n\t\tForeground(lipgloss.Color(\"207\")).\n\t\tBold(true)\n\ts.dimmed = s.base.\n\t\tForeground(lipgloss.Color(\"241\"))\n\treturn s\n}\n\ntype dir struct {\n\tname   string\n\topen   bool\n\tstyles styles\n}\n\nfunc (d dir) String() string {\n\tt := d.styles.toggle.PaddingLeft(1).Render\n\tn := d.styles.dir.Render\n\tif d.open {\n\t\treturn t(\"▼\") + n(d.name)\n\t}\n\treturn t(\"▶\") + n(d.name)\n}\n\n// file implements the Node interface.\ntype file struct {\n\tname   string\n\tstyles styles\n}\n\nfunc (s file) String() string {\n\treturn path.Base(s.name)\n}\n\nfunc (s file) Hidden() bool {\n\treturn false\n}\n\nfunc (s file) Children() tree.Children {\n\treturn tree.NodeChildren(nil)\n}\n\nfunc (s file) Value() string {\n\treturn s.String()\n}\n\nfunc (s file) SetValue(val any) {\n\treturn\n}\n\nfunc (s file) SetHidden(val bool) {\n\treturn\n}\n\nfunc isItemSelected(children tree.Children, index int) bool {\n\tchild := children.At(index)\n\tif file, ok := child.(file); ok && file.name == selected {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc itemStyle(children tree.Children, index int) lipgloss.Style {\n\ts := defaultStyles()\n\tif isItemSelected(children, index) {\n\t\treturn s.selected\n\t}\n\n\treturn s.base\n}\n\nfunc indenterStyle(children tree.Children, index int) lipgloss.Style {\n\ts := defaultStyles()\n\tif isItemSelected(children, index) {\n\t\treturn s.dimmed.Background(s.selected.GetBackground())\n\t}\n\n\treturn s.dimmed\n}\n\nfunc main() {\n\ts := defaultStyles()\n\n\tt := tree.Root(dir{\"~/charm\", true, s}).\n\t\tChild(\n\t\t\tdir{\"ayman\", false, s},\n\t\t\ttree.Root(dir{\"bash\", true, s}).\n\t\t\t\tChild(\n\t\t\t\t\tfile{\"/Users/bash/.config/doom-emacs\", s},\n\t\t\t\t),\n\t\t\ttree.Root(dir{\"carlos\", true, s}).\n\t\t\t\tChild(\n\t\t\t\t\ttree.Root(dir{\"emotes\", true, s}).\n\t\t\t\t\t\tChild(\n\t\t\t\t\t\t\tfile{\"/home/caarlos0/Pictures/chefkiss.png\", s},\n\t\t\t\t\t\t\tfile{\"/home/caarlos0/Pictures/kekw.png\", s},\n\t\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tdir{\"maas\", false, s},\n\t\t).\n\t\tWidth(30).\n\t\tIndenter(Indenter).\n\t\tEnumerator(Enumerator).\n\t\tEnumeratorStyleFunc(indenterStyle).\n\t\tIndenterStyleFunc(indenterStyle).\n\t\tItemStyleFunc(itemStyle)\n\n\tfmt.Println(s.container.Render(t.String()))\n}\n\nfunc Enumerator(children tree.Children, index int) string {\n\treturn \" │ \"\n}\n\nfunc Indenter(children tree.Children, index int) string {\n\treturn \" │ \"\n}\n"
  },
  {
    "path": "examples/tree/simple/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/tree\"\n)\n\nfunc main() {\n\tt := tree.Root(\".\").\n\t\tChild(\"macOS\").\n\t\tChild(\n\t\t\ttree.New().\n\t\t\t\tRoot(\"Linux\").\n\t\t\t\tChild(\"NixOS\").\n\t\t\t\tChild(\"Arch Linux (btw)\").\n\t\t\t\tChild(\"Void Linux\"),\n\t\t).\n\t\tChild(\n\t\t\ttree.New().\n\t\t\t\tRoot(\"BSD\").\n\t\t\t\tChild(\"FreeBSD\").\n\t\t\t\tChild(\"OpenBSD\"),\n\t\t)\n\n\tlipgloss.Println(t)\n}\n"
  },
  {
    "path": "examples/tree/styles/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/tree\"\n)\n\nfunc main() {\n\tpurple := lipgloss.NewStyle().Foreground(lipgloss.Color(\"99\")).MarginRight(1)\n\tpink := lipgloss.NewStyle().Foreground(lipgloss.Color(\"212\")).MarginRight(1)\n\n\tt := tree.New().\n\t\tChild(\n\t\t\t\"Glossier\",\n\t\t\t\"Claire’s Boutique\",\n\t\t\ttree.Root(\"Nyx\").\n\t\t\t\tChild(\"Lip Gloss\", \"Foundation\").\n\t\t\t\tEnumeratorStyle(pink).\n\t\t\t\tIndenterStyle(purple),\n\t\t\t\"Mac\",\n\t\t\t\"Milk\",\n\t\t).\n\t\tEnumeratorStyle(purple).\n\t\tIndenterStyle(purple)\n\tfmt.Println(t)\n}\n"
  },
  {
    "path": "examples/tree/toggle/main.go",
    "content": "package main\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/tree\"\n)\n\ntype styles struct {\n\tbase,\n\tblock,\n\tpink,\n\tdir,\n\ttoggle,\n\tfile lipgloss.Style\n}\n\nfunc defaultStyles() styles {\n\tvar s styles\n\ts.base = lipgloss.NewStyle().\n\t\tBackground(lipgloss.Color(\"57\")).\n\t\tForeground(lipgloss.Color(\"225\"))\n\ts.block = s.base.\n\t\tPadding(1, 3).\n\t\tMargin(1, 3).\n\t\tWidth(40)\n\ts.pink = s.base.\n\t\tForeground(lipgloss.Color(\"212\")).\n\t\tPaddingRight(1)\n\ts.dir = s.base.\n\t\tInline(true)\n\ts.toggle = s.base.\n\t\tForeground(lipgloss.Color(\"207\")).\n\t\tPaddingRight(1)\n\ts.file = s.base\n\treturn s\n}\n\ntype dir struct {\n\tname   string\n\topen   bool\n\tstyles styles\n}\n\nfunc (d dir) String() string {\n\tt := d.styles.toggle.Render\n\tn := d.styles.dir.Render\n\tif d.open {\n\t\treturn t(\"▼\") + n(d.name)\n\t}\n\treturn t(\"▶\") + n(d.name)\n}\n\ntype file struct {\n\tname   string\n\tstyles styles\n}\n\nfunc (s file) String() string {\n\treturn s.styles.file.Render(s.name)\n}\n\nfunc main() {\n\ts := defaultStyles()\n\n\tt := tree.Root(dir{\"~/charm\", true, s}).\n\t\tEnumerator(tree.RoundedEnumerator).\n\t\tIndenterStyle(s.pink).\n\t\tEnumeratorStyle(s.pink).\n\t\tChild(\n\t\t\tdir{\"ayman\", false, s},\n\t\t\ttree.Root(dir{\"bash\", true, s}).\n\t\t\t\tChild(\n\t\t\t\t\ttree.Root(dir{\"tools\", true, s}).\n\t\t\t\t\t\tChild(\n\t\t\t\t\t\t\tfile{\"zsh\", s},\n\t\t\t\t\t\t\tfile{\"doom-emacs\", s},\n\t\t\t\t\t\t),\n\t\t\t\t),\n\t\t\ttree.Root(dir{\"carlos\", true, s}).\n\t\t\t\tChild(\n\t\t\t\t\ttree.Root(dir{\"emotes\", true, s}).\n\t\t\t\t\t\tChild(\n\t\t\t\t\t\t\tfile{\"chefkiss.png\", s},\n\t\t\t\t\t\t\tfile{\"kekw.png\", s},\n\t\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tdir{\"maas\", false, s},\n\t\t)\n\n\tlipgloss.Println(s.block.Render(t.String()))\n}\n"
  },
  {
    "path": "get.go",
    "content": "package lipgloss\n\nimport (\n\t\"image/color\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// GetBold returns the style's bold value. If no value is set false is returned.\nfunc (s Style) GetBold() bool {\n\treturn s.getAsBool(boldKey, false)\n}\n\n// GetItalic returns the style's italic value. If no value is set false is\n// returned.\nfunc (s Style) GetItalic() bool {\n\treturn s.getAsBool(italicKey, false)\n}\n\n// GetUnderline returns the style's underline value. If no value is set false is\n// returned.\nfunc (s Style) GetUnderline() bool {\n\treturn s.ul != UnderlineNone\n}\n\n// GetUnderlineStyle returns the style's underline style. If no value is set\n// UnderlineNone is returned.\nfunc (s Style) GetUnderlineStyle() Underline {\n\treturn s.ul\n}\n\n// GetUnderlineColor returns the style's underline color. If no value is set\n// NoColor{} is returned.\nfunc (s Style) GetUnderlineColor() color.Color {\n\treturn s.getAsColor(underlineColorKey)\n}\n\n// GetStrikethrough returns the style's strikethrough value. If no value is set false\n// is returned.\nfunc (s Style) GetStrikethrough() bool {\n\treturn s.getAsBool(strikethroughKey, false)\n}\n\n// GetReverse returns the style's reverse value. If no value is set false is\n// returned.\nfunc (s Style) GetReverse() bool {\n\treturn s.getAsBool(reverseKey, false)\n}\n\n// GetBlink returns the style's blink value. If no value is set false is\n// returned.\nfunc (s Style) GetBlink() bool {\n\treturn s.getAsBool(blinkKey, false)\n}\n\n// GetFaint returns the style's faint value. If no value is set false is\n// returned.\nfunc (s Style) GetFaint() bool {\n\treturn s.getAsBool(faintKey, false)\n}\n\n// GetForeground returns the style's foreground color. If no value is set\n// NoColor{} is returned.\nfunc (s Style) GetForeground() color.Color {\n\treturn s.getAsColor(foregroundKey)\n}\n\n// GetBackground returns the style's background color. If no value is set\n// NoColor{} is returned.\nfunc (s Style) GetBackground() color.Color {\n\treturn s.getAsColor(backgroundKey)\n}\n\n// GetWidth returns the style's width setting. If no width is set 0 is\n// returned.\nfunc (s Style) GetWidth() int {\n\treturn s.getAsInt(widthKey)\n}\n\n// GetHeight returns the style's height setting. If no height is set 0 is\n// returned.\nfunc (s Style) GetHeight() int {\n\treturn s.getAsInt(heightKey)\n}\n\n// GetAlign returns the style's implicit horizontal alignment setting.\n// If no alignment is set Position.Left is returned.\nfunc (s Style) GetAlign() Position {\n\tv := s.getAsPosition(alignHorizontalKey)\n\tif v == Position(0) {\n\t\treturn Left\n\t}\n\treturn v\n}\n\n// GetAlignHorizontal returns the style's implicit horizontal alignment setting.\n// If no alignment is set Position.Left is returned.\nfunc (s Style) GetAlignHorizontal() Position {\n\tv := s.getAsPosition(alignHorizontalKey)\n\tif v == Position(0) {\n\t\treturn Left\n\t}\n\treturn v\n}\n\n// GetAlignVertical returns the style's implicit vertical alignment setting.\n// If no alignment is set Position.Top is returned.\nfunc (s Style) GetAlignVertical() Position {\n\tv := s.getAsPosition(alignVerticalKey)\n\tif v == Position(0) {\n\t\treturn Top\n\t}\n\treturn v\n}\n\n// GetPadding returns the style's top, right, bottom, and left padding values,\n// in that order. 0 is returned for unset values.\nfunc (s Style) GetPadding() (top, right, bottom, left int) {\n\treturn s.getAsInt(paddingTopKey),\n\t\ts.getAsInt(paddingRightKey),\n\t\ts.getAsInt(paddingBottomKey),\n\t\ts.getAsInt(paddingLeftKey)\n}\n\n// GetPaddingTop returns the style's top padding. If no value is set 0 is\n// returned.\nfunc (s Style) GetPaddingTop() int {\n\treturn s.getAsInt(paddingTopKey)\n}\n\n// GetPaddingRight returns the style's right padding. If no value is set 0 is\n// returned.\nfunc (s Style) GetPaddingRight() int {\n\treturn s.getAsInt(paddingRightKey)\n}\n\n// GetPaddingBottom returns the style's bottom padding. If no value is set 0 is\n// returned.\nfunc (s Style) GetPaddingBottom() int {\n\treturn s.getAsInt(paddingBottomKey)\n}\n\n// GetPaddingLeft returns the style's left padding. If no value is set 0 is\n// returned.\nfunc (s Style) GetPaddingLeft() int {\n\treturn s.getAsInt(paddingLeftKey)\n}\n\n// GetPaddingChar returns the style's padding character. If no value is set a\n// space is returned.\nfunc (s Style) GetPaddingChar() rune {\n\tchar := s.getAsRune(paddingCharKey)\n\tif char == 0 {\n\t\treturn ' '\n\t}\n\treturn char\n}\n\n// GetHorizontalPadding returns the style's left and right padding. Unset\n// values are measured as 0.\nfunc (s Style) GetHorizontalPadding() int {\n\treturn s.getAsInt(paddingLeftKey) + s.getAsInt(paddingRightKey)\n}\n\n// GetVerticalPadding returns the style's top and bottom padding. Unset values\n// are measured as 0.\nfunc (s Style) GetVerticalPadding() int {\n\treturn s.getAsInt(paddingTopKey) + s.getAsInt(paddingBottomKey)\n}\n\n// GetColorWhitespace returns the style's whitespace coloring setting. If no\n// value is set false is returned.\nfunc (s Style) GetColorWhitespace() bool {\n\treturn s.getAsBool(colorWhitespaceKey, false)\n}\n\n// GetMargin returns the style's top, right, bottom, and left margins, in that\n// order. 0 is returned for unset values.\nfunc (s Style) GetMargin() (top, right, bottom, left int) {\n\treturn s.getAsInt(marginTopKey),\n\t\ts.getAsInt(marginRightKey),\n\t\ts.getAsInt(marginBottomKey),\n\t\ts.getAsInt(marginLeftKey)\n}\n\n// GetMarginTop returns the style's top margin. If no value is set 0 is\n// returned.\nfunc (s Style) GetMarginTop() int {\n\treturn s.getAsInt(marginTopKey)\n}\n\n// GetMarginRight returns the style's right margin. If no value is set 0 is\n// returned.\nfunc (s Style) GetMarginRight() int {\n\treturn s.getAsInt(marginRightKey)\n}\n\n// GetMarginBottom returns the style's bottom margin. If no value is set 0 is\n// returned.\nfunc (s Style) GetMarginBottom() int {\n\treturn s.getAsInt(marginBottomKey)\n}\n\n// GetMarginLeft returns the style's left margin. If no value is set 0 is\n// returned.\nfunc (s Style) GetMarginLeft() int {\n\treturn s.getAsInt(marginLeftKey)\n}\n\n// GetMarginChar returns the style's padding character. If no value is set a\n// space is returned.\nfunc (s Style) GetMarginChar() rune {\n\tchar := s.getAsRune(marginCharKey)\n\tif char == 0 {\n\t\treturn ' '\n\t}\n\treturn char\n}\n\n// GetHorizontalMargins returns the style's left and right margins. Unset\n// values are measured as 0.\nfunc (s Style) GetHorizontalMargins() int {\n\treturn s.getAsInt(marginLeftKey) + s.getAsInt(marginRightKey)\n}\n\n// GetVerticalMargins returns the style's top and bottom margins. Unset values\n// are measured as 0.\nfunc (s Style) GetVerticalMargins() int {\n\treturn s.getAsInt(marginTopKey) + s.getAsInt(marginBottomKey)\n}\n\n// GetBorder returns the style's border style (type Border) and value for the\n// top, right, bottom, and left in that order. If no value is set for the\n// border style, Border{} is returned. For all other unset values false is\n// returned.\nfunc (s Style) GetBorder() (b Border, top, right, bottom, left bool) {\n\treturn s.getBorderStyle(),\n\t\ts.getAsBool(borderTopKey, false),\n\t\ts.getAsBool(borderRightKey, false),\n\t\ts.getAsBool(borderBottomKey, false),\n\t\ts.getAsBool(borderLeftKey, false)\n}\n\n// GetBorderStyle returns the style's border style (type Border). If no value\n// is set Border{} is returned.\nfunc (s Style) GetBorderStyle() Border {\n\treturn s.getBorderStyle()\n}\n\n// GetBorderTop returns the style's top border setting. If no value is set\n// false is returned.\nfunc (s Style) GetBorderTop() bool {\n\treturn s.getAsBool(borderTopKey, false)\n}\n\n// GetBorderRight returns the style's right border setting. If no value is set\n// false is returned.\nfunc (s Style) GetBorderRight() bool {\n\treturn s.getAsBool(borderRightKey, false)\n}\n\n// GetBorderBottom returns the style's bottom border setting. If no value is\n// set false is returned.\nfunc (s Style) GetBorderBottom() bool {\n\treturn s.getAsBool(borderBottomKey, false)\n}\n\n// GetBorderLeft returns the style's left border setting. If no value is\n// set false is returned.\nfunc (s Style) GetBorderLeft() bool {\n\treturn s.getAsBool(borderLeftKey, false)\n}\n\n// GetBorderTopForeground returns the style's border top foreground color. If\n// no value is set NoColor{} is returned.\nfunc (s Style) GetBorderTopForeground() color.Color {\n\treturn s.getAsColor(borderTopForegroundKey)\n}\n\n// GetBorderRightForeground returns the style's border right foreground color.\n// If no value is set NoColor{} is returned.\nfunc (s Style) GetBorderRightForeground() color.Color {\n\treturn s.getAsColor(borderRightForegroundKey)\n}\n\n// GetBorderBottomForeground returns the style's border bottom foreground\n// color.  If no value is set NoColor{} is returned.\nfunc (s Style) GetBorderBottomForeground() color.Color {\n\treturn s.getAsColor(borderBottomForegroundKey)\n}\n\n// GetBorderLeftForeground returns the style's border left foreground\n// color.  If no value is set NoColor{} is returned.\nfunc (s Style) GetBorderLeftForeground() color.Color {\n\treturn s.getAsColor(borderLeftForegroundKey)\n}\n\n// GetBorderForegroundBlend returns the style's border blend foreground\n// colors. If no value is set, nil is returned.\nfunc (s Style) GetBorderForegroundBlend() []color.Color {\n\treturn s.getAsColors(borderForegroundBlendKey)\n}\n\n// GetBorderForegroundBlendOffset returns the style's border blend offset. If no\n// value is set, 0 is returned.\nfunc (s Style) GetBorderForegroundBlendOffset() int {\n\treturn s.getAsInt(borderForegroundBlendOffsetKey)\n}\n\n// GetBorderTopBackground returns the style's border top background color. If\n// no value is set NoColor{} is returned.\nfunc (s Style) GetBorderTopBackground() color.Color {\n\treturn s.getAsColor(borderTopBackgroundKey)\n}\n\n// GetBorderRightBackground returns the style's border right background color.\n// If no value is set NoColor{} is returned.\nfunc (s Style) GetBorderRightBackground() color.Color {\n\treturn s.getAsColor(borderRightBackgroundKey)\n}\n\n// GetBorderBottomBackground returns the style's border bottom background\n// color.  If no value is set NoColor{} is returned.\nfunc (s Style) GetBorderBottomBackground() color.Color {\n\treturn s.getAsColor(borderBottomBackgroundKey)\n}\n\n// GetBorderLeftBackground returns the style's border left background\n// color.  If no value is set NoColor{} is returned.\nfunc (s Style) GetBorderLeftBackground() color.Color {\n\treturn s.getAsColor(borderLeftBackgroundKey)\n}\n\n// GetBorderTopWidth returns the width of the top border. If borders contain\n// runes of varying widths, the widest rune is returned. If no border exists on\n// the top edge, 0 is returned.\n//\n// Deprecated: This function simply calls Style.GetBorderTopSize.\nfunc (s Style) GetBorderTopWidth() int {\n\treturn s.GetBorderTopSize()\n}\n\n// GetBorderTopSize returns the width of the top border. If borders contain\n// runes of varying widths, the widest rune is returned. If no border exists on\n// the top edge, 0 is returned.\nfunc (s Style) GetBorderTopSize() int {\n\tif s.isBorderStyleSetWithoutSides() {\n\t\treturn 1\n\t}\n\tif !s.getAsBool(borderTopKey, false) {\n\t\treturn 0\n\t}\n\treturn s.getBorderStyle().GetTopSize()\n}\n\n// GetBorderLeftSize returns the width of the left border. If borders contain\n// runes of varying widths, the widest rune is returned. If no border exists on\n// the left edge, 0 is returned.\nfunc (s Style) GetBorderLeftSize() int {\n\tif s.isBorderStyleSetWithoutSides() {\n\t\treturn 1\n\t}\n\tif !s.getAsBool(borderLeftKey, false) {\n\t\treturn 0\n\t}\n\treturn s.getBorderStyle().GetLeftSize()\n}\n\n// GetBorderBottomSize returns the width of the bottom border. If borders\n// contain runes of varying widths, the widest rune is returned. If no border\n// exists on the left edge, 0 is returned.\nfunc (s Style) GetBorderBottomSize() int {\n\tif s.isBorderStyleSetWithoutSides() {\n\t\treturn 1\n\t}\n\tif !s.getAsBool(borderBottomKey, false) {\n\t\treturn 0\n\t}\n\treturn s.getBorderStyle().GetBottomSize()\n}\n\n// GetBorderRightSize returns the width of the right border. If borders\n// contain runes of varying widths, the widest rune is returned. If no border\n// exists on the right edge, 0 is returned.\nfunc (s Style) GetBorderRightSize() int {\n\tif s.isBorderStyleSetWithoutSides() {\n\t\treturn 1\n\t}\n\tif !s.getAsBool(borderRightKey, false) {\n\t\treturn 0\n\t}\n\treturn s.getBorderStyle().GetRightSize()\n}\n\n// GetHorizontalBorderSize returns the width of the horizontal borders. If\n// borders contain runes of varying widths, the widest rune is returned. If no\n// border exists on the horizontal edges, 0 is returned.\nfunc (s Style) GetHorizontalBorderSize() int {\n\treturn s.GetBorderLeftSize() + s.GetBorderRightSize()\n}\n\n// GetVerticalBorderSize returns the width of the vertical borders. If\n// borders contain runes of varying widths, the widest rune is returned. If no\n// border exists on the vertical edges, 0 is returned.\nfunc (s Style) GetVerticalBorderSize() int {\n\treturn s.GetBorderTopSize() + s.GetBorderBottomSize()\n}\n\n// GetInline returns the style's inline setting. If no value is set false is\n// returned.\nfunc (s Style) GetInline() bool {\n\treturn s.getAsBool(inlineKey, false)\n}\n\n// GetMaxWidth returns the style's max width setting. If no value is set 0 is\n// returned.\nfunc (s Style) GetMaxWidth() int {\n\treturn s.getAsInt(maxWidthKey)\n}\n\n// GetMaxHeight returns the style's max height setting. If no value is set 0 is\n// returned.\nfunc (s Style) GetMaxHeight() int {\n\treturn s.getAsInt(maxHeightKey)\n}\n\n// GetTabWidth returns the style's tab width setting. If no value is set 4 is\n// returned which is the implicit default.\nfunc (s Style) GetTabWidth() int {\n\treturn s.getAsInt(tabWidthKey)\n}\n\n// GetUnderlineSpaces returns whether or not the style is set to underline\n// spaces. If not value is set false is returned.\nfunc (s Style) GetUnderlineSpaces() bool {\n\treturn s.getAsBool(underlineSpacesKey, false)\n}\n\n// GetStrikethroughSpaces returns whether or not the style is set to strikethrough\n// spaces. If not value is set false is returned.\nfunc (s Style) GetStrikethroughSpaces() bool {\n\treturn s.getAsBool(strikethroughSpacesKey, false)\n}\n\n// GetHorizontalFrameSize returns the sum of the style's horizontal margins, padding\n// and border widths.\n//\n// Provisional: this method may be renamed.\nfunc (s Style) GetHorizontalFrameSize() int {\n\treturn s.GetHorizontalMargins() + s.GetHorizontalPadding() + s.GetHorizontalBorderSize()\n}\n\n// GetVerticalFrameSize returns the sum of the style's vertical margins, padding\n// and border widths.\n//\n// Provisional: this method may be renamed.\nfunc (s Style) GetVerticalFrameSize() int {\n\treturn s.GetVerticalMargins() + s.GetVerticalPadding() + s.GetVerticalBorderSize()\n}\n\n// GetFrameSize returns the sum of the margins, padding and border width for\n// both the horizontal and vertical margins.\nfunc (s Style) GetFrameSize() (x, y int) {\n\treturn s.GetHorizontalFrameSize(), s.GetVerticalFrameSize()\n}\n\n// GetTransform returns the transform set on the style. If no transform is set\n// nil is returned.\nfunc (s Style) GetTransform() func(string) string {\n\treturn s.getAsTransform(transformKey)\n}\n\n// GetHyperlink returns the hyperlink along with its parameters. If no\n// hyperlink is set, empty strings are returned.\nfunc (s Style) GetHyperlink() (link, params string) {\n\tif s.isSet(linkKey) {\n\t\tlink = s.link\n\t}\n\tif s.isSet(linkParamsKey) {\n\t\tparams = s.linkParams\n\t}\n\treturn\n}\n\n// Returns whether or not the given property is set.\nfunc (s Style) isSet(k propKey) bool {\n\treturn s.props.has(k)\n}\n\nfunc (s Style) getAsRune(k propKey) rune {\n\tif !s.isSet(k) {\n\t\treturn 0\n\t}\n\tswitch k { //nolint:exhaustive\n\tcase paddingCharKey:\n\t\treturn s.paddingChar\n\tcase marginCharKey:\n\t\treturn s.marginChar\n\t}\n\treturn 0\n}\n\nfunc (s Style) getAsBool(k propKey, defaultVal bool) bool {\n\tif !s.isSet(k) {\n\t\treturn defaultVal\n\t}\n\treturn s.attrs&int(k) != 0\n}\n\nfunc (s Style) getAsColors(k propKey) (colors []color.Color) {\n\tif !s.isSet(k) {\n\t\treturn nil\n\t}\n\n\tswitch k { //nolint:exhaustive\n\tcase borderForegroundBlendKey:\n\t\treturn s.borderBlendFgColor\n\t}\n\n\treturn nil\n}\n\nfunc (s Style) getAsColor(k propKey) color.Color {\n\tif !s.isSet(k) {\n\t\treturn noColor\n\t}\n\n\tvar c color.Color\n\tswitch k { //nolint:exhaustive\n\tcase foregroundKey:\n\t\tc = s.fgColor\n\tcase backgroundKey:\n\t\tc = s.bgColor\n\tcase marginBackgroundKey:\n\t\tc = s.marginBgColor\n\tcase borderTopForegroundKey:\n\t\tc = s.borderTopFgColor\n\tcase borderRightForegroundKey:\n\t\tc = s.borderRightFgColor\n\tcase borderBottomForegroundKey:\n\t\tc = s.borderBottomFgColor\n\tcase borderLeftForegroundKey:\n\t\tc = s.borderLeftFgColor\n\tcase borderTopBackgroundKey:\n\t\tc = s.borderTopBgColor\n\tcase borderRightBackgroundKey:\n\t\tc = s.borderRightBgColor\n\tcase borderBottomBackgroundKey:\n\t\tc = s.borderBottomBgColor\n\tcase borderLeftBackgroundKey:\n\t\tc = s.borderLeftBgColor\n\tcase underlineColorKey:\n\t\tc = s.ulColor\n\t}\n\n\tif c != nil {\n\t\treturn c\n\t}\n\n\treturn noColor\n}\n\nfunc (s Style) getAsInt(k propKey) int {\n\tif !s.isSet(k) {\n\t\treturn 0\n\t}\n\tswitch k { //nolint:exhaustive\n\tcase widthKey:\n\t\treturn s.width\n\tcase heightKey:\n\t\treturn s.height\n\tcase paddingTopKey:\n\t\treturn s.paddingTop\n\tcase paddingRightKey:\n\t\treturn s.paddingRight\n\tcase paddingBottomKey:\n\t\treturn s.paddingBottom\n\tcase paddingLeftKey:\n\t\treturn s.paddingLeft\n\tcase marginTopKey:\n\t\treturn s.marginTop\n\tcase marginRightKey:\n\t\treturn s.marginRight\n\tcase marginBottomKey:\n\t\treturn s.marginBottom\n\tcase marginLeftKey:\n\t\treturn s.marginLeft\n\tcase borderForegroundBlendOffsetKey:\n\t\treturn s.borderForegroundBlendOffset\n\tcase maxWidthKey:\n\t\treturn s.maxWidth\n\tcase maxHeightKey:\n\t\treturn s.maxHeight\n\tcase tabWidthKey:\n\t\treturn s.tabWidth\n\t}\n\treturn 0\n}\n\nfunc (s Style) getAsPosition(k propKey) Position {\n\tif !s.isSet(k) {\n\t\treturn Position(0)\n\t}\n\tswitch k { //nolint:exhaustive\n\tcase alignHorizontalKey:\n\t\treturn s.alignHorizontal\n\tcase alignVerticalKey:\n\t\treturn s.alignVertical\n\t}\n\treturn Position(0)\n}\n\nfunc (s Style) getBorderStyle() Border {\n\tif !s.isSet(borderStyleKey) {\n\t\treturn noBorder\n\t}\n\treturn s.borderStyle\n}\n\nfunc (s Style) getAsTransform(propKey) func(string) string {\n\tif !s.isSet(transformKey) {\n\t\treturn nil\n\t}\n\treturn s.transform\n}\n\n// Split a string into lines, additionally returning the size of the widest\n// line.\nfunc getLines(s string) (lines []string, widest int) {\n\ts = strings.ReplaceAll(s, \"\\t\", \"    \")\n\ts = strings.ReplaceAll(s, \"\\r\\n\", \"\\n\")\n\tlines = strings.Split(s, \"\\n\")\n\n\tfor _, l := range lines {\n\t\tw := ansi.StringWidth(l)\n\t\tif widest < w {\n\t\t\twidest = w\n\t\t}\n\t}\n\n\treturn lines, widest\n}\n\n// isBorderStyleSetWithoutSides returns true if the border style is set but no\n// sides are set. This is used to determine if the border should be rendered by\n// default.\nfunc (s Style) isBorderStyleSetWithoutSides() bool {\n\tvar (\n\t\tborder    = s.getBorderStyle()\n\t\ttopSet    = s.isSet(borderTopKey)\n\t\trightSet  = s.isSet(borderRightKey)\n\t\tbottomSet = s.isSet(borderBottomKey)\n\t\tleftSet   = s.isSet(borderLeftKey)\n\t)\n\treturn border != noBorder && !(topSet || rightSet || bottomSet || leftSet) //nolint:staticcheck\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module charm.land/lipgloss/v2\n\nretract v2.0.0-beta1 // We add a \".\" after the \"beta\" in the version number.\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/aymanbagabas/go-udiff v0.4.1\n\tgithub.com/charmbracelet/colorprofile v0.4.3\n\tgithub.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318\n\tgithub.com/charmbracelet/x/ansi v0.11.6\n\tgithub.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f\n\tgithub.com/charmbracelet/x/term v0.2.2\n\tgithub.com/clipperhouse/displaywidth v0.11.0\n\tgithub.com/lucasb-eyer/go-colorful v1.3.0\n\tgithub.com/rivo/uniseg v0.4.7\n\tgolang.org/x/sys v0.42.0\n)\n\nrequire (\n\tgithub.com/charmbracelet/x/termios v0.1.1 // indirect\n\tgithub.com/charmbracelet/x/windows v0.2.2 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.7.0 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.19 // indirect\n\tgithub.com/muesli/cancelreader v0.2.2 // 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.18.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.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/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/ultraviolet v0.0.0-20251205161215-1948445e3318 h1:OqDqxQZliC7C8adA7KjelW3OjtAxREfeHkNcd66wpeI=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI=\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-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/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/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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\n"
  },
  {
    "path": "join.go",
    "content": "package lipgloss\n\nimport (\n\t\"math\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// JoinHorizontal is a utility function for horizontally joining two\n// potentially multi-lined strings along a vertical axis. The first argument is\n// the position, with 0 being all the way at the top and 1 being all the way\n// at the bottom.\n//\n// If you just want to align to the top, center or bottom you may as well just\n// use the helper constants Top, Center, and Bottom.\n//\n// Example:\n//\n//\tblockB := \"...\\n...\\n...\"\n//\tblockA := \"...\\n...\\n...\\n...\\n...\"\n//\n//\t// Join 20% from the top\n//\tstr := lipgloss.JoinHorizontal(0.2, blockA, blockB)\n//\n//\t// Join on the top edge\n//\tstr := lipgloss.JoinHorizontal(lipgloss.Top, blockA, blockB)\nfunc JoinHorizontal(pos Position, strs ...string) string {\n\tif len(strs) == 0 {\n\t\treturn \"\"\n\t}\n\tif len(strs) == 1 {\n\t\treturn strs[0]\n\t}\n\n\tvar (\n\t\t// Groups of strings broken into multiple lines\n\t\tblocks = make([][]string, len(strs))\n\n\t\t// Max line widths for the above text blocks\n\t\tmaxWidths = make([]int, len(strs))\n\n\t\t// Height of the tallest block\n\t\tmaxHeight int\n\t)\n\n\t// Break text blocks into lines and get max widths for each text block\n\tfor i, str := range strs {\n\t\tblocks[i], maxWidths[i] = getLines(str)\n\t\tif len(blocks[i]) > maxHeight {\n\t\t\tmaxHeight = len(blocks[i])\n\t\t}\n\t}\n\n\t// Add extra lines to make each side the same height\n\tfor i := range blocks {\n\t\tif len(blocks[i]) >= maxHeight {\n\t\t\tcontinue\n\t\t}\n\n\t\textraLines := make([]string, maxHeight-len(blocks[i]))\n\n\t\tswitch pos {\n\t\tcase Top:\n\t\t\tblocks[i] = append(blocks[i], extraLines...)\n\n\t\tcase Bottom:\n\t\t\tblocks[i] = append(extraLines, blocks[i]...)\n\n\t\tdefault: // Somewhere in the middle\n\t\t\tn := len(extraLines)\n\t\t\tsplit := int(math.Round(float64(n) * pos.value()))\n\t\t\ttop := n - split\n\t\t\tbottom := n - top\n\n\t\t\tblocks[i] = append(extraLines[top:], blocks[i]...)\n\t\t\tblocks[i] = append(blocks[i], extraLines[bottom:]...)\n\t\t}\n\t}\n\n\t// Merge lines\n\tvar b strings.Builder\n\tfor i := range blocks[0] { // remember, all blocks have the same number of members now\n\t\tfor j, block := range blocks {\n\t\t\tb.WriteString(block[i])\n\n\t\t\t// Also make lines the same length\n\t\t\tb.WriteString(strings.Repeat(\" \", maxWidths[j]-ansi.StringWidth(block[i])))\n\t\t}\n\t\tif i < len(blocks[0])-1 {\n\t\t\tb.WriteRune('\\n')\n\t\t}\n\t}\n\n\treturn b.String()\n}\n\n// JoinVertical is a utility function for vertically joining two potentially\n// multi-lined strings along a horizontal axis. The first argument is the\n// position, with 0 being all the way to the left and 1 being all the way to\n// the right.\n//\n// If you just want to align to the left, right or center you may as well just\n// use the helper constants Left, Center, and Right.\n//\n// Example:\n//\n//\tblockB := \"...\\n...\\n...\"\n//\tblockA := \"...\\n...\\n...\\n...\\n...\"\n//\n//\t// Join 20% from the top\n//\tstr := lipgloss.JoinVertical(0.2, blockA, blockB)\n//\n//\t// Join on the right edge\n//\tstr := lipgloss.JoinVertical(lipgloss.Right, blockA, blockB)\nfunc JoinVertical(pos Position, strs ...string) string {\n\tif len(strs) == 0 {\n\t\treturn \"\"\n\t}\n\tif len(strs) == 1 {\n\t\treturn strs[0]\n\t}\n\n\tvar (\n\t\tblocks   = make([][]string, len(strs))\n\t\tmaxWidth int\n\t)\n\n\tfor i := range strs {\n\t\tvar w int\n\t\tblocks[i], w = getLines(strs[i])\n\t\tif w > maxWidth {\n\t\t\tmaxWidth = w\n\t\t}\n\t}\n\n\tvar b strings.Builder\n\tfor i, block := range blocks {\n\t\tfor j, line := range block {\n\t\t\tw := maxWidth - ansi.StringWidth(line)\n\n\t\t\tswitch pos {\n\t\t\tcase Left:\n\t\t\t\tb.WriteString(line)\n\t\t\t\tb.WriteString(strings.Repeat(\" \", w))\n\n\t\t\tcase Right:\n\t\t\t\tb.WriteString(strings.Repeat(\" \", w))\n\t\t\t\tb.WriteString(line)\n\n\t\t\tdefault: // Somewhere in the middle\n\t\t\t\tif w < 1 {\n\t\t\t\t\tb.WriteString(line)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tsplit := int(math.Round(float64(w) * pos.value()))\n\t\t\t\tright := w - split\n\t\t\t\tleft := w - right\n\n\t\t\t\tb.WriteString(strings.Repeat(\" \", left))\n\t\t\t\tb.WriteString(line)\n\t\t\t\tb.WriteString(strings.Repeat(\" \", right))\n\t\t\t}\n\n\t\t\t// Write a newline as long as we're not on the last line of the\n\t\t\t// last block.\n\t\t\tif !(i == len(blocks)-1 && j == len(block)-1) { //nolint:staticcheck\n\t\t\t\tb.WriteRune('\\n')\n\t\t\t}\n\t\t}\n\t}\n\n\treturn b.String()\n}\n"
  },
  {
    "path": "join_test.go",
    "content": "package lipgloss\n\nimport \"testing\"\n\nfunc TestJoinVertical(t *testing.T) {\n\ttype test struct {\n\t\tname     string\n\t\tresult   string\n\t\texpected string\n\t}\n\ttests := []test{\n\t\t{\"pos0\", JoinVertical(Left, \"A\", \"BBBB\"), \"A   \\nBBBB\"},\n\t\t{\"pos1\", JoinVertical(Right, \"A\", \"BBBB\"), \"   A\\nBBBB\"},\n\t\t{\"pos0.25\", JoinVertical(0.25, \"A\", \"BBBB\"), \" A  \\nBBBB\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tif test.result != test.expected {\n\t\t\t\tt.Errorf(\"Got \\n%s\\n, expected \\n%s\\n\", test.result, test.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestJoinHorizontal(t *testing.T) {\n\ttype test struct {\n\t\tname     string\n\t\tresult   string\n\t\texpected string\n\t}\n\ttests := []test{\n\t\t{\"pos0\", JoinHorizontal(Top, \"A\", \"B\\nB\\nB\\nB\"), \"AB\\n B\\n B\\n B\"},\n\t\t{\"pos1\", JoinHorizontal(Bottom, \"A\", \"B\\nB\\nB\\nB\"), \" B\\n B\\n B\\nAB\"},\n\t\t{\"pos0.25\", JoinHorizontal(0.25, \"A\", \"B\\nB\\nB\\nB\"), \" B\\nAB\\n B\\n B\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tif test.result != test.expected {\n\t\t\t\tt.Errorf(\"Got \\n%s\\n, expected \\n%s\\n\", test.result, test.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "layer.go",
    "content": "package lipgloss\n\nimport (\n\t\"fmt\"\n\t\"image\"\n\t\"slices\"\n\n\tuv \"github.com/charmbracelet/ultraviolet\"\n)\n\n// Layer represents a visual layer with content and positioning. It's a pure\n// data structure that defines the layer hierarchy without any computation.\ntype Layer struct {\n\tid            string\n\tcontent       string\n\twidth, height int\n\tx, y, z       int\n\tlayers        []*Layer\n}\n\n// NewLayer creates a new [Layer] with the given content and optional child layers.\nfunc NewLayer(content string, layers ...*Layer) *Layer {\n\tl := &Layer{\n\t\tcontent: content,\n\t}\n\tl.AddLayers(layers...)\n\treturn l\n}\n\n// GetContent returns the content of the Layer.\nfunc (l *Layer) GetContent() string {\n\treturn l.content\n}\n\n// Width returns the width of the Layer.\nfunc (l *Layer) Width() int {\n\treturn l.width\n}\n\n// Height returns the height of the Layer.\nfunc (l *Layer) Height() int {\n\treturn l.height\n}\n\n// GetID returns the ID of the Layer.\nfunc (l *Layer) GetID() string {\n\treturn l.id\n}\n\n// ID sets the ID of the Layer.\nfunc (l *Layer) ID(id string) *Layer {\n\tl.id = id\n\treturn l\n}\n\n// X sets the x-coordinate of the Layer relative to its parent.\nfunc (l *Layer) X(x int) *Layer {\n\tl.x = x\n\treturn l\n}\n\n// Y sets the y-coordinate of the Layer relative to its parent.\nfunc (l *Layer) Y(y int) *Layer {\n\tl.y = y\n\treturn l\n}\n\n// Z sets the z-index of the Layer relative to its parent.\nfunc (l *Layer) Z(z int) *Layer {\n\tl.z = z\n\treturn l\n}\n\n// GetX returns the x-coordinate of the Layer relative to its parent.\nfunc (l *Layer) GetX() int {\n\treturn l.x\n}\n\n// GetY returns the y-coordinate of the Layer relative to its parent.\nfunc (l *Layer) GetY() int {\n\treturn l.y\n}\n\n// GetZ returns the z-index of the Layer relative to its parent.\nfunc (l *Layer) GetZ() int {\n\treturn l.z\n}\n\n// AddLayers adds child layers to the Layer.\nfunc (l *Layer) AddLayers(layers ...*Layer) *Layer {\n\tfor i, layer := range layers {\n\t\tif layer == nil {\n\t\t\tpanic(fmt.Sprintf(\"layer at index %d is nil\", i))\n\t\t}\n\t\tl.layers = append(l.layers, layer)\n\t}\n\tarea := l.boundsWithOffset(0, 0)\n\tl.width = area.Dx()\n\tl.height = area.Dy()\n\treturn l\n}\n\n// GetLayer returns a descendant layer by its ID, or nil if not found.\n// Layers with empty IDs are skipped.\nfunc (l *Layer) GetLayer(id string) *Layer {\n\tif id == \"\" {\n\t\treturn nil\n\t}\n\tif l.id == id {\n\t\treturn l\n\t}\n\tfor _, child := range l.layers {\n\t\tif found := child.GetLayer(id); found != nil {\n\t\t\treturn found\n\t\t}\n\t}\n\treturn nil\n}\n\n// MaxZ returns the maximum z-index among this layer and all its descendants.\nfunc (l *Layer) MaxZ() int {\n\tmaxZ := l.z\n\tfor _, child := range l.layers {\n\t\tchildMaxZ := child.MaxZ()\n\t\tif childMaxZ > maxZ {\n\t\t\tmaxZ = childMaxZ\n\t\t}\n\t}\n\treturn maxZ\n}\n\n// boundsWithOffset calculates bounds with parent offset applied.\nfunc (l *Layer) boundsWithOffset(parentX, parentY int) image.Rectangle {\n\tabsX := l.x + parentX\n\tabsY := l.y + parentY\n\n\twidth, height := Width(l.content), Height(l.content)\n\tbounds := image.Rectangle{\n\t\tMin: image.Pt(absX, absY),\n\t\tMax: image.Pt(absX+width, absY+height),\n\t}\n\n\tfor _, child := range l.layers {\n\t\tbounds = bounds.Union(child.boundsWithOffset(absX, absY))\n\t}\n\n\treturn bounds\n}\n\nvar _ uv.Drawable = (*Layer)(nil)\n\n// Draw draws the content of the layer on the screen at the specified area.\nfunc (l *Layer) Draw(scr uv.Screen, area uv.Rectangle) {\n\tcontent := uv.NewStyledString(l.content)\n\tcontent.Draw(scr, area)\n}\n\n// LayerHit represents the result of a hit test on a [Layer].\ntype LayerHit struct {\n\tid     string\n\tlayer  *Layer\n\tbounds image.Rectangle\n}\n\n// Empty returns true if the LayerHit represents no hit.\nfunc (lh LayerHit) Empty() bool {\n\treturn lh.layer == nil\n}\n\n// ID returns the ID of the hit Layer.\nfunc (lh LayerHit) ID() string {\n\treturn lh.id\n}\n\n// Layer returns the layer that was hit.\nfunc (lh LayerHit) Layer() *Layer {\n\treturn lh.layer\n}\n\n// Bounds returns the bounds of the LayerHit.\nfunc (lh LayerHit) Bounds() image.Rectangle {\n\treturn lh.bounds\n}\n\n// Compositor manages the composition of layers. It flattens a layer hierarchy\n// once and provides efficient drawing and hit testing operations. All computation\n// related to layers happens in the Compositor.\ntype Compositor struct {\n\troot   *Layer\n\tlayers []compositeLayer\n\tindex  map[string]*Layer\n\tbounds image.Rectangle\n}\n\n// compositeLayer holds a flattened layer with its calculated absolute position and bounds.\ntype compositeLayer struct {\n\tlayer  *Layer\n\tabsX   int\n\tabsY   int\n\tbounds image.Rectangle\n}\n\n// NewCompositor creates a new Compositor with an internal root layer. Optional\n// layers can be provided which will be added as children of the root. The layer\n// hierarchy is flattened and sorted by z-index for efficient rendering and hit testing.\nfunc NewCompositor(layers ...*Layer) *Compositor {\n\troot := NewLayer(\"\")\n\troot.AddLayers(layers...)\n\tc := &Compositor{\n\t\troot:  root,\n\t\tindex: make(map[string]*Layer),\n\t}\n\tc.flatten()\n\treturn c\n}\n\n// AddLayers adds layers to the compositor's root and refreshes the internal state.\nfunc (c *Compositor) AddLayers(layers ...*Layer) *Compositor {\n\tc.root.AddLayers(layers...)\n\tc.flatten()\n\treturn c\n}\n\n// flatten builds the internal flattened layer list and calculates overall bounds.\nfunc (c *Compositor) flatten() {\n\tc.layers = nil\n\tc.index = make(map[string]*Layer)\n\tc.flattenRecursive(c.root, 0, 0)\n\n\t// Sort by absolute z-index (lowest to highest for drawing)\n\tslices.SortFunc(c.layers, func(a, b compositeLayer) int {\n\t\treturn a.layer.z - b.layer.z\n\t})\n\n\t// Calculate overall bounds\n\tif len(c.layers) > 0 {\n\t\tc.bounds = c.layers[0].bounds\n\t\tfor i := 1; i < len(c.layers); i++ {\n\t\t\tc.bounds = c.bounds.Union(c.layers[i].bounds)\n\t\t}\n\t}\n}\n\n// flattenRecursive recursively collects all layers with their absolute positions.\nfunc (c *Compositor) flattenRecursive(layer *Layer, parentX, parentY int) {\n\tabsX := layer.x + parentX\n\tabsY := layer.y + parentY\n\n\twidth, height := Width(layer.content), Height(layer.content)\n\tbounds := image.Rectangle{\n\t\tMin: image.Pt(absX, absY),\n\t\tMax: image.Pt(absX+width, absY+height),\n\t}\n\n\tc.layers = append(c.layers, compositeLayer{\n\t\tlayer:  layer,\n\t\tabsX:   absX,\n\t\tabsY:   absY,\n\t\tbounds: bounds,\n\t})\n\n\t// Index layer by ID if it has one\n\tif layer.id != \"\" {\n\t\tc.index[layer.id] = layer\n\t}\n\n\tfor _, child := range layer.layers {\n\t\tc.flattenRecursive(child, absX, absY)\n\t}\n}\n\n// Bounds returns the overall bounds of all layers in the compositor.\nfunc (c *Compositor) Bounds() image.Rectangle {\n\treturn c.bounds\n}\n\n// Draw draws all layers onto the given [uv.Screen] in z-index order.\nfunc (c *Compositor) Draw(scr uv.Screen, area image.Rectangle) {\n\tfor _, cl := range c.layers {\n\t\tif cl.bounds.Overlaps(area) {\n\t\t\tcl.layer.Draw(scr, cl.bounds)\n\t\t}\n\t}\n}\n\n// Hit performs a hit test at the given (x, y) coordinates. If a layer is hit,\n// it returns the ID of the top-most layer at that point. Layers with empty IDs\n// are ignored. If no layer is hit, it returns an empty [LayerHit].\nfunc (c *Compositor) Hit(x, y int) LayerHit {\n\tvar hit LayerHit\n\tpt := image.Pt(x, y)\n\t// Check from highest z to lowest (reverse order)\n\tfor i := len(c.layers) - 1; i >= 0; i-- {\n\t\tcl := c.layers[i]\n\t\tif cl.layer.id != \"\" && pt.In(cl.bounds) {\n\t\t\thit.id = cl.layer.id\n\t\t\thit.layer = cl.layer\n\t\t\thit.bounds = cl.bounds\n\t\t\treturn hit\n\t\t}\n\t}\n\treturn hit\n}\n\n// GetLayer returns a layer by its ID, or nil if not found.\n// Layers with empty IDs are not indexed and cannot be retrieved.\nfunc (c *Compositor) GetLayer(id string) *Layer {\n\tif id == \"\" {\n\t\treturn nil\n\t}\n\treturn c.index[id]\n}\n\n// Refresh re-flattens the layer hierarchy. Call this after modifying the layer\n// tree structure or positions to update the compositor's internal state.\nfunc (c *Compositor) Refresh() {\n\tc.flatten()\n}\n\n// Render renders the compositor into a styled string. This is a helper\n// function that creates a temporary canvas, draws the compositor onto it, and\n// returns the resulting string.\nfunc (c *Compositor) Render() string {\n\twidth, height := c.bounds.Dx(), c.bounds.Dy()\n\tcanvas := NewCanvas(width, height)\n\treturn canvas.Compose(c).Render()\n}\n"
  },
  {
    "path": "lipgloss.go",
    "content": "// Package lipgloss provides style definitions for nice terminal layouts. Built\n// with TUIs in mind.\npackage lipgloss\n"
  },
  {
    "path": "list/enumerator.go",
    "content": "package list\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// Enumerator enumerates a list. Given a list of items and the index of the\n// current enumeration, it returns the prefix that should be displayed for the\n// current item.\n//\n// For example, a simple Arabic numeral enumeration would be:\n//\n//\tfunc Arabic(_ Items, i int) string {\n//\t  return fmt.Sprintf(\"%d.\", i+1)\n//\t}\n//\n// There are several predefined enumerators:\n//   - Alphabet\n//   - Arabic\n//   - Bullet\n//   - Dash\n//   - Roman\n//\n// Or, define your own.\ntype Enumerator func(items Items, index int) string\n\n// Indenter indents the children of a tree.\n//\n// Indenters allow for displaying nested tree items with connecting borders\n// to sibling nodes.\n//\n// For example, the default indenter would be:\n//\n//\tfunc TreeIndenter(children Children, index int) string {\n//\t\tif children.Length()-1 == index {\n//\t\t\treturn \"│  \"\n//\t\t}\n//\n//\t\treturn \"   \"\n//\t}\ntype Indenter func(items Items, index int) string\n\n// Alphabet is the enumeration for alphabetical listing.\n//\n//\tExample:\n//\t  a. Foo\n//\t  b. Bar\n//\t  c. Baz\n//\t  d. Qux.\nfunc Alphabet(_ Items, i int) string {\n\tif i >= abcLen*abcLen+abcLen {\n\t\treturn fmt.Sprintf(\"%c%c%c.\", 'A'+i/abcLen/abcLen-1, 'A'+(i/abcLen)%abcLen-1, 'A'+i%abcLen)\n\t}\n\tif i >= abcLen {\n\t\treturn fmt.Sprintf(\"%c%c.\", 'A'+i/abcLen-1, 'A'+(i)%abcLen)\n\t}\n\treturn fmt.Sprintf(\"%c.\", 'A'+i%abcLen)\n}\n\nconst abcLen = 26\n\n// Arabic is the enumeration for arabic numerals listing.\n//\n//\tExample:\n//\t  1. Foo\n//\t  2. Bar\n//\t  3. Baz\n//\t  4. Qux.\nfunc Arabic(_ Items, i int) string {\n\treturn fmt.Sprintf(\"%d.\", i+1)\n}\n\n// Roman is the enumeration for roman numerals listing.\n//\n//\tExample:\n//\t  I. Foo\n//\t II. Bar\n//\tIII. Baz\n//\t IV. Qux.\nfunc Roman(_ Items, i int) string {\n\tvar (\n\t\troman  = []string{\"M\", \"CM\", \"D\", \"CD\", \"C\", \"XC\", \"L\", \"XL\", \"X\", \"IX\", \"V\", \"IV\", \"I\"}\n\t\tarabic = []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}\n\t\tresult strings.Builder\n\t)\n\tfor v, value := range arabic {\n\t\tfor i >= value-1 {\n\t\t\ti -= value\n\t\t\tresult.WriteString(roman[v])\n\t\t}\n\t}\n\tresult.WriteRune('.')\n\treturn result.String()\n}\n\n// Bullet is the enumeration for bullet listing.\n//\n//\tExample:\n//\t  • Foo\n//\t  • Bar\n//\t  • Baz\n//\t  • Qux.\nfunc Bullet(Items, int) string {\n\treturn \"•\"\n}\n\n// Asterisk is an enumeration using asterisks.\n//\n//\tExample:\n//\t  * Foo\n//\t  * Bar\n//\t  * Baz\n//\t  * Qux.\nfunc Asterisk(Items, int) string {\n\treturn \"*\"\n}\n\n// Dash is an enumeration using dashes.\n//\n//\tExample:\n//\t  - Foo\n//\t  - Bar\n//\t  - Baz\n//\t  - Qux.\nfunc Dash(Items, int) string {\n\treturn \"-\"\n}\n"
  },
  {
    "path": "list/list.go",
    "content": "// Package list allows you to build lists, as simple or complicated as you need.\n//\n// Simply, define a list with some items and set it's rendering properties, like\n// enumerator and styling:\n//\n//\tgroceries := list.New(\n//\t\t\"Bananas\",\n//\t\t\"Barley\",\n//\t\t\"Cashews\",\n//\t\t\"Milk\",\n//\t\tlist.New(\n//\t\t\t\"Almond Milk\"\n//\t\t\t\"Coconut Milk\"\n//\t\t\t\"Full Fat Milk\"\n//\t\t)\n//\t\t\"Eggs\",\n//\t\t\"Fish Cake\",\n//\t\t\"Leeks\",\n//\t\t\"Papaya\",\n//\t)\n//\n//\tfmt.Println(groceries)\npackage list //nolint:revive\n\nimport (\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/tree\"\n)\n\n// List represents a list of items that can be displayed. Lists can contain\n// lists as items, they will be rendered as nested (sub)lists.\n//\n// In fact, lists can contain anything as items, like lipgloss.Table or lipgloss.Tree.\ntype List struct{ tree *tree.Tree }\n\n// New returns a new list with the given items.\n//\n//\talphabet := list.New(\n//\t\t\"A\",\n//\t\t\"B\",\n//\t\t\"C\",\n//\t\t\"D\",\n//\t\t\"E\",\n//\t\t\"F\",\n//\t\t...\n//\t)\n//\n// Items can be other lists, trees, tables, rendered markdown;\n// anything you want, really.\nfunc New(items ...any) *List {\n\tl := &List{tree: tree.New()}\n\treturn l.Items(items...).\n\t\tEnumerator(Bullet).\n\t\tIndenter(func(Items, int) string { return \" \" })\n}\n\n// Items represents the list items.\ntype Items tree.Children\n\n// StyleFunc is the style function that determines the style of an item.\n//\n// It takes the list items and index of the list and determines the lipgloss\n// Style to use for that index.\n//\n// Example:\n//\n//\tl := list.New().\n//\t\tItem(\"Red\").\n//\t\tItem(\"Green\").\n//\t\tItem(\"Blue\").\n//\t\tItemStyleFunc(func(items list.Items, i int) lipgloss.Style {\n//\t\t\tswitch {\n//\t\t\tcase i == 0:\n//\t\t\t\treturn RedStyle\n//\t\t\tcase i == 1:\n//\t\t\t\treturn GreenStyle\n//\t\t\tcase i == 2:\n//\t\t\t\treturn BlueStyle\n//\t\t\t}\n//\t})\ntype StyleFunc func(items Items, index int) lipgloss.Style\n\n// Hidden returns whether this list is hidden.\nfunc (l *List) Hidden() bool {\n\treturn l.tree.Hidden()\n}\n\n// Hide hides this list.\n// If this list is hidden, it will not be shown when rendered.\nfunc (l *List) Hide(hide bool) *List {\n\tl.tree.Hide(hide)\n\treturn l\n}\n\n// Offset sets the start and end offset for the list.\n//\n//\tExample:\n//\t\tl := list.New(\"A\", \"B\", \"C\", \"D\").\n//\t\t\tOffset(1, -1)\n//\n//\tfmt.Println(l)\n//\n//\t • B\n//\t • C\n//\t • D\nfunc (l *List) Offset(start, end int) *List {\n\tl.tree.Offset(start, end)\n\treturn l\n}\n\n// Value returns the value of this node.\nfunc (l *List) Value() string {\n\treturn l.tree.Value()\n}\n\nfunc (l *List) String() string {\n\treturn l.tree.String()\n}\n\n// EnumeratorStyle sets the enumerator style for all enumerators.\n//\n// To set the enumerator style conditionally based on the item value or index,\n// use [EnumeratorStyleFunc].\nfunc (l *List) EnumeratorStyle(style lipgloss.Style) *List {\n\tl.tree.EnumeratorStyle(style)\n\treturn l\n}\n\n// EnumeratorStyleFunc sets the enumerator style function for the list items.\n//\n// Use this to conditionally set different styles based on the current items,\n// sibling items, or index values (i.e. even or odd).\n//\n// Example:\n//\n//\tl := list.New().\n//\t\tEnumeratorStyleFunc(func(_ list.Items, i int) lipgloss.Style {\n//\t\t\tif selected == i {\n//\t\t\t\treturn lipgloss.NewStyle().Foreground(brightPink)\n//\t\t\t}\n//\t\t\treturn lipgloss.NewStyle()\n//\t\t})\nfunc (l *List) EnumeratorStyleFunc(f StyleFunc) *List {\n\tl.tree.EnumeratorStyleFunc(func(children tree.Children, index int) lipgloss.Style {\n\t\treturn f(children, index)\n\t})\n\treturn l\n}\n\n// IndenterStyle sets the enumerator style for all enumerators.\n//\n// To set the enumerator style conditionally based on the item value or index,\n// use [IndenterStyleFunc].\nfunc (l *List) IndenterStyle(style lipgloss.Style) *List {\n\tl.tree.IndenterStyle(style)\n\treturn l\n}\n\n// IndenterStyleFunc sets the enumerator style function for the list items.\n//\n// Use this to conditionally set different styles based on the current items,\n// sibling items, or index values (i.e. even or odd).\n//\n// Example:\n//\n//\tl := list.New().\n//\t\tIndenterStyleFunc(func(_ list.Items, i int) lipgloss.Style {\n//\t\t\tif selected == i {\n//\t\t\t\treturn lipgloss.NewStyle().Foreground(brightPink)\n//\t\t\t}\n//\t\t\treturn lipgloss.NewStyle()\n//\t\t})\nfunc (l *List) IndenterStyleFunc(f StyleFunc) *List {\n\tl.tree.IndenterStyleFunc(func(children tree.Children, index int) lipgloss.Style {\n\t\treturn f(children, index)\n\t})\n\treturn l\n}\n\n// Indenter sets the indenter implementation. This is used to change the way\n// the tree is indented. The default indentor places a border connecting sibling\n// elements and no border for the last child.\n//\n//\t└── Foo\n//\t    └── Bar\n//\t        └── Baz\n//\t            └── Qux\n//\t                └── Quux\n//\n// You can define your own indenter.\n//\n//\tfunc ArrowIndenter(children tree.Children, index int) string {\n//\t\treturn \"→ \"\n//\t}\n//\n//\t→ Foo\n//\t→ → Bar\n//\t→ → → Baz\n//\t→ → → → Qux\n//\t→ → → → → Quux\nfunc (l *List) Indenter(indenter Indenter) *List {\n\tl.tree.Indenter(\n\t\tfunc(children tree.Children, index int) string {\n\t\t\treturn indenter(children, index)\n\t\t},\n\t)\n\treturn l\n}\n\n// ItemStyle sets the item style for all items.\n//\n// To set the item style conditionally based on the item value or index,\n// use [ItemStyleFunc].\nfunc (l *List) ItemStyle(style lipgloss.Style) *List {\n\tl.tree.ItemStyle(style)\n\treturn l\n}\n\n// ItemStyleFunc sets the item style function for the list items.\n//\n// Use this to conditionally set different styles based on the current items,\n// sibling items, or index values.\n//\n// Example:\n//\n//\tl := list.New().\n//\t\tItemStyleFunc(func(_ list.Items, i int) lipgloss.Style {\n//\t\t\tif selected == i {\n//\t\t\t\treturn lipgloss.NewStyle().Foreground(brightPink)\n//\t\t\t}\n//\t\t\treturn lipgloss.NewStyle()\n//\t\t})\nfunc (l *List) ItemStyleFunc(f StyleFunc) *List {\n\tl.tree.ItemStyleFunc(func(children tree.Children, index int) lipgloss.Style {\n\t\treturn f(children, index)\n\t})\n\treturn l\n}\n\n// Item appends an item to the list.\n//\n//\tl := list.New().\n//\t\tItem(\"Foo\").\n//\t\tItem(\"Bar\").\n//\t\tItem(\"Baz\")\nfunc (l *List) Item(item any) *List {\n\tswitch item := item.(type) {\n\tcase *List:\n\t\tl.tree.Child(item.tree)\n\tdefault:\n\t\tl.tree.Child(item)\n\t}\n\treturn l\n}\n\n// Items appends multiple items to the list.\n//\n//\tl := list.New().\n//\t\tItems(\"Foo\", \"Bar\", \"Baz\"),\nfunc (l *List) Items(items ...any) *List {\n\tfor _, item := range items {\n\t\tl.Item(item)\n\t}\n\treturn l\n}\n\n// Enumerator sets the list enumerator.\n//\n// There are several predefined enumerators:\n// • Alphabet\n// • Arabic\n// • Bullet\n// • Dash\n// • Roman\n//\n// Or, define your own.\n//\n//\tfunc echoEnumerator(items list.Items, i int) string {\n//\t\treturn items.At(i).Value() + \". \"\n//\t}\n//\n//\tl := list.New(\"Foo\", \"Bar\", \"Baz\").Enumerator(echoEnumerator)\n//\tfmt.Println(l)\n//\n//\t Foo. Foo\n//\t Bar. Bar\n//\t Baz. Baz\nfunc (l *List) Enumerator(enumerator Enumerator) *List {\n\tl.tree.Enumerator(func(c tree.Children, i int) string { return enumerator(c, i) })\n\treturn l\n}\n"
  },
  {
    "path": "list/list_test.go",
    "content": "package list_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"unicode\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/list\"\n\t\"charm.land/lipgloss/v2/tree\"\n\t\"github.com/aymanbagabas/go-udiff\"\n\t\"github.com/charmbracelet/x/exp/golden\"\n)\n\n// XXX: can't write multi-line examples if the underlying string uses\n// lipgloss.JoinVertical.\n\nfunc TestList(t *testing.T) {\n\tl := list.New().\n\t\tItem(\"Foo\").\n\t\tItem(\"Bar\").\n\t\tItem(\"Baz\")\n\n\tgolden.RequireEqual(t, []byte(l.String()))\n}\n\nfunc TestListItems(t *testing.T) {\n\tl := list.New().\n\t\tItems([]string{\"Foo\", \"Bar\", \"Baz\"})\n\n\tgolden.RequireEqual(t, []byte(l.String()))\n}\n\nfunc TestSublist(t *testing.T) {\n\tl := list.New().\n\t\tItem(\"Foo\").\n\t\tItem(\"Bar\").\n\t\tItem(list.New(\"Hi\", \"Hello\", \"Halo\").Enumerator(list.Roman)).\n\t\tItem(\"Qux\")\n\n\tgolden.RequireEqual(t, []byte(l.String()))\n}\n\nfunc TestSublistItems(t *testing.T) {\n\tl := list.New(\n\t\t\"A\",\n\t\t\"B\",\n\t\t\"C\",\n\t\tlist.New(\n\t\t\t\"D\",\n\t\t\t\"E\",\n\t\t\t\"F\",\n\t\t).Enumerator(list.Roman),\n\t\t\"G\",\n\t)\n\n\tgolden.RequireEqual(t, []byte(l.String()))\n}\n\nfunc TestComplexSublist(t *testing.T) {\n\tstyle1 := lipgloss.NewStyle().\n\t\tForeground(lipgloss.Color(\"99\")).\n\t\tPaddingRight(1)\n\tstyle2 := lipgloss.NewStyle().\n\t\tForeground(lipgloss.Color(\"212\")).\n\t\tPaddingRight(1)\n\n\tl := list.New().\n\t\tItem(\"Foo\").\n\t\tItem(\"Bar\").\n\t\tItem(list.New(\"foo2\", \"bar2\")).\n\t\tItem(\"Qux\").\n\t\tItem(\n\t\t\tlist.New(\"aaa\", \"bbb\").\n\t\t\t\tEnumeratorStyle(style1).\n\t\t\t\tEnumerator(list.Roman),\n\t\t).\n\t\tItem(\"Deep\").\n\t\tItem(\n\t\t\tlist.New().\n\t\t\t\tEnumeratorStyle(style2).\n\t\t\t\tIndenterStyle(style2).\n\t\t\t\tEnumerator(list.Alphabet).\n\t\t\t\tItem(\"foo\").\n\t\t\t\tItem(\"Deeper\").\n\t\t\t\tItem(\n\t\t\t\t\tlist.New().\n\t\t\t\t\t\tIndenterStyle(style1).\n\t\t\t\t\t\tEnumeratorStyle(style1).\n\t\t\t\t\t\tEnumerator(list.Arabic).\n\t\t\t\t\t\tItem(\"a\").\n\t\t\t\t\t\tItem(\"b\").\n\t\t\t\t\t\tItem(\"Even Deeper, inherit parent renderer\").\n\t\t\t\t\t\tItem(\n\t\t\t\t\t\t\tlist.New().\n\t\t\t\t\t\t\t\tEnumerator(list.Asterisk).\n\t\t\t\t\t\t\t\tIndenterStyle(style2).\n\t\t\t\t\t\t\t\tEnumeratorStyle(style2).\n\t\t\t\t\t\t\t\tItem(\"sus\").\n\t\t\t\t\t\t\t\tItem(\"d minor\").\n\t\t\t\t\t\t\t\tItem(\"f#\").\n\t\t\t\t\t\t\t\tItem(\"One ore level, with another renderer\").\n\t\t\t\t\t\t\t\tItem(\n\t\t\t\t\t\t\t\t\tlist.New().\n\t\t\t\t\t\t\t\t\t\tIndenterStyle(style1).\n\t\t\t\t\t\t\t\t\t\tEnumeratorStyle(style1).\n\t\t\t\t\t\t\t\t\t\tEnumerator(list.Dash).\n\t\t\t\t\t\t\t\t\t\tItem(\"a\\nmultine\\nstring\").\n\t\t\t\t\t\t\t\t\t\tItem(\"hoccus poccus\").\n\t\t\t\t\t\t\t\t\t\tItem(\"abra kadabra\").\n\t\t\t\t\t\t\t\t\t\tItem(\"And finally, a tree within all this\").\n\t\t\t\t\t\t\t\t\t\tItem(\n\n\t\t\t\t\t\t\t\t\t\t\ttree.New().\n\t\t\t\t\t\t\t\t\t\t\t\tIndenterStyle(style2).\n\t\t\t\t\t\t\t\t\t\t\t\tEnumeratorStyle(style2).\n\t\t\t\t\t\t\t\t\t\t\t\tChild(\"another\\nmultine\\nstring\").\n\t\t\t\t\t\t\t\t\t\t\t\tChild(\"something\").\n\t\t\t\t\t\t\t\t\t\t\t\tChild(\"a subtree\").\n\t\t\t\t\t\t\t\t\t\t\t\tChild(\n\t\t\t\t\t\t\t\t\t\t\t\t\ttree.New().\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tIndenterStyle(style2).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tEnumeratorStyle(style2).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tChild(\"yup\").\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tChild(\"many itens\").\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tChild(\"another\"),\n\t\t\t\t\t\t\t\t\t\t\t\t).\n\t\t\t\t\t\t\t\t\t\t\t\tChild(\"hallo\").\n\t\t\t\t\t\t\t\t\t\t\t\tChild(\"wunderbar!\"),\n\t\t\t\t\t\t\t\t\t\t).\n\t\t\t\t\t\t\t\t\t\tItem(\"this is a tree\\nand other obvious statements\"),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t).\n\t\t\t\tItem(\"bar\"),\n\t\t).\n\t\tItem(\"Baz\")\n\n\tgolden.RequireEqual(t, []byte(l.String()))\n}\n\nfunc TestMultiline(t *testing.T) {\n\tl := list.New().\n\t\tItem(\"Item1\\nline 2\\nline 3\").\n\t\tItem(\"Item2\\nline 2\\nline 3\").\n\t\tItem(\"3\")\n\n\tgolden.RequireEqual(t, []byte(l.String()))\n}\n\nfunc TestListIntegers(t *testing.T) {\n\tl := list.New().\n\t\tItem(\"1\").\n\t\tItem(\"2\").\n\t\tItem(\"3\")\n\n\tgolden.RequireEqual(t, []byte(l.String()))\n}\n\nfunc TestEnumerators(t *testing.T) {\n\ttests := map[string]struct {\n\t\tenumerator list.Enumerator\n\t\texpected   string\n\t}{\n\t\t\"alphabet\": {\n\t\t\tenumerator: list.Alphabet,\n\t\t\texpected: `\nA. Foo\nB. Bar\nC. Baz\n\t\t\t`,\n\t\t},\n\t\t\"arabic\": {\n\t\t\tenumerator: list.Arabic,\n\t\t\texpected: `\n1. Foo\n2. Bar\n3. Baz\n\t\t\t`,\n\t\t},\n\t\t\"roman\": {\n\t\t\tenumerator: list.Roman,\n\t\t\texpected: `\n  I. Foo\n II. Bar\nIII. Baz\n\t\t\t`,\n\t\t},\n\t\t\"bullet\": {\n\t\t\tenumerator: list.Bullet,\n\t\t\texpected: `\n• Foo\n• Bar\n• Baz\n\t\t\t`,\n\t\t},\n\t\t\"asterisk\": {\n\t\t\tenumerator: list.Asterisk,\n\t\t\texpected: `\n* Foo\n* Bar\n* Baz\n\t\t\t`,\n\t\t},\n\t\t\"dash\": {\n\t\t\tenumerator: list.Dash,\n\t\t\texpected: `\n- Foo\n- Bar\n- Baz\n\t\t\t`,\n\t\t},\n\t}\n\n\tfor name, test := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tl := list.New().\n\t\t\t\tEnumerator(test.enumerator).\n\t\t\t\tItem(\"Foo\").\n\t\t\t\tItem(\"Bar\").\n\t\t\t\tItem(\"Baz\")\n\n\t\t\tgolden.RequireEqual(t, []byte(l.String()))\n\t\t})\n\t}\n}\n\nfunc TestEnumeratorsTransform(t *testing.T) {\n\ttests := map[string]struct {\n\t\tenumeration list.Enumerator\n\t\tstyle       lipgloss.Style\n\t\texpected    string\n\t}{\n\t\t\"alphabet lower\": {\n\t\t\tenumeration: list.Alphabet,\n\t\t\tstyle:       lipgloss.NewStyle().PaddingRight(1).Transform(strings.ToLower),\n\t\t\texpected: `\na. Foo\nb. Bar\nc. Baz\n\t\t\t`,\n\t\t},\n\t\t\"arabic)\": {\n\t\t\tenumeration: list.Arabic,\n\t\t\tstyle: lipgloss.NewStyle().PaddingRight(1).Transform(func(s string) string {\n\t\t\t\treturn strings.Replace(s, \".\", \")\", 1)\n\t\t\t}),\n\t\t\texpected: `\n1) Foo\n2) Bar\n3) Baz\n\t\t\t`,\n\t\t},\n\t\t\"roman within ()\": {\n\t\t\tenumeration: list.Roman,\n\t\t\tstyle: lipgloss.NewStyle().Transform(func(s string) string {\n\t\t\t\treturn \"(\" + strings.Replace(strings.ToLower(s), \".\", \"\", 1) + \") \"\n\t\t\t}),\n\t\t\texpected: `\n  (i) Foo\n (ii) Bar\n(iii) Baz\n\t\t\t`,\n\t\t},\n\t\t\"bullet is dash\": {\n\t\t\tenumeration: list.Bullet,\n\t\t\tstyle: lipgloss.NewStyle().Transform(func(s string) string {\n\t\t\t\treturn \"- \" // this is better done by replacing the enumerator.\n\t\t\t}),\n\t\t\texpected: `\n- Foo\n- Bar\n- Baz\n\t\t\t`,\n\t\t},\n\t}\n\n\tfor name, test := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tl := list.New().\n\t\t\t\tEnumeratorStyle(test.style).\n\t\t\t\tEnumerator(test.enumeration).\n\t\t\t\tItem(\"Foo\").\n\t\t\t\tItem(\"Bar\").\n\t\t\t\tItem(\"Baz\")\n\n\t\t\tgolden.RequireEqual(t, []byte(l.String()))\n\t\t})\n\t}\n}\n\nfunc TestBullet(t *testing.T) {\n\ttests := []struct {\n\t\tenum list.Enumerator\n\t\ti    int\n\t\texp  string\n\t}{\n\t\t{list.Alphabet, 0, \"A\"},\n\t\t{list.Alphabet, 25, \"Z\"},\n\t\t{list.Alphabet, 26, \"AA\"},\n\t\t{list.Alphabet, 51, \"AZ\"},\n\t\t{list.Alphabet, 52, \"BA\"},\n\t\t{list.Alphabet, 79, \"CB\"},\n\t\t{list.Alphabet, 701, \"ZZ\"},\n\t\t{list.Alphabet, 702, \"AAA\"},\n\t\t{list.Alphabet, 801, \"ADV\"},\n\t\t{list.Alphabet, 1000, \"ALM\"},\n\t\t{list.Roman, 0, \"I\"},\n\t\t{list.Roman, 25, \"XXVI\"},\n\t\t{list.Roman, 26, \"XXVII\"},\n\t\t{list.Roman, 50, \"LI\"},\n\t\t{list.Roman, 100, \"CI\"},\n\t\t{list.Roman, 701, \"DCCII\"},\n\t\t{list.Roman, 1000, \"MI\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tprefix := test.enum(nil, test.i)\n\t\tbullet := strings.TrimSuffix(prefix, \".\")\n\t\tif bullet != test.exp {\n\t\t\tt.Errorf(\"expected: %s, got: %s\\n\", test.exp, bullet)\n\t\t}\n\t}\n}\n\nfunc TestEnumeratorsAlign(t *testing.T) {\n\tfooList := strings.Split(strings.TrimSuffix(strings.Repeat(\"Foo \", 100), \" \"), \" \")\n\tl := list.New().Enumerator(list.Roman)\n\tfor _, f := range fooList {\n\t\tl.Item(f)\n\t}\n\n\tgolden.RequireEqual(t, []byte(l.String()))\n}\n\nfunc TestSubListItems2(t *testing.T) {\n\tl := list.New().Items(\n\t\t\"S\",\n\t\tlist.New().Items(\"neovim\", \"vscode\"),\n\t\t\"HI\",\n\t\tlist.New().Items([]string{\"vim\", \"doom emacs\"}),\n\t\t\"Parent 2\",\n\t\tlist.New().Item(\"I like fuzzy socks\"),\n\t)\n\n\tgolden.RequireEqual(t, []byte(l.String()))\n}\n\n// assertEqual verifies the strings are equal, assuming its terminal output.\nfunc assertEqual(tb testing.TB, expected, got string) {\n\ttb.Helper()\n\n\tcleanExpected := trimSpace(expected)\n\tcleanGot := trimSpace(got)\n\tdiff := udiff.Unified(\"expected\", \"got\", cleanExpected, cleanGot)\n\tif diff != \"\" {\n\t\ttb.Fatalf(\"expected:\\n\\n%s\\n\\ngot:\\n\\n%s\\n\\ndiff:\\n\\n%s\\n\\n\", cleanExpected, cleanGot, diff)\n\t}\n}\n\nfunc trimSpace(s string) string {\n\tvar result []string //nolint: prealloc\n\tss := strings.Split(s, \"\\n\")\n\tfor i, line := range ss {\n\t\tif strings.TrimSpace(line) == \"\" && (i == 0 || i == len(ss)-1) {\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, strings.TrimRightFunc(line, unicode.IsSpace))\n\t}\n\treturn strings.Join(result, \"\\n\")\n}\n"
  },
  {
    "path": "list/testdata/TestComplexSublist.golden",
    "content": "• Foo\n• Bar\n  • foo2\n  • bar2\n• Qux\n   \u001b[38;5;99mI.\u001b[m aaa\n  \u001b[38;5;99mII.\u001b[m bbb\n• Deep\n  \u001b[38;5;212mA.\u001b[m foo\n  \u001b[38;5;212mB.\u001b[m Deeper\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m1.\u001b[m a\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m2.\u001b[m b\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m3.\u001b[m Even Deeper, inherit parent renderer\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m*\u001b[m sus\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m*\u001b[m d minor\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m*\u001b[m f#\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m*\u001b[m One ore level, with another renderer\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m-\u001b[m a      \n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m multine\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m string \n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m-\u001b[m hoccus poccus\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m-\u001b[m abra kadabra\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m-\u001b[m And finally, a tree within all this\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m├──\u001b[m another\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m│  \u001b[m multine\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m│  \u001b[m string \n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m├──\u001b[m something\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m├──\u001b[m a subtree\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m│  \u001b[m \u001b[38;5;212m├──\u001b[m yup\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m│  \u001b[m \u001b[38;5;212m├──\u001b[m many itens\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m│  \u001b[m \u001b[38;5;212m└──\u001b[m another\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m├──\u001b[m hallo\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m└──\u001b[m wunderbar!\n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m-\u001b[m this is a tree              \n  \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m \u001b[38;5;212m \u001b[m \u001b[38;5;99m \u001b[m and other obvious statements\n  \u001b[38;5;212mC.\u001b[m bar\n• Baz"
  },
  {
    "path": "list/testdata/TestEnumerators/alphabet.golden",
    "content": "A. Foo\nB. Bar\nC. Baz"
  },
  {
    "path": "list/testdata/TestEnumerators/arabic.golden",
    "content": "1. Foo\n2. Bar\n3. Baz"
  },
  {
    "path": "list/testdata/TestEnumerators/asterisk.golden",
    "content": "* Foo\n* Bar\n* Baz"
  },
  {
    "path": "list/testdata/TestEnumerators/bullet.golden",
    "content": "• Foo\n• Bar\n• Baz"
  },
  {
    "path": "list/testdata/TestEnumerators/dash.golden",
    "content": "- Foo\n- Bar\n- Baz"
  },
  {
    "path": "list/testdata/TestEnumerators/roman.golden",
    "content": "  I. Foo\n II. Bar\nIII. Baz"
  },
  {
    "path": "list/testdata/TestEnumeratorsAlign.golden",
    "content": "       I. Foo\n      II. Foo\n     III. Foo\n      IV. Foo\n       V. Foo\n      VI. Foo\n     VII. Foo\n    VIII. Foo\n      IX. Foo\n       X. Foo\n      XI. Foo\n     XII. Foo\n    XIII. Foo\n     XIV. Foo\n      XV. Foo\n     XVI. Foo\n    XVII. Foo\n   XVIII. Foo\n     XIX. Foo\n      XX. Foo\n     XXI. Foo\n    XXII. Foo\n   XXIII. Foo\n    XXIV. Foo\n     XXV. Foo\n    XXVI. Foo\n   XXVII. Foo\n  XXVIII. Foo\n    XXIX. Foo\n     XXX. Foo\n    XXXI. Foo\n   XXXII. Foo\n  XXXIII. Foo\n   XXXIV. Foo\n    XXXV. Foo\n   XXXVI. Foo\n  XXXVII. Foo\n XXXVIII. Foo\n   XXXIX. Foo\n      XL. Foo\n     XLI. Foo\n    XLII. Foo\n   XLIII. Foo\n    XLIV. Foo\n     XLV. Foo\n    XLVI. Foo\n   XLVII. Foo\n  XLVIII. Foo\n    XLIX. Foo\n       L. Foo\n      LI. Foo\n     LII. Foo\n    LIII. Foo\n     LIV. Foo\n      LV. Foo\n     LVI. Foo\n    LVII. Foo\n   LVIII. Foo\n     LIX. Foo\n      LX. Foo\n     LXI. Foo\n    LXII. Foo\n   LXIII. Foo\n    LXIV. Foo\n     LXV. Foo\n    LXVI. Foo\n   LXVII. Foo\n  LXVIII. Foo\n    LXIX. Foo\n     LXX. Foo\n    LXXI. Foo\n   LXXII. Foo\n  LXXIII. Foo\n   LXXIV. Foo\n    LXXV. Foo\n   LXXVI. Foo\n  LXXVII. Foo\n LXXVIII. Foo\n   LXXIX. Foo\n    LXXX. Foo\n   LXXXI. Foo\n  LXXXII. Foo\n LXXXIII. Foo\n  LXXXIV. Foo\n   LXXXV. Foo\n  LXXXVI. Foo\n LXXXVII. Foo\nLXXXVIII. Foo\n  LXXXIX. Foo\n      XC. Foo\n     XCI. Foo\n    XCII. Foo\n   XCIII. Foo\n    XCIV. Foo\n     XCV. Foo\n    XCVI. Foo\n   XCVII. Foo\n  XCVIII. Foo\n    XCIX. Foo\n       C. Foo"
  },
  {
    "path": "list/testdata/TestEnumeratorsTransform/alphabet_lower.golden",
    "content": "a. Foo\nb. Bar\nc. Baz"
  },
  {
    "path": "list/testdata/TestEnumeratorsTransform/arabic).golden",
    "content": "1) Foo\n2) Bar\n3) Baz"
  },
  {
    "path": "list/testdata/TestEnumeratorsTransform/bullet_is_dash.golden",
    "content": "- Foo\n- Bar\n- Baz"
  },
  {
    "path": "list/testdata/TestEnumeratorsTransform/roman_within_().golden",
    "content": "  (i) Foo\n (ii) Bar\n(iii) Baz"
  },
  {
    "path": "list/testdata/TestList.golden",
    "content": "• Foo\n• Bar\n• Baz"
  },
  {
    "path": "list/testdata/TestListIntegers.golden",
    "content": "• 1\n• 2\n• 3"
  },
  {
    "path": "list/testdata/TestListItems.golden",
    "content": "• Foo\n• Bar\n• Baz"
  },
  {
    "path": "list/testdata/TestMultiline.golden",
    "content": "• Item1 \n  line 2\n  line 3\n• Item2 \n  line 2\n  line 3\n• 3"
  },
  {
    "path": "list/testdata/TestSubListItems2.golden",
    "content": "• S\n  • neovim\n  • vscode\n• HI\n  • vim\n  • doom emacs\n• Parent 2\n  • I like fuzzy socks"
  },
  {
    "path": "list/testdata/TestSublist.golden",
    "content": "• Foo\n• Bar\n    I. Hi\n   II. Hello\n  III. Halo\n• Qux"
  },
  {
    "path": "list/testdata/TestSublistItems.golden",
    "content": "• A\n• B\n• C\n    I. D\n   II. E\n  III. F\n• G"
  },
  {
    "path": "position.go",
    "content": "package lipgloss\n\nimport (\n\t\"math\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// Position represents a position along a horizontal or vertical axis. It's in\n// situations where an axis is involved, like alignment, joining, placement and\n// so on.\n//\n// A value of 0 represents the start (the left or top) and 1 represents the end\n// (the right or bottom). 0.5 represents the center.\n//\n// There are constants Top, Bottom, Center, Left and Right in this package that\n// can be used to aid readability.\ntype Position float64\n\nfunc (p Position) value() float64 {\n\treturn math.Min(1, math.Max(0, float64(p)))\n}\n\n// Position aliases.\nconst (\n\tTop    Position = 0.0\n\tBottom Position = 1.0\n\tCenter Position = 0.5\n\tLeft   Position = 0.0\n\tRight  Position = 1.0\n)\n\n// Place places a string or text block vertically in an unstyled box of a given\n// width or height.\nfunc Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {\n\treturn PlaceVertical(height, vPos, PlaceHorizontal(width, hPos, str, opts...), opts...)\n}\n\n// PlaceHorizontal places a string or text block horizontally in an unstyled\n// block of a given width. If the given width is shorter than the max width of\n// the string (measured by its longest line) this will be a noop.\nfunc PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {\n\tlines, contentWidth := getLines(str)\n\tgap := width - contentWidth\n\n\tif gap <= 0 {\n\t\treturn str\n\t}\n\n\tws := newWhitespace(opts...)\n\n\tvar b strings.Builder\n\tfor i, l := range lines {\n\t\t// Is this line shorter than the longest line?\n\t\tshort := max(0, contentWidth-ansi.StringWidth(l))\n\n\t\tswitch pos {\n\t\tcase Left:\n\t\t\tb.WriteString(l)\n\t\t\tb.WriteString(ws.render(gap + short))\n\n\t\tcase Right:\n\t\t\tb.WriteString(ws.render(gap + short))\n\t\t\tb.WriteString(l)\n\n\t\tdefault: // somewhere in the middle\n\t\t\ttotalGap := gap + short\n\n\t\t\tsplit := int(math.Round(float64(totalGap) * pos.value()))\n\t\t\tleft := totalGap - split\n\t\t\tright := totalGap - left\n\n\t\t\tb.WriteString(ws.render(left))\n\t\t\tb.WriteString(l)\n\t\t\tb.WriteString(ws.render(right))\n\t\t}\n\n\t\tif i < len(lines)-1 {\n\t\t\tb.WriteRune('\\n')\n\t\t}\n\t}\n\n\treturn b.String()\n}\n\n// PlaceVertical places a string or text block vertically in an unstyled block\n// of a given height. If the given height is shorter than the height of the\n// string (measured by its newlines) then this will be a noop.\nfunc PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {\n\tcontentHeight := strings.Count(str, \"\\n\") + 1\n\tgap := height - contentHeight\n\n\tif gap <= 0 {\n\t\treturn str\n\t}\n\n\tws := newWhitespace(opts...)\n\n\t_, width := getLines(str)\n\temptyLine := ws.render(width)\n\tb := strings.Builder{}\n\n\tswitch pos {\n\tcase Top:\n\t\tb.WriteString(str)\n\t\tb.WriteRune('\\n')\n\t\tfor i := range gap {\n\t\t\tb.WriteString(emptyLine)\n\t\t\tif i < gap-1 {\n\t\t\t\tb.WriteRune('\\n')\n\t\t\t}\n\t\t}\n\n\tcase Bottom:\n\t\tb.WriteString(strings.Repeat(emptyLine+\"\\n\", gap))\n\t\tb.WriteString(str)\n\n\tdefault: // Somewhere in the middle\n\t\tsplit := int(math.Round(float64(gap) * pos.value()))\n\t\ttop := gap - split\n\t\tbottom := gap - top\n\n\t\tb.WriteString(strings.Repeat(emptyLine+\"\\n\", top))\n\t\tb.WriteString(str)\n\n\t\tfor range bottom {\n\t\t\tb.WriteRune('\\n')\n\t\t\tb.WriteString(emptyLine)\n\t\t}\n\t}\n\n\treturn b.String()\n}\n"
  },
  {
    "path": "query.go",
    "content": "package lipgloss\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"os\"\n\t\"runtime\"\n\n\t\"github.com/charmbracelet/x/term\"\n)\n\nfunc backgroundColor(in term.File, out term.File) (color.Color, error) {\n\tstate, err := term.MakeRaw(in.Fd())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error setting raw state to detect background color: %w\", err)\n\t}\n\n\tdefer term.Restore(in.Fd(), state) //nolint:errcheck\n\n\tbg, err := queryBackgroundColor(in, out)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn bg, nil\n}\n\n// BackgroundColor queries the terminal's background color. Typically, you'll\n// want to query against stdin and either stdout or stderr, depending on what\n// you're writing to.\n//\n// This function is intended for standalone Lip Gloss use only. If you're using\n// Bubble Tea, listen for tea.BackgroundColorMsg in your update function.\nfunc BackgroundColor(in term.File, out term.File) (bg color.Color, err error) {\n\tif runtime.GOOS == \"windows\" { //nolint:nestif\n\t\t// On Windows, when the input/output is redirected or piped, we need to\n\t\t// open the console explicitly.\n\t\t// See https://learn.microsoft.com/en-us/windows/console/getstdhandle#remarks\n\t\tif !term.IsTerminal(in.Fd()) {\n\t\t\tf, err := os.OpenFile(\"CONIN$\", os.O_RDWR, 0o644) //nolint:gosec\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error opening CONIN$: %w\", err)\n\t\t\t}\n\t\t\tin = f\n\t\t}\n\t\tif !term.IsTerminal(out.Fd()) {\n\t\t\tf, err := os.OpenFile(\"CONOUT$\", os.O_RDWR, 0o644) //nolint:gosec\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error opening CONOUT$: %w\", err)\n\t\t\t}\n\t\t\tout = f\n\t\t}\n\t\treturn backgroundColor(in, out)\n\t}\n\n\t// NOTE: On Unix, one of the given files must be a tty.\n\tfor _, f := range []term.File{in, out} {\n\t\tif bg, err = backgroundColor(f, f); err == nil {\n\t\t\treturn bg, nil\n\t\t}\n\t}\n\n\treturn\n}\n\n// HasDarkBackground detects whether the terminal has a light or dark\n// background.\n//\n// Typically, you'll want to query against stdin and either stdout or stderr\n// depending on what you're writing to.\n//\n//\thasDarkBG := HasDarkBackground(os.Stdin, os.Stdout)\n//\tlightDark := LightDark(hasDarkBG)\n//\tmyHotColor := lightDark(\"#ff0000\", \"#0000ff\")\n//\n// This is intended for use in standalone Lip Gloss only. In Bubble Tea, listen\n// for tea.BackgroundColorMsg in your Update function.\n//\n//\tcase tea.BackgroundColorMsg:\n//\t    hasDarkBackground = msg.IsDark()\n//\n// By default, this function will return true if it encounters an error.\nfunc HasDarkBackground(in term.File, out term.File) bool {\n\tbg, err := BackgroundColor(in, out)\n\tif err != nil || bg == nil {\n\t\treturn true\n\t}\n\treturn isDarkColor(bg)\n}\n"
  },
  {
    "path": "ranges.go",
    "content": "package lipgloss\n\nimport (\n\t\"strings\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// StyleRanges applying styling to ranges in a string. Existing styles will be\n// taken into account. Ranges should not overlap.\nfunc StyleRanges(s string, ranges ...Range) string {\n\tif len(ranges) == 0 {\n\t\treturn s\n\t}\n\n\tvar buf strings.Builder\n\tlastIdx := 0\n\tstripped := ansi.Strip(s)\n\n\t// Use Truncate and TruncateLeft to style match.MatchedIndexes without\n\t// losing the original option style:\n\tfor _, rng := range ranges {\n\t\t// Add the text before this match\n\t\tif rng.Start > lastIdx {\n\t\t\tbuf.WriteString(ansi.Cut(s, lastIdx, rng.Start))\n\t\t}\n\t\t// Add the matched range with its highlight\n\t\tbuf.WriteString(rng.Style.Render(ansi.Cut(stripped, rng.Start, rng.End)))\n\t\tlastIdx = rng.End\n\t}\n\n\t// Add any remaining text after the last match\n\tbuf.WriteString(ansi.TruncateLeft(s, lastIdx, \"\"))\n\n\treturn buf.String()\n}\n\n// NewRange returns a range and style that can be used with [StyleRanges].\nfunc NewRange(start, end int, style Style) Range {\n\treturn Range{start, end, style}\n}\n\n// Range is a range of text and associated styling to be used with\n// [StyleRanges].\ntype Range struct {\n\tStart, End int\n\tStyle      Style\n}\n"
  },
  {
    "path": "ranges_test.go",
    "content": "package lipgloss\n\nimport (\n\t\"testing\"\n)\n\nfunc TestStyleRanges(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    string\n\t\tranges   []Range\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"empty ranges\",\n\t\t\tinput:    \"hello world\",\n\t\t\tranges:   []Range{},\n\t\t\texpected: \"hello world\",\n\t\t},\n\t\t{\n\t\t\tname:  \"single range in middle\",\n\t\t\tinput: \"hello world\",\n\t\t\tranges: []Range{\n\t\t\t\tNewRange(6, 11, NewStyle().Bold(true)),\n\t\t\t},\n\t\t\texpected: \"hello \\x1b[1mworld\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tname:  \"multiple ranges\",\n\t\t\tinput: \"hello world\",\n\t\t\tranges: []Range{\n\t\t\t\tNewRange(0, 5, NewStyle().Bold(true)),\n\t\t\t\tNewRange(6, 11, NewStyle().Italic(true)),\n\t\t\t},\n\t\t\texpected: \"\\x1b[1mhello\\x1b[m \\x1b[3mworld\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tname:  \"overlapping with existing ANSI\",\n\t\t\tinput: \"hello \\x1b[32mworld\\x1b[m\",\n\t\t\tranges: []Range{\n\t\t\t\tNewRange(0, 5, NewStyle().Bold(true)),\n\t\t\t},\n\t\t\texpected: \"\\x1b[1mhello\\x1b[m \\x1b[32mworld\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tname:  \"style at start\",\n\t\t\tinput: \"hello world\",\n\t\t\tranges: []Range{\n\t\t\t\tNewRange(0, 5, NewStyle().Bold(true)),\n\t\t\t},\n\t\t\texpected: \"\\x1b[1mhello\\x1b[m world\",\n\t\t},\n\t\t{\n\t\t\tname:  \"style at end\",\n\t\t\tinput: \"hello world\",\n\t\t\tranges: []Range{\n\t\t\t\tNewRange(6, 11, NewStyle().Bold(true)),\n\t\t\t},\n\t\t\texpected: \"hello \\x1b[1mworld\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tname:  \"multiple styles with gap\",\n\t\t\tinput: \"hello beautiful world\",\n\t\t\tranges: []Range{\n\t\t\t\tNewRange(0, 5, NewStyle().Bold(true)),\n\t\t\t\tNewRange(16, 23, NewStyle().Italic(true)),\n\t\t\t},\n\t\t\texpected: \"\\x1b[1mhello\\x1b[m beautiful \\x1b[3mworld\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tname:  \"adjacent ranges\",\n\t\t\tinput: \"hello world\",\n\t\t\tranges: []Range{\n\t\t\t\tNewRange(0, 5, NewStyle().Bold(true)),\n\t\t\t\tNewRange(6, 11, NewStyle().Italic(true)),\n\t\t\t},\n\t\t\texpected: \"\\x1b[1mhello\\x1b[m \\x1b[3mworld\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tname:  \"wide-width characters\",\n\t\t\tinput: \"Hello 你好 世界\",\n\t\t\tranges: []Range{\n\t\t\t\tNewRange(0, 5, NewStyle().Bold(true)),    // \"Hello\"\n\t\t\t\tNewRange(7, 10, NewStyle().Italic(true)), // \"你好\"\n\t\t\t\tNewRange(11, 50, NewStyle().Bold(true)),  // \"世界\"\n\t\t\t},\n\t\t\texpected: \"\\x1b[1mHello\\x1b[m \\x1b[3m你好\\x1b[m \\x1b[1m世界\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tname:  \"ansi and emoji\",\n\t\t\tinput: \"\\x1b[90m\\ue615\\x1b[39m \\x1b[3mDownloads\",\n\t\t\tranges: []Range{\n\t\t\t\tNewRange(2, 5, NewStyle().Foreground(Color(\"2\"))),\n\t\t\t},\n\t\t\texpected: \"\\x1b[90m\\ue615\\x1b[39m \\x1b[3m\\x1b[32mDow\\x1b[m\\x1b[90m\\x1b[39m\\x1b[3mnloads\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := StyleRanges(tt.input, tt.ranges...)\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"StyleRanges()\\n got = %q\\nwant = %q\\n\", result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "runes.go",
    "content": "package lipgloss\n\nimport (\n\t\"strings\"\n)\n\n// StyleRunes apply a given style to runes at the given indices in the string.\n// Note that you must provide styling options for both matched and unmatched\n// runes. Indices out of bounds will be ignored.\nfunc StyleRunes(str string, indices []int, matched, unmatched Style) string {\n\t// Convert slice of indices to a map for easier lookups\n\tm := make(map[int]struct{})\n\tfor _, i := range indices {\n\t\tm[i] = struct{}{}\n\t}\n\n\tvar (\n\t\tout   strings.Builder\n\t\tgroup strings.Builder\n\t\tstyle Style\n\t\trunes = []rune(str)\n\t)\n\n\tfor i, r := range runes {\n\t\tgroup.WriteRune(r)\n\n\t\t_, matches := m[i]\n\t\t_, nextMatches := m[i+1]\n\n\t\tif matches != nextMatches || i == len(runes)-1 {\n\t\t\t// Flush\n\t\t\tif matches {\n\t\t\t\tstyle = matched\n\t\t\t} else {\n\t\t\t\tstyle = unmatched\n\t\t\t}\n\t\t\tout.WriteString(style.Render(group.String()))\n\t\t\tgroup.Reset()\n\t\t}\n\t}\n\n\treturn out.String()\n}\n"
  },
  {
    "path": "runes_test.go",
    "content": "package lipgloss\n\nimport (\n\t\"testing\"\n)\n\nfunc TestStyleRunes(t *testing.T) {\n\tmatchedStyle := NewStyle().Reverse(true)\n\tunmatchedStyle := NewStyle()\n\n\ttt := []struct {\n\t\tname     string\n\t\tinput    string\n\t\tindices  []int\n\t\texpected string\n\t}{\n\t\t{\n\t\t\t\"hello 0\",\n\t\t\t\"hello\",\n\t\t\t[]int{0},\n\t\t\t\"\\x1b[7mh\\x1b[mello\",\n\t\t},\n\t\t{\n\t\t\t\"你好 1\",\n\t\t\t\"你好\",\n\t\t\t[]int{1},\n\t\t\t\"你\\x1b[7m好\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\t\"hello 你好 6,7\",\n\t\t\t\"hello 你好\",\n\t\t\t[]int{6, 7},\n\t\t\t\"hello \\x1b[7m你好\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\t\"hello 1,3\",\n\t\t\t\"hello\",\n\t\t\t[]int{1, 3},\n\t\t\t\"h\\x1b[7me\\x1b[ml\\x1b[7ml\\x1b[mo\",\n\t\t},\n\t\t{\n\t\t\t\"你好 0,1\",\n\t\t\t\"你好\",\n\t\t\t[]int{0, 1},\n\t\t\t\"\\x1b[7m你好\\x1b[m\",\n\t\t},\n\t}\n\n\tfn := func(str string, indices []int) string {\n\t\treturn StyleRunes(str, indices, matchedStyle, unmatchedStyle)\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := fn(tc.input, tc.indices)\n\t\t\tif res != tc.expected {\n\t\t\t\tt.Errorf(\"Expected:\\n\\n`%q`\\n`%q`\\n\\nActual Output:\\n\\n`%q`\\n`%q`\\n\\n\",\n\t\t\t\t\ttc.expected, tc.expected,\n\t\t\t\t\tres, res)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "set.go",
    "content": "package lipgloss\n\nimport (\n\t\"image/color\"\n\t\"strings\"\n)\n\n// Set a value on the underlying rules map.\nfunc (s *Style) set(key propKey, value any) {\n\t// We don't allow negative integers on any of our other values, so just keep\n\t// them at zero or above. We could use uints instead, but the\n\t// conversions are a little tedious, so we're sticking with ints for\n\t// sake of usability.\n\tswitch key {\n\tcase foregroundKey:\n\t\ts.fgColor = colorOrNil(value)\n\tcase backgroundKey:\n\t\ts.bgColor = colorOrNil(value)\n\tcase underlineColorKey:\n\t\ts.ulColor = colorOrNil(value)\n\tcase underlineKey:\n\t\ts.ul = value.(Underline)\n\tcase widthKey:\n\t\ts.width = max(0, value.(int))\n\tcase heightKey:\n\t\ts.height = max(0, value.(int))\n\tcase alignHorizontalKey:\n\t\ts.alignHorizontal = value.(Position)\n\tcase alignVerticalKey:\n\t\ts.alignVertical = value.(Position)\n\tcase paddingTopKey:\n\t\ts.paddingTop = max(0, value.(int))\n\tcase paddingRightKey:\n\t\ts.paddingRight = max(0, value.(int))\n\tcase paddingBottomKey:\n\t\ts.paddingBottom = max(0, value.(int))\n\tcase paddingLeftKey:\n\t\ts.paddingLeft = max(0, value.(int))\n\tcase paddingCharKey:\n\t\ts.paddingChar = value.(rune)\n\tcase marginTopKey:\n\t\ts.marginTop = max(0, value.(int))\n\tcase marginRightKey:\n\t\ts.marginRight = max(0, value.(int))\n\tcase marginBottomKey:\n\t\ts.marginBottom = max(0, value.(int))\n\tcase marginLeftKey:\n\t\ts.marginLeft = max(0, value.(int))\n\tcase marginBackgroundKey:\n\t\ts.marginBgColor = colorOrNil(value)\n\tcase marginCharKey:\n\t\ts.marginChar = value.(rune)\n\tcase borderStyleKey:\n\t\ts.borderStyle = value.(Border)\n\tcase borderTopForegroundKey:\n\t\ts.borderTopFgColor = colorOrNil(value)\n\tcase borderRightForegroundKey:\n\t\ts.borderRightFgColor = colorOrNil(value)\n\tcase borderBottomForegroundKey:\n\t\ts.borderBottomFgColor = colorOrNil(value)\n\tcase borderLeftForegroundKey:\n\t\ts.borderLeftFgColor = colorOrNil(value)\n\tcase borderForegroundBlendKey:\n\t\ts.borderBlendFgColor = value.([]color.Color)\n\tcase borderForegroundBlendOffsetKey:\n\t\ts.borderForegroundBlendOffset = value.(int)\n\tcase borderTopBackgroundKey:\n\t\ts.borderTopBgColor = colorOrNil(value)\n\tcase borderRightBackgroundKey:\n\t\ts.borderRightBgColor = colorOrNil(value)\n\tcase borderBottomBackgroundKey:\n\t\ts.borderBottomBgColor = colorOrNil(value)\n\tcase borderLeftBackgroundKey:\n\t\ts.borderLeftBgColor = colorOrNil(value)\n\tcase maxWidthKey:\n\t\ts.maxWidth = max(0, value.(int))\n\tcase maxHeightKey:\n\t\ts.maxHeight = max(0, value.(int))\n\tcase tabWidthKey:\n\t\t// TabWidth is the only property that may have a negative value (and\n\t\t// that negative value can be no less than -1).\n\t\ts.tabWidth = value.(int)\n\tcase transformKey:\n\t\ts.transform = value.(func(string) string)\n\tcase linkKey:\n\t\ts.link = value.(string)\n\tcase linkParamsKey:\n\t\ts.linkParams = value.(string)\n\tdefault:\n\t\tif v, ok := value.(bool); ok { //nolint:nestif\n\t\t\tif v {\n\t\t\t\ts.attrs |= int(key)\n\t\t\t} else {\n\t\t\t\ts.attrs &^= int(key)\n\t\t\t}\n\t\t} else if attrs, ok := value.(int); ok {\n\t\t\t// bool attrs\n\t\t\tif attrs&int(key) != 0 {\n\t\t\t\ts.attrs |= int(key)\n\t\t\t} else {\n\t\t\t\ts.attrs &^= int(key)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the prop on\n\ts.props = s.props.set(key)\n}\n\n// setFrom sets the property from another style.\nfunc (s *Style) setFrom(key propKey, i Style) {\n\tswitch key {\n\tcase foregroundKey:\n\t\ts.set(foregroundKey, i.fgColor)\n\tcase backgroundKey:\n\t\ts.set(backgroundKey, i.bgColor)\n\tcase underlineColorKey:\n\t\ts.set(underlineColorKey, i.ulColor)\n\tcase underlineKey:\n\t\ts.set(underlineKey, i.ul)\n\tcase widthKey:\n\t\ts.set(widthKey, i.width)\n\tcase heightKey:\n\t\ts.set(heightKey, i.height)\n\tcase alignHorizontalKey:\n\t\ts.set(alignHorizontalKey, i.alignHorizontal)\n\tcase alignVerticalKey:\n\t\ts.set(alignVerticalKey, i.alignVertical)\n\tcase paddingTopKey:\n\t\ts.set(paddingTopKey, i.paddingTop)\n\tcase paddingRightKey:\n\t\ts.set(paddingRightKey, i.paddingRight)\n\tcase paddingBottomKey:\n\t\ts.set(paddingBottomKey, i.paddingBottom)\n\tcase paddingLeftKey:\n\t\ts.set(paddingLeftKey, i.paddingLeft)\n\tcase paddingCharKey:\n\t\ts.set(paddingCharKey, i.paddingChar)\n\tcase marginTopKey:\n\t\ts.set(marginTopKey, i.marginTop)\n\tcase marginRightKey:\n\t\ts.set(marginRightKey, i.marginRight)\n\tcase marginBottomKey:\n\t\ts.set(marginBottomKey, i.marginBottom)\n\tcase marginLeftKey:\n\t\ts.set(marginLeftKey, i.marginLeft)\n\tcase marginBackgroundKey:\n\t\ts.set(marginBackgroundKey, i.marginBgColor)\n\tcase marginCharKey:\n\t\ts.set(marginCharKey, i.marginChar)\n\tcase borderStyleKey:\n\t\ts.set(borderStyleKey, i.borderStyle)\n\tcase borderTopForegroundKey:\n\t\ts.set(borderTopForegroundKey, i.borderTopFgColor)\n\tcase borderRightForegroundKey:\n\t\ts.set(borderRightForegroundKey, i.borderRightFgColor)\n\tcase borderBottomForegroundKey:\n\t\ts.set(borderBottomForegroundKey, i.borderBottomFgColor)\n\tcase borderLeftForegroundKey:\n\t\ts.set(borderLeftForegroundKey, i.borderLeftFgColor)\n\tcase borderForegroundBlendKey:\n\t\ts.set(borderForegroundBlendKey, i.borderBlendFgColor)\n\tcase borderForegroundBlendOffsetKey:\n\t\ts.set(borderForegroundBlendOffsetKey, i.borderForegroundBlendOffset)\n\tcase borderTopBackgroundKey:\n\t\ts.set(borderTopBackgroundKey, i.borderTopBgColor)\n\tcase borderRightBackgroundKey:\n\t\ts.set(borderRightBackgroundKey, i.borderRightBgColor)\n\tcase borderBottomBackgroundKey:\n\t\ts.set(borderBottomBackgroundKey, i.borderBottomBgColor)\n\tcase borderLeftBackgroundKey:\n\t\ts.set(borderLeftBackgroundKey, i.borderLeftBgColor)\n\tcase maxWidthKey:\n\t\ts.set(maxWidthKey, i.maxWidth)\n\tcase maxHeightKey:\n\t\ts.set(maxHeightKey, i.maxHeight)\n\tcase tabWidthKey:\n\t\ts.set(tabWidthKey, i.tabWidth)\n\tcase transformKey:\n\t\ts.set(transformKey, i.transform)\n\tdefault:\n\t\t// Set attributes for set bool properties\n\t\ts.set(key, i.attrs)\n\t}\n}\n\nfunc colorOrNil(c any) color.Color {\n\tif c, ok := c.(color.Color); ok {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// Bold sets a bold formatting rule.\nfunc (s Style) Bold(v bool) Style {\n\ts.set(boldKey, v)\n\treturn s\n}\n\n// Italic sets an italic formatting rule. In some terminal emulators this will\n// render with \"reverse\" coloring if not italic font variant is available.\nfunc (s Style) Italic(v bool) Style {\n\ts.set(italicKey, v)\n\treturn s\n}\n\n// Underline sets an underline rule. By default, underlines will not be drawn on\n// whitespace like margins and padding. To change this behavior set\n// [Style.UnderlineSpaces].\nfunc (s Style) Underline(v bool) Style {\n\tif v {\n\t\treturn s.UnderlineStyle(UnderlineSingle)\n\t}\n\treturn s.UnderlineStyle(UnderlineNone)\n}\n\n// UnderlineStyle sets the underline style. This can be used to set the underline\n// to be a single, double, curly, dotted, or dashed line.\n//\n// Note that not all terminal emulators support underline styles. If a style is\n// not supported, it will typically fall back to a single underline but this is\n// not guaranteed. This depends on the terminal emulator being used.\nfunc (s Style) UnderlineStyle(u Underline) Style {\n\ts.set(underlineKey, u)\n\treturn s\n}\n\n// UnderlineColor sets the color of the underline. By default, the underline\n// will be the same color as the foreground.\n//\n// Note that not all terminal emulators support colored underlines. If color is\n// not supported, it might produce unexpected results. This depends on the\n// terminal emulator being used.\nfunc (s Style) UnderlineColor(c color.Color) Style {\n\ts.set(underlineColorKey, c)\n\treturn s\n}\n\n// Strikethrough sets a strikethrough rule. By default, strikes will not be\n// drawn on whitespace like margins and padding. To change this behavior set\n// StrikethroughSpaces.\nfunc (s Style) Strikethrough(v bool) Style {\n\ts.set(strikethroughKey, v)\n\treturn s\n}\n\n// Reverse sets a rule for inverting foreground and background colors.\nfunc (s Style) Reverse(v bool) Style {\n\ts.set(reverseKey, v)\n\treturn s\n}\n\n// Blink sets a rule for blinking foreground text.\nfunc (s Style) Blink(v bool) Style {\n\ts.set(blinkKey, v)\n\treturn s\n}\n\n// Faint sets a rule for rendering the foreground color in a dimmer shade.\nfunc (s Style) Faint(v bool) Style {\n\ts.set(faintKey, v)\n\treturn s\n}\n\n// Foreground sets a foreground color.\n//\n//\t// Sets the foreground to blue\n//\ts := lipgloss.NewStyle().Foreground(lipgloss.Color(\"#0000ff\"))\n//\n//\t// Removes the foreground color\n//\ts.Foreground(lipgloss.NoColor)\nfunc (s Style) Foreground(c color.Color) Style {\n\ts.set(foregroundKey, c)\n\treturn s\n}\n\n// Background sets a background color.\nfunc (s Style) Background(c color.Color) Style {\n\ts.set(backgroundKey, c)\n\treturn s\n}\n\n// Width sets the width of the block before applying margins. This means your\n// styled content will exactly equal the size set here. Text will wrap based on\n// Padding and Borders set on the style.\nfunc (s Style) Width(i int) Style {\n\ts.set(widthKey, i)\n\treturn s\n}\n\n// Height sets the height of the block before applying margins. If the height of\n// the text block is less than this value after applying padding (or not), the\n// block will be set to this height.\nfunc (s Style) Height(i int) Style {\n\ts.set(heightKey, i)\n\treturn s\n}\n\n// Align is a shorthand method for setting horizontal and vertical alignment.\n//\n// With one argument, the position value is applied to the horizontal alignment.\n//\n// With two arguments, the value is applied to the horizontal and vertical\n// alignments, in that order.\nfunc (s Style) Align(p ...Position) Style {\n\tif len(p) > 0 {\n\t\ts.set(alignHorizontalKey, p[0])\n\t}\n\tif len(p) > 1 {\n\t\ts.set(alignVerticalKey, p[1])\n\t}\n\treturn s\n}\n\n// AlignHorizontal sets a horizontal text alignment rule.\nfunc (s Style) AlignHorizontal(p Position) Style {\n\ts.set(alignHorizontalKey, p)\n\treturn s\n}\n\n// AlignVertical sets a vertical text alignment rule.\nfunc (s Style) AlignVertical(p Position) Style {\n\ts.set(alignVerticalKey, p)\n\treturn s\n}\n\n// Padding is a shorthand method for setting padding on all sides at once.\n//\n// With one argument, the value is applied to all sides.\n//\n// With two arguments, the value is applied to the vertical and horizontal\n// sides, in that order.\n//\n// With three arguments, the value is applied to the top side, the horizontal\n// sides, and the bottom side, in that order.\n//\n// With four arguments, the value is applied clockwise starting from the top\n// side, followed by the right side, then the bottom, and finally the left.\n//\n// With more than four arguments no padding will be added.\nfunc (s Style) Padding(i ...int) Style {\n\ttop, right, bottom, left, ok := whichSidesInt(i...)\n\tif !ok {\n\t\treturn s\n\t}\n\n\ts.set(paddingTopKey, top)\n\ts.set(paddingRightKey, right)\n\ts.set(paddingBottomKey, bottom)\n\ts.set(paddingLeftKey, left)\n\treturn s\n}\n\n// PaddingLeft adds padding on the left.\nfunc (s Style) PaddingLeft(i int) Style {\n\ts.set(paddingLeftKey, i)\n\treturn s\n}\n\n// PaddingRight adds padding on the right.\nfunc (s Style) PaddingRight(i int) Style {\n\ts.set(paddingRightKey, i)\n\treturn s\n}\n\n// PaddingTop adds padding to the top of the block.\nfunc (s Style) PaddingTop(i int) Style {\n\ts.set(paddingTopKey, i)\n\treturn s\n}\n\n// PaddingBottom adds padding to the bottom of the block.\nfunc (s Style) PaddingBottom(i int) Style {\n\ts.set(paddingBottomKey, i)\n\treturn s\n}\n\n// PaddingChar sets the character used for padding. This is useful for\n// rendering blocks with a specific character, such as a space or a dot.\n// Example of using [NBSP] as padding to prevent line breaks:\n//\n//\t```go\n//\ts := lipgloss.NewStyle().PaddingChar(lipgloss.NBSP)\n//\t```\nfunc (s Style) PaddingChar(r rune) Style {\n\ts.set(paddingCharKey, r)\n\treturn s\n}\n\n// ColorWhitespace determines whether or not the background color should be\n// applied to the padding. This is true by default as it's more than likely the\n// desired and expected behavior, but it can be disabled for certain graphic\n// effects.\n//\n// Deprecated: Just use margins and padding.\nfunc (s Style) ColorWhitespace(v bool) Style {\n\ts.set(colorWhitespaceKey, v)\n\treturn s\n}\n\n// Margin is a shorthand method for setting margins on all sides at once.\n//\n// With one argument, the value is applied to all sides.\n//\n// With two arguments, the value is applied to the vertical and horizontal\n// sides, in that order.\n//\n// With three arguments, the value is applied to the top side, the horizontal\n// sides, and the bottom side, in that order.\n//\n// With four arguments, the value is applied clockwise starting from the top\n// side, followed by the right side, then the bottom, and finally the left.\n//\n// With more than four arguments no margin will be added.\nfunc (s Style) Margin(i ...int) Style {\n\ttop, right, bottom, left, ok := whichSidesInt(i...)\n\tif !ok {\n\t\treturn s\n\t}\n\n\ts.set(marginTopKey, top)\n\ts.set(marginRightKey, right)\n\ts.set(marginBottomKey, bottom)\n\ts.set(marginLeftKey, left)\n\treturn s\n}\n\n// MarginLeft sets the value of the left margin.\nfunc (s Style) MarginLeft(i int) Style {\n\ts.set(marginLeftKey, i)\n\treturn s\n}\n\n// MarginRight sets the value of the right margin.\nfunc (s Style) MarginRight(i int) Style {\n\ts.set(marginRightKey, i)\n\treturn s\n}\n\n// MarginTop sets the value of the top margin.\nfunc (s Style) MarginTop(i int) Style {\n\ts.set(marginTopKey, i)\n\treturn s\n}\n\n// MarginBottom sets the value of the bottom margin.\nfunc (s Style) MarginBottom(i int) Style {\n\ts.set(marginBottomKey, i)\n\treturn s\n}\n\n// MarginBackground sets the background color of the margin. Note that this is\n// also set when inheriting from a style with a background color. In that case\n// the background color on that style will set the margin color on this style.\nfunc (s Style) MarginBackground(c color.Color) Style {\n\ts.set(marginBackgroundKey, c)\n\treturn s\n}\n\n// MarginChar sets the character used for the margin. This is useful for\n// rendering blocks with a specific character, such as a space or a dot.\nfunc (s Style) MarginChar(r rune) Style {\n\ts.set(marginCharKey, r)\n\treturn s\n}\n\n// Border is shorthand for setting the border style and which sides should\n// have a border at once. The variadic argument sides works as follows:\n//\n// With one value, the value is applied to all sides.\n//\n// With two values, the values are applied to the vertical and horizontal\n// sides, in that order.\n//\n// With three values, the values are applied to the top side, the horizontal\n// sides, and the bottom side, in that order.\n//\n// With four values, the values are applied clockwise starting from the top\n// side, followed by the right side, then the bottom, and finally the left.\n//\n// With more than four arguments the border will be applied to all sides.\n//\n// Examples:\n//\n//\t// Applies borders to the top and bottom only\n//\tlipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false)\n//\n//\t// Applies rounded borders to the right and bottom only\n//\tlipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false)\nfunc (s Style) Border(b Border, sides ...bool) Style {\n\ts.set(borderStyleKey, b)\n\n\ttop, right, bottom, left, ok := whichSidesBool(sides...)\n\tif !ok {\n\t\ttop = true\n\t\tright = true\n\t\tbottom = true\n\t\tleft = true\n\t}\n\n\ts.set(borderTopKey, top)\n\ts.set(borderRightKey, right)\n\ts.set(borderBottomKey, bottom)\n\ts.set(borderLeftKey, left)\n\n\treturn s\n}\n\n// BorderStyle defines the Border on a style. A Border contains a series of\n// definitions for the sides and corners of a border.\n//\n// Note that if border visibility has not been set for any sides when setting\n// the border style, the border will be enabled for all sides during rendering.\n//\n// You can define border characters as you'd like, though several default\n// styles are included: NormalBorder(), RoundedBorder(), BlockBorder(),\n// OuterHalfBlockBorder(), InnerHalfBlockBorder(), ThickBorder(),\n// and DoubleBorder().\n//\n// Example:\n//\n//\tlipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder())\nfunc (s Style) BorderStyle(b Border) Style {\n\ts.set(borderStyleKey, b)\n\treturn s\n}\n\n// BorderTop determines whether or not to draw a top border.\nfunc (s Style) BorderTop(v bool) Style {\n\ts.set(borderTopKey, v)\n\treturn s\n}\n\n// BorderRight determines whether or not to draw a right border.\nfunc (s Style) BorderRight(v bool) Style {\n\ts.set(borderRightKey, v)\n\treturn s\n}\n\n// BorderBottom determines whether or not to draw a bottom border.\nfunc (s Style) BorderBottom(v bool) Style {\n\ts.set(borderBottomKey, v)\n\treturn s\n}\n\n// BorderLeft determines whether or not to draw a left border.\nfunc (s Style) BorderLeft(v bool) Style {\n\ts.set(borderLeftKey, v)\n\treturn s\n}\n\n// BorderForeground is a shorthand function for setting all of the\n// foreground colors of the borders at once. The arguments work as follows:\n//\n// With one argument, the argument is applied to all sides.\n//\n// With two arguments, the arguments are applied to the vertical and horizontal\n// sides, in that order.\n//\n// With three arguments, the arguments are applied to the top side, the\n// horizontal sides, and the bottom side, in that order.\n//\n// With four arguments, the arguments are applied clockwise starting from the\n// top side, followed by the right side, then the bottom, and finally the left.\n//\n// With more than four arguments nothing will be set.\nfunc (s Style) BorderForeground(c ...color.Color) Style {\n\tif len(c) == 0 {\n\t\treturn s\n\t}\n\n\ttop, right, bottom, left, ok := whichSidesColor(c...)\n\tif !ok {\n\t\treturn s\n\t}\n\n\ts.set(borderTopForegroundKey, top)\n\ts.set(borderRightForegroundKey, right)\n\ts.set(borderBottomForegroundKey, bottom)\n\ts.set(borderLeftForegroundKey, left)\n\n\treturn s\n}\n\n// BorderTopForeground set the foreground color for the top of the border.\nfunc (s Style) BorderTopForeground(c color.Color) Style {\n\ts.set(borderTopForegroundKey, c)\n\treturn s\n}\n\n// BorderRightForeground sets the foreground color for the right side of the\n// border.\nfunc (s Style) BorderRightForeground(c color.Color) Style {\n\ts.set(borderRightForegroundKey, c)\n\treturn s\n}\n\n// BorderBottomForeground sets the foreground color for the bottom of the\n// border.\nfunc (s Style) BorderBottomForeground(c color.Color) Style {\n\ts.set(borderBottomForegroundKey, c)\n\treturn s\n}\n\n// BorderLeftForeground sets the foreground color for the left side of the\n// border.\nfunc (s Style) BorderLeftForeground(c color.Color) Style {\n\ts.set(borderLeftForegroundKey, c)\n\treturn s\n}\n\n// BorderForegroundBlend sets the foreground colors for the border blend. At least\n// 2 colors are required to use blending, otherwise this will no-op with 0 colors,\n// and pass to BorderForeground with 1 color. This will override all other border\n// foreground colors when used.\n//\n// When providing colors, in most cases (e.g. when all border sides are enabled),\n// you will want to provide a wrapping-set of colors, so the start and end color\n// are either the same, or very similar. For example:\n//\n//\tlipgloss.NewStyle().BorderForegroundBlend(\n//\t\tlipgloss.Color(\"#00FA68\"),\n//\t\tlipgloss.Color(\"#9900FF\"),\n//\t\tlipgloss.Color(\"#ED5353\"),\n//\t\tlipgloss.Color(\"#9900FF\"),\n//\t\tlipgloss.Color(\"#00FA68\"),\n//\t)\nfunc (s Style) BorderForegroundBlend(c ...color.Color) Style {\n\tif len(c) == 0 {\n\t\treturn s\n\t}\n\n\t// Insufficient colors to use blending, pass to BorderForeground.\n\tif len(c) == 1 {\n\t\treturn s.BorderForeground(c...)\n\t}\n\n\ts.set(borderForegroundBlendKey, c)\n\treturn s\n}\n\n// BorderForegroundBlendOffset sets the border blend offset cells, starting from\n// the top left corner. Value can be positive or negative, and does not need to\n// equal the dimensions of the border region. Direction (when positive) is as\n// follows (\"o\" is starting point):\n//\n//\t  o -------->\n//\t  ┌──────────┐\n//\t^ │          │ |\n//\t| │          │ |\n//\t| │          │ |\n//\t| │          │ v\n//\t  └──────────┘\n//\t   <---------\nfunc (s Style) BorderForegroundBlendOffset(v int) Style {\n\ts.set(borderForegroundBlendOffsetKey, v)\n\treturn s\n}\n\n// BorderBackground is a shorthand function for setting all of the\n// background colors of the borders at once. The arguments work as follows:\n//\n// With one argument, the argument is applied to all sides.\n//\n// With two arguments, the arguments are applied to the vertical and horizontal\n// sides, in that order.\n//\n// With three arguments, the arguments are applied to the top side, the\n// horizontal sides, and the bottom side, in that order.\n//\n// With four arguments, the arguments are applied clockwise starting from the\n// top side, followed by the right side, then the bottom, and finally the left.\n//\n// With more than four arguments nothing will be set.\nfunc (s Style) BorderBackground(c ...color.Color) Style {\n\tif len(c) == 0 {\n\t\treturn s\n\t}\n\n\ttop, right, bottom, left, ok := whichSidesColor(c...)\n\tif !ok {\n\t\treturn s\n\t}\n\n\ts.set(borderTopBackgroundKey, top)\n\ts.set(borderRightBackgroundKey, right)\n\ts.set(borderBottomBackgroundKey, bottom)\n\ts.set(borderLeftBackgroundKey, left)\n\n\treturn s\n}\n\n// BorderTopBackground sets the background color of the top of the border.\nfunc (s Style) BorderTopBackground(c color.Color) Style {\n\ts.set(borderTopBackgroundKey, c)\n\treturn s\n}\n\n// BorderRightBackground sets the background color of right side the border.\nfunc (s Style) BorderRightBackground(c color.Color) Style {\n\ts.set(borderRightBackgroundKey, c)\n\treturn s\n}\n\n// BorderBottomBackground sets the background color of the bottom of the\n// border.\nfunc (s Style) BorderBottomBackground(c color.Color) Style {\n\ts.set(borderBottomBackgroundKey, c)\n\treturn s\n}\n\n// BorderLeftBackground set the background color of the left side of the\n// border.\nfunc (s Style) BorderLeftBackground(c color.Color) Style {\n\ts.set(borderLeftBackgroundKey, c)\n\treturn s\n}\n\n// Inline makes rendering output one line and disables the rendering of\n// margins, padding and borders. This is useful when you need a style to apply\n// only to font rendering and don't want it to change any physical dimensions.\n// It works well with Style.MaxWidth.\n//\n// Because this in intended to be used at the time of render, this method will\n// not mutate the style and instead return a copy.\n//\n// Example:\n//\n//\tvar userInput string = \"...\"\n//\tvar userStyle = text.Style{ /* ... */ }\n//\tfmt.Println(userStyle.Inline(true).Render(userInput))\nfunc (s Style) Inline(v bool) Style {\n\to := s // copy\n\to.set(inlineKey, v)\n\treturn o\n}\n\n// MaxWidth applies a max width to a given style. This is useful in enforcing\n// a certain width at render time, particularly with arbitrary strings and\n// styles.\n//\n// Because this in intended to be used at the time of render, this method will\n// not mutate the style and instead return a copy.\n//\n// Example:\n//\n//\tvar userInput string = \"...\"\n//\tvar userStyle = text.Style{ /* ... */ }\n//\tfmt.Println(userStyle.MaxWidth(16).Render(userInput))\nfunc (s Style) MaxWidth(n int) Style {\n\to := s // copy\n\to.set(maxWidthKey, n)\n\treturn o\n}\n\n// MaxHeight applies a max height to a given style. This is useful in enforcing\n// a certain height at render time, particularly with arbitrary strings and\n// styles.\n//\n// Because this in intended to be used at the time of render, this method will\n// not mutate the style and instead returns a copy.\nfunc (s Style) MaxHeight(n int) Style {\n\to := s // copy\n\to.set(maxHeightKey, n)\n\treturn o\n}\n\n// NoTabConversion can be passed to [Style.TabWidth] to disable the replacement\n// of tabs with spaces at render time.\nconst NoTabConversion = -1\n\n// TabWidth sets the number of spaces that a tab (/t) should be rendered as.\n// When set to 0, tabs will be removed. To disable the replacement of tabs with\n// spaces entirely, set this to [NoTabConversion].\n//\n// By default, tabs will be replaced with 4 spaces.\nfunc (s Style) TabWidth(n int) Style {\n\tif n <= -1 {\n\t\tn = -1\n\t}\n\ts.set(tabWidthKey, n)\n\treturn s\n}\n\n// UnderlineSpaces determines whether to underline spaces between words. By\n// default, this is true. Spaces can also be underlined without underlining the\n// text itself.\nfunc (s Style) UnderlineSpaces(v bool) Style {\n\ts.set(underlineSpacesKey, v)\n\treturn s\n}\n\n// StrikethroughSpaces determines whether to apply strikethroughs to spaces\n// between words. By default, this is true. Spaces can also be struck without\n// underlining the text itself.\nfunc (s Style) StrikethroughSpaces(v bool) Style {\n\ts.set(strikethroughSpacesKey, v)\n\treturn s\n}\n\n// Transform applies a given function to a string at render time, allowing for\n// the string being rendered to be manipuated.\n//\n// Example:\n//\n//\ts := NewStyle().Transform(strings.ToUpper)\n//\tfmt.Println(s.Render(\"raow!\") // \"RAOW!\"\nfunc (s Style) Transform(fn func(string) string) Style {\n\ts.set(transformKey, fn)\n\treturn s\n}\n\n// Hyperlink sets a hyperlink on a style. This is useful for rendering text that\n// can be clicked on in a terminal emulator that supports hyperlinks.\n//\n// Example:\n//\n//\ts := lipgloss.NewStyle().Hyperlink(\"https://charm.sh\")\n//\ts := lipgloss.NewStyle().Hyperlink(\"https://charm.sh\", \"id=1\")\nfunc (s Style) Hyperlink(link string, params ...string) Style {\n\ts.set(linkKey, link)\n\tif len(params) > 0 {\n\t\ts.set(linkParamsKey, strings.Join(params, \":\"))\n\t}\n\treturn s\n}\n\n// whichSidesInt is a helper method for setting values on sides of a block based\n// on the number of arguments. It follows the CSS shorthand rules for blocks\n// like margin, padding. and borders. Here are how the rules work:\n//\n// 0 args:  do nothing\n// 1 arg:   all sides\n// 2 args:  top -> bottom\n// 3 args:  top -> horizontal -> bottom\n// 4 args:  top -> right -> bottom -> left\n// 5+ args: do nothing.\nfunc whichSidesInt(i ...int) (top, right, bottom, left int, ok bool) {\n\tswitch len(i) {\n\tcase 1:\n\t\ttop = i[0]\n\t\tbottom = i[0]\n\t\tleft = i[0]\n\t\tright = i[0]\n\t\tok = true\n\tcase 2: //nolint:mnd\n\t\ttop = i[0]\n\t\tbottom = i[0]\n\t\tleft = i[1]\n\t\tright = i[1]\n\t\tok = true\n\tcase 3: //nolint:mnd\n\t\ttop = i[0]\n\t\tleft = i[1]\n\t\tright = i[1]\n\t\tbottom = i[2]\n\t\tok = true\n\tcase 4: //nolint:mnd\n\t\ttop = i[0]\n\t\tright = i[1]\n\t\tbottom = i[2]\n\t\tleft = i[3]\n\t\tok = true\n\t}\n\treturn top, right, bottom, left, ok\n}\n\n// whichSidesBool is like whichSidesInt, except it operates on a series of\n// boolean values. See the comment on whichSidesInt for details on how this\n// works.\nfunc whichSidesBool(i ...bool) (top, right, bottom, left bool, ok bool) {\n\tswitch len(i) {\n\tcase 1:\n\t\ttop = i[0]\n\t\tbottom = i[0]\n\t\tleft = i[0]\n\t\tright = i[0]\n\t\tok = true\n\tcase 2: //nolint:mnd\n\t\ttop = i[0]\n\t\tbottom = i[0]\n\t\tleft = i[1]\n\t\tright = i[1]\n\t\tok = true\n\tcase 3: //nolint:mnd\n\t\ttop = i[0]\n\t\tleft = i[1]\n\t\tright = i[1]\n\t\tbottom = i[2]\n\t\tok = true\n\tcase 4: //nolint:mnd\n\t\ttop = i[0]\n\t\tright = i[1]\n\t\tbottom = i[2]\n\t\tleft = i[3]\n\t\tok = true\n\t}\n\treturn top, right, bottom, left, ok\n}\n\n// whichSidesColor is like whichSides, except it operates on a series of\n// boolean values. See the comment on whichSidesInt for details on how this\n// works.\nfunc whichSidesColor(i ...color.Color) (top, right, bottom, left color.Color, ok bool) {\n\tswitch len(i) {\n\tcase 1:\n\t\ttop = i[0]\n\t\tbottom = i[0]\n\t\tleft = i[0]\n\t\tright = i[0]\n\t\tok = true\n\tcase 2: //nolint:mnd\n\t\ttop = i[0]\n\t\tbottom = i[0]\n\t\tleft = i[1]\n\t\tright = i[1]\n\t\tok = true\n\tcase 3: //nolint:mnd\n\t\ttop = i[0]\n\t\tleft = i[1]\n\t\tright = i[1]\n\t\tbottom = i[2]\n\t\tok = true\n\tcase 4: //nolint:mnd\n\t\ttop = i[0]\n\t\tright = i[1]\n\t\tbottom = i[2]\n\t\tleft = i[3]\n\t\tok = true\n\t}\n\treturn top, right, bottom, left, ok\n}\n"
  },
  {
    "path": "size.go",
    "content": "package lipgloss\n\nimport (\n\t\"strings\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// Width returns the cell width of characters in the string. ANSI sequences are\n// ignored and characters wider than one cell (such as Chinese characters and\n// emojis) are appropriately measured.\n//\n// You should use this instead of len(string) or len([]rune(string) as neither\n// will give you accurate results.\nfunc Width(str string) (width int) {\n\tfor l := range strings.SplitSeq(str, \"\\n\") {\n\t\tw := ansi.StringWidth(l)\n\t\tif w > width {\n\t\t\twidth = w\n\t\t}\n\t}\n\n\treturn width\n}\n\n// Height returns height of a string in cells. This is done simply by\n// counting \\n characters. If your output has \\r\\n, that sequence will be\n// replaced with a \\n in [Style.Render].\nfunc Height(str string) int {\n\treturn strings.Count(str, \"\\n\") + 1\n}\n\n// Size returns the width and height of the string in cells. ANSI sequences are\n// ignored and characters wider than one cell (such as Chinese characters and\n// emojis) are appropriately measured.\nfunc Size(str string) (width, height int) {\n\twidth = Width(str)\n\theight = Height(str)\n\treturn width, height\n}\n"
  },
  {
    "path": "size_test.go",
    "content": "package lipgloss\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc BenchmarkWidthSimple(b *testing.B) {\n\tsimpleStrings := []string{\n\t\t\"ab\",\n\t\t\"abcdef\",\n\t\t\"abcdefghij\",\n\t}\n\n\tfor _, str := range simpleStrings {\n\t\tb.Run(\"len-\"+str, func(b *testing.B) {\n\t\t\tfor b.Loop() {\n\t\t\t\t_ = Width(str)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkWidthMultiLine(b *testing.B) {\n\tmultiLineStrings := []struct {\n\t\tname string\n\t\tstr  string\n\t}{\n\t\t{\"2-lines\", \"Line 1\\nLine 2\"},\n\t\t{\"10-lines\", \"Line 1\\nLine 2\\nLine 3\\nLine 4\\nLine 5\\nLine 6\\nLine 7\\nLine 8\\nLine 9\\nLine 10\"},\n\t\t{\"50-lines\", strings.Repeat(\"Line\\n\", 49) + \"Line\"},\n\t}\n\n\tfor _, tc := range multiLineStrings {\n\t\tb.Run(tc.name, func(b *testing.B) {\n\t\t\tfor b.Loop() {\n\t\t\t\t_ = Width(tc.str)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "style.go",
    "content": "package lipgloss\n\nimport (\n\t\"image/color\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\nconst (\n\t// NBSP is the non-breaking space rune.\n\tNBSP            = '\\u00A0'\n\ttabWidthDefault = 4\n)\n\n// Property for a key.\ntype propKey int64\n\n// Available properties.\nconst (\n\t// Boolean props come first.\n\tboldKey propKey = 1 << iota\n\titalicKey\n\tstrikethroughKey\n\treverseKey\n\tblinkKey\n\tfaintKey\n\tunderlineSpacesKey\n\tstrikethroughSpacesKey\n\tcolorWhitespaceKey\n\n\t// Non-boolean props.\n\tunderlineKey\n\tforegroundKey\n\tbackgroundKey\n\tunderlineColorKey\n\twidthKey\n\theightKey\n\talignHorizontalKey\n\talignVerticalKey\n\n\t// Padding.\n\tpaddingTopKey\n\tpaddingRightKey\n\tpaddingBottomKey\n\tpaddingLeftKey\n\tpaddingCharKey\n\n\t// Margins.\n\tmarginTopKey\n\tmarginRightKey\n\tmarginBottomKey\n\tmarginLeftKey\n\tmarginBackgroundKey\n\tmarginCharKey\n\n\t// Border runes.\n\tborderStyleKey\n\n\t// Border edges.\n\tborderTopKey\n\tborderRightKey\n\tborderBottomKey\n\tborderLeftKey\n\n\t// Border foreground colors.\n\tborderTopForegroundKey\n\tborderRightForegroundKey\n\tborderBottomForegroundKey\n\tborderLeftForegroundKey\n\tborderForegroundBlendKey\n\tborderForegroundBlendOffsetKey\n\n\t// Border background colors.\n\tborderTopBackgroundKey\n\tborderRightBackgroundKey\n\tborderBottomBackgroundKey\n\tborderLeftBackgroundKey\n\n\tinlineKey\n\tmaxWidthKey\n\tmaxHeightKey\n\ttabWidthKey\n\n\ttransformKey\n\n\t// Hyperlink.\n\tlinkKey\n\tlinkParamsKey\n)\n\n// props is a set of properties.\ntype props int64\n\n// set sets a property.\nfunc (p props) set(k propKey) props {\n\treturn p | props(k)\n}\n\n// unset unsets a property.\nfunc (p props) unset(k propKey) props {\n\treturn p &^ props(k)\n}\n\n// has checks if a property is set.\nfunc (p props) has(k propKey) bool {\n\treturn p&props(k) != 0\n}\n\n// Underline is the style of the underline.\n//\n// Caveats:\n// - Not all terminals support all underline styles.\n// - Some terminals may render unsupported styles as standard underlines.\n// - Terminal themes may affect the visibility of different underline styles.\ntype Underline = ansi.Underline\n\nconst (\n\t// UnderlineNone is no underline.\n\tUnderlineNone = ansi.UnderlineNone\n\t// UnderlineSingle is a single underline. This is the default when underline is enabled.\n\tUnderlineSingle = ansi.UnderlineSingle\n\t// UnderlineDouble is a double underline.\n\tUnderlineDouble = ansi.UnderlineDouble\n\t// UnderlineCurly is a curly underline.\n\tUnderlineCurly = ansi.UnderlineCurly\n\t// UnderlineDotted is a dotted underline.\n\tUnderlineDotted = ansi.UnderlineDotted\n\t// UnderlineDashed is a dashed underline.\n\tUnderlineDashed = ansi.UnderlineDashed\n)\n\n// NewStyle returns a new, empty Style. While it's syntactic sugar for the\n// [Style]{} primitive, it's recommended to use this function for creating styles\n// in case the underlying implementation changes.\nfunc NewStyle() Style {\n\treturn Style{}\n}\n\n// Style contains a set of rules that comprise a style as a whole.\ntype Style struct {\n\tprops props\n\tvalue string\n\n\t// hyperlink\n\tlink, linkParams string\n\n\t// we store bool props values here\n\tattrs int\n\n\t// props that have values\n\tfgColor color.Color\n\tbgColor color.Color\n\tulColor color.Color\n\n\tul Underline\n\n\twidth  int\n\theight int\n\n\talignHorizontal Position\n\talignVertical   Position\n\n\tpaddingTop    int\n\tpaddingRight  int\n\tpaddingBottom int\n\tpaddingLeft   int\n\tpaddingChar   rune\n\n\tmarginTop     int\n\tmarginRight   int\n\tmarginBottom  int\n\tmarginLeft    int\n\tmarginBgColor color.Color\n\tmarginChar    rune\n\n\tborderStyle                 Border\n\tborderTopFgColor            color.Color\n\tborderRightFgColor          color.Color\n\tborderBottomFgColor         color.Color\n\tborderLeftFgColor           color.Color\n\tborderBlendFgColor          []color.Color\n\tborderForegroundBlendOffset int\n\tborderTopBgColor            color.Color\n\tborderRightBgColor          color.Color\n\tborderBottomBgColor         color.Color\n\tborderLeftBgColor           color.Color\n\n\tmaxWidth  int\n\tmaxHeight int\n\ttabWidth  int\n\n\ttransform func(string) string\n}\n\n// joinString joins a list of strings into a single string separated with a\n// space.\nfunc joinString(strs ...string) string {\n\treturn strings.Join(strs, \" \")\n}\n\n// SetString sets the underlying string value for this style. To render once\n// the underlying string is set, use the [Style.String]. This method is\n// a convenience for cases when having a stringer implementation is handy, such\n// as when using fmt.Sprintf. You can also simply define a style and render out\n// strings directly with [Style.Render].\nfunc (s Style) SetString(strs ...string) Style {\n\ts.value = joinString(strs...)\n\treturn s\n}\n\n// Value returns the raw, unformatted, underlying string value for this style.\nfunc (s Style) Value() string {\n\treturn s.value\n}\n\n// String implements stringer for a Style, returning the rendered result based\n// on the rules in this style. An underlying string value must be set with\n// Style.SetString prior to using this method.\nfunc (s Style) String() string {\n\treturn s.Render()\n}\n\n// Copy returns a copy of this style, including any underlying string values.\n//\n// Deprecated: to copy just use assignment (i.e. a := b). All methods also\n// return a new style.\nfunc (s Style) Copy() Style {\n\treturn s\n}\n\n// Inherit overlays the style in the argument onto this style by copying each explicitly\n// set value from the argument style onto this style if it is not already explicitly set.\n// Existing set values are kept intact and not overwritten.\n//\n// Margins, padding, and underlying string values are not inherited.\nfunc (s Style) Inherit(i Style) Style {\n\tfor k := boldKey; k <= transformKey; k <<= 1 {\n\t\tif !i.isSet(k) {\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch k { //nolint:exhaustive\n\t\tcase marginTopKey, marginRightKey, marginBottomKey, marginLeftKey:\n\t\t\t// Margins are not inherited\n\t\t\tcontinue\n\t\tcase paddingTopKey, paddingRightKey, paddingBottomKey, paddingLeftKey:\n\t\t\t// Padding is not inherited\n\t\t\tcontinue\n\t\tcase backgroundKey:\n\t\t\t// The margins also inherit the background color\n\t\t\tif !s.isSet(marginBackgroundKey) && !i.isSet(marginBackgroundKey) {\n\t\t\t\ts.set(marginBackgroundKey, i.bgColor)\n\t\t\t}\n\t\t}\n\n\t\tif s.isSet(k) {\n\t\t\tcontinue\n\t\t}\n\n\t\ts.setFrom(k, i)\n\t}\n\treturn s\n}\n\n// Render applies the defined style formatting to a given string.\nfunc (s Style) Render(strs ...string) string {\n\tif s.value != \"\" {\n\t\tstrs = append([]string{s.value}, strs...)\n\t}\n\n\tvar (\n\t\tstr = joinString(strs...)\n\n\t\tte           ansi.Style\n\t\tteSpace      ansi.Style\n\t\tteWhitespace ansi.Style\n\n\t\tbold          = s.getAsBool(boldKey, false)\n\t\titalic        = s.getAsBool(italicKey, false)\n\t\tstrikethrough = s.getAsBool(strikethroughKey, false)\n\t\treverse       = s.getAsBool(reverseKey, false)\n\t\tblink         = s.getAsBool(blinkKey, false)\n\t\tfaint         = s.getAsBool(faintKey, false)\n\n\t\tfg = s.getAsColor(foregroundKey)\n\t\tbg = s.getAsColor(backgroundKey)\n\t\tul = s.getAsColor(underlineColorKey)\n\n\t\tunderline       = s.ul != UnderlineNone\n\t\twidth           = s.getAsInt(widthKey)\n\t\theight          = s.getAsInt(heightKey)\n\t\thorizontalAlign = s.getAsPosition(alignHorizontalKey)\n\t\tverticalAlign   = s.getAsPosition(alignVerticalKey)\n\n\t\ttopPadding    = s.getAsInt(paddingTopKey)\n\t\trightPadding  = s.getAsInt(paddingRightKey)\n\t\tbottomPadding = s.getAsInt(paddingBottomKey)\n\t\tleftPadding   = s.getAsInt(paddingLeftKey)\n\n\t\thorizontalBorderSize = s.GetHorizontalBorderSize()\n\t\tverticalBorderSize   = s.GetVerticalBorderSize()\n\n\t\tcolorWhitespace = s.getAsBool(colorWhitespaceKey, true)\n\t\tinline          = s.getAsBool(inlineKey, false)\n\t\tmaxWidth        = s.getAsInt(maxWidthKey)\n\t\tmaxHeight       = s.getAsInt(maxHeightKey)\n\n\t\tunderlineSpaces     = s.getAsBool(underlineSpacesKey, false) || (underline && s.getAsBool(underlineSpacesKey, true))\n\t\tstrikethroughSpaces = s.getAsBool(strikethroughSpacesKey, false) || (strikethrough && s.getAsBool(strikethroughSpacesKey, true))\n\n\t\t// Do we need to style whitespace (padding and space outside\n\t\t// paragraphs) separately?\n\t\tstyleWhitespace = reverse\n\n\t\t// Do we need to style spaces separately?\n\t\tuseSpaceStyler = (underline && !underlineSpaces) || (strikethrough && !strikethroughSpaces) || underlineSpaces || strikethroughSpaces\n\n\t\ttransform = s.getAsTransform(transformKey)\n\n\t\tlink, linkParams = s.GetHyperlink()\n\t)\n\n\tif transform != nil {\n\t\tstr = transform(str)\n\t}\n\n\tif s.props == 0 {\n\t\treturn s.maybeConvertTabs(str)\n\t}\n\n\tif bold {\n\t\tte = te.Bold()\n\t}\n\tif italic {\n\t\tte = te.Italic(true)\n\t}\n\tif underline {\n\t\tte = te.Underline(true)\n\t}\n\tif reverse {\n\t\tteWhitespace = teWhitespace.Reverse(true)\n\t\tte = te.Reverse(true)\n\t}\n\tif blink {\n\t\tte = te.Blink(true)\n\t}\n\tif faint {\n\t\tte = te.Faint()\n\t}\n\n\tif fg != noColor {\n\t\tte = te.ForegroundColor(fg)\n\t\tif styleWhitespace {\n\t\t\tteWhitespace = teWhitespace.ForegroundColor(fg)\n\t\t}\n\t\tif useSpaceStyler {\n\t\t\tteSpace = teSpace.ForegroundColor(fg)\n\t\t}\n\t}\n\n\tif bg != noColor {\n\t\tte = te.BackgroundColor(bg)\n\t\tif colorWhitespace {\n\t\t\tteWhitespace = teWhitespace.BackgroundColor(bg)\n\t\t}\n\t\tif useSpaceStyler {\n\t\t\tteSpace = teSpace.BackgroundColor(bg)\n\t\t}\n\t}\n\n\tif ul != noColor {\n\t\tte = te.UnderlineColor(ul)\n\t\tif colorWhitespace {\n\t\t\tteWhitespace = teWhitespace.UnderlineColor(ul)\n\t\t}\n\t\tif useSpaceStyler {\n\t\t\tteSpace = teSpace.UnderlineColor(ul)\n\t\t}\n\t}\n\n\tif underline {\n\t\tte = te.UnderlineStyle(s.ul)\n\t}\n\tif strikethrough {\n\t\tte = te.Strikethrough(true)\n\t}\n\n\tif underlineSpaces {\n\t\tteSpace = teSpace.Underline(true)\n\t}\n\tif strikethroughSpaces {\n\t\tteSpace = teSpace.Strikethrough(true)\n\t}\n\n\t// Potentially convert tabs to spaces\n\tstr = s.maybeConvertTabs(str)\n\t// carriage returns can cause strange behaviour when rendering.\n\tstr = strings.ReplaceAll(str, \"\\r\\n\", \"\\n\")\n\n\t// Strip newlines in single line mode\n\tif inline {\n\t\tstr = strings.ReplaceAll(str, \"\\n\", \"\")\n\t}\n\n\t// Include borders in block size.\n\twidth -= horizontalBorderSize\n\theight -= verticalBorderSize\n\n\t// Word wrap\n\tif !inline && width > 0 {\n\t\twrapAt := width - leftPadding - rightPadding\n\t\tstr = Wrap(str, wrapAt, \"\")\n\t}\n\n\t// Render core text\n\t{\n\t\tvar b strings.Builder\n\n\t\tisFirst := true\n\t\tfor line := range strings.SplitSeq(str, \"\\n\") {\n\t\t\tif isFirst {\n\t\t\t\tisFirst = false\n\t\t\t} else {\n\t\t\t\tb.WriteRune('\\n')\n\t\t\t}\n\t\t\tif useSpaceStyler {\n\t\t\t\t// Look for spaces and apply a different styler\n\t\t\t\tfor _, r := range line {\n\t\t\t\t\tif unicode.IsSpace(r) {\n\t\t\t\t\t\tb.WriteString(teSpace.Styled(string(r)))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tb.WriteString(te.Styled(string(r)))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tb.WriteString(te.Styled(line))\n\t\t\t}\n\t\t}\n\n\t\tstr = b.String()\n\n\t\tif len(link) > 0 {\n\t\t\tstr = ansi.SetHyperlink(link, linkParams) + str + ansi.ResetHyperlink()\n\t\t}\n\t}\n\n\t// Padding\n\tif !inline { //nolint:nestif\n\t\tpadChar := s.paddingChar\n\t\tif padChar == 0 {\n\t\t\tpadChar = ' '\n\t\t}\n\t\tif leftPadding > 0 {\n\t\t\tvar st *ansi.Style\n\t\t\tif colorWhitespace || styleWhitespace {\n\t\t\t\tst = &teWhitespace\n\t\t\t}\n\t\t\tstr = padLeft(str, leftPadding, st, padChar)\n\t\t}\n\n\t\tif rightPadding > 0 {\n\t\t\tvar st *ansi.Style\n\t\t\tif colorWhitespace || styleWhitespace {\n\t\t\t\tst = &teWhitespace\n\t\t\t}\n\t\t\tstr = padRight(str, rightPadding, st, padChar)\n\t\t}\n\n\t\tif topPadding > 0 {\n\t\t\tstr = strings.Repeat(\"\\n\", topPadding) + str\n\t\t}\n\n\t\tif bottomPadding > 0 {\n\t\t\tstr += strings.Repeat(\"\\n\", bottomPadding)\n\t\t}\n\t}\n\n\t// Height\n\tif height > 0 {\n\t\tstr = alignTextVertical(str, verticalAlign, height, nil)\n\t}\n\n\t// Set alignment. This will also pad short lines with spaces so that all\n\t// lines are the same length, so we run it under a few different conditions\n\t// beyond alignment.\n\t{\n\t\tnumLines := strings.Count(str, \"\\n\")\n\n\t\tif numLines != 0 || width != 0 {\n\t\t\tvar st *ansi.Style\n\t\t\tif colorWhitespace || styleWhitespace {\n\t\t\t\tst = &teWhitespace\n\t\t\t}\n\t\t\tstr = alignTextHorizontal(str, horizontalAlign, width, st)\n\t\t}\n\t}\n\n\tif !inline {\n\t\tstr = s.applyBorder(str)\n\t\tstr = s.applyMargins(str, inline)\n\t}\n\n\t// Truncate according to MaxWidth\n\tif maxWidth > 0 {\n\t\tlines := strings.Split(str, \"\\n\")\n\n\t\tfor i := range lines {\n\t\t\tlines[i] = ansi.Truncate(lines[i], maxWidth, \"\")\n\t\t}\n\n\t\tstr = strings.Join(lines, \"\\n\")\n\t}\n\n\t// Truncate according to MaxHeight\n\tif maxHeight > 0 {\n\t\tlines := strings.Split(str, \"\\n\")\n\t\theight := min(maxHeight, len(lines))\n\t\tif len(lines) > 0 {\n\t\t\tstr = strings.Join(lines[:height], \"\\n\")\n\t\t}\n\t}\n\n\treturn str\n}\n\nfunc (s Style) maybeConvertTabs(str string) string {\n\ttw := tabWidthDefault\n\tif s.isSet(tabWidthKey) {\n\t\ttw = s.getAsInt(tabWidthKey)\n\t}\n\tswitch tw {\n\tcase -1:\n\t\treturn str\n\tcase 0:\n\t\treturn strings.ReplaceAll(str, \"\\t\", \"\")\n\tdefault:\n\t\treturn strings.ReplaceAll(str, \"\\t\", strings.Repeat(\" \", tw))\n\t}\n}\n\nfunc (s Style) applyMargins(str string, inline bool) string {\n\tvar (\n\t\ttopMargin    = s.getAsInt(marginTopKey)\n\t\trightMargin  = s.getAsInt(marginRightKey)\n\t\tbottomMargin = s.getAsInt(marginBottomKey)\n\t\tleftMargin   = s.getAsInt(marginLeftKey)\n\n\t\tstyle ansi.Style\n\t)\n\n\tbgc := s.getAsColor(marginBackgroundKey)\n\tif bgc != noColor {\n\t\tstyle = style.BackgroundColor(bgc)\n\t}\n\n\t// Add left and right margin\n\tmarginChar := s.marginChar\n\tif marginChar == 0 {\n\t\tmarginChar = ' '\n\t}\n\tstr = padLeft(str, leftMargin, &style, marginChar)\n\tstr = padRight(str, rightMargin, &style, marginChar)\n\n\t// Top/bottom margin\n\tif !inline {\n\t\t_, width := getLines(str)\n\t\tspaces := strings.Repeat(\" \", width)\n\n\t\tif topMargin > 0 {\n\t\t\tstr = style.Styled(strings.Repeat(spaces+\"\\n\", topMargin)) + str\n\t\t}\n\t\tif bottomMargin > 0 {\n\t\t\tstr += style.Styled(strings.Repeat(\"\\n\"+spaces, bottomMargin))\n\t\t}\n\t}\n\n\treturn str\n}\n\n// Apply left padding.\nfunc padLeft(str string, n int, style *ansi.Style, r rune) string {\n\treturn pad(str, -n, style, r)\n}\n\n// Apply right padding.\nfunc padRight(str string, n int, style *ansi.Style, r rune) string {\n\treturn pad(str, n, style, r)\n}\n\n// pad adds padding to either the left or right side of a string.\n// Positive values add to the right side while negative values\n// add to the left side.\n// r is the rune to use for padding. We use \" \" for margins and\n// \"\\u00A0\" for padding so that the padding is preserved when the\n// string is copied and pasted.\nfunc pad(str string, n int, style *ansi.Style, r rune) string {\n\tif n == 0 {\n\t\treturn str\n\t}\n\n\tsp := strings.Repeat(string(r), abs(n))\n\tif style != nil {\n\t\tsp = style.Styled(sp)\n\t}\n\n\tb := strings.Builder{}\n\tisFirst := true\n\tfor line := range strings.SplitSeq(str, \"\\n\") {\n\t\tif isFirst {\n\t\t\tisFirst = false\n\t\t} else {\n\t\t\tb.WriteRune('\\n')\n\t\t}\n\t\tswitch {\n\t\t// pad right\n\t\tcase n > 0:\n\t\t\tb.WriteString(line)\n\t\t\tb.WriteString(sp)\n\t\t// pad left\n\t\tdefault:\n\t\t\tb.WriteString(sp)\n\t\t\tb.WriteString(line)\n\t\t}\n\t}\n\n\treturn b.String()\n}\n\nfunc abs(a int) int {\n\tif a < 0 {\n\t\treturn -a\n\t}\n\n\treturn a\n}\n"
  },
  {
    "path": "style_test.go",
    "content": "package lipgloss\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestUnderline(t *testing.T) {\n\tt.Parallel()\n\n\ttt := []struct {\n\t\tstyle    Style\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tNewStyle().Underline(true),\n\t\t\t\"\\x1b[4;4ma\\x1b[m\\x1b[4;4mb\\x1b[m\\x1b[4m \\x1b[m\\x1b[4;4mc\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().Underline(true).UnderlineSpaces(true),\n\t\t\t\"\\x1b[4;4ma\\x1b[m\\x1b[4;4mb\\x1b[m\\x1b[4m \\x1b[m\\x1b[4;4mc\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().Underline(true).UnderlineSpaces(false),\n\t\t\t\"\\x1b[4;4ma\\x1b[m\\x1b[4;4mb\\x1b[m \\x1b[4;4mc\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().UnderlineSpaces(true),\n\t\t\t\"ab\\x1b[4m \\x1b[mc\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().UnderlineStyle(UnderlineCurly),\n\t\t\t\"\\x1b[4;4:3ma\\x1b[m\\x1b[4;4:3mb\\x1b[m\\x1b[4m \\x1b[m\\x1b[4;4:3mc\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().UnderlineStyle(UnderlineCurly).UnderlineColor(Color(\"#FF0000\")),\n\t\t\t\"\\x1b[4;58;2;255;0;0;4:3ma\\x1b[m\\x1b[4;58;2;255;0;0;4:3mb\\x1b[m\\x1b[58;2;255;0;0;4m \\x1b[m\\x1b[4;58;2;255;0;0;4:3mc\\x1b[m\",\n\t\t},\n\t}\n\n\tfor i, tc := range tt {\n\t\ts := tc.style.SetString(\"ab c\")\n\t\tres := s.Render()\n\t\tif res != tc.expected {\n\t\t\tt.Errorf(\"Test %d, expected:\\n`%q`\\n\\nActual output:\\n`%q`\\n\\n\",\n\t\t\t\ti, tc.expected,\n\t\t\t\tres)\n\t\t}\n\t}\n}\n\nfunc TestGetUnderlineColor(t *testing.T) {\n\tt.Parallel()\n\n\tred := Color(\"#FF0000\")\n\ts := NewStyle().Underline(true).UnderlineColor(red)\n\tif s.GetUnderlineColor() != red {\n\t\tt.Errorf(\"GetUnderlineColor() = %v, want %v\", s.GetUnderlineColor(), red)\n\t}\n}\n\nfunc TestStrikethrough(t *testing.T) {\n\tt.Parallel()\n\n\ttt := []struct {\n\t\tstyle    Style\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tNewStyle().Strikethrough(true),\n\t\t\t\"\\x1b[9ma\\x1b[m\\x1b[9mb\\x1b[m\\x1b[9m \\x1b[m\\x1b[9mc\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().Strikethrough(true).StrikethroughSpaces(true),\n\t\t\t\"\\x1b[9ma\\x1b[m\\x1b[9mb\\x1b[m\\x1b[9m \\x1b[m\\x1b[9mc\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().Strikethrough(true).StrikethroughSpaces(false),\n\t\t\t\"\\x1b[9ma\\x1b[m\\x1b[9mb\\x1b[m \\x1b[9mc\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().StrikethroughSpaces(true),\n\t\t\t\"ab\\x1b[9m \\x1b[mc\",\n\t\t},\n\t}\n\n\tfor i, tc := range tt {\n\t\ts := tc.style.SetString(\"ab c\")\n\t\tres := s.Render()\n\t\tif res != tc.expected {\n\t\t\tt.Errorf(\"Test %d, expected:\\n`%q`\\n\\nActual output:\\n`%q`\\n\\n\",\n\t\t\t\ti, tc.expected,\n\t\t\t\tres)\n\t\t}\n\t}\n}\n\nfunc TestStyleRender(t *testing.T) {\n\tt.Parallel()\n\n\ttt := []struct {\n\t\tstyle    Style\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tNewStyle().Foreground(Color(\"#5A56E0\")),\n\t\t\t\"\\x1b[38;2;90;86;224mhello\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().Bold(true),\n\t\t\t\"\\x1b[1mhello\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().Italic(true),\n\t\t\t\"\\x1b[3mhello\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().Underline(true),\n\t\t\t\"\\x1b[4;4mh\\x1b[m\\x1b[4;4me\\x1b[m\\x1b[4;4ml\\x1b[m\\x1b[4;4ml\\x1b[m\\x1b[4;4mo\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().Blink(true),\n\t\t\t\"\\x1b[5mhello\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tNewStyle().Faint(true),\n\t\t\t\"\\x1b[2mhello\\x1b[m\",\n\t\t},\n\t}\n\n\tfor i, tc := range tt {\n\t\ts := tc.style.SetString(\"hello\")\n\t\tres := s.Render()\n\t\tif res != tc.expected {\n\t\t\tt.Errorf(\"Test %d, expected:\\n`%q`\\n\\nActual output:\\n`%q`\\n\\n\",\n\t\t\t\ti, tc.expected,\n\t\t\t\tres)\n\t\t}\n\t}\n}\n\nfunc TestValueCopy(t *testing.T) {\n\tt.Parallel()\n\n\ts := NewStyle().\n\t\tBold(true)\n\n\ti := s\n\ti.Bold(false)\n\n\trequireEqual(t, s.GetBold(), i.GetBold())\n}\n\nfunc TestStyleInherit(t *testing.T) {\n\tt.Parallel()\n\n\ts := NewStyle().\n\t\tBold(true).\n\t\tItalic(true).\n\t\tUnderline(true).\n\t\tStrikethrough(true).\n\t\tBlink(true).\n\t\tFaint(true).\n\t\tForeground(Color(\"#ffffff\")).\n\t\tBackground(Color(\"#111111\")).\n\t\tMargin(1, 1, 1, 1).\n\t\tPadding(1, 1, 1, 1)\n\n\ti := NewStyle().Inherit(s)\n\n\trequireEqual(t, s.GetBold(), i.GetBold())\n\trequireEqual(t, s.GetItalic(), i.GetItalic())\n\trequireEqual(t, s.GetUnderline(), i.GetUnderline())\n\trequireEqual(t, s.GetUnderlineSpaces(), i.GetUnderlineSpaces())\n\trequireEqual(t, s.GetStrikethrough(), i.GetStrikethrough())\n\trequireEqual(t, s.GetStrikethroughSpaces(), i.GetStrikethroughSpaces())\n\trequireEqual(t, s.GetBlink(), i.GetBlink())\n\trequireEqual(t, s.GetFaint(), i.GetFaint())\n\trequireEqual(t, s.GetForeground(), i.GetForeground())\n\trequireEqual(t, s.GetBackground(), i.GetBackground())\n\n\trequireNotEqual(t, s.GetMarginLeft(), i.GetMarginLeft())\n\trequireNotEqual(t, s.GetMarginRight(), i.GetMarginRight())\n\trequireNotEqual(t, s.GetMarginTop(), i.GetMarginTop())\n\trequireNotEqual(t, s.GetMarginBottom(), i.GetMarginBottom())\n\trequireNotEqual(t, s.GetPaddingLeft(), i.GetPaddingLeft())\n\trequireNotEqual(t, s.GetPaddingRight(), i.GetPaddingRight())\n\trequireNotEqual(t, s.GetPaddingTop(), i.GetPaddingTop())\n\trequireNotEqual(t, s.GetPaddingBottom(), i.GetPaddingBottom())\n}\n\nfunc TestStyleCopy(t *testing.T) {\n\tt.Parallel()\n\n\ts := NewStyle().\n\t\tBold(true).\n\t\tItalic(true).\n\t\tUnderline(true).\n\t\tStrikethrough(true).\n\t\tBlink(true).\n\t\tFaint(true).\n\t\tForeground(Color(\"#ffffff\")).\n\t\tBackground(Color(\"#111111\")).\n\t\tMargin(1, 1, 1, 1).\n\t\tPadding(1, 1, 1, 1).\n\t\tTabWidth(2)\n\n\ti := s // copy\n\n\trequireEqual(t, s.GetBold(), i.GetBold())\n\trequireEqual(t, s.GetItalic(), i.GetItalic())\n\trequireEqual(t, s.GetUnderline(), i.GetUnderline())\n\trequireEqual(t, s.GetUnderlineSpaces(), i.GetUnderlineSpaces())\n\trequireEqual(t, s.GetStrikethrough(), i.GetStrikethrough())\n\trequireEqual(t, s.GetStrikethroughSpaces(), i.GetStrikethroughSpaces())\n\trequireEqual(t, s.GetBlink(), i.GetBlink())\n\trequireEqual(t, s.GetFaint(), i.GetFaint())\n\trequireEqual(t, s.GetForeground(), i.GetForeground())\n\trequireEqual(t, s.GetBackground(), i.GetBackground())\n\n\trequireEqual(t, s.GetMarginLeft(), i.GetMarginLeft())\n\trequireEqual(t, s.GetMarginRight(), i.GetMarginRight())\n\trequireEqual(t, s.GetMarginTop(), i.GetMarginTop())\n\trequireEqual(t, s.GetMarginBottom(), i.GetMarginBottom())\n\trequireEqual(t, s.GetPaddingLeft(), i.GetPaddingLeft())\n\trequireEqual(t, s.GetPaddingRight(), i.GetPaddingRight())\n\trequireEqual(t, s.GetPaddingTop(), i.GetPaddingTop())\n\trequireEqual(t, s.GetPaddingBottom(), i.GetPaddingBottom())\n\trequireEqual(t, s.GetTabWidth(), i.GetTabWidth())\n}\n\nfunc TestStyleUnset(t *testing.T) {\n\tt.Parallel()\n\n\ts := NewStyle().Bold(true)\n\trequireTrue(t, s.GetBold())\n\ts = s.UnsetBold()\n\trequireFalse(t, s.GetBold())\n\n\ts = NewStyle().Italic(true)\n\trequireTrue(t, s.GetItalic())\n\ts = s.UnsetItalic()\n\trequireFalse(t, s.GetItalic())\n\n\ts = NewStyle().Underline(true)\n\trequireTrue(t, s.GetUnderline())\n\ts = s.UnsetUnderline()\n\trequireFalse(t, s.GetUnderline())\n\n\ts = NewStyle().UnderlineSpaces(true)\n\trequireTrue(t, s.GetUnderlineSpaces())\n\ts = s.UnsetUnderlineSpaces()\n\trequireFalse(t, s.GetUnderlineSpaces())\n\n\ts = NewStyle().Strikethrough(true)\n\trequireTrue(t, s.GetStrikethrough())\n\ts = s.UnsetStrikethrough()\n\trequireFalse(t, s.GetStrikethrough())\n\n\ts = NewStyle().StrikethroughSpaces(true)\n\trequireTrue(t, s.GetStrikethroughSpaces())\n\ts = s.UnsetStrikethroughSpaces()\n\trequireFalse(t, s.GetStrikethroughSpaces())\n\n\ts = NewStyle().Reverse(true)\n\trequireTrue(t, s.GetReverse())\n\ts = s.UnsetReverse()\n\trequireFalse(t, s.GetReverse())\n\n\ts = NewStyle().Blink(true)\n\trequireTrue(t, s.GetBlink())\n\ts = s.UnsetBlink()\n\trequireFalse(t, s.GetBlink())\n\n\ts = NewStyle().Faint(true)\n\trequireTrue(t, s.GetFaint())\n\ts = s.UnsetFaint()\n\trequireFalse(t, s.GetFaint())\n\n\ts = NewStyle().Inline(true)\n\trequireTrue(t, s.GetInline())\n\ts = s.UnsetInline()\n\trequireFalse(t, s.GetInline())\n\n\t// colors\n\tcol := Color(\"#ffffff\")\n\ts = NewStyle().Foreground(col)\n\trequireEqual(t, col, s.GetForeground())\n\ts = s.UnsetForeground()\n\trequireNotEqual(t, col, s.GetForeground())\n\n\ts = NewStyle().Background(col)\n\trequireEqual(t, col, s.GetBackground())\n\ts = s.UnsetBackground()\n\trequireNotEqual(t, col, s.GetBackground())\n\n\t// margins\n\ts = NewStyle().Margin(1, 2, 3, 4)\n\trequireEqual(t, 1, s.GetMarginTop())\n\ts = s.UnsetMarginTop()\n\trequireEqual(t, 0, s.GetMarginTop())\n\n\trequireEqual(t, 2, s.GetMarginRight())\n\ts = s.UnsetMarginRight()\n\trequireEqual(t, 0, s.GetMarginRight())\n\n\trequireEqual(t, 3, s.GetMarginBottom())\n\ts = s.UnsetMarginBottom()\n\trequireEqual(t, 0, s.GetMarginBottom())\n\n\trequireEqual(t, 4, s.GetMarginLeft())\n\ts = s.UnsetMarginLeft()\n\trequireEqual(t, 0, s.GetMarginLeft())\n\n\t// padding\n\ts = NewStyle().Padding(1, 2, 3, 4).PaddingChar('x')\n\trequireEqual(t, 1, s.GetPaddingTop())\n\ts = s.UnsetPaddingTop()\n\trequireEqual(t, 0, s.GetPaddingTop())\n\n\trequireEqual(t, 2, s.GetPaddingRight())\n\ts = s.UnsetPaddingRight()\n\trequireEqual(t, 0, s.GetPaddingRight())\n\n\trequireEqual(t, 3, s.GetPaddingBottom())\n\ts = s.UnsetPaddingBottom()\n\trequireEqual(t, 0, s.GetPaddingBottom())\n\n\trequireEqual(t, 4, s.GetPaddingLeft())\n\ts = s.UnsetPaddingLeft()\n\trequireEqual(t, 0, s.GetPaddingLeft())\n\n\trequireEqual(t, 'x', s.GetPaddingChar())\n\ts = s.UnsetPaddingChar()\n\trequireEqual(t, ' ', s.GetPaddingChar())\n\n\t// border\n\ts = NewStyle().Border(normalBorder, true, true, true, true)\n\trequireTrue(t, s.GetBorderTop())\n\ts = s.UnsetBorderTop()\n\trequireFalse(t, s.GetBorderTop())\n\n\trequireTrue(t, s.GetBorderRight())\n\ts = s.UnsetBorderRight()\n\trequireFalse(t, s.GetBorderRight())\n\n\trequireTrue(t, s.GetBorderBottom())\n\ts = s.UnsetBorderBottom()\n\trequireFalse(t, s.GetBorderBottom())\n\n\trequireTrue(t, s.GetBorderLeft())\n\ts = s.UnsetBorderLeft()\n\trequireFalse(t, s.GetBorderLeft())\n\n\t// tab width\n\ts = NewStyle().TabWidth(2)\n\trequireEqual(t, s.GetTabWidth(), 2)\n\ts = s.UnsetTabWidth()\n\trequireNotEqual(t, s.GetTabWidth(), 4)\n}\n\nfunc TestStyleValue(t *testing.T) {\n\tt.Parallel()\n\n\ttt := []struct {\n\t\tname     string\n\t\ttext     string\n\t\tstyle    Style\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"empty\",\n\t\t\ttext:     \"foo\",\n\t\t\tstyle:    NewStyle(),\n\t\t\texpected: \"foo\",\n\t\t},\n\t\t{\n\t\t\tname:     \"set string\",\n\t\t\ttext:     \"foo\",\n\t\t\tstyle:    NewStyle().SetString(\"bar\"),\n\t\t\texpected: \"bar foo\",\n\t\t},\n\t\t{\n\t\t\tname:     \"set string with bold\",\n\t\t\ttext:     \"foo\",\n\t\t\tstyle:    NewStyle().SetString(\"bar\").Bold(true),\n\t\t\texpected: \"\\x1b[1mbar foo\\x1b[m\",\n\t\t},\n\t\t{\n\t\t\tname:     \"new style with string\",\n\t\t\ttext:     \"foo\",\n\t\t\tstyle:    NewStyle().SetString(\"bar\", \"foobar\"),\n\t\t\texpected: \"bar foobar foo\",\n\t\t},\n\t\t{\n\t\t\tname:     \"margin right\",\n\t\t\ttext:     \"foo\",\n\t\t\tstyle:    NewStyle().MarginRight(1),\n\t\t\texpected: \"foo \",\n\t\t},\n\t\t{\n\t\t\tname:     \"margin left\",\n\t\t\ttext:     \"foo\",\n\t\t\tstyle:    NewStyle().MarginLeft(1),\n\t\t\texpected: \" foo\",\n\t\t},\n\t\t{\n\t\t\tname:     \"empty text margin right\",\n\t\t\ttext:     \"\",\n\t\t\tstyle:    NewStyle().MarginRight(1),\n\t\t\texpected: \" \",\n\t\t},\n\t\t{\n\t\t\tname:     \"empty text margin left\",\n\t\t\ttext:     \"\",\n\t\t\tstyle:    NewStyle().MarginLeft(1),\n\t\t\texpected: \" \",\n\t\t},\n\t}\n\n\tfor i, tc := range tt {\n\t\tres := tc.style.Render(tc.text)\n\t\tif res != tc.expected {\n\t\t\tt.Errorf(\"Test %d, expected:\\n`%q`\\n\\nActual output:\\n`%q`\\n\\n\",\n\t\t\t\ti, tc.expected,\n\t\t\t\tres)\n\t\t}\n\t}\n}\n\nfunc TestCustomPaddingChar(t *testing.T) {\n\ts := NewStyle().Padding(0, 3).PaddingChar('x')\n\trequireEqual(t, \"xxxTESTxxx\", s.Render(\"TEST\"))\n}\n\nfunc TestTabConversion(t *testing.T) {\n\ts := NewStyle()\n\trequireEqual(t, \"[    ]\", s.Render(\"[\\t]\"))\n\ts = NewStyle().TabWidth(2)\n\trequireEqual(t, \"[  ]\", s.Render(\"[\\t]\"))\n\ts = NewStyle().TabWidth(0)\n\trequireEqual(t, \"[]\", s.Render(\"[\\t]\"))\n\ts = NewStyle().TabWidth(-1)\n\trequireEqual(t, \"[\\t]\", s.Render(\"[\\t]\"))\n}\n\nfunc TestStringTransform(t *testing.T) {\n\tfor i, tc := range []struct {\n\t\tinput    string\n\t\tfn       func(string) string\n\t\texpected string\n\t}{\n\t\t// No-op.\n\t\t{\n\t\t\t\"hello\",\n\t\t\tfunc(s string) string { return s },\n\t\t\t\"hello\",\n\t\t},\n\t\t// Uppercase.\n\t\t{\n\t\t\t\"raow\",\n\t\t\tstrings.ToUpper,\n\t\t\t\"RAOW\",\n\t\t},\n\t\t// English and Chinese.\n\t\t{\n\t\t\t\"The quick brown 狐 jumped over the lazy 犬\",\n\t\t\tfunc(s string) string {\n\t\t\t\tn := 0\n\t\t\t\trune := make([]rune, len(s))\n\t\t\t\tfor _, r := range s {\n\t\t\t\t\trune[n] = r\n\t\t\t\t\tn++\n\t\t\t\t}\n\t\t\t\trune = rune[0:n]\n\t\t\t\tfor i := range n / 2 {\n\t\t\t\t\trune[i], rune[n-1-i] = rune[n-1-i], rune[i]\n\t\t\t\t}\n\t\t\t\treturn string(rune)\n\t\t\t},\n\t\t\t\"犬 yzal eht revo depmuj 狐 nworb kciuq ehT\",\n\t\t},\n\t} {\n\t\tres := NewStyle().Bold(true).Transform(tc.fn).Render(tc.input)\n\t\texpected := \"\\x1b[1m\" + tc.expected + \"\\x1b[m\"\n\t\tif res != expected {\n\t\t\tt.Errorf(\"Test #%d:\\nExpected: %q\\nGot:      %q\", i+1, expected, res)\n\t\t}\n\t}\n}\n\nfunc requireTrue(tb testing.TB, b bool) {\n\ttb.Helper()\n\trequireEqual(tb, true, b)\n}\n\nfunc requireFalse(tb testing.TB, b bool) {\n\ttb.Helper()\n\trequireEqual(tb, false, b)\n}\n\nfunc requireEqual(tb testing.TB, a, b any) {\n\ttb.Helper()\n\tif !reflect.DeepEqual(a, b) {\n\t\ttb.Errorf(\"%v != %v\", a, b)\n\t\ttb.FailNow()\n\t}\n}\n\nfunc requireNotEqual(tb testing.TB, a, b any) {\n\ttb.Helper()\n\tif reflect.DeepEqual(a, b) {\n\t\ttb.Errorf(\"%v == %v\", a, b)\n\t\ttb.FailNow()\n\t}\n}\n\nfunc TestCarriageReturnInRender(t *testing.T) {\n\tout := fmt.Sprintf(\"%s\\r\\n%s\\r\\n\", \"Super duper california oranges\", \"Hello world\")\n\ttestStyle := NewStyle().\n\t\tMarginLeft(1)\n\tgot := testStyle.Render(string(out))\n\twant := testStyle.Render(fmt.Sprintf(\"%s\\n%s\\n\", \"Super duper california oranges\", \"Hello world\"))\n\n\tif got != want {\n\t\tt.Logf(\"got(detailed):\\n%q\\nwant(detailed):\\n%q\", got, want)\n\t\tt.Fatalf(\"got(string):\\n%s\\nwant(string):\\n%s\", got, want)\n\t}\n}\n\nfunc TestWidth(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tstyle Style\n\t}{\n\t\t{\"width with borders\", NewStyle().Padding(0, 2).Border(NormalBorder(), true)},\n\t\t{\"width no borders\", NewStyle().Padding(0, 2)},\n\t\t{\"width unset borders\", NewStyle().Padding(0, 2).Border(NormalBorder(), true).BorderLeft(false).BorderRight(false)},\n\t\t{\"width single-sided border\", NewStyle().Padding(0, 2).Border(NormalBorder(), true).UnsetBorderBottom().UnsetBorderTop().UnsetBorderRight()},\n\t}\n\t{\n\t\tfor _, tc := range tests {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tcontent := \"The Romans learned from the Greeks that quinces slowly cooked with honey would “set” when cool. The Apicius gives a recipe for preserving whole quinces, stems and leaves attached, in a bath of honey diluted with defrutum: Roman marmalade. Preserves of quince and lemon appear (along with rose, apple, plum and pear) in the Book of ceremonies of the Byzantine Emperor Constantine VII Porphyrogennetos.\"\n\t\t\t\tcontentWidth := 80 - tc.style.GetHorizontalFrameSize()\n\t\t\t\trendered := tc.style.Width(contentWidth).Render(content)\n\t\t\t\tif Width(rendered) != contentWidth {\n\t\t\t\t\tt.Log(\"\\n\" + rendered)\n\t\t\t\t\tt.Fatalf(\"got: %d\\n, want: %d\", Width(rendered), contentWidth)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestHeight(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tstyle Style\n\t}{\n\t\t{\"height with borders\", NewStyle().Width(80).Padding(0, 2).Border(NormalBorder(), true)},\n\t\t{\"height no borders\", NewStyle().Width(80).Padding(0, 2)},\n\t\t{\"height unset borders\", NewStyle().Width(80).Padding(0, 2).Border(NormalBorder(), true).BorderBottom(false).BorderTop(false)},\n\t\t{\"height single-sided border\", NewStyle().Width(80).Padding(0, 2).Border(NormalBorder(), true).UnsetBorderLeft().UnsetBorderBottom().UnsetBorderRight()},\n\t}\n\t{\n\t\tfor _, tc := range tests {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tcontent := \"The Romans learned from the Greeks that quinces slowly cooked with honey would “set” when cool. The Apicius gives a recipe for preserving whole quinces, stems and leaves attached, in a bath of honey diluted with defrutum: Roman marmalade. Preserves of quince and lemon appear (along with rose, apple, plum and pear) in the Book of ceremonies of the Byzantine Emperor Constantine VII Porphyrogennetos.\"\n\t\t\t\tcontentHeight := 20 - tc.style.GetVerticalFrameSize()\n\t\t\t\trendered := tc.style.Height(contentHeight).Render(content)\n\t\t\t\tif Height(rendered) != contentHeight {\n\t\t\t\t\tt.Log(\"\\n\" + rendered)\n\t\t\t\t\tt.Fatalf(\"got: %d\\n, want: %d\", Height(rendered), contentHeight)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestHyperlink(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tstyle    Style\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"hyperlink\",\n\t\t\tstyle:    NewStyle().Hyperlink(\"https://example.com\").SetString(\"https://example.com\"),\n\t\t\texpected: \"\\x1b]8;;https://example.com\\x07https://example.com\\x1b]8;;\\x07\",\n\t\t},\n\t\t{\n\t\t\tname:     \"hyperlink with text\",\n\t\t\tstyle:    NewStyle().Hyperlink(\"https://example.com\", \"id=123\").SetString(\"example\"),\n\t\t\texpected: \"\\x1b]8;id=123;https://example.com\\x07example\\x1b]8;;\\x07\",\n\t\t},\n\t\t{\n\t\t\tname: \"hyperlink with text and style\",\n\t\t\tstyle: NewStyle().Hyperlink(\"https://example.com\", \"id=123\").SetString(\"example\").\n\t\t\t\tBold(true).Foreground(Color(\"234\")),\n\t\t\texpected: \"\\x1b]8;id=123;https://example.com\\x07\\x1b[1;38;5;234mexample\\x1b[m\\x1b]8;;\\x07\",\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif tc.style.String() != tc.expected {\n\t\t\t\tt.Fatalf(\"got: %q, want: %q\", tc.style.String(), tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnsetHyperlink(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tstyle    Style\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"unset hyperlink\",\n\t\t\tstyle:    NewStyle().Hyperlink(\"https://example.com\").SetString(\"https://example.com\").UnsetHyperlink(),\n\t\t\texpected: \"https://example.com\",\n\t\t},\n\t\t{\n\t\t\tname:     \"unset hyperlink with text\",\n\t\t\tstyle:    NewStyle().Hyperlink(\"https://example.com\", \"id=123\").SetString(\"example\").UnsetHyperlink(),\n\t\t\texpected: \"example\",\n\t\t},\n\t\t{\n\t\t\tname: \"unset hyperlink with text and style\",\n\t\t\tstyle: NewStyle().Hyperlink(\"https://example.com\", \"id=123\").SetString(\"example\").\n\t\t\t\tBold(true).Foreground(Color(\"234\")).UnsetHyperlink(),\n\t\t\texpected: \"\\x1b[1;38;5;234mexample\\x1b[m\",\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif tc.style.String() != tc.expected {\n\t\t\t\tt.Fatalf(\"got: %q, want: %q\", tc.style.String(), tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkPad(b *testing.B) {\n\ttests := []struct {\n\t\tname string\n\t\tstr  string\n\t\tn    int\n\t}{\n\t\t{name: \"pad-10\", str: \"foo bar\", n: 10},\n\t\t{name: \"pad-100\", str: \"foo bar\", n: 100},\n\t\t{name: \"pad-negative-10\", str: \"foo bar\", n: -10},\n\t\t{name: \"pad-negative-100\", str: \"foo bar\", n: -100},\n\t}\n\tfor _, tt := range tests {\n\t\tb.Run(tt.name, func(b *testing.B) {\n\t\t\tfor b.Loop() {\n\t\t\t\tpad(tt.str, tt.n, nil, ' ')\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkStyleRender(b *testing.B) {\n\ttests := []struct {\n\t\tname  string\n\t\tstyle Style\n\t\tinput string\n\t}{\n\t\t{\n\t\t\tname: \"simple-1-line\",\n\t\t\tstyle: NewStyle().\n\t\t\t\tBold(true).\n\t\t\t\tForeground(Color(\"#ffffff\")),\n\t\t\tinput: \"Hello world\",\n\t\t},\n\t\t{\n\t\t\tname: \"simple-5-lines\",\n\t\t\tstyle: NewStyle().\n\t\t\t\tBold(true).\n\t\t\t\tForeground(Color(\"#ffffff\")),\n\t\t\tinput: strings.Repeat(\"Hello world\\n\", 5),\n\t\t},\n\t\t{\n\t\t\tname: \"simple-5-lines-inline\",\n\t\t\tstyle: NewStyle().\n\t\t\t\tBold(true).\n\t\t\t\tForeground(Color(\"#ffffff\")).\n\t\t\t\tInline(true),\n\t\t\tinput: strings.Repeat(\"Hello world\\n\", 5),\n\t\t},\n\t\t{\n\t\t\tname: \"simple-10-lines-5-height-40-width\",\n\t\t\tstyle: NewStyle().\n\t\t\t\tBold(true).\n\t\t\t\tForeground(Color(\"#ffffff\")).\n\t\t\t\tHeight(5).\n\t\t\t\tWidth(40),\n\t\t\tinput: strings.Repeat(\"Hello world\\n\", 10),\n\t\t},\n\t\t{\n\t\t\tname: \"simple-10-lines-width-maxwidth\",\n\t\t\tstyle: NewStyle().\n\t\t\t\tBold(true).\n\t\t\t\tForeground(Color(\"#ffffff\")).\n\t\t\t\tWidth(40).\n\t\t\t\tMaxWidth(40),\n\t\t\tinput: strings.Repeat(\"Hello world\\n\", 10),\n\t\t},\n\t\t{\n\t\t\tname: \"simple-10-lines-width-maxwidth-borders\",\n\t\t\tstyle: NewStyle().\n\t\t\t\tBold(true).\n\t\t\t\tForeground(Color(\"#ffffff\")).\n\t\t\t\tWidth(40).\n\t\t\t\tMaxWidth(40).\n\t\t\t\tBorder(RoundedBorder(), true),\n\t\t\tinput: strings.Repeat(\"Hello world\\n\", 10),\n\t\t},\n\t\t{\n\t\t\tname: \"simple-10-lines-width-maxwidth-borders-padding-margins\",\n\t\t\tstyle: NewStyle().\n\t\t\t\tBold(true).\n\t\t\t\tForeground(Color(\"#ffffff\")).\n\t\t\t\tWidth(40).\n\t\t\t\tMaxWidth(40).\n\t\t\t\tBorder(RoundedBorder(), true).\n\t\t\t\tPadding(1, 1).\n\t\t\t\tMargin(1, 1),\n\t\t\tinput: strings.Repeat(\"Hello world\\n\", 10),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tb.Run(tt.name, func(b *testing.B) {\n\t\t\tfor b.Loop() {\n\t\t\t\ttt.style.Render(tt.input)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "table/resizing.go",
    "content": "package table\n\nimport (\n\t\"math\"\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// resize resizes the table to fit the specified width.\n//\n// Given a user defined table width, we must ensure the table is exactly that\n// width. This must account for all borders, column, separators, and column\n// data.\n//\n// In the case where the table is narrower than the specified table width,\n// we simply expand the columns evenly to fit the width.\n// For example, a table with 3 columns takes up 50 characters total, and the\n// width specified is 80, we expand each column by 10 characters, adding 30\n// to the total width.\n//\n// In the case where the table is wider than the specified table width, we\n// _could_ simply shrink the columns evenly but this would result in data\n// being truncated (perhaps unnecessarily). The naive approach could result\n// in very poor cropping of the table data. So, instead of shrinking columns\n// evenly, we calculate the median non-whitespace length of each column, and\n// shrink the columns based on the largest median.\n//\n// For example,\n//\n//\t┌──────┬───────────────┬──────────┐\n//\t│ Name │ Age of Person │ Location │\n//\t├──────┼───────────────┼──────────┤\n//\t│ Kini │ 40            │ New York │\n//\t│ Eli  │ 30            │ London   │\n//\t│ Iris │ 20            │ Paris    │\n//\t└──────┴───────────────┴──────────┘\n//\n// Median non-whitespace length  vs column width of each column:\n//\n// Name: 4 / 5\n// Age of Person: 2 / 15\n// Location: 6 / 10\n//\n// The biggest difference is 15 - 2, so we can shrink the 2nd column by 13.\nfunc (t *Table) resize() {\n\thasHeaders := len(t.headers) > 0\n\trows := DataToMatrix(t.data)\n\tr := newResizer(t.width, t.height, t.headers, rows)\n\tr.wrap = t.wrap\n\tr.borderColumn = t.borderColumn\n\tr.yPaddings = make([][]int, len(r.allRows))\n\n\tr.yOffset = t.yOffset\n\tr.useManualHeight = t.useManualHeight\n\tr.borderTop = t.borderTop\n\tr.borderBottom = t.borderBottom\n\tr.borderLeft = t.borderLeft\n\tr.borderRight = t.borderRight\n\tr.borderHeader = t.borderHeader\n\tr.borderRow = t.borderRow\n\n\tvar allRows [][]string\n\tif hasHeaders {\n\t\tallRows = append([][]string{t.headers}, rows...)\n\t} else {\n\t\tallRows = rows\n\t}\n\n\tstyleFunc := t.styleFunc\n\tif t.styleFunc == nil {\n\t\tstyleFunc = DefaultStyles\n\t}\n\n\tr.rowHeights = r.defaultRowHeights()\n\n\tfor i, row := range allRows {\n\t\tr.yPaddings[i] = make([]int, len(row))\n\n\t\tfor j := range row {\n\t\t\tcolumn := &r.columns[j]\n\n\t\t\t// Making sure we're passing the right index to `styleFunc`. The header row should be `-1` and\n\t\t\t// the others should start from `0`.\n\t\t\trowIndex := i\n\t\t\tif hasHeaders {\n\t\t\t\trowIndex--\n\t\t\t}\n\t\t\tstyle := styleFunc(rowIndex, j)\n\n\t\t\tcolumn.xPadding = max(column.xPadding, style.GetHorizontalFrameSize())\n\t\t\tcolumn.fixedWidth = max(column.fixedWidth, style.GetWidth())\n\n\t\t\tr.rowHeights[i] = max(r.rowHeights[i], style.GetHeight())\n\t\t\tr.yPaddings[i][j] = style.GetVerticalFrameSize()\n\t\t}\n\t}\n\n\t// A table width wasn't specified. In this case, detect according to\n\t// content width.\n\tif r.tableWidth <= 0 {\n\t\tr.tableWidth = r.detectTableWidth()\n\t}\n\n\tt.widths, t.heights = r.optimizedWidths()\n\tt.firstVisibleRowIndex, t.lastVisibleRowIndex, t.overflowHeight = r.visibleRowIndexes()\n}\n\n// resizerColumn is a column in the resizer.\ntype resizerColumn struct {\n\tindex      int\n\tmin        int\n\tmax        int\n\tmedian     int\n\trows       [][]string\n\txPadding   int // horizontal padding\n\tfixedWidth int\n}\n\n// resizer is a table resizer.\ntype resizer struct {\n\ttableWidth  int\n\ttableHeight int\n\theaders     []string\n\tallRows     [][]string\n\trowHeights  []int\n\tcolumns     []resizerColumn\n\n\twrap         bool\n\tborderColumn bool\n\tyPaddings    [][]int // vertical paddings\n\n\tyOffset         int\n\tuseManualHeight bool\n\tborderTop       bool\n\tborderBottom    bool\n\tborderLeft      bool\n\tborderRight     bool\n\tborderHeader    bool\n\tborderRow       bool\n}\n\n// newResizer creates a new resizer.\nfunc newResizer(tableWidth, tableHeight int, headers []string, rows [][]string) *resizer {\n\tr := &resizer{\n\t\ttableWidth:  tableWidth,\n\t\ttableHeight: tableHeight,\n\t\theaders:     headers,\n\t}\n\n\tif len(headers) > 0 {\n\t\tr.allRows = append([][]string{headers}, rows...)\n\t} else {\n\t\tr.allRows = rows\n\t}\n\n\tfor _, row := range r.allRows {\n\t\tfor i, cell := range row {\n\t\t\tcellLen := lipgloss.Width(cell)\n\n\t\t\t// Header or first row. Just add as is.\n\t\t\tif len(r.columns) <= i {\n\t\t\t\tr.columns = append(r.columns, resizerColumn{\n\t\t\t\t\tindex:  i,\n\t\t\t\t\tmin:    cellLen,\n\t\t\t\t\tmax:    cellLen,\n\t\t\t\t\tmedian: cellLen,\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.columns[i].rows = append(r.columns[i].rows, row)\n\t\t\tr.columns[i].min = min(r.columns[i].min, cellLen)\n\t\t\tr.columns[i].max = max(r.columns[i].max, cellLen)\n\t\t}\n\t}\n\tfor j := range r.columns {\n\t\twidths := make([]int, len(r.columns[j].rows))\n\t\tfor i, row := range r.columns[j].rows {\n\t\t\twidths[i] = lipgloss.Width(row[j])\n\t\t}\n\t\tr.columns[j].median = median(widths)\n\t}\n\n\treturn r\n}\n\n// optimizedWidths returns the optimized column widths and row heights.\nfunc (r *resizer) optimizedWidths() (colWidths, rowHeights []int) {\n\tif r.maxTotal() <= r.tableWidth {\n\t\treturn r.expandTableWidth(), r.rowHeights\n\t}\n\treturn r.shrinkTableWidth(), r.rowHeights\n}\n\n// detectTableWidth detects the table width.\nfunc (r *resizer) detectTableWidth() int {\n\treturn r.maxCharCount() + r.totalHorizontalPadding() + r.totalHorizontalBorder()\n}\n\n// expandTableWidth expands the table width.\nfunc (r *resizer) expandTableWidth() (colWidths []int) {\n\tcolWidths = r.maxColumnWidths()\n\n\tfor {\n\t\ttotalWidth := sum(colWidths) + r.totalHorizontalBorder()\n\t\tif totalWidth >= r.tableWidth {\n\t\t\tbreak\n\t\t}\n\n\t\tshorterColumnIndex := 0\n\t\tshorterColumnWidth := math.MaxInt32\n\n\t\tfor j, width := range colWidths {\n\t\t\tif width == r.columns[j].fixedWidth {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif width < shorterColumnWidth {\n\t\t\t\tshorterColumnWidth = width\n\t\t\t\tshorterColumnIndex = j\n\t\t\t}\n\t\t}\n\n\t\tcolWidths[shorterColumnIndex]++\n\t}\n\n\tr.expandRowHeights(colWidths)\n\n\treturn\n}\n\n// shrinkTableWidth shrinks the table width.\nfunc (r *resizer) shrinkTableWidth() (colWidths []int) {\n\tcolWidths = r.maxColumnWidths()\n\n\t// Cut width of columns that are way too big.\n\tshrinkBiggestColumns := func(veryBigOnly bool) {\n\t\tfor {\n\t\t\ttotalWidth := sum(colWidths) + r.totalHorizontalBorder()\n\t\t\tif totalWidth <= r.tableWidth {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tbigColumnIndex := -math.MaxInt32\n\t\t\tbigColumnWidth := -math.MaxInt32\n\n\t\t\tfor j, width := range colWidths {\n\t\t\t\tif width == r.columns[j].fixedWidth {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif veryBigOnly {\n\t\t\t\t\tif width >= (r.tableWidth/2) && width > bigColumnWidth { //nolint:mnd\n\t\t\t\t\t\tbigColumnWidth = width\n\t\t\t\t\t\tbigColumnIndex = j\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif width > bigColumnWidth {\n\t\t\t\t\t\tbigColumnWidth = width\n\t\t\t\t\t\tbigColumnIndex = j\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif bigColumnIndex < 0 || colWidths[bigColumnIndex] == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcolWidths[bigColumnIndex]--\n\t\t}\n\t}\n\n\t// Cut width of columns that differ the most from the median.\n\tshrinkToMedian := func() {\n\t\tfor {\n\t\t\ttotalWidth := sum(colWidths) + r.totalHorizontalBorder()\n\t\t\tif totalWidth <= r.tableWidth {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tbiggestDiffToMedian := -math.MaxInt32\n\t\t\tbiggestDiffToMedianIndex := -math.MaxInt32\n\n\t\t\tfor j, width := range colWidths {\n\t\t\t\tif width == r.columns[j].fixedWidth {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tdiffToMedian := width - r.columns[j].median\n\t\t\t\tif diffToMedian > 0 && diffToMedian > biggestDiffToMedian {\n\t\t\t\t\tbiggestDiffToMedian = diffToMedian\n\t\t\t\t\tbiggestDiffToMedianIndex = j\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif biggestDiffToMedianIndex <= 0 || colWidths[biggestDiffToMedianIndex] == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcolWidths[biggestDiffToMedianIndex]--\n\t\t}\n\t}\n\n\tshrinkBiggestColumns(true)\n\tshrinkToMedian()\n\tshrinkBiggestColumns(false)\n\n\tr.expandRowHeights(colWidths)\n\n\treturn colWidths\n}\n\n// expandRowHeights expands the row heights.\nfunc (r *resizer) expandRowHeights(colWidths []int) {\n\tr.rowHeights = r.defaultRowHeights()\n\tif !r.wrap {\n\t\treturn\n\t}\n\thasHeaders := len(r.headers) > 0\n\n\tfor i, row := range r.allRows {\n\t\tfor j, cell := range row {\n\t\t\t// NOTE(@andreynering): Headers always have a height of 1 (+ padding), even when wrap is enabled.\n\t\t\tif hasHeaders && i == 0 {\n\t\t\t\tr.rowHeights[i] = 1 + r.yPaddingForCell(i, j)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\theight := r.detectContentHeight(cell, colWidths[j]-r.xPaddingForCol(j)) + r.yPaddingForCell(i, j)\n\t\t\tr.rowHeights[i] = max(r.rowHeights[i], height)\n\t\t}\n\t}\n}\n\n// defaultRowHeights returns the default row heights.\nfunc (r *resizer) defaultRowHeights() (rowHeights []int) {\n\trowHeights = make([]int, len(r.allRows))\n\tfor i := range rowHeights {\n\t\tif i < len(r.rowHeights) {\n\t\t\trowHeights[i] = r.rowHeights[i]\n\t\t}\n\t\trowHeights[i] = max(rowHeights[i], 1)\n\t}\n\treturn\n}\n\n// maxColumnWidths returns the maximum column widths.\nfunc (r *resizer) maxColumnWidths() []int {\n\tmaxColumnWidths := make([]int, len(r.columns))\n\tfor i, col := range r.columns {\n\t\tif col.fixedWidth > 0 {\n\t\t\tmaxColumnWidths[i] = col.fixedWidth\n\t\t} else {\n\t\t\tmaxColumnWidths[i] = col.max + r.xPaddingForCol(col.index)\n\t\t}\n\t}\n\treturn maxColumnWidths\n}\n\n// columnCount returns the column count.\nfunc (r *resizer) columnCount() int {\n\treturn len(r.columns)\n}\n\n// maxCharCount returns the maximum character count.\nfunc (r *resizer) maxCharCount() int {\n\tvar count int\n\tfor _, col := range r.columns {\n\t\tif col.fixedWidth > 0 {\n\t\t\tcount += col.fixedWidth - r.xPaddingForCol(col.index)\n\t\t} else {\n\t\t\tcount += col.max\n\t\t}\n\t}\n\treturn count\n}\n\n// maxTotal returns the maximum total width.\nfunc (r *resizer) maxTotal() (maxTotal int) {\n\tfor j, column := range r.columns {\n\t\tif column.fixedWidth > 0 {\n\t\t\tmaxTotal += column.fixedWidth\n\t\t} else {\n\t\t\tmaxTotal += column.max + r.xPaddingForCol(j)\n\t\t}\n\t}\n\treturn\n}\n\n// totalHorizontalPadding returns the total padding.\nfunc (r *resizer) totalHorizontalPadding() (totalHorizontalPadding int) {\n\tfor _, col := range r.columns {\n\t\ttotalHorizontalPadding += col.xPadding\n\t}\n\treturn\n}\n\n// xPaddingForCol returns the horizontal padding for a column.\nfunc (r *resizer) xPaddingForCol(j int) int {\n\tif j >= len(r.columns) {\n\t\treturn 0\n\t}\n\treturn r.columns[j].xPadding\n}\n\n// yPaddingForCell returns the horizontal padding for a cell.\nfunc (r *resizer) yPaddingForCell(i, j int) int {\n\tif i >= len(r.yPaddings) || j >= len(r.yPaddings[i]) {\n\t\treturn 0\n\t}\n\treturn r.yPaddings[i][j]\n}\n\n// totalHorizontalBorder returns the total border.\nfunc (r *resizer) totalHorizontalBorder() int {\n\treturn btoi(r.borderLeft) + btoi(r.borderRight) + (r.columnCount()-1)*btoi(r.borderColumn)\n}\n\n// detectContentHeight detects the content height.\nfunc (r *resizer) detectContentHeight(content string, width int) (height int) {\n\tif width == 0 {\n\t\treturn 1\n\t}\n\tcontent = strings.ReplaceAll(content, \"\\r\\n\", \"\\n\")\n\tfor _, line := range strings.Split(content, \"\\n\") {\n\t\theight += strings.Count(ansi.Wrap(line, width, \"\"), \"\\n\") + 1\n\t}\n\treturn\n}\n\n// visibleRowIndexes calculates the indexes of the first and last visible rows\n// according to the current yOffset and tableHeight. If the table height is not\n// fixed or if the last row is visible, lastVisibleRowIndex will be -2.\n// Note that the calculated indexes ignore the header row, so 0 corresponds to\n// the first data row. The header row is always visible if it exists.\n// The last return value is the number of cells that the overflow row should\n// take up for the table to fill the available height.\nfunc (r *resizer) visibleRowIndexes() (firstVisibleRowIndex, lastVisibleRowIndex, overflowHeight int) {\n\tif !r.useManualHeight {\n\t\treturn 0, -2, 0\n\t}\n\n\thasHeaders := len(r.headers) > 0\n\tlastIndex := len(r.allRows) - 1 - btoi(hasHeaders)\n\n\t// Account for fixed elements (top/bottom borders, headers with their border).\n\tavailable := r.tableHeight - btoi(r.borderTop) -\n\t\tbtoi(r.borderBottom) -\n\t\tbton(hasHeaders, r.rowHeights[0]) -\n\t\tbtoi(hasHeaders && r.borderHeader)\n\n\t// The first row we add does not need a row border.\n\tavailable += btoi(r.borderRow)\n\n\t// Start from the offset with no visible rows.\n\tfirstVisibleRowIndex = r.yOffset\n\tlastVisibleRowIndex = firstVisibleRowIndex - 1\n\n\t// First add rows at the bottom until we reach the available height, or the last row.\n\tfor available > 0 && lastVisibleRowIndex < lastIndex {\n\t\trow := r.rowHeights[lastVisibleRowIndex+1+btoi(hasHeaders)] + btoi(r.borderRow)\n\t\toverflow := bton(lastVisibleRowIndex+1 < lastIndex, 1+btoi(r.borderRow)+r.yPaddingForCell(lastVisibleRowIndex+2, 0))\n\n\t\tif available-row-overflow < 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tlastVisibleRowIndex++\n\t\tavailable -= row\n\t}\n\n\tif lastVisibleRowIndex == lastIndex {\n\t\t// Then add rows at the top until we reach the available height, or the first row.\n\t\tfor available > 0 && firstVisibleRowIndex > 0 {\n\t\t\trow := r.rowHeights[firstVisibleRowIndex-1+btoi(hasHeaders)] + btoi(r.borderRow)\n\n\t\t\tif available-row < 0 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tfirstVisibleRowIndex--\n\t\t\tavailable -= row\n\t\t}\n\t}\n\n\tif lastVisibleRowIndex >= lastIndex {\n\t\treturn firstVisibleRowIndex, -2, 0\n\t}\n\n\toverflow := 1 + r.yPaddingForCell(lastVisibleRowIndex+1, 0)\n\n\treturn firstVisibleRowIndex, lastVisibleRowIndex, overflow\n}\n"
  },
  {
    "path": "table/rows.go",
    "content": "package table\n\n// Data is the interface that wraps the basic methods of a table model.\ntype Data interface {\n\t// At returns the contents of the cell at the given index.\n\tAt(row, cell int) string\n\n\t// Rows returns the number of rows in the table.\n\tRows() int\n\n\t// Columns returns the number of columns in the table.\n\tColumns() int\n}\n\n// StringData is a string-based implementation of the Data interface.\ntype StringData struct {\n\trows    [][]string\n\tcolumns int\n}\n\n// NewStringData creates a new StringData with the given number of columns.\nfunc NewStringData(rows ...[]string) *StringData {\n\tm := StringData{columns: 0}\n\n\tfor _, row := range rows {\n\t\tm.columns = max(m.columns, len(row))\n\t\tm.rows = append(m.rows, row)\n\t}\n\n\treturn &m\n}\n\n// Append appends the given row to the table.\nfunc (m *StringData) Append(row []string) {\n\tm.columns = max(m.columns, len(row))\n\tm.rows = append(m.rows, row)\n}\n\n// At returns the contents of the cell at the given index.\nfunc (m *StringData) At(row, cell int) string {\n\tif row >= len(m.rows) || cell >= len(m.rows[row]) {\n\t\treturn \"\"\n\t}\n\n\treturn m.rows[row][cell]\n}\n\n// Columns returns the number of columns in the table.\nfunc (m *StringData) Columns() int {\n\treturn m.columns\n}\n\n// Item appends the given row to the table.\nfunc (m *StringData) Item(rows ...string) *StringData {\n\tm.columns = max(m.columns, len(rows))\n\tm.rows = append(m.rows, rows)\n\treturn m\n}\n\n// Rows returns the number of rows in the table.\nfunc (m *StringData) Rows() int {\n\treturn len(m.rows)\n}\n\n// Filter applies a filter on some data.\ntype Filter struct {\n\tdata   Data\n\tfilter func(row int) bool\n}\n\n// NewFilter initializes a new Filter.\nfunc NewFilter(data Data) *Filter {\n\treturn &Filter{data: data}\n}\n\n// Filter applies the given filter function to the data.\nfunc (m *Filter) Filter(f func(row int) bool) *Filter {\n\tm.filter = f\n\treturn m\n}\n\n// At returns the row at the given index.\nfunc (m *Filter) At(row, cell int) string {\n\tj := 0\n\tfor i := range m.data.Rows() {\n\t\tif m.filter(i) {\n\t\t\tif j == row {\n\t\t\t\treturn m.data.At(i, cell)\n\t\t\t}\n\n\t\t\tj++\n\t\t}\n\t}\n\n\treturn \"\"\n}\n\n// Columns returns the number of columns in the table.\nfunc (m *Filter) Columns() int {\n\treturn m.data.Columns()\n}\n\n// Rows returns the number of rows in the table.\nfunc (m *Filter) Rows() int {\n\tj := 0\n\tfor i := range m.data.Rows() {\n\t\tif m.filter(i) {\n\t\t\tj++\n\t\t}\n\t}\n\n\treturn j\n}\n\n// DataToMatrix is a helper function that converts an object that implements the\n// Data interface into a table.\nfunc DataToMatrix(data Data) (rows [][]string) {\n\tnumRows := data.Rows()\n\tnumCols := data.Columns()\n\trows = make([][]string, numRows)\n\n\tfor i := range numRows {\n\t\trows[i] = make([]string, numCols)\n\n\t\tfor j := range numCols {\n\t\t\trows[i][j] = data.At(i, j)\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "table/table.go",
    "content": "// Package table provides a styled table renderer for terminals.\npackage table\n\nimport (\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// HeaderRow denotes the header's row index used when rendering headers. Use\n// this value when looking to customize header styles in StyleFunc.\nconst HeaderRow int = -1\n\n// StyleFunc is the style function that determines the style of a Cell.\n//\n// It takes the row and column of the cell as an input and determines the\n// lipgloss Style to use for that cell position.\n//\n// Example:\n//\n//\tt := table.New().\n//\t    Headers(\"Name\", \"Age\").\n//\t    Row(\"Kini\", 4).\n//\t    Row(\"Eli\", 1).\n//\t    Row(\"Iris\", 102).\n//\t    StyleFunc(func(row, col int) lipgloss.Style {\n//\t        switch {\n//\t           case row == 0:\n//\t               return HeaderStyle\n//\t           case row%2 == 0:\n//\t               return EvenRowStyle\n//\t           default:\n//\t               return OddRowStyle\n//\t           }\n//\t    })\ntype StyleFunc func(row, col int) lipgloss.Style\n\n// DefaultStyles is a TableStyleFunc that returns a new Style with no attributes.\nfunc DefaultStyles(_, _ int) lipgloss.Style {\n\treturn lipgloss.NewStyle()\n}\n\n// Table is a type for rendering tables.\ntype Table struct {\n\tbaseStyle lipgloss.Style\n\tstyleFunc StyleFunc\n\tborder    lipgloss.Border\n\n\tborderTop    bool\n\tborderBottom bool\n\tborderLeft   bool\n\tborderRight  bool\n\tborderHeader bool\n\tborderColumn bool\n\tborderRow    bool\n\n\tborderStyle lipgloss.Style\n\theaders     []string\n\tdata        Data\n\n\twidth           int\n\theight          int\n\tuseManualHeight bool\n\tyOffset         int\n\twrap            bool\n\n\twidths  []int\n\theights []int\n\n\tfirstVisibleRowIndex int\n\tlastVisibleRowIndex  int\n\toverflowHeight       int\n}\n\n// New returns a new Table that can be modified through different\n// attributes.\n//\n// By default, a table has normal border, no styling, and no rows.\nfunc New() *Table {\n\treturn &Table{\n\t\tstyleFunc:    DefaultStyles,\n\t\tborder:       lipgloss.NormalBorder(),\n\t\tborderBottom: true,\n\t\tborderColumn: true,\n\t\tborderHeader: true,\n\t\tborderLeft:   true,\n\t\tborderRight:  true,\n\t\tborderTop:    true,\n\t\twrap:         true,\n\t\tdata:         NewStringData(),\n\t}\n}\n\n// ClearRows clears the table rows.\nfunc (t *Table) ClearRows() *Table {\n\tt.data = NewStringData()\n\treturn t\n}\n\n// BaseStyle sets the base style for the whole table. If you need to set a\n// background color for the whole table, use this.\nfunc (t *Table) BaseStyle(baseStyle lipgloss.Style) *Table {\n\tt.baseStyle = baseStyle\n\tt.borderStyle = t.borderStyle.Inherit(baseStyle)\n\treturn t\n}\n\n// StyleFunc sets the style for a cell based on it's position (row, column).\nfunc (t *Table) StyleFunc(style StyleFunc) *Table {\n\tt.styleFunc = style\n\treturn t\n}\n\n// style returns the style for a cell based on it's position (row, column).\nfunc (t *Table) style(row, col int) lipgloss.Style {\n\tif t.styleFunc == nil {\n\t\treturn t.baseStyle\n\t}\n\treturn t.styleFunc(row, col).Inherit(t.baseStyle)\n}\n\n// Data sets the table data.\nfunc (t *Table) Data(data Data) *Table {\n\tt.data = data\n\treturn t\n}\n\n// GetData returns the table data.\nfunc (t *Table) GetData() Data {\n\treturn t.data\n}\n\n// Rows appends rows to the table data.\nfunc (t *Table) Rows(rows ...[]string) *Table {\n\tfor _, row := range rows {\n\t\tswitch t.data.(type) {\n\t\tcase *StringData:\n\t\t\tt.data.(*StringData).Append(row)\n\t\t}\n\t}\n\treturn t\n}\n\n// Row appends a row to the table data.\nfunc (t *Table) Row(row ...string) *Table {\n\tswitch t.data.(type) {\n\tcase *StringData:\n\t\tt.data.(*StringData).Append(row)\n\t}\n\treturn t\n}\n\n// Headers sets the table headers.\nfunc (t *Table) Headers(headers ...string) *Table {\n\tt.headers = headers\n\treturn t\n}\n\n// GetHeaders returns the table headers.\nfunc (t *Table) GetHeaders() []string {\n\treturn t.headers\n}\n\n// Border sets the table border.\nfunc (t *Table) Border(border lipgloss.Border) *Table {\n\tt.border = border\n\treturn t\n}\n\n// BorderTop sets the top border.\nfunc (t *Table) BorderTop(v bool) *Table {\n\tt.borderTop = v\n\treturn t\n}\n\n// BorderBottom sets the bottom border.\nfunc (t *Table) BorderBottom(v bool) *Table {\n\tt.borderBottom = v\n\treturn t\n}\n\n// BorderLeft sets the left border.\nfunc (t *Table) BorderLeft(v bool) *Table {\n\tt.borderLeft = v\n\treturn t\n}\n\n// BorderRight sets the right border.\nfunc (t *Table) BorderRight(v bool) *Table {\n\tt.borderRight = v\n\treturn t\n}\n\n// BorderHeader sets the header separator border.\nfunc (t *Table) BorderHeader(v bool) *Table {\n\tt.borderHeader = v\n\treturn t\n}\n\n// BorderColumn sets the column border separator.\nfunc (t *Table) BorderColumn(v bool) *Table {\n\tt.borderColumn = v\n\treturn t\n}\n\n// BorderRow sets the row border separator.\nfunc (t *Table) BorderRow(v bool) *Table {\n\tt.borderRow = v\n\treturn t\n}\n\n// BorderStyle sets the style for the table border.\nfunc (t *Table) BorderStyle(style lipgloss.Style) *Table {\n\tt.borderStyle = style.Inherit(t.baseStyle)\n\treturn t\n}\n\n// GetBorderTop gets the top border.\nfunc (t *Table) GetBorderTop() bool {\n\treturn t.borderTop\n}\n\n// GetBorderBottom gets the bottom border.\nfunc (t *Table) GetBorderBottom() bool {\n\treturn t.borderBottom\n}\n\n// GetBorderLeft gets the left border.\nfunc (t *Table) GetBorderLeft() bool {\n\treturn t.borderLeft\n}\n\n// GetBorderRight gets the right border.\nfunc (t *Table) GetBorderRight() bool {\n\treturn t.borderRight\n}\n\n// GetBorderHeader gets the header separator border.\nfunc (t *Table) GetBorderHeader() bool {\n\treturn t.borderHeader\n}\n\n// GetBorderColumn gets the column border separator.\nfunc (t *Table) GetBorderColumn() bool {\n\treturn t.borderColumn\n}\n\n// GetBorderRow gets the row border separator.\nfunc (t *Table) GetBorderRow() bool {\n\treturn t.borderRow\n}\n\n// Width sets the table width, this auto-sizes the columns to fit the width by\n// either expanding or contracting the widths of each column as a best effort\n// approach.\nfunc (t *Table) Width(w int) *Table {\n\tt.width = w\n\treturn t\n}\n\n// Height sets the table height.\nfunc (t *Table) Height(h int) *Table {\n\tt.height = h\n\tt.useManualHeight = true\n\treturn t\n}\n\n// GetHeight returns the height of the table.\nfunc (t *Table) GetHeight() int {\n\treturn t.height\n}\n\n// YOffset sets the table rendering offset.\nfunc (t *Table) YOffset(o int) *Table {\n\tt.yOffset = o\n\treturn t\n}\n\n// GetYOffset returns the table rendering offset.\nfunc (t *Table) GetYOffset() int {\n\treturn t.yOffset\n}\n\n// FirstVisibleRowIndex returns the index of the first visible row in the table.\nfunc (t *Table) FirstVisibleRowIndex() int {\n\treturn t.firstVisibleRowIndex\n}\n\n// LastVisibleRowIndex returns the index of the last visible row in the table.\nfunc (t *Table) LastVisibleRowIndex() int {\n\treturn t.lastVisibleRowIndex\n}\n\n// VisibleRows returns the number of visible rows in the table.\nfunc (t *Table) VisibleRows() int {\n\tif t.lastVisibleRowIndex == -2 {\n\t\treturn t.data.Rows() - t.firstVisibleRowIndex\n\t}\n\treturn t.lastVisibleRowIndex - t.firstVisibleRowIndex + 1\n}\n\n// Wrap dictates whether or not the table content should wrap.\n//\n// This only applies to data cells. Headers are never wrapped.\nfunc (t *Table) Wrap(w bool) *Table {\n\tt.wrap = w\n\treturn t\n}\n\n// String returns the table as a string.\nfunc (t *Table) String() string {\n\thasHeaders := len(t.headers) > 0\n\thasRows := t.data != nil && t.data.Rows() > 0\n\n\tif !hasHeaders && !hasRows {\n\t\treturn \"\"\n\t}\n\n\t// Add empty cells to the headers, until it's the same length as the longest\n\t// row (only if there are at headers in the first place).\n\tif hasHeaders {\n\t\tfor i := len(t.headers); i < t.data.Columns(); i++ {\n\t\t\tt.headers = append(t.headers, \"\")\n\t\t}\n\t}\n\n\t// Do all the sizing calculations for width and height.\n\tt.resize()\n\n\tvar sb strings.Builder\n\n\tif t.borderTop {\n\t\tsb.WriteString(t.constructTopBorder())\n\t\tsb.WriteString(\"\\n\")\n\t}\n\n\tif hasHeaders {\n\t\tsb.WriteString(t.constructHeaders())\n\t}\n\n\tvar bottom string\n\tif t.borderBottom {\n\t\tbottom = t.constructBottomBorder()\n\t}\n\n\t// If there are no data rows render nothing.\n\tif t.data.Rows() > 0 {\n\t\tfor r := t.firstVisibleRowIndex; r < t.data.Rows(); r++ {\n\t\t\tif t.lastVisibleRowIndex != -2 && r > t.lastVisibleRowIndex {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tsb.WriteString(t.constructRow(r, false))\n\t\t}\n\n\t\t// Add an overflow row to show that there are more rows not being rendered.\n\t\tif t.lastVisibleRowIndex != -2 {\n\t\t\tsb.WriteString(t.constructRow(t.lastVisibleRowIndex+1, true))\n\t\t}\n\t}\n\n\tsb.WriteString(bottom)\n\n\treturn lipgloss.NewStyle().\n\t\tMaxHeight(min(t.height, t.computeHeight())).\n\t\tMaxWidth(t.width).\n\t\tRender(strings.TrimSuffix(sb.String(), \"\\n\"))\n}\n\n// computeHeight computes the height of the table in it's current configuration.\nfunc (t *Table) computeHeight() int {\n\thasHeaders := len(t.headers) > 0\n\treturn sum(t.heights) - 1 + btoi(hasHeaders) +\n\t\tbtoi(t.borderTop) + btoi(t.borderBottom) +\n\t\tbtoi(t.borderHeader) + t.data.Rows()*btoi(t.borderRow)\n}\n\n// Render returns the table as a string.\nfunc (t *Table) Render() string {\n\treturn t.String()\n}\n\n// constructTopBorder constructs the top border for the table given it's current\n// border configuration and data.\nfunc (t *Table) constructTopBorder() string {\n\tvar s strings.Builder\n\tif t.borderLeft {\n\t\ts.WriteString(t.borderStyle.Render(t.border.TopLeft))\n\t}\n\tfor i := range t.widths {\n\t\ts.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Top, t.widths[i])))\n\t\tif i < len(t.widths)-1 && t.borderColumn {\n\t\t\ts.WriteString(t.borderStyle.Render(t.border.MiddleTop))\n\t\t}\n\t}\n\tif t.borderRight {\n\t\ts.WriteString(t.borderStyle.Render(t.border.TopRight))\n\t}\n\treturn s.String()\n}\n\n// constructBottomBorder constructs the bottom border for the table given it's current\n// border configuration and data.\nfunc (t *Table) constructBottomBorder() string {\n\tvar s strings.Builder\n\tif t.borderLeft {\n\t\ts.WriteString(t.borderStyle.Render(t.border.BottomLeft))\n\t}\n\tfor i := range t.widths {\n\t\ts.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[i])))\n\t\tif i < len(t.widths)-1 && t.borderColumn {\n\t\t\ts.WriteString(t.borderStyle.Render(t.border.MiddleBottom))\n\t\t}\n\t}\n\tif t.borderRight {\n\t\ts.WriteString(t.borderStyle.Render(t.border.BottomRight))\n\t}\n\treturn s.String()\n}\n\n// constructHeaders constructs the headers for the table given it's current\n// header configuration and data.\nfunc (t *Table) constructHeaders() string {\n\tvar s strings.Builder\n\tcells := make([]string, 0, len(t.headers)*2+1)\n\theight := t.heights[0]\n\n\tleft := strings.Repeat(t.borderStyle.Render(t.border.Left)+\"\\n\", height)\n\tif t.borderLeft {\n\t\tcells = append(cells, left)\n\t}\n\n\tfor j, header := range t.headers {\n\t\tcellStyle := t.style(HeaderRow, j)\n\n\t\t// NOTE(@andreynering): We always truncate headers.\n\t\theader = t.truncateCell(header, HeaderRow, j)\n\n\t\tcells = append(cells,\n\t\t\tcellStyle.\n\t\t\t\tHeight(height-cellStyle.GetVerticalMargins()).\n\t\t\t\tWidth(t.widths[j]-cellStyle.GetHorizontalMargins()).\n\t\t\t\tRender(header),\n\t\t)\n\n\t\tif j < len(t.headers)-1 && t.borderColumn {\n\t\t\tcells = append(cells, left)\n\t\t}\n\t}\n\n\tif t.borderRight {\n\t\tright := strings.Repeat(t.borderStyle.Render(t.border.Right)+\"\\n\", height)\n\t\tcells = append(cells, right)\n\t}\n\n\tfor i, cell := range cells {\n\t\tcells[i] = strings.TrimRight(cell, \"\\n\")\n\t}\n\n\ts.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, cells...) + \"\\n\")\n\n\tif t.borderHeader {\n\t\tif t.borderLeft {\n\t\t\ts.WriteString(t.borderStyle.Render(t.border.MiddleLeft))\n\t\t}\n\t\tfor i := range t.headers {\n\t\t\ts.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Top, t.widths[i])))\n\t\t\tif i < len(t.headers)-1 && t.borderColumn {\n\t\t\t\ts.WriteString(t.borderStyle.Render(t.border.Middle))\n\t\t\t}\n\t\t}\n\t\tif t.borderRight {\n\t\t\ts.WriteString(t.borderStyle.Render(t.border.MiddleRight))\n\t\t}\n\t\ts.WriteString(\"\\n\")\n\t}\n\n\treturn s.String()\n}\n\n// constructRow constructs the row for the table given an index and row data\n// based on the current configuration. If isOverflow is true, the row is\n// rendered as an overflow row (using ellipsis).\nfunc (t *Table) constructRow(index int, isOverflow bool) string {\n\tvar s strings.Builder\n\tcells := make([]string, 0, t.data.Columns()*2+1)\n\n\thasHeaders := len(t.headers) > 0\n\n\tvar height int\n\tif !isOverflow {\n\t\theight = t.heights[index+btoi(hasHeaders)]\n\t} else {\n\t\theight = t.overflowHeight\n\t}\n\n\tleft := strings.Repeat(t.borderStyle.Render(t.border.Left)+\"\\n\", height)\n\tif t.borderLeft {\n\t\tcells = append(cells, left)\n\t}\n\n\tfor c := range t.data.Columns() {\n\t\tcell := \"…\"\n\t\tif !isOverflow {\n\t\t\tcell = t.data.At(index, c)\n\t\t}\n\n\t\tcellStyle := t.style(index, c)\n\t\tif !t.wrap {\n\t\t\tcell = t.truncateCell(cell, index, c)\n\t\t}\n\t\tcells = append(cells, cellStyle.\n\t\t\t// Account for the margins in the cell sizing.\n\t\t\tHeight(height-cellStyle.GetVerticalMargins()).\n\t\t\tMaxHeight(height).\n\t\t\tWidth(t.widths[c]-cellStyle.GetHorizontalMargins()).\n\t\t\tMaxWidth(t.widths[c]).\n\t\t\tRender(cell))\n\n\t\tif c < t.data.Columns()-1 && t.borderColumn {\n\t\t\tcells = append(cells, left)\n\t\t}\n\t}\n\n\tif t.borderRight {\n\t\tright := strings.Repeat(t.borderStyle.Render(t.border.Right)+\"\\n\", height)\n\t\tcells = append(cells, right)\n\t}\n\n\tfor i, cell := range cells {\n\t\tcells[i] = strings.TrimRight(cell, \"\\n\")\n\t}\n\n\ts.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, cells...) + \"\\n\")\n\n\tif t.borderRow && !isOverflow && index < t.data.Rows()-1 {\n\t\tif t.borderLeft {\n\t\t\ts.WriteString(t.borderStyle.Render(t.border.MiddleLeft))\n\t\t}\n\t\tfor i := range t.widths {\n\t\t\ts.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[i])))\n\t\t\tif i < len(t.widths)-1 && t.borderColumn {\n\t\t\t\ts.WriteString(t.borderStyle.Render(t.border.Middle))\n\t\t\t}\n\t\t}\n\t\tif t.borderRight {\n\t\t\ts.WriteString(t.borderStyle.Render(t.border.MiddleRight))\n\t\t}\n\t\ts.WriteString(\"\\n\")\n\t}\n\n\treturn s.String()\n}\n\nfunc (t *Table) truncateCell(cell string, rowIndex, colIndex int) string {\n\thasHeaders := len(t.headers) > 0\n\theight := t.heights[rowIndex+btoi(hasHeaders)]\n\tcellWidth := t.widths[colIndex]\n\tcellStyle := t.style(rowIndex, colIndex)\n\n\t// NOTE(@andreynering): We always truncate headers to 1 line.\n\tif rowIndex == HeaderRow {\n\t\theight = 1\n\t}\n\n\tlength := (cellWidth * height) - cellStyle.GetHorizontalPadding() - cellStyle.GetHorizontalMargins()\n\treturn ansi.Truncate(cell, length, \"…\")\n}\n"
  },
  {
    "path": "table/table_test.go",
    "content": "package table\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"github.com/charmbracelet/x/exp/golden\"\n)\n\nvar TableStyle = func(row, col int) lipgloss.Style {\n\tswitch {\n\tcase row == HeaderRow:\n\t\treturn lipgloss.NewStyle().Padding(0, 1).Align(lipgloss.Center)\n\tcase row%2 == 0:\n\t\treturn lipgloss.NewStyle().Padding(0, 1)\n\tdefault:\n\t\treturn lipgloss.NewStyle().Padding(0, 1)\n\t}\n}\n\nfunc TestTable(t *testing.T) {\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableWithBackground(t *testing.T) {\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tBaseStyle(lipgloss.NewStyle().Background(lipgloss.Color(\"18\"))).\n\t\tBorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color(\"15\"))).\n\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\treturn lipgloss.NewStyle().Foreground(lipgloss.Color(\"15\"))\n\t\t}).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableExample(t *testing.T) {\n\tHeaderStyle := lipgloss.NewStyle().Padding(0, 1).Align(lipgloss.Center)\n\tEvenRowStyle := lipgloss.NewStyle().Padding(0, 1)\n\tOddRowStyle := lipgloss.NewStyle().Padding(0, 1)\n\n\trows := [][]string{\n\t\t{\"Chinese\", \"您好\", \"你好\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Здравствуйте\", \"Привет\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tBorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color(\"99\"))).\n\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\tswitch {\n\t\t\tcase row == HeaderRow:\n\t\t\t\treturn HeaderStyle\n\t\t\tcase row%2 == 0:\n\t\t\t\treturn EvenRowStyle\n\t\t\tdefault:\n\t\t\t\treturn OddRowStyle\n\t\t\t}\n\t\t}).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...)\n\n\t// You can also add tables row-by-row\n\ttable.Row(\"English\", \"You look absolutely fabulous.\", \"How's it going?\")\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableEmpty(t *testing.T) {\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\")\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableNoStyleFunc(t *testing.T) {\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(nil).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableYOffset(t *testing.T) {\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\").\n\t\tYOffset(1).\n\t\tHeight(8)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableBorder(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttable := New().\n\t\tBorder(lipgloss.DoubleBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableSetRows(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestMoreCellsThanHeaders(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\").\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestMoreCellsThanHeadersExtra(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\", \"Privet\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\").\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableNoHeaders(t *testing.T) {\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableNoColumnSeparators(t *testing.T) {\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tBorderColumn(false).\n\t\tStyleFunc(TableStyle).\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableNoColumnSeparatorsWithHeaders(t *testing.T) {\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tBorderColumn(false).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestInnerBordersOnly(t *testing.T) {\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tBorderColumn(false).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\").\n\t\tBorderTop(false).\n\t\tBorderRight(false).\n\t\tBorderBottom(false).\n\t\tBorderLeft(false).\n\t\tBorderRow(true).\n\t\tBorderColumn(true)\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestBorderColumnsWithExtraRows(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\", \"Privet\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tBorderColumn(false).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\").\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestNew(t *testing.T) {\n\ttable := New()\n\texpected := \"\"\n\tif table.String() != expected {\n\t\tt.Fatalf(\"expected:\\n\\n%s\\n\\ngot:\\n\\n%s\", expected, table.String())\n\t}\n}\n\nfunc TestTableUnsetBorders(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...).\n\t\tBorderTop(false).\n\t\tBorderBottom(false).\n\t\tBorderLeft(false).\n\t\tBorderRight(false)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableUnsetHeaderSeparator(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...).\n\t\tBorderHeader(false).\n\t\tBorderTop(false).\n\t\tBorderBottom(false).\n\t\tBorderLeft(false).\n\t\tBorderRight(false)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableUnsetHeaderSeparatorWithBorder(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...).\n\t\tBorderHeader(false)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableRowSeparators(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tBorderRow(true).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableHeights(t *testing.T) {\n\tstyleFunc := func(row, col int) lipgloss.Style {\n\t\tif row == HeaderRow {\n\t\t\treturn lipgloss.NewStyle().Padding(0, 1)\n\t\t}\n\t\tif col == 0 {\n\t\t\treturn lipgloss.NewStyle().Width(18).Padding(1)\n\t\t}\n\t\treturn lipgloss.NewStyle().Width(25).Padding(1, 2)\n\t}\n\n\trows := [][]string{\n\t\t{\"Chutar o balde\", `Literally translates to \"kick the bucket.\" It's used when someone gives up or loses patience.`},\n\t\t{\"Engolir sapos\", `Literally means \"to swallow frogs.\" It's used to describe someone who has to tolerate or endure unpleasant situations.`},\n\t\t{\"Arroz de festa\", `Literally means \"party rice.\" It´s used to refer to someone who shows up everywhere.`},\n\t}\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(styleFunc).\n\t\tHeaders(\"EXPRESSION\", \"MEANING\").\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableMultiLineRowSeparator(t *testing.T) {\n\tstyleFunc := func(row, col int) lipgloss.Style {\n\t\tif row == HeaderRow {\n\t\t\treturn lipgloss.NewStyle().Padding(0, 1)\n\t\t}\n\t\tif col == 0 {\n\t\t\treturn lipgloss.NewStyle().Width(18).Padding(1)\n\t\t}\n\t\treturn lipgloss.NewStyle().Width(25).Padding(1, 2)\n\t}\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(styleFunc).\n\t\tHeaders(\"EXPRESSION\", \"MEANING\").\n\t\tBorderRow(true).\n\t\tRow(\"Chutar o balde\", `Literally translates to \"kick the bucket.\" It's used when someone gives up or loses patience.`).\n\t\tRow(\"Engolir sapos\", `Literally means \"to swallow frogs.\" It's used to describe someone who has to tolerate or endure unpleasant situations.`).\n\t\tRow(\"Arroz de festa\", `Literally means \"party rice.\" It´s used to refer to someone who shows up everywhere.`)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableWidthExpand(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttable := New().\n\t\tWidth(80).\n\t\tStyleFunc(TableStyle).\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...)\n\n\tif lipgloss.Width(table.String()) != 80 {\n\t\tt.Fatalf(\"expected table width to be 80, got %d\", lipgloss.Width(table.String()))\n\t}\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableWidthShrink(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\tt.Run(\"NoBorders\", func(t *testing.T) {\n\t\ttable := New().\n\t\t\tWidth(30).\n\t\t\tStyleFunc(TableStyle).\n\t\t\tBorderLeft(false).\n\t\t\tBorderRight(false).\n\t\t\tBorder(lipgloss.NormalBorder()).\n\t\t\tBorderColumn(false).\n\t\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\t\tRows(rows...)\n\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t})\n\n\tt.Run(\"DefaultBorders\", func(t *testing.T) {\n\t\ttable := New().\n\t\t\tWidth(30).\n\t\t\tStyleFunc(TableStyle).\n\t\t\tBorder(lipgloss.NormalBorder()).\n\t\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\t\tRows(rows...)\n\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t})\n\n\tt.Run(\"OutlineBordersOnly\", func(t *testing.T) {\n\t\ttable := New().\n\t\t\tWidth(30).\n\t\t\tStyleFunc(TableStyle).\n\t\t\tBorder(lipgloss.NormalBorder()).\n\t\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\t\tRows(rows...).\n\t\t\tBorderTop(true).\n\t\t\tBorderBottom(true).\n\t\t\tBorderLeft(true).\n\t\t\tBorderRight(true).\n\t\t\tBorderColumn(false).\n\t\t\tBorderRow(false).\n\t\t\tBorderHeader(true)\n\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t})\n}\n\nfunc TestTableWidthSmartCrop(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Kini\", \"40\", \"New York\"},\n\t\t{\"Eli\", \"30\", \"London\"},\n\t\t{\"Iris\", \"20\", \"Paris\"},\n\t}\n\n\ttable := New().\n\t\tWidth(25).\n\t\tStyleFunc(TableStyle).\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tHeaders(\"Name\", \"Age of Person\", \"Location\").\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableWidthSmartCropExtensive(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"您好\", \"你好\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Arabic\", \"أهلين\", \"أهلا\"},\n\t\t{\"Russian\", \"Здравствуйте\", \"Привет\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t\t{\"English\", \"You look absolutely fabulous.\", \"How's it going?\"},\n\t}\n\n\ttable := New().\n\t\tWidth(18).\n\t\tStyleFunc(TableStyle).\n\t\tBorder(lipgloss.ThickBorder()).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tWrap(false).\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableWidthSmartCropTiny(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"您好\", \"你好\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Здравствуйте\", \"Привет\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t\t{\"English\", \"You look absolutely fabulous.\", \"How's it going?\"},\n\t}\n\n\ttable := New().\n\t\tWidth(1).\n\t\tStyleFunc(TableStyle).\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableWidths(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttable := New().\n\t\tWidth(30).\n\t\tStyleFunc(TableStyle).\n\t\tBorderLeft(false).\n\t\tBorderRight(false).\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tBorderColumn(false).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestFilter(t *testing.T) {\n\tdata := NewStringData().\n\t\tItem(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tItem(\"French\", \"Bonjour\", \"Salut\").\n\t\tItem(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tItem(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tItem(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\tfilter := NewFilter(data).Filter(func(row int) bool {\n\t\treturn data.At(row, 0) != \"French\"\n\t})\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tData(filter)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestFilterInverse(t *testing.T) {\n\tdata := NewStringData().\n\t\tItem(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tItem(\"French\", \"Bonjour\", \"Salut\").\n\t\tItem(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tItem(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tItem(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\tfilter := NewFilter(data).Filter(func(row int) bool {\n\t\treturn data.At(row, 0) == \"French\"\n\t})\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tData(filter)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableANSI(t *testing.T) {\n\tconst code = \"\\x1b[31mC\\x1b[0m\\x1b[32mo\\x1b[0m\\x1b[34md\\x1b[0m\\x1b[33me\\x1b[0m\"\n\n\trows := [][]string{\n\t\t{\"Apple\", \"Red\", \"\\x1b[31m31\\x1b[0m\"},\n\t\t{\"Lime\", \"Green\", \"\\x1b[32m32\\x1b[0m\"},\n\t\t{\"Banana\", \"Yellow\", \"\\x1b[33m33\\x1b[0m\"},\n\t\t{\"Blueberry\", \"Blue\", \"\\x1b[34m34\\x1b[0m\"},\n\t}\n\n\ttable := New().\n\t\tWidth(29).\n\t\tStyleFunc(TableStyle).\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tHeaders(\"Fruit\", \"Color\", code).\n\t\tRows(rows...)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableHeightExact(t *testing.T) {\n\ttable := New().\n\t\tHeight(9).\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableHeightExtra(t *testing.T) {\n\ttable := New().\n\t\tHeight(100).\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableHeightShrink(t *testing.T) {\n\theaders := []string{\"LANGUAGE\", \"FORMAL\", \"INFORMAL\"}\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\tpaddingStyleFunc := func(row, col int) lipgloss.Style {\n\t\treturn TableStyle(row, col).Padding(1)\n\t}\n\n\tt.Run(\"NoBorderRow\", func(t *testing.T) {\n\t\tfor i := 1; i <= 9; i++ {\n\t\t\tt.Run(fmt.Sprintf(\"HeightOf%02d\", i), func(t *testing.T) {\n\t\t\t\ttable := New().\n\t\t\t\t\tHeight(i).\n\t\t\t\t\tBorder(lipgloss.NormalBorder()).\n\t\t\t\t\tBorderRow(false).\n\t\t\t\t\tStyleFunc(TableStyle).\n\t\t\t\t\tHeaders(headers...).\n\t\t\t\t\tRows(rows...)\n\t\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"WithBorderRow\", func(t *testing.T) {\n\t\tfor i := 1; i <= 13; i++ {\n\t\t\tt.Run(fmt.Sprintf(\"HeightOf%02d\", i), func(t *testing.T) {\n\t\t\t\ttable := New().\n\t\t\t\t\tHeight(i).\n\t\t\t\t\tBorder(lipgloss.NormalBorder()).\n\t\t\t\t\tBorderRow(true).\n\t\t\t\t\tStyleFunc(TableStyle).\n\t\t\t\t\tHeaders(headers...).\n\t\t\t\t\tRows(rows...)\n\t\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"NoBorderRowPadding\", func(t *testing.T) {\n\t\tfor i := 1; i <= 21; i++ {\n\t\t\tt.Run(fmt.Sprintf(\"HeightOf%02d\", i), func(t *testing.T) {\n\t\t\t\ttable := New().\n\t\t\t\t\tHeight(i).\n\t\t\t\t\tBorder(lipgloss.NormalBorder()).\n\t\t\t\t\tBorderRow(false).\n\t\t\t\t\tStyleFunc(paddingStyleFunc).\n\t\t\t\t\tHeaders(headers...).\n\t\t\t\t\tRows(rows...)\n\t\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"WithBorderRowPadding\", func(t *testing.T) {\n\t\tfor i := 1; i <= 25; i++ {\n\t\t\tt.Run(fmt.Sprintf(\"HeightOf%02d\", i), func(t *testing.T) {\n\t\t\t\ttable := New().\n\t\t\t\t\tHeight(i).\n\t\t\t\t\tBorder(lipgloss.NormalBorder()).\n\t\t\t\t\tBorderRow(true).\n\t\t\t\t\tStyleFunc(paddingStyleFunc).\n\t\t\t\t\tHeaders(headers...).\n\t\t\t\t\tRows(rows...)\n\t\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestTableHeightWithYOffset(t *testing.T) {\n\t// This test exists to check for a bug/edge case when the table has an\n\t// offset and the height is set.\n\n\ttable := New().\n\t\tHeight(8).\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tStyleFunc(TableStyle).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\").\n\t\tYOffset(1)\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestStyleFunc(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tstyle StyleFunc\n\t}{\n\t\t{\n\t\t\t\"RightAlignedTextWithMargins\",\n\t\t\tfunc(row, col int) lipgloss.Style {\n\t\t\t\tswitch {\n\t\t\t\tcase row == HeaderRow:\n\t\t\t\t\treturn lipgloss.NewStyle().Align(lipgloss.Center)\n\t\t\t\tdefault:\n\t\t\t\t\treturn lipgloss.NewStyle().Margin(0, 1).Align(lipgloss.Right)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"MarginAndPaddingSet\",\n\t\t\t// this test case uses background colors to differentiate margins\n\t\t\t// and padding.\n\t\t\tfunc(row, col int) lipgloss.Style {\n\t\t\t\tswitch {\n\t\t\t\tcase row == HeaderRow:\n\t\t\t\t\treturn lipgloss.NewStyle().Align(lipgloss.Center)\n\t\t\t\tdefault:\n\t\t\t\t\treturn lipgloss.NewStyle().\n\t\t\t\t\t\tPadding(1).\n\t\t\t\t\t\tMargin(1).\n\t\t\t\t\t\t// keeping right-aligned text as it's the most likely to\n\t\t\t\t\t\t// be broken when truncated.\n\t\t\t\t\t\tAlign(lipgloss.Right).\n\t\t\t\t\t\tBackground(lipgloss.Color(\"#874bfc\"))\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttable := New().\n\t\t\t\tBorder(lipgloss.NormalBorder()).\n\t\t\t\tStyleFunc(tc.style).\n\t\t\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\t\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\").\n\t\t\t\tRow(\"French\", \"Bonjour\", \"Salut\").\n\t\t\t\tRow(\"Japanese\", \"こんにちは\", \"やあ\").\n\t\t\t\tRow(\"Russian\", \"Zdravstvuyte\", \"Privet\").\n\t\t\t\tRow(\"Spanish\", \"Hola\", \"¿Qué tal?\")\n\n\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t})\n\t}\n}\n\nfunc TestClearRows(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tt.Fatalf(\"had to recover: %v\", r)\n\t\t}\n\t}()\n\n\ttable := New().\n\t\tBorder(lipgloss.NormalBorder()).\n\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\tRow(\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\")\n\ttable.ClearRows()\n\ttable.Row(\"French\", \"Bonjour\", \"Salut\")\n\n\t// String() will try to get the rows from table.data\n\ttable.String()\n}\n\nfunc TestContentWrapping(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\theaders   []string\n\t\tdata      [][]string\n\t\twrap      bool\n\t\tstyleFunc StyleFunc\n\t}{\n\t\t{\n\t\t\t\"LongRowContent\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"hello\", \"yep\"}},\n\t\t\ttrue,\n\t\t\tTableStyle,\n\t\t},\n\t\t{\n\t\t\t\"LongRowContentNoWrap\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"hello\", \"yep\"}},\n\t\t\tfalse,\n\t\t\tTableStyle,\n\t\t},\n\t\t{\n\t\t\t\"LongRowContentNoWrapNoMargins\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"hello\", \"yep\"}},\n\t\t\tfalse,\n\t\t\tfunc(row, col int) lipgloss.Style {\n\t\t\t\tswitch {\n\t\t\t\tcase row == HeaderRow:\n\t\t\t\t\treturn lipgloss.NewStyle().Padding(0).Align(lipgloss.Center)\n\t\t\t\tdefault:\n\t\t\t\t\treturn lipgloss.NewStyle().Padding(0)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"LongRowContentNoWrapCustomMargins\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"hello\", \"yep\"}},\n\t\t\tfalse,\n\t\t\tfunc(row, col int) lipgloss.Style {\n\t\t\t\tswitch {\n\t\t\t\tcase row == HeaderRow:\n\t\t\t\t\treturn lipgloss.NewStyle().Padding(0, 2).Align(lipgloss.Center)\n\t\t\t\tdefault:\n\t\t\t\t\treturn lipgloss.NewStyle().Padding(0, 2)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"MissingRowContent\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"\", \"\"}},\n\t\t\ttrue,\n\t\t\tTableStyle,\n\t\t},\n\t\t{\n\t\t\t\"LongHeaderContentLongAndShortRows\",\n\t\t\t[]string{\"Destination\", \"Why are you going on this trip? Is it a hot or cold climate?\", \"Affordability\"},\n\t\t\t[][]string{\n\t\t\t\t{\"Mexico\", \"I want to go somewhere hot, dry, and affordable. Mexico has really good food, just don't drink tap water!\", \"$\"},\n\t\t\t\t{\"New York\", \"I'm thinking about going during the Christmas season to check out Rockefeller center. Might be cold though...\", \"$$$\"},\n\t\t\t\t{\"California\", \"\", \"$$$\"},\n\t\t\t},\n\t\t\ttrue,\n\t\t\tTableStyle,\n\t\t},\n\t\t{\n\t\t\t\"LongTextDifferentLanguages\",\n\t\t\t[]string{\"Hello\", \"你好\", \"مرحبًا\", \"안녕하세요\"},\n\t\t\t[][]string{\n\t\t\t\t{\n\t\t\t\t\t\"Lorem ipsum dolor sit amet, regione detracto eos an. Has ei quidam hendrerit intellegebat, id tamquam iudicabit necessitatibus ius, at errem officiis hendrerit mei. Exerci noster at has, sit id tota convenire, vel ex rebum inciderint liberavisse. Quaeque delectus corrumpit cu cum.\",\n\t\t\t\t\t`耐許ヱヨカハ調出あゆ監件び理別よン國給災レホチ権輝モエフ会割もフ響3現エツ文時しだびほ経機ムイメフ敗文ヨク現義なさド請情ゆじょて憶主管州けでふく。排ゃわつげ美刊ヱミ出見ツ南者オ抜豆ハトロネ論索モネニイ任償スヲ話破リヤヨ秒止口イセソス止央のさ食周健でてつだ官送ト読聴遊容ひるべ。際ぐドらづ市居ネムヤ研校35岩6繹ごわク報拐イ革深52球ゃレスご究東スラ衝3間ラ録占たス。\n禁にンご忘康ざほぎル騰般ねど事超スんいう真表何カモ自浩ヲシミ図客線るふ静王ぱーま写村月掛焼詐面ぞゃ。昇強ごントほ価保キ族85岡モテ恋困ひりこな刊並せご出来ぼぎむう点目ヲウ止環公ニレ事応タス必書タメムノ当84無信升ちひょ。価ーぐ中客テサ告覧ヨトハ極整ラ得95稿はかラせ江利ス宏丸霊ミ考整ス静将ず業巨職ノラホ収嗅ざな。`,\n\t\t\t\t\t\"شيء قد للحكومة والكوري الأوروبيّون, بوابة تعديل واعتلاء ضرب بـ. إذ أسر اتّجة اعلان, ٣٠ اكتوبر العصبة استمرار ومن. أفاق للسيطرة التاريخ، مع بحث, كلّ اتّجة القوى مع. فبعد ايطاليا، تم حتى, لكل تم جسيمة الإحتفاظ وباستثناء, عل فرنسا وانتهاءً الإقتصادية عرض. ونتج دأبوا إحكام بال إذ. لغات عملية وتم مع, وصل بداية وبغطاء البرية بل, أي قررت بلاده فكانت حدى\",\n\t\t\t\t\t\"版応道潟部中幕爆営報門案名見壌府。博健必権次覧編仕断青場内凄新東深簿代供供。守聞書神秀同浜東波恋闘秀。未格打好作器来利阪持西焦朝三女。権幽問季負娘購合旧資健載員式活陸。未倍校朝遺続術吉迎暮広知角亡志不説空住。法省当死年勝絡聞方北投健。室分性山天態意画詳知浅方裁。変激伝阜中野品省載嗅闘額端反。中必台際造事寄民経能前作臓\",\n\t\t\t\t\t\"각급 선거관리위원회의 조직·직무범위 기타 필요한 사항은 법률로 정한다. 임시회의 회기는 30일을 초과할 수 없다. 국가는 여자의 복지와 권익의 향상을 위하여 노력하여야 한다. 국군의 조직과 편성은 법률로 정한다.\",\n\t\t\t\t},\n\t\t\t},\n\t\t\ttrue,\n\t\t\tTableStyle,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttable := New().\n\t\t\t\tHeaders(tc.headers...).\n\t\t\t\tRows(tc.data...).\n\t\t\t\tWidth(80).\n\t\t\t\tStyleFunc(tc.styleFunc).\n\t\t\t\tWrap(tc.wrap)\n\n\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t})\n\t}\n}\n\nfunc TestContentWrapping_WithPadding(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\theaders []string\n\t\tdata    [][]string\n\t}{\n\t\t{\n\t\t\t\"LongRowContent\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"hello\", \"yep\"}},\n\t\t},\n\t\t{\n\t\t\t\"MissingRowContent\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"\", \"\"}},\n\t\t},\n\t\t{\n\t\t\t\"LongHeaderContentLongAndShortRows\",\n\t\t\t[]string{\"Destination\", \"Why are you going on this trip? Is it a hot or cold climate?\", \"Affordability\"},\n\t\t\t[][]string{\n\t\t\t\t{\"Mexico\", \"I want to go somewhere hot, dry, and affordable. Mexico has really good food, just don't drink tap water!\", \"$\"},\n\t\t\t\t{\"New York\", \"I'm thinking about going during the Christmas season to check out Rockefeller center. Might be cold though...\", \"$$$\"},\n\t\t\t\t{\"California\", \"\", \"$$$\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"LongTextDifferentLanguages\",\n\t\t\t[]string{\"Hello\", \"你好\", \"مرحبًا\", \"안녕하세요\"},\n\t\t\t[][]string{\n\t\t\t\t{\n\t\t\t\t\t\"\",\n\t\t\t\t\t`耐許ヱヨカハ調出あゆ監件び理別よン國給災レホチ権輝モエフ会割もフ響3現エツ文時しだびほ経機ムイメフ敗文ヨク現義なさド請情ゆじょて憶主管州けでふく。排ゃわつげ美刊ヱミ出見ツ南者オ抜豆ハトロネ論索モネニイ任償スヲ話破リヤヨ秒止口イセソス止央のさ食周健でてつだ官送ト読聴遊容ひるべ。際ぐドらづ市居ネムヤ研校35岩6繹ごわク報拐イ革深52球ゃレスご究東スラ衝3間ラ録占たス。\n禁にンご忘康ざほぎル騰般ねど事超スんいう真表何カモ自浩ヲシミ図客線るふ静王ぱーま写村月掛焼詐面ぞゃ。昇強ごントほ価保キ族85岡モテ恋困ひりこな刊並せご出来ぼぎむう点目ヲウ止環公ニレ事応タス必書タメムノ当84無信升ちひょ。価ーぐ中客テサ告覧ヨトハ極整ラ得95稿はかラせ江利ス宏丸霊ミ考整ス静将ず業巨職ノラホ収嗅ざな。`,\n\t\t\t\t\t\"شيء قد للحكومة والكوري الأوروبيّون, بوابة تعديل واعتلاء ضرب بـ. إذ أسر اتّجة اعلان, ٣٠ اكتوبر العصبة استمرار ومن. أفاق للسيطرة التاريخ، مع بحث, كلّ اتّجة القوى مع. فبعد ايطاليا، تم حتى, لكل تم جسيمة الإحتفاظ وباستثناء, عل فرنسا وانتهاءً الإقتصادية عرض. ونتج دأبوا إحكام بال إذ. لغات عملية وتم مع, وصل بداية وبغطاء البرية بل, أي قررت بلاده فكانت حدى\",\n\t\t\t\t\t\"版応道潟部中幕爆営報門案名見壌府。博健必権次覧編仕断青場内凄新東深簿代供供。守聞書神秀同浜東波恋闘秀。未格打好作器来利阪持西焦朝三女。権幽問季負娘購合旧資健載員式活陸。未倍校朝遺続術吉迎暮広知角亡志不説空住。法省当死年勝絡聞方北投健。室分性山天態意画詳知浅方裁。変激伝阜中野品省載嗅闘額端反。中必台際造事寄民経能前作臓\",\n\t\t\t\t\t\"각급 선거관리위원회의 조직·직무범위 기타 필요한 사항은 법률로 정한다. 임시회의 회기는 30일을 초과할 수 없다. 국가는 여자의 복지와 권익의 향상을 위하여 노력하여야 한다. 국군의 조직과 편성은 법률로 정한다.\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tdefaultWidth := 80\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttable := New().\n\t\t\t\tHeaders(tc.headers...).\n\t\t\t\tRows(tc.data...).\n\t\t\t\tStyleFunc(func(_, col int) lipgloss.Style {\n\t\t\t\t\treturn lipgloss.NewStyle().Padding(0, 1)\n\t\t\t\t})\n\t\t\ttable.Width(defaultWidth)\n\n\t\t\t// check total width.\n\t\t\tif got := lipgloss.Width(table.String()); got != defaultWidth {\n\t\t\t\tt.Fatalf(\"Table is not the correct width. got %d, want %d\", got, defaultWidth)\n\t\t\t\tt.Log(table.String())\n\t\t\t}\n\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t})\n\t}\n}\n\nfunc TestContentWrapping_WithMargins(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\theaders []string\n\t\tdata    [][]string\n\t}{\n\t\t{\n\t\t\t\"LongRowContent\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"hello\", \"yep\"}},\n\t\t},\n\t\t{\n\t\t\t\"MissingRowContent\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"\", \"\"}},\n\t\t},\n\t\t{\n\t\t\t\"LongHeaderContentLongAndShortRows\",\n\t\t\t[]string{\"Destination\", \"Why are you going on this trip? Is it a hot or cold climate?\", \"Affordability\"},\n\t\t\t[][]string{\n\t\t\t\t{\"Mexico\", \"I want to go somewhere hot, dry, and affordable. Mexico has really good food, just don't drink tap water!\", \"$\"},\n\t\t\t\t{\"New York\", \"I'm thinking about going during the Christmas season to check out Rockefeller center. Might be cold though...\", \"$$$\"},\n\t\t\t\t{\"California\", \"\", \"$$$\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"LongTextDifferentLanguages\",\n\t\t\t[]string{\"Hello\", \"你好\", \"مرحبًا\", \"안녕하세요\"},\n\t\t\t[][]string{\n\t\t\t\t{\n\t\t\t\t\t\"Lorem ipsum dolor sit amet, regione detracto eos an. Has ei quidam hendrerit intellegebat, id tamquam iudicabit necessitatibus ius, at errem officiis hendrerit mei. Exerci noster at has, sit id tota convenire, vel ex rebum inciderint liberavisse. Quaeque delectus corrumpit cu cum.\",\n\t\t\t\t\t`耐許ヱヨカハ調出あゆ監件び理別よン國給災レホチ権輝モエフ会割もフ響3現エツ文時しだびほ経機ムイメフ敗文ヨク現義なさド請情ゆじょて憶主管州けでふく。排ゃわつげ美刊ヱミ出見ツ南者オ抜豆ハトロネ論索モネニイ任償スヲ話破リヤヨ秒止口イセソス止央のさ食周健でてつだ官送ト読聴遊容ひるべ。際ぐドらづ市居ネムヤ研校35岩6繹ごわク報拐イ革深52球ゃレスご究東スラ衝3間ラ録占たス。\n禁にンご忘康ざほぎル騰般ねど事超スんいう真表何カモ自浩ヲシミ図客線るふ静王ぱーま写村月掛焼詐面ぞゃ。昇強ごントほ価保キ族85岡モテ恋困ひりこな刊並せご出来ぼぎむう点目ヲウ止環公ニレ事応タス必書タメムノ当84無信升ちひょ。価ーぐ中客テサ告覧ヨトハ極整ラ得95稿はかラせ江利ス宏丸霊ミ考整ス静将ず業巨職ノラホ収嗅ざな。`,\n\t\t\t\t\t\"شيء قد للحكومة والكوري الأوروبيّون, بوابة تعديل واعتلاء ضرب بـ. إذ أسر اتّجة اعلان, ٣٠ اكتوبر العصبة استمرار ومن. أفاق للسيطرة التاريخ، مع بحث, كلّ اتّجة القوى مع. فبعد ايطاليا، تم حتى, لكل تم جسيمة الإحتفاظ وباستثناء, عل فرنسا وانتهاءً الإقتصادية عرض. ونتج دأبوا إحكام بال إذ. لغات عملية وتم مع, وصل بداية وبغطاء البرية بل, أي قررت بلاده فكانت حدى\",\n\t\t\t\t\t\"版応道潟部中幕爆営報門案名見壌府。博健必権次覧編仕断青場内凄新東深簿代供供。守聞書神秀同浜東波恋闘秀。未格打好作器来利阪持西焦朝三女。権幽問季負娘購合旧資健載員式活陸。未倍校朝遺続術吉迎暮広知角亡志不説空住。法省当死年勝絡聞方北投健。室分性山天態意画詳知浅方裁。変激伝阜中野品省載嗅闘額端反。中必台際造事寄民経能前作臓\",\n\t\t\t\t\t\"각급 선거관리위원회의 조직·직무범위 기타 필요한 사항은 법률로 정한다. 임시회의 회기는 30일을 초과할 수 없다. 국가는 여자의 복지와 권익의 향상을 위하여 노력하여야 한다. 국군의 조직과 편성은 법률로 정한다.\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttable := New().\n\t\t\t\tHeaders(tc.headers...).\n\t\t\t\tRows(tc.data...).\n\t\t\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\t\t\treturn lipgloss.NewStyle().Margin(0, 4)\n\t\t\t\t})\n\t\t\ttable.Width(80)\n\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t})\n\t}\n}\n\nfunc TestContentWrapping_WithHeight(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\theaders  []string\n\t\tdata     [][]string\n\t}{\n\t\t{\n\t\t\t\"LongHeaderContentLongAndShortRows\",\n\t\t\t[]string{\"Destination\", \"Why are you going on this trip? Is it a hot or cold climate?\", \"Affordability\"},\n\t\t\t[][]string{\n\t\t\t\t{\"Mexico\", \"I want to go somewhere hot, dry, and affordable. Mexico has really good food, just don't drink tap water!\", \"$\"},\n\t\t\t\t{\"New York\", \"I'm thinking about going during the Christmas season to check out Rockefeller center. Might be cold though...\", \"$$$\"},\n\t\t\t\t{\"California\", \"\", \"$$$\"},\n\t\t\t\t{\"Florida\", \"I want to go somewhere hot, humid, and affordable. Florida has really good food, just don't go during hurricane season!\", \"$$\"},\n\t\t\t\t{\"Maine\", \"I'm thinking about going during the summer to check out Acadia National Park. Might be cold though...\", \"$$\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tfor i := 5; i <= 35; i += 10 {\n\t\t\t\tt.Run(fmt.Sprintf(\"HeightOf%02d\", i), func(t *testing.T) {\n\t\t\t\t\ttable := New().\n\t\t\t\t\t\tHeight(i).\n\t\t\t\t\t\tWidth(60).\n\t\t\t\t\t\tBorder(lipgloss.NormalBorder()).\n\t\t\t\t\t\tBorderRow(false).\n\t\t\t\t\t\tStyleFunc(TableStyle).\n\t\t\t\t\t\tHeaders(tc.headers...).\n\t\t\t\t\t\tRows(tc.data...)\n\t\t\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestContentWrapping_ColumnWidth(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\theaders []string\n\t\tdata    [][]string\n\t}{\n\t\t{\n\t\t\t\"LongRowContent\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"hello\", \"yep\"}},\n\t\t},\n\t\t{\n\t\t\t\"MissingRowContent\",\n\t\t\t[]string{\"Name\", \"Description\", \"Type\", \"Required\", \"Default\"},\n\t\t\t[][]string{{\"command\", \"A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.\", \"yes\", \"\", \"\"}},\n\t\t},\n\t\t{\n\t\t\t\"LongHeaderContentLongAndShortRows\",\n\t\t\t[]string{\"Destination\", \"Why are you going on this trip? Is it a hot or cold climate?\", \"Affordability\"},\n\t\t\t[][]string{\n\t\t\t\t{\"Mexico\", \"I want to go somewhere hot, dry, and affordable. Mexico has really good food, just don't drink tap water!\", \"$\"},\n\t\t\t\t{\"New York\", \"I'm thinking about going during the Christmas season to check out Rockefeller center. Might be cold though...\", \"$$$\"},\n\t\t\t\t{\"California\", \"\", \"$$$\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"LongTextDifferentLanguages\",\n\t\t\t[]string{\"Hello\", \"你好\", \"مرحبًا\", \"안녕하세요\"},\n\t\t\t[][]string{\n\t\t\t\t{\n\t\t\t\t\t\"Lorem ipsum dolor sit amet, regione detracto eos an. Has ei quidam hendrerit intellegebat, id tamquam iudicabit necessitatibus ius, at errem officiis hendrerit mei. Exerci noster at has, sit id tota convenire, vel ex rebum inciderint liberavisse. Quaeque delectus corrumpit cu cum.\",\n\t\t\t\t\t`耐許ヱヨカハ調出あゆ監件び理別よン國給災レホチ権輝モエフ会割もフ響3現エツ文時しだびほ経機ムイメフ敗文ヨク現義なさド請情ゆじょて憶主管州けでふく。排ゃわつげ美刊ヱミ出見ツ南者オ抜豆ハトロネ論索モネニイ任償スヲ話破リヤヨ秒止口イセソス止央のさ食周健でてつだ官送ト読聴遊容ひるべ。際ぐドらづ市居ネムヤ研校35岩6繹ごわク報拐イ革深52球ゃレスご究東スラ衝3間ラ録占たス。\n禁にンご忘康ざほぎル騰般ねど事超スんいう真表何カモ自浩ヲシミ図客線るふ静王ぱーま写村月掛焼詐面ぞゃ。昇強ごントほ価保キ族85岡モテ恋困ひりこな刊並せご出来ぼぎむう点目ヲウ止環公ニレ事応タス必書タメムノ当84無信升ちひょ。価ーぐ中客テサ告覧ヨトハ極整ラ得95稿はかラせ江利ス宏丸霊ミ考整ス静将ず業巨職ノラホ収嗅ざな。`,\n\t\t\t\t\t\"شيء قد للحكومة والكوري الأوروبيّون, بوابة تعديل واعتلاء ضرب بـ. إذ أسر اتّجة اعلان, ٣٠ اكتوبر العصبة استمرار ومن. أفاق للسيطرة التاريخ، مع بحث, كلّ اتّجة القوى مع. فبعد ايطاليا، تم حتى, لكل تم جسيمة الإحتفاظ وباستثناء, عل فرنسا وانتهاءً الإقتصادية عرض. ونتج دأبوا إحكام بال إذ. لغات عملية وتم مع, وصل بداية وبغطاء البرية بل, أي قررت بلاده فكانت حدى\",\n\t\t\t\t\t\"版応道潟部中幕爆営報門案名見壌府。博健必権次覧編仕断青場内凄新東深簿代供供。守聞書神秀同浜東波恋闘秀。未格打好作器来利阪持西焦朝三女。権幽問季負娘購合旧資健載員式活陸。未倍校朝遺続術吉迎暮広知角亡志不説空住。法省当死年勝絡聞方北投健。室分性山天態意画詳知浅方裁。変激伝阜中野品省載嗅闘額端反。中必台際造事寄民経能前作臓\",\n\t\t\t\t\t\"각급 선거관리위원회의 조직·직무범위 기타 필요한 사항은 법률로 정한다. 임시회의 회기는 30일을 초과할 수 없다. 국가는 여자의 복지와 권익의 향상을 위하여 노력하여야 한다. 국군의 조직과 편성은 법률로 정한다.\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tdefaultWidth := 80\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttable := New().\n\t\t\t\tHeaders(tc.headers...).\n\t\t\t\tRows(tc.data...).\n\t\t\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\t\t\t// If we set a specific cell width, it should count for all rows\n\t\t\t\t\t// in that column.\n\t\t\t\t\tif row == 0 && col == 1 {\n\t\t\t\t\t\treturn lipgloss.NewStyle().Width(30)\n\t\t\t\t\t}\n\t\t\t\t\t// Set a column's width directly.\n\t\t\t\t\tif col == 2 {\n\t\t\t\t\t\treturn lipgloss.NewStyle().Width(5)\n\t\t\t\t\t}\n\t\t\t\t\treturn lipgloss.NewStyle()\n\t\t\t\t})\n\t\t\ttable.Width(defaultWidth)\n\t\t\t// check total width.\n\t\t\tif got := lipgloss.Width(table.String()); got != defaultWidth {\n\t\t\t\tt.Log(table.String())\n\t\t\t\tt.Fatalf(\"Table is not the correct width. got %d, want %d\", got, defaultWidth)\n\t\t\t}\n\n\t\t\t// check that width is overridden with a small value.\n\t\t\tif table.widths[2] != 5 {\n\t\t\t\tt.Log(table.String())\n\t\t\t\tt.Fatalf(\"Did not set correct width value at column at index %d.\\ngot %d, want %d\", 2, table.widths[2], 5)\n\t\t\t}\n\n\t\t\t// check that width is overridden with a wide value.\n\t\t\tif table.widths[1] != 30 {\n\t\t\t\tt.Log(table.String())\n\t\t\t\tt.Fatalf(\"Did not set correct width value at column at index %d.\\ngot %d, want %d\", 1, table.widths[1], 30)\n\t\t\t}\n\n\t\t\tt.Log(table.widths[2])\n\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t})\n\t}\n}\n\n// Test truncation for overflow and no wrap when combined.\nfunc TestTableOverFlowNoWrap(t *testing.T) {\n\t// LongTextDifferentLanguages\n\theaders := []string{\"Hello\", \"你好\", \"مرحبًا\", \"안녕하세요\"}\n\tdata := [][]string{\n\t\t{\n\t\t\t\"Lorem ipsum dolor sit amet, regione detracto eos an. Has ei quidam hendrerit intellegebat, id tamquam iudicabit necessitatibus ius, at errem officiis hendrerit mei. Exerci noster at has, sit id tota convenire, vel ex rebum inciderint liberavisse. Quaeque delectus corrumpit cu cum.\",\n\t\t\t`耐許ヱヨカハ調出あゆ監件び理別よン國給災レホチ権輝モエフ会割もフ響3現エツ文時しだびほ経機ムイメフ敗文ヨク現義なさド請情ゆじょて憶主管州けでふく。排ゃわつげ美刊ヱミ出見ツ南者オ抜豆ハトロネ論索モネニイ任償スヲ話破リヤヨ秒止口イセソス止央のさ食周健でてつだ官送ト読聴遊容ひるべ。際ぐドらづ市居ネムヤ研校35岩6繹ごわク報拐イ革深52球ゃレスご究東スラ衝3間ラ録占たス。\n禁にンご忘康ざほぎル騰般ねど事超スんいう真表何カモ自浩ヲシミ図客線るふ静王ぱーま写村月掛焼詐面ぞゃ。昇強ごントほ価保キ族85岡モテ恋困ひりこな刊並せご出来ぼぎむう点目ヲウ止環公ニレ事応タス必書タメムノ当84無信升ちひょ。価ーぐ中客テサ告覧ヨトハ極整ラ得95稿はかラせ江利ス宏丸霊ミ考整ス静将ず業巨職ノラホ収嗅ざな。`,\n\t\t\t\"شيء قد للحكومة والكوري الأوروبيّون, بوابة تعديل واعتلاء ضرب بـ. إذ أسر اتّجة اعلان, ٣٠ اكتوبر العصبة استمرار ومن. أفاق للسيطرة التاريخ، مع بحث, كلّ اتّجة القوى مع. فبعد ايطاليا، تم حتى, لكل تم جسيمة الإحتفاظ وباستثناء, عل فرنسا وانتهاءً الإقتصادية عرض. ونتج دأبوا إحكام بال إذ. لغات عملية وتم مع, وصل بداية وبغطاء البرية بل, أي قررت بلاده فكانت حدى\",\n\t\t\t\"각급 선거관리위원회의 조직·직무범위 기타 필요한 사항은 법률로 정한다. 임시회의 회기는 30일을 초과할 수 없다. 국가는 여자의 복지와 권익의 향상을 위하여 노력하여야 한다. 국군의 조직과 편성은 법률로 정한다.\",\n\t\t\t\"版応道潟部中幕爆営報門案名見壌府。博健必権次覧編仕断青場内凄新東深簿代供供。守聞書神秀同浜東波恋闘秀。未格打好作器来利阪持西焦朝三女。権幽問季負娘購合旧資健載員式活陸。未倍校朝遺続術吉迎暮広知角亡志不説空住。法省当死年勝絡聞方北投健。室分性山天態意画詳知浅方裁。変激伝阜中野品省載嗅闘額端反。中必台際造事寄民経能前作臓\",\n\t\t},\n\t\t{\"Welcome\", \"いらっしゃいませ\", \"مرحباً\", \"환영\", \"欢迎\"},\n\t\t{\"Goodbye\", \"さようなら\", \"مع السلامة\", \"안녕히 가세요\", \"再见\"},\n\t}\n\ttableHeight := 6\n\ttable := New().\n\t\tHeaders(headers...).\n\t\tRows(data...).\n\t\tStyleFunc(TableStyle).\n\t\tHeight(tableHeight).\n\t\tWidth(80).\n\t\tWrap(false)\n\tif got := lipgloss.Height(table.String()); got != tableHeight {\n\t\tt.Fatalf(\"got the wrong height. got %d, want %d\", got, tableHeight)\n\t}\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestCarriageReturn(t *testing.T) {\n\tdata := [][]string{\n\t\t{\"a0\", \"b0\", \"c0\", \"d0\"},\n\t\t{\"a1\", \"b1.0\\r\\nb1.1\\r\\nb1.2\\r\\nb1.3\\r\\nb1.4\\r\\nb1.5\\r\\nb1.6\", \"c1\", \"d1\"},\n\t\t{\"a2\", \"b2\", \"c2\", \"d2\"},\n\t\t{\"a3\", \"b3\", \"c3\", \"d3\"},\n\t}\n\ttable := New().Rows(data...).Border(lipgloss.NormalBorder())\n\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestTableShrinkWithYOffset(t *testing.T) {\n\trows := [][]string{\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.Run(\"NoHeaders\", func(t *testing.T) {\n\t\ttable := New().\n\t\t\tRows(rows...).\n\t\t\tYOffset(80).\n\t\t\tHeight(45)\n\t\tcontent := table.String()\n\t\tgolden.RequireEqual(t, []byte(content))\n\t})\n\n\tt.Run(\"WithHeaders\", func(t *testing.T) {\n\t\ttable := New().\n\t\t\tHeaders(\"Rank\", \"City\", \"Country\", \"Population\").\n\t\t\tRows(rows...).\n\t\t\tYOffset(80).\n\t\t\tHeight(45)\n\t\tcontent := table.String()\n\t\tgolden.RequireEqual(t, []byte(content))\n\t})\n\n\tt.Run(\"WithBorderRow\", func(t *testing.T) {\n\t\ttable := New().\n\t\t\tHeaders(\"Rank\", \"City\", \"Country\", \"Population\").\n\t\t\tRows(rows...).\n\t\t\tBorderRow(true).\n\t\t\tYOffset(80).\n\t\t\tHeight(43)\n\t\tcontent := table.String()\n\t\tgolden.RequireEqual(t, []byte(content))\n\t})\n}\n\nfunc TestBorderStyles(t *testing.T) {\n\trows := [][]string{\n\t\t{\"Chinese\", \"Nǐn hǎo\", \"Nǐ hǎo\"},\n\t\t{\"French\", \"Bonjour\", \"Salut\"},\n\t\t{\"Japanese\", \"こんにちは\", \"やあ\"},\n\t\t{\"Russian\", \"Zdravstvuyte\", \"Privet\"},\n\t\t{\"Spanish\", \"Hola\", \"¿Qué tal?\"},\n\t}\n\n\ttests := []struct {\n\t\tname             string\n\t\tborderFunc       func() lipgloss.Border\n\t\ttopBottomBorders bool\n\t}{\n\t\t{\"NormalBorder\", lipgloss.NormalBorder, true},\n\t\t{\"RoundedBorder\", lipgloss.RoundedBorder, true},\n\t\t{\"BlockBorder\", lipgloss.BlockBorder, true},\n\t\t{\"ThickBorder\", lipgloss.ThickBorder, true},\n\t\t{\"HiddenBorder\", lipgloss.HiddenBorder, true},\n\t\t{\"MarkdownBorder\", lipgloss.MarkdownBorder, false},\n\t\t{\"ASCIIBorder\", lipgloss.ASCIIBorder, true},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttable := New().\n\t\t\t\tStyleFunc(TableStyle).\n\t\t\t\tBorder(test.borderFunc()).\n\t\t\t\tHeaders(\"LANGUAGE\", \"FORMAL\", \"INFORMAL\").\n\t\t\t\tRows(rows...).\n\t\t\t\tBorderTop(test.topBottomBorders).\n\t\t\t\tBorderBottom(test.topBottomBorders)\n\n\t\t\tgolden.RequireEqual(t, []byte(table.String()))\n\t\t})\n\t}\n}\n\nfunc TestNoFinalEmptyRowWhenOverflow(t *testing.T) {\n\theaders := []string{\"Rank\", \"City\", \"Country\", \"Population\"}\n\trows := [][]string{\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}\n\ttable := New().\n\t\tHeaders(headers...).\n\t\tRows(rows...).\n\t\tBorderRow(true).\n\t\tHeight(16)\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestExtraPaddingHeading(t *testing.T) {\n\theaders := []string{\"Name\", \"Country of Origin\", \"Dunk-able\"}\n\trows := [][]string{\n\t\t{\"Chocolate Digestives\", \"UK\", \"Yes\"},\n\t\t{\"Tim Tams\", \"Australia\", \"No\"},\n\t\t{\"Hobnobs\", \"UK\", \"Yes\"},\n\t}\n\tstyleFunc := func(row, col int) lipgloss.Style {\n\t\treturn lipgloss.NewStyle().Padding(2, 2)\n\t}\n\ttable := New().\n\t\tHeaders(headers...).\n\t\tRows(rows...).\n\t\tStyleFunc(styleFunc)\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestExtraPaddingHeadingLong(t *testing.T) {\n\theaders := []string{\"Looong Name\", \"Looong Country of Origin\", \"Looong Dunk-able\"}\n\trows := [][]string{\n\t\t{\"Chocolate Digestives\", \"UK\", \"Yes\"},\n\t\t{\"Tim Tams\", \"Australia\", \"No\"},\n\t\t{\"Hobnobs\", \"UK\", \"Yes\"},\n\t}\n\tstyleFunc := func(row, col int) lipgloss.Style {\n\t\treturn lipgloss.NewStyle().Padding(2, 2)\n\t}\n\ttable := New().\n\t\tWidth(46).\n\t\tHeaders(headers...).\n\t\tRows(rows...).\n\t\tStyleFunc(styleFunc)\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\nfunc TestBorderedCells(t *testing.T) {\n\theaders := []string{\"Name\", \"Country of Origin\", \"Dunk-able\"}\n\trows := [][]string{\n\t\t{\"Chocolate Digestives\", \"UK\", \"Yes\"},\n\t\t{\"Tim Tams\", \"Australia\", \"No\"},\n\t\t{\"Hobnobs\", \"UK\", \"Yes\"},\n\t}\n\tstyleFunc := func(row, col int) lipgloss.Style {\n\t\treturn lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder())\n\t}\n\ttable := New().\n\t\tHeaders(headers...).\n\t\tRows(rows...).\n\t\tStyleFunc(styleFunc)\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\n// Examples\n\nfunc ExampleTable_Wrap() {\n\t// LongTextDifferentLanguages\n\theaders := []string{\"Hello\", \"你好\", \"مرحبًا\", \"안녕하세요\"}\n\tdata := [][]string{\n\t\t{\n\t\t\t\"Lorem ipsum dolor sit amet, regione detracto eos an. Has ei quidam hendrerit intellegebat, id tamquam iudicabit necessitatibus ius, at errem officiis hendrerit mei. Exerci noster at has, sit id tota convenire, vel ex rebum inciderint liberavisse. Quaeque delectus corrumpit cu cum.\",\n\t\t\t`耐許ヱヨカハ調出あゆ監件び理別よン國給災レホチ権輝モエフ会割もフ響3現エツ文時しだびほ経機ムイメフ敗文ヨク現義なさド請情ゆじょて憶主管州けでふく。排ゃわつげ美刊ヱミ出見ツ南者オ抜豆ハトロネ論索モネニイ任償スヲ話破リヤヨ秒止口イセソス止央のさ食周健でてつだ官送ト読聴遊容ひるべ。際ぐドらづ市居ネムヤ研校35岩6繹ごわク報拐イ革深52球ゃレスご究東スラ衝3間ラ録占たス。\n禁にンご忘康ざほぎル騰般ねど事超スんいう真表何カモ自浩ヲシミ図客線るふ静王ぱーま写村月掛焼詐面ぞゃ。昇強ごントほ価保キ族85岡モテ恋困ひりこな刊並せご出来ぼぎむう点目ヲウ止環公ニレ事応タス必書タメムノ当84無信升ちひょ。価ーぐ中客テサ告覧ヨトハ極整ラ得95稿はかラせ江利ス宏丸霊ミ考整ス静将ず業巨職ノラホ収嗅ざな。`,\n\t\t\t\"شيء قد للحكومة والكوري الأوروبيّون, بوابة تعديل واعتلاء ضرب بـ. إذ أسر اتّجة اعلان, ٣٠ اكتوبر العصبة استمرار ومن. أفاق للسيطرة التاريخ، مع بحث, كلّ اتّجة القوى مع. فبعد ايطاليا، تم حتى, لكل تم جسيمة الإحتفاظ وباستثناء, عل فرنسا وانتهاءً الإقتصادية عرض. ونتج دأبوا إحكام بال إذ. لغات عملية وتم مع, وصل بداية وبغطاء البرية بل, أي قررت بلاده فكانت حدى\",\n\t\t\t\"版応道潟部中幕爆営報門案名見壌府。博健必権次覧編仕断青場内凄新東深簿代供供。守聞書神秀同浜東波恋闘秀。未格打好作器来利阪持西焦朝三女。権幽問季負娘購合旧資健載員式活陸。未倍校朝遺続術吉迎暮広知角亡志不説空住。法省当死年勝絡聞方北投健。室分性山天態意画詳知浅方裁。変激伝阜中野品省載嗅闘額端反。中必台際造事寄民経能前作臓\",\n\t\t\t\"각급 선거관리위원회의 조직·직무범위 기타 필요한 사항은 법률로 정한다. 임시회의 회기는 30일을 초과할 수 없다. 국가는 여자의 복지와 권익의 향상을 위하여 노력하여야 한다. 국군의 조직과 편성은 법률로 정한다.\",\n\t\t},\n\t}\n\n\ttable := New().\n\t\tHeaders(headers...).\n\t\tRows(data...).\n\t\tStyleFunc(TableStyle).\n\t\tWidth(80).\n\t\tWrap(false)\n\tfmt.Println(table.String())\n\n\ttable.Wrap(true)\n\tfmt.Println(table.String())\n\n\t// Output:\n\t//\n\t// \t┌──────────────┬───────────────┬───────────────┬───────────────┬───────────────┐\n\t// │    Hello     │     你好      │     مرحبًا     │  안녕하세요   │               │\n\t// ├──────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n\t// │ Lorem ipsum… │ 耐許ヱヨカハ… │ شيء قد للحكو… │ 版応道潟部中… │ 각급 선거관…  │\n\t// └──────────────┴───────────────┴───────────────┴───────────────┴───────────────┘\n\t// ┌──────────────┬───────────────┬───────────────┬───────────────┬───────────────┐\n\t// │    Hello     │     你好      │     مرحبًا     │  안녕하세요   │               │\n\t// ├──────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n\t// │ Lorem ipsum  │ 耐許ヱヨカハ  │ شيء قد        │ 版応道潟部中  │ 각급          │\n\t// │ dolor sit    │ 調出あゆ監件  │ للحكومة       │ 幕爆営報門案  │ 선거관리위원  │\n\t// │ amet,        │ び理別よン國  │ والكوري       │ 名見壌府。博  │ 회의          │\n\t// │ regione      │ 給災レホチ権  │ الأوروبيّون,   │ 健必権次覧編  │ 조직·직무범위 │\n\t// │ detracto eos │ 輝モエフ会割  │ بوابة تعديل   │ 仕断青場内凄  │ 기타 필요한   │\n\t// │ an. Has ei   │ もフ響3現エツ │ واعتلاء ضرب   │ 新東深簿代供  │ 사항은 법률로 │\n\t// │ quidam       │ 文時しだびほ  │ بـ. إذ أسر    │ 供。守聞書神  │ 정한다.       │\n\t// │ hendrerit    │ 経機ムイメフ  │ اتّجة اعلان,   │ 秀同浜東波恋  │ 임시회의      │\n\t// │ intellegebat │ 敗文ヨク現義  │ ٣٠ اكتوبر     │ 闘秀。未格打  │ 회기는 30일을 │\n\t// │ , id tamquam │ なさド請情ゆ  │ العصبة        │ 好作器来利阪  │ 초과할 수     │\n\t// │ iudicabit    │ じょて憶主管  │ استمرار ومن.  │ 持西焦朝三女  │ 없다. 국가는  │\n\t// │ necessitatib │ 州けでふく。  │ أفاق للسيطرة  │ 。権幽問季負  │ 여자의 복지와 │\n\t// │ us ius, at   │ 排ゃわつげ美  │ التاريخ، مع   │ 娘購合旧資健  │ 권익의 향상을 │\n\t// │ errem        │ 刊ヱミ出見ツ  │ بحث, كلّ اتّجة  │ 載員式活陸。  │ 위하여        │\n\t// │ officiis     │ 南者オ抜豆ハ  │ القوى مع.     │ 未倍校朝遺続  │ 노력하여야    │\n\t// │ hendrerit    │ トロネ論索モ  │ فبعد ايطاليا، │ 術吉迎暮広知  │ 한다. 국군의  │\n\t// │ mei. Exerci  │ ネニイ任償ス  │ تم حتى, لكل   │ 角亡志不説空  │ 조직과 편성은 │\n\t// │ noster at    │ ヲ話破リヤヨ  │ تم جسيمة      │ 住。法省当死  │ 법률로        │\n\t// │ has, sit id  │ 秒止口イセソ  │ الإحتفاظ      │ 年勝絡聞方北  │ 정한다.       │\n\t// │ tota         │ ス止央のさ食  │ وباستثناء, عل │ 投健。室分性  │               │\n\t// │ convenire,   │ 周健でてつだ  │ فرنسا وانتهاءً │ 山天態意画詳  │               │\n\t// │ vel ex rebum │ 官送ト読聴遊  │ الإقتصادية    │ 知浅方裁。変  │               │\n\t// │ inciderint   │ 容ひるべ。際  │ عرض. ونتج     │ 激伝阜中野品  │               │\n\t// │ liberavisse. │ ぐドらづ市居  │ دأبوا إحكام   │ 省載嗅闘額端  │               │\n\t// │ Quaeque      │ ネムヤ研校35  │ بال إذ. لغات  │ 反。中必台際  │               │\n\t// │ delectus     │ 岩6繹ごわク報 │ عملية وتم مع, │ 造事寄民経能  │               │\n\t// │ corrumpit cu │ 拐イ革深52球  │ وصل بداية     │ 前作臓        │               │\n\t// │ cum.         │ ゃレスご究東  │ وبغطاء البرية │               │               │\n\t// │              │ スラ衝3間ラ録 │ بل, أي قررت   │               │               │\n\t// │              │ 占たス。      │ بلاده فكانت   │               │               │\n\t// │              │ 禁にンご忘康  │ حدى           │               │               │\n\t// │              │ ざほぎル騰般  │               │               │               │\n\t// │              │ ねど事超スん  │               │               │               │\n\t// │              │ いう真表何カ  │               │               │               │\n\t// │              │ モ自浩ヲシミ  │               │               │               │\n\t// │              │ 図客線るふ静  │               │               │               │\n\t// │              │ 王ぱーま写村  │               │               │               │\n\t// │              │ 月掛焼詐面ぞ  │               │               │               │\n\t// │              │ ゃ。昇強ごン  │               │               │               │\n\t// │              │ トほ価保キ族8 │               │               │               │\n\t// │              │ 5岡モテ恋困ひ │               │               │               │\n\t// │              │ りこな刊並せ  │               │               │               │\n\t// │              │ ご出来ぼぎむ  │               │               │               │\n\t// │              │ う点目ヲウ止  │               │               │               │\n\t// │              │ 環公ニレ事応  │               │               │               │\n\t// │              │ タス必書タメ  │               │               │               │\n\t// │              │ ムノ当84無信  │               │               │               │\n\t// │              │ 升ちひょ。価  │               │               │               │\n\t// │              │ ーぐ中客テサ  │               │               │               │\n\t// │              │ 告覧ヨトハ極  │               │               │               │\n\t// │              │ 整ラ得95稿は  │               │               │               │\n\t// │              │ かラせ江利ス  │               │               │               │\n\t// │              │ 宏丸霊ミ考整  │               │               │               │\n\t// │              │ ス静将ず業巨  │               │               │               │\n\t// │              │ 職ノラホ収嗅  │               │               │               │\n\t// │              │ ざな。        │               │               │               │\n\t// └──────────────┴───────────────┴───────────────┴───────────────┴───────────────┘\n}\n\n// Check that stylized wrapped content does not go beyond its cell.\nfunc TestWrapPreStyledContent(t *testing.T) {\n\theaders := []string{\"Package\", \"Version\", \"Link\"}\n\tdata := [][]string{\n\t\t{\"sourcegit\", \"0.19\", lipgloss.JoinHorizontal(lipgloss.Left, lipgloss.NewStyle().Foreground(lipgloss.Color(\"#31BB71\")).Render(\"https://aur.archlinux.org/packages/sourcegit-bin\"))},\n\t\t{},\n\t\t{\"Welcome\", \"いらっしゃいませ\", \"مرحباً\", \"환영\", \"欢迎\"},\n\t\t{\"Goodbye\", \"さようなら\", \"مع السلامة\", \"안녕히 가세요\", \"再见\"},\n\t}\n\ttable := New().\n\t\tHeaders(headers...).\n\t\tRows(data...).\n\t\tWidth(80).\n\t\tWrap(true)\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n\n// Check that stylized wrapped content does not go beyond its cell.\nfunc TestWrapStyleFuncContent(t *testing.T) {\n\theaders := []string{\"Package\", \"Version\", \"Link\"}\n\tdata := [][]string{\n\t\t{\"sourcegit\", \"0.19\", \"https://aur.archlinux.org/packages/sourcegit-bin\"},\n\t\t{\"Welcome\", \"いらっしゃいませ\", \"مرحباً\"},\n\t\t{\"Goodbye\", \"さようなら\", \"مع السلامة\"},\n\t}\n\ttable := New().\n\t\tHeaders(headers...).\n\t\tRows(data...).\n\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\tif row == HeaderRow {\n\t\t\t\treturn lipgloss.NewStyle()\n\t\t\t}\n\t\t\tif strings.Contains(data[row][col], \"https://\") {\n\t\t\t\treturn lipgloss.NewStyle().Foreground(lipgloss.Color(\"#31BB71\"))\n\t\t\t}\n\t\t\treturn lipgloss.NewStyle()\n\t\t}).\n\t\tWidth(60).\n\t\tWrap(true)\n\tgolden.RequireEqual(t, []byte(table.String()))\n}\n"
  },
  {
    "path": "table/testdata/TestBorderColumnsWithExtraRows.golden",
    "content": "┌───────────────────────────────────────────────────┐\n│ LANGUAGE     FORMAL                               │\n├───────────────────────────────────────────────────┤\n│ Chinese   Nǐn hǎo       Nǐ hǎo                    │\n│ French    Bonjour       Salut      Salut          │\n│ Japanese  こんにちは    やあ                      │\n│ Russian   Zdravstvuyte  Privet     Privet  Privet │\n│ Spanish   Hola          ¿Qué tal?                 │\n└───────────────────────────────────────────────────┘"
  },
  {
    "path": "table/testdata/TestBorderStyles/ASCIIBorder.golden",
    "content": "+----------+--------------+-----------+\n| LANGUAGE |    FORMAL    | INFORMAL  |\n+----------+--------------+-----------+\n| Chinese  | Nǐn hǎo      | Nǐ hǎo    |\n| French   | Bonjour      | Salut     |\n| Japanese | こんにちは   | やあ      |\n| Russian  | Zdravstvuyte | Privet    |\n| Spanish  | Hola         | ¿Qué tal? |\n+----------+--------------+-----------+"
  },
  {
    "path": "table/testdata/TestBorderStyles/BlockBorder.golden",
    "content": "███████████████████████████████████████\n█ LANGUAGE █    FORMAL    █ INFORMAL  █\n███████████████████████████████████████\n█ Chinese  █ Nǐn hǎo      █ Nǐ hǎo    █\n█ French   █ Bonjour      █ Salut     █\n█ Japanese █ こんにちは   █ やあ      █\n█ Russian  █ Zdravstvuyte █ Privet    █\n█ Spanish  █ Hola         █ ¿Qué tal? █\n███████████████████████████████████████"
  },
  {
    "path": "table/testdata/TestBorderStyles/HiddenBorder.golden",
    "content": "                                       \n  LANGUAGE      FORMAL      INFORMAL   \n                                       \n  Chinese    Nǐn hǎo        Nǐ hǎo     \n  French     Bonjour        Salut      \n  Japanese   こんにちは     やあ       \n  Russian    Zdravstvuyte   Privet     \n  Spanish    Hola           ¿Qué tal?  \n                                       "
  },
  {
    "path": "table/testdata/TestBorderStyles/MarkdownBorder.golden",
    "content": "| LANGUAGE |    FORMAL    | INFORMAL  |\n|----------|--------------|-----------|\n| Chinese  | Nǐn hǎo      | Nǐ hǎo    |\n| French   | Bonjour      | Salut     |\n| Japanese | こんにちは   | やあ      |\n| Russian  | Zdravstvuyte | Privet    |\n| Spanish  | Hola         | ¿Qué tal? |"
  },
  {
    "path": "table/testdata/TestBorderStyles/NormalBorder.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestBorderStyles/RoundedBorder.golden",
    "content": "╭──────────┬──────────────┬───────────╮\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n╰──────────┴──────────────┴───────────╯"
  },
  {
    "path": "table/testdata/TestBorderStyles/ThickBorder.golden",
    "content": "┏━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━┓\n┃ LANGUAGE ┃    FORMAL    ┃ INFORMAL  ┃\n┣━━━━━━━━━━╋━━━━━━━━━━━━━━╋━━━━━━━━━━━┫\n┃ Chinese  ┃ Nǐn hǎo      ┃ Nǐ hǎo    ┃\n┃ French   ┃ Bonjour      ┃ Salut     ┃\n┃ Japanese ┃ こんにちは   ┃ やあ      ┃\n┃ Russian  ┃ Zdravstvuyte ┃ Privet    ┃\n┃ Spanish  ┃ Hola         ┃ ¿Qué tal? ┃\n┗━━━━━━━━━━┻━━━━━━━━━━━━━━┻━━━━━━━━━━━┛"
  },
  {
    "path": "table/testdata/TestBorderedCells.golden",
    "content": "┌──────────────────────┬───────────────────┬───────────┐\n│┌────────────────────┐│┌─────────────────┐│┌─────────┐│\n││Name                │││Country of Origin│││Dunk-able││\n│└────────────────────┘│└─────────────────┘│└─────────┘│\n├──────────────────────┼───────────────────┼───────────┤\n│┌────────────────────┐│┌─────────────────┐│┌─────────┐│\n││Chocolate Digestives│││UK               │││Yes      ││\n│└────────────────────┘│└─────────────────┘│└─────────┘│\n│┌────────────────────┐│┌─────────────────┐│┌─────────┐│\n││Tim Tams            │││Australia        │││No       ││\n│└────────────────────┘│└─────────────────┘│└─────────┘│\n│┌────────────────────┐│┌─────────────────┐│┌─────────┐│\n││Hobnobs             │││UK               │││Yes      ││\n│└────────────────────┘│└─────────────────┘│└─────────┘│\n└──────────────────────┴───────────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestCarriageReturn.golden",
    "content": "┌──┬────┬──┬──┐\n│a0│b0  │c0│d0│\n│a1│b1.0│c1│d1│\n│  │b1.1│  │  │\n│  │b1.2│  │  │\n│  │b1.3│  │  │\n│  │b1.4│  │  │\n│  │b1.5│  │  │\n│  │b1.6│  │  │\n│a2│b2  │c2│d2│\n│a3│b3  │c3│d3│\n└──┴────┴──┴──┘"
  },
  {
    "path": "table/testdata/TestContentWrapping/LongHeaderContentLongAndShortRows.golden",
    "content": "┌─────────────┬────────────────────────────────────────────────┬───────────────┐\n│ Destination │ Why are you going on this trip? Is it a hot o… │ Affordability │\n├─────────────┼────────────────────────────────────────────────┼───────────────┤\n│ Mexico      │ I want to go somewhere hot, dry, and           │ $             │\n│             │ affordable. Mexico has really good food, just  │               │\n│             │ don't drink tap water!                         │               │\n│ New York    │ I'm thinking about going during the Christmas  │ $$$           │\n│             │ season to check out Rockefeller center. Might  │               │\n│             │ be cold though...                              │               │\n│ California  │                                                │ $$$           │\n└─────────────┴────────────────────────────────────────────────┴───────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping/LongRowContent.golden",
    "content": "┌─────────┬────────────────────────────────────────┬──────┬──────────┬─────────┐\n│  Name   │              Description               │ Type │ Required │ Default │\n├─────────┼────────────────────────────────────────┼──────┼──────────┼─────────┤\n│ command │ A command to be executed inside the    │ yes  │ hello    │ yep     │\n│         │ container to assess its health. Each   │      │          │         │\n│         │ space delimited token of the command   │      │          │         │\n│         │ is a separate array element. Commands  │      │          │         │\n│         │ exiting 0 are considered to be         │      │          │         │\n│         │ successful probes, whilst all other    │      │          │         │\n│         │ exit codes are considered failures.    │      │          │         │\n└─────────┴────────────────────────────────────────┴──────┴──────────┴─────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping/LongRowContentNoWrap.golden",
    "content": "┌─────────┬────────────────────────────────────────┬──────┬──────────┬─────────┐\n│  Name   │              Description               │ Type │ Required │ Default │\n├─────────┼────────────────────────────────────────┼──────┼──────────┼─────────┤\n│ command │ A command to be executed inside the c… │ yes  │ hello    │ yep     │\n└─────────┴────────────────────────────────────────┴──────┴──────────┴─────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping/LongRowContentNoWrapCustomMargins.golden",
    "content": "┌───────────┬───────────────────────────────────────┬───────┬─────────┬────────┐\n│   Name    │              Description              │  Ty…  │  Requ…  │  Def…  │\n├───────────┼───────────────────────────────────────┼───────┼─────────┼────────┤\n│  command  │  A command to be executed inside th…  │  yes  │  hello  │  yep   │\n└───────────┴───────────────────────────────────────┴───────┴─────────┴────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping/LongRowContentNoWrapNoMargins.golden",
    "content": "┌───────┬────────────────────────────────────────────────┬────┬────────┬───────┐\n│ Name  │                  Description                   │Type│Required│Default│\n├───────┼────────────────────────────────────────────────┼────┼────────┼───────┤\n│command│A command to be executed inside the container t…│yes │hello   │yep    │\n└───────┴────────────────────────────────────────────────┴────┴────────┴───────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping/LongTextDifferentLanguages.golden",
    "content": "┌──────────────┬───────────────┬───────────────┬───────────────┬───────────────┐\n│    Hello     │     你好      │     مرحبًا     │  안녕하세요   │               │\n├──────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n│ Lorem ipsum  │ 耐許ヱヨカハ  │ شيء قد        │ 版応道潟部中  │ 각급          │\n│ dolor sit    │ 調出あゆ監件  │ للحكومة       │ 幕爆営報門案  │ 선거관리위원  │\n│ amet,        │ び理別よン國  │ والكوري       │ 名見壌府。博  │ 회의          │\n│ regione      │ 給災レホチ権  │ الأوروبيّون,   │ 健必権次覧編  │ 조직·직무범위 │\n│ detracto eos │ 輝モエフ会割  │ بوابة تعديل   │ 仕断青場内凄  │ 기타 필요한   │\n│ an. Has ei   │ もフ響3現エツ │ واعتلاء ضرب   │ 新東深簿代供  │ 사항은 법률로 │\n│ quidam       │ 文時しだびほ  │ بـ. إذ أسر    │ 供。守聞書神  │ 정한다.       │\n│ hendrerit    │ 経機ムイメフ  │ اتّجة اعلان,   │ 秀同浜東波恋  │ 임시회의      │\n│ intellegebat │ 敗文ヨク現義  │ ٣٠ اكتوبر     │ 闘秀。未格打  │ 회기는 30일을 │\n│ , id tamquam │ なさド請情ゆ  │ العصبة        │ 好作器来利阪  │ 초과할 수     │\n│ iudicabit    │ じょて憶主管  │ استمرار ومن.  │ 持西焦朝三女  │ 없다. 국가는  │\n│ necessitatib │ 州けでふく。  │ أفاق للسيطرة  │ 。権幽問季負  │ 여자의 복지와 │\n│ us ius, at   │ 排ゃわつげ美  │ التاريخ، مع   │ 娘購合旧資健  │ 권익의 향상을 │\n│ errem        │ 刊ヱミ出見ツ  │ بحث, كلّ اتّجة  │ 載員式活陸。  │ 위하여        │\n│ officiis     │ 南者オ抜豆ハ  │ القوى مع.     │ 未倍校朝遺続  │ 노력하여야    │\n│ hendrerit    │ トロネ論索モ  │ فبعد ايطاليا، │ 術吉迎暮広知  │ 한다. 국군의  │\n│ mei. Exerci  │ ネニイ任償ス  │ تم حتى, لكل   │ 角亡志不説空  │ 조직과 편성은 │\n│ noster at    │ ヲ話破リヤヨ  │ تم جسيمة      │ 住。法省当死  │ 법률로        │\n│ has, sit id  │ 秒止口イセソ  │ الإحتفاظ      │ 年勝絡聞方北  │ 정한다.       │\n│ tota         │ ス止央のさ食  │ وباستثناء, عل │ 投健。室分性  │               │\n│ convenire,   │ 周健でてつだ  │ فرنسا وانتهاءً │ 山天態意画詳  │               │\n│ vel ex rebum │ 官送ト読聴遊  │ الإقتصادية    │ 知浅方裁。変  │               │\n│ inciderint   │ 容ひるべ。際  │ عرض. ونتج     │ 激伝阜中野品  │               │\n│ liberavisse. │ ぐドらづ市居  │ دأبوا إحكام   │ 省載嗅闘額端  │               │\n│ Quaeque      │ ネムヤ研校35  │ بال إذ. لغات  │ 反。中必台際  │               │\n│ delectus     │ 岩6繹ごわク報 │ عملية وتم مع, │ 造事寄民経能  │               │\n│ corrumpit cu │ 拐イ革深52球  │ وصل بداية     │ 前作臓        │               │\n│ cum.         │ ゃレスご究東  │ وبغطاء البرية │               │               │\n│              │ スラ衝3間ラ録 │ بل, أي قررت   │               │               │\n│              │ 占たス。      │ بلاده فكانت   │               │               │\n│              │ 禁にンご忘康  │ حدى           │               │               │\n│              │ ざほぎル騰般  │               │               │               │\n│              │ ねど事超スん  │               │               │               │\n│              │ いう真表何カ  │               │               │               │\n│              │ モ自浩ヲシミ  │               │               │               │\n│              │ 図客線るふ静  │               │               │               │\n│              │ 王ぱーま写村  │               │               │               │\n│              │ 月掛焼詐面ぞ  │               │               │               │\n│              │ ゃ。昇強ごン  │               │               │               │\n│              │ トほ価保キ族8 │               │               │               │\n│              │ 5岡モテ恋困ひ │               │               │               │\n│              │ りこな刊並せ  │               │               │               │\n│              │ ご出来ぼぎむ  │               │               │               │\n│              │ う点目ヲウ止  │               │               │               │\n│              │ 環公ニレ事応  │               │               │               │\n│              │ タス必書タメ  │               │               │               │\n│              │ ムノ当84無信  │               │               │               │\n│              │ 升ちひょ。価  │               │               │               │\n│              │ ーぐ中客テサ  │               │               │               │\n│              │ 告覧ヨトハ極  │               │               │               │\n│              │ 整ラ得95稿は  │               │               │               │\n│              │ かラせ江利ス  │               │               │               │\n│              │ 宏丸霊ミ考整  │               │               │               │\n│              │ ス静将ず業巨  │               │               │               │\n│              │ 職ノラホ収嗅  │               │               │               │\n│              │ ざな。        │               │               │               │\n└──────────────┴───────────────┴───────────────┴───────────────┴───────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping/MissingRowContent.golden",
    "content": "┌─────────┬────────────────────────────────────────┬──────┬──────────┬─────────┐\n│  Name   │              Description               │ Type │ Required │ Default │\n├─────────┼────────────────────────────────────────┼──────┼──────────┼─────────┤\n│ command │ A command to be executed inside the    │ yes  │          │         │\n│         │ container to assess its health. Each   │      │          │         │\n│         │ space delimited token of the command   │      │          │         │\n│         │ is a separate array element. Commands  │      │          │         │\n│         │ exiting 0 are considered to be         │      │          │         │\n│         │ successful probes, whilst all other    │      │          │         │\n│         │ exit codes are considered failures.    │      │          │         │\n└─────────┴────────────────────────────────────────┴──────┴──────────┴─────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_ColumnWidth/LongHeaderContentLongAndShortRows.golden",
    "content": "┌─────────────────────────────────────────┬──────────────────────────────┬─────┐\n│Destination                              │Why are you going on this tri…│Affo…│\n├─────────────────────────────────────────┼──────────────────────────────┼─────┤\n│Mexico                                   │I want to go somewhere hot,   │$    │\n│                                         │dry, and affordable. Mexico   │     │\n│                                         │has really good food, just    │     │\n│                                         │don't drink tap water!        │     │\n│New York                                 │I'm thinking about going      │$$$  │\n│                                         │during the Christmas season to│     │\n│                                         │check out Rockefeller center. │     │\n│                                         │Might be cold though...       │     │\n│California                               │                              │$$$  │\n└─────────────────────────────────────────┴──────────────────────────────┴─────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_ColumnWidth/LongRowContent.golden",
    "content": "┌─────────────┬──────────────────────────────┬─────┬─────────────┬─────────────┐\n│Name         │Description                   │Type │Required     │Default      │\n├─────────────┼──────────────────────────────┼─────┼─────────────┼─────────────┤\n│command      │A command to be executed      │yes  │hello        │yep          │\n│             │inside the container to assess│     │             │             │\n│             │its health. Each space        │     │             │             │\n│             │delimited token of the command│     │             │             │\n│             │is a separate array element.  │     │             │             │\n│             │Commands exiting 0 are        │     │             │             │\n│             │considered to be successful   │     │             │             │\n│             │probes, whilst all other exit │     │             │             │\n│             │codes are considered failures.│     │             │             │\n└─────────────┴──────────────────────────────┴─────┴─────────────┴─────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_ColumnWidth/LongTextDifferentLanguages.golden",
    "content": "┌─────────────┬──────────────────────────────┬─────┬─────────────┬─────────────┐\n│Hello        │你好                          │مرحبًا│안녕하세요   │             │\n├─────────────┼──────────────────────────────┼─────┼─────────────┼─────────────┤\n│Lorem ipsum  │耐許ヱヨカハ調出あゆ監件び理別│شيء  │版応道潟部中 │각급         │\n│dolor sit    │よン國給災レホチ権輝モエフ会割│قد   │幕爆営報門案 │선거관리위원 │\n│amet, regione│もフ響3現エツ文時しだびほ経機 │للحكو│名見壌府。博 │회의         │\n│detracto eos │ムイメフ敗文ヨク現義なさド請情│مة   │健必権次覧編 │조직·직무범위│\n│an. Has ei   │ゆじょて憶主管州けでふく。排ゃ│والكو│仕断青場内凄 │기타 필요한  │\n│quidam       │わつげ美刊ヱミ出見ツ南者オ抜豆│ري   │新東深簿代供 │사항은 법률로│\n│hendrerit    │ハトロネ論索モネニイ任償スヲ話│الأور│供。守聞書神 │정한다.      │\n│intellegebat,│破リヤヨ秒止口イセソス止央のさ│وبيّون│秀同浜東波恋 │임시회의     │\n│id tamquam   │食周健でてつだ官送ト読聴遊容ひ│,    │闘秀。未格打 │회기는 30일을│\n│iudicabit    │るべ。際ぐドらづ市居ネムヤ研校│بوابة│好作器来利阪 │초과할 수    │\n│necessitatibu│35岩6繹ごわク報拐イ革深52球ゃ │تعديل│持西焦朝三女 │없다. 국가는 │\n│s ius, at    │レスご究東スラ衝3間ラ録占たス │واعتل│。権幽問季負 │여자의 복지와│\n│errem        │。                            │اء   │娘購合旧資健 │권익의 향상을│\n│officiis     │禁にンご忘康ざほぎル騰般ねど事│ضرب  │載員式活陸。 │위하여       │\n│hendrerit    │超スんいう真表何カモ自浩ヲシミ│بـ.  │未倍校朝遺続 │노력하여야   │\n│mei. Exerci  │図客線るふ静王ぱーま写村月掛焼│إذ   │術吉迎暮広知 │한다. 국군의 │\n│noster at    │詐面ぞゃ。昇強ごントほ価保キ族│أسر  │角亡志不説空 │조직과 편성은│\n│has, sit id  │85岡モテ恋困ひりこな刊並せご出│اتّجة │住。法省当死 │법률로       │\n│tota         │来ぼぎむう点目ヲウ止環公ニレ事│اعلان│年勝絡聞方北 │정한다.      │\n│convenire,   │応タス必書タメムノ当84無信升ち│, ٣٠ │投健。室分性 │             │\n│vel ex rebum │ひょ。価ーぐ中客テサ告覧ヨトハ│اكتوب│山天態意画詳 │             │\n│inciderint   │極整ラ得95稿はかラせ江利ス宏丸│ر    │知浅方裁。変 │             │\n│liberavisse. │霊ミ考整ス静将ず業巨職ノラホ収│العصب│激伝阜中野品 │             │\n│Quaeque      │嗅ざな。                      │ة    │省載嗅闘額端 │             │\n│delectus     │                              │استمر│反。中必台際 │             │\n│corrumpit cu │                              │ار   │造事寄民経能 │             │\n│cum.         │                              │ومن. │前作臓       │             │\n│             │                              │أفاق │             │             │\n│             │                              │للسيط│             │             │\n│             │                              │رة   │             │             │\n│             │                              │التار│             │             │\n│             │                              │يخ،  │             │             │\n│             │                              │مع   │             │             │\n│             │                              │بحث, │             │             │\n│             │                              │كلّ   │             │             │\n│             │                              │اتّجة │             │             │\n│             │                              │القوى│             │             │\n│             │                              │مع.  │             │             │\n│             │                              │فبعد │             │             │\n│             │                              │ايطال│             │             │\n│             │                              │يا،  │             │             │\n│             │                              │تم   │             │             │\n│             │                              │حتى, │             │             │\n│             │                              │لكل  │             │             │\n│             │                              │تم   │             │             │\n│             │                              │جسيمة│             │             │\n│             │                              │الإحت│             │             │\n│             │                              │فاظ  │             │             │\n│             │                              │وباست│             │             │\n│             │                              │ثناء,│             │             │\n│             │                              │عل   │             │             │\n│             │                              │فرنسا│             │             │\n│             │                              │وانته│             │             │\n│             │                              │اءً   │             │             │\n│             │                              │الإقت│             │             │\n│             │                              │صادية│             │             │\n│             │                              │عرض. │             │             │\n│             │                              │ونتج │             │             │\n│             │                              │دأبوا│             │             │\n│             │                              │إحكام│             │             │\n│             │                              │بال  │             │             │\n│             │                              │إذ.  │             │             │\n│             │                              │لغات │             │             │\n│             │                              │عملية│             │             │\n│             │                              │وتم  │             │             │\n│             │                              │مع,  │             │             │\n│             │                              │وصل  │             │             │\n│             │                              │بداية│             │             │\n│             │                              │وبغطا│             │             │\n│             │                              │ء    │             │             │\n│             │                              │البري│             │             │\n│             │                              │ة بل,│             │             │\n│             │                              │أي   │             │             │\n│             │                              │قررت │             │             │\n│             │                              │بلاده│             │             │\n│             │                              │فكانت│             │             │\n│             │                              │حدى  │             │             │\n└─────────────┴──────────────────────────────┴─────┴─────────────┴─────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_ColumnWidth/MissingRowContent.golden",
    "content": "┌─────────────┬──────────────────────────────┬─────┬─────────────┬─────────────┐\n│Name         │Description                   │Type │Required     │Default      │\n├─────────────┼──────────────────────────────┼─────┼─────────────┼─────────────┤\n│command      │A command to be executed      │yes  │             │             │\n│             │inside the container to assess│     │             │             │\n│             │its health. Each space        │     │             │             │\n│             │delimited token of the command│     │             │             │\n│             │is a separate array element.  │     │             │             │\n│             │Commands exiting 0 are        │     │             │             │\n│             │considered to be successful   │     │             │             │\n│             │probes, whilst all other exit │     │             │             │\n│             │codes are considered failures.│     │             │             │\n└─────────────┴──────────────────────────────┴─────┴─────────────┴─────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithHeight/LongHeaderContentLongAndShortRows/HeightOf05.golden",
    "content": "┌─────────────┬─────────────────────────────┬──────────────┐\n│ Destination │ Why are you going on this … │ Affordabili… │\n├─────────────┼─────────────────────────────┼──────────────┤\n│ …           │ …                           │ …            │\n└─────────────┴─────────────────────────────┴──────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithHeight/LongHeaderContentLongAndShortRows/HeightOf15.golden",
    "content": "┌─────────────┬─────────────────────────────┬──────────────┐\n│ Destination │ Why are you going on this … │ Affordabili… │\n├─────────────┼─────────────────────────────┼──────────────┤\n│ Mexico      │ I want to go somewhere hot, │ $            │\n│             │ dry, and affordable. Mexico │              │\n│             │ has really good food, just  │              │\n│             │ don't drink tap water!      │              │\n│ New York    │ I'm thinking about going    │ $$$          │\n│             │ during the Christmas season │              │\n│             │ to check out Rockefeller    │              │\n│             │ center. Might be cold       │              │\n│             │ though...                   │              │\n│ California  │                             │ $$$          │\n│ …           │ …                           │ …            │\n└─────────────┴─────────────────────────────┴──────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithHeight/LongHeaderContentLongAndShortRows/HeightOf25.golden",
    "content": "┌─────────────┬─────────────────────────────┬──────────────┐\n│ Destination │ Why are you going on this … │ Affordabili… │\n├─────────────┼─────────────────────────────┼──────────────┤\n│ Mexico      │ I want to go somewhere hot, │ $            │\n│             │ dry, and affordable. Mexico │              │\n│             │ has really good food, just  │              │\n│             │ don't drink tap water!      │              │\n│ New York    │ I'm thinking about going    │ $$$          │\n│             │ during the Christmas season │              │\n│             │ to check out Rockefeller    │              │\n│             │ center. Might be cold       │              │\n│             │ though...                   │              │\n│ California  │                             │ $$$          │\n│ Florida     │ I want to go somewhere hot, │ $$           │\n│             │ humid, and affordable.      │              │\n│             │ Florida has really good     │              │\n│             │ food, just don't go during  │              │\n│             │ hurricane season!           │              │\n│ Maine       │ I'm thinking about going    │ $$           │\n│             │ during the summer to check  │              │\n│             │ out Acadia National Park.   │              │\n│             │ Might be cold though...     │              │\n└─────────────┴─────────────────────────────┴──────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithHeight/LongHeaderContentLongAndShortRows/HeightOf35.golden",
    "content": "┌─────────────┬─────────────────────────────┬──────────────┐\n│ Destination │ Why are you going on this … │ Affordabili… │\n├─────────────┼─────────────────────────────┼──────────────┤\n│ Mexico      │ I want to go somewhere hot, │ $            │\n│             │ dry, and affordable. Mexico │              │\n│             │ has really good food, just  │              │\n│             │ don't drink tap water!      │              │\n│ New York    │ I'm thinking about going    │ $$$          │\n│             │ during the Christmas season │              │\n│             │ to check out Rockefeller    │              │\n│             │ center. Might be cold       │              │\n│             │ though...                   │              │\n│ California  │                             │ $$$          │\n│ Florida     │ I want to go somewhere hot, │ $$           │\n│             │ humid, and affordable.      │              │\n│             │ Florida has really good     │              │\n│             │ food, just don't go during  │              │\n│             │ hurricane season!           │              │\n│ Maine       │ I'm thinking about going    │ $$           │\n│             │ during the summer to check  │              │\n│             │ out Acadia National Park.   │              │\n│             │ Might be cold though...     │              │\n└─────────────┴─────────────────────────────┴──────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithMargins/LongHeaderContentLongAndShortRows.golden",
    "content": "┌───────────────────┬───────────────────────────────────────┬──────────────────┐\n│    Destination    │    Why are you going on this trip…    │    Affordabi…    │\n├───────────────────┼───────────────────────────────────────┼──────────────────┤\n│    Mexico         │    I want to go somewhere hot,        │    $             │\n│                   │    dry, and affordable. Mexico has    │                  │\n│                   │    really good food, just don't       │                  │\n│                   │    drink tap water!                   │                  │\n│    New York       │    I'm thinking about going during    │    $$$           │\n│                   │    the Christmas season to check      │                  │\n│                   │    out Rockefeller center. Might      │                  │\n│                   │    be cold though...                  │                  │\n│    California     │                                       │    $$$           │\n└───────────────────┴───────────────────────────────────────┴──────────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithMargins/LongRowContent.golden",
    "content": "┌───────────────┬────────────────────────┬───────────┬─────────────┬───────────┐\n│    Name       │    Description         │    Ty…    │    Requ…    │    De…    │\n├───────────────┼────────────────────────┼───────────┼─────────────┼───────────┤\n│    command    │    A command to be     │    yes    │    hello    │    yep    │\n│               │    executed inside     │           │             │           │\n│               │    the container to    │           │             │           │\n│               │    assess its          │           │             │           │\n│               │    health. Each        │           │             │           │\n│               │    space delimited     │           │             │           │\n│               │    token of the        │           │             │           │\n│               │    command is a        │           │             │           │\n│               │    separate array      │           │             │           │\n│               │    element.            │           │             │           │\n│               │    Commands exiting    │           │             │           │\n│               │    0 are considered    │           │             │           │\n│               │    to be successful    │           │             │           │\n│               │    probes, whilst      │           │             │           │\n│               │    all other exit      │           │             │           │\n│               │    codes are           │           │             │           │\n│               │    considered          │           │             │           │\n│               │    failures.           │           │             │           │\n└───────────────┴────────────────────────┴───────────┴─────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithMargins/LongTextDifferentLanguages.golden",
    "content": "┌──────────────┬───────────────┬───────────────┬───────────────┬───────────────┐\n│    Hello     │    你好       │    مرحبًا      │    안녕하…    │               │\n├──────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n│    Lorem     │    耐許ヱ     │    شيء قد     │    版応道     │    각급       │\n│    ipsum     │    ヨカハ     │    للحكومة    │    潟部中     │    선거관     │\n│    dolor     │    調出あ     │    والكوري    │    幕爆営     │    리위원     │\n│    sit       │    ゆ監件     │    الأوروب    │    報門案     │    회의       │\n│    amet,     │    び理別     │    يّون,       │    名見壌     │    조직·직    │\n│    region    │    よン國     │    بوابة      │    府。博     │    무범위     │\n│    e         │    給災レ     │    تعديل      │    健必権     │    기타       │\n│    detrac    │    ホチ権     │    واعتلاء    │    次覧編     │    필요한     │\n│    to eos    │    輝モエ     │    ضرب بـ.    │    仕断青     │    사항은     │\n│    an.       │    フ会割     │    إذ أسر     │    場内凄     │    법률로     │\n│    Has ei    │    もフ響3    │    اتّجة       │    新東深     │    정한다.    │\n│    quidam    │    現エツ     │    اعلان,     │    簿代供     │    임시회     │\n│    hendre    │    文時し     │    ٣٠         │    供。守     │    의         │\n│    rit       │    だびほ     │    اكتوبر     │    聞書神     │    회기는     │\n│    intell    │    経機ム     │    العصبة     │    秀同浜     │    30일을     │\n│    egebat    │    イメフ     │    استمرار    │    東波恋     │    초과할     │\n│    , id      │    敗文ヨ     │    ومن.       │    闘秀。     │    수         │\n│    tamqua    │    ク現義     │    أفاق       │    未格打     │    없다.      │\n│    m         │    なさド     │    للسيطرة    │    好作器     │    국가는     │\n│    iudica    │    請情ゆ     │    التاريخ    │    来利阪     │    여자의     │\n│    bit       │    じょて     │    ، مع       │    持西焦     │    복지와     │\n│    necess    │    憶主管     │    بحث, كلّ    │    朝三女     │    권익의     │\n│    itatib    │    州けで     │    اتّجة       │    。権幽     │    향상을     │\n│    us        │    ふく。     │    القوى      │    問季負     │    위하여     │\n│    ius,      │    排ゃわ     │    مع.        │    娘購合     │    노력하     │\n│    at        │    つげ美     │    فبعد       │    旧資健     │    여야       │\n│    errem     │    刊ヱミ     │    ايطاليا    │    載員式     │    한다.      │\n│    offici    │    出見ツ     │    ، تم       │    活陸。     │    국군의     │\n│    is        │    南者オ     │    حتى,       │    未倍校     │    조직과     │\n│    hendre    │    抜豆ハ     │    لكل تم     │    朝遺続     │    편성은     │\n│    rit       │    トロネ     │    جسيمة      │    術吉迎     │    법률로     │\n│    mei.      │    論索モ     │    الإحتفا    │    暮広知     │    정한다.    │\n│    Exerci    │    ネニイ     │    ظ          │    角亡志     │               │\n│    noster    │    任償ス     │    وباستثن    │    不説空     │               │\n│    at        │    ヲ話破     │    اء, عل     │    住。法     │               │\n│    has,      │    リヤヨ     │    فرنسا      │    省当死     │               │\n│    sit id    │    秒止口     │    وانتهاءً    │    年勝絡     │               │\n│    tota      │    イセソ     │    الإقتصا    │    聞方北     │               │\n│    conven    │    ス止央     │    دية        │    投健。     │               │\n│    ire,      │    のさ食     │    عرض.       │    室分性     │               │\n│    vel ex    │    周健で     │    ونتج       │    山天態     │               │\n│    rebum     │    てつだ     │    دأبوا      │    意画詳     │               │\n│    incide    │    官送ト     │    إحكام      │    知浅方     │               │\n│    rint      │    読聴遊     │    بال إذ.    │    裁。変     │               │\n│    libera    │    容ひる     │    لغات       │    激伝阜     │               │\n│    visse.    │    べ。際     │    عملية      │    中野品     │               │\n│    Quaequ    │    ぐドら     │    وتم مع,    │    省載嗅     │               │\n│    e         │    づ市居     │    وصل        │    闘額端     │               │\n│    delect    │    ネムヤ     │    بداية      │    反。中     │               │\n│    us        │    研校35     │    وبغطاء     │    必台際     │               │\n│    corrum    │    岩6繹ご    │    البرية     │    造事寄     │               │\n│    pit cu    │    わク報     │    بل, أي     │    民経能     │               │\n│    cum.      │    拐イ革     │    قررت       │    前作臓     │               │\n│              │    深52球     │    بلاده      │               │               │\n│              │    ゃレス     │    فكانت      │               │               │\n│              │    ご究東     │    حدى        │               │               │\n│              │    スラ衝3    │               │               │               │\n│              │    間ラ録     │               │               │               │\n│              │    占たス     │               │               │               │\n│              │    。         │               │               │               │\n│              │    禁にン     │               │               │               │\n│              │    ご忘康     │               │               │               │\n│              │    ざほぎ     │               │               │               │\n│              │    ル騰般     │               │               │               │\n│              │    ねど事     │               │               │               │\n│              │    超スん     │               │               │               │\n│              │    いう真     │               │               │               │\n│              │    表何カ     │               │               │               │\n│              │    モ自浩     │               │               │               │\n│              │    ヲシミ     │               │               │               │\n│              │    図客線     │               │               │               │\n│              │    るふ静     │               │               │               │\n│              │    王ぱー     │               │               │               │\n│              │    ま写村     │               │               │               │\n│              │    月掛焼     │               │               │               │\n│              │    詐面ぞ     │               │               │               │\n│              │    ゃ。昇     │               │               │               │\n│              │    強ごン     │               │               │               │\n│              │    トほ価     │               │               │               │\n│              │    保キ族8    │               │               │               │\n│              │    5岡モテ    │               │               │               │\n│              │    恋困ひ     │               │               │               │\n│              │    りこな     │               │               │               │\n│              │    刊並せ     │               │               │               │\n│              │    ご出来     │               │               │               │\n│              │    ぼぎむ     │               │               │               │\n│              │    う点目     │               │               │               │\n│              │    ヲウ止     │               │               │               │\n│              │    環公ニ     │               │               │               │\n│              │    レ事応     │               │               │               │\n│              │    タス必     │               │               │               │\n│              │    書タメ     │               │               │               │\n│              │    ムノ当8    │               │               │               │\n│              │    4無信升    │               │               │               │\n│              │    ちひょ     │               │               │               │\n│              │    。価ー     │               │               │               │\n│              │    ぐ中客     │               │               │               │\n│              │    テサ告     │               │               │               │\n│              │    覧ヨト     │               │               │               │\n│              │    ハ極整     │               │               │               │\n│              │    ラ得95     │               │               │               │\n│              │    稿はか     │               │               │               │\n│              │    ラせ江     │               │               │               │\n│              │    利ス宏     │               │               │               │\n│              │    丸霊ミ     │               │               │               │\n│              │    考整ス     │               │               │               │\n│              │    静将ず     │               │               │               │\n│              │    業巨職     │               │               │               │\n│              │    ノラホ     │               │               │               │\n│              │    収嗅ざ     │               │               │               │\n│              │    な。       │               │               │               │\n└──────────────┴───────────────┴───────────────┴───────────────┴───────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithMargins/MissingRowContent.golden",
    "content": "┌───────────────┬────────────────────────────────┬───────────┬────────┬────────┐\n│    Name       │    Description                 │    Ty…    │        │        │\n├───────────────┼────────────────────────────────┼───────────┼────────┼────────┤\n│    command    │    A command to be executed    │    yes    │        │        │\n│               │    inside the container to     │           │        │        │\n│               │    assess its health. Each     │           │        │        │\n│               │    space delimited token of    │           │        │        │\n│               │    the command is a            │           │        │        │\n│               │    separate array element.     │           │        │        │\n│               │    Commands exiting 0 are      │           │        │        │\n│               │    considered to be            │           │        │        │\n│               │    successful probes,          │           │        │        │\n│               │    whilst all other exit       │           │        │        │\n│               │    codes are considered        │           │        │        │\n│               │    failures.                   │           │        │        │\n└───────────────┴────────────────────────────────┴───────────┴────────┴────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithPadding/LongHeaderContentLongAndShortRows.golden",
    "content": "┌─────────────┬────────────────────────────────────────────────┬───────────────┐\n│ Destination │ Why are you going on this trip? Is it a hot o… │ Affordability │\n├─────────────┼────────────────────────────────────────────────┼───────────────┤\n│ Mexico      │ I want to go somewhere hot, dry, and           │ $             │\n│             │ affordable. Mexico has really good food, just  │               │\n│             │ don't drink tap water!                         │               │\n│ New York    │ I'm thinking about going during the Christmas  │ $$$           │\n│             │ season to check out Rockefeller center. Might  │               │\n│             │ be cold though...                              │               │\n│ California  │                                                │ $$$           │\n└─────────────┴────────────────────────────────────────────────┴───────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithPadding/LongRowContent.golden",
    "content": "┌─────────┬────────────────────────────────────────┬──────┬──────────┬─────────┐\n│ Name    │ Description                            │ Type │ Required │ Default │\n├─────────┼────────────────────────────────────────┼──────┼──────────┼─────────┤\n│ command │ A command to be executed inside the    │ yes  │ hello    │ yep     │\n│         │ container to assess its health. Each   │      │          │         │\n│         │ space delimited token of the command   │      │          │         │\n│         │ is a separate array element. Commands  │      │          │         │\n│         │ exiting 0 are considered to be         │      │          │         │\n│         │ successful probes, whilst all other    │      │          │         │\n│         │ exit codes are considered failures.    │      │          │         │\n└─────────┴────────────────────────────────────────┴──────┴──────────┴─────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithPadding/LongTextDifferentLanguages.golden",
    "content": "┌───────┬────────────────┬─────────────────┬─────────────────┬─────────────────┐\n│ Hello │ 你好           │ مرحبًا           │ 안녕하세요      │                 │\n├───────┼────────────────┼─────────────────┼─────────────────┼─────────────────┤\n│       │ 耐許ヱヨカハ調 │ شيء قد للحكومة  │ 版応道潟部中幕  │ 각급            │\n│       │ 出あゆ監件び理 │ والكوري         │ 爆営報門案名見  │ 선거관리위원회  │\n│       │ 別よン國給災レ │ الأوروبيّون,     │ 壌府。博健必権  │ 의              │\n│       │ ホチ権輝モエフ │ بوابة تعديل     │ 次覧編仕断青場  │ 조직·직무범위   │\n│       │ 会割もフ響3現  │ واعتلاء ضرب بـ. │ 内凄新東深簿代  │ 기타 필요한     │\n│       │ エツ文時しだび │ إذ أسر اتّجة     │ 供供。守聞書神  │ 사항은 법률로   │\n│       │ ほ経機ムイメフ │ اعلان, ٣٠       │ 秀同浜東波恋闘  │ 정한다.         │\n│       │ 敗文ヨク現義な │ اكتوبر العصبة   │ 秀。未格打好作  │ 임시회의 회기는 │\n│       │ さド請情ゆじょ │ استمرار ومن.    │ 器来利阪持西焦  │ 30일을 초과할   │\n│       │ て憶主管州けで │ أفاق للسيطرة    │ 朝三女。権幽問  │ 수 없다. 국가는 │\n│       │ ふく。排ゃわつ │ التاريخ، مع     │ 季負娘購合旧資  │ 여자의 복지와   │\n│       │ げ美刊ヱミ出見 │ بحث, كلّ اتّجة    │ 健載員式活陸。  │ 권익의 향상을   │\n│       │ ツ南者オ抜豆ハ │ القوى مع. فبعد  │ 未倍校朝遺続術  │ 위하여          │\n│       │ トロネ論索モネ │ ايطاليا، تم     │ 吉迎暮広知角亡  │ 노력하여야      │\n│       │ ニイ任償スヲ話 │ حتى, لكل تم     │ 志不説空住。法  │ 한다. 국군의    │\n│       │ 破リヤヨ秒止口 │ جسيمة الإحتفاظ  │ 省当死年勝絡聞  │ 조직과 편성은   │\n│       │ イセソス止央の │ وباستثناء, عل   │ 方北投健。室分  │ 법률로 정한다.  │\n│       │ さ食周健でてつ │ فرنسا وانتهاءً   │ 性山天態意画詳  │                 │\n│       │ だ官送ト読聴遊 │ الإقتصادية عرض. │ 知浅方裁。変激  │                 │\n│       │ 容ひるべ。際ぐ │ ونتج دأبوا      │ 伝阜中野品省載  │                 │\n│       │ ドらづ市居ネム │ إحكام بال إذ.   │ 嗅闘額端反。中  │                 │\n│       │ ヤ研校35岩6繹  │ لغات عملية وتم  │ 必台際造事寄民  │                 │\n│       │ ごわク報拐イ革 │ مع, وصل بداية   │ 経能前作臓      │                 │\n│       │ 深52球ゃレスご │ وبغطاء البرية   │                 │                 │\n│       │ 究東スラ衝3間  │ بل, أي قررت     │                 │                 │\n│       │ ラ録占たス。   │ بلاده فكانت حدى │                 │                 │\n│       │ 禁にンご忘康ざ │                 │                 │                 │\n│       │ ほぎル騰般ねど │                 │                 │                 │\n│       │ 事超スんいう真 │                 │                 │                 │\n│       │ 表何カモ自浩ヲ │                 │                 │                 │\n│       │ シミ図客線るふ │                 │                 │                 │\n│       │ 静王ぱーま写村 │                 │                 │                 │\n│       │ 月掛焼詐面ぞゃ │                 │                 │                 │\n│       │ 。昇強ごントほ │                 │                 │                 │\n│       │ 価保キ族85岡モ │                 │                 │                 │\n│       │ テ恋困ひりこな │                 │                 │                 │\n│       │ 刊並せご出来ぼ │                 │                 │                 │\n│       │ ぎむう点目ヲウ │                 │                 │                 │\n│       │ 止環公ニレ事応 │                 │                 │                 │\n│       │ タス必書タメム │                 │                 │                 │\n│       │ ノ当84無信升ち │                 │                 │                 │\n│       │ ひょ。価ーぐ中 │                 │                 │                 │\n│       │ 客テサ告覧ヨト │                 │                 │                 │\n│       │ ハ極整ラ得95稿 │                 │                 │                 │\n│       │ はかラせ江利ス │                 │                 │                 │\n│       │ 宏丸霊ミ考整ス │                 │                 │                 │\n│       │ 静将ず業巨職ノ │                 │                 │                 │\n│       │ ラホ収嗅ざな。 │                 │                 │                 │\n└───────┴────────────────┴─────────────────┴─────────────────┴─────────────────┘"
  },
  {
    "path": "table/testdata/TestContentWrapping_WithPadding/MissingRowContent.golden",
    "content": "┌─────────┬────────────────────────────────────────┬──────┬──────────┬─────────┐\n│ Name    │ Description                            │ Type │ Required │ Default │\n├─────────┼────────────────────────────────────────┼──────┼──────────┼─────────┤\n│ command │ A command to be executed inside the    │ yes  │          │         │\n│         │ container to assess its health. Each   │      │          │         │\n│         │ space delimited token of the command   │      │          │         │\n│         │ is a separate array element. Commands  │      │          │         │\n│         │ exiting 0 are considered to be         │      │          │         │\n│         │ successful probes, whilst all other    │      │          │         │\n│         │ exit codes are considered failures.    │      │          │         │\n└─────────┴────────────────────────────────────────┴──────┴──────────┴─────────┘"
  },
  {
    "path": "table/testdata/TestExtraPaddingHeading.golden",
    "content": "┌────────────────────────┬─────────────────────┬─────────────┐\n│                        │                     │             │\n│                        │                     │             │\n│  Name                  │  Country of Origin  │  Dunk-able  │\n│                        │                     │             │\n│                        │                     │             │\n├────────────────────────┼─────────────────────┼─────────────┤\n│                        │                     │             │\n│                        │                     │             │\n│  Chocolate Digestives  │  UK                 │  Yes        │\n│                        │                     │             │\n│                        │                     │             │\n│                        │                     │             │\n│                        │                     │             │\n│  Tim Tams              │  Australia          │  No         │\n│                        │                     │             │\n│                        │                     │             │\n│                        │                     │             │\n│                        │                     │             │\n│  Hobnobs               │  UK                 │  Yes        │\n│                        │                     │             │\n│                        │                     │             │\n└────────────────────────┴─────────────────────┴─────────────┘"
  },
  {
    "path": "table/testdata/TestExtraPaddingHeadingLong.golden",
    "content": "┌──────────────┬──────────────┬──────────────┐\n│              │              │              │\n│              │              │              │\n│  Looong Na…  │  Looong Co…  │  Looong Du…  │\n│              │              │              │\n│              │              │              │\n├──────────────┼──────────────┼──────────────┤\n│              │              │              │\n│              │              │              │\n│  Chocolate   │  UK          │  Yes         │\n│  Digestives  │              │              │\n│              │              │              │\n│              │              │              │\n│              │              │              │\n│              │              │              │\n│  Tim Tams    │  Australia   │  No          │\n│              │              │              │\n│              │              │              │\n│              │              │              │\n│              │              │              │\n│  Hobnobs     │  UK          │  Yes         │\n│              │              │              │\n│              │              │              │\n└──────────────┴──────────────┴──────────────┘"
  },
  {
    "path": "table/testdata/TestFilter.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestFilterInverse.golden",
    "content": "┌──────────┬─────────┬──────────┐\n│ LANGUAGE │ FORMAL  │ INFORMAL │\n├──────────┼─────────┼──────────┤\n│ French   │ Bonjour │ Salut    │\n└──────────┴─────────┴──────────┘"
  },
  {
    "path": "table/testdata/TestInnerBordersOnly.golden",
    "content": " LANGUAGE │    FORMAL    │ INFORMAL  \n──────────┼──────────────┼───────────\n Chinese  │ Nǐn hǎo      │ Nǐ hǎo    \n──────────┼──────────────┼───────────\n French   │ Bonjour      │ Salut     \n──────────┼──────────────┼───────────\n Japanese │ こんにちは   │ やあ      \n──────────┼──────────────┼───────────\n Russian  │ Zdravstvuyte │ Privet    \n──────────┼──────────────┼───────────\n Spanish  │ Hola         │ ¿Qué tal? "
  },
  {
    "path": "table/testdata/TestMoreCellsThanHeaders.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │           │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestMoreCellsThanHeadersExtra.golden",
    "content": "┌──────────┬──────────────┬───────────┬────────┬────────┐\n│ LANGUAGE │    FORMAL    │           │        │        │\n├──────────┼──────────────┼───────────┼────────┼────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │        │        │\n│ French   │ Bonjour      │ Salut     │ Salut  │        │\n│ Japanese │ こんにちは   │ やあ      │        │        │\n│ Russian  │ Zdravstvuyte │ Privet    │ Privet │ Privet │\n│ Spanish  │ Hola         │ ¿Qué tal? │        │        │\n└──────────┴──────────────┴───────────┴────────┴────────┘"
  },
  {
    "path": "table/testdata/TestNoFinalEmptyRowWhenOverflow.golden",
    "content": "┌────┬────────────┬──────────┬──────────┐\n│Rank│City        │Country   │Population│\n├────┼────────────┼──────────┼──────────┤\n│1   │Tokyo       │Japan     │37,274,000│\n├────┼────────────┼──────────┼──────────┤\n│2   │Delhi       │India     │32,065,760│\n├────┼────────────┼──────────┼──────────┤\n│3   │Shanghai    │China     │28,516,904│\n├────┼────────────┼──────────┼──────────┤\n│4   │Dhaka       │Bangladesh│22,478,116│\n├────┼────────────┼──────────┼──────────┤\n│5   │São Paulo   │Brazil    │22,429,800│\n├────┼────────────┼──────────┼──────────┤\n│…   │…           │…         │…         │\n└────┴────────────┴──────────┴──────────┘"
  },
  {
    "path": "table/testdata/TestStyleFunc/MarginAndPaddingSet.golden",
    "content": "┌────────────┬────────────────┬─────────────┐\n│  LANGUAGE  │     FORMAL     │  INFORMAL   │\n├────────────┼────────────────┼─────────────┤\n│            │                │             │\n│ \u001b[48;2;135;75;252m          \u001b[m │ \u001b[48;2;135;75;252m              \u001b[m │ \u001b[48;2;135;75;252m           \u001b[m │\n│ \u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mChinese\u001b[m\u001b[48;2;135;75;252m \u001b[m │ \u001b[48;2;135;75;252m     \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mNǐn hǎo\u001b[m\u001b[48;2;135;75;252m \u001b[m │ \u001b[48;2;135;75;252m   \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mNǐ hǎo\u001b[m\u001b[48;2;135;75;252m \u001b[m │\n│ \u001b[48;2;135;75;252m          \u001b[m │ \u001b[48;2;135;75;252m              \u001b[m │ \u001b[48;2;135;75;252m           \u001b[m │\n│            │                │             │\n│            │                │             │\n│ \u001b[48;2;135;75;252m          \u001b[m │ \u001b[48;2;135;75;252m              \u001b[m │ \u001b[48;2;135;75;252m           \u001b[m │\n│ \u001b[48;2;135;75;252m  \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mFrench\u001b[m\u001b[48;2;135;75;252m \u001b[m │ \u001b[48;2;135;75;252m     \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mBonjour\u001b[m\u001b[48;2;135;75;252m \u001b[m │ \u001b[48;2;135;75;252m    \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mSalut\u001b[m\u001b[48;2;135;75;252m \u001b[m │\n│ \u001b[48;2;135;75;252m          \u001b[m │ \u001b[48;2;135;75;252m              \u001b[m │ \u001b[48;2;135;75;252m           \u001b[m │\n│            │                │             │\n│            │                │             │\n│ \u001b[48;2;135;75;252m          \u001b[m │ \u001b[48;2;135;75;252m              \u001b[m │ \u001b[48;2;135;75;252m           \u001b[m │\n│ \u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mJapanese\u001b[m\u001b[48;2;135;75;252m \u001b[m │ \u001b[48;2;135;75;252m  \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mこんにちは\u001b[m\u001b[48;2;135;75;252m \u001b[m │ \u001b[48;2;135;75;252m     \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mやあ\u001b[m\u001b[48;2;135;75;252m \u001b[m │\n│ \u001b[48;2;135;75;252m          \u001b[m │ \u001b[48;2;135;75;252m              \u001b[m │ \u001b[48;2;135;75;252m           \u001b[m │\n│            │                │             │\n│            │                │             │\n│ \u001b[48;2;135;75;252m          \u001b[m │ \u001b[48;2;135;75;252m              \u001b[m │ \u001b[48;2;135;75;252m           \u001b[m │\n│ \u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mRussian\u001b[m\u001b[48;2;135;75;252m \u001b[m │ \u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mZdravstvuyte\u001b[m\u001b[48;2;135;75;252m \u001b[m │ \u001b[48;2;135;75;252m   \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mPrivet\u001b[m\u001b[48;2;135;75;252m \u001b[m │\n│ \u001b[48;2;135;75;252m          \u001b[m │ \u001b[48;2;135;75;252m              \u001b[m │ \u001b[48;2;135;75;252m           \u001b[m │\n│            │                │             │\n│            │                │             │\n│ \u001b[48;2;135;75;252m          \u001b[m │ \u001b[48;2;135;75;252m              \u001b[m │ \u001b[48;2;135;75;252m           \u001b[m │\n│ \u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mSpanish\u001b[m\u001b[48;2;135;75;252m \u001b[m │ \u001b[48;2;135;75;252m        \u001b[m\u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252mHola\u001b[m\u001b[48;2;135;75;252m \u001b[m │ \u001b[48;2;135;75;252m \u001b[m\u001b[48;2;135;75;252m¿Qué tal?\u001b[m\u001b[48;2;135;75;252m \u001b[m │\n│ \u001b[48;2;135;75;252m          \u001b[m │ \u001b[48;2;135;75;252m              \u001b[m │ \u001b[48;2;135;75;252m           \u001b[m │\n│            │                │             │\n└────────────┴────────────────┴─────────────┘"
  },
  {
    "path": "table/testdata/TestStyleFunc/RightAlignedTextWithMargins.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│  Chinese │      Nǐn hǎo │    Nǐ hǎo │\n│   French │      Bonjour │     Salut │\n│ Japanese │   こんにちは │      やあ │\n│  Russian │ Zdravstvuyte │    Privet │\n│  Spanish │         Hola │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTable.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableANSI.golden",
    "content": "┌───────────┬────────┬──────┐\n│   Fruit   │ Color  │ \u001b[31mC\u001b[0m\u001b[32mo\u001b[0m\u001b[34md\u001b[0m\u001b[33me\u001b[0m │\n├───────────┼────────┼──────┤\n│ Apple     │ Red    │ \u001b[31m31\u001b[0m   │\n│ Lime      │ Green  │ \u001b[32m32\u001b[0m   │\n│ Banana    │ Yellow │ \u001b[33m33\u001b[0m   │\n│ Blueberry │ Blue   │ \u001b[34m34\u001b[0m   │\n└───────────┴────────┴──────┘"
  },
  {
    "path": "table/testdata/TestTableBorder.golden",
    "content": "╔══════════╦══════════════╦═══════════╗\n║ LANGUAGE ║    FORMAL    ║ INFORMAL  ║\n╠══════════╬══════════════╬═══════════╣\n║ Chinese  ║ Nǐn hǎo      ║ Nǐ hǎo    ║\n║ French   ║ Bonjour      ║ Salut     ║\n║ Japanese ║ こんにちは   ║ やあ      ║\n║ Russian  ║ Zdravstvuyte ║ Privet    ║\n║ Spanish  ║ Hola         ║ ¿Qué tal? ║\n╚══════════╩══════════════╩═══════════╝"
  },
  {
    "path": "table/testdata/TestTableEmpty.golden",
    "content": "┌──────────┬────────┬──────────┐\n│ LANGUAGE │ FORMAL │ INFORMAL │\n├──────────┼────────┼──────────┤\n└──────────┴────────┴──────────┘"
  },
  {
    "path": "table/testdata/TestTableExample.golden",
    "content": "\u001b[38;5;99m┌\u001b[m\u001b[38;5;99m──────────\u001b[m\u001b[38;5;99m┬\u001b[m\u001b[38;5;99m───────────────────────────────\u001b[m\u001b[38;5;99m┬\u001b[m\u001b[38;5;99m─────────────────\u001b[m\u001b[38;5;99m┐\u001b[m\n\u001b[38;5;99m│\u001b[m LANGUAGE \u001b[38;5;99m│\u001b[m            FORMAL             \u001b[38;5;99m│\u001b[m    INFORMAL     \u001b[38;5;99m│\u001b[m\n\u001b[38;5;99m├\u001b[m\u001b[38;5;99m──────────\u001b[m\u001b[38;5;99m┼\u001b[m\u001b[38;5;99m───────────────────────────────\u001b[m\u001b[38;5;99m┼\u001b[m\u001b[38;5;99m─────────────────\u001b[m\u001b[38;5;99m┤\u001b[m\n\u001b[38;5;99m│\u001b[m Chinese  \u001b[38;5;99m│\u001b[m 您好                          \u001b[38;5;99m│\u001b[m 你好            \u001b[38;5;99m│\u001b[m\n\u001b[38;5;99m│\u001b[m Japanese \u001b[38;5;99m│\u001b[m こんにちは                    \u001b[38;5;99m│\u001b[m やあ            \u001b[38;5;99m│\u001b[m\n\u001b[38;5;99m│\u001b[m Russian  \u001b[38;5;99m│\u001b[m Здравствуйте                  \u001b[38;5;99m│\u001b[m Привет          \u001b[38;5;99m│\u001b[m\n\u001b[38;5;99m│\u001b[m Spanish  \u001b[38;5;99m│\u001b[m Hola                          \u001b[38;5;99m│\u001b[m ¿Qué tal?       \u001b[38;5;99m│\u001b[m\n\u001b[38;5;99m│\u001b[m English  \u001b[38;5;99m│\u001b[m You look absolutely fabulous. \u001b[38;5;99m│\u001b[m How's it going? \u001b[38;5;99m│\u001b[m\n\u001b[38;5;99m└\u001b[m\u001b[38;5;99m──────────\u001b[m\u001b[38;5;99m┴\u001b[m\u001b[38;5;99m───────────────────────────────\u001b[m\u001b[38;5;99m┴\u001b[m\u001b[38;5;99m─────────────────\u001b[m\u001b[38;5;99m┘\u001b[m"
  },
  {
    "path": "table/testdata/TestTableHeightExact.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightExtra.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRow/HeightOf01.golden",
    "content": "┌──────────┬──────────────┬───────────┐"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRow/HeightOf02.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRow/HeightOf03.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRow/HeightOf04.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRow/HeightOf05.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRow/HeightOf06.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRow/HeightOf07.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRow/HeightOf08.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRow/HeightOf09.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf01.golden",
    "content": "┌──────────┬──────────────┬───────────┐"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf02.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf03.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf04.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf05.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf06.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf07.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf08.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf09.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf10.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf11.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf12.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf13.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf14.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf15.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf16.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf17.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf18.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n│          │              │           │\n│ Japanese │ こんにちは   │ やあ      │\n│          │              │           │\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf19.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n│          │              │           │\n│ Japanese │ こんにちは   │ やあ      │\n│          │              │           │\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf20.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n│          │              │           │\n│ Japanese │ こんにちは   │ やあ      │\n│          │              │           │\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/NoBorderRowPadding/HeightOf21.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n│          │              │           │\n│ Japanese │ こんにちは   │ やあ      │\n│          │              │           │\n│          │              │           │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│          │              │           │\n│          │              │           │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf01.golden",
    "content": "┌──────────┬──────────────┬───────────┐"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf02.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf03.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf04.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf05.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf06.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf07.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf08.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf09.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n├──────────┼──────────────┼───────────┤\n│ French   │ Bonjour      │ Salut     │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf10.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n├──────────┼──────────────┼───────────┤\n│ French   │ Bonjour      │ Salut     │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf11.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n├──────────┼──────────────┼───────────┤\n│ French   │ Bonjour      │ Salut     │\n├──────────┼──────────────┼───────────┤\n│ Japanese │ こんにちは   │ やあ      │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf12.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n├──────────┼──────────────┼───────────┤\n│ French   │ Bonjour      │ Salut     │\n├──────────┼──────────────┼───────────┤\n│ Japanese │ こんにちは   │ やあ      │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRow/HeightOf13.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n├──────────┼──────────────┼───────────┤\n│ French   │ Bonjour      │ Salut     │\n├──────────┼──────────────┼───────────┤\n│ Japanese │ こんにちは   │ やあ      │\n├──────────┼──────────────┼───────────┤\n│ Russian  │ Zdravstvuyte │ Privet    │\n├──────────┼──────────────┼───────────┤\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf01.golden",
    "content": "┌──────────┬──────────────┬───────────┐"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf02.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf03.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf04.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf05.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf06.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf07.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf08.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf09.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf10.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf11.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf12.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf13.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf14.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf15.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf16.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf17.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf18.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf19.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf20.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf21.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Japanese │ こんにちは   │ やあ      │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf22.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Japanese │ こんにちは   │ やあ      │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf23.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Japanese │ こんにちは   │ やあ      │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf24.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Japanese │ こんにちは   │ やあ      │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ …        │ …            │ …         │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightShrink/WithBorderRowPadding/HeightOf25.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│          │              │           │\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ French   │ Bonjour      │ Salut     │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Japanese │ こんにちは   │ やあ      │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│          │              │           │\n├──────────┼──────────────┼───────────┤\n│          │              │           │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n│          │              │           │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeightWithYOffset.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableHeights.golden",
    "content": "┌──────────────────┬─────────────────────────┐\n│ EXPRESSION       │ MEANING                 │\n├──────────────────┼─────────────────────────┤\n│                  │                         │\n│ Chutar o balde   │  Literally translates   │\n│                  │  to \"kick the bucket.\"  │\n│                  │  It's used when         │\n│                  │  someone gives up or    │\n│                  │  loses patience.        │\n│                  │                         │\n│                  │                         │\n│ Engolir sapos    │  Literally means \"to    │\n│                  │  swallow frogs.\" It's   │\n│                  │  used to describe       │\n│                  │  someone who has to     │\n│                  │  tolerate or endure     │\n│                  │  unpleasant             │\n│                  │  situations.            │\n│                  │                         │\n│                  │                         │\n│ Arroz de festa   │  Literally means        │\n│                  │  \"party rice.\" It´s     │\n│                  │  used to refer to       │\n│                  │  someone who shows up   │\n│                  │  everywhere.            │\n│                  │                         │\n└──────────────────┴─────────────────────────┘"
  },
  {
    "path": "table/testdata/TestTableMarginAndRightAlignment.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │       FORMAL │  INFORMAL │\n├──────────┼──────────────┼───────────┤\n│   Arabic │        أهلين │      أهلا │\n│  Chinese │      Nǐn hǎo │    Nǐ hǎo │\n│   French │      Bonjour │     Salut │\n│ Japanese │   こんにちは │      やあ │\n│  Russian │ Zdravstvuyte │    Privet │\n│  Spanish │         Hola │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableMultiLineRowSeparator.golden",
    "content": "┌──────────────────┬─────────────────────────┐\n│ EXPRESSION       │ MEANING                 │\n├──────────────────┼─────────────────────────┤\n│                  │                         │\n│ Chutar o balde   │  Literally translates   │\n│                  │  to \"kick the bucket.\"  │\n│                  │  It's used when         │\n│                  │  someone gives up or    │\n│                  │  loses patience.        │\n│                  │                         │\n├──────────────────┼─────────────────────────┤\n│                  │                         │\n│ Engolir sapos    │  Literally means \"to    │\n│                  │  swallow frogs.\" It's   │\n│                  │  used to describe       │\n│                  │  someone who has to     │\n│                  │  tolerate or endure     │\n│                  │  unpleasant             │\n│                  │  situations.            │\n│                  │                         │\n├──────────────────┼─────────────────────────┤\n│                  │                         │\n│ Arroz de festa   │  Literally means        │\n│                  │  \"party rice.\" It´s     │\n│                  │  used to refer to       │\n│                  │  someone who shows up   │\n│                  │  everywhere.            │\n│                  │                         │\n└──────────────────┴─────────────────────────┘"
  },
  {
    "path": "table/testdata/TestTableNoColumnSeparators.golden",
    "content": "┌───────────────────────────────────┐\n│ Chinese   Nǐn hǎo       Nǐ hǎo    │\n│ French    Bonjour       Salut     │\n│ Japanese  こんにちは    やあ      │\n│ Russian   Zdravstvuyte  Privet    │\n│ Spanish   Hola          ¿Qué tal? │\n└───────────────────────────────────┘"
  },
  {
    "path": "table/testdata/TestTableNoColumnSeparatorsWithHeaders.golden",
    "content": "┌───────────────────────────────────┐\n│ LANGUAGE     FORMAL     INFORMAL  │\n├───────────────────────────────────┤\n│ Chinese   Nǐn hǎo       Nǐ hǎo    │\n│ French    Bonjour       Salut     │\n│ Japanese  こんにちは    やあ      │\n│ Russian   Zdravstvuyte  Privet    │\n│ Spanish   Hola          ¿Qué tal? │\n└───────────────────────────────────┘"
  },
  {
    "path": "table/testdata/TestTableNoHeaders.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableNoStyleFunc.golden",
    "content": "┌────────┬────────────┬─────────┐\n│LANGUAGE│FORMAL      │INFORMAL │\n├────────┼────────────┼─────────┤\n│Chinese │Nǐn hǎo     │Nǐ hǎo   │\n│French  │Bonjour     │Salut    │\n│Japanese│こんにちは  │やあ     │\n│Russian │Zdravstvuyte│Privet   │\n│Spanish │Hola        │¿Qué tal?│\n└────────┴────────────┴─────────┘"
  },
  {
    "path": "table/testdata/TestTableOverFlowNoWrap.golden",
    "content": "┌──────────────┬───────────────┬───────────────┬───────────────┬───────────────┐\n│    Hello     │     你好      │     مرحبًا     │  안녕하세요   │               │\n├──────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n│ Lorem ipsum… │ 耐許ヱヨカハ… │ شيء قد للحكو… │ 각급 선거관…  │ 版応道潟部中… │\n│ …            │ …             │ …             │ …             │ …             │\n└──────────────┴───────────────┴───────────────┴───────────────┴───────────────┘"
  },
  {
    "path": "table/testdata/TestTableRowSeparators/no_overflow.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n├──────────┼──────────────┼───────────┤\n│ French   │ Bonjour      │ Salut     │\n├──────────┼──────────────┼───────────┤\n│ Japanese │ こんにちは   │ やあ      │\n├──────────┼──────────────┼───────────┤\n│ Russian  │ Zdravstvuyte │ Privet    │\n├──────────┼──────────────┼───────────┤\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableRowSeparators/with_overflow.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n├──────────┼──────────────┼───────────┤\n│ French   │ Bonjour      │ Salut     │\n├──────────┼──────────────┼───────────┤\n│ Japanese │ こんにちは   │ やあ      │\n├──────────┼──────────────┼───────────┤\n│ …        │ …            │ …         │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableRowSeparators.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n├──────────┼──────────────┼───────────┤\n│ French   │ Bonjour      │ Salut     │\n├──────────┼──────────────┼───────────┤\n│ Japanese │ こんにちは   │ やあ      │\n├──────────┼──────────────┼───────────┤\n│ Russian  │ Zdravstvuyte │ Privet    │\n├──────────┼──────────────┼───────────┤\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableSetRows.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableShrinkWithYOffset/NoHeaders.golden",
    "content": "┌───┬────────────────┬──────────────┬──────────┐\n│58 │Madrid          │Spain         │6,713,557 │\n│59 │Haerbin         │China         │6,665,951 │\n│60 │Toronto         │Canada        │6,312,974 │\n│61 │Belo Horizonte  │Brazil        │6,194,292 │\n│62 │Khartoum        │Sudan         │6,160,327 │\n│63 │Johannesburg    │South Africa  │6,065,354 │\n│64 │Singapore       │Singapore     │6,039,577 │\n│65 │Dalian          │China         │5,930,140 │\n│66 │Qingdao         │China         │5,865,232 │\n│67 │Zhengzhou       │China         │5,690,312 │\n│68 │Ji Nan Shandong │China         │5,663,015 │\n│69 │Barcelona       │Spain         │5,658,472 │\n│70 │Saint Petersburg│Russia        │5,535,556 │\n│71 │Abidjan         │Ivory Coast   │5,515,790 │\n│72 │Yangon          │Myanmar       │5,514,454 │\n│73 │Fukuoka         │Japan         │5,502,591 │\n│74 │Alexandria      │Egypt         │5,483,605 │\n│75 │Guadalajara     │Mexico        │5,339,583 │\n│76 │Ankara          │Turkey        │5,309,690 │\n│77 │Chittagong      │Bangladesh    │5,252,842 │\n│78 │Addis Ababa     │Ethiopia      │5,227,794 │\n│79 │Melbourne       │Australia     │5,150,766 │\n│80 │Nairobi         │Kenya         │5,118,844 │\n│81 │Hanoi           │Vietnam       │5,067,352 │\n│82 │Sydney          │Australia     │5,056,571 │\n│83 │Monterrey       │Mexico        │5,036,535 │\n│84 │Changsha        │China         │4,809,887 │\n│85 │Brasilia        │Brazil        │4,803,877 │\n│86 │Cape Town       │South Africa  │4,800,954 │\n│87 │Jiddah          │Saudi Arabia  │4,780,740 │\n│88 │Urumqi          │China         │4,710,203 │\n│89 │Kunming         │China         │4,657,381 │\n│90 │Changchun       │China         │4,616,002 │\n│91 │Hefei           │China         │4,496,456 │\n│92 │Shantou         │China         │4,490,411 │\n│93 │Xinbei          │Taiwan        │4,470,672 │\n│94 │Kabul           │Afghanistan   │4,457,882 │\n│95 │Ningbo          │China         │4,405,292 │\n│96 │Tel Aviv        │Israel        │4,343,584 │\n│97 │Yaounde         │Cameroon      │4,336,670 │\n│98 │Rome            │Italy         │4,297,877 │\n│99 │Shijiazhuang    │China         │4,285,135 │\n│100│Montreal        │Canada        │4,276,526 │\n└───┴────────────────┴──────────────┴──────────┘"
  },
  {
    "path": "table/testdata/TestTableShrinkWithYOffset/WithBorderRow.golden",
    "content": "┌────┬────────────────┬──────────────┬──────────┐\n│Rank│City            │Country       │Population│\n├────┼────────────────┼──────────────┼──────────┤\n│81  │Hanoi           │Vietnam       │5,067,352 │\n├────┼────────────────┼──────────────┼──────────┤\n│82  │Sydney          │Australia     │5,056,571 │\n├────┼────────────────┼──────────────┼──────────┤\n│83  │Monterrey       │Mexico        │5,036,535 │\n├────┼────────────────┼──────────────┼──────────┤\n│84  │Changsha        │China         │4,809,887 │\n├────┼────────────────┼──────────────┼──────────┤\n│85  │Brasilia        │Brazil        │4,803,877 │\n├────┼────────────────┼──────────────┼──────────┤\n│86  │Cape Town       │South Africa  │4,800,954 │\n├────┼────────────────┼──────────────┼──────────┤\n│87  │Jiddah          │Saudi Arabia  │4,780,740 │\n├────┼────────────────┼──────────────┼──────────┤\n│88  │Urumqi          │China         │4,710,203 │\n├────┼────────────────┼──────────────┼──────────┤\n│89  │Kunming         │China         │4,657,381 │\n├────┼────────────────┼──────────────┼──────────┤\n│90  │Changchun       │China         │4,616,002 │\n├────┼────────────────┼──────────────┼──────────┤\n│91  │Hefei           │China         │4,496,456 │\n├────┼────────────────┼──────────────┼──────────┤\n│92  │Shantou         │China         │4,490,411 │\n├────┼────────────────┼──────────────┼──────────┤\n│93  │Xinbei          │Taiwan        │4,470,672 │\n├────┼────────────────┼──────────────┼──────────┤\n│94  │Kabul           │Afghanistan   │4,457,882 │\n├────┼────────────────┼──────────────┼──────────┤\n│95  │Ningbo          │China         │4,405,292 │\n├────┼────────────────┼──────────────┼──────────┤\n│96  │Tel Aviv        │Israel        │4,343,584 │\n├────┼────────────────┼──────────────┼──────────┤\n│97  │Yaounde         │Cameroon      │4,336,670 │\n├────┼────────────────┼──────────────┼──────────┤\n│98  │Rome            │Italy         │4,297,877 │\n├────┼────────────────┼──────────────┼──────────┤\n│99  │Shijiazhuang    │China         │4,285,135 │\n├────┼────────────────┼──────────────┼──────────┤\n│100 │Montreal        │Canada        │4,276,526 │\n└────┴────────────────┴──────────────┴──────────┘"
  },
  {
    "path": "table/testdata/TestTableShrinkWithYOffset/WithHeaders.golden",
    "content": "┌────┬────────────────┬──────────────┬──────────┐\n│Rank│City            │Country       │Population│\n├────┼────────────────┼──────────────┼──────────┤\n│60  │Toronto         │Canada        │6,312,974 │\n│61  │Belo Horizonte  │Brazil        │6,194,292 │\n│62  │Khartoum        │Sudan         │6,160,327 │\n│63  │Johannesburg    │South Africa  │6,065,354 │\n│64  │Singapore       │Singapore     │6,039,577 │\n│65  │Dalian          │China         │5,930,140 │\n│66  │Qingdao         │China         │5,865,232 │\n│67  │Zhengzhou       │China         │5,690,312 │\n│68  │Ji Nan Shandong │China         │5,663,015 │\n│69  │Barcelona       │Spain         │5,658,472 │\n│70  │Saint Petersburg│Russia        │5,535,556 │\n│71  │Abidjan         │Ivory Coast   │5,515,790 │\n│72  │Yangon          │Myanmar       │5,514,454 │\n│73  │Fukuoka         │Japan         │5,502,591 │\n│74  │Alexandria      │Egypt         │5,483,605 │\n│75  │Guadalajara     │Mexico        │5,339,583 │\n│76  │Ankara          │Turkey        │5,309,690 │\n│77  │Chittagong      │Bangladesh    │5,252,842 │\n│78  │Addis Ababa     │Ethiopia      │5,227,794 │\n│79  │Melbourne       │Australia     │5,150,766 │\n│80  │Nairobi         │Kenya         │5,118,844 │\n│81  │Hanoi           │Vietnam       │5,067,352 │\n│82  │Sydney          │Australia     │5,056,571 │\n│83  │Monterrey       │Mexico        │5,036,535 │\n│84  │Changsha        │China         │4,809,887 │\n│85  │Brasilia        │Brazil        │4,803,877 │\n│86  │Cape Town       │South Africa  │4,800,954 │\n│87  │Jiddah          │Saudi Arabia  │4,780,740 │\n│88  │Urumqi          │China         │4,710,203 │\n│89  │Kunming         │China         │4,657,381 │\n│90  │Changchun       │China         │4,616,002 │\n│91  │Hefei           │China         │4,496,456 │\n│92  │Shantou         │China         │4,490,411 │\n│93  │Xinbei          │Taiwan        │4,470,672 │\n│94  │Kabul           │Afghanistan   │4,457,882 │\n│95  │Ningbo          │China         │4,405,292 │\n│96  │Tel Aviv        │Israel        │4,343,584 │\n│97  │Yaounde         │Cameroon      │4,336,670 │\n│98  │Rome            │Italy         │4,297,877 │\n│99  │Shijiazhuang    │China         │4,285,135 │\n│100 │Montreal        │Canada        │4,276,526 │\n└────┴────────────────┴──────────────┴──────────┘"
  },
  {
    "path": "table/testdata/TestTableUnsetBorders.golden",
    "content": " LANGUAGE │    FORMAL    │ INFORMAL  \n──────────┼──────────────┼───────────\n Chinese  │ Nǐn hǎo      │ Nǐ hǎo    \n French   │ Bonjour      │ Salut     \n Japanese │ こんにちは   │ やあ      \n Russian  │ Zdravstvuyte │ Privet    \n Spanish  │ Hola         │ ¿Qué tal? "
  },
  {
    "path": "table/testdata/TestTableUnsetHeaderSeparator.golden",
    "content": " LANGUAGE │    FORMAL    │ INFORMAL  \n Chinese  │ Nǐn hǎo      │ Nǐ hǎo    \n French   │ Bonjour      │ Salut     \n Japanese │ こんにちは   │ やあ      \n Russian  │ Zdravstvuyte │ Privet    \n Spanish  │ Hola         │ ¿Qué tal? "
  },
  {
    "path": "table/testdata/TestTableUnsetHeaderSeparatorWithBorder.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n│ Chinese  │ Nǐn hǎo      │ Nǐ hǎo    │\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestTableWidthExpand.golden",
    "content": "┌──────────────────────────┬─────────────────────────┬─────────────────────────┐\n│         LANGUAGE         │         FORMAL          │        INFORMAL         │\n├──────────────────────────┼─────────────────────────┼─────────────────────────┤\n│ Chinese                  │ Nǐn hǎo                 │ Nǐ hǎo                  │\n│ French                   │ Bonjour                 │ Salut                   │\n│ Japanese                 │ こんにちは              │ やあ                    │\n│ Russian                  │ Zdravstvuyte            │ Privet                  │\n│ Spanish                  │ Hola                    │ ¿Qué tal?               │\n└──────────────────────────┴─────────────────────────┴─────────────────────────┘"
  },
  {
    "path": "table/testdata/TestTableWidthShrink/DefaultBorders.golden",
    "content": "┌────────┬─────────┬─────────┐\n│ LANGU… │ FORMAL  │ INFORM… │\n├────────┼─────────┼─────────┤\n│ Chines │ Nǐn hǎo │ Nǐ hǎo  │\n│ e      │         │         │\n│ French │ Bonjour │ Salut   │\n│ Japane │ こんに  │ やあ    │\n│ se     │ ちは    │         │\n│ Russia │ Zdravst │ Privet  │\n│ n      │ vuyte   │         │\n│ Spanis │ Hola    │ ¿Qué    │\n│ h      │         │ tal?    │\n└────────┴─────────┴─────────┘"
  },
  {
    "path": "table/testdata/TestTableWidthShrink/NoBorders.golden",
    "content": "──────────────────────────────\n LANGUAGE   FORMAL   INFORMAL \n──────────────────────────────\n Chinese   Nǐn hǎo   Nǐ hǎo   \n French    Bonjour   Salut    \n Japanese  こんにち  やあ     \n           は                 \n Russian   Zdravstv  Privet   \n           uyte               \n Spanish   Hola      ¿Qué     \n                     tal?     \n──────────────────────────────"
  },
  {
    "path": "table/testdata/TestTableWidthShrink/OutlineBordersOnly.golden",
    "content": "┌────────────────────────────┐\n│ LANGUA…   FORMAL   INFORM… │\n├────────────────────────────┤\n│ Chinese  Nǐn hǎo   Nǐ hǎo  │\n│ French   Bonjour   Salut   │\n│ Japanes  こんにち  やあ    │\n│ e        は                │\n│ Russian  Zdravstv  Privet  │\n│          uyte              │\n│ Spanish  Hola      ¿Qué    │\n│                    tal?    │\n└────────────────────────────┘"
  },
  {
    "path": "table/testdata/TestTableWidthSmartCrop.golden",
    "content": "┌──────┬─────┬──────────┐\n│ Name │ Ag… │ Location │\n├──────┼─────┼──────────┤\n│ Kini │ 40  │ New York │\n│ Eli  │ 30  │ London   │\n│ Iris │ 20  │ Paris    │\n└──────┴─────┴──────────┘"
  },
  {
    "path": "table/testdata/TestTableWidthSmartCropExtensive.golden",
    "content": "┏━━━━┳━━━━━┳━━━━━┓\n┃ L… ┃ FO… ┃ IN… ┃\n┣━━━━╋━━━━━╋━━━━━┫\n┃ C… ┃ 您… ┃ 你… ┃\n┃ J… ┃ こ… ┃ や… ┃\n┃ A… ┃ أه… ┃ أه… ┃\n┃ R… ┃ Зд… ┃ Пр… ┃\n┃ S… ┃ Ho… ┃ ¿Q… ┃\n┃ E… ┃ Yo… ┃ Ho… ┃\n┗━━━━┻━━━━━┻━━━━━┛"
  },
  {
    "path": "table/testdata/TestTableWidthSmartCropTiny.golden",
    "content": "┌\n│\n├\n│\n│\n│\n│\n│\n└"
  },
  {
    "path": "table/testdata/TestTableWidths.golden",
    "content": "──────────────────────────────\n LANGUAGE   FORMAL   INFORMAL \n──────────────────────────────\n Chinese   Nǐn hǎo   Nǐ hǎo   \n French    Bonjour   Salut    \n Japanese  こんにち  やあ     \n           は                 \n Russian   Zdravstv  Privet   \n           uyte               \n Spanish   Hola      ¿Qué     \n                     tal?     \n──────────────────────────────"
  },
  {
    "path": "table/testdata/TestTableWithBackground.golden",
    "content": "\u001b[97;48;5;18m┌\u001b[m\u001b[97;48;5;18m────────\u001b[m\u001b[97;48;5;18m┬\u001b[m\u001b[97;48;5;18m────────────\u001b[m\u001b[97;48;5;18m┬\u001b[m\u001b[97;48;5;18m─────────\u001b[m\u001b[97;48;5;18m┐\u001b[m\n\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mLANGUAGE\u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mFORMAL\u001b[m\u001b[48;5;18m      \u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mINFORMAL\u001b[m\u001b[48;5;18m \u001b[m\u001b[97;48;5;18m│\u001b[m\n\u001b[97;48;5;18m├\u001b[m\u001b[97;48;5;18m────────\u001b[m\u001b[97;48;5;18m┼\u001b[m\u001b[97;48;5;18m────────────\u001b[m\u001b[97;48;5;18m┼\u001b[m\u001b[97;48;5;18m─────────\u001b[m\u001b[97;48;5;18m┤\u001b[m\n\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mChinese\u001b[m\u001b[48;5;18m \u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mNǐn hǎo\u001b[m\u001b[48;5;18m     \u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mNǐ hǎo\u001b[m\u001b[48;5;18m   \u001b[m\u001b[97;48;5;18m│\u001b[m\n\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mFrench\u001b[m\u001b[48;5;18m  \u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mBonjour\u001b[m\u001b[48;5;18m     \u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mSalut\u001b[m\u001b[48;5;18m    \u001b[m\u001b[97;48;5;18m│\u001b[m\n\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mJapanese\u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mこんにちは\u001b[m\u001b[48;5;18m  \u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mやあ\u001b[m\u001b[48;5;18m     \u001b[m\u001b[97;48;5;18m│\u001b[m\n\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mRussian\u001b[m\u001b[48;5;18m \u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mZdravstvuyte\u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mPrivet\u001b[m\u001b[48;5;18m   \u001b[m\u001b[97;48;5;18m│\u001b[m\n\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mSpanish\u001b[m\u001b[48;5;18m \u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18mHola\u001b[m\u001b[48;5;18m        \u001b[m\u001b[97;48;5;18m│\u001b[m\u001b[97;48;5;18m¿Qué tal?\u001b[m\u001b[97;48;5;18m│\u001b[m\n\u001b[97;48;5;18m└\u001b[m\u001b[97;48;5;18m────────\u001b[m\u001b[97;48;5;18m┴\u001b[m\u001b[97;48;5;18m────────────\u001b[m\u001b[97;48;5;18m┴\u001b[m\u001b[97;48;5;18m─────────\u001b[m\u001b[97;48;5;18m┘\u001b[m"
  },
  {
    "path": "table/testdata/TestTableYOffset.golden",
    "content": "┌──────────┬──────────────┬───────────┐\n│ LANGUAGE │    FORMAL    │ INFORMAL  │\n├──────────┼──────────────┼───────────┤\n│ French   │ Bonjour      │ Salut     │\n│ Japanese │ こんにちは   │ やあ      │\n│ Russian  │ Zdravstvuyte │ Privet    │\n│ Spanish  │ Hola         │ ¿Qué tal? │\n└──────────┴──────────────┴───────────┘"
  },
  {
    "path": "table/testdata/TestWrapPreStyledContent.golden",
    "content": "┌─────────┬────────────────┬────────────────────────────────┬─────────────┬────┐\n│Package  │Version         │Link                            │             │    │\n├─────────┼────────────────┼────────────────────────────────┼─────────────┼────┤\n│sourcegit│0.19            │\u001b[38;2;49;187;113mhttps://aur.archlinux.org/packag\u001b[m│             │    │\n│         │                │\u001b[38;2;49;187;113mes/sourcegit-bin\u001b[m                │             │    │\n│         │                │                                │             │    │\n│Welcome  │いらっしゃいませ│مرحباً                           │환영         │欢迎│\n│Goodbye  │さようなら      │مع السلامة                      │안녕히 가세요│再见│\n└─────────┴────────────────┴────────────────────────────────┴─────────────┴────┘"
  },
  {
    "path": "table/testdata/TestWrapStyleFuncContent.golden",
    "content": "┌─────────┬────────────────┬───────────────────────────────┐\n│Package  │Version         │Link                           │\n├─────────┼────────────────┼───────────────────────────────┤\n│sourcegit│0.19            │\u001b[38;2;49;187;113mhttps://aur.archlinux.org/packa\u001b[m│\n│         │                │\u001b[38;2;49;187;113mges/sourcegit-bin\u001b[m              │\n│Welcome  │いらっしゃいませ│مرحباً                          │\n│Goodbye  │さようなら      │مع السلامة                     │\n└─────────┴────────────────┴───────────────────────────────┘"
  },
  {
    "path": "table/util.go",
    "content": "package table\n\nimport (\n\t\"sort\"\n)\n\n// btoi converts a boolean to an integer, 1 if true, 0 if false.\nfunc btoi(b bool) int {\n\tif b {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\n// bton converts a boolean to a specific integer, n if true, 0 if false.\nfunc bton(b bool, n int) int {\n\tif b {\n\t\treturn n\n\t}\n\treturn 0\n}\n\n// sum returns the sum of all integers in a slice.\nfunc sum(n []int) int {\n\tvar sum int\n\tfor _, i := range n {\n\t\tsum += i\n\t}\n\treturn sum\n}\n\n// median returns the median of a slice of integers.\nfunc median(n []int) int {\n\tsort.Ints(n)\n\n\tif len(n) <= 0 {\n\t\treturn 0\n\t}\n\tif len(n)%2 == 0 {\n\t\th := len(n) / 2            //nolint:mnd\n\t\treturn (n[h-1] + n[h]) / 2 //nolint:mnd\n\t}\n\treturn n[len(n)/2]\n}\n"
  },
  {
    "path": "terminal.go",
    "content": "package lipgloss\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\tuv \"github.com/charmbracelet/ultraviolet\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// queryBackgroundColor queries the terminal for the background color.\n// If the terminal does not support querying the background color, nil is\n// returned.\n//\n// Note: you will need to set the input to raw mode before calling this\n// function.\n//\n//\tstate, _ := term.MakeRaw(in.Fd())\n//\tdefer term.Restore(in.Fd(), state)\n//\n// copied from x/term@v0.1.3.\nfunc queryBackgroundColor(in io.Reader, out io.Writer) (c color.Color, err error) {\n\terr = queryTerminal(in, out, defaultQueryTimeout,\n\t\tfunc(seq string, pa *ansi.Parser) bool {\n\t\t\tswitch {\n\t\t\tcase ansi.HasOscPrefix(seq):\n\t\t\t\tswitch pa.Command() {\n\t\t\t\tcase 11: // OSC 11\n\t\t\t\t\tparts := strings.Split(string(pa.Data()), \";\")\n\t\t\t\t\tif len(parts) != 2 {\n\t\t\t\t\t\tbreak // invalid, but we still need to parse the next sequence\n\t\t\t\t\t}\n\t\t\t\t\tc = ansi.XParseColor(parts[1])\n\t\t\t\t}\n\t\t\tcase ansi.HasCsiPrefix(seq):\n\t\t\t\tswitch pa.Command() {\n\t\t\t\tcase ansi.Command('?', 0, 'c'): // DA1\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t}, ansi.RequestBackgroundColor+ansi.RequestPrimaryDeviceAttributes)\n\treturn\n}\n\nconst defaultQueryTimeout = time.Second * 2\n\n// queryTerminalFilter is a function that filters input events using a type\n// switch. If false is returned, the QueryTerminal function will stop reading\n// input.\ntype queryTerminalFilter func(seq string, pa *ansi.Parser) bool\n\n// queryTerminal queries the terminal for support of various features and\n// returns a list of response events.\n// Most of the time, you will need to set stdin to raw mode before calling this\n// function.\n// Note: This function will block until the terminal responds or the timeout\n// is reached.\n// copied from x/term@v0.1.3.\nfunc queryTerminal(\n\tin io.Reader,\n\tout io.Writer,\n\ttimeout time.Duration,\n\tfilter queryTerminalFilter,\n\tquery string,\n) error {\n\t// We use [uv.NewCancelReader] because it uses a different Windows\n\t// implementation than the on in the [cancelreader] library, which uses\n\t// the Cancel IO API to cancel reads instead of using Overlapped IO.\n\trd, err := uv.NewCancelReader(in)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not create cancel reader: %w\", err)\n\t}\n\n\tdefer rd.Close() //nolint: errcheck\n\n\tdone := make(chan struct{}, 1)\n\tdefer close(done)\n\tgo func() {\n\t\tselect {\n\t\tcase <-done:\n\t\tcase <-time.After(timeout):\n\t\t\trd.Cancel()\n\t\t}\n\t}()\n\n\tif _, err := io.WriteString(out, query); err != nil {\n\t\treturn fmt.Errorf(\"could not write query: %w\", err)\n\t}\n\n\tpa := ansi.GetParser()\n\tdefer ansi.PutParser(pa)\n\n\tvar acc []byte    // Accumulate partial responses before filtering\n\tvar buf [256]byte // 256 bytes should be enough for most responses\n\tvar state byte\n\tfor {\n\t\tn, err := rd.Read(buf[:])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not read from input: %w\", err)\n\t\t}\n\n\t\tp := buf[:]\n\t\tfor n > 0 {\n\t\t\tseq, _, read, newState := ansi.DecodeSequence(p[:n], state, pa)\n\t\t\tacc = append(acc, seq...)\n\n\t\t\tif newState == ansi.NormalState {\n\t\t\t\tif !filter(string(acc), pa) {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tacc = acc[:0]\n\t\t\t}\n\n\t\t\tstate = newState\n\t\t\tn -= read\n\t\t\tp = p[read:]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tree/children.go",
    "content": "package tree\n\nimport \"slices\"\n\n// Children is the interface that wraps the basic methods of a tree model.\ntype Children interface {\n\t// At returns the content item of the given index.\n\tAt(index int) Node\n\n\t// Length returns the number of children in the tree.\n\tLength() int\n}\n\n// NodeChildren is the implementation of the Children interface with tree Nodes.\ntype NodeChildren []Node\n\n// Append appends a child to the list of children.\nfunc (n NodeChildren) Append(child Node) NodeChildren {\n\tn = append(n, child)\n\treturn n\n}\n\n// Remove removes a child from the list at the given index.\nfunc (n NodeChildren) Remove(index int) NodeChildren {\n\tif index < 0 || len(n) < index+1 {\n\t\treturn n\n\t}\n\tn = slices.Delete(n, index, index+1)\n\treturn n\n}\n\n// Length returns the number of children in the list.\nfunc (n NodeChildren) Length() int {\n\treturn len(n)\n}\n\n// At returns the child at the given index.\nfunc (n NodeChildren) At(i int) Node {\n\tif i >= 0 && i < len(n) {\n\t\treturn n[i]\n\t}\n\treturn nil\n}\n\n// NewStringData returns a Data of strings.\nfunc NewStringData(data ...string) Children {\n\tresult := make([]Node, 0, len(data))\n\tfor _, d := range data {\n\t\ts := Leaf{value: d}\n\t\tresult = append(result, &s)\n\t}\n\treturn NodeChildren(result)\n}\n\nvar _ Children = NewFilter(nil)\n\n// Filter applies a filter on some data. You could use this to create a new\n// tree whose values all satisfy the condition provided in the Filter() function.\ntype Filter struct {\n\tdata   Children\n\tfilter func(index int) bool\n}\n\n// NewFilter initializes a new Filter.\nfunc NewFilter(data Children) *Filter {\n\treturn &Filter{data: data}\n}\n\n// At returns the item at the given index.\n// The index is relative to the filtered results.\nfunc (m *Filter) At(index int) Node {\n\tj := 0\n\tfor i := range m.data.Length() {\n\t\tif m.filter(i) {\n\t\t\tif j == index {\n\t\t\t\treturn m.data.At(i)\n\t\t\t}\n\t\t\tj++\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Filter uses a filter function to set a condition that all the data must satisfy to be in the Tree.\nfunc (m *Filter) Filter(f func(index int) bool) *Filter {\n\tm.filter = f\n\treturn m\n}\n\n// Length returns the number of children in the tree.\nfunc (m *Filter) Length() int {\n\tj := 0\n\tfor i := range m.data.Length() {\n\t\tif m.filter(i) {\n\t\t\tj++\n\t\t}\n\t}\n\treturn j\n}\n"
  },
  {
    "path": "tree/enumerator.go",
    "content": "package tree\n\n// Enumerator enumerates a tree. Typically, this is used to draw the branches\n// for the tree nodes and is different for the last child.\n//\n// For example, the default enumerator would be:\n//\n//\tfunc TreeEnumerator(children Children, index int) string {\n//\t\tif children.Length()-1 == index {\n//\t\t\treturn \"└──\"\n//\t\t}\n//\n//\t\treturn \"├──\"\n//\t}\ntype Enumerator func(children Children, index int) string\n\n// DefaultEnumerator enumerates a tree.\n//\n// ├── Foo\n// ├── Bar\n// ├── Baz\n// └── Qux.\nfunc DefaultEnumerator(children Children, index int) string {\n\tif children.Length()-1 == index {\n\t\treturn \"└──\"\n\t}\n\treturn \"├──\"\n}\n\n// RoundedEnumerator enumerates a tree with rounded edges.\n//\n// ├── Foo\n// ├── Bar\n// ├── Baz\n// ╰── Qux.\nfunc RoundedEnumerator(children Children, index int) string {\n\tif children.Length()-1 == index {\n\t\treturn \"╰──\"\n\t}\n\treturn \"├──\"\n}\n\n// Indenter indents the children of a tree.\n//\n// Indenters allow for displaying nested tree items with connecting borders\n// to sibling nodes.\n//\n// For example, the default indenter would be:\n//\n//\tfunc TreeIndenter(children Children, index int) string {\n//\t\tif children.Length()-1 == index {\n//\t\t\treturn \"│  \"\n//\t\t}\n//\n//\t\treturn \"   \"\n//\t}\ntype Indenter func(children Children, index int) string\n\n// DefaultIndenter indents a tree for nested trees and multiline content.\n//\n// ├── Foo\n// ├── Bar\n// │   ├── Qux\n// │   ├── Quux\n// │   │   ├── Foo\n// │   │   └── Bar\n// │   └── Quuux\n// └── Baz.\nfunc DefaultIndenter(children Children, index int) string {\n\tif children.Length()-1 == index {\n\t\treturn \"   \"\n\t}\n\treturn \"│  \"\n}\n"
  },
  {
    "path": "tree/example_test.go",
    "content": "package tree_test\n\nimport (\n\t\"fmt\"\n\n\t\"charm.land/lipgloss/v2/tree\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// Leaf Examples\n\nfunc ExampleLeaf_SetHidden() {\n\ttr := tree.New().\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.Root(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\ttree.Root(\"Quux\").\n\t\t\t\t\t\tChild(\"Hello!\"),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t\t\"Baz\",\n\t\t)\n\n\ttr.Children().At(1).Children().At(2).SetHidden(true)\n\tfmt.Println(tr.String())\n\t// Output:\n\t//\n\t// ├── Foo\n\t// ├── Bar\n\t// │   ├── Qux\n\t// │   └── Quux\n\t// │       └── Hello!\n\t// └── Baz\n}\n\nfunc ExampleNewLeaf() {\n\ttr := tree.New().\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.Root(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\ttree.Root(\"Quux\").\n\t\t\t\t\t\tChild(\n\t\t\t\t\t\t\ttree.NewLeaf(\"This should be hidden\", true),\n\t\t\t\t\t\t\ttree.NewLeaf(\n\t\t\t\t\t\t\t\ttree.Root(\"I am groot\").Child(\"leaves\"), false),\n\t\t\t\t\t\t),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t\t\"Baz\",\n\t\t)\n\n\tfmt.Println(tr.String())\n\t// Output:\n\t// ├── Foo\n\t// ├── Bar\n\t// │   ├── Qux\n\t// │   ├── Quux\n\t// │   │   └── I am groot\n\t// │   │       └── leaves\n\t// │   └── Quuux\n\t// └── Baz\n}\n\nfunc ExampleLeaf_SetValue() {\n\tt := tree.\n\t\tRoot(\"⁜ Makeup\").\n\t\tChild(\n\t\t\t\"Glossier\",\n\t\t\t\"Fenty Beauty\",\n\t\t\ttree.New().Child(\n\t\t\t\t\"Gloss Bomb Universal Lip Luminizer\",\n\t\t\t\t\"Hot Cheeks Velour Blushlighter\",\n\t\t\t),\n\t\t\t\"Nyx\",\n\t\t\t\"Mac\",\n\t\t\t\"Milk\",\n\t\t).\n\t\tEnumerator(tree.RoundedEnumerator)\n\tglossier := t.Children().At(0)\n\tglossier.SetValue(\"Il Makiage\")\n\tfmt.Println(ansi.Strip(t.String()))\n\t// Output:\n\t// ⁜ Makeup\n\t// ├── Il Makiage\n\t// ├── Fenty Beauty\n\t// │   ├── Gloss Bomb Universal Lip Luminizer\n\t// │   ╰── Hot Cheeks Velour Blushlighter\n\t// ├── Nyx\n\t// ├── Mac\n\t// ╰── Milk\n}\n\n// Tree Examples\n\nfunc ExampleTree_Hide() {\n\ttr := tree.New().\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.Root(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\ttree.Root(\"Quux\").\n\t\t\t\t\t\tChild(\"Foo\", \"Bar\").\n\t\t\t\t\t\tHide(true),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t\t\"Baz\",\n\t\t)\n\n\tfmt.Println(tr.String())\n\t// Output:\n\t// ├── Foo\n\t// ├── Bar\n\t// │   ├── Qux\n\t// │   └── Quuux\n\t// └── Baz\n}\n\nfunc ExampleTree_SetHidden() {\n\ttr := tree.New().\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.Root(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\ttree.Root(\"Quux\").\n\t\t\t\t\t\tChild(\"Foo\", \"Bar\"),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t\t\"Baz\",\n\t\t)\n\n\t// Hide a tree after its creation. We'll hide Quux.\n\ttr.Children().At(1).Children().At(1).SetHidden(true)\n\t// Output:\n\t// ├── Foo\n\t// ├── Bar\n\t// │   ├── Qux\n\t// │   └── Quuux\n\t// └── Baz\n\tfmt.Println(tr.String())\n}\n"
  },
  {
    "path": "tree/renderer.go",
    "content": "package tree\n\nimport (\n\t\"strings\"\n\n\t\"charm.land/lipgloss/v2\"\n)\n\n// StyleFunc allows the tree to be styled per item.\ntype StyleFunc func(children Children, i int) lipgloss.Style\n\n// Style is the styling applied to the tree.\ntype Style struct {\n\tenumeratorFunc StyleFunc\n\tindenterFunc   StyleFunc\n\titemFunc       StyleFunc\n\troot           lipgloss.Style\n}\n\n// newRenderer returns the renderer used to render a tree.\nfunc newRenderer() *renderer {\n\treturn &renderer{\n\t\tstyle: Style{\n\t\t\tenumeratorFunc: func(Children, int) lipgloss.Style {\n\t\t\t\treturn lipgloss.NewStyle().PaddingRight(1)\n\t\t\t},\n\t\t\tindenterFunc: func(Children, int) lipgloss.Style {\n\t\t\t\treturn lipgloss.NewStyle().PaddingRight(1)\n\t\t\t},\n\t\t\titemFunc: func(Children, int) lipgloss.Style {\n\t\t\t\treturn lipgloss.NewStyle()\n\t\t\t},\n\t\t},\n\t\tenumerator: DefaultEnumerator,\n\t\tindenter:   DefaultIndenter,\n\t}\n}\n\ntype renderer struct {\n\tstyle      Style\n\tenumerator Enumerator\n\tindenter   Indenter\n\twidth      int\n}\n\n// render is responsible for actually rendering the tree.\nfunc (r *renderer) render(node Node, root bool, prefix string) string {\n\tif node.Hidden() {\n\t\treturn \"\"\n\t}\n\n\tvar maxLen int\n\tchildren := node.Children()\n\tenumerator := r.enumerator\n\tindenter := r.indenter\n\n\tstrs := make([]string, 0, children.Length())\n\n\t// print the root node name if its not empty.\n\tif name := node.Value(); name != \"\" && root {\n\t\tline := r.style.root.Render(name)\n\t\t// If the line is shorter than the desired width, we pad it with spaces.\n\t\tif pad := r.width - lipgloss.Width(line); pad > 0 {\n\t\t\tline = name + r.style.root.Render(strings.Repeat(\" \", pad))\n\t\t}\n\t\tstrs = append(strs, r.style.root.Render(line))\n\t}\n\n\tfor i := range children.Length() {\n\t\tif i < children.Length()-1 {\n\t\t\tif child := children.At(i + 1); child.Hidden() {\n\t\t\t\t// Don't count the last child if its hidden. This renders the\n\t\t\t\t// last visible element with the right prefix\n\t\t\t\t//\n\t\t\t\t// The only type of Children is NodeChildren.\n\t\t\t\tchildren = children.(NodeChildren).Remove(i + 1)\n\t\t\t}\n\t\t}\n\t\tprefix := enumerator(children, i)\n\t\tprefix = r.style.enumeratorFunc(children, i).Render(prefix)\n\t\tmaxLen = max(lipgloss.Width(prefix), maxLen)\n\t}\n\n\tfor i := range children.Length() {\n\t\tchild := children.At(i)\n\t\tif child.Hidden() {\n\t\t\tcontinue\n\t\t}\n\t\tindentStyle := r.style.indenterFunc(children, i)\n\t\tenumStyle := r.style.enumeratorFunc(children, i)\n\n\t\titemStyle := r.style.itemFunc(children, i)\n\n\t\tindent := indentStyle.Render(indenter(children, i))\n\t\tnodePrefix := enumStyle.Render(enumerator(children, i))\n\n\t\t// Preserve the background color of the enumerator when adding the padding\n\t\tenumBgStyle := lipgloss.NewStyle().Background(enumStyle.GetBackground())\n\n\t\t// Add padding to the left of the node to align it with the longest prefix of its siblings\n\t\tif l := maxLen - lipgloss.Width(nodePrefix); l > 0 {\n\t\t\tnodePrefix = enumBgStyle.Render(strings.Repeat(\" \", l)) + nodePrefix\n\t\t}\n\n\t\titem := itemStyle.Render(child.Value())\n\t\tmultineLinePrefix := enumBgStyle.Render(prefix)\n\n\t\t// This dance below is to account for multiline prefixes, e.g. \"|\\n|\".\n\t\t// In that case, we need to make sure that both the parent prefix and\n\t\t// the current node's prefix have the same height.\n\t\tfor lipgloss.Height(item) > lipgloss.Height(nodePrefix) {\n\t\t\tnodePrefix = lipgloss.JoinVertical(\n\t\t\t\tlipgloss.Left,\n\t\t\t\tnodePrefix,\n\t\t\t\tindent,\n\t\t\t)\n\t\t}\n\t\tfor lipgloss.Height(nodePrefix) > lipgloss.Height(multineLinePrefix) {\n\t\t\tmultineLinePrefix = lipgloss.JoinVertical(\n\t\t\t\tlipgloss.Left,\n\t\t\t\tmultineLinePrefix,\n\t\t\t\tprefix,\n\t\t\t)\n\t\t}\n\n\t\tline := lipgloss.JoinHorizontal(\n\t\t\tlipgloss.Top,\n\t\t\tmultineLinePrefix,\n\t\t\tnodePrefix,\n\t\t\titem,\n\t\t)\n\n\t\t// If the line is shorter than the desired width, we pad it with spaces.\n\t\tif pad := r.width - lipgloss.Width(line); pad > 0 {\n\t\t\tline = line + itemStyle.Render(strings.Repeat(\" \", pad))\n\t\t}\n\t\tstrs = append(\n\t\t\tstrs,\n\t\t\tline,\n\t\t)\n\n\t\tif children.Length() > 0 {\n\t\t\t// Here we see if the child has a custom renderer, which means the\n\t\t\t// user set a custom enumerator/indenter/item style, etc.\n\t\t\t// If it has one, we'll use it to render itself.\n\t\t\t// otherwise, we keep using the current renderer.\n\t\t\t// Note that the renderer doesn't inherit its parent's styles.\n\t\t\trenderer := r\n\t\t\tswitch child := child.(type) {\n\t\t\tcase *Tree:\n\t\t\t\tif child.r != nil {\n\t\t\t\t\trenderer = child.r\n\t\t\t\t}\n\t\t\t}\n\t\t\tif s := renderer.render(\n\t\t\t\tchild,\n\t\t\t\tfalse,\n\t\t\t\tprefix+indent,\n\t\t\t); s != \"\" {\n\t\t\t\tstrs = append(strs, s)\n\t\t\t}\n\t\t}\n\t}\n\treturn strings.Join(strs, \"\\n\")\n}\n"
  },
  {
    "path": "tree/testdata/TestAddItemWithAndWithoutRoot/with_root.golden",
    "content": "├── Foo\n├── Bar\n│   └── Baz\n└── Qux"
  },
  {
    "path": "tree/testdata/TestAddItemWithAndWithoutRoot/without_root.golden",
    "content": "├── Foo\n├── Bar\n│   └── Baz\n└── Qux"
  },
  {
    "path": "tree/testdata/TestEmbedListWithinTree.golden",
    "content": "├── 1. A\n│   2. B\n│   3. C\n└── A. 1\n    B. 2\n    C. 3"
  },
  {
    "path": "tree/testdata/TestFilter.golden",
    "content": "Root\n├── Foo\n├── Bar\n└── Baz"
  },
  {
    "path": "tree/testdata/TestMultilinePrefix.golden",
    "content": "   Foo Document \n   The Foo Files\n                \n│  Bar Document \n│  The Bar Files\n                \n   Baz Document \n   The Baz Files\n                "
  },
  {
    "path": "tree/testdata/TestMultilinePrefixInception.golden",
    "content": "    Foo Document \n    The Foo Files\n                 \n│   Bar Document \n│   The Bar Files\n                 \n       Qux Document \n       The Qux Files\n                    \n   │   Quux Document \n   │   The Quux Files\n                     \n       Quuux Document \n       The Quuux Files\n                      \n    Baz Document \n    The Baz Files\n                 "
  },
  {
    "path": "tree/testdata/TestMultilinePrefixSubtree.golden",
    "content": "├── Foo\n├── Bar\n├── Baz\n│      Foo Document \n│      The Foo Files\n│                   \n│   │  Bar Document \n│   │  The Bar Files\n│                   \n│      Baz Document \n│      The Baz Files\n│                   \n└── Qux"
  },
  {
    "path": "tree/testdata/TestRootStyle.golden",
    "content": "Root\n├── Foo\n└── Baz"
  },
  {
    "path": "tree/testdata/TestTree/after.golden",
    "content": "├── Foo\n├── Bar\n│   ├── Qux\n│   ├── Quux\n│   │   ├── Foo\n│   │   ╰── Bar\n│   ╰── Quuux\n╰── Baz"
  },
  {
    "path": "tree/testdata/TestTree/before.golden",
    "content": "├── Foo\n├── Bar\n│   ├── Qux\n│   ├── Quux\n│   │   ├── Foo\n│   │   └── Bar\n│   └── Quuux\n└── Baz"
  },
  {
    "path": "tree/testdata/TestTreeAddTwoSubTreesWithoutName.golden",
    "content": "├── Bar\n├── Foo\n│   ├── Qux\n│   ├── Qux\n│   ├── Qux\n│   ├── Qux\n│   ├── Qux\n│   ├── Quux\n│   ├── Quux\n│   ├── Quux\n│   ├── Quux\n│   └── Quux\n└── Baz"
  },
  {
    "path": "tree/testdata/TestTreeAllHidden.golden",
    "content": ""
  },
  {
    "path": "tree/testdata/TestTreeCustom.golden",
    "content": "\u001b[94m->\u001b[m \u001b[91mFoo\u001b[m\n\u001b[94m->\u001b[m \u001b[91mBar\u001b[m\n\u001b[94m->\u001b[m \u001b[94m->\u001b[m \u001b[91mQux\u001b[m\n\u001b[94m->\u001b[m \u001b[94m->\u001b[m \u001b[91mQuux\u001b[m\n\u001b[94m->\u001b[m \u001b[94m->\u001b[m \u001b[94m->\u001b[m \u001b[91mFoo\u001b[m\n\u001b[94m->\u001b[m \u001b[94m->\u001b[m \u001b[94m->\u001b[m \u001b[91mBar\u001b[m\n\u001b[94m->\u001b[m \u001b[94m->\u001b[m \u001b[91mQuuux\u001b[m\n\u001b[94m->\u001b[m \u001b[91mBaz\u001b[m"
  },
  {
    "path": "tree/testdata/TestTreeHidden.golden",
    "content": "├── Foo\n├── Bar\n│   ├── Qux\n│   └── Quuux\n└── Baz"
  },
  {
    "path": "tree/testdata/TestTreeLastNodeIsSubTree.golden",
    "content": "├── Foo\n└── Bar\n    ├── Qux\n    ├── Quux\n    │   ├── Foo\n    │   └── Bar\n    └── Quuux"
  },
  {
    "path": "tree/testdata/TestTreeMixedEnumeratorSize.golden",
    "content": "The Root Node™\n  I Foo\n II Foo\nIII Foo\n IV Foo\n  V Foo"
  },
  {
    "path": "tree/testdata/TestTreeMultilineNode.golden",
    "content": "Big\nRoot\nNode\n├── Foo\n├── Bar\n│   ├── Line 1\n│   │   Line 2\n│   │   Line 3\n│   │   Line 4\n│   ├── Quux\n│   │   ├── Foo\n│   │   └── Bar\n│   └── Quuux\n└── Baz   \n    Line 2"
  },
  {
    "path": "tree/testdata/TestTreeNil.golden",
    "content": "├── Bar\n│   ├── Qux\n│   ├── Quux\n│   │   └── Bar\n│   └── Quuux\n└── Baz"
  },
  {
    "path": "tree/testdata/TestTreeRoot.golden",
    "content": "Root\n├── Foo\n├── Bar\n│   ├── Qux\n│   └── Quuux\n└── Baz"
  },
  {
    "path": "tree/testdata/TestTreeStartsWithSubtree.golden",
    "content": "├── Bar\n│   ├── Qux\n│   └── Quuux\n└── Baz"
  },
  {
    "path": "tree/testdata/TestTreeStyleAt.golden",
    "content": "Root\n> Foo\n- Baz"
  },
  {
    "path": "tree/testdata/TestTreeStyleNilFuncs.golden",
    "content": "Silly\n├──Willy \n└──Nilly"
  },
  {
    "path": "tree/testdata/TestTreeSubTreeWithCustomEnumerator.golden",
    "content": "The Root Node™\n├── Parent\n│   + ├── * child 1\n│   + └── * child 2\n└── Baz"
  },
  {
    "path": "tree/testdata/TestTreeTable.golden",
    "content": "├── Foo\n├── Bar\n│   ├── Baz\n│   ├── Baz\n│   ├── ┌─────────┬────────┐\n│   │   │ Foo     │ Bar    │\n│   │   ├─────────┼────────┤\n│   │   │ Qux     │ Baz    │\n│   │   │ Qux     │ Baz    │\n│   │   └─────────┴────────┘\n│   └── Baz\n└── Qux"
  },
  {
    "path": "tree/testdata/TestTypes.golden",
    "content": "├── 0\n├── true\n├── Foo\n├── Bar\n├── Qux\n├── Quux\n└── Quuux"
  },
  {
    "path": "tree/tree.go",
    "content": "// Package tree allows you to build trees, as simple or complicated as you need.\n//\n// Define a tree with a root node, and children, set rendering properties (such\n// as style, enumerators, etc...), and print it.\n//\n//\tt := tree.New().\n//\t\tChild(\n//\t\t\t\".git\",\n//\t\t\ttree.Root(\"examples/\").\n//\t\t\t\tChild(\n//\t\t\t\t\ttree.Root(\"list/\").\n//\t\t\t\t\t\tChild(\"main.go\").\n//\t\t\t\t\ttree.Root(\"table/\").\n//\t\t\t\t\t\tChild(\"main.go\").\n//\t\t\t\t).\n//\t\t\ttree.Root(\"list/\").\n//\t\t\t\tChild(\"list.go\", \"list_test.go\").\n//\t\t\ttree.New().\n//\t\t\t\tRoot(\"table/\").\n//\t\t\t\tChild(\"table.go\", \"table_test.go\").\n//\t\t\t\"align.go\",\n//\t\t\t\"align_test.go\",\n//\t\t\t\"join.go\",\n//\t\t\t\"join_test.go\",\n//\t\t)\npackage tree\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"charm.land/lipgloss/v2\"\n)\n\n// Node defines a node in a tree.\ntype Node interface {\n\tfmt.Stringer\n\tValue() string\n\tChildren() Children\n\tHidden() bool\n\tSetHidden(bool)\n\tSetValue(any)\n}\n\n// Leaf is a node without children.\ntype Leaf struct {\n\tvalue  string\n\thidden bool\n}\n\n// NewLeaf returns a new Leaf.\nfunc NewLeaf(value any, hidden bool) *Leaf {\n\ts := Leaf{}\n\ts.SetValue(value)\n\ts.SetHidden(hidden)\n\treturn &s\n}\n\n// Children of a Leaf node are always empty.\nfunc (Leaf) Children() Children {\n\treturn NodeChildren(nil)\n}\n\n// Value returns the value of a Leaf node.\nfunc (s Leaf) Value() string {\n\treturn s.value\n}\n\n// SetValue sets the value of a Leaf node.\nfunc (s *Leaf) SetValue(value any) {\n\tswitch item := value.(type) {\n\tcase Node, fmt.Stringer:\n\t\ts.value = item.(fmt.Stringer).String()\n\tcase string, nil:\n\t\ts.value = item.(string)\n\tdefault:\n\t\ts.value = fmt.Sprintf(\"%v\", item)\n\t}\n}\n\n// Hidden returns whether a Leaf node is hidden.\nfunc (s Leaf) Hidden() bool {\n\treturn s.hidden\n}\n\n// SetHidden hides a Leaf node.\nfunc (s *Leaf) SetHidden(hidden bool) { s.hidden = hidden }\n\n// String returns the string representation of a Leaf node.\n// For leaf nodes, this is the same as Value.\nfunc (s Leaf) String() string {\n\treturn s.Value()\n}\n\n// Tree implements a Node.\ntype Tree struct {\n\tvalue    string\n\thidden   bool\n\toffset   [2]int\n\tchildren Children\n\n\tr     *renderer\n\tronce sync.Once\n}\n\n// Hidden returns whether a Tree node is hidden.\nfunc (t *Tree) Hidden() bool {\n\treturn t.hidden\n}\n\n// Hide sets whether to hide the Tree node. Use this when creating a new\n// hidden Tree.\nfunc (t *Tree) Hide(hide bool) *Tree {\n\tt.hidden = hide\n\treturn t\n}\n\n// SetHidden hides a Tree node.\nfunc (t *Tree) SetHidden(hidden bool) { t.Hide(hidden) }\n\n// Offset sets the Tree children offsets.\nfunc (t *Tree) Offset(start, end int) *Tree {\n\tif start > end {\n\t\t_start := start\n\t\tstart = end\n\t\tend = _start\n\t}\n\n\tif start < 0 {\n\t\tstart = 0\n\t}\n\tif end < 0 || end > t.children.Length() {\n\t\tend = t.children.Length()\n\t}\n\n\tt.offset[0] = start\n\tt.offset[1] = end\n\treturn t\n}\n\n// Value returns the root name of this node.\n// If the root implements fmt.Stringer, it will return the value returned by it.\nfunc (t *Tree) Value() string {\n\treturn t.value\n}\n\n// SetValue sets the value of a Tree node.\nfunc (t *Tree) SetValue(value any) {\n\tt.Root(value)\n}\n\n// String returns the string representation of the Tree node.\nfunc (t *Tree) String() string {\n\treturn t.ensureRenderer().render(t, true, \"\")\n}\n\n// Child adds a child to this Tree.\n//\n// If a Child Tree is passed without a root, it will be parented to its sibling\n// child (auto-nesting).\n//\n//\ttree.Root(\"Foo\").Child(\"Bar\", tree.New().Child(\"Baz\"), \"Qux\")\n//\ttree.Root(\"Foo\").Child(tree.Root(\"Bar\").Child(\"Baz\"), \"Qux\")\n//\n//\t├── Foo\n//\t├── Bar\n//\t│   └── Baz\n//\t└── Qux\nfunc (t *Tree) Child(children ...any) *Tree {\n\tfor _, child := range children {\n\t\tswitch item := child.(type) {\n\t\tcase *Tree:\n\t\t\tnewItem, rm := ensureParent(t.children, item)\n\t\t\tif rm >= 0 {\n\t\t\t\tt.children = t.children.(NodeChildren).Remove(rm)\n\t\t\t}\n\t\t\tt.children = t.children.(NodeChildren).Append(newItem)\n\t\tcase Children:\n\t\t\tfor i := range item.Length() {\n\t\t\t\tt.children = t.children.(NodeChildren).Append(item.At(i))\n\t\t\t}\n\t\tcase Node:\n\t\t\tt.children = t.children.(NodeChildren).Append(item)\n\t\tcase fmt.Stringer:\n\t\t\ts := Leaf{value: item.String()}\n\t\t\tt.children = t.children.(NodeChildren).Append(&s)\n\t\tcase string:\n\t\t\ts := Leaf{value: item}\n\t\t\tt.children = t.children.(NodeChildren).Append(&s)\n\t\tcase []any:\n\t\t\treturn t.Child(item...)\n\t\tcase []string:\n\t\t\tss := make([]any, 0, len(item))\n\t\t\tfor _, s := range item {\n\t\t\t\tss = append(ss, s)\n\t\t\t}\n\t\t\treturn t.Child(ss...)\n\t\tcase nil:\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn t.Child(fmt.Sprintf(\"%v\", item))\n\t\t}\n\t}\n\treturn t\n}\n\nfunc ensureParent(nodes Children, item *Tree) (*Tree, int) {\n\tif item.Value() != \"\" || nodes.Length() == 0 {\n\t\treturn item, -1\n\t}\n\tj := nodes.Length() - 1\n\tparent := nodes.At(j)\n\tswitch parent := parent.(type) {\n\tcase *Tree:\n\t\tfor i := range item.Children().Length() {\n\t\t\tparent.Child(item.children.At(i))\n\t\t}\n\t\treturn parent, j\n\tcase *Leaf:\n\t\titem.value = parent.Value()\n\t\treturn item, j\n\t}\n\treturn item, -1\n}\n\nfunc (t *Tree) ensureRenderer() *renderer {\n\tt.ronce.Do(func() { t.r = newRenderer() })\n\treturn t.r\n}\n\n// EnumeratorStyle sets a static style for all enumerators.\n//\n// Use EnumeratorStyleFunc to conditionally set styles based on the tree node.\nfunc (t *Tree) EnumeratorStyle(style lipgloss.Style) *Tree {\n\tt.ensureRenderer().style.enumeratorFunc = func(Children, int) lipgloss.Style {\n\t\treturn style\n\t}\n\treturn t\n}\n\n// EnumeratorStyleFunc sets the enumeration style function. Use this function\n// for conditional styling.\n//\n//\tt := tree.New().\n//\t\tEnumeratorStyleFunc(func(_ tree.Children, i int) lipgloss.Style {\n//\t\t    if selected == i {\n//\t\t        return lipgloss.NewStyle().Foreground(hightlightColor)\n//\t\t    }\n//\t\t    return lipgloss.NewStyle().Foreground(dimColor)\n//\t\t})\nfunc (t *Tree) EnumeratorStyleFunc(fn StyleFunc) *Tree {\n\tif fn == nil {\n\t\tfn = func(Children, int) lipgloss.Style { return lipgloss.NewStyle() }\n\t}\n\tt.ensureRenderer().style.enumeratorFunc = fn\n\treturn t\n}\n\n// IndenterStyle sets a static style for all indenters.\n//\n// Use IndenterStyleFunc to conditionally set styles based on the tree node.\nfunc (t *Tree) IndenterStyle(style lipgloss.Style) *Tree {\n\tt.ensureRenderer().style.indenterFunc = func(Children, int) lipgloss.Style {\n\t\treturn style\n\t}\n\treturn t\n}\n\n// IndenterStyleFunc sets the indentation style function. Use this function\n// for conditional styling.\n//\n//\tt := tree.New().\n//\t\tIndenterStyleFunc(func(_ tree.Children, i int) lipgloss.Style {\n//\t\t    if selected == i {\n//\t\t        return lipgloss.NewStyle().Foreground(hightlightColor)\n//\t\t    }\n//\t\t    return lipgloss.NewStyle().Foreground(dimColor)\n//\t\t})\nfunc (t *Tree) IndenterStyleFunc(fn StyleFunc) *Tree {\n\tif fn == nil {\n\t\tfn = func(Children, int) lipgloss.Style { return lipgloss.NewStyle() }\n\t}\n\tt.ensureRenderer().style.indenterFunc = fn\n\treturn t\n}\n\n// RootStyle sets a style for the root element.\nfunc (t *Tree) RootStyle(style lipgloss.Style) *Tree {\n\tt.ensureRenderer().style.root = style\n\treturn t\n}\n\n// ItemStyle sets a static style for all items.\n//\n// Use ItemStyleFunc to conditionally set styles based on the tree node.\nfunc (t *Tree) ItemStyle(style lipgloss.Style) *Tree {\n\tt.ensureRenderer().style.itemFunc = func(Children, int) lipgloss.Style { return style }\n\treturn t\n}\n\n// ItemStyleFunc sets the item style function. Use this for conditional styling.\n// For example:\n//\n//\tt := tree.New().\n//\t\tItemStyleFunc(func(_ tree.Data, i int) lipgloss.Style {\n//\t\t\tif selected == i {\n//\t\t\t\treturn lipgloss.NewStyle().Foreground(hightlightColor)\n//\t\t\t}\n//\t\t\treturn lipgloss.NewStyle().Foreground(dimColor)\n//\t\t})\nfunc (t *Tree) ItemStyleFunc(fn StyleFunc) *Tree {\n\tif fn == nil {\n\t\tfn = func(Children, int) lipgloss.Style { return lipgloss.NewStyle() }\n\t}\n\tt.ensureRenderer().style.itemFunc = fn\n\treturn t\n}\n\n// Enumerator sets the enumerator implementation. This can be used to change the\n// way the branches indicators look.  Lipgloss includes predefined enumerators\n// for a classic or rounded tree. For example, you can have a rounded tree:\n//\n//\ttree.New().\n//\t\tEnumerator(RoundedEnumerator)\nfunc (t *Tree) Enumerator(enum Enumerator) *Tree {\n\tt.ensureRenderer().enumerator = enum\n\treturn t\n}\n\n// Indenter sets the indenter implementation. This is used to change the way\n// the tree is indented. The default indentor places a border connecting sibling\n// elements and no border for the last child.\n//\n//\t└── Foo\n//\t    └── Bar\n//\t        └── Baz\n//\t            └── Qux\n//\t                └── Quux\n//\n// You can define your own indenter.\n//\n//\tfunc ArrowIndenter(children tree.Children, index int) string {\n//\t\treturn \"→ \"\n//\t}\n//\n//\t→ Foo\n//\t→ → Bar\n//\t→ → → Baz\n//\t→ → → → Qux\n//\t→ → → → → Quux\nfunc (t *Tree) Indenter(indenter Indenter) *Tree {\n\tt.ensureRenderer().indenter = indenter\n\treturn t\n}\n\n// Width sets the tree width.\n//\n// Items will be padded to account for the entire width of the tree.\nfunc (t *Tree) Width(width int) *Tree {\n\tt.ensureRenderer().width = width\n\treturn t\n}\n\n// Children returns the children of a node.\nfunc (t *Tree) Children() Children {\n\tvar data []Node\n\tfor i := t.offset[0]; i < t.children.Length()-t.offset[1]; i++ {\n\t\tdata = append(data, t.children.At(i))\n\t}\n\treturn NodeChildren(data)\n}\n\n// Root returns a new tree with the root set.\n//\n//\ttree.Root(root)\n//\n// It is a shorthand for:\n//\n//\ttree.New().Root(root)\nfunc Root(root any) *Tree {\n\tt := New()\n\treturn t.Root(root)\n}\n\n// Root sets the root value of this tree.\nfunc (t *Tree) Root(root any) *Tree {\n\t// root is a tree or string\n\tswitch item := root.(type) {\n\tcase *Tree:\n\t\tt.value = item.value\n\t\tt = t.Child(item.children)\n\tcase Node, fmt.Stringer:\n\t\tt.value = item.(fmt.Stringer).String()\n\tcase string, nil:\n\t\tt.value = item.(string)\n\tdefault:\n\t\tt.value = fmt.Sprintf(\"%v\", item)\n\t}\n\treturn t\n}\n\n// New returns a new tree.\nfunc New() *Tree {\n\treturn &Tree{\n\t\tchildren: NodeChildren(nil),\n\t}\n}\n"
  },
  {
    "path": "tree/tree_test.go",
    "content": "package tree_test\n\nimport (\n\t\"testing\"\n\n\t\"charm.land/lipgloss/v2\"\n\t\"charm.land/lipgloss/v2/list\"\n\t\"charm.land/lipgloss/v2/table\"\n\t\"charm.land/lipgloss/v2/tree\"\n\t\"github.com/charmbracelet/x/ansi\"\n\t\"github.com/charmbracelet/x/exp/golden\"\n)\n\nfunc TestTree(t *testing.T) {\n\ttr := tree.New().\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.Root(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\ttree.Root(\"Quux\").\n\t\t\t\t\t\tChild(\n\t\t\t\t\t\t\t\"Foo\",\n\t\t\t\t\t\t\t\"Bar\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t\t\"Baz\",\n\t\t)\n\n\tt.Run(\"before\", func(t *testing.T) {\n\t\tgolden.RequireEqual(t, []byte(tr.String()))\n\t})\n\n\ttr.Enumerator(tree.RoundedEnumerator)\n\n\tt.Run(\"after\", func(t *testing.T) {\n\t\tgolden.RequireEqual(t, []byte(tr.String()))\n\t})\n}\n\nfunc TestTreeHidden(t *testing.T) {\n\ttree := tree.New().\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.Root(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\ttree.Root(\"Quux\").\n\t\t\t\t\t\tChild(\"Foo\", \"Bar\").\n\t\t\t\t\t\tHide(true),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t\t\"Baz\",\n\t\t)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeAllHidden(t *testing.T) {\n\ttree := tree.New().\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.Root(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\ttree.Root(\"Quux\").\n\t\t\t\t\t\tChild(\n\t\t\t\t\t\t\t\"Foo\",\n\t\t\t\t\t\t\t\"Bar\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t\t\"Baz\",\n\t\t).Hide(true)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeRoot(t *testing.T) {\n\ttree := tree.New().\n\t\tRoot(\"Root\").\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.Root(\"Bar\").\n\t\t\t\tChild(\"Qux\", \"Quuux\"),\n\t\t\t\"Baz\",\n\t\t)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeStartsWithSubtree(t *testing.T) {\n\ttree := tree.New().\n\t\tChild(\n\t\t\ttree.New().\n\t\t\t\tRoot(\"Bar\").\n\t\t\t\tChild(\"Qux\", \"Quuux\"),\n\t\t\t\"Baz\",\n\t\t)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeAddTwoSubTreesWithoutName(t *testing.T) {\n\ttree := tree.New().\n\t\tChild(\n\t\t\t\"Bar\",\n\t\t\t\"Foo\",\n\t\t\ttree.New().\n\t\t\t\tChild(\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\t\"Qux\",\n\t\t\t\t),\n\t\t\ttree.New().\n\t\t\t\tChild(\n\t\t\t\t\t\"Quux\",\n\t\t\t\t\t\"Quux\",\n\t\t\t\t\t\"Quux\",\n\t\t\t\t\t\"Quux\",\n\t\t\t\t\t\"Quux\",\n\t\t\t\t),\n\t\t\t\"Baz\",\n\t\t)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeLastNodeIsSubTree(t *testing.T) {\n\ttree := tree.New().\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.Root(\"Bar\").\n\t\t\t\tChild(\"Qux\",\n\t\t\t\t\ttree.Root(\"Quux\").Child(\"Foo\", \"Bar\"),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeNil(t *testing.T) {\n\ttree := tree.New().\n\t\tChild(\n\t\t\tnil,\n\t\t\ttree.Root(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\ttree.Root(\"Quux\").\n\t\t\t\t\t\tChild(\"Bar\"),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t\t\"Baz\",\n\t\t)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeCustom(t *testing.T) {\n\ttree := tree.New().\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.New().\n\t\t\t\tRoot(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Qux\",\n\t\t\t\t\ttree.New().\n\t\t\t\t\t\tRoot(\"Quux\").\n\t\t\t\t\t\tChild(\"Foo\",\n\t\t\t\t\t\t\t\"Bar\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t\t\"Baz\",\n\t\t).\n\t\tItemStyle(lipgloss.NewStyle().\n\t\t\tForeground(lipgloss.Color(\"9\"))).\n\t\tEnumeratorStyle(lipgloss.NewStyle().\n\t\t\tForeground(lipgloss.Color(\"12\")).\n\t\t\tPaddingRight(1)).\n\t\tIndenterStyle(lipgloss.NewStyle().\n\t\t\tForeground(lipgloss.Color(\"12\")).\n\t\t\tPaddingRight(1)).\n\t\tEnumerator(func(tree.Children, int) string {\n\t\t\treturn \"->\"\n\t\t}).\n\t\tIndenter(func(tree.Children, int) string {\n\t\t\treturn \"->\"\n\t\t})\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeMultilineNode(t *testing.T) {\n\ttree := tree.New().\n\t\tRoot(\"Big\\nRoot\\nNode\").\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.New().\n\t\t\t\tRoot(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Line 1\\nLine 2\\nLine 3\\nLine 4\",\n\t\t\t\t\ttree.New().\n\t\t\t\t\t\tRoot(\"Quux\").\n\t\t\t\t\t\tChild(\n\t\t\t\t\t\t\t\"Foo\",\n\t\t\t\t\t\t\t\"Bar\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\"Quuux\",\n\t\t\t\t),\n\t\t\t\"Baz\\nLine 2\",\n\t\t)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeSubTreeWithCustomEnumerator(t *testing.T) {\n\ttree := tree.New().\n\t\tRoot(\"The Root Node™\").\n\t\tChild(\n\t\t\ttree.New().\n\t\t\t\tRoot(\"Parent\").\n\t\t\t\tChild(\"child 1\", \"child 2\").\n\t\t\t\tItemStyleFunc(func(tree.Children, int) lipgloss.Style {\n\t\t\t\t\treturn lipgloss.NewStyle().\n\t\t\t\t\t\tSetString(\"*\")\n\t\t\t\t}).\n\t\t\t\tEnumeratorStyleFunc(func(_ tree.Children, i int) lipgloss.Style {\n\t\t\t\t\treturn lipgloss.NewStyle().\n\t\t\t\t\t\tSetString(\"+\").\n\t\t\t\t\t\tPaddingRight(1)\n\t\t\t\t}),\n\t\t\t\"Baz\",\n\t\t)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeMixedEnumeratorSize(t *testing.T) {\n\ttree := tree.New().\n\t\tRoot(\"The Root Node™\").\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\t\"Foo\",\n\t\t\t\"Foo\",\n\t\t\t\"Foo\",\n\t\t\t\"Foo\",\n\t\t).Enumerator(func(_ tree.Children, i int) string {\n\t\tromans := map[int]string{\n\t\t\t1: \"I\",\n\t\t\t2: \"II\",\n\t\t\t3: \"III\",\n\t\t\t4: \"IV\",\n\t\t\t5: \"V\",\n\t\t\t6: \"VI\",\n\t\t}\n\t\treturn romans[i+1]\n\t})\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeStyleNilFuncs(t *testing.T) {\n\ttree := tree.New().\n\t\tRoot(\"Silly\").\n\t\tChild(\"Willy \", \"Nilly\").\n\t\tItemStyleFunc(nil).\n\t\tEnumeratorStyleFunc(nil)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTreeStyleAt(t *testing.T) {\n\ttree := tree.New().\n\t\tRoot(\"Root\").\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\t\"Baz\",\n\t\t).Enumerator(func(data tree.Children, i int) string {\n\t\tif data.At(i).Value() == \"Foo\" {\n\t\t\treturn \">\"\n\t\t}\n\t\treturn \"-\"\n\t})\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestRootStyle(t *testing.T) {\n\ttree := tree.New().\n\t\tRoot(\"Root\").\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\t\"Baz\",\n\t\t).\n\t\tRootStyle(lipgloss.NewStyle().Background(lipgloss.Color(\"#5A56E0\"))).\n\t\tItemStyle(lipgloss.NewStyle().Background(lipgloss.Color(\"#04B575\")))\n\n\tgolden.RequireEqual(t, []byte(ansi.Strip(tree.String())))\n}\n\nfunc TestAt(t *testing.T) {\n\tdata := tree.NewStringData(\"Foo\", \"Bar\")\n\n\tif s := data.At(0).String(); s != \"Foo\" {\n\t\tt.Errorf(\"want 'Foo', got '%s'\", s)\n\t}\n\n\tif n := data.At(10); n != nil {\n\t\tt.Errorf(\"want nil, got '%s'\", n)\n\t}\n\n\tif n := data.At(-1); n != nil {\n\t\tt.Errorf(\"want nil, got '%s'\", n)\n\t}\n}\n\nfunc TestFilter(t *testing.T) {\n\tdata := tree.NewFilter(tree.NewStringData(\n\t\t\"Foo\",\n\t\t\"Bar\",\n\t\t\"Baz\",\n\t\t\"Nope\",\n\t)).\n\t\tFilter(func(index int) bool {\n\t\t\treturn index != 3\n\t\t})\n\n\ttree := tree.New().\n\t\tRoot(\"Root\").\n\t\tChild(data)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n\tif got := data.At(1); got.Value() != \"Bar\" {\n\t\tt.Errorf(\"want Bar, got %v\", got)\n\t}\n\tif got := data.At(10); got != nil {\n\t\tt.Errorf(\"want nil, got %v\", got)\n\t}\n}\n\nfunc TestNodeDataRemoveOutOfBounds(t *testing.T) {\n\tdata := tree.NewStringData(\"a\")\n\tif l := data.Length(); l != 1 {\n\t\tt.Errorf(\"want data to contain 1 items, has %d\", l)\n\t}\n}\n\nfunc TestTreeTable(t *testing.T) {\n\ttree := tree.New().\n\t\tChild(\n\t\t\t\"Foo\",\n\t\t\ttree.New().\n\t\t\t\tRoot(\"Bar\").\n\t\t\t\tChild(\n\t\t\t\t\t\"Baz\",\n\t\t\t\t\t\"Baz\",\n\t\t\t\t\ttable.New().\n\t\t\t\t\t\tWidth(20).\n\t\t\t\t\t\tStyleFunc(func(row, col int) lipgloss.Style {\n\t\t\t\t\t\t\treturn lipgloss.NewStyle().Padding(0, 1)\n\t\t\t\t\t\t}).\n\t\t\t\t\t\tHeaders(\"Foo\", \"Bar\").\n\t\t\t\t\t\tRow(\"Qux\", \"Baz\").\n\t\t\t\t\t\tRow(\"Qux\", \"Baz\"),\n\t\t\t\t\t\"Baz\",\n\t\t\t\t),\n\t\t\t\"Qux\",\n\t\t)\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestAddItemWithAndWithoutRoot(t *testing.T) {\n\tt.Run(\"with root\", func(t *testing.T) {\n\t\tt1 := tree.New().\n\t\t\tChild(\n\t\t\t\t\"Foo\",\n\t\t\t\t\"Bar\",\n\t\t\t\ttree.New().\n\t\t\t\t\tChild(\"Baz\"),\n\t\t\t\t\"Qux\",\n\t\t\t)\n\t\tgolden.RequireEqual(t, []byte(t1.String()))\n\t})\n\n\tt.Run(\"without root\", func(t *testing.T) {\n\t\tt2 := tree.New().\n\t\t\tChild(\n\t\t\t\t\"Foo\",\n\t\t\t\ttree.New().\n\t\t\t\t\tRoot(\"Bar\").\n\t\t\t\t\tChild(\"Baz\"),\n\t\t\t\t\"Qux\",\n\t\t\t)\n\t\tgolden.RequireEqual(t, []byte(t2.String()))\n\t})\n}\n\nfunc TestEmbedListWithinTree(t *testing.T) {\n\tt1 := tree.New().\n\t\tChild(list.New(\"A\", \"B\", \"C\").\n\t\t\tEnumerator(list.Arabic)).\n\t\tChild(list.New(\"1\", \"2\", \"3\").\n\t\t\tEnumerator(list.Alphabet))\n\n\tgolden.RequireEqual(t, []byte(t1.String()))\n}\n\nfunc TestMultilinePrefix(t *testing.T) {\n\tpaddingsStyle := lipgloss.NewStyle().PaddingLeft(1).PaddingBottom(1)\n\ttree := tree.New().\n\t\tEnumerator(func(_ tree.Children, i int) string {\n\t\t\tif i == 1 {\n\t\t\t\treturn \"│\\n│\"\n\t\t\t}\n\t\t\treturn \" \"\n\t\t}).\n\t\tIndenter(func(_ tree.Children, i int) string {\n\t\t\treturn \" \"\n\t\t}).\n\t\tItemStyle(paddingsStyle).\n\t\tChild(\"Foo Document\\nThe Foo Files\").\n\t\tChild(\"Bar Document\\nThe Bar Files\").\n\t\tChild(\"Baz Document\\nThe Baz Files\")\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestMultilinePrefixSubtree(t *testing.T) {\n\tpaddingsStyle := lipgloss.NewStyle().\n\t\tPadding(0, 0, 1, 1)\n\n\ttree := tree.New().\n\t\tChild(\"Foo\").\n\t\tChild(\"Bar\").\n\t\tChild(\n\t\t\ttree.New().\n\t\t\t\tRoot(\"Baz\").\n\t\t\t\tEnumerator(func(_ tree.Children, i int) string {\n\t\t\t\t\tif i == 1 {\n\t\t\t\t\t\treturn \"│\\n│\"\n\t\t\t\t\t}\n\t\t\t\t\treturn \" \"\n\t\t\t\t}).\n\t\t\t\tIndenter(func(tree.Children, int) string {\n\t\t\t\t\treturn \" \"\n\t\t\t\t}).\n\t\t\t\tItemStyle(paddingsStyle).\n\t\t\t\tChild(\"Foo Document\\nThe Foo Files\").\n\t\t\t\tChild(\"Bar Document\\nThe Bar Files\").\n\t\t\t\tChild(\"Baz Document\\nThe Baz Files\"),\n\t\t).\n\t\tChild(\"Qux\")\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestMultilinePrefixInception(t *testing.T) {\n\tglowEnum := func(_ tree.Children, i int) string {\n\t\tif i == 1 {\n\t\t\treturn \"│\\n│\"\n\t\t}\n\t\treturn \" \"\n\t}\n\tglowIndenter := func(_ tree.Children, i int) string {\n\t\treturn \"  \"\n\t}\n\tpaddingsStyle := lipgloss.NewStyle().PaddingLeft(1).PaddingBottom(1)\n\ttree := tree.New().\n\t\tEnumerator(glowEnum).\n\t\tIndenter(glowIndenter).\n\t\tItemStyle(paddingsStyle).\n\t\tChild(\"Foo Document\\nThe Foo Files\").\n\t\tChild(\"Bar Document\\nThe Bar Files\").\n\t\tChild(\n\t\t\ttree.New().\n\t\t\t\tEnumerator(glowEnum).\n\t\t\t\tIndenter(glowIndenter).\n\t\t\t\tItemStyle(paddingsStyle).\n\t\t\t\tChild(\"Qux Document\\nThe Qux Files\").\n\t\t\t\tChild(\"Quux Document\\nThe Quux Files\").\n\t\t\t\tChild(\"Quuux Document\\nThe Quuux Files\"),\n\t\t).\n\t\tChild(\"Baz Document\\nThe Baz Files\")\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n\nfunc TestTypes(t *testing.T) {\n\ttree := tree.New().\n\t\tChild(0).\n\t\tChild(true).\n\t\tChild([]any{\"Foo\", \"Bar\"}).\n\t\tChild([]string{\"Qux\", \"Quux\", \"Quuux\"})\n\n\tgolden.RequireEqual(t, []byte(tree.String()))\n}\n"
  },
  {
    "path": "unset.go",
    "content": "package lipgloss\n\n// unset unsets a property from a style.\nfunc (s *Style) unset(key propKey) {\n\ts.props = s.props.unset(key)\n}\n\n// UnsetBold removes the bold style rule, if set.\nfunc (s Style) UnsetBold() Style {\n\ts.unset(boldKey)\n\treturn s\n}\n\n// UnsetItalic removes the italic style rule, if set.\nfunc (s Style) UnsetItalic() Style {\n\ts.unset(italicKey)\n\treturn s\n}\n\n// UnsetUnderline removes the underline style rule, if set.\nfunc (s Style) UnsetUnderline() Style {\n\treturn s.Underline(false)\n}\n\n// UnsetStrikethrough removes the strikethrough style rule, if set.\nfunc (s Style) UnsetStrikethrough() Style {\n\ts.unset(strikethroughKey)\n\treturn s\n}\n\n// UnsetReverse removes the reverse style rule, if set.\nfunc (s Style) UnsetReverse() Style {\n\ts.unset(reverseKey)\n\treturn s\n}\n\n// UnsetBlink removes the blink style rule, if set.\nfunc (s Style) UnsetBlink() Style {\n\ts.unset(blinkKey)\n\treturn s\n}\n\n// UnsetFaint removes the faint style rule, if set.\nfunc (s Style) UnsetFaint() Style {\n\ts.unset(faintKey)\n\treturn s\n}\n\n// UnsetForeground removes the foreground style rule, if set.\nfunc (s Style) UnsetForeground() Style {\n\ts.unset(foregroundKey)\n\treturn s\n}\n\n// UnsetBackground removes the background style rule, if set.\nfunc (s Style) UnsetBackground() Style {\n\ts.unset(backgroundKey)\n\treturn s\n}\n\n// UnsetWidth removes the width style rule, if set.\nfunc (s Style) UnsetWidth() Style {\n\ts.unset(widthKey)\n\treturn s\n}\n\n// UnsetHeight removes the height style rule, if set.\nfunc (s Style) UnsetHeight() Style {\n\ts.unset(heightKey)\n\treturn s\n}\n\n// UnsetAlign removes the horizontal and vertical text alignment style rule, if set.\nfunc (s Style) UnsetAlign() Style {\n\ts.unset(alignHorizontalKey)\n\ts.unset(alignVerticalKey)\n\treturn s\n}\n\n// UnsetAlignHorizontal removes the horizontal text alignment style rule, if set.\nfunc (s Style) UnsetAlignHorizontal() Style {\n\ts.unset(alignHorizontalKey)\n\treturn s\n}\n\n// UnsetAlignVertical removes the vertical text alignment style rule, if set.\nfunc (s Style) UnsetAlignVertical() Style {\n\ts.unset(alignVerticalKey)\n\treturn s\n}\n\n// UnsetPadding removes all padding style rules.\nfunc (s Style) UnsetPadding() Style {\n\ts.unset(paddingLeftKey)\n\ts.unset(paddingRightKey)\n\ts.unset(paddingTopKey)\n\ts.unset(paddingBottomKey)\n\ts.unset(paddingCharKey)\n\treturn s\n}\n\n// UnsetPaddingChar removes the padding character style rule, if set.\nfunc (s Style) UnsetPaddingChar() Style {\n\ts.unset(paddingCharKey)\n\treturn s\n}\n\n// UnsetPaddingLeft removes the left padding style rule, if set.\nfunc (s Style) UnsetPaddingLeft() Style {\n\ts.unset(paddingLeftKey)\n\treturn s\n}\n\n// UnsetPaddingRight removes the right padding style rule, if set.\nfunc (s Style) UnsetPaddingRight() Style {\n\ts.unset(paddingRightKey)\n\treturn s\n}\n\n// UnsetPaddingTop removes the top padding style rule, if set.\nfunc (s Style) UnsetPaddingTop() Style {\n\ts.unset(paddingTopKey)\n\treturn s\n}\n\n// UnsetPaddingBottom removes the bottom padding style rule, if set.\nfunc (s Style) UnsetPaddingBottom() Style {\n\ts.unset(paddingBottomKey)\n\treturn s\n}\n\n// UnsetColorWhitespace removes the rule for coloring padding, if set.\nfunc (s Style) UnsetColorWhitespace() Style {\n\ts.unset(colorWhitespaceKey)\n\treturn s\n}\n\n// UnsetMargins removes all margin style rules.\nfunc (s Style) UnsetMargins() Style {\n\ts.unset(marginLeftKey)\n\ts.unset(marginRightKey)\n\ts.unset(marginTopKey)\n\ts.unset(marginBottomKey)\n\treturn s\n}\n\n// UnsetMarginLeft removes the left margin style rule, if set.\nfunc (s Style) UnsetMarginLeft() Style {\n\ts.unset(marginLeftKey)\n\treturn s\n}\n\n// UnsetMarginRight removes the right margin style rule, if set.\nfunc (s Style) UnsetMarginRight() Style {\n\ts.unset(marginRightKey)\n\treturn s\n}\n\n// UnsetMarginTop removes the top margin style rule, if set.\nfunc (s Style) UnsetMarginTop() Style {\n\ts.unset(marginTopKey)\n\treturn s\n}\n\n// UnsetMarginBottom removes the bottom margin style rule, if set.\nfunc (s Style) UnsetMarginBottom() Style {\n\ts.unset(marginBottomKey)\n\treturn s\n}\n\n// UnsetMarginBackground removes the margin's background color. Note that the\n// margin's background color can be set from the background color of another\n// style during inheritance.\nfunc (s Style) UnsetMarginBackground() Style {\n\ts.unset(marginBackgroundKey)\n\treturn s\n}\n\n// UnsetBorderStyle removes the border style rule, if set.\nfunc (s Style) UnsetBorderStyle() Style {\n\ts.unset(borderStyleKey)\n\treturn s\n}\n\n// UnsetBorderTop removes the border top style rule, if set.\nfunc (s Style) UnsetBorderTop() Style {\n\ts.unset(borderTopKey)\n\treturn s\n}\n\n// UnsetBorderRight removes the border right style rule, if set.\nfunc (s Style) UnsetBorderRight() Style {\n\ts.unset(borderRightKey)\n\treturn s\n}\n\n// UnsetBorderBottom removes the border bottom style rule, if set.\nfunc (s Style) UnsetBorderBottom() Style {\n\ts.unset(borderBottomKey)\n\treturn s\n}\n\n// UnsetBorderLeft removes the border left style rule, if set.\nfunc (s Style) UnsetBorderLeft() Style {\n\ts.unset(borderLeftKey)\n\treturn s\n}\n\n// UnsetBorderForeground removes all border foreground color styles, if set.\nfunc (s Style) UnsetBorderForeground() Style {\n\ts.unset(borderTopForegroundKey)\n\ts.unset(borderRightForegroundKey)\n\ts.unset(borderBottomForegroundKey)\n\ts.unset(borderLeftForegroundKey)\n\treturn s\n}\n\n// UnsetBorderTopForeground removes the top border foreground color rule,\n// if set.\nfunc (s Style) UnsetBorderTopForeground() Style {\n\ts.unset(borderTopForegroundKey)\n\treturn s\n}\n\n// UnsetBorderRightForeground removes the right border foreground color rule,\n// if set.\nfunc (s Style) UnsetBorderRightForeground() Style {\n\ts.unset(borderRightForegroundKey)\n\treturn s\n}\n\n// UnsetBorderBottomForeground removes the bottom border foreground color\n// rule, if set.\nfunc (s Style) UnsetBorderBottomForeground() Style {\n\ts.unset(borderBottomForegroundKey)\n\treturn s\n}\n\n// UnsetBorderLeftForeground removes the left border foreground color rule,\n// if set.\nfunc (s Style) UnsetBorderLeftForeground() Style {\n\ts.unset(borderLeftForegroundKey)\n\treturn s\n}\n\n// UnsetBorderForegroundBlend removes the border blend foreground color rules,\n// if set.\nfunc (s Style) UnsetBorderForegroundBlend() Style {\n\ts.unset(borderForegroundBlendKey)\n\treturn s\n}\n\n// UnsetBorderForegroundBlendOffset removes the border blend offset style rule,\n// if set.\nfunc (s Style) UnsetBorderForegroundBlendOffset() Style {\n\ts.unset(borderForegroundBlendOffsetKey)\n\treturn s\n}\n\n// UnsetBorderBackground removes all border background color styles, if\n// set.\nfunc (s Style) UnsetBorderBackground() Style {\n\ts.unset(borderTopBackgroundKey)\n\ts.unset(borderRightBackgroundKey)\n\ts.unset(borderBottomBackgroundKey)\n\ts.unset(borderLeftBackgroundKey)\n\treturn s\n}\n\n// UnsetBorderTopBackgroundColor removes the top border background color rule,\n// if set.\n//\n// Deprecated: This function simply calls Style.UnsetBorderTopBackground.\nfunc (s Style) UnsetBorderTopBackgroundColor() Style {\n\treturn s.UnsetBorderTopBackground()\n}\n\n// UnsetBorderTopBackground removes the top border background color rule,\n// if set.\nfunc (s Style) UnsetBorderTopBackground() Style {\n\ts.unset(borderTopBackgroundKey)\n\treturn s\n}\n\n// UnsetBorderRightBackground removes the right border background color\n// rule, if set.\nfunc (s Style) UnsetBorderRightBackground() Style {\n\ts.unset(borderRightBackgroundKey)\n\treturn s\n}\n\n// UnsetBorderBottomBackground removes the bottom border background color\n// rule, if set.\nfunc (s Style) UnsetBorderBottomBackground() Style {\n\ts.unset(borderBottomBackgroundKey)\n\treturn s\n}\n\n// UnsetBorderLeftBackground removes the left border color rule, if set.\nfunc (s Style) UnsetBorderLeftBackground() Style {\n\ts.unset(borderLeftBackgroundKey)\n\treturn s\n}\n\n// UnsetInline removes the inline style rule, if set.\nfunc (s Style) UnsetInline() Style {\n\ts.unset(inlineKey)\n\treturn s\n}\n\n// UnsetMaxWidth removes the max width style rule, if set.\nfunc (s Style) UnsetMaxWidth() Style {\n\ts.unset(maxWidthKey)\n\treturn s\n}\n\n// UnsetMaxHeight removes the max height style rule, if set.\nfunc (s Style) UnsetMaxHeight() Style {\n\ts.unset(maxHeightKey)\n\treturn s\n}\n\n// UnsetTabWidth removes the tab width style rule, if set.\nfunc (s Style) UnsetTabWidth() Style {\n\ts.unset(tabWidthKey)\n\treturn s\n}\n\n// UnsetUnderlineSpaces removes the value set by UnderlineSpaces.\nfunc (s Style) UnsetUnderlineSpaces() Style {\n\ts.unset(underlineSpacesKey)\n\treturn s\n}\n\n// UnsetStrikethroughSpaces removes the value set by StrikethroughSpaces.\nfunc (s Style) UnsetStrikethroughSpaces() Style {\n\ts.unset(strikethroughSpacesKey)\n\treturn s\n}\n\n// UnsetTransform removes the value set by Transform.\nfunc (s Style) UnsetTransform() Style {\n\ts.unset(transformKey)\n\treturn s\n}\n\n// UnsetHyperlink removes the value set by Hyperlink.\nfunc (s Style) UnsetHyperlink() Style {\n\ts.unset(linkKey)\n\ts.unset(linkParamsKey)\n\ts.link, s.linkParams = \"\", \"\" // save memory\n\treturn s\n}\n\n// UnsetString sets the underlying string value to the empty string.\nfunc (s Style) UnsetString() Style {\n\ts.value = \"\"\n\treturn s\n}\n"
  },
  {
    "path": "whitespace.go",
    "content": "package lipgloss\n\nimport (\n\t\"strings\"\n\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// whitespace is a whitespace renderer.\ntype whitespace struct {\n\tchars string\n\tstyle Style\n}\n\n// newWhitespace creates a new whitespace renderer.\nfunc newWhitespace(opts ...WhitespaceOption) *whitespace {\n\tw := &whitespace{}\n\tfor _, opt := range opts {\n\t\topt(w)\n\t}\n\treturn w\n}\n\n// Render whitespaces.\nfunc (w whitespace) render(width int) string {\n\tif w.chars == \"\" {\n\t\tw.chars = \" \"\n\t}\n\n\tr := []rune(w.chars)\n\tj := 0\n\tb := strings.Builder{}\n\n\t// Cycle through runes and print them into the whitespace.\n\tfor i := 0; i < width; {\n\t\tb.WriteRune(r[j])\n\t\t// Measure the width of the rune we just wrote, ensuring we always\n\t\t// make progress to avoid infinite loops with zero-width characters\n\t\t// like tabs.\n\t\truneWidth := ansi.StringWidth(string(r[j]))\n\t\tif runeWidth < 1 {\n\t\t\truneWidth = 1\n\t\t}\n\t\ti += runeWidth\n\t\tj++\n\t\tif j >= len(r) {\n\t\t\tj = 0\n\t\t}\n\t}\n\n\t// Fill any extra gaps white spaces. This might be necessary if any runes\n\t// are more than one cell wide, which could leave a one-rune gap.\n\tshort := width - ansi.StringWidth(b.String())\n\tif short > 0 {\n\t\tb.WriteString(strings.Repeat(\" \", short))\n\t}\n\n\treturn w.style.Render(b.String())\n}\n\n// WhitespaceOption sets a styling rule for rendering whitespace.\ntype WhitespaceOption func(*whitespace)\n\n// WithWhitespaceStyle sets the style for the whitespace.\nfunc WithWhitespaceStyle(s Style) WhitespaceOption {\n\treturn func(w *whitespace) {\n\t\tw.style = s\n\t}\n}\n\n// WithWhitespaceChars sets the characters to be rendered in the whitespace.\nfunc WithWhitespaceChars(s string) WhitespaceOption {\n\treturn func(w *whitespace) {\n\t\tw.chars = s\n\t}\n}\n"
  },
  {
    "path": "whitespace_test.go",
    "content": "package lipgloss\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestWhitespaceRenderWithTab(t *testing.T) {\n\t// This test verifies that rendering whitespace with tab characters\n\t// doesn't cause an infinite loop (issue #108)\n\tdone := make(chan bool, 1)\n\n\tgo func() {\n\t\tws := newWhitespace(WithWhitespaceChars(\"\\t\"))\n\t\t_ = ws.render(10)\n\t\tdone <- true\n\t}()\n\n\tselect {\n\tcase <-done:\n\t\t// Success - render completed\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"whitespace.render() with tab character caused infinite loop\")\n\t}\n}\n\nfunc TestWhitespaceRenderWithZeroWidthChar(t *testing.T) {\n\t// Test with zero-width joiner (another zero-width character)\n\tdone := make(chan bool, 1)\n\n\tgo func() {\n\t\tws := newWhitespace(WithWhitespaceChars(\"\\u200d\")) // zero-width joiner\n\t\t_ = ws.render(5)\n\t\tdone <- true\n\t}()\n\n\tselect {\n\tcase <-done:\n\t\t// Success\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"whitespace.render() with zero-width character caused infinite loop\")\n\t}\n}\n\nfunc TestWhitespaceRenderNormal(t *testing.T) {\n\t// Verify normal behavior still works\n\tws := newWhitespace(WithWhitespaceChars(\"*\"))\n\tresult := ws.render(5)\n\tif len(result) != 5 {\n\t\tt.Errorf(\"expected 5 characters, got %d\", len(result))\n\t}\n}\n"
  },
  {
    "path": "wrap.go",
    "content": "package lipgloss\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\n\tuv \"github.com/charmbracelet/ultraviolet\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// Wrap wraps the given string to the given width, preserving ANSI styles and links.\nfunc Wrap(s string, width int, breakpoints string) string {\n\tvar buf bytes.Buffer\n\ts = ansi.Wrap(s, width, breakpoints)\n\tw := NewWrapWriter(&buf)\n\tdefer w.Close() //nolint:errcheck\n\t_, _ = io.WriteString(w, s)\n\treturn buf.String()\n}\n\n// WrapWriter is a writer that writes to a buffer and keeps track of the\n// current pen style and link state for the purpose of wrapping with newlines.\n//\n// When it encounters a newline, it resets the style and link, writes the\n// newline, and then reapplies the style and link to the next line.\ntype WrapWriter struct {\n\tw     io.Writer\n\tp     *ansi.Parser\n\tstyle uv.Style\n\tlink  uv.Link\n}\n\n// NewWrapWriter returns a new [WrapWriter].\nfunc NewWrapWriter(w io.Writer) *WrapWriter {\n\tpw := &WrapWriter{w: w}\n\tpw.p = ansi.GetParser()\n\thandleCsi := func(cmd ansi.Cmd, params ansi.Params) {\n\t\tif cmd == 'm' {\n\t\t\tuv.ReadStyle(params, &pw.style)\n\t\t}\n\t}\n\thandleOsc := func(cmd int, data []byte) {\n\t\tif cmd == 8 {\n\t\t\tuv.ReadLink(data, &pw.link)\n\t\t}\n\t}\n\tpw.p.SetHandler(ansi.Handler{\n\t\tHandleCsi: handleCsi,\n\t\tHandleOsc: handleOsc,\n\t})\n\treturn pw\n}\n\n// Style returns the current pen style.\nfunc (w *WrapWriter) Style() uv.Style {\n\treturn w.style\n}\n\n// Link returns the current pen link.\nfunc (w *WrapWriter) Link() uv.Link {\n\treturn w.link\n}\n\n// Write writes to the buffer.\nfunc (w *WrapWriter) Write(p []byte) (int, error) {\n\tfor i := range p {\n\t\tb := p[i]\n\t\tw.p.Advance(b)\n\t\tif b == '\\n' {\n\t\t\tif !w.style.IsZero() {\n\t\t\t\t_, _ = w.w.Write([]byte(ansi.ResetStyle))\n\t\t\t}\n\t\t\tif !w.link.IsZero() {\n\t\t\t\t_, _ = w.w.Write([]byte(ansi.ResetHyperlink()))\n\t\t\t}\n\t\t}\n\n\t\t_, _ = w.w.Write([]byte{b})\n\t\tif b == '\\n' {\n\t\t\tif !w.link.IsZero() {\n\t\t\t\t_, _ = w.w.Write([]byte(ansi.SetHyperlink(w.link.URL, w.link.Params)))\n\t\t\t}\n\t\t\tif !w.style.IsZero() {\n\t\t\t\t_, _ = w.w.Write([]byte(w.style.String()))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn len(p), nil\n}\n\n// Close closes the writer, resets the style and link if necessary, and releases\n// its parser. Calling it is performance critical, but forgetting it does not\n// cause safety issues or leaks.\nfunc (w *WrapWriter) Close() error {\n\tif !w.style.IsZero() {\n\t\t_, _ = w.w.Write([]byte(ansi.ResetStyle))\n\t}\n\tif !w.link.IsZero() {\n\t\t_, _ = w.w.Write([]byte(ansi.ResetHyperlink()))\n\t}\n\tif w.p != nil {\n\t\tansi.PutParser(w.p)\n\t\tw.p = nil\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "writer.go",
    "content": "package lipgloss\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/charmbracelet/colorprofile\"\n)\n\n// Writer is the default writer that prints to stdout, automatically\n// downsampling colors when necessary.\nvar Writer = colorprofile.NewWriter(os.Stdout, os.Environ())\n\n// Println to stdout, automatically downsampling colors when necessary, ending\n// with a trailing newline.\n//\n// Example:\n//\n//\tstr := NewStyle().\n//\t    Foreground(lipgloss.Color(\"#6a00ff\")).\n//\t    Render(\"breakfast\")\n//\n//\tPrintln(\"Time for a\", str, \"sandwich!\")\nfunc Println(v ...any) (int, error) {\n\treturn fmt.Fprintln(Writer, v...) //nolint:wrapcheck\n}\n\n// Printf prints formatted text to stdout, automatically downsampling colors\n// when necessary.\n//\n// Example:\n//\n//\tstr := NewStyle().\n//\t  Foreground(lipgloss.Color(\"#6a00ff\")).\n//\t  Render(\"knuckle\")\n//\n//\tPrintf(\"Time for a %s sandwich!\\n\", str)\nfunc Printf(format string, v ...any) (int, error) {\n\treturn fmt.Fprintf(Writer, format, v...) //nolint:wrapcheck\n}\n\n// Print to stdout, automatically downsampling colors when necessary.\n//\n// Example:\n//\n//\tstr := NewStyle().\n//\t    Foreground(lipgloss.Color(\"#6a00ff\")).\n//\t    Render(\"Who wants marmalade?\\n\")\n//\n//\tPrint(str)\nfunc Print(v ...any) (int, error) {\n\treturn fmt.Fprint(Writer, v...) //nolint:wrapcheck\n}\n\n// Fprint pritnts to the given writer, automatically downsampling colors when\n// necessary.\n//\n// Example:\n//\n//\tstr := NewStyle().\n//\t    Foreground(lipgloss.Color(\"#6a00ff\")).\n//\t    Render(\"guzzle\")\n//\n//\tFprint(os.Stderr, \"I %s horchata pretty much all the time.\\n\", str)\nfunc Fprint(w io.Writer, v ...any) (int, error) {\n\treturn fmt.Fprint(colorprofile.NewWriter(w, os.Environ()), v...) //nolint:wrapcheck\n}\n\n// Fprintln prints to the given writer, automatically downsampling colors when\n// necessary, and ending with a trailing newline.\n//\n// Example:\n//\n//\tstr := NewStyle().\n//\t    Foreground(lipgloss.Color(\"#6a00ff\")).\n//\t    Render(\"Sandwich time!\")\n//\n//\tFprintln(os.Stderr, str)\nfunc Fprintln(w io.Writer, v ...any) (int, error) {\n\treturn fmt.Fprintln(colorprofile.NewWriter(w, os.Environ()), v...) //nolint:wrapcheck\n}\n\n// Fprintf prints text to a writer, against the given format, automatically\n// downsampling colors when necessary.\n//\n// Example:\n//\n//\tstr := NewStyle().\n//\t    Foreground(lipgloss.Color(\"#6a00ff\")).\n//\t    Render(\"artichokes\")\n//\n//\tFprintf(os.Stderr, \"I really love %s!\\n\", food)\nfunc Fprintf(w io.Writer, format string, v ...any) (int, error) {\n\treturn fmt.Fprintf(colorprofile.NewWriter(w, os.Environ()), format, v...) //nolint:wrapcheck\n}\n\n// Sprint returns a string for stdout, automatically downsampling colors when\n// necessary.\n//\n// Example:\n//\n//\tstr := NewStyle().\n//\t\tFaint(true).\n//\t    Foreground(lipgloss.Color(\"#6a00ff\")).\n//\t    Render(\"I love to eat\")\n//\n//\tstr = Sprint(str)\nfunc Sprint(v ...any) string {\n\tvar buf bytes.Buffer\n\tw := colorprofile.Writer{\n\t\tForward: &buf,\n\t\tProfile: Writer.Profile,\n\t}\n\tfmt.Fprint(&w, v...) //nolint:errcheck\n\treturn buf.String()\n}\n\n// Sprintln returns a string for stdout, automatically downsampling colors when\n// necessary, and ending with a trailing newline.\n//\n// Example:\n//\n//\tstr := NewStyle().\n//\t\tBold(true).\n//\t\tForeground(lipgloss.Color(\"#6a00ff\")).\n//\t\tRender(\"Yummy!\")\n//\n//\tstr = Sprintln(str)\nfunc Sprintln(v ...any) string {\n\tvar buf bytes.Buffer\n\tw := colorprofile.Writer{\n\t\tForward: &buf,\n\t\tProfile: Writer.Profile,\n\t}\n\tfmt.Fprintln(&w, v...) //nolint:errcheck\n\treturn buf.String()\n}\n\n// Sprintf returns a formatted string for stdout, automatically downsampling\n// colors when necessary.\n//\n// Example:\n//\n//\tstr := NewStyle().\n//\t\tBold(true).\n//\t\tForeground(lipgloss.Color(\"#fccaee\")).\n//\t\tRender(\"Cantaloupe\")\n//\n//\tstr = Sprintf(\"I really love %s!\", str)\nfunc Sprintf(format string, v ...any) string {\n\tvar buf bytes.Buffer\n\tw := colorprofile.Writer{\n\t\tForward: &buf,\n\t\tProfile: Writer.Profile,\n\t}\n\tfmt.Fprintf(&w, format, v...) //nolint:errcheck\n\treturn buf.String()\n}\n"
  }
]