[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: rivo\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to tview\n\nFirst of all, thank you for taking the time to contribute.\n\nThe following provides you with some guidance on how to contribute to this project. Mainly, it is meant to save us all some time so please read it, it's not long.\n\nPlease note that this document is work in progress so I might add to it in the future.\n\n## Issues\n\n- Please include enough information so everybody understands your request.\n- Screenshots or code that illustrates your point always helps.\n- It's fine to ask for help. But you should have checked out the [documentation](https://godoc.org/github.com/rivo/tview) first in any case.\n- If you request a new feature, state your motivation and share a use case that you faced where you needed that new feature. It should be something that others will also need.\n\n## Pull Requests\n\nIn my limited time I can spend on this project, I will always go through issues first before looking at pull requests. It takes a _lot_ of time to look at code that you submitted and I may not have that time. So be prepared to have your pull requests lying around for a long time.\n\nTherefore, if you have a feature request, open an issue first before sending me a pull request, and allow for some discussion. It may save you from writing code that will get rejected. If your case is strong, there is a good chance that I will add the feature for you.\n\nI'm very picky about the code that goes into this repo. So if you violate any of the following guidelines, there is a good chance I won't merge your pull request.\n\n- There must be a strong case for your additions/changes, such as:\n  - Bug fixes\n  - Features that are needed (see \"Issues\" above; state your motivation)\n  - Improvements in stability or performance (if readability does not suffer)\n- Your code must follow the structure of the existing code. Don't just patch something on. Try to understand how `tview` is currently designed and follow that design. Your code needs to be consistent with existing code.\n- If you're adding code that increases the work required to maintain the project, you must be willing to take responsibility for that extra work. I will ask you to maintain your part of the code in the long run.\n- Function/type/variable/constant names must be as descriptive as they are right now. Follow the conventions of the package.\n- All functions/types/variables/constants, even private ones, must have comments in good English. These comments must be elaborate enough so that new users of the package understand them and can follow them. Provide examples if you have to. Start all sentences upper-case, as is common in English, and end them with a period. Comments in their own lines must not exceed the 80 character border. Break over if necessary.\n- A new function should be located close to related functions in the file. For example, `GetColor()` should come after (or before) `SetColor()`.\n- Your changes must not decrease the project's [Go Report](https://goreportcard.com/report/github.com/rivo/tview) rating.\n- No breaking changes unless there is absolutely no other way.\n- If an issue accompanies your pull request, reference it in the PR's comments, e.g. \"Fixes #123\", so it is closed automatically when the PR is closed.\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2018 Oliver Kuederle\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": "# Rich Interactive Widgets for Terminal UIs\n\n[![PkgGoDev](https://pkg.go.dev/badge/github.com/rivo/tview)](https://pkg.go.dev/github.com/rivo/tview)\n[![Go Report](https://img.shields.io/badge/go%20report-A%2B-brightgreen.svg)](https://goreportcard.com/report/github.com/rivo/tview)\n\nThis Go package provides commonly used components for terminal based user interfaces.\n\n![Screenshot](tview.gif)\n\nAmong these components are:\n\n- __Input forms__ (including __text input__, __selections__, __checkboxes__, and __buttons__)\n- Navigable multi-color __text views__\n- Editable multi-line __text areas__\n- Sophisticated navigable __table views__\n- Flexible __tree views__\n- Selectable __lists__\n- __Images__\n- __Grid__, __Flexbox__ and __page layouts__\n- Modal __message windows__\n- An __application__ wrapper\n\nThey come with lots of customization options and can be easily extended to fit your needs.\n\n## Usage\n\nTo add this package to your project:\n\n```bash\ngo get github.com/rivo/tview@master\n```\n\n## Hello World\n\nThis basic example creates a box titled \"Hello, World!\" and displays it in your terminal:\n\n```go\npackage main\n\nimport (\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tbox := tview.NewBox().SetBorder(true).SetTitle(\"Hello, world!\")\n\tif err := tview.NewApplication().SetRoot(box, true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n```\n\nCheck out the [GitHub Wiki](https://github.com/rivo/tview/wiki) for more examples along with screenshots. Or try the examples in the \"demos\" subdirectory.\n\nFor a presentation highlighting this package, compile and run the program found in the \"demos/presentation\" subdirectory.\n\n## Projects using `tview`\n\n- [K9s - Kubernetes CLI](https://github.com/derailed/k9s)\n- [IRCCloud Terminal Client](https://github.com/termoose/irccloud)\n- [Window manager for `tview`](https://github.com/epiclabs-io/winman)\n- [CLI bookmark manager](https://github.com/Endi1/drawer)\n- [A caving database interface written in Go](https://github.com/IdlePhysicist/cave-logger)\n- [Interactive file browse and exec any command.](https://github.com/bannzai/itree)\n- [A complete TUI for LDAP](https://github.com/Macmod/godap)\n- [A simple CRM](https://github.com/broadcastle/crm)\n- [Terminal UI for todist](https://github.com/cyberdummy/todoista)\n- [Graphical kubectl wrapper](https://github.com/dcaiafa/kpick)\n- [Decred Decentralized Exchange ](https://github.com/decred/dcrdex)\n- [A CLI file browser for Raspberry PI](https://github.com/destinmoulton/pixi)\n- [A tool to manage projects.](https://github.com/divramod/dp)\n- [A simple app for BMI monitoring](https://github.com/erleene/go-bmi)\n- [Stream TIDAL from command line](https://github.com/godsic/vibe)\n- [Secure solution for fully decentralized password management](https://github.com/guillaumemichel/passtor/)\n- [A growing collection of convenient little tools to work with systemd services](https://github.com/muesli/service-tools/)\n- [A terminal based browser for Redis written in Go](https://github.com/nitishm/redis-terminal)\n- [First project for the Computer Networks course.](https://github.com/pablogadhi/XMPPClient)\n- [Test your typing speed in the terminal!](https://github.com/shilangyu/typer-go)\n- [TUI Client for Docker](https://github.com/skanehira/docui)\n- [SSH client using certificates signed by HashiCorp Vault](https://github.com/stephane-martin/vssh)\n- [VMware vCenter Text UI](https://github.com/thebsdbox/vctui)\n- [Bookmarks on terminal](https://github.com/tryffel/bookmarker)\n- [A UDP testing utility](https://github.com/vaelen/udp-tester)\n- [A simple Kanban board for your terminal](https://github.com/witchard/toukan)\n- [The personal information dashboard for your terminal. ](https://github.com/wtfutil/wtf)\n- [MySQL database to Golang struct](https://github.com/xxjwxc/gormt)\n- [Discord, TUI and SIXEL.](https://gitlab.com/diamondburned/6cord)\n- [A CLI Audio Player](https://www.github.com/dhulihan/grump)\n- [GLab, a GitLab CLI tool](https://gitlab.com/profclems/glab)\n- [Browse your AWS ECS Clusters in the Terminal](https://github.com/swartzrock/ecsview)\n- [The CLI Task Manager for Geeks](https://github.com/ajaxray/geek-life)\n- [Fast disk usage analyzer written in Go](https://github.com/dundee/gdu)\n- [Multiplayer Chess On Terminal](https://github.com/qnkhuat/gochess)\n- [Scriptable TUI music player](https://github.com/issadarkthing/gomu)\n- [MangaDesk : TUI Client for downloading manga to your computer](https://github.com/darylhjd/mangadesk)\n- [Go How Much? a Crypto coin price tracking from terminal](https://github.com/ledongthuc/gohowmuch)\n- [dbui: Universal CLI for Database Connections](https://github.com/KenanBek/dbui)\n- [ssmbrowse: Simple and elegant cli AWS SSM parameter browser](https://github.com/bnaydenov/ssmbrowse)\n- [gobit: binance intelligence terminal](https://github.com/infl00p/gobit)\n- [viddy: A modern watch command](https://github.com/sachaos/viddy)\n- [s3surfer: CLI tool for browsing S3 bucket and download objects interactively](https://github.com/hirose31/s3surfer)\n- [libgen-tui: A terminal UI for downloading books from Library Genesis](https://github.com/audstanley/libgen-tui)\n- [kubectl-lazy: kubectl plugin to easy to view pod](https://github.com/togettoyou/kubectl-lazy)\n- [podman-tui: podman user interface](https://github.com/containers/podman-tui)\n- [tvxwidgets: tview extra widgets](https://github.com/navidys/tvxwidgets)\n- [Domino card game on terminal](https://github.com/gusti-andika/card-domino.git)\n- [goaround: Query stackoverflow API and get results on terminal](https://github.com/glendsoza/goaround)\n- [resto: a CLI app can send pretty HTTP & API requests with TUI](https://github.com/abdfnx/resto)\n- [twad: a WAD launcher for the terminal](https://github.com/zmnpl/twad)\n- [pacseek: A TUI for searching and installing Arch Linux packages](https://github.com/moson-mo/pacseek)\n- [7GUIs demo](https://github.com/letientai299/7guis/tree/master/tui)\n- [tuihub: A utility hub/dashboard for personal use](https://github.com/ashis0013/tuihub)\n- [l'oggo: A terminal app for structured log streaming (GCP stack driver, k8s, local streaming)](https://github.com/aurc/loggo)\n- [reminder: Terminal based interactive app for organising tasks with minimal efforts.](https://github.com/goyalmunish/reminder)\n- [tufw: A terminal UI for ufw.](https://github.com/peltho/tufw)\n- [gh: the GitHub CLI](https://github.com/cli/cli)\n- [piptui: Terminal UI to manage pip packages](https://github.com/glendsoza/piptui/)\n- [cross-clipboard: A cross-platform clipboard sharing](https://github.com/ntsd/cross-clipboard)\n- [tui-deck: nextcloud deck frontend](https://github.com/mebitek/tui-deck)\n- [ktop: A top-like tool for your Kubernetes clusters](https://github.com/vladimirvivien/ktop)\n- [blimp: UI for weather, network latency, application status, & more](https://github.com/merlinfuchs/blimp)\n- [Curly - A simple TUI leveraging curl to test endpoints](https://github.com/migcaraballo/curly)\n- [amtui: Alertmanager TUI](https://github.com/pehlicd/amtui)\n- [A TUI CLI manager](https://github.com/costa86/cli-manager)\n- [PrivateBTC](https://github.com/adrianbrad/privatebtc)\n- [play: A TUI playground to experiment with your favorite programs, such as grep, sed, awk, jq and yq](https://github.com/paololazzari/play)\n- [gorest: Enjoy making HTTP requests in your terminal, just like you do in Insomnia.](https://github.com/NathanFirmo/gorest)\n- [Terminal-based application to listen Radio Stations around the world!](https://github.com/vergonha/garden-tui)\n- [ntui: A TUI to manage Hashicorp Nomad clusters](https://github.com/SHAPPY0/ntui)\n- [lazysql: A cross-platform TUI database management tool written in Go](https://github.com/jorgerojas26/lazysql)\n- [redis-tui: A Redis Text-based UI client in CLI](https://github.com/mylxsw/redis-tui)\n- [fen: File manager](https://github.com/kivattt/fen)\n- [sqltui: A terminal UI to operate sql and nosql databases](https://github.com/LinPr/sqltui)\n- [DBee: Simple database browser](https://github.com/murat-cileli/dbee)\n- [oddshub: A TUI for sports betting odds](https://github.com/dos-2/oddshub)\n- [envolve: Terminal based interactive app for manage enviroment variables](https://github.com/erdemkosk/envolve)\n- [zfs-file-history: Terminal UI for inspecting and restoring file history on ZFS snapshots](https://github.com/markusressel/zfs-file-history)\n- [fan2go-tui: Terminal UI for fan2go](https://github.com/markusressel/fan2go-tui)\n- [NatsDash: Terminal UI for NATS Jetstream](https://nats-dash-gui.returnzero.win/)\n- [tuissh: A terminal UI to manage ssh connections](https://github.com/linuxexam/tuissh)\n- [chiko: Ultimate Beauty TUI gRPC Client](https://github.com/felangga/chiko)\n- [kmip-explorer: Browse & manage your KMIP objects from the terminal](https://github.com/phsym/kmip-explorer)\n- [stui: Slurm TUI for managing HPC clusters](https://github.com/antvirf/stui)\n- [nerdlog: Fast, remote-first, multi-host log viewer with timeline histogram](https://github.com/dimonomid/nerdlog)\n- [lxz: A powerful DevOps graphical command-line interface tool](https://github.com/liangzhaoliang95/lxz)\n- [vaulty: Terminal UI for Azure Keyvault](https://github.com/declan-whiting/vaulty)\n- [pago: Command-line password manager](https://github.com/dbohdan/pago)\n\n## Documentation\n\nRefer to https://pkg.go.dev/github.com/rivo/tview for the package's documentation. Also check out the [Wiki](https://github.com/rivo/tview/wiki).\n\n## Dependencies\n\nThis package is based on [github.com/gdamore/tcell](https://github.com/gdamore/tcell) (and its dependencies) as well as on [github.com/rivo/uniseg](https://github.com/rivo/uniseg).\n\n## Sponsor this Project\n\n[Become a Sponsor on GitHub](https://github.com/sponsors/rivo?metadata_source=tview_readme) to further this project!\n\n## Backwards-Compatibility\n\nI try really hard to keep this project backwards compatible. Your software should not break when you upgrade `tview`. But this also means that some of its shortcomings that were present in the initial versions will remain. Having said that, backwards compatibility may still break when:\n\n- a new version of an imported package (most likely [`tcell`](https://github.com/gdamore/tcell)) changes in such a way that forces me to make changes in `tview` as well,\n- I fix something that I consider a bug, rather than a feature, something that does not work as originally intended,\n- I make changes to \"internal\" interfaces such as [`Primitive`](https://pkg.go.dev/github.com/rivo/tview#Primitive). You shouldn't need these interfaces unless you're writing your own primitives for `tview`. (Yes, I realize these are public interfaces. This has advantages as well as disadvantages. For the time being, it is what it is.)\n\n## Your Feedback\n\nAdd your issue here on GitHub. Feel free to get in touch if you have any questions.\n\n## Code of Conduct\n\nWe follow Golang's Code of Conduct which you can find [here](https://golang.org/conduct).\n"
  },
  {
    "path": "ansi.go",
    "content": "package tview\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// The states of the ANSI escape code parser.\nconst (\n\tansiText = iota\n\tansiEscape\n\tansiSubstring\n\tansiControlSequence\n)\n\n// ansi is a io.Writer which translates ANSI escape codes into tview color\n// tags.\ntype ansi struct {\n\tio.Writer\n\n\t// Reusable buffers.\n\tbuffer                        *bytes.Buffer // The entire output text of one Write().\n\tcsiParameter, csiIntermediate *bytes.Buffer // Partial CSI strings.\n\tattributes                    string        // The buffer's current text attributes (a tview attribute string).\n\n\t// The current state of the parser. One of the ansi constants.\n\tstate int\n}\n\n// ANSIWriter returns an io.Writer which translates any ANSI escape codes\n// written to it into tview style tags. Other escape codes don't have an effect\n// and are simply removed. The translated text is written to the provided\n// writer.\nfunc ANSIWriter(writer io.Writer) io.Writer {\n\treturn &ansi{\n\t\tWriter:          writer,\n\t\tbuffer:          new(bytes.Buffer),\n\t\tcsiParameter:    new(bytes.Buffer),\n\t\tcsiIntermediate: new(bytes.Buffer),\n\t\tstate:           ansiText,\n\t}\n}\n\n// Write parses the given text as a string of runes, translates ANSI escape\n// codes to style tags and writes them to the output writer.\nfunc (a *ansi) Write(text []byte) (int, error) {\n\tdefer func() {\n\t\ta.buffer.Reset()\n\t}()\n\n\tfor _, r := range string(text) {\n\t\tswitch a.state {\n\n\t\t// We just entered an escape sequence.\n\t\tcase ansiEscape:\n\t\t\tswitch r {\n\t\t\tcase '[': // Control Sequence Introducer.\n\t\t\t\ta.csiParameter.Reset()\n\t\t\t\ta.csiIntermediate.Reset()\n\t\t\t\ta.state = ansiControlSequence\n\t\t\tcase 'c': // Reset.\n\t\t\t\tfmt.Fprint(a.buffer, \"[-:-:-]\")\n\t\t\t\ta.state = ansiText\n\t\t\tcase 'P', ']', 'X', '^', '_': // Substrings and commands.\n\t\t\t\ta.state = ansiSubstring\n\t\t\tdefault: // Ignore.\n\t\t\t\ta.state = ansiText\n\t\t\t}\n\n\t\t// CSI Sequences.\n\t\tcase ansiControlSequence:\n\t\t\tswitch {\n\t\t\tcase r >= 0x30 && r <= 0x3f: // Parameter bytes.\n\t\t\t\tif _, err := a.csiParameter.WriteRune(r); err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\tcase r >= 0x20 && r <= 0x2f: // Intermediate bytes.\n\t\t\t\tif _, err := a.csiIntermediate.WriteRune(r); err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\tcase r >= 0x40 && r <= 0x7e: // Final byte.\n\t\t\t\tswitch r {\n\t\t\t\tcase 'E': // Next line.\n\t\t\t\t\tcount, _ := strconv.Atoi(a.csiParameter.String())\n\t\t\t\t\tif count == 0 {\n\t\t\t\t\t\tcount = 1\n\t\t\t\t\t}\n\t\t\t\t\tfmt.Fprint(a.buffer, strings.Repeat(\"\\n\", count))\n\t\t\t\tcase 'm': // Select Graphic Rendition.\n\t\t\t\t\tvar background, foreground string\n\t\t\t\t\tparams := a.csiParameter.String()\n\t\t\t\t\tfields := strings.Split(params, \";\")\n\t\t\t\t\tif len(params) == 0 || fields[0] == \"\" || fields[0] == \"0\" {\n\t\t\t\t\t\t// Reset.\n\t\t\t\t\t\tforeground = \"-\"\n\t\t\t\t\t\tbackground = \"-\"\n\t\t\t\t\t\ta.attributes = \"-\"\n\t\t\t\t\t}\n\t\t\t\t\tlookupColor := func(colorNumber int) string {\n\t\t\t\t\t\tif colorNumber < 0 || colorNumber > 15 {\n\t\t\t\t\t\t\treturn \"black\"\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn []string{\n\t\t\t\t\t\t\t\"black\",\n\t\t\t\t\t\t\t\"maroon\",\n\t\t\t\t\t\t\t\"green\",\n\t\t\t\t\t\t\t\"olive\",\n\t\t\t\t\t\t\t\"navy\",\n\t\t\t\t\t\t\t\"purple\",\n\t\t\t\t\t\t\t\"teal\",\n\t\t\t\t\t\t\t\"silver\",\n\t\t\t\t\t\t\t\"gray\",\n\t\t\t\t\t\t\t\"red\",\n\t\t\t\t\t\t\t\"lime\",\n\t\t\t\t\t\t\t\"yellow\",\n\t\t\t\t\t\t\t\"blue\",\n\t\t\t\t\t\t\t\"fuchsia\",\n\t\t\t\t\t\t\t\"aqua\",\n\t\t\t\t\t\t\t\"white\",\n\t\t\t\t\t\t}[colorNumber]\n\t\t\t\t\t}\n\t\t\t\tFieldLoop:\n\t\t\t\t\tfor index, field := range fields {\n\t\t\t\t\t\tswitch field {\n\t\t\t\t\t\tcase \"1\", \"01\":\n\t\t\t\t\t\t\tif !strings.ContainsRune(a.attributes, 'b') {\n\t\t\t\t\t\t\t\ta.attributes += \"b\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"2\", \"02\":\n\t\t\t\t\t\t\tif !strings.ContainsRune(a.attributes, 'd') {\n\t\t\t\t\t\t\t\ta.attributes += \"d\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"3\", \"03\":\n\t\t\t\t\t\t\tif !strings.ContainsRune(a.attributes, 'i') {\n\t\t\t\t\t\t\t\ta.attributes += \"i\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"4\", \"04\":\n\t\t\t\t\t\t\tif !strings.ContainsRune(a.attributes, 'u') {\n\t\t\t\t\t\t\t\ta.attributes += \"u\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"5\", \"05\":\n\t\t\t\t\t\t\tif !strings.ContainsRune(a.attributes, 'l') {\n\t\t\t\t\t\t\t\ta.attributes += \"l\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"7\", \"07\":\n\t\t\t\t\t\t\tif !strings.ContainsRune(a.attributes, 'r') {\n\t\t\t\t\t\t\t\ta.attributes += \"r\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"9\", \"09\":\n\t\t\t\t\t\t\tif !strings.ContainsRune(a.attributes, 's') {\n\t\t\t\t\t\t\t\ta.attributes += \"s\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"22\":\n\t\t\t\t\t\t\tif i := strings.IndexRune(a.attributes, 'b'); i >= 0 {\n\t\t\t\t\t\t\t\ta.attributes = a.attributes[:i] + a.attributes[i+1:]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif i := strings.IndexRune(a.attributes, 'd'); i >= 0 {\n\t\t\t\t\t\t\t\ta.attributes = a.attributes[:i] + a.attributes[i+1:]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"23\":\n\t\t\t\t\t\t\tif i := strings.IndexRune(a.attributes, 'i'); i >= 0 {\n\t\t\t\t\t\t\t\ta.attributes = a.attributes[:i] + a.attributes[i+1:]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"24\":\n\t\t\t\t\t\t\tif i := strings.IndexRune(a.attributes, 'u'); i >= 0 {\n\t\t\t\t\t\t\t\ta.attributes = a.attributes[:i] + a.attributes[i+1:]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"25\":\n\t\t\t\t\t\t\tif i := strings.IndexRune(a.attributes, 'l'); i >= 0 {\n\t\t\t\t\t\t\t\ta.attributes = a.attributes[:i] + a.attributes[i+1:]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"27\":\n\t\t\t\t\t\t\tif i := strings.IndexRune(a.attributes, 'r'); i >= 0 {\n\t\t\t\t\t\t\t\ta.attributes = a.attributes[:i] + a.attributes[i+1:]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"29\":\n\t\t\t\t\t\t\tif i := strings.IndexRune(a.attributes, 's'); i >= 0 {\n\t\t\t\t\t\t\t\ta.attributes = a.attributes[:i] + a.attributes[i+1:]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase \"30\", \"31\", \"32\", \"33\", \"34\", \"35\", \"36\", \"37\":\n\t\t\t\t\t\t\tcolorNumber, _ := strconv.Atoi(field)\n\t\t\t\t\t\t\tforeground = lookupColor(colorNumber - 30)\n\t\t\t\t\t\tcase \"39\":\n\t\t\t\t\t\t\tforeground = \"-\"\n\t\t\t\t\t\tcase \"40\", \"41\", \"42\", \"43\", \"44\", \"45\", \"46\", \"47\":\n\t\t\t\t\t\t\tcolorNumber, _ := strconv.Atoi(field)\n\t\t\t\t\t\t\tbackground = lookupColor(colorNumber - 40)\n\t\t\t\t\t\tcase \"49\":\n\t\t\t\t\t\t\tbackground = \"-\"\n\t\t\t\t\t\tcase \"90\", \"91\", \"92\", \"93\", \"94\", \"95\", \"96\", \"97\":\n\t\t\t\t\t\t\tcolorNumber, _ := strconv.Atoi(field)\n\t\t\t\t\t\t\tforeground = lookupColor(colorNumber - 82)\n\t\t\t\t\t\tcase \"100\", \"101\", \"102\", \"103\", \"104\", \"105\", \"106\", \"107\":\n\t\t\t\t\t\t\tcolorNumber, _ := strconv.Atoi(field)\n\t\t\t\t\t\t\tbackground = lookupColor(colorNumber - 92)\n\t\t\t\t\t\tcase \"38\", \"48\":\n\t\t\t\t\t\t\tvar color string\n\t\t\t\t\t\t\tif len(fields) > index+1 {\n\t\t\t\t\t\t\t\tif fields[index+1] == \"5\" && len(fields) > index+2 { // 8-bit colors.\n\t\t\t\t\t\t\t\t\tcolorNumber, _ := strconv.Atoi(fields[index+2])\n\t\t\t\t\t\t\t\t\tif colorNumber <= 15 {\n\t\t\t\t\t\t\t\t\t\tcolor = lookupColor(colorNumber)\n\t\t\t\t\t\t\t\t\t} else if colorNumber <= 231 {\n\t\t\t\t\t\t\t\t\t\tred := (colorNumber - 16) / 36\n\t\t\t\t\t\t\t\t\t\tgreen := ((colorNumber - 16) / 6) % 6\n\t\t\t\t\t\t\t\t\t\tblue := (colorNumber - 16) % 6\n\t\t\t\t\t\t\t\t\t\tcolor = fmt.Sprintf(\"#%02x%02x%02x\", 255*red/5, 255*green/5, 255*blue/5)\n\t\t\t\t\t\t\t\t\t} else if colorNumber <= 255 {\n\t\t\t\t\t\t\t\t\t\tgrey := 255 * (colorNumber - 232) / 23\n\t\t\t\t\t\t\t\t\t\tcolor = fmt.Sprintf(\"#%02x%02x%02x\", grey, grey, grey)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if fields[index+1] == \"2\" && len(fields) > index+4 { // 24-bit colors.\n\t\t\t\t\t\t\t\t\tred, _ := strconv.Atoi(fields[index+2])\n\t\t\t\t\t\t\t\t\tgreen, _ := strconv.Atoi(fields[index+3])\n\t\t\t\t\t\t\t\t\tblue, _ := strconv.Atoi(fields[index+4])\n\t\t\t\t\t\t\t\t\tcolor = fmt.Sprintf(\"#%02x%02x%02x\", red, green, blue)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif len(color) > 0 {\n\t\t\t\t\t\t\t\tif field == \"38\" {\n\t\t\t\t\t\t\t\t\tforeground = color\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tbackground = color\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak FieldLoop\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tvar colon string\n\t\t\t\t\tif len(a.attributes) > 1 && a.attributes[0] == '-' {\n\t\t\t\t\t\ta.attributes = a.attributes[1:]\n\t\t\t\t\t}\n\t\t\t\t\tif len(a.attributes) > 0 {\n\t\t\t\t\t\tcolon = \":\"\n\t\t\t\t\t}\n\t\t\t\t\tif len(foreground) > 0 || len(background) > 0 || len(a.attributes) > 0 {\n\t\t\t\t\t\tfmt.Fprintf(a.buffer, \"[%s:%s%s%s]\", foreground, background, colon, a.attributes)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ta.state = ansiText\n\t\t\tdefault: // Undefined byte.\n\t\t\t\ta.state = ansiText // Abort CSI.\n\t\t\t}\n\n\t\t\t// We just entered a substring/command sequence.\n\t\tcase ansiSubstring:\n\t\t\tif r == 27 { // Most likely the end of the substring.\n\t\t\t\ta.state = ansiEscape\n\t\t\t} // Ignore all other characters.\n\n\t\t\t// \"ansiText\" and all others.\n\t\tdefault:\n\t\t\tif r == 27 {\n\t\t\t\t// This is the start of an escape sequence.\n\t\t\t\ta.state = ansiEscape\n\t\t\t} else {\n\t\t\t\t// Just a regular rune. Send to buffer.\n\t\t\t\tif _, err := a.buffer.WriteRune(r); err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Write buffer to target writer.\n\tn, err := a.buffer.WriteTo(a.Writer)\n\tif err != nil {\n\t\treturn int(n), err\n\t}\n\treturn len(text), nil\n}\n\n// TranslateANSI replaces ANSI escape sequences found in the provided string\n// with tview's style tags and returns the resulting string.\nfunc TranslateANSI(text string) string {\n\tvar buffer bytes.Buffer\n\twriter := ANSIWriter(&buffer)\n\twriter.Write([]byte(text))\n\treturn buffer.String()\n}\n"
  },
  {
    "path": "application.go",
    "content": "package tview\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gdamore/tcell/v2\"\n)\n\nconst (\n\t// The size of the event/update/redraw channels.\n\tqueueSize = 100\n\n\t// The minimum time between two consecutive redraws.\n\tredrawPause = 50 * time.Millisecond\n)\n\n// DoubleClickInterval specifies the maximum time between clicks to register a\n// double click rather than click.\nvar DoubleClickInterval = 500 * time.Millisecond\n\n// MouseAction indicates one of the actions the mouse is logically doing.\ntype MouseAction int16\n\n// Available mouse actions.\nconst (\n\tMouseMove MouseAction = iota\n\tMouseLeftDown\n\tMouseLeftUp\n\tMouseLeftClick\n\tMouseLeftDoubleClick\n\tMouseMiddleDown\n\tMouseMiddleUp\n\tMouseMiddleClick\n\tMouseMiddleDoubleClick\n\tMouseRightDown\n\tMouseRightUp\n\tMouseRightClick\n\tMouseRightDoubleClick\n\tMouseScrollUp\n\tMouseScrollDown\n\tMouseScrollLeft\n\tMouseScrollRight\n\n\t// The following special value will not be provided as a mouse action but\n\t// indicate that an overridden mouse event was consumed. See\n\t// [Box.SetMouseCapture] for details.\n\tMouseConsumed\n)\n\n// queuedUpdate represented the execution of f queued by\n// Application.QueueUpdate(). If \"done\" is not nil, it receives exactly one\n// element after f has executed.\ntype queuedUpdate struct {\n\tf    func()\n\tdone chan struct{}\n}\n\n// Application represents the top node of an application.\n//\n// It is not strictly required to use this class as none of the other classes\n// depend on it. However, it provides useful tools to set up an application and\n// plays nicely with all widgets.\n//\n// The following command displays a primitive p on the screen until Ctrl-C is\n// pressed:\n//\n//\tif err := tview.NewApplication().SetRoot(p, true).Run(); err != nil {\n//\t    panic(err)\n//\t}\ntype Application struct {\n\tsync.RWMutex\n\n\t// The application's screen. Apart from Run(), this variable should never be\n\t// set directly. Always use the screenReplacement channel after calling\n\t// Fini(), to set a new screen (or nil to stop the application).\n\tscreen tcell.Screen\n\n\t// The application's title. If not empty, it will be set on every new screen\n\t// that is added.\n\ttitle string\n\n\t// The root primitive to be seen on the screen.\n\troot Primitive\n\n\t// Whether or not the application resizes the root primitive.\n\trootFullscreen bool\n\n\t// Set to true if mouse events are enabled.\n\tenableMouse bool\n\n\t// Set to true if paste events are enabled.\n\tenablePaste bool\n\n\t// An optional capture function which receives a key event and returns the\n\t// event to be forwarded to the default input handler (nil if nothing should\n\t// be forwarded).\n\tinputCapture func(event *tcell.EventKey) *tcell.EventKey\n\n\t// An optional callback function which is invoked just before the root\n\t// primitive is drawn.\n\tbeforeDraw func(screen tcell.Screen) bool\n\n\t// An optional callback function which is invoked after the root primitive\n\t// was drawn.\n\tafterDraw func(screen tcell.Screen)\n\n\t// Used to send screen events from separate goroutine to main event loop\n\tevents chan tcell.Event\n\n\t// Functions queued from goroutines, used to serialize updates to primitives.\n\tupdates chan queuedUpdate\n\n\t// An object that the screen variable will be set to after Fini() was called.\n\t// Use this channel to set a new screen object for the application\n\t// (screen.Init() and draw() will be called implicitly). A value of nil will\n\t// stop the application.\n\tscreenReplacement chan tcell.Screen\n\n\t// An optional capture function which receives a mouse event and returns the\n\t// event to be forwarded to the default mouse handler (nil if nothing should\n\t// be forwarded).\n\tmouseCapture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)\n\n\tmouseCapturingPrimitive Primitive        // A Primitive returned by a MouseHandler which will capture future mouse events.\n\tlastMouseX, lastMouseY  int              // The last position of the mouse.\n\tmouseDownX, mouseDownY  int              // The position of the mouse when its button was last pressed.\n\tlastMouseClick          time.Time        // The time when a mouse button was last clicked.\n\tlastMouseButtons        tcell.ButtonMask // The last mouse button state.\n}\n\n// NewApplication creates and returns a new application.\nfunc NewApplication() *Application {\n\treturn &Application{\n\t\tevents:            make(chan tcell.Event, queueSize),\n\t\tupdates:           make(chan queuedUpdate, queueSize),\n\t\tscreenReplacement: make(chan tcell.Screen, 1),\n\t}\n}\n\n// SetInputCapture sets a function which captures all key events before they are\n// forwarded to the key event handler of the primitive which currently has\n// focus. This function can then choose to forward that key event (or a\n// different one) by returning it or stop the key event processing by returning\n// nil.\n//\n// The only default global key event is Ctrl-C which stops the application. It\n// requires special handling:\n//\n//   - If you do not wish to change the default behavior, return the original\n//     event object passed to your input capture function.\n//   - If you wish to block Ctrl-C from any functionality, return nil.\n//   - If you do not wish Ctrl-C to stop the application but still want to\n//     forward the Ctrl-C event to primitives down the hierarchy, return a new\n//     key event with the same key and modifiers, e.g.\n//     tcell.NewEventKey(tcell.KeyCtrlC, 0, tcell.ModNone).\n//\n// Pasted key events are not forwarded to the input capture function if pasting\n// is enabled (see [Application.EnablePaste]).\nfunc (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Application {\n\ta.inputCapture = capture\n\treturn a\n}\n\n// GetInputCapture returns the function installed with SetInputCapture() or nil\n// if no such function has been installed.\nfunc (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {\n\treturn a.inputCapture\n}\n\n// SetMouseCapture sets a function which captures mouse events (consisting of\n// the original tcell mouse event and the semantic mouse action) before they are\n// forwarded to the appropriate mouse event handler. This function can then\n// choose to forward that event (or a different one) by returning it or stop\n// the event processing by returning a nil mouse event. In such a case, the\n// event is considered consumed and the screen will be redrawn.\nfunc (a *Application) SetMouseCapture(capture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)) *Application {\n\ta.mouseCapture = capture\n\treturn a\n}\n\n// GetMouseCapture returns the function installed with SetMouseCapture() or nil\n// if no such function has been installed.\nfunc (a *Application) GetMouseCapture() func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction) {\n\treturn a.mouseCapture\n}\n\n// SetScreen allows you to provide your own tcell.Screen object. For most\n// applications, this is not needed and you should be familiar with\n// tcell.Screen when using this function. As the tcell.Screen interface may\n// change in the future, you may need to update your code when this package\n// updates to a new tcell version.\n//\n// This function is typically called before the first call to Run(). Init() need\n// not be called on the screen.\nfunc (a *Application) SetScreen(screen tcell.Screen) *Application {\n\tif screen == nil {\n\t\treturn a // Invalid input. Do nothing.\n\t}\n\n\ta.Lock()\n\tif a.screen == nil {\n\t\t// Run() has not been called yet.\n\t\ta.screen = screen\n\t\ta.Unlock()\n\t\tscreen.Init()\n\t\treturn a\n\t}\n\n\t// Run() is already in progress. Exchange screen.\n\toldScreen := a.screen\n\ta.Unlock()\n\toldScreen.Fini()\n\ta.screenReplacement <- screen\n\n\treturn a\n}\n\n// SetTitle sets the title of the terminal window, to the extent that the\n// terminal supports it. A non-empty title will be set on every new tcell.Screen\n// that is created by or added to this application.\nfunc (a *Application) SetTitle(title string) *Application {\n\ta.Lock()\n\tdefer a.Unlock()\n\ta.title = title\n\tif a.screen != nil {\n\t\ta.screen.SetTitle(title)\n\t}\n\treturn a\n}\n\n// EnableMouse enables mouse events or disables them (if \"false\" is provided).\nfunc (a *Application) EnableMouse(enable bool) *Application {\n\ta.Lock()\n\tdefer a.Unlock()\n\tif enable != a.enableMouse && a.screen != nil {\n\t\tif enable {\n\t\t\ta.screen.EnableMouse()\n\t\t} else {\n\t\t\ta.screen.DisableMouse()\n\t\t}\n\t}\n\ta.enableMouse = enable\n\treturn a\n}\n\n// EnablePaste enables the capturing of paste events or disables them (if\n// \"false\" is provided). This must be supported by the terminal.\n//\n// Widgets won't interpret paste events for navigation or selection purposes.\n// Paste events are typically only used to insert a block of text into an\n// [InputField] or a [TextArea].\nfunc (a *Application) EnablePaste(enable bool) *Application {\n\ta.Lock()\n\tdefer a.Unlock()\n\tif enable != a.enablePaste && a.screen != nil {\n\t\tif enable {\n\t\t\ta.screen.EnablePaste()\n\t\t} else {\n\t\t\ta.screen.DisablePaste()\n\t\t}\n\t}\n\ta.enablePaste = enable\n\treturn a\n}\n\n// Run starts the application and thus the event loop. This function returns\n// when [Application.Stop] was called.\n//\n// Note that while an application is running, it fully claims stdin, stdout, and\n// stderr. If you use these standard streams, they may not work as expected.\n// Consider stopping the application first or suspending it (using\n// [Application.Suspend]) if you have to interact with the standard streams, for\n// example when needing to print a call stack during a panic.\nfunc (a *Application) Run() error {\n\tvar (\n\t\terr, appErr error\n\t\tlastRedraw  time.Time   // The time the screen was last redrawn.\n\t\tredrawTimer *time.Timer // A timer to schedule the next redraw.\n\t)\n\ta.Lock()\n\n\t// Make a screen if there is none yet.\n\tif a.screen == nil {\n\t\ta.screen, err = tcell.NewScreen()\n\t\tif err != nil {\n\t\t\ta.Unlock()\n\t\t\treturn err\n\t\t}\n\t\tif err = a.screen.Init(); err != nil {\n\t\t\ta.Unlock()\n\t\t\treturn err\n\t\t}\n\t\tif a.enableMouse {\n\t\t\ta.screen.EnableMouse()\n\t\t} else {\n\t\t\ta.screen.DisableMouse()\n\t\t}\n\t\tif a.enablePaste {\n\t\t\ta.screen.EnablePaste()\n\t\t} else {\n\t\t\ta.screen.DisablePaste()\n\t\t}\n\t\tif a.title != \"\" {\n\t\t\ta.screen.SetTitle(a.title)\n\t\t}\n\t}\n\n\t// We catch panics to clean up because they mess up the terminal.\n\tdefer func() {\n\t\tif p := recover(); p != nil {\n\t\t\tif a.screen != nil {\n\t\t\t\ta.screen.Fini()\n\t\t\t}\n\t\t\tpanic(p)\n\t\t}\n\t}()\n\n\t// Draw the screen for the first time.\n\ta.Unlock()\n\ta.draw()\n\n\t// Separate loop to wait for screen events.\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor {\n\t\t\ta.RLock()\n\t\t\tscreen := a.screen\n\t\t\ta.RUnlock()\n\t\t\tif screen == nil {\n\t\t\t\t// We have no screen. Let's stop.\n\t\t\t\ta.QueueEvent(nil)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Wait for next event and queue it.\n\t\t\tevent := screen.PollEvent()\n\t\t\tif event != nil {\n\t\t\t\t// Regular event. Queue.\n\t\t\t\ta.QueueEvent(event)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// A screen was finalized (event is nil). Wait for a new screen.\n\t\t\tscreen = <-a.screenReplacement\n\t\t\tif screen == nil {\n\t\t\t\t// No new screen. We're done.\n\t\t\t\ta.QueueEvent(nil) // Stop the event loop.\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// We have a new screen. Keep going.\n\t\t\ta.Lock()\n\t\t\ta.screen = screen\n\t\t\tenableMouse := a.enableMouse\n\t\t\tenablePaste := a.enablePaste\n\t\t\ta.Unlock()\n\n\t\t\t// Initialize and draw this screen.\n\t\t\tif err := screen.Init(); err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tif enableMouse {\n\t\t\t\tscreen.EnableMouse()\n\t\t\t} else {\n\t\t\t\tscreen.DisableMouse()\n\t\t\t}\n\t\t\tif enablePaste {\n\t\t\t\tscreen.EnablePaste()\n\t\t\t} else {\n\t\t\t\tscreen.DisablePaste()\n\t\t\t}\n\t\t\tif a.title != \"\" {\n\t\t\t\tscreen.SetTitle(a.title)\n\t\t\t}\n\t\t\ta.draw()\n\t\t}\n\t}()\n\n\t// Start event loop.\n\tvar (\n\t\tpasteBuffer strings.Builder\n\t\tpasting     bool // Set to true while we receive paste key events.\n\t)\nEventLoop:\n\tfor {\n\t\tselect {\n\t\t// If we received an event, handle it.\n\t\tcase event := <-a.events:\n\t\t\tif event == nil {\n\t\t\t\tbreak EventLoop\n\t\t\t}\n\n\t\t\tswitch event := event.(type) {\n\t\t\tcase *tcell.EventKey:\n\t\t\t\t// If we are pasting, collect runes, nothing else.\n\t\t\t\tif pasting {\n\t\t\t\t\tswitch event.Key() {\n\t\t\t\t\tcase tcell.KeyRune:\n\t\t\t\t\t\tpasteBuffer.WriteRune(event.Rune())\n\t\t\t\t\tcase tcell.KeyEnter:\n\t\t\t\t\t\tpasteBuffer.WriteRune('\\n')\n\t\t\t\t\tcase tcell.KeyCtrlJ:\n\t\t\t\t\t\tpasteBuffer.WriteRune('\\n')\n\t\t\t\t\tcase tcell.KeyTab:\n\t\t\t\t\t\tpasteBuffer.WriteRune('\\t')\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\ta.RLock()\n\t\t\t\troot := a.root\n\t\t\t\tinputCapture := a.inputCapture\n\t\t\t\ta.RUnlock()\n\n\t\t\t\t// Intercept keys.\n\t\t\t\tvar draw bool\n\t\t\t\toriginalEvent := event\n\t\t\t\tif inputCapture != nil {\n\t\t\t\t\tevent = inputCapture(event)\n\t\t\t\t\tif event == nil {\n\t\t\t\t\t\ta.draw()\n\t\t\t\t\t\tbreak // Don't forward event.\n\t\t\t\t\t}\n\t\t\t\t\tdraw = true\n\t\t\t\t}\n\n\t\t\t\t// Ctrl-C closes the application.\n\t\t\t\tif event == originalEvent && event.Key() == tcell.KeyCtrlC {\n\t\t\t\t\ta.Stop()\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Pass other key events to the root primitive.\n\t\t\t\tif root != nil && root.HasFocus() {\n\t\t\t\t\tif handler := root.InputHandler(); handler != nil {\n\t\t\t\t\t\thandler(event, func(p Primitive) {\n\t\t\t\t\t\t\ta.SetFocus(p)\n\t\t\t\t\t\t})\n\t\t\t\t\t\tdraw = true\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Redraw.\n\t\t\t\tif draw {\n\t\t\t\t\ta.draw()\n\t\t\t\t}\n\t\t\tcase *tcell.EventPaste:\n\t\t\t\tif !a.enablePaste {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif event.Start() {\n\t\t\t\t\tpasting = true\n\t\t\t\t\tpasteBuffer.Reset()\n\t\t\t\t} else if event.End() {\n\t\t\t\t\tpasting = false\n\t\t\t\t\ta.RLock()\n\t\t\t\t\troot := a.root\n\t\t\t\t\ta.RUnlock()\n\t\t\t\t\tif root != nil && root.HasFocus() && pasteBuffer.Len() > 0 {\n\t\t\t\t\t\t// Pass paste event to the root primitive.\n\t\t\t\t\t\tif handler := root.PasteHandler(); handler != nil {\n\t\t\t\t\t\t\thandler(pasteBuffer.String(), func(p Primitive) {\n\t\t\t\t\t\t\t\ta.SetFocus(p)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Redraw.\n\t\t\t\t\t\ta.draw()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase *tcell.EventResize:\n\t\t\t\tif time.Since(lastRedraw) < redrawPause {\n\t\t\t\t\tif redrawTimer != nil {\n\t\t\t\t\t\tredrawTimer.Stop()\n\t\t\t\t\t}\n\t\t\t\t\tredrawTimer = time.AfterFunc(redrawPause, func() {\n\t\t\t\t\t\ta.events <- event\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\ta.RLock()\n\t\t\t\tscreen := a.screen\n\t\t\t\ta.RUnlock()\n\t\t\t\tif screen == nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tlastRedraw = time.Now()\n\t\t\t\tscreen.Clear()\n\t\t\t\ta.draw()\n\t\t\tcase *tcell.EventMouse:\n\t\t\t\tconsumed, isMouseDownAction := a.fireMouseActions(event)\n\t\t\t\tif consumed {\n\t\t\t\t\ta.draw()\n\t\t\t\t}\n\t\t\t\ta.lastMouseButtons = event.Buttons()\n\t\t\t\tif isMouseDownAction {\n\t\t\t\t\ta.mouseDownX, a.mouseDownY = event.Position()\n\t\t\t\t}\n\t\t\tcase *tcell.EventError:\n\t\t\t\tappErr = event\n\t\t\t\ta.Stop()\n\t\t\t}\n\n\t\t// If we have updates, now is the time to execute them.\n\t\tcase update := <-a.updates:\n\t\t\tupdate.f()\n\t\t\tif update.done != nil {\n\t\t\t\tupdate.done <- struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Wait for the event loop to finish.\n\twg.Wait()\n\ta.screen = nil\n\n\treturn appErr\n}\n\n// fireMouseActions analyzes the provided mouse event, derives mouse actions\n// from it and then forwards them to the corresponding primitives.\nfunc (a *Application) fireMouseActions(event *tcell.EventMouse) (consumed, isMouseDownAction bool) {\n\t// We want to relay follow-up events to the same target primitive.\n\tvar targetPrimitive Primitive\n\n\t// Helper function to fire a mouse action.\n\tfire := func(action MouseAction) {\n\t\tswitch action {\n\t\tcase MouseLeftDown, MouseMiddleDown, MouseRightDown:\n\t\t\tisMouseDownAction = true\n\t\t}\n\n\t\t// Intercept event.\n\t\tif a.mouseCapture != nil {\n\t\t\tevent, action = a.mouseCapture(event, action)\n\t\t\tif event == nil {\n\t\t\t\tconsumed = true\n\t\t\t\treturn // Don't forward event.\n\t\t\t}\n\t\t}\n\n\t\t// Determine the target primitive.\n\t\tvar primitive, capturingPrimitive Primitive\n\t\tif a.mouseCapturingPrimitive != nil {\n\t\t\tprimitive = a.mouseCapturingPrimitive\n\t\t\ttargetPrimitive = a.mouseCapturingPrimitive\n\t\t} else if targetPrimitive != nil {\n\t\t\tprimitive = targetPrimitive\n\t\t} else {\n\t\t\tprimitive = a.root\n\t\t}\n\t\tif primitive != nil {\n\t\t\tif handler := primitive.MouseHandler(); handler != nil {\n\t\t\t\tvar wasConsumed bool\n\t\t\t\twasConsumed, capturingPrimitive = handler(action, event, func(p Primitive) {\n\t\t\t\t\ta.SetFocus(p)\n\t\t\t\t})\n\t\t\t\tif wasConsumed {\n\t\t\t\t\tconsumed = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ta.mouseCapturingPrimitive = capturingPrimitive\n\t}\n\n\tx, y := event.Position()\n\tbuttons := event.Buttons()\n\tclickMoved := x != a.mouseDownX || y != a.mouseDownY\n\tbuttonChanges := buttons ^ a.lastMouseButtons\n\n\tif x != a.lastMouseX || y != a.lastMouseY {\n\t\tfire(MouseMove)\n\t\ta.lastMouseX = x\n\t\ta.lastMouseY = y\n\t}\n\n\tfor _, buttonEvent := range []struct {\n\t\tbutton                  tcell.ButtonMask\n\t\tdown, up, click, dclick MouseAction\n\t}{\n\t\t{tcell.ButtonPrimary, MouseLeftDown, MouseLeftUp, MouseLeftClick, MouseLeftDoubleClick},\n\t\t{tcell.ButtonMiddle, MouseMiddleDown, MouseMiddleUp, MouseMiddleClick, MouseMiddleDoubleClick},\n\t\t{tcell.ButtonSecondary, MouseRightDown, MouseRightUp, MouseRightClick, MouseRightDoubleClick},\n\t} {\n\t\tif buttonChanges&buttonEvent.button != 0 {\n\t\t\tif buttons&buttonEvent.button != 0 {\n\t\t\t\tfire(buttonEvent.down)\n\t\t\t} else {\n\t\t\t\tfire(buttonEvent.up) // A user override might set event to nil.\n\t\t\t\tif !clickMoved && event != nil {\n\t\t\t\t\tif a.lastMouseClick.Add(DoubleClickInterval).Before(time.Now()) {\n\t\t\t\t\t\tfire(buttonEvent.click)\n\t\t\t\t\t\ta.lastMouseClick = time.Now()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfire(buttonEvent.dclick)\n\t\t\t\t\t\ta.lastMouseClick = time.Time{} // reset\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, wheelEvent := range []struct {\n\t\tbutton tcell.ButtonMask\n\t\taction MouseAction\n\t}{\n\t\t{tcell.WheelUp, MouseScrollUp},\n\t\t{tcell.WheelDown, MouseScrollDown},\n\t\t{tcell.WheelLeft, MouseScrollLeft},\n\t\t{tcell.WheelRight, MouseScrollRight}} {\n\t\tif buttons&wheelEvent.button != 0 {\n\t\t\tfire(wheelEvent.action)\n\t\t}\n\t}\n\n\treturn consumed, isMouseDownAction\n}\n\n// Stop stops the application, causing Run() to return.\nfunc (a *Application) Stop() {\n\ta.Lock()\n\tdefer a.Unlock()\n\tscreen := a.screen\n\tif screen == nil {\n\t\treturn\n\t}\n\ta.screen = nil\n\tscreen.Fini()\n\ta.screenReplacement <- nil\n}\n\n// Suspend temporarily suspends the application by exiting terminal UI mode and\n// invoking the provided function \"f\". When \"f\" returns, terminal UI mode is\n// entered again and the application resumes.\n//\n// A return value of true indicates that the application was suspended and \"f\"\n// was called. If false is returned, the application was already suspended,\n// terminal UI mode was not exited, and \"f\" was not called.\nfunc (a *Application) Suspend(f func()) bool {\n\ta.RLock()\n\tscreen := a.screen\n\ta.RUnlock()\n\tif screen == nil {\n\t\treturn false // Screen has not yet been initialized.\n\t}\n\n\t// Enter suspended mode.\n\tif err := screen.Suspend(); err != nil {\n\t\treturn false // Suspension failed.\n\t}\n\n\t// Wait for \"f\" to return.\n\tf()\n\n\t// If the screen object has changed in the meantime, we need to do more.\n\ta.RLock()\n\tdefer a.RUnlock()\n\tif a.screen != screen {\n\t\t// Calling Stop() while in suspend mode currently still leads to a\n\t\t// panic, see https://github.com/gdamore/tcell/issues/440.\n\t\tscreen.Fini()\n\t\tif a.screen == nil {\n\t\t\treturn true // If stop was called (a.screen is nil), we're done already.\n\t\t}\n\t} else {\n\t\t// It hasn't changed. Resume.\n\t\tscreen.Resume() // Not much we can do in case of an error.\n\t}\n\n\t// Continue application loop.\n\treturn true\n}\n\n// Draw refreshes the screen (during the next update cycle). It calls the Draw()\n// function of the application's root primitive and then syncs the screen\n// buffer. It is almost never necessary to call this function. It can actually\n// deadlock your application if you call it from the main thread (e.g. in a\n// callback function of a widget). Please see\n// https://github.com/rivo/tview/wiki/Concurrency for details.\nfunc (a *Application) Draw() *Application {\n\ta.QueueUpdate(func() {\n\t\ta.draw()\n\t})\n\treturn a\n}\n\n// ForceDraw refreshes the screen immediately. Use this function with caution as\n// it may lead to race conditions with updates to primitives in other\n// goroutines. It is always preferable to call [Application.Draw] instead.\n// Never call this function from a goroutine.\n//\n// It is safe to call this function during queued updates and direct event\n// handling.\nfunc (a *Application) ForceDraw() *Application {\n\treturn a.draw()\n}\n\n// draw actually does what Draw() promises to do.\nfunc (a *Application) draw() *Application {\n\ta.Lock()\n\tdefer a.Unlock()\n\n\tscreen := a.screen\n\troot := a.root\n\tfullscreen := a.rootFullscreen\n\tbefore := a.beforeDraw\n\tafter := a.afterDraw\n\n\t// Maybe we're not ready yet or not anymore.\n\tif screen == nil || root == nil {\n\t\treturn a\n\t}\n\n\t// Resize if requested.\n\tif fullscreen { // root is not nil here.\n\t\twidth, height := screen.Size()\n\t\troot.SetRect(0, 0, width, height)\n\t}\n\n\t// Clear screen to remove unwanted artifacts from the previous cycle.\n\tscreen.Clear()\n\n\t// Call before handler if there is one.\n\tif before != nil {\n\t\tif before(screen) {\n\t\t\tscreen.Show()\n\t\t\treturn a\n\t\t}\n\t}\n\n\t// Draw all primitives.\n\troot.Draw(screen)\n\n\t// Call after handler if there is one.\n\tif after != nil {\n\t\tafter(screen)\n\t}\n\n\t// Sync screen.\n\tscreen.Show()\n\n\treturn a\n}\n\n// Sync forces a full re-sync of the screen buffer with the actual screen during\n// the next event cycle. This is useful for when the terminal screen is\n// corrupted so you may want to offer your users a keyboard shortcut to refresh\n// the screen.\nfunc (a *Application) Sync() *Application {\n\ta.updates <- queuedUpdate{f: func() {\n\t\ta.RLock()\n\t\tscreen := a.screen\n\t\ta.RUnlock()\n\t\tif screen == nil {\n\t\t\treturn\n\t\t}\n\t\tscreen.Sync()\n\t}}\n\treturn a\n}\n\n// SetBeforeDrawFunc installs a callback function which is invoked just before\n// the root primitive is drawn during screen updates. If the function returns\n// true, drawing will not continue, i.e. the root primitive will not be drawn\n// (and an after-draw-handler will not be called).\n//\n// Note that the screen is not cleared by the application. To clear the screen,\n// you may call screen.Clear().\n//\n// Provide nil to uninstall the callback function.\nfunc (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) *Application {\n\ta.beforeDraw = handler\n\treturn a\n}\n\n// GetBeforeDrawFunc returns the callback function installed with\n// SetBeforeDrawFunc() or nil if none has been installed.\nfunc (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {\n\treturn a.beforeDraw\n}\n\n// SetAfterDrawFunc installs a callback function which is invoked after the root\n// primitive was drawn during screen updates.\n//\n// Provide nil to uninstall the callback function.\nfunc (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) *Application {\n\ta.afterDraw = handler\n\treturn a\n}\n\n// GetAfterDrawFunc returns the callback function installed with\n// SetAfterDrawFunc() or nil if none has been installed.\nfunc (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {\n\treturn a.afterDraw\n}\n\n// SetRoot sets the root primitive for this application. If \"fullscreen\" is set\n// to true, the root primitive's position will be changed to fill the screen.\n//\n// This function must be called at least once or nothing will be displayed when\n// the application starts.\n//\n// It also calls SetFocus() on the primitive.\nfunc (a *Application) SetRoot(root Primitive, fullscreen bool) *Application {\n\ta.Lock()\n\ta.root = root\n\ta.rootFullscreen = fullscreen\n\tif a.screen != nil {\n\t\ta.screen.Clear()\n\t}\n\ta.Unlock()\n\n\ta.SetFocus(root)\n\n\treturn a\n}\n\n// ResizeToFullScreen resizes the given primitive such that it fills the entire\n// screen.\nfunc (a *Application) ResizeToFullScreen(p Primitive) *Application {\n\ta.RLock()\n\twidth, height := a.screen.Size()\n\ta.RUnlock()\n\tp.SetRect(0, 0, width, height)\n\treturn a\n}\n\n// SetFocus sets the focus to a new primitive. All key events will be directed\n// down the hierarchy (starting at the root) until a primitive handles them,\n// which per default goes towards the focused primitive.\n//\n// Blur will be called on the previously focused [Primitive] and all of its\n// parents (including the root). Then Focus will be called on the new\n// [Primitive] and all of its parents (including the root).\nfunc (a *Application) SetFocus(p Primitive) *Application {\n\ta.RLock()\n\troot := a.root\n\tscreen := a.screen\n\ta.RUnlock()\n\n\t// We make a focus chain with some pre-allocated space.\n\tchain := make([]Primitive, 0, 10)\n\n\t// Send blur events along the focus chain.\n\tif root != nil && root.focusChain(&chain) {\n\t\tfor index, pr := range chain {\n\t\t\tif index == 0 {\n\t\t\t\tpr.Blur()\n\t\t\t}\n\t\t\tpr.blurred()\n\t\t}\n\n\t\t// Hide the cursor. If it's needed, the new focused primitive will show it\n\t\t// again.\n\t\tif screen != nil {\n\t\t\tscreen.HideCursor()\n\t\t}\n\t} // At this point, no primitive has focus.\n\n\t// Focus the new primitive.\n\tvar delegated bool\n\tif p != nil {\n\t\tp.Focus(func(p Primitive) {\n\t\t\tdelegated = true // Avoids multiple focus notifications.\n\t\t\ta.SetFocus(p)\n\t\t})\n\t}\n\n\t// If the primitive delegated focus to a child, that call has already\n\t// notified the focus listeners.\n\tif delegated {\n\t\treturn a\n\t}\n\n\t// Send focus events along the new focus chain.\n\tchain = chain[:0]\n\tif root != nil && root.focusChain(&chain) {\n\t\tfor _, pr := range chain {\n\t\t\tpr.focused()\n\t\t}\n\t}\n\n\treturn a\n}\n\n// GetFocus returns the primitive which has the current focus. If none has it,\n// nil is returned.\nfunc (a *Application) GetFocus() Primitive {\n\ta.RLock()\n\tdefer a.RUnlock()\n\tif a.root == nil {\n\t\treturn nil\n\t}\n\tchain := make([]Primitive, 0, 10)\n\tif a.root.focusChain(&chain) && len(chain) > 0 {\n\t\treturn chain[0]\n\t}\n\treturn nil\n}\n\n// QueueUpdate is used to synchronize access to primitives from non-main\n// goroutines. The provided function will be executed as part of the event loop\n// and thus will not cause race conditions with other such update functions or\n// the Draw() function.\n//\n// Note that Draw() is not implicitly called after the execution of f as that\n// may not be desirable. You can call Draw() from f if the screen should be\n// refreshed after each update. Alternatively, use QueueUpdateDraw() to follow\n// up with an immediate refresh of the screen.\n//\n// This function returns after f has executed.\nfunc (a *Application) QueueUpdate(f func()) *Application {\n\tch := make(chan struct{})\n\ta.updates <- queuedUpdate{f: f, done: ch}\n\t<-ch\n\treturn a\n}\n\n// QueueUpdateDraw works like QueueUpdate() except it refreshes the screen\n// immediately after executing f.\nfunc (a *Application) QueueUpdateDraw(f func()) *Application {\n\ta.QueueUpdate(func() {\n\t\tf()\n\t\ta.draw()\n\t})\n\treturn a\n}\n\n// QueueEvent sends an event to the Application event loop.\n//\n// It is not recommended for event to be nil.\nfunc (a *Application) QueueEvent(event tcell.Event) *Application {\n\ta.events <- event\n\treturn a\n}\n"
  },
  {
    "path": "borders.go",
    "content": "package tview\n\n// Borders defines various borders used when primitives are drawn.\n// These may be changed to accommodate a different look and feel.\nvar Borders = struct {\n\tHorizontal  rune\n\tVertical    rune\n\tTopLeft     rune\n\tTopRight    rune\n\tBottomLeft  rune\n\tBottomRight rune\n\n\tLeftT   rune\n\tRightT  rune\n\tTopT    rune\n\tBottomT rune\n\tCross   rune\n\n\tHorizontalFocus  rune\n\tVerticalFocus    rune\n\tTopLeftFocus     rune\n\tTopRightFocus    rune\n\tBottomLeftFocus  rune\n\tBottomRightFocus rune\n}{\n\tHorizontal:  BoxDrawingsLightHorizontal,\n\tVertical:    BoxDrawingsLightVertical,\n\tTopLeft:     BoxDrawingsLightDownAndRight,\n\tTopRight:    BoxDrawingsLightDownAndLeft,\n\tBottomLeft:  BoxDrawingsLightUpAndRight,\n\tBottomRight: BoxDrawingsLightUpAndLeft,\n\n\tLeftT:   BoxDrawingsLightVerticalAndRight,\n\tRightT:  BoxDrawingsLightVerticalAndLeft,\n\tTopT:    BoxDrawingsLightDownAndHorizontal,\n\tBottomT: BoxDrawingsLightUpAndHorizontal,\n\tCross:   BoxDrawingsLightVerticalAndHorizontal,\n\n\tHorizontalFocus:  BoxDrawingsDoubleHorizontal,\n\tVerticalFocus:    BoxDrawingsDoubleVertical,\n\tTopLeftFocus:     BoxDrawingsDoubleDownAndRight,\n\tTopRightFocus:    BoxDrawingsDoubleDownAndLeft,\n\tBottomLeftFocus:  BoxDrawingsDoubleUpAndRight,\n\tBottomRightFocus: BoxDrawingsDoubleUpAndLeft,\n}\n"
  },
  {
    "path": "box.go",
    "content": "package tview\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// Box implements the Primitive interface with an empty background and optional\n// elements such as a border and a title. Box itself does not hold any content\n// but serves as the superclass of all other primitives. Subclasses add their\n// own content, typically (but not necessarily) keeping their content within the\n// box's rectangle.\n//\n// Box provides a number of utility functions available to all primitives.\n//\n// See https://github.com/rivo/tview/wiki/Box for an example.\ntype Box struct {\n\t// Points to the implementing primitive at the bottom of the hierarchy.\n\tPrimitive\n\n\t// The position of the rect.\n\tx, y, width, height int\n\n\t// The inner rect reserved for the box's content. If innerX is negative,\n\t// the rect is undefined and must be calculated.\n\tinnerX, innerY, innerWidth, innerHeight int\n\n\t// Border padding.\n\tpaddingTop, paddingBottom, paddingLeft, paddingRight int\n\n\t// The box's background color.\n\tbackgroundColor tcell.Color\n\n\t// If set to true, the background of this box is not cleared while drawing.\n\tdontClear bool\n\n\t// Whether or not a border is drawn, reducing the box's space for content by\n\t// two in width and height.\n\tborder bool\n\n\t// The border style.\n\tborderStyle tcell.Style\n\n\t// The title. Only visible if there is a border, too.\n\ttitle string\n\n\t// The color of the title.\n\ttitleColor tcell.Color\n\n\t// The alignment of the title.\n\ttitleAlign int\n\n\t// Whether or not this box has focus. At any time, this must be true only\n\t// for one primitive in the entire application. Such a primitive is usually\n\t// a visible and enabled widget but may also be a container primitive (if\n\t// no contained primitive has focus) or a primitive inaccessible to the user\n\t// (e.g. a child primitive of a widget to which interaction is delegated).\n\thasFocus bool\n\n\t// Optional callback functions invoked when the primitive receives or loses\n\t// focus.\n\tfocus, blur func()\n\n\t// Callback function invoked when the box itself is resized, nil if not set.\n\tboxResize func()\n\n\t// Callback function invoked when the box's inner content area is resized,\n\t// nil if not set.\n\tcontentResize func()\n\n\t// An optional capture function which receives a key event and returns the\n\t// event to be forwarded to the primitive's default input handler (nil if\n\t// nothing should be forwarded).\n\tinputCapture func(event *tcell.EventKey) *tcell.EventKey\n\n\t// An optional function which is called before the box is drawn.\n\tdraw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)\n\n\t// An optional capture function which receives a mouse event and returns the\n\t// event to be forwarded to the primitive's default mouse event handler (at\n\t// least one nil if nothing should be forwarded).\n\tmouseCapture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)\n}\n\n// NewBox returns a [Box] without a border.\nfunc NewBox() *Box {\n\tb := &Box{\n\t\twidth:           15,\n\t\theight:          10,\n\t\tinnerX:          -1, // Mark as uninitialized.\n\t\tbackgroundColor: Styles.PrimitiveBackgroundColor,\n\t\tborderStyle:     tcell.StyleDefault.Foreground(Styles.BorderColor).Background(Styles.PrimitiveBackgroundColor),\n\t\ttitleColor:      Styles.TitleColor,\n\t\ttitleAlign:      AlignCenter,\n\t}\n\tb.Primitive = b\n\treturn b\n}\n\n// SetBorderPadding sets the size of the borders around the box content.\nfunc (b *Box) SetBorderPadding(top, bottom, left, right int) *Box {\n\tb.paddingTop, b.paddingBottom, b.paddingLeft, b.paddingRight = top, bottom, left, right\n\treturn b\n}\n\n// GetRect returns the current position of the rectangle, x, y, width, and\n// height.\nfunc (b *Box) GetRect() (int, int, int, int) {\n\treturn b.x, b.y, b.width, b.height\n}\n\n// GetInnerRect returns the position of the inner rectangle (x, y, width,\n// height), without the border and without any padding. Width and height values\n// will clamp to 0 and thus never be negative.\nfunc (b *Box) GetInnerRect() (int, int, int, int) {\n\tif b.innerX >= 0 {\n\t\treturn b.innerX, b.innerY, b.innerWidth, b.innerHeight\n\t}\n\tx, y, width, height := b.GetRect()\n\tif b.border {\n\t\tx++\n\t\ty++\n\t\twidth -= 2\n\t\theight -= 2\n\t}\n\tx, y, width, height = x+b.paddingLeft,\n\t\ty+b.paddingTop,\n\t\twidth-b.paddingLeft-b.paddingRight,\n\t\theight-b.paddingTop-b.paddingBottom\n\tif width < 0 {\n\t\twidth = 0\n\t}\n\tif height < 0 {\n\t\theight = 0\n\t}\n\treturn x, y, width, height\n}\n\n// SetRect sets a new position of the primitive. Note that this has no effect\n// if this primitive is part of a layout (e.g. Flex, Grid) or if it was added\n// like this:\n//\n//\tapplication.SetRoot(p, true)\nfunc (b *Box) SetRect(x, y, width, height int) {\n\tb.x = x\n\tb.y = y\n\tb.width, width = width, b.width\n\tb.height, height = height, b.height\n\tif b.width != width || b.height != height {\n\t\tif b.boxResize != nil {\n\t\t\tb.boxResize()\n\t\t}\n\t\tif b.contentResize != nil {\n\t\t\tb.contentResize()\n\t\t}\n\t}\n\tb.innerX = -1 // Mark inner rect as uninitialized.\n}\n\n// SetDrawFunc sets a callback function which is invoked after the box primitive\n// has been drawn. This allows you to add a more individual style to the box\n// (and all primitives which extend it).\n//\n// The function is provided with the box's dimensions (set via SetRect()). It\n// must return the box's inner dimensions (x, y, width, height) which will be\n// returned by GetInnerRect(), used by descendent primitives to draw their own\n// content.\nfunc (b *Box) SetDrawFunc(handler func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)) *Box {\n\tb.draw = handler\n\treturn b\n}\n\n// GetDrawFunc returns the callback function which was installed with\n// SetDrawFunc() or nil if no such function has been installed.\nfunc (b *Box) GetDrawFunc() func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) {\n\treturn b.draw\n}\n\n// SetBoxResizeFunc sets a callback function which is invoked when the size of\n// the box itself changes. Note that this is not called when the box is moved\n// (i.e. when only x and y change). Set to nil to remove the callback function.\nfunc (b *Box) SetBoxResizeFunc(handler func()) *Box {\n\tb.boxResize = handler\n\treturn b\n}\n\n// SetContentResizeFunc sets a callback function which is invoked when the size\n// of the box's inner content area changes. Note that this is not called when\n// the area is moved (i.e. when only x and y change). Set to nil to remove the\n// callback function.\nfunc (b *Box) SetContentResizeFunc(handler func()) *Box {\n\tb.contentResize = handler\n\treturn b\n}\n\n// WrapInputHandler wraps an input handler (see [Box.InputHandler]) with the\n// functionality to capture input (see [Box.SetInputCapture]) before passing it\n// on to the provided (default) input handler.\n//\n// This is only meant to be used by subclassing primitives.\nfunc (b *Box) WrapInputHandler(inputHandler func(*tcell.EventKey, func(p Primitive))) func(*tcell.EventKey, func(p Primitive)) {\n\treturn func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tif b.inputCapture != nil {\n\t\t\tevent = b.inputCapture(event)\n\t\t}\n\t\tif event != nil && inputHandler != nil {\n\t\t\tinputHandler(event, setFocus)\n\t\t}\n\t}\n}\n\n// InputHandler returns nil. Box has no default input handling.\nfunc (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn b.WrapInputHandler(nil)\n}\n\n// WrapPasteHandler wraps a paste handler (see [Box.PasteHandler]).\nfunc (b *Box) WrapPasteHandler(pasteHandler func(string, func(p Primitive))) func(string, func(p Primitive)) {\n\treturn func(text string, setFocus func(p Primitive)) {\n\t\tif pasteHandler != nil {\n\t\t\tpasteHandler(text, setFocus)\n\t\t}\n\t}\n}\n\n// PasteHandler returns nil. Box has no default paste handling.\nfunc (b *Box) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {\n\treturn b.WrapPasteHandler(nil)\n}\n\n// SetInputCapture installs a function which captures key events before they are\n// forwarded to the primitive's default key event handler. This function can\n// then choose to forward that key event (or a different one) to the default\n// handler by returning it. If nil is returned, the default handler will not\n// be called.\n//\n// Providing a nil handler will remove a previously existing handler.\n//\n// This function can also be used on container primitives (like Flex, Grid, or\n// Form) as keyboard events will be handed down until they are handled.\n//\n// Pasted key events are not forwarded to the input capture function if pasting\n// is enabled (see [Application.EnablePaste]).\nfunc (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Box {\n\tb.inputCapture = capture\n\treturn b\n}\n\n// GetInputCapture returns the function installed with SetInputCapture() or nil\n// if no such function has been installed.\nfunc (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {\n\treturn b.inputCapture\n}\n\n// WrapMouseHandler wraps a mouse event handler (see [Box.MouseHandler]) with the\n// functionality to capture mouse events (see [Box.SetMouseCapture]) before passing\n// them on to the provided (default) event handler.\n//\n// This is only meant to be used by subclassing primitives.\nfunc (b *Box) WrapMouseHandler(mouseHandler func(MouseAction, *tcell.EventMouse, func(p Primitive)) (bool, Primitive)) func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif b.mouseCapture != nil {\n\t\t\taction, event = b.mouseCapture(action, event)\n\t\t}\n\t\tif event == nil {\n\t\t\tif action == MouseConsumed {\n\t\t\t\tconsumed = true\n\t\t\t}\n\t\t} else if mouseHandler != nil {\n\t\t\tconsumed, capture = mouseHandler(action, event, setFocus)\n\t\t}\n\t\treturn\n\t}\n}\n\n// MouseHandler returns nil. Box has no default mouse handling.\nfunc (b *Box) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn b.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif action == MouseLeftDown && b.InRect(event.Position()) {\n\t\t\tsetFocus(b)\n\t\t\tconsumed = true\n\t\t}\n\t\treturn\n\t})\n}\n\n// SetMouseCapture sets a function which captures mouse events (consisting of\n// the original tcell mouse event and the semantic mouse action) before they are\n// forwarded to the primitive's default mouse event handler. This function can\n// then choose to forward that event (or a different one) by returning it or\n// returning a nil mouse event, in which case the default handler will not be\n// called.\n//\n// When a nil event is returned, the returned mouse action value may be set to\n// [MouseConsumed] to indicate that the event was consumed and the screen should\n// be redrawn. Any other value will not cause a redraw.\n//\n// Providing a nil handler will remove a previously existing handler.\n//\n// Note that mouse events are ignored completely if the application has not been\n// enabled for mouse events (see [Application.EnableMouse]), which is the\n// default.\nfunc (b *Box) SetMouseCapture(capture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)) *Box {\n\tb.mouseCapture = capture\n\treturn b\n}\n\n// InRect returns true if the given coordinate is within the bounds of the box's\n// rectangle.\nfunc (b *Box) InRect(x, y int) bool {\n\trectX, rectY, width, height := b.GetRect()\n\treturn x >= rectX && x < rectX+width && y >= rectY && y < rectY+height\n}\n\n// InInnerRect returns true if the given coordinate is within the bounds of the\n// box's inner rectangle (within the border and padding).\nfunc (b *Box) InInnerRect(x, y int) bool {\n\trectX, rectY, width, height := b.GetInnerRect()\n\treturn x >= rectX && x < rectX+width && y >= rectY && y < rectY+height\n}\n\n// GetMouseCapture returns the function installed with SetMouseCapture() or nil\n// if no such function has been installed.\nfunc (b *Box) GetMouseCapture() func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse) {\n\treturn b.mouseCapture\n}\n\n// SetBackgroundColor sets the box's background color.\nfunc (b *Box) SetBackgroundColor(color tcell.Color) *Box {\n\tb.backgroundColor = color\n\tb.borderStyle = b.borderStyle.Background(color)\n\treturn b\n}\n\n// SetBorder sets the flag indicating whether or not the box should have a\n// border.\nfunc (b *Box) SetBorder(show bool) *Box {\n\tb.border, show = show, b.border\n\tif b.border != show {\n\t\tif b.contentResize != nil {\n\t\t\tb.contentResize()\n\t\t}\n\t}\n\treturn b\n}\n\n// SetBorderStyle sets the box's border style.\nfunc (b *Box) SetBorderStyle(style tcell.Style) *Box {\n\tb.borderStyle = style\n\treturn b\n}\n\n// SetBorderColor sets the box's border color.\nfunc (b *Box) SetBorderColor(color tcell.Color) *Box {\n\tb.borderStyle = b.borderStyle.Foreground(color)\n\treturn b\n}\n\n// SetBorderAttributes sets the border's style attributes. You can combine\n// different attributes using bitmask operations:\n//\n//\tbox.SetBorderAttributes(tcell.AttrItalic | tcell.AttrBold)\nfunc (b *Box) SetBorderAttributes(attr tcell.AttrMask) *Box {\n\tb.borderStyle = b.borderStyle.Attributes(attr)\n\treturn b\n}\n\n// GetBorderAttributes returns the border's style attributes.\nfunc (b *Box) GetBorderAttributes() tcell.AttrMask {\n\t_, _, attr := b.borderStyle.Decompose()\n\treturn attr\n}\n\n// GetBorderColor returns the box's border color.\nfunc (b *Box) GetBorderColor() tcell.Color {\n\tcolor, _, _ := b.borderStyle.Decompose()\n\treturn color\n}\n\n// GetBackgroundColor returns the box's background color.\nfunc (b *Box) GetBackgroundColor() tcell.Color {\n\treturn b.backgroundColor\n}\n\n// SetTitle sets the box's title.\nfunc (b *Box) SetTitle(title string) *Box {\n\tb.title = title\n\treturn b\n}\n\n// GetTitle returns the box's current title.\nfunc (b *Box) GetTitle() string {\n\treturn b.title\n}\n\n// SetTitleColor sets the box's title color.\nfunc (b *Box) SetTitleColor(color tcell.Color) *Box {\n\tb.titleColor = color\n\treturn b\n}\n\n// SetTitleAlign sets the alignment of the title, one of AlignLeft, AlignCenter,\n// or AlignRight.\nfunc (b *Box) SetTitleAlign(align int) *Box {\n\tb.titleAlign = align\n\treturn b\n}\n\n// Draw draws this primitive onto the screen.\nfunc (b *Box) Draw(screen tcell.Screen) {\n\tb.DrawForSubclass(screen, b)\n}\n\n// DrawForSubclass draws this box under the assumption that primitive p is a\n// subclass of this box. This is needed e.g. to draw proper box frames which\n// depend on the subclass's focus.\n//\n// Only call this function from your own custom primitives. It is not needed in\n// applications that have no custom primitives.\nfunc (b *Box) DrawForSubclass(screen tcell.Screen, p Primitive) {\n\t// Don't draw anything if there is no space.\n\tif b.width <= 0 || b.height <= 0 {\n\t\treturn\n\t}\n\n\t// Fill background.\n\tbackground := tcell.StyleDefault.Background(b.backgroundColor)\n\tif !b.dontClear {\n\t\tfor y := b.y; y < b.y+b.height; y++ {\n\t\t\tfor x := b.x; x < b.x+b.width; x++ {\n\t\t\t\tscreen.SetContent(x, y, ' ', nil, background)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Draw border.\n\tif b.border && b.width >= 2 && b.height >= 2 {\n\t\tvar vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune\n\t\tif p.HasFocus() {\n\t\t\thorizontal = Borders.HorizontalFocus\n\t\t\tvertical = Borders.VerticalFocus\n\t\t\ttopLeft = Borders.TopLeftFocus\n\t\t\ttopRight = Borders.TopRightFocus\n\t\t\tbottomLeft = Borders.BottomLeftFocus\n\t\t\tbottomRight = Borders.BottomRightFocus\n\t\t} else {\n\t\t\thorizontal = Borders.Horizontal\n\t\t\tvertical = Borders.Vertical\n\t\t\ttopLeft = Borders.TopLeft\n\t\t\ttopRight = Borders.TopRight\n\t\t\tbottomLeft = Borders.BottomLeft\n\t\t\tbottomRight = Borders.BottomRight\n\t\t}\n\t\tfor x := b.x + 1; x < b.x+b.width-1; x++ {\n\t\t\tscreen.SetContent(x, b.y, horizontal, nil, b.borderStyle)\n\t\t\tscreen.SetContent(x, b.y+b.height-1, horizontal, nil, b.borderStyle)\n\t\t}\n\t\tfor y := b.y + 1; y < b.y+b.height-1; y++ {\n\t\t\tscreen.SetContent(b.x, y, vertical, nil, b.borderStyle)\n\t\t\tscreen.SetContent(b.x+b.width-1, y, vertical, nil, b.borderStyle)\n\t\t}\n\t\tscreen.SetContent(b.x, b.y, topLeft, nil, b.borderStyle)\n\t\tscreen.SetContent(b.x+b.width-1, b.y, topRight, nil, b.borderStyle)\n\t\tscreen.SetContent(b.x, b.y+b.height-1, bottomLeft, nil, b.borderStyle)\n\t\tscreen.SetContent(b.x+b.width-1, b.y+b.height-1, bottomRight, nil, b.borderStyle)\n\n\t\t// Draw title.\n\t\tif b.title != \"\" && b.width >= 4 {\n\t\t\tprinted, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)\n\t\t\tif len(b.title)-printed > 0 && printed > 0 {\n\t\t\t\txEllipsis := b.x + b.width - 2\n\t\t\t\tif b.titleAlign == AlignRight {\n\t\t\t\t\txEllipsis = b.x + 1\n\t\t\t\t}\n\t\t\t\t_, _, style, _ := screen.GetContent(xEllipsis, b.y)\n\t\t\t\tfg, _, _ := style.Decompose()\n\t\t\t\tPrint(screen, string(SemigraphicsHorizontalEllipsis), xEllipsis, b.y, 1, AlignLeft, fg)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Call custom draw function.\n\tif b.draw != nil {\n\t\tb.innerX, b.innerY, b.innerWidth, b.innerHeight = b.draw(screen, b.x, b.y, b.width, b.height)\n\t} else {\n\t\t// Remember the inner rect.\n\t\tb.innerX = -1\n\t\tb.innerX, b.innerY, b.innerWidth, b.innerHeight = b.GetInnerRect()\n\t}\n}\n\n// SetFocusFunc sets a callback function which is invoked when this primitive\n// receives focus. Container primitives such as [Flex] or [Grid] will also be\n// notified if one of their descendents receive focus directly. Note that this\n// may result in a blur notification, immediately followed by a focus\n// notification, when the focus is set to a different descendent of the\n// container primitive.\n//\n// At this point, the order in which the focus callbacks are invoked during one\n// draw cycle, is not defined. However, the blur callbacks are always invoked\n// before the focus callbacks.\n//\n// Set to nil to remove the callback function.\nfunc (b *Box) SetFocusFunc(callback func()) *Box {\n\tb.focus = callback\n\treturn b\n}\n\n// SetBlurFunc sets a callback function which is invoked when this primitive\n// loses focus. Container primitives such as [Flex] or [Grid] will also be\n// notified if one of their descendents lose focus. Note that this may result in\n// a blur notification, immediately followed by a focus notification, when the\n// focus is set to a different different descendent of the container primitive.\n//\n// At this point, the order in which the blur callbacks are invoked during one\n// draw cycle, is not defined. However, the blur callbacks are always invoked\n// before the focus callbacks.\n//\n// Set to nil to remove the callback function.\nfunc (b *Box) SetBlurFunc(callback func()) *Box {\n\tb.blur = callback\n\treturn b\n}\n\n// Focus is called when this primitive directly receives focus.\nfunc (b *Box) Focus(delegate func(p Primitive)) {\n\tb.hasFocus = true\n}\n\n// focused is called when this primitive or one of its descendents receives\n// focus.\nfunc (b *Box) focused() {\n\tif b.focus != nil {\n\t\tb.focus()\n\t}\n}\n\n// Blur is called when this primitive directly loses focus.\nfunc (b *Box) Blur() {\n\tb.hasFocus = false\n}\n\n// blurred is called when this primitive or one of its descendents loses focus.\nfunc (b *Box) blurred() {\n\tif b.blur != nil {\n\t\tb.blur()\n\t}\n}\n\n// HasFocus returns whether or not this primitive has focus.\nfunc (b *Box) HasFocus() bool {\n\treturn b.Primitive.focusChain(nil)\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (b *Box) focusChain(chain *[]Primitive) bool {\n\tif !b.hasFocus {\n\t\treturn false\n\t}\n\tif chain != nil {\n\t\t*chain = append(*chain, b.Primitive)\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "button.go",
    "content": "package tview\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// Button is labeled box that triggers an action when selected.\n//\n// See https://github.com/rivo/tview/wiki/Button for an example.\ntype Button struct {\n\t*Box\n\n\t// If set to true, the button cannot be activated.\n\tdisabled bool\n\n\t// The text to be displayed inside the button.\n\ttext string\n\n\t// The button's style (when deactivated).\n\tstyle tcell.Style\n\n\t// The button's style (when activated).\n\tactivatedStyle tcell.Style\n\n\t// The button's style (when disabled).\n\tdisabledStyle tcell.Style\n\n\t// An optional function which is called when the button was selected.\n\tselected func()\n\n\t// An optional function which is called when the user leaves the button. A\n\t// key is provided indicating which key was pressed to leave (tab or\n\t// backtab).\n\texit func(tcell.Key)\n}\n\n// NewButton returns a new [Button].\nfunc NewButton(label string) *Button {\n\tbox := NewBox()\n\tbox.SetRect(0, 0, TaggedStringWidth(label)+4, 1)\n\tb := &Button{\n\t\tBox:            box,\n\t\ttext:           label,\n\t\tstyle:          tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),\n\t\tactivatedStyle: tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.InverseTextColor),\n\t\tdisabledStyle:  tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.ContrastSecondaryTextColor),\n\t}\n\tb.Box.Primitive = b\n\treturn b\n}\n\n// SetLabel sets the button text.\nfunc (b *Button) SetLabel(label string) *Button {\n\tb.text = label\n\treturn b\n}\n\n// GetLabel returns the button text.\nfunc (b *Button) GetLabel() string {\n\treturn b.text\n}\n\n// SetLabelColor sets the color of the button text.\nfunc (b *Button) SetLabelColor(color tcell.Color) *Button {\n\tb.style = b.style.Foreground(color)\n\treturn b\n}\n\n// SetStyle sets the style of the button used when it is not focused.\nfunc (b *Button) SetStyle(style tcell.Style) *Button {\n\tb.style = style\n\treturn b\n}\n\n// SetLabelColorActivated sets the color of the button text when the button is\n// in focus.\nfunc (b *Button) SetLabelColorActivated(color tcell.Color) *Button {\n\tb.activatedStyle = b.activatedStyle.Foreground(color)\n\treturn b\n}\n\n// SetBackgroundColorActivated sets the background color of the button text when\n// the button is in focus.\nfunc (b *Button) SetBackgroundColorActivated(color tcell.Color) *Button {\n\tb.activatedStyle = b.activatedStyle.Background(color)\n\treturn b\n}\n\n// SetActivatedStyle sets the style of the button used when it is focused.\nfunc (b *Button) SetActivatedStyle(style tcell.Style) *Button {\n\tb.activatedStyle = style\n\treturn b\n}\n\n// SetDisabledStyle sets the style of the button used when it is disabled.\nfunc (b *Button) SetDisabledStyle(style tcell.Style) *Button {\n\tb.disabledStyle = style\n\treturn b\n}\n\n// SetDisabled sets whether or not the button is disabled. Disabled buttons\n// cannot be activated.\n//\n// If the button is part of a form, you should set focus to the form itself\n// after calling this function to set focus to the next non-disabled form item.\nfunc (b *Button) SetDisabled(disabled bool) *Button {\n\tb.disabled = disabled\n\treturn b\n}\n\n// GetDisabled returns whether or not the button is disabled.\nfunc (b *Button) GetDisabled() bool {\n\treturn b.disabled\n}\n\n// IsDisabled is an alias for [Button.GetDisabled]. Only here for backwards\n// compatibility.\nfunc (b *Button) IsDisabled() bool {\n\treturn b.GetDisabled()\n}\n\n// SetSelectedFunc sets a handler which is called when the button was selected.\nfunc (b *Button) SetSelectedFunc(handler func()) *Button {\n\tb.selected = handler\n\treturn b\n}\n\n// SetExitFunc sets a handler which is called when the user leaves the button.\n// The callback function is provided with the key that was pressed, which is one\n// of the following:\n//\n//   - KeyEscape: Leaving the button with no specific direction.\n//   - KeyTab: Move to the next field.\n//   - KeyBacktab: Move to the previous field.\nfunc (b *Button) SetExitFunc(handler func(key tcell.Key)) *Button {\n\tb.exit = handler\n\treturn b\n}\n\n// Draw draws this primitive onto the screen.\nfunc (b *Button) Draw(screen tcell.Screen) {\n\t// Draw the box.\n\tstyle := b.style\n\tif b.disabled {\n\t\tstyle = b.disabledStyle\n\t}\n\tif b.HasFocus() && !b.disabled {\n\t\tstyle = b.activatedStyle\n\t}\n\t_, backgroundColor, _ := style.Decompose()\n\tb.SetBackgroundColor(backgroundColor)\n\tb.Box.DrawForSubclass(screen, b)\n\n\t// Draw label.\n\tx, y, width, height := b.GetInnerRect()\n\tif width > 0 && height > 0 {\n\t\ty = y + height/2\n\t\tprintWithStyle(screen, b.text, x, y, 0, width, AlignCenter, style, true)\n\t}\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn b.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tif b.disabled {\n\t\t\treturn\n\t\t}\n\n\t\t// Process key event.\n\t\tswitch key := event.Key(); key {\n\t\tcase tcell.KeyEnter: // Selected.\n\t\t\tif b.selected != nil {\n\t\t\t\tb.selected()\n\t\t\t}\n\t\tcase tcell.KeyBacktab, tcell.KeyTab, tcell.KeyEscape: // Leave. No action.\n\t\t\tif b.exit != nil {\n\t\t\t\tb.exit(key)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (b *Button) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn b.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif b.disabled {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tif !b.InRect(event.Position()) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Process mouse event.\n\t\tif action == MouseLeftDown {\n\t\t\tsetFocus(b)\n\t\t\tconsumed = true\n\t\t} else if action == MouseLeftClick {\n\t\t\tif b.selected != nil {\n\t\t\t\tb.selected()\n\t\t\t}\n\t\t\tconsumed = true\n\t\t}\n\n\t\treturn\n\t})\n}\n"
  },
  {
    "path": "checkbox.go",
    "content": "package tview\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// Checkbox implements a simple box for boolean values which can be checked and\n// unchecked.\n//\n// See https://github.com/rivo/tview/wiki/Checkbox for an example.\ntype Checkbox struct {\n\t*Box\n\n\t// Whether or not this checkbox is disabled/read-only.\n\tdisabled bool\n\n\t// Whether or not this box is checked.\n\tchecked bool\n\n\t// The text to be displayed before the input area.\n\tlabel string\n\n\t// The screen width of the label area. A value of 0 means use the width of\n\t// the label text.\n\tlabelWidth int\n\n\t// The label style.\n\tlabelStyle tcell.Style\n\n\t// The style of the unchecked checkbox.\n\tuncheckedStyle tcell.Style\n\n\t// The style of the checked checkbox.\n\tcheckedStyle tcell.Style\n\n\t// The style of the checkbox when it is currently focused.\n\tfocusStyle tcell.Style\n\n\t// The string used to display an unchecked box.\n\tuncheckedString string\n\n\t// The string used to display a checked box.\n\tcheckedString string\n\n\t// An optional function which is called when the user changes the checked\n\t// state of this checkbox.\n\tchanged func(checked bool)\n\n\t// An optional function which is called when the user indicated that they\n\t// are done entering text. The key which was pressed is provided (tab,\n\t// shift-tab, or escape).\n\tdone func(tcell.Key)\n\n\t// A callback function set by the Form class and called when the user leaves\n\t// this form item.\n\tfinished func(tcell.Key)\n}\n\n// NewCheckbox returns a new [Checkbox].\nfunc NewCheckbox() *Checkbox {\n\tc := &Checkbox{\n\t\tBox:             NewBox(),\n\t\tlabelStyle:      tcell.StyleDefault.Foreground(Styles.SecondaryTextColor),\n\t\tuncheckedStyle:  tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),\n\t\tcheckedStyle:    tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),\n\t\tfocusStyle:      tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.ContrastBackgroundColor),\n\t\tuncheckedString: \" \",\n\t\tcheckedString:   \"X\",\n\t}\n\tc.Box.Primitive = c\n\treturn c\n}\n\n// SetChecked sets the state of the checkbox. This also triggers the \"changed\"\n// callback if the state changes with this call.\nfunc (c *Checkbox) SetChecked(checked bool) *Checkbox {\n\tif c.checked != checked {\n\t\tif c.changed != nil {\n\t\t\tc.changed(checked)\n\t\t}\n\t\tc.checked = checked\n\t}\n\treturn c\n}\n\n// IsChecked returns whether or not the box is checked.\nfunc (c *Checkbox) IsChecked() bool {\n\treturn c.checked\n}\n\n// SetLabel sets the text to be displayed before the input area.\nfunc (c *Checkbox) SetLabel(label string) *Checkbox {\n\tc.label = label\n\treturn c\n}\n\n// GetLabel returns the text to be displayed before the input area.\nfunc (c *Checkbox) GetLabel() string {\n\treturn c.label\n}\n\n// SetLabelWidth sets the screen width of the label. A value of 0 will cause the\n// primitive to use the width of the label string.\nfunc (c *Checkbox) SetLabelWidth(width int) *Checkbox {\n\tc.labelWidth = width\n\treturn c\n}\n\n// SetLabelColor sets the color of the label.\nfunc (c *Checkbox) SetLabelColor(color tcell.Color) *Checkbox {\n\tc.labelStyle = c.labelStyle.Foreground(color)\n\treturn c\n}\n\n// SetLabelStyle sets the style of the label.\nfunc (c *Checkbox) SetLabelStyle(style tcell.Style) *Checkbox {\n\tc.labelStyle = style\n\treturn c\n}\n\n// SetFieldBackgroundColor sets the background color of the input area.\nfunc (c *Checkbox) SetFieldBackgroundColor(color tcell.Color) *Checkbox {\n\tc.uncheckedStyle = c.uncheckedStyle.Background(color)\n\tc.checkedStyle = c.checkedStyle.Background(color)\n\tc.focusStyle = c.focusStyle.Foreground(color)\n\treturn c\n}\n\n// SetFieldTextColor sets the text color of the input area.\nfunc (c *Checkbox) SetFieldTextColor(color tcell.Color) *Checkbox {\n\tc.uncheckedStyle = c.uncheckedStyle.Foreground(color)\n\tc.checkedStyle = c.checkedStyle.Foreground(color)\n\tc.focusStyle = c.focusStyle.Background(color)\n\treturn c\n}\n\n// SetUncheckedStyle sets the style of the unchecked checkbox.\nfunc (c *Checkbox) SetUncheckedStyle(style tcell.Style) *Checkbox {\n\tc.uncheckedStyle = style\n\treturn c\n}\n\n// SetCheckedStyle sets the style of the checked checkbox.\nfunc (c *Checkbox) SetCheckedStyle(style tcell.Style) *Checkbox {\n\tc.checkedStyle = style\n\treturn c\n}\n\n// SetActivatedStyle sets the style of the checkbox when it is currently\n// focused.\nfunc (c *Checkbox) SetActivatedStyle(style tcell.Style) *Checkbox {\n\tc.focusStyle = style\n\treturn c\n}\n\n// SetCheckedString sets the string to be displayed when the checkbox is\n// checked (defaults to \"X\"). The string may contain color tags (consider\n// adapting the checkbox's various styles accordingly). See [Escape] in\n// case you want to display square brackets.\nfunc (c *Checkbox) SetCheckedString(checked string) *Checkbox {\n\tc.checkedString = checked\n\treturn c\n}\n\n// SetUncheckedString sets the string to be displayed when the checkbox is\n// not checked (defaults to the empty space \" \"). The string may contain color\n// tags (consider adapting the checkbox's various styles accordingly). See\n// [Escape] in case you want to display square brackets.\nfunc (c *Checkbox) SetUncheckedString(unchecked string) *Checkbox {\n\tc.uncheckedString = unchecked\n\treturn c\n}\n\n// SetFormAttributes sets attributes shared by all form items.\nfunc (c *Checkbox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {\n\tc.labelWidth = labelWidth\n\tc.SetLabelColor(labelColor)\n\tc.backgroundColor = bgColor\n\tc.SetFieldTextColor(fieldTextColor)\n\tc.SetFieldBackgroundColor(fieldBgColor)\n\treturn c\n}\n\n// GetFieldWidth returns this primitive's field width.\nfunc (c *Checkbox) GetFieldWidth() int {\n\treturn 1\n}\n\n// GetFieldHeight returns this primitive's field height.\nfunc (c *Checkbox) GetFieldHeight() int {\n\treturn 1\n}\n\n// SetDisabled sets whether or not the item is disabled / read-only.\nfunc (c *Checkbox) SetDisabled(disabled bool) FormItem {\n\tc.disabled = disabled\n\tif c.finished != nil {\n\t\tc.finished(-1)\n\t}\n\treturn c\n}\n\n// GetDisabled returns whether or not the item is disabled / read-only.\nfunc (c *Checkbox) GetDisabled() bool {\n\treturn c.disabled\n}\n\n// SetChangedFunc sets a handler which is called when the checked state of this\n// checkbox was changed. The handler function receives the new state.\nfunc (c *Checkbox) SetChangedFunc(handler func(checked bool)) *Checkbox {\n\tc.changed = handler\n\treturn c\n}\n\n// SetDoneFunc sets a handler which is called when the user is done using the\n// checkbox. The callback function is provided with the key that was pressed,\n// which is one of the following:\n//\n//   - KeyEscape: Abort text input.\n//   - KeyTab: Move to the next field.\n//   - KeyBacktab: Move to the previous field.\nfunc (c *Checkbox) SetDoneFunc(handler func(key tcell.Key)) *Checkbox {\n\tc.done = handler\n\treturn c\n}\n\n// SetFinishedFunc sets a callback invoked when the user leaves this form item.\nfunc (c *Checkbox) SetFinishedFunc(handler func(key tcell.Key)) FormItem {\n\tc.finished = handler\n\treturn c\n}\n\n// Focus is called when this primitive receives focus.\nfunc (c *Checkbox) Focus(delegate func(p Primitive)) {\n\t// If we're part of a form and this item is disabled, there's nothing the\n\t// user can do here so we're finished.\n\tif c.finished != nil && c.disabled {\n\t\tc.finished(-1)\n\t\treturn\n\t}\n\n\tc.Box.Focus(delegate)\n}\n\n// Draw draws this primitive onto the screen.\nfunc (c *Checkbox) Draw(screen tcell.Screen) {\n\tc.Box.DrawForSubclass(screen, c)\n\n\t// Prepare\n\tx, y, width, height := c.GetInnerRect()\n\trightLimit := x + width\n\tif height < 1 || rightLimit <= x {\n\t\treturn\n\t}\n\n\t// Draw label.\n\t_, labelBg, _ := c.labelStyle.Decompose()\n\tif c.labelWidth > 0 {\n\t\tlabelWidth := c.labelWidth\n\t\tif labelWidth > width {\n\t\t\tlabelWidth = width\n\t\t}\n\t\tprintWithStyle(screen, c.label, x, y, 0, labelWidth, AlignLeft, c.labelStyle, labelBg == tcell.ColorDefault)\n\t\tx += labelWidth\n\t\twidth -= labelWidth\n\t} else {\n\t\t_, _, drawnWidth := printWithStyle(screen, c.label, x, y, 0, width, AlignLeft, c.labelStyle, labelBg == tcell.ColorDefault)\n\t\tx += drawnWidth\n\t\twidth -= drawnWidth\n\t}\n\n\t// Draw checkbox.\n\tstr := c.uncheckedString\n\tstyle := c.uncheckedStyle\n\tif c.checked {\n\t\tstr = c.checkedString\n\t\tstyle = c.checkedStyle\n\t}\n\tif c.disabled {\n\t\tstyle = style.Background(c.backgroundColor)\n\t}\n\tif c.HasFocus() {\n\t\tstyle = c.focusStyle\n\t}\n\tprintWithStyle(screen, str, x, y, 0, width, AlignLeft, style, c.disabled)\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn c.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tif c.disabled {\n\t\t\treturn\n\t\t}\n\n\t\t// Process key event.\n\t\tswitch key := event.Key(); key {\n\t\tcase tcell.KeyRune, tcell.KeyEnter: // Check.\n\t\t\tif key == tcell.KeyRune && event.Rune() != ' ' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tc.checked = !c.checked\n\t\t\tif c.changed != nil {\n\t\t\t\tc.changed(c.checked)\n\t\t\t}\n\t\tcase tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done.\n\t\t\tif c.done != nil {\n\t\t\t\tc.done(key)\n\t\t\t}\n\t\t\tif c.finished != nil {\n\t\t\t\tc.finished(key)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (c *Checkbox) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn c.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif c.disabled {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tx, y := event.Position()\n\t\t_, rectY, _, _ := c.GetInnerRect()\n\t\tif !c.InRect(x, y) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Process mouse event.\n\t\tif y == rectY {\n\t\t\tif action == MouseLeftDown {\n\t\t\t\tsetFocus(c)\n\t\t\t\tconsumed = true\n\t\t\t} else if action == MouseLeftClick {\n\t\t\t\tc.checked = !c.checked\n\t\t\t\tif c.changed != nil {\n\t\t\t\t\tc.changed(c.checked)\n\t\t\t\t}\n\t\t\t\tconsumed = true\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t})\n}\n"
  },
  {
    "path": "demos/box/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/box/main.go",
    "content": "// Demo code for the Box primitive.\npackage main\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tbox := tview.NewBox().\n\t\tSetBorder(true).\n\t\tSetBorderAttributes(tcell.AttrBold).\n\t\tSetTitle(\"A [red]c[yellow]o[green]l[darkcyan]o[blue]r[darkmagenta]f[red]u[yellow]l[white] [black:red]c[:yellow]o[:green]l[:darkcyan]o[:blue]r[:darkmagenta]f[:red]u[:yellow]l[white:-] [::bu]title\")\n\tif err := tview.NewApplication().SetRoot(box, true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/button/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/button/main.go",
    "content": "// Demo code for the Button primitive.\npackage main\n\nimport \"github.com/rivo/tview\"\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tbutton := tview.NewButton(\"Hit Enter to close\").SetSelectedFunc(func() {\n\t\tapp.Stop()\n\t})\n\tbutton.SetBorder(true).SetRect(0, 0, 22, 3)\n\tif err := app.SetRoot(button, false).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/checkbox/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/checkbox/main.go",
    "content": "// Demo code for the Checkbox primitive.\npackage main\n\nimport \"github.com/rivo/tview\"\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tcheckbox := tview.NewCheckbox().SetLabel(\"Hit Enter to check box: \")\n\tif err := app.SetRoot(checkbox, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/dropdown/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/dropdown/main.go",
    "content": "// Demo code for the DropDown primitive.\npackage main\n\nimport \"github.com/rivo/tview\"\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tdropdown := tview.NewDropDown().\n\t\tSetLabel(\"Select an option (hit Enter): \").\n\t\tSetOptions([]string{\"First\", \"Second\", \"Third\", \"Fourth\", \"Fifth\"}, nil)\n\tif err := app.SetRoot(dropdown, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/flex/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/flex/main.go",
    "content": "// Demo code for the Flex primitive.\npackage main\n\nimport (\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tflex := tview.NewFlex().\n\t\tAddItem(tview.NewBox().SetBorder(true).SetTitle(\"Left (1/2 x width of Top)\"), 0, 1, false).\n\t\tAddItem(tview.NewFlex().SetDirection(tview.FlexRow).\n\t\t\tAddItem(tview.NewBox().SetBorder(true).SetTitle(\"Top\"), 0, 1, false).\n\t\t\tAddItem(tview.NewBox().SetBorder(true).SetTitle(\"Middle (3 x height of Top)\"), 0, 3, false).\n\t\t\tAddItem(tview.NewBox().SetBorder(true).SetTitle(\"Bottom (5 rows)\"), 5, 1, false), 0, 2, false).\n\t\tAddItem(tview.NewBox().SetBorder(true).SetTitle(\"Right (20 cols)\"), 20, 1, false)\n\tif err := app.SetRoot(flex, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/form/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/form/main.go",
    "content": "// Demo code for the Form primitive.\npackage main\n\nimport (\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tform := tview.NewForm().\n\t\tAddDropDown(\"Title\", []string{\"Mr.\", \"Ms.\", \"Mrs.\", \"Dr.\", \"Prof.\"}, 0, nil).\n\t\tAddInputField(\"First name\", \"\", 20, nil, nil).\n\t\tAddInputField(\"Last name\", \"\", 20, nil, nil).\n\t\tAddTextArea(\"Address\", \"\", 40, 0, 0, nil).\n\t\tAddTextView(\"Notes\", \"This is just a demo.\\nYou can enter whatever you wish.\", 40, 2, true, false).\n\t\tAddCheckbox(\"Age 18+\", false, nil).\n\t\tAddPasswordField(\"Password\", \"\", 10, '*', nil).\n\t\tAddButton(\"Save\", nil).\n\t\tAddButton(\"Quit\", func() {\n\t\t\tapp.Stop()\n\t\t})\n\tform.SetBorder(true).SetTitle(\"Enter some data\").SetTitleAlign(tview.AlignLeft)\n\tif err := app.SetRoot(form, true).EnableMouse(true).EnablePaste(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/frame/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/frame/main.go",
    "content": "// Demo code for the Frame primitive.\npackage main\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tframe := tview.NewFrame(tview.NewBox().SetBackgroundColor(tcell.ColorBlue)).\n\t\tSetBorders(2, 2, 2, 2, 4, 4).\n\t\tAddText(\"Header left\", true, tview.AlignLeft, tcell.ColorWhite).\n\t\tAddText(\"Header middle\", true, tview.AlignCenter, tcell.ColorWhite).\n\t\tAddText(\"Header right\", true, tview.AlignRight, tcell.ColorWhite).\n\t\tAddText(\"Header second middle\", true, tview.AlignCenter, tcell.ColorRed).\n\t\tAddText(\"Footer middle\", false, tview.AlignCenter, tcell.ColorGreen).\n\t\tAddText(\"Footer second middle\", false, tview.AlignCenter, tcell.ColorGreen)\n\tif err := app.SetRoot(frame, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/grid/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/grid/main.go",
    "content": "// Demo code for the Grid primitive.\npackage main\n\nimport (\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tnewPrimitive := func(text string) tview.Primitive {\n\t\treturn tview.NewTextView().\n\t\t\tSetTextAlign(tview.AlignCenter).\n\t\t\tSetText(text)\n\t}\n\tmenu := newPrimitive(\"Menu\")\n\tmain := newPrimitive(\"Main content\")\n\tsideBar := newPrimitive(\"Side Bar\")\n\n\tgrid := tview.NewGrid().\n\t\tSetRows(3, 0, 3).\n\t\tSetColumns(30, 0, 30).\n\t\tSetBorders(true).\n\t\tAddItem(newPrimitive(\"Header\"), 0, 0, 1, 3, 0, 0, false).\n\t\tAddItem(newPrimitive(\"Footer\"), 2, 0, 1, 3, 0, 0, false)\n\n\t// Layout for screens narrower than 100 cells (menu and side bar are hidden).\n\tgrid.AddItem(menu, 0, 0, 0, 0, 0, 0, false).\n\t\tAddItem(main, 1, 0, 1, 3, 0, 0, false).\n\t\tAddItem(sideBar, 0, 0, 0, 0, 0, 0, false)\n\n\t// Layout for screens wider than 100 cells.\n\tgrid.AddItem(menu, 1, 0, 1, 1, 0, 100, false).\n\t\tAddItem(main, 1, 1, 1, 1, 0, 100, false).\n\t\tAddItem(sideBar, 1, 2, 1, 1, 0, 100, false)\n\n\tif err := tview.NewApplication().SetRoot(grid, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/image/README.md",
    "content": "![Screenshot](screenshot.jpg)\n"
  },
  {
    "path": "demos/image/main.go",
    "content": "// Demo code for the Image primitive.\npackage main\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\n\t\"image/jpeg\"\n\t\"image/png\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nconst (\n\tbeach = `/9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABgEGAAMAAAABAAIAAAESAAMAAAABAAEAAAEaAAUAAAABAAAAVgEbAAUAAAABAAAAXgEoAAMAAAABAAIAAIdpAAQAAAABAAAAZgAAAAAAAABIAAAAAQAAAEgAAAABAAKgAgAEAAAAAQAAAUCgAwAEAAAAAQAAAPAAAAAA/+EJIWh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0idyI/PgD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/+IP0ElDQ19QUk9GSUxFAAEBAAAPwGFwcGwCEAAAbW50clJHQiBYWVogB+YADAAVAAsADQAEYWNzcEFQUEwAAAAAQVBQTAAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1hcHBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARZGVzYwAAAVAAAABiZHNjbQAAAbQAAAScY3BydAAABlAAAAAjd3RwdAAABnQAAAAUclhZWgAABogAAAAUZ1hZWgAABpwAAAAUYlhZWgAABrAAAAAUclRSQwAABsQAAAgMYWFyZwAADtAAAAAgdmNndAAADvAAAAAwbmRpbgAADyAAAAA+bW1vZAAAD2AAAAAodmNncAAAD4gAAAA4YlRSQwAABsQAAAgMZ1RSQwAABsQAAAgMYWFiZwAADtAAAAAgYWFnZwAADtAAAAAgZGVzYwAAAAAAAAAIRGlzcGxheQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1sdWMAAAAAAAAAJgAAAAxockhSAAAAFAAAAdhrb0tSAAAADAAAAexuYk5PAAAAEgAAAfhpZAAAAAAAEgAAAgpodUhVAAAAFAAAAhxjc0NaAAAAFgAAAjBkYURLAAAAHAAAAkZubE5MAAAAFgAAAmJmaUZJAAAAEAAAAnhpdElUAAAAGAAAAohlc0VTAAAAFgAAAqByb1JPAAAAEgAAArZmckNBAAAAFgAAAshhcgAAAAAAFAAAAt51a1VBAAAAHAAAAvJoZUlMAAAAFgAAAw56aFRXAAAACgAAAyR2aVZOAAAADgAAAy5za1NLAAAAFgAAAzx6aENOAAAACgAAAyRydVJVAAAAJAAAA1JlbkdCAAAAFAAAA3ZmckZSAAAAFgAAA4ptcwAAAAAAEgAAA6BoaUlOAAAAEgAAA7J0aFRIAAAADAAAA8RjYUVTAAAAGAAAA9BlbkFVAAAAFAAAA3Zlc1hMAAAAEgAAArZkZURFAAAAEAAAA+hlblVTAAAAEgAAA/hwdEJSAAAAGAAABApwbFBMAAAAEgAABCJlbEdSAAAAIgAABDRzdlNFAAAAEAAABFZ0clRSAAAAFAAABGZwdFBUAAAAFgAABHpqYUpQAAAADAAABJAATABDAEQAIAB1ACAAYgBvAGoAac7st+wAIABMAEMARABGAGEAcgBnAGUALQBMAEMARABMAEMARAAgAFcAYQByAG4AYQBTAHoA7QBuAGUAcwAgAEwAQwBEAEIAYQByAGUAdgBuAP0AIABMAEMARABMAEMARAAtAGYAYQByAHYAZQBzAGsA5gByAG0ASwBsAGUAdQByAGUAbgAtAEwAQwBEAFYA5AByAGkALQBMAEMARABMAEMARAAgAGEAIABjAG8AbABvAHIAaQBMAEMARAAgAGEAIABjAG8AbABvAHIATABDAEQAIABjAG8AbABvAHIAQQBDAEwAIABjAG8AdQBsAGUAdQByIA8ATABDAEQAIAZFBkQGSAZGBikEGgQ+BDsETAQ+BEAEPgQyBDgEOQAgAEwAQwBEIA8ATABDAEQAIAXmBdEF4gXVBeAF2V9pgnIATABDAEQATABDAEQAIABNAOAAdQBGAGEAcgBlAGIAbgD9ACAATABDAEQEJgQyBDUEQgQ9BD4EOQAgBBYEGgAtBDQEOARBBD8EOwQ1BDkAQwBvAGwAbwB1AHIAIABMAEMARABMAEMARAAgAGMAbwB1AGwAZQB1AHIAVwBhAHIAbgBhACAATABDAEQJMAkCCRcJQAkoACAATABDAEQATABDAEQAIA4qDjUATABDAEQAIABlAG4AIABjAG8AbABvAHIARgBhAHIAYgAtAEwAQwBEAEMAbwBsAG8AcgAgAEwAQwBEAEwAQwBEACAAQwBvAGwAbwByAGkAZABvAEsAbwBsAG8AcgAgAEwAQwBEA4gDswPHA8EDyQO8A7cAIAO/A7gDzAO9A7cAIABMAEMARABGAOQAcgBnAC0ATABDAEQAUgBlAG4AawBsAGkAIABMAEMARABMAEMARAAgAGEAIABjAG8AcgBlAHMwqzDpMPwATABDAER0ZXh0AAAAAENvcHlyaWdodCBBcHBsZSBJbmMuLCAyMDIyAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAACD3wAAPb////+7WFlaIAAAAAAAAEq/AACxNwAACrlYWVogAAAAAAAAKDgAABELAADIuWN1cnYAAAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANgA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACVAJoAnwCjAKgArQCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwC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`\n\n\tchart = `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`\n)\n\nfunc main() {\n\tapp := tview.NewApplication()\n\n\timage := tview.NewImage()\n\tb, _ := base64.StdEncoding.DecodeString(beach)\n\tphoto, _ := jpeg.Decode(bytes.NewReader(b))\n\tb, _ = base64.StdEncoding.DecodeString(chart)\n\tgraphics, _ := png.Decode(bytes.NewReader(b))\n\timage.SetImage(photo)\n\n\timgType := tview.NewList().\n\t\tShowSecondaryText(false).\n\t\tAddItem(\"Photo\", \"\", 0, func() { image.SetImage(photo) }).\n\t\tAddItem(\"Graphics\", \"\", 0, func() { image.SetImage(graphics) })\n\timgType.SetTitle(\"Image Type\").SetBorder(true)\n\n\tcolors := tview.NewList().\n\t\tShowSecondaryText(false).\n\t\tAddItem(\"2 colors\", \"\", 0, func() { image.SetColors(2) }).\n\t\tAddItem(\"8 colors\", \"\", 0, func() { image.SetColors(8) }).\n\t\tAddItem(\"256 colors\", \"\", 0, func() { image.SetColors(256) }).\n\t\tAddItem(\"True-color\", \"\", 0, func() { image.SetColors(tview.TrueColor) })\n\tcolors.SetTitle(\"Colors\").SetBorder(true)\n\tfor i, c := range []int{2, 8, 256, tview.TrueColor} {\n\t\tif c == image.GetColors() {\n\t\t\tcolors.SetCurrentItem(i)\n\t\t\tbreak\n\t\t}\n\t}\n\n\tdithering := tview.NewList().\n\t\tShowSecondaryText(false).\n\t\tAddItem(\"None\", \"\", 0, func() { image.SetDithering(tview.DitheringNone) }).\n\t\tAddItem(\"Floyd-Steinberg\", \"\", 0, func() { image.SetDithering(tview.DitheringFloydSteinberg) }).\n\t\tSetCurrentItem(1)\n\tdithering.SetTitle(\"Dithering\").SetBorder(true)\n\n\tselections := []*tview.Box{imgType.Box, colors.Box, dithering.Box}\n\tfor i, box := range selections {\n\t\t(func(index int) {\n\t\t\tbox.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {\n\t\t\t\tswitch event.Key() {\n\t\t\t\tcase tcell.KeyTab:\n\t\t\t\t\tapp.SetFocus(selections[(index+1)%len(selections)])\n\t\t\t\t\treturn nil\n\t\t\t\tcase tcell.KeyBacktab:\n\t\t\t\t\tapp.SetFocus(selections[(index+len(selections)-1)%len(selections)])\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn event\n\t\t\t})\n\t\t})(i)\n\t}\n\n\tgrid := tview.NewGrid().\n\t\tSetBorders(false).\n\t\tSetColumns(18, -1).\n\t\tSetRows(4, 6, 4, -1).\n\t\tAddItem(imgType, 0, 0, 1, 1, 0, 0, true).\n\t\tAddItem(colors, 1, 0, 1, 1, 0, 0, false).\n\t\tAddItem(dithering, 2, 0, 1, 1, 0, 0, false).\n\t\tAddItem(image, 0, 1, 4, 1, 0, 0, false)\n\n\tif err := app.SetRoot(grid, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/inputfield/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/inputfield/autocomplete/main.go",
    "content": "package main\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\n// 1,000 most common English words.\nconst wordList = \"ability,able,about,above,accept,according,account,across,act,action,activity,actually,add,address,administration,admit,adult,affect,after,again,against,age,agency,agent,ago,agree,agreement,ahead,air,all,allow,almost,alone,along,already,also,although,always,American,among,amount,analysis,and,animal,another,answer,any,anyone,anything,appear,apply,approach,area,argue,arm,around,arrive,art,article,artist,as,ask,assume,at,attack,attention,attorney,audience,author,authority,available,avoid,away,baby,back,bad,bag,ball,bank,bar,base,be,beat,beautiful,because,become,bed,before,begin,behavior,behind,believe,benefit,best,better,between,beyond,big,bill,billion,bit,black,blood,blue,board,body,book,born,both,box,boy,break,bring,brother,budget,build,building,business,but,buy,by,call,camera,campaign,can,cancer,candidate,capital,car,card,care,career,carry,case,catch,cause,cell,center,central,century,certain,certainly,chair,challenge,chance,change,character,charge,check,child,choice,choose,church,citizen,city,civil,claim,class,clear,clearly,close,coach,cold,collection,college,color,come,commercial,common,community,company,compare,computer,concern,condition,conference,Congress,consider,consumer,contain,continue,control,cost,could,country,couple,course,court,cover,create,crime,cultural,culture,cup,current,customer,cut,dark,data,daughter,day,dead,deal,death,debate,decade,decide,decision,deep,defense,degree,Democrat,democratic,describe,design,despite,detail,determine,develop,development,die,difference,different,difficult,dinner,direction,director,discover,discuss,discussion,disease,do,doctor,dog,door,down,draw,dream,drive,drop,drug,during,each,early,east,easy,eat,economic,economy,edge,education,effect,effort,eight,either,election,else,employee,end,energy,enjoy,enough,enter,entire,environment,environmental,especially,establish,even,evening,event,ever,every,everybody,everyone,everything,evidence,exactly,example,executive,exist,expect,experience,expert,explain,eye,face,fact,factor,fail,fall,family,far,fast,father,fear,federal,feel,feeling,few,field,fight,figure,fill,film,final,finally,financial,find,fine,finger,finish,fire,firm,first,fish,five,floor,fly,focus,follow,food,foot,for,force,foreign,forget,form,former,forward,four,free,friend,from,front,full,fund,future,game,garden,gas,general,generation,get,girl,give,glass,go,goal,good,government,great,green,ground,group,grow,growth,guess,gun,guy,hair,half,hand,hang,happen,happy,hard,have,he,head,health,hear,heart,heat,heavy,help,her,here,herself,high,him,himself,his,history,hit,hold,home,hope,hospital,hot,hotel,hour,house,how,however,huge,human,hundred,husband,idea,identify,if,image,imagine,impact,important,improve,in,include,including,increase,indeed,indicate,individual,industry,information,inside,instead,institution,interest,interesting,international,interview,into,investment,involve,issue,it,item,its,itself,job,join,just,keep,key,kid,kill,kind,kitchen,know,knowledge,land,language,large,last,late,later,laugh,law,lawyer,lay,lead,leader,learn,least,leave,left,leg,legal,less,let,letter,level,lie,life,light,like,likely,line,list,listen,little,live,local,long,look,lose,loss,lot,love,low,machine,magazine,main,maintain,major,majority,make,man,manage,management,manager,many,market,marriage,material,matter,may,maybe,me,mean,measure,media,medical,meet,meeting,member,memory,mention,message,method,middle,might,military,million,mind,minute,miss,mission,model,modern,moment,money,month,more,morning,most,mother,mouth,move,movement,movie,Mr,Mrs,much,music,must,my,myself,n't,name,nation,national,natural,nature,near,nearly,necessary,need,network,never,new,news,newspaper,next,nice,night,no,none,nor,north,not,note,nothing,notice,now,number,occur,of,off,offer,office,officer,official,often,oh,oil,ok,old,on,once,one,only,onto,open,operation,opportunity,option,or,order,organization,other,others,our,out,outside,over,own,owner,page,pain,painting,paper,parent,part,participant,particular,particularly,partner,party,pass,past,patient,pattern,pay,peace,people,per,perform,performance,perhaps,period,person,personal,phone,physical,pick,picture,piece,place,plan,plant,play,player,PM,point,police,policy,political,politics,poor,popular,population,position,positive,possible,power,practice,prepare,present,president,pressure,pretty,prevent,price,private,probably,problem,process,produce,product,production,professional,professor,program,project,property,protect,prove,provide,public,pull,purpose,push,put,quality,question,quickly,quite,race,radio,raise,range,rate,rather,reach,read,ready,real,reality,realize,really,reason,receive,recent,recently,recognize,record,red,reduce,reflect,region,relate,relationship,religious,remain,remember,remove,report,represent,Republican,require,research,resource,respond,response,responsibility,rest,result,return,reveal,rich,right,rise,risk,road,rock,role,room,rule,run,safe,same,save,say,scene,school,science,scientist,score,sea,season,seat,second,section,security,see,seek,seem,sell,send,senior,sense,series,serious,serve,service,set,seven,several,sex,sexual,shake,share,she,shoot,short,shot,should,shoulder,show,side,sign,significant,similar,simple,simply,since,sing,single,sister,sit,site,situation,six,size,skill,skin,small,smile,so,social,society,soldier,some,somebody,someone,something,sometimes,son,song,soon,sort,sound,source,south,southern,space,speak,special,specific,speech,spend,sport,spring,staff,stage,stand,standard,star,start,state,statement,station,stay,step,still,stock,stop,store,story,strategy,street,strong,structure,student,study,stuff,style,subject,success,successful,such,suddenly,suffer,suggest,summer,support,sure,surface,system,table,take,talk,task,tax,teach,teacher,team,technology,television,tell,ten,tend,term,test,than,thank,that,the,their,them,themselves,then,theory,there,these,they,thing,think,third,this,those,though,thought,thousand,threat,three,through,throughout,throw,thus,time,to,today,together,tonight,too,top,total,tough,toward,town,trade,traditional,training,travel,treat,treatment,tree,trial,trip,trouble,true,truth,try,turn,TV,two,type,under,understand,unit,until,up,upon,us,use,usually,value,various,very,victim,view,violence,visit,voice,vote,wait,walk,wall,want,war,watch,water,way,we,weapon,wear,week,weight,well,west,western,what,whatever,when,where,whether,which,while,white,who,whole,whom,whose,why,wide,wife,will,win,wind,window,wish,with,within,without,woman,wonder,word,work,worker,world,worry,would,write,writer,wrong,yard,yeah,year,yes,yet,you,young,your,yourself\"\n\nfunc main() {\n\twords := strings.Split(wordList, \",\")\n\tapp := tview.NewApplication()\n\tinputField := tview.NewInputField().\n\t\tSetLabel(\"Enter a word: \").\n\t\tSetFieldWidth(30).\n\t\tSetDoneFunc(func(key tcell.Key) {\n\t\t\tapp.Stop()\n\t\t})\n\tinputField.SetAutocompleteFunc(func(currentText string) (entries []string) {\n\t\tif len(currentText) == 0 {\n\t\t\treturn\n\t\t}\n\t\tfor _, word := range words {\n\t\t\tif strings.HasPrefix(strings.ToLower(word), strings.ToLower(currentText)) {\n\t\t\t\tentries = append(entries, word)\n\t\t\t}\n\t\t}\n\t\tif len(entries) <= 1 {\n\t\t\tentries = nil\n\t\t}\n\t\treturn\n\t})\n\tinputField.SetAutocompletedFunc(func(text string, index, source int) bool {\n\t\tif source != tview.AutocompletedNavigate {\n\t\t\tinputField.SetText(text)\n\t\t}\n\t\treturn source == tview.AutocompletedEnter || source == tview.AutocompletedClick\n\t})\n\tif err := app.EnableMouse(true).SetRoot(inputField, true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/inputfield/autocompleteasync/main.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\ntype company struct {\n\tName string `json:\"name\"`\n}\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tinputField := tview.NewInputField().\n\t\tSetLabel(\"Enter a company name: \").\n\t\tSetFieldWidth(30).\n\t\tSetDoneFunc(func(key tcell.Key) {\n\t\t\tapp.Stop()\n\t\t})\n\n\t// Set up autocomplete function.\n\tvar mutex sync.Mutex\n\tprefixMap := make(map[string][]string)\n\tinputField.SetAutocompleteFunc(func(currentText string) []string {\n\t\t// Ignore empty text.\n\t\tprefix := strings.TrimSpace(strings.ToLower(currentText))\n\t\tif prefix == \"\" {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Do we have entries for this text already?\n\t\tmutex.Lock()\n\t\tdefer mutex.Unlock()\n\t\tentries, ok := prefixMap[prefix]\n\t\tif ok {\n\t\t\treturn entries\n\t\t}\n\n\t\t// No entries yet. Issue a request to the API in a goroutine.\n\t\tgo func() {\n\t\t\t// Ignore errors in this demo.\n\t\t\turl := \"https://autocomplete.clearbit.com/v1/companies/suggest?query=\" + url.QueryEscape(prefix)\n\t\t\tres, err := http.Get(url)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Store the result in the prefix map.\n\t\t\tvar companies []*company\n\t\t\tdec := json.NewDecoder(res.Body)\n\t\t\tif err := dec.Decode(&companies); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tentries := make([]string, 0, len(companies))\n\t\tOuterLoop:\n\t\t\tfor _, c := range companies {\n\t\t\t\tfor _, entry := range entries { // Eliminate duplicates.\n\t\t\t\t\tif strings.EqualFold(entry, c.Name) {\n\t\t\t\t\t\tcontinue OuterLoop\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tentries = append(entries, c.Name)\n\t\t\t}\n\t\t\tmutex.Lock()\n\t\t\tprefixMap[prefix] = entries\n\t\t\tmutex.Unlock()\n\n\t\t\t// Trigger an update to the input field.\n\t\t\tinputField.Autocomplete()\n\n\t\t\t// Also redraw the screen.\n\t\t\tapp.Draw()\n\t\t}()\n\n\t\treturn nil\n\t})\n\n\tif err := app.EnableMouse(true).SetRoot(inputField, true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/inputfield/main.go",
    "content": "// Demo code for the InputField primitive.\npackage main\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tinputField := tview.NewInputField().\n\t\tSetLabel(\"Enter a number: \").\n\t\tSetPlaceholder(\"E.g. 1234\").\n\t\tSetFieldWidth(10).\n\t\tSetAcceptanceFunc(tview.InputFieldInteger).\n\t\tSetDoneFunc(func(key tcell.Key) {\n\t\t\tapp.Stop()\n\t\t})\n\tif err := app.SetRoot(inputField, true).EnableMouse(true).EnablePaste(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/list/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/list/main.go",
    "content": "// Demo code for the List primitive.\npackage main\n\nimport (\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tlist := tview.NewList().\n\t\tAddItem(\"List item 1\", \"Some explanatory text\", 'a', nil).\n\t\tAddItem(\"List item 2\", \"Some explanatory text\", 'b', nil).\n\t\tAddItem(\"List item 3\", \"Some explanatory text\", 'c', nil).\n\t\tAddItem(\"List item 4\", \"Some explanatory text\", 'd', nil).\n\t\tAddItem(\"Quit\", \"Press to exit\", 'q', func() {\n\t\t\tapp.Stop()\n\t\t})\n\tif err := app.SetRoot(list, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/modal/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/modal/main.go",
    "content": "// Demo code for the Modal primitive.\npackage main\n\nimport (\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tmodal := tview.NewModal().\n\t\tSetText(\"Do you want to quit the application?\").\n\t\tAddButtons([]string{\"Quit\", \"Cancel\"}).\n\t\tSetDoneFunc(func(buttonIndex int, buttonLabel string) {\n\t\t\tif buttonLabel == \"Quit\" {\n\t\t\t\tapp.Stop()\n\t\t\t}\n\t\t})\n\tif err := app.SetRoot(modal, false).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/pages/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/pages/main.go",
    "content": "// Demo code for the Pages primitive.\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/rivo/tview\"\n)\n\nconst pageCount = 5\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tpages := tview.NewPages()\n\tfor page := 0; page < pageCount; page++ {\n\t\tfunc(page int) {\n\t\t\tpages.AddPage(fmt.Sprintf(\"page-%d\", page),\n\t\t\t\ttview.NewModal().\n\t\t\t\t\tSetText(fmt.Sprintf(\"This is page %d. Choose where to go next.\", page+1)).\n\t\t\t\t\tAddButtons([]string{\"Next\", \"Quit\"}).\n\t\t\t\t\tSetDoneFunc(func(buttonIndex int, buttonLabel string) {\n\t\t\t\t\t\tif buttonIndex == 0 {\n\t\t\t\t\t\t\tpages.SwitchToPage(fmt.Sprintf(\"page-%d\", (page+1)%pageCount))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tapp.Stop()\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\tfalse,\n\t\t\t\tpage == 0)\n\t\t}(page)\n\t}\n\tif err := app.SetRoot(pages, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/presentation/center.go",
    "content": "package main\n\nimport \"github.com/rivo/tview\"\n\n// Center returns a new primitive which shows the provided primitive in its\n// center, given the provided primitive's size.\nfunc Center(width, height int, p tview.Primitive) tview.Primitive {\n\treturn tview.NewFlex().\n\t\tAddItem(nil, 0, 1, false).\n\t\tAddItem(tview.NewFlex().\n\t\t\tSetDirection(tview.FlexRow).\n\t\t\tAddItem(nil, 0, 1, false).\n\t\t\tAddItem(p, height, 1, true).\n\t\t\tAddItem(nil, 0, 1, false), width, 1, true).\n\t\tAddItem(nil, 0, 1, false)\n}\n"
  },
  {
    "path": "demos/presentation/code.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/rivo/tview\"\n)\n\n// The width of the code window.\nconst codeWidth = 56\n\n// Code returns a primitive which displays the given primitive (with the given\n// size) on the left side and its source code on the right side.\nfunc Code(p tview.Primitive, width, height int, code string) tview.Primitive {\n\t// Set up code view.\n\tcodeView := tview.NewTextView().\n\t\tSetWrap(false).\n\t\tSetDynamicColors(true)\n\tcodeView.SetBorderPadding(1, 1, 2, 0)\n\tfmt.Fprint(codeView, code)\n\n\treturn tview.NewFlex().\n\t\tAddItem(Center(width, height, p), 0, 1, true).\n\t\tAddItem(codeView, codeWidth, 1, false)\n}\n"
  },
  {
    "path": "demos/presentation/colors.go",
    "content": "package main\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nconst colorsText = `You can use style tags almost everywhere to partially change the color of a string. Simply put a color name or hex string in square brackets to change [::s]all[::-]the following characters' color. H[green]er[white]e i[yellow]s a[darkcyan]n ex[red]amp[white]le. [::i]The [black:red]tags [black:green]look [black:yellow]like [::u]this: [blue:yellow:u[] or [#00ff00[]. [:::https://github.com/rivo/tview]Hyperlinks[:::-] are also supported.`\n\n// Colors demonstrates how to use colors.\nfunc Colors(nextSlide func()) (title string, content tview.Primitive) {\n\ttable := tview.NewTable().\n\t\tSetBorders(true).\n\t\tSetBordersColor(tcell.ColorBlue).\n\t\tSetDoneFunc(func(key tcell.Key) {\n\t\t\tnextSlide()\n\t\t})\n\tvar row, column int\n\tfor _, word := range strings.Split(colorsText, \" \") {\n\t\ttable.SetCellSimple(row, column, word)\n\t\tcolumn++\n\t\tif column > 6 {\n\t\t\tcolumn = 0\n\t\t\trow++\n\t\t}\n\t}\n\ttable.SetBorderPadding(1, 1, 2, 2).\n\t\tSetBorder(true).\n\t\tSetTitle(\"A [red]c[yellow]o[green]l[darkcyan]o[blue]r[darkmagenta]f[red]u[yellow]l[white] [black:red]c[:yellow]o[:green]l[:darkcyan]o[:blue]r[:darkmagenta]f[:red]u[:yellow]l[white:-] [::bu]title\")\n\treturn \"Colors\", Center(82, 19, table)\n}\n"
  },
  {
    "path": "demos/presentation/cover.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nconst logo = `\n   __        _\n  / /__   __(_)__ _      __\n / __/ | / / / _ \\ | /| / /\n/ /_ | |/ / /  __/ |/ |/ /\n\\__/ |___/_/\\___/|__/|__/\n\n`\n\nconst (\n\tsubtitle   = `tview - Rich Widgets for Terminal UIs`\n\tnavigation = `[yellow]Ctrl-N[-]: Next slide    [yellow]Ctrl-P[-]: Previous slide    [yellow]Ctrl-C[-]: Exit`\n\tmouse      = `(or use your mouse)`\n)\n\n// Cover returns the cover page.\nfunc Cover(nextSlide func()) (title string, content tview.Primitive) {\n\t// What's the size of the logo?\n\tlines := strings.Split(logo, \"\\n\")\n\tlogoWidth := 0\n\tlogoHeight := len(lines)\n\tfor _, line := range lines {\n\t\tif len(line) > logoWidth {\n\t\t\tlogoWidth = len(line)\n\t\t}\n\t}\n\tlogoBox := tview.NewTextView().\n\t\tSetTextColor(tcell.ColorGreen).\n\t\tSetDoneFunc(func(key tcell.Key) {\n\t\t\tnextSlide()\n\t\t})\n\tfmt.Fprint(logoBox, logo)\n\n\t// Create a frame for the subtitle and navigation infos.\n\tframe := tview.NewFrame(tview.NewBox()).\n\t\tSetBorders(0, 0, 0, 0, 0, 0).\n\t\tAddText(subtitle, true, tview.AlignCenter, tcell.ColorWhite).\n\t\tAddText(\"\", true, tview.AlignCenter, tcell.ColorWhite).\n\t\tAddText(navigation, true, tview.AlignCenter, tcell.ColorDarkMagenta).\n\t\tAddText(mouse, true, tview.AlignCenter, tcell.ColorDarkMagenta)\n\n\t// Create a Flex layout that centers the logo and subtitle.\n\tflex := tview.NewFlex().\n\t\tSetDirection(tview.FlexRow).\n\t\tAddItem(tview.NewBox(), 0, 7, false).\n\t\tAddItem(tview.NewFlex().\n\t\t\tAddItem(tview.NewBox(), 0, 1, false).\n\t\t\tAddItem(logoBox, logoWidth, 1, true).\n\t\t\tAddItem(tview.NewBox(), 0, 1, false), logoHeight, 1, true).\n\t\tAddItem(frame, 0, 10, false)\n\n\treturn \"Start\", flex\n}\n"
  },
  {
    "path": "demos/presentation/end.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\n// End shows the final slide.\nfunc End(nextSlide func()) (title string, content tview.Primitive) {\n\ttextView := tview.NewTextView().\n\t\tSetDynamicColors(true).\n\t\tSetDoneFunc(func(key tcell.Key) {\n\t\t\tnextSlide()\n\t\t})\n\turl := \"[:::https://github.com/rivo/tview]https://github.com/rivo/tview\"\n\tfmt.Fprint(textView, url)\n\treturn \"End\", Center(tview.TaggedStringWidth(url), 1, textView)\n}\n"
  },
  {
    "path": "demos/presentation/flex.go",
    "content": "package main\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\n// Flex demonstrates flexbox layout.\nfunc Flex(nextSlide func()) (title string, content tview.Primitive) {\n\tmodalShown := false\n\tpages := tview.NewPages()\n\tflex := tview.NewFlex().\n\t\tAddItem(tview.NewBox().SetBorder(true).SetTitle(\"Flexible width, twice of middle column\"), 0, 2, true).\n\t\tAddItem(tview.NewFlex().\n\t\t\tSetDirection(tview.FlexRow).\n\t\t\tAddItem(tview.NewBox().SetBorder(true).SetTitle(\"Flexible width\"), 0, 1, false).\n\t\t\tAddItem(tview.NewBox().SetBorder(true).SetTitle(\"Fixed height\"), 15, 1, false).\n\t\t\tAddItem(tview.NewBox().SetBorder(true).SetTitle(\"Flexible height\"), 0, 1, false), 0, 1, false).\n\t\tAddItem(tview.NewBox().SetBorder(true).SetTitle(\"Fixed width\"), 30, 1, false)\n\tflex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {\n\t\tif modalShown {\n\t\t\tnextSlide()\n\t\t\tmodalShown = false\n\t\t} else {\n\t\t\tpages.ShowPage(\"modal\")\n\t\t\tmodalShown = true\n\t\t}\n\t\treturn event\n\t})\n\tmodal := tview.NewModal().\n\t\tSetText(\"Resize the window to see the effect of the flexbox parameters\").\n\t\tAddButtons([]string{\"Ok\"}).SetDoneFunc(func(buttonIndex int, buttonLabel string) {\n\t\tpages.HidePage(\"modal\")\n\t})\n\tpages.AddPage(\"flex\", flex, true, true).\n\t\tAddPage(\"modal\", modal, false, false)\n\treturn \"Flex\", pages\n}\n"
  },
  {
    "path": "demos/presentation/form.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"image/jpeg\"\n\n\t\"github.com/rivo/tview\"\n)\n\nconst form = `[green]package[white] main\n\n[green]import[white] (\n    [red]\"github.com/rivo/tview\"[white]\n)\n\n[green]func[white] [yellow]main[white]() {\n    form := tview.[yellow]NewForm[white]().\n\t\t[yellow]AddImage[white]([red]\"Photo:\"[white], img, [red]0[white], [red]12[white], 0[white]).\n        [yellow]AddInputField[white]([red]\"First name:\"[white], [red]\"\"[white], [red]20[white], nil, nil).\n        [yellow]AddInputField[white]([red]\"Last name:\"[white], [red]\"\"[white], [red]20[white], nil, nil).\n        [yellow]AddDropDown[white]([red]\"Role:\"[white], [][green]string[white]{\n            [red]\"Engineer\"[white],\n            [red]\"Manager\"[white],\n            [red]\"Administration\"[white],\n        }, [red]0[white], nil).\n        [yellow]AddCheckbox[white]([red]\"On vacation:\"[white], false, nil).\n        [yellow]AddPasswordField[white]([red]\"Password:\"[white], [red]\"\"[white], [red]10[white], [red]'*'[white], nil).\n        [yellow]AddTextArea[white]([red]\"Notes:\"[white], [red]\"\"[white], [red]0[white], [red]5[white], [red]0[white], nil).\n        [yellow]AddButton[white]([red]\"Save\"[white], [yellow]func[white]() { [blue]/* Save data */[white] }).\n        [yellow]AddButton[white]([red]\"Cancel\"[white], [yellow]func[white]() { [blue]/* Cancel */[white] })\n    tview.[yellow]NewApplication[white]().\n        [yellow]SetRoot[white](form, true).\n        [yellow]Run[white]()\n}`\n\nconst photo = `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`\n\n// Form demonstrates forms.\nfunc Form(nextSlide func()) (title string, content tview.Primitive) {\n\tb, _ := base64.StdEncoding.DecodeString(photo)\n\timg, _ := jpeg.Decode(bytes.NewReader(b))\n\tf := tview.NewForm().\n\t\tAddImage(\"Photo:\", img, 0, 12, 0).\n\t\tAddInputField(\"First name:\", \"\", 0, nil, nil).\n\t\tAddInputField(\"Last name:\", \"\", 0, nil, nil).\n\t\tAddDropDown(\"Role:\", []string{\"Engineer\", \"Manager\", \"Administration\"}, 0, nil).\n\t\tAddCheckbox(\"On vacation:\", false, nil).\n\t\tAddPasswordField(\"Password:\", \"\", 10, '*', nil).\n\t\tAddTextArea(\"Notes:\", \"\", 0, 2, 0, nil).\n\t\tAddButton(\"Save\", nextSlide).\n\t\tAddButton(\"Cancel\", nextSlide)\n\tf.SetBorder(true).SetTitle(\"Employee Information\")\n\treturn \"Forms\", Code(f, 36, 31, form)\n}\n"
  },
  {
    "path": "demos/presentation/grid.go",
    "content": "package main\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\n// Grid demonstrates the grid layout.\nfunc Grid(nextSlide func()) (title string, content tview.Primitive) {\n\tmodalShown := false\n\tpages := tview.NewPages()\n\n\tnewPrimitive := func(text string) tview.Primitive {\n\t\treturn tview.NewFrame(nil).\n\t\t\tSetBorders(0, 0, 0, 0, 0, 0).\n\t\t\tAddText(text, true, tview.AlignCenter, tcell.ColorWhite)\n\t}\n\n\tmenu := newPrimitive(\"Menu\")\n\tmain := newPrimitive(\"Main content\")\n\tsideBar := newPrimitive(\"Side Bar\")\n\n\tgrid := tview.NewGrid().\n\t\tSetRows(3, 0, 3).\n\t\tSetColumns(0, -4, 0).\n\t\tSetBorders(true).\n\t\tAddItem(newPrimitive(\"Header\"), 0, 0, 1, 3, 0, 0, true).\n\t\tAddItem(newPrimitive(\"Footer\"), 2, 0, 1, 3, 0, 0, false)\n\tgrid.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {\n\t\tif modalShown {\n\t\t\tnextSlide()\n\t\t\tmodalShown = false\n\t\t} else {\n\t\t\tpages.ShowPage(\"modal\")\n\t\t\tmodalShown = true\n\t\t}\n\t\treturn event\n\t})\n\n\t// Layout for screens narrower than 100 cells (menu and side bar are hidden).\n\tgrid.AddItem(menu, 0, 0, 0, 0, 0, 0, false).\n\t\tAddItem(main, 1, 0, 1, 3, 0, 0, false).\n\t\tAddItem(sideBar, 0, 0, 0, 0, 0, 0, false)\n\n\t// Layout for screens wider than 100 cells.\n\tgrid.AddItem(menu, 1, 0, 1, 1, 0, 100, false).\n\t\tAddItem(main, 1, 1, 1, 1, 0, 100, false).\n\t\tAddItem(sideBar, 1, 2, 1, 1, 0, 100, false)\n\n\tmodal := tview.NewModal().\n\t\tSetText(\"Resize the window to see how the grid layout adapts\").\n\t\tAddButtons([]string{\"Ok\"}).SetDoneFunc(func(buttonIndex int, buttonLabel string) {\n\t\tpages.HidePage(\"modal\")\n\t})\n\n\tpages.AddPage(\"grid\", grid, true, true).\n\t\tAddPage(\"modal\", modal, false, false)\n\n\treturn \"Grid\", pages\n}\n"
  },
  {
    "path": "demos/presentation/helloworld.go",
    "content": "package main\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nconst helloWorld = `[green]package[white] main\n\n[green]import[white] (\n    [red]\"github.com/rivo/tview\"[white]\n)\n\n[green]func[white] [yellow]main[white]() {\n    box := tview.[yellow]NewBox[white]().\n        [yellow]SetBorder[white](true).\n        [yellow]SetTitle[white]([red]\"Hello, world!\"[white])\n    tview.[yellow]NewApplication[white]().\n        [yellow]SetRoot[white](box, true).\n        [yellow]Run[white]()\n}`\n\n// HelloWorld shows a simple \"Hello world\" example.\nfunc HelloWorld(nextSlide func()) (title string, content tview.Primitive) {\n\t// We use a text view because we want to capture keyboard input.\n\ttextView := tview.NewTextView().SetDoneFunc(func(key tcell.Key) {\n\t\tnextSlide()\n\t})\n\ttextView.SetBorder(true).SetTitle(\"Hello, world!\")\n\treturn \"Hello, world\", Code(textView, 30, 10, helloWorld)\n}\n"
  },
  {
    "path": "demos/presentation/inputfield.go",
    "content": "package main\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nconst inputField = `[green]package[white] main\n\n[green]import[white] (\n    [red]\"strconv\"[white]\n\n    [red]\"github.com/gdamore/tcell/v2\"[white]\n    [red]\"github.com/rivo/tview\"[white]\n)\n\n[green]func[white] [yellow]main[white]() {\n    input := tview.[yellow]NewInputField[white]().\n        [yellow]SetLabel[white]([red]\"Enter a number: \"[white]).\n        [yellow]SetAcceptanceFunc[white](\n            tview.InputFieldInteger,\n        ).[yellow]SetDoneFunc[white]([yellow]func[white](key tcell.Key) {\n            text := input.[yellow]GetText[white]()\n            n, _ := strconv.[yellow]Atoi[white](text)\n            [blue]// We have a number.[white]\n        })\n    tview.[yellow]NewApplication[white]().\n        [yellow]SetRoot[white](input, true).\n        [yellow]Run[white]()\n}`\n\n// InputField demonstrates the InputField.\nfunc InputField(nextSlide func()) (title string, content tview.Primitive) {\n\tinput := tview.NewInputField().\n\t\tSetLabel(\"Enter a number: \").\n\t\tSetAcceptanceFunc(tview.InputFieldInteger).SetDoneFunc(func(key tcell.Key) {\n\t\tnextSlide()\n\t})\n\treturn \"Input\", Code(input, 30, 1, inputField)\n}\n"
  },
  {
    "path": "demos/presentation/introduction.go",
    "content": "package main\n\nimport \"github.com/rivo/tview\"\n\n// Introduction returns a tview.List with the highlights of the tview package.\nfunc Introduction(nextSlide func()) (title string, content tview.Primitive) {\n\tlist := tview.NewList().\n\t\tAddItem(\"A Go package for terminal based UIs\", \"with a special focus on rich interactive widgets\", '1', nextSlide).\n\t\tAddItem(\"Based on github.com/gdamore/tcell\", \"Like termbox but better (see tcell docs)\", '2', nextSlide).\n\t\tAddItem(\"Designed to be simple\", `\"Hello world\" is 5 lines of code`, '3', nextSlide).\n\t\tAddItem(\"Good for data entry\", `For charts, use \"termui\" - for low-level views, use \"gocui\" - ...`, '4', nextSlide).\n\t\tAddItem(\"Extensive documentation\", \"Everything is documented, examples in GitHub wiki, demo code for each widget\", '5', nextSlide)\n\treturn \"Introduction\", Center(80, 10, list)\n}\n"
  },
  {
    "path": "demos/presentation/main.go",
    "content": "/*\nA presentation of the tview package, implemented with tview.\n\n# Navigation\n\nThe presentation will advance to the next slide when the primitive demonstrated\nin the current slide is left (usually by hitting Enter or Escape). Additionally,\nthe following shortcuts can be used:\n\n  - Ctrl-N: Jump to next slide\n  - Ctrl-P: Jump to previous slide\n*/\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\n// Slide is a function which returns the slide's main primitive and its title.\n// It receives a \"nextSlide\" function which can be called to advance the\n// presentation to the next slide.\ntype Slide func(nextSlide func()) (title string, content tview.Primitive)\n\n// The application.\nvar app = tview.NewApplication()\n\n// Starting point for the presentation.\nfunc main() {\n\t// The presentation slides.\n\tslides := []Slide{\n\t\tCover,\n\t\tIntroduction,\n\t\tHelloWorld,\n\t\tInputField,\n\t\tForm,\n\t\tTextView1,\n\t\tTextView2,\n\t\tTable,\n\t\tTreeView,\n\t\tFlex,\n\t\tGrid,\n\t\tColors,\n\t\tEnd,\n\t}\n\n\tpages := tview.NewPages()\n\n\t// The bottom row has some info on where we are.\n\tinfo := tview.NewTextView().\n\t\tSetDynamicColors(true).\n\t\tSetRegions(true).\n\t\tSetWrap(false).\n\t\tSetHighlightedFunc(func(added, removed, remaining []string) {\n\t\t\tif len(added) == 0 {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tapp.SetFocus(pages)\n\t\t\tpages.SwitchToPage(added[0])\n\t\t})\n\n\t// Create the pages for all slides.\n\tpreviousSlide := func() {\n\t\tslide, _ := strconv.Atoi(info.GetHighlights()[0])\n\t\tslide = (slide - 1 + len(slides)) % len(slides)\n\t\tinfo.Highlight(strconv.Itoa(slide)).\n\t\t\tScrollToHighlight()\n\t}\n\tnextSlide := func() {\n\t\tslide, _ := strconv.Atoi(info.GetHighlights()[0])\n\t\tslide = (slide + 1) % len(slides)\n\t\tinfo.Highlight(strconv.Itoa(slide)).\n\t\t\tScrollToHighlight()\n\t}\n\tfor index, slide := range slides {\n\t\ttitle, primitive := slide(nextSlide)\n\t\tpages.AddPage(strconv.Itoa(index), primitive, true, index == 0)\n\t\tfmt.Fprintf(info, `%d [\"%d\"][darkcyan]%s[white][\"\"]  `, index+1, index, title)\n\t}\n\tinfo.Highlight(\"0\")\n\n\t// Create the main layout.\n\tlayout := tview.NewFlex().\n\t\tSetDirection(tview.FlexRow).\n\t\tAddItem(pages, 0, 1, true).\n\t\tAddItem(info, 1, 1, false)\n\n\t// Shortcuts to navigate the slides.\n\tapp.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {\n\t\tif event.Key() == tcell.KeyCtrlN {\n\t\t\tnextSlide()\n\t\t\treturn nil\n\t\t} else if event.Key() == tcell.KeyCtrlP {\n\t\t\tpreviousSlide()\n\t\t\treturn nil\n\t\t}\n\t\treturn event\n\t})\n\n\t// Start the application.\n\tif err := app.SetRoot(layout, true).EnableMouse(true).EnablePaste(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/presentation/table.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nconst tableData = `OrderDate|Region|Rep|Item|Units|UnitCost|Total\n1/6/2017|East|Jones|Pencil|95|1.99|189.05\n1/23/2017|Central|Kivell|Binder|50|19.99|999.50\n2/9/2017|Central|Jardine|Pencil|36|4.99|179.64\n2/26/2017|Central|Gill|Pen|27|19.99|539.73\n3/15/2017|West|Sorvino|Pencil|56|2.99|167.44\n4/1/2017|East|Jones|Binder|60|4.99|299.40\n4/18/2017|Central|Andrews|Pencil|75|1.99|149.25\n5/5/2017|Central|Jardine|Pencil|90|4.99|449.10\n5/22/2017|West|Thompson|Pencil|32|1.99|63.68\n6/8/2017|East|Jones|Binder|60|8.99|539.40\n6/25/2017|Central|Morgan|Pencil|90|4.99|449.10\n7/12/2017|East|Howard|Binder|29|1.99|57.71\n7/29/2017|East|Parent|Binder|81|19.99|1,619.19\n8/15/2017|East|Jones|Pencil|35|4.99|174.65\n9/1/2017|Central|Smith|Desk|2|125.00|250.00\n9/18/2017|East|Jones|Pen Set|16|15.99|255.84\n10/5/2017|Central|Morgan|Binder|28|8.99|251.72\n10/22/2017|East|Jones|Pen|64|8.99|575.36\n11/8/2017|East|Parent|Pen|15|19.99|299.85\n11/25/2017|Central|Kivell|Pen Set|96|4.99|479.04\n12/12/2017|Central|Smith|Pencil|67|1.29|86.43\n12/29/2017|East|Parent|Pen Set|74|15.99|1,183.26\n1/15/2018|Central|Gill|Binder|46|8.99|413.54\n2/1/2018|Central|Smith|Binder|87|15.00|1,305.00\n2/18/2018|East|Jones|Binder|4|4.99|19.96\n3/7/2018|West|Sorvino|Binder|7|19.99|139.93\n3/24/2018|Central|Jardine|Pen Set|50|4.99|249.50\n4/10/2018|Central|Andrews|Pencil|66|1.99|131.34\n4/27/2018|East|Howard|Pen|96|4.99|479.04\n5/14/2018|Central|Gill|Pencil|53|1.29|68.37\n5/31/2018|Central|Gill|Binder|80|8.99|719.20\n6/17/2018|Central|Kivell|Desk|5|125.00|625.00\n7/4/2018|East|Jones|Pen Set|62|4.99|309.38\n7/21/2018|Central|Morgan|Pen Set|55|12.49|686.95\n8/7/2018|Central|Kivell|Pen Set|42|23.95|1,005.90\n8/24/2018|West|Sorvino|Desk|3|275.00|825.00\n9/10/2018|Central|Gill|Pencil|7|1.29|9.03\n9/27/2018|West|Sorvino|Pen|76|1.99|151.24\n10/14/2018|West|Thompson|Binder|57|19.99|1,139.43\n10/31/2018|Central|Andrews|Pencil|14|1.29|18.06\n11/17/2018|Central|Jardine|Binder|11|4.99|54.89\n12/4/2018|Central|Jardine|Binder|94|19.99|1,879.06\n12/21/2018|Central|Andrews|Binder|28|4.99|139.72`\n\nconst tableBasic = `[green]func[white] [yellow]main[white]() {\n    table := tview.[yellow]NewTable[white]().\n        [yellow]SetFixed[white]([red]1[white], [red]1[white])\n    [yellow]for[white] row := [red]0[white]; row < [red]40[white]; row++ {\n        [yellow]for[white] column := [red]0[white]; column < [red]7[white]; column++ {\n            color := tcell.ColorWhite\n            [yellow]if[white] row == [red]0[white] {\n                color = tcell.ColorYellow\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] {\n                color = tcell.ColorDarkCyan\n            }\n            align := tview.AlignLeft\n            [yellow]if[white] row == [red]0[white] {\n                align = tview.AlignCenter\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] || column >= [red]4[white] {\n                align = tview.AlignRight\n            }\n            table.[yellow]SetCell[white](row,\n                column,\n                &tview.TableCell{\n                    Text:  [red]\"...\"[white],\n                    Color: color,\n                    Align: align,\n                })\n        }\n    }\n    tview.[yellow]NewApplication[white]().\n        [yellow]SetRoot[white](table, true).\n        [yellow]Run[white]()\n}`\n\nconst tableSeparator = `[green]func[white] [yellow]main[white]() {\n    table := tview.[yellow]NewTable[white]().\n        [yellow]SetFixed[white]([red]1[white], [red]1[white]).\n        [yellow]SetSeparator[white](Borders.Vertical)\n    [yellow]for[white] row := [red]0[white]; row < [red]40[white]; row++ {\n        [yellow]for[white] column := [red]0[white]; column < [red]7[white]; column++ {\n            color := tcell.ColorWhite\n            [yellow]if[white] row == [red]0[white] {\n                color = tcell.ColorYellow\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] {\n                color = tcell.ColorDarkCyan\n            }\n            align := tview.AlignLeft\n            [yellow]if[white] row == [red]0[white] {\n                align = tview.AlignCenter\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] || column >= [red]4[white] {\n                align = tview.AlignRight\n            }\n            table.[yellow]SetCell[white](row,\n                column,\n                &tview.TableCell{\n                    Text:  [red]\"...\"[white],\n                    Color: color,\n                    Align: align,\n                })\n        }\n    }\n    tview.[yellow]NewApplication[white]().\n        [yellow]SetRoot[white](table, true).\n        [yellow]Run[white]()\n}`\n\nconst tableBorders = `[green]func[white] [yellow]main[white]() {\n    table := tview.[yellow]NewTable[white]().\n        [yellow]SetFixed[white]([red]1[white], [red]1[white]).\n        [yellow]SetBorders[white](true)\n    [yellow]for[white] row := [red]0[white]; row < [red]40[white]; row++ {\n        [yellow]for[white] column := [red]0[white]; column < [red]7[white]; column++ {\n            color := tcell.ColorWhite\n            [yellow]if[white] row == [red]0[white] {\n                color = tcell.ColorYellow\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] {\n                color = tcell.ColorDarkCyan\n            }\n            align := tview.AlignLeft\n            [yellow]if[white] row == [red]0[white] {\n                align = tview.AlignCenter\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] || column >= [red]4[white] {\n                align = tview.AlignRight\n            }\n            table.[yellow]SetCell[white](row,\n                column,\n                &tview.TableCell{\n                    Text:  [red]\"...\"[white],\n                    Color: color,\n                    Align: align,\n                })\n        }\n    }\n    tview.[yellow]NewApplication[white]().\n        [yellow]SetRoot[white](table, true).\n        [yellow]Run[white]()\n}`\n\nconst tableSelectRow = `[green]func[white] [yellow]main[white]() {\n    table := tview.[yellow]NewTable[white]().\n        [yellow]SetFixed[white]([red]1[white], [red]1[white]).\n        [yellow]SetSelectable[white](true, false)\n    [yellow]for[white] row := [red]0[white]; row < [red]40[white]; row++ {\n        [yellow]for[white] column := [red]0[white]; column < [red]7[white]; column++ {\n            color := tcell.ColorWhite\n            [yellow]if[white] row == [red]0[white] {\n                color = tcell.ColorYellow\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] {\n                color = tcell.ColorDarkCyan\n            }\n            align := tview.AlignLeft\n            [yellow]if[white] row == [red]0[white] {\n                align = tview.AlignCenter\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] || column >= [red]4[white] {\n                align = tview.AlignRight\n            }\n            table.[yellow]SetCell[white](row,\n                column,\n                &tview.TableCell{\n                    Text:          [red]\"...\"[white],\n                    Color:         color,\n                    Align:         align,\n                    NotSelectable: row == [red]0[white] || column == [red]0[white],\n                })\n        }\n    }\n    tview.[yellow]NewApplication[white]().\n        [yellow]SetRoot[white](table, true).\n        [yellow]Run[white]()\n}`\n\nconst tableSelectColumn = `[green]func[white] [yellow]main[white]() {\n    table := tview.[yellow]NewTable[white]().\n        [yellow]SetFixed[white]([red]1[white], [red]1[white]).\n        [yellow]SetSelectable[white](false, true)\n    [yellow]for[white] row := [red]0[white]; row < [red]40[white]; row++ {\n        [yellow]for[white] column := [red]0[white]; column < [red]7[white]; column++ {\n            color := tcell.ColorWhite\n            [yellow]if[white] row == [red]0[white] {\n                color = tcell.ColorYellow\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] {\n                color = tcell.ColorDarkCyan\n            }\n            align := tview.AlignLeft\n            [yellow]if[white] row == [red]0[white] {\n                align = tview.AlignCenter\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] || column >= [red]4[white] {\n                align = tview.AlignRight\n            }\n            table.[yellow]SetCell[white](row,\n                column,\n                &tview.TableCell{\n                    Text:          [red]\"...\"[white],\n                    Color:         color,\n                    Align:         align,\n                    NotSelectable: row == [red]0[white] || column == [red]0[white],\n                })\n        }\n    }\n    tview.[yellow]NewApplication[white]().\n        [yellow]SetRoot[white](table, true).\n        [yellow]Run[white]()\n}`\n\nconst tableSelectCell = `[green]func[white] [yellow]main[white]() {\n    table := tview.[yellow]NewTable[white]().\n        [yellow]SetFixed[white]([red]1[white], [red]1[white]).\n        [yellow]SetSelectable[white](true, true)\n    [yellow]for[white] row := [red]0[white]; row < [red]40[white]; row++ {\n        [yellow]for[white] column := [red]0[white]; column < [red]7[white]; column++ {\n            color := tcell.ColorWhite\n            [yellow]if[white] row == [red]0[white] {\n                color = tcell.ColorYellow\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] {\n                color = tcell.ColorDarkCyan\n            }\n            align := tview.AlignLeft\n            [yellow]if[white] row == [red]0[white] {\n                align = tview.AlignCenter\n            } [yellow]else[white] [yellow]if[white] column == [red]0[white] || column >= [red]4[white] {\n                align = tview.AlignRight\n            }\n            table.[yellow]SetCell[white](row,\n                column,\n                &tview.TableCell{\n                    Text:          [red]\"...\"[white],\n                    Color:         color,\n                    Align:         align,\n                    NotSelectable: row == [red]0[white] || column == [red]0[white],\n                })\n        }\n    }\n    tview.[yellow]NewApplication[white]().\n        [yellow]SetRoot[white](table, true).\n        [yellow]Run[white]()\n}`\n\n// Table demonstrates the Table.\nfunc Table(nextSlide func()) (title string, content tview.Primitive) {\n\ttable := tview.NewTable().\n\t\tSetFixed(1, 1)\n\tfor row, line := range strings.Split(tableData, \"\\n\") {\n\t\tfor column, cell := range strings.Split(line, \"|\") {\n\t\t\tcolor := tcell.ColorWhite\n\t\t\tif row == 0 {\n\t\t\t\tcolor = tcell.ColorYellow\n\t\t\t} else if column == 0 {\n\t\t\t\tcolor = tcell.ColorDarkCyan\n\t\t\t}\n\t\t\talign := tview.AlignLeft\n\t\t\tif row == 0 {\n\t\t\t\talign = tview.AlignCenter\n\t\t\t} else if column == 0 || column >= 4 {\n\t\t\t\talign = tview.AlignRight\n\t\t\t}\n\t\t\ttableCell := tview.NewTableCell(cell).\n\t\t\t\tSetTextColor(color).\n\t\t\t\tSetAlign(align).\n\t\t\t\tSetSelectable(row != 0 && column != 0)\n\t\t\tif column >= 1 && column <= 3 {\n\t\t\t\ttableCell.SetExpansion(1)\n\t\t\t}\n\t\t\ttable.SetCell(row, column, tableCell)\n\t\t}\n\t}\n\ttable.SetBorder(true).SetTitle(\"Table\")\n\n\tcode := tview.NewTextView().\n\t\tSetWrap(false).\n\t\tSetDynamicColors(true)\n\tcode.SetBorderPadding(1, 1, 2, 0)\n\n\tlist := tview.NewList()\n\n\tbasic := func() {\n\t\ttable.SetBorders(false).\n\t\t\tSetSelectable(false, false).\n\t\t\tSetSeparator(' ')\n\t\tcode.Clear()\n\t\tfmt.Fprint(code, tableBasic)\n\t}\n\n\tseparator := func() {\n\t\ttable.SetBorders(false).\n\t\t\tSetSelectable(false, false).\n\t\t\tSetSeparator(tview.Borders.Vertical)\n\t\tcode.Clear()\n\t\tfmt.Fprint(code, tableSeparator)\n\t}\n\n\tborders := func() {\n\t\ttable.SetBorders(true).\n\t\t\tSetSelectable(false, false)\n\t\tcode.Clear()\n\t\tfmt.Fprint(code, tableBorders)\n\t}\n\n\tselectRow := func() {\n\t\ttable.SetBorders(false).\n\t\t\tSetSelectable(true, false).\n\t\t\tSetSeparator(' ')\n\t\tcode.Clear()\n\t\tfmt.Fprint(code, tableSelectRow)\n\t}\n\n\tselectColumn := func() {\n\t\ttable.SetBorders(false).\n\t\t\tSetSelectable(false, true).\n\t\t\tSetSeparator(' ')\n\t\tcode.Clear()\n\t\tfmt.Fprint(code, tableSelectColumn)\n\t}\n\n\tselectCell := func() {\n\t\ttable.SetBorders(false).\n\t\t\tSetSelectable(true, true).\n\t\t\tSetSeparator(' ')\n\t\tcode.Clear()\n\t\tfmt.Fprint(code, tableSelectCell)\n\t}\n\n\tnavigate := func() {\n\t\tapp.SetFocus(table)\n\t\ttable.SetDoneFunc(func(key tcell.Key) {\n\t\t\tapp.SetFocus(list)\n\t\t}).SetSelectedFunc(func(row int, column int) {\n\t\t\tapp.SetFocus(list)\n\t\t})\n\t}\n\n\tlist.ShowSecondaryText(false).\n\t\tAddItem(\"Basic table\", \"\", 'b', basic).\n\t\tAddItem(\"Table with separator\", \"\", 's', separator).\n\t\tAddItem(\"Table with borders\", \"\", 'o', borders).\n\t\tAddItem(\"Selectable rows\", \"\", 'r', selectRow).\n\t\tAddItem(\"Selectable columns\", \"\", 'c', selectColumn).\n\t\tAddItem(\"Selectable cells\", \"\", 'l', selectCell).\n\t\tAddItem(\"Navigate\", \"\", 'n', navigate).\n\t\tAddItem(\"Next slide\", \"\", 'x', nextSlide)\n\tlist.SetBorderPadding(1, 1, 2, 2)\n\n\tbasic()\n\n\treturn \"Table\", tview.NewFlex().\n\t\tAddItem(tview.NewFlex().\n\t\t\tSetDirection(tview.FlexRow).\n\t\t\tAddItem(list, 10, 1, true).\n\t\t\tAddItem(table, 0, 1, false), 0, 1, true).\n\t\tAddItem(code, codeWidth, 1, false)\n}\n"
  },
  {
    "path": "demos/presentation/textview.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nconst textView1 = `[green]func[white] [yellow]main[white]() {\n\tapp := tview.[yellow]NewApplication[white]()\n    textView := tview.[yellow]NewTextView[white]().\n        [yellow]SetTextColor[white](tcell.ColorYellow).\n        [yellow]SetScrollable[white](false).\n        [yellow]SetChangedFunc[white]([yellow]func[white]() {\n            app.[yellow]Draw[white]()\n        })\n    [green]go[white] [yellow]func[white]() {\n        [green]var[white] n [green]int\n[white]        [yellow]for[white] {\n            n++\n            fmt.[yellow]Fprintf[white](textView, [red]\"%d \"[white], n)\n            time.[yellow]Sleep[white]([red]200[white] * time.Millisecond)\n        }\n    }()\n    app.[yellow]SetRoot[white](textView, true).\n        [yellow]Run[white]()\n}`\n\n// TextView1 demonstrates the basic text view.\nfunc TextView1(nextSlide func()) (title string, content tview.Primitive) {\n\ttextView := tview.NewTextView().\n\t\tSetTextColor(tcell.ColorYellow).\n\t\tSetScrollable(false).\n\t\tSetDoneFunc(func(key tcell.Key) {\n\t\t\tnextSlide()\n\t\t})\n\ttextView.SetChangedFunc(func() {\n\t\tif textView.HasFocus() {\n\t\t\tapp.Draw()\n\t\t}\n\t})\n\tgo func() {\n\t\tvar n int\n\t\tfor {\n\t\t\tif textView.HasFocus() {\n\t\t\t\tn++\n\t\t\t\tif n > 512 {\n\t\t\t\t\tn = 1\n\t\t\t\t\ttextView.SetText(\"\")\n\t\t\t\t}\n\n\t\t\t\tfmt.Fprintf(textView, \"%d \", n)\n\t\t\t\ttime.Sleep(200 * time.Millisecond)\n\t\t\t} else {\n\t\t\t\ttime.Sleep(time.Second)\n\t\t\t}\n\t\t}\n\t}()\n\ttextView.SetBorder(true).SetTitle(\"TextView implements io.Writer\")\n\treturn \"Text 1\", Code(textView, 36, 13, textView1)\n}\n\nconst textView2 = `[green]package[white] main\n\n[green]import[white] (\n    [red]\"strconv\"[white]\n\n    [red]\"github.com/gdamore/tcell/v2\"[white]\n    [red]\"github.com/rivo/tview\"[white]\n)\n\n[green]func[white] [yellow]main[white]() {\n    [\"0\"]textView[\"\"] := tview.[yellow]NewTextView[white]()\n    [\"1\"]textView[\"\"].[yellow]SetDynamicColors[white](true).\n        [yellow]SetWrap[white](false).\n        [yellow]SetRegions[white](true).\n        [yellow]SetDoneFunc[white]([yellow]func[white](key tcell.Key) {\n            highlights := [\"2\"]textView[\"\"].[yellow]GetHighlights[white]()\n            hasHighlights := [yellow]len[white](highlights) > [red]0\n            [yellow]switch[white] key {\n            [yellow]case[white] tcell.KeyEnter:\n                [yellow]if[white] hasHighlights {\n                    [\"3\"]textView[\"\"].[yellow]Highlight[white]()\n                } [yellow]else[white] {\n                    [\"4\"]textView[\"\"].[yellow]Highlight[white]([red]\"0\"[white]).\n                        [yellow]ScrollToHighlight[white]()\n                }\n            [yellow]case[white] tcell.KeyTab:\n                [yellow]if[white] hasHighlights {\n                    current, _ := strconv.[yellow]Atoi[white](highlights[[red]0[white]])\n                    next := (current + [red]1[white]) % [red]9\n                    [\"5\"]textView[\"\"].[yellow]Highlight[white](strconv.[yellow]Itoa[white](next)).\n                        [yellow]ScrollToHighlight[white]()\n                }\n            [yellow]case[white] tcell.KeyBacktab:\n                [yellow]if[white] hasHighlights {\n                    current, _ := strconv.[yellow]Atoi[white](highlights[[red]0[white]])\n                    next := (current - [red]1[white] + [red]9[white]) % [red]9\n                    [\"6\"]textView[\"\"].[yellow]Highlight[white](strconv.[yellow]Itoa[white](next)).\n                        [yellow]ScrollToHighlight[white]()\n                }\n            }\n        })\n    fmt.[yellow]Fprint[white]([\"7\"]textView[\"\"], content)\n    tview.[yellow]NewApplication[white]().\n        [yellow]SetRoot[white]([\"8\"]textView[\"\"], true).\n        [yellow]Run[white]()\n}`\n\n// TextView2 demonstrates the extended text view.\nfunc TextView2(nextSlide func()) (title string, content tview.Primitive) {\n\tcodeView := tview.NewTextView().\n\t\tSetWrap(false)\n\tfmt.Fprint(codeView, textView2)\n\tcodeView.SetBorder(true).SetTitle(\"Raw text\")\n\n\ttextView := tview.NewTextView()\n\ttextView.SetDynamicColors(true).\n\t\tSetWrap(false).\n\t\tSetRegions(true).\n\t\tSetDoneFunc(func(key tcell.Key) {\n\t\t\tif key == tcell.KeyEscape {\n\t\t\t\tnextSlide()\n\t\t\t\treturn\n\t\t\t}\n\t\t\thighlights := textView.GetHighlights()\n\t\t\thasHighlights := len(highlights) > 0\n\t\t\tswitch key {\n\t\t\tcase tcell.KeyEnter:\n\t\t\t\tif hasHighlights {\n\t\t\t\t\ttextView.Highlight()\n\t\t\t\t} else {\n\t\t\t\t\ttextView.Highlight(\"0\").\n\t\t\t\t\t\tScrollToHighlight()\n\t\t\t\t}\n\t\t\tcase tcell.KeyTab:\n\t\t\t\tif hasHighlights {\n\t\t\t\t\tcurrent, _ := strconv.Atoi(highlights[0])\n\t\t\t\t\tnext := (current + 1) % 9\n\t\t\t\t\ttextView.Highlight(strconv.Itoa(next)).\n\t\t\t\t\t\tScrollToHighlight()\n\t\t\t\t}\n\t\t\tcase tcell.KeyBacktab:\n\t\t\t\tif hasHighlights {\n\t\t\t\t\tcurrent, _ := strconv.Atoi(highlights[0])\n\t\t\t\t\tnext := (current - 1 + 9) % 9\n\t\t\t\t\ttextView.Highlight(strconv.Itoa(next)).\n\t\t\t\t\t\tScrollToHighlight()\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\tfmt.Fprint(textView, textView2)\n\ttextView.SetBorder(true).SetTitle(\"TextView output\")\n\treturn \"Text 2\", tview.NewFlex().\n\t\tAddItem(textView, 0, 1, true).\n\t\tAddItem(codeView, 0, 1, false)\n}\n"
  },
  {
    "path": "demos/presentation/treeview.go",
    "content": "package main\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nconst treeAllCode = `[green]package[white] main\n\n[green]import[white] [red]\"github.com/rivo/tview\"[white]\n\n[green]func[white] [yellow]main[white]() {\n\t$$$\n\n\troot := tview.[yellow]NewTreeNode[white]([red]\"Root\"[white]).\n\t\t[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]\"First child\"[white]).\n\t\t\t[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]\"Grandchild A\"[white])).\n\t\t\t[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]\"Grandchild B\"[white]))).\n\t\t[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]\"Second child\"[white]).\n\t\t\t[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]\"Grandchild C\"[white])).\n\t\t\t[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]\"Grandchild D\"[white]))).\n\t\t[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]\"Third child\"[white]))\n\n\ttree.[yellow]SetRoot[white](root).\n\t\t[yellow]SetCurrentNode[white](root)\n\n\ttview.[yellow]NewApplication[white]().\n\t\t[yellow]SetRoot[white](tree, true).\n\t\t[yellow]Run[white]()\n}`\n\nconst treeBasicCode = `tree := tview.[yellow]NewTreeView[white]()`\n\nconst treeTopLevelCode = `tree := tview.[yellow]NewTreeView[white]().\n\t\t[yellow]SetTopLevel[white]([red]1[white])`\n\nconst treeAlignCode = `tree := tview.[yellow]NewTreeView[white]().\n\t\t[yellow]SetAlign[white](true)`\n\nconst treePrefixCode = `tree := tview.[yellow]NewTreeView[white]().\n\t\t[yellow]SetGraphics[white](false).\n\t\t[yellow]SetTopLevel[white]([red]1[white]).\n\t\t[yellow]SetPrefixes[white]([][green]string[white]{\n\t\t\t[red]\"[red[]* \"[white],\n\t\t\t[red]\"[darkcyan[]- \"[white],\n\t\t\t[red]\"[darkmagenta[]- \"[white],\n\t\t})`\n\ntype node struct {\n\ttext     string\n\texpand   bool\n\tselected func()\n\tchildren []*node\n}\n\nvar (\n\ttree          = tview.NewTreeView()\n\ttreeNextSlide func()\n\ttreeCode      = tview.NewTextView().SetWrap(false).SetDynamicColors(true)\n)\n\nvar rootNode = &node{\n\ttext: \"Root\",\n\tchildren: []*node{\n\t\t{text: \"Expand all\", selected: func() { tree.GetRoot().ExpandAll() }},\n\t\t{text: \"Collapse all\", selected: func() {\n\t\t\tfor _, child := range tree.GetRoot().GetChildren() {\n\t\t\t\tchild.CollapseAll()\n\t\t\t}\n\t\t}},\n\t\t{text: \"Hide root node\", expand: true, children: []*node{\n\t\t\t{text: \"Tree list starts one level down\"},\n\t\t\t{text: \"Works better for lists where no top node is needed\"},\n\t\t\t{text: \"Switch to this layout\", selected: func() {\n\t\t\t\ttree.SetAlign(false).SetTopLevel(1).SetGraphics(true).SetPrefixes(nil)\n\t\t\t\ttreeCode.SetText(strings.Replace(treeAllCode, \"$$$\", treeTopLevelCode, -1))\n\t\t\t}},\n\t\t}},\n\t\t{text: \"Align node text\", expand: true, children: []*node{\n\t\t\t{text: \"For trees that are similar to lists\"},\n\t\t\t{text: \"Hierarchy shown only in line drawings\"},\n\t\t\t{text: \"Switch to this layout\", selected: func() {\n\t\t\t\ttree.SetAlign(true).SetTopLevel(0).SetGraphics(true).SetPrefixes(nil)\n\t\t\t\ttreeCode.SetText(strings.Replace(treeAllCode, \"$$$\", treeAlignCode, -1))\n\t\t\t}},\n\t\t}},\n\t\t{text: \"Prefixes\", expand: true, children: []*node{\n\t\t\t{text: \"Best for hierarchical bullet point lists\"},\n\t\t\t{text: \"You can define your own prefixes per level\"},\n\t\t\t{text: \"Switch to this layout\", selected: func() {\n\t\t\t\ttree.SetAlign(false).SetTopLevel(1).SetGraphics(false).SetPrefixes([]string{\"[red]* \", \"[darkcyan]- \", \"[darkmagenta]- \"})\n\t\t\t\ttreeCode.SetText(strings.Replace(treeAllCode, \"$$$\", treePrefixCode, -1))\n\t\t\t}},\n\t\t}},\n\t\t{text: \"Basic tree with graphics\", expand: true, children: []*node{\n\t\t\t{text: \"Lines illustrate hierarchy\"},\n\t\t\t{text: \"Basic indentation\"},\n\t\t\t{text: \"Switch to this layout\", selected: func() {\n\t\t\t\ttree.SetAlign(false).SetTopLevel(0).SetGraphics(true).SetPrefixes(nil)\n\t\t\t\ttreeCode.SetText(strings.Replace(treeAllCode, \"$$$\", treeBasicCode, -1))\n\t\t\t}},\n\t\t}},\n\t\t{text: \"Next slide\", selected: func() { treeNextSlide() }},\n\t}}\n\n// TreeView demonstrates the tree view.\nfunc TreeView(nextSlide func()) (title string, content tview.Primitive) {\n\ttreeNextSlide = nextSlide\n\ttree.SetBorder(true).\n\t\tSetTitle(\"TreeView\")\n\n\t// Add nodes.\n\tvar add func(target *node) *tview.TreeNode\n\tadd = func(target *node) *tview.TreeNode {\n\t\tnode := tview.NewTreeNode(target.text).\n\t\t\tSetSelectable(target.expand || target.selected != nil).\n\t\t\tSetExpanded(target == rootNode).\n\t\t\tSetReference(target)\n\t\tif target.expand {\n\t\t\tnode.SetColor(tcell.ColorGreen)\n\t\t} else if target.selected != nil {\n\t\t\tnode.SetColor(tcell.ColorRed)\n\t\t}\n\t\tfor _, child := range target.children {\n\t\t\tnode.AddChild(add(child))\n\t\t}\n\t\treturn node\n\t}\n\troot := add(rootNode)\n\ttree.SetRoot(root).\n\t\tSetCurrentNode(root).\n\t\tSetSelectedFunc(func(n *tview.TreeNode) {\n\t\t\toriginal := n.GetReference().(*node)\n\t\t\tif original.expand {\n\t\t\t\tn.SetExpanded(!n.IsExpanded())\n\t\t\t} else if original.selected != nil {\n\t\t\t\toriginal.selected()\n\t\t\t}\n\t\t})\n\n\ttreeCode.SetText(strings.Replace(treeAllCode, \"$$$\", treeBasicCode, -1)).\n\t\tSetBorderPadding(1, 1, 2, 0)\n\n\treturn \"Tree\", tview.NewFlex().\n\t\tAddItem(tree, 0, 1, true).\n\t\tAddItem(treeCode, codeWidth, 1, false)\n}\n"
  },
  {
    "path": "demos/primitive/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/primitive/main.go",
    "content": "// Demo code which illustrates how to implement your own primitive.\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\n// RadioButtons implements a simple primitive for radio button selections.\ntype RadioButtons struct {\n\t*tview.Box\n\toptions       []string\n\tcurrentOption int\n}\n\n// NewRadioButtons returns a new radio button primitive.\nfunc NewRadioButtons(options []string) *RadioButtons {\n\treturn &RadioButtons{\n\t\tBox:     tview.NewBox(),\n\t\toptions: options,\n\t}\n}\n\n// Draw draws this primitive onto the screen.\nfunc (r *RadioButtons) Draw(screen tcell.Screen) {\n\tr.Box.DrawForSubclass(screen, r)\n\tx, y, width, height := r.GetInnerRect()\n\n\tfor index, option := range r.options {\n\t\tif index >= height {\n\t\t\tbreak\n\t\t}\n\t\tradioButton := \"\\u25ef\" // Unchecked.\n\t\tif index == r.currentOption {\n\t\t\tradioButton = \"\\u25c9\" // Checked.\n\t\t}\n\t\tline := fmt.Sprintf(`%s[white]  %s`, radioButton, option)\n\t\ttview.Print(screen, line, x, y+index, width, tview.AlignLeft, tcell.ColorYellow)\n\t}\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (r *RadioButtons) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {\n\treturn r.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {\n\t\tswitch event.Key() {\n\t\tcase tcell.KeyUp:\n\t\t\tr.currentOption--\n\t\t\tif r.currentOption < 0 {\n\t\t\t\tr.currentOption = 0\n\t\t\t}\n\t\tcase tcell.KeyDown:\n\t\t\tr.currentOption++\n\t\t\tif r.currentOption >= len(r.options) {\n\t\t\t\tr.currentOption = len(r.options) - 1\n\t\t\t}\n\t\t}\n\t})\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (r *RadioButtons) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {\n\treturn r.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {\n\t\tx, y := event.Position()\n\t\t_, rectY, _, _ := r.GetInnerRect()\n\t\tif !r.InRect(x, y) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tif action == tview.MouseLeftClick {\n\t\t\tsetFocus(r)\n\t\t\tindex := y - rectY\n\t\t\tif index >= 0 && index < len(r.options) {\n\t\t\t\tr.currentOption = index\n\t\t\t\tconsumed = true\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t})\n}\n\nfunc main() {\n\tradioButtons := NewRadioButtons([]string{\"Lions\", \"Elephants\", \"Giraffes\"})\n\tradioButtons.SetBorder(true).\n\t\tSetTitle(\"Radio Button Demo\").\n\t\tSetRect(0, 0, 30, 5)\n\tif err := tview.NewApplication().SetRoot(radioButtons, false).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/table/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/table/main.go",
    "content": "// Demo code for the Table primitive.\npackage main\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tapp := tview.NewApplication()\n\ttable := tview.NewTable().\n\t\tSetBorders(true)\n\tlorem := strings.Split(\"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\", \" \")\n\tcols, rows := 10, 40\n\tword := 0\n\tfor r := 0; r < rows; r++ {\n\t\tfor c := 0; c < cols; c++ {\n\t\t\tcolor := tcell.ColorWhite\n\t\t\tif c < 1 || r < 1 {\n\t\t\t\tcolor = tcell.ColorYellow\n\t\t\t}\n\t\t\ttable.SetCell(r, c,\n\t\t\t\ttview.NewTableCell(lorem[word]).\n\t\t\t\t\tSetTextColor(color).\n\t\t\t\t\tSetAlign(tview.AlignCenter))\n\t\t\tword = (word + 1) % len(lorem)\n\t\t}\n\t}\n\ttable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {\n\t\tif key == tcell.KeyEscape {\n\t\t\tapp.Stop()\n\t\t}\n\t\tif key == tcell.KeyEnter {\n\t\t\ttable.SetSelectable(true, true)\n\t\t}\n\t}).SetSelectedFunc(func(row int, column int) {\n\t\ttable.GetCell(row, column).SetTextColor(tcell.ColorRed)\n\t\ttable.SetSelectable(false, false)\n\t})\n\tif err := app.SetRoot(table, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/table/virtualtable/README.md",
    "content": "![Screenshot](screenshot.png)"
  },
  {
    "path": "demos/table/virtualtable/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\n\t\"github.com/rivo/tview\"\n)\n\ntype TableData struct {\n\ttview.TableContentReadOnly\n}\n\nfunc (d *TableData) GetCell(row, column int) *tview.TableCell {\n\tletters := [...]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'A' + byte(row%26)} // log(math.MaxInt64) / log(26) ~= 14\n\tstart := len(letters) - 1\n\trow /= 26\n\tfor row > 0 {\n\t\tstart--\n\t\trow--\n\t\tletters[start] = 'A' + byte(row%26)\n\t\trow /= 26\n\t}\n\treturn tview.NewTableCell(fmt.Sprintf(\"[red]%s[green]%d\", letters[start:], column))\n}\n\nfunc (d *TableData) GetRowCount() int {\n\treturn math.MaxInt64\n}\n\nfunc (d *TableData) GetColumnCount() int {\n\treturn math.MaxInt64\n}\n\nfunc main() {\n\tdata := &TableData{}\n\ttable := tview.NewTable().\n\t\tSetBorders(false).\n\t\tSetSelectable(true, true).\n\t\tSetContent(data)\n\tif err := tview.NewApplication().SetRoot(table, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/textarea/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/textarea/main.go",
    "content": "// Demo code for the TextArea primitive.\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tapp := tview.NewApplication()\n\n\ttextArea := tview.NewTextArea().\n\t\tSetWrap(false).\n\t\tSetPlaceholder(\"Enter text here...\")\n\ttextArea.SetTitle(\"Text Area\").SetBorder(true)\n\thelpInfo := tview.NewTextView().\n\t\tSetText(\" Press F1 for help, press Ctrl-C to exit\")\n\tposition := tview.NewTextView().\n\t\tSetDynamicColors(true).\n\t\tSetTextAlign(tview.AlignRight)\n\tpages := tview.NewPages()\n\n\tupdateInfos := func() {\n\t\tfromRow, fromColumn, toRow, toColumn := textArea.GetCursor()\n\t\tif fromRow == toRow && fromColumn == toColumn {\n\t\t\tposition.SetText(fmt.Sprintf(\"Row: [yellow]%d[white], Column: [yellow]%d \", fromRow, fromColumn))\n\t\t} else {\n\t\t\tposition.SetText(fmt.Sprintf(\"[red]From[white] Row: [yellow]%d[white], Column: [yellow]%d[white] - [red]To[white] Row: [yellow]%d[white], To Column: [yellow]%d \", fromRow, fromColumn, toRow, toColumn))\n\t\t}\n\t}\n\n\ttextArea.SetMovedFunc(updateInfos)\n\tupdateInfos()\n\n\tmainView := tview.NewGrid().\n\t\tSetRows(0, 1).\n\t\tAddItem(textArea, 0, 0, 1, 2, 0, 0, true).\n\t\tAddItem(helpInfo, 1, 0, 1, 1, 0, 0, false).\n\t\tAddItem(position, 1, 1, 1, 1, 0, 0, false)\n\n\thelp1 := tview.NewTextView().\n\t\tSetDynamicColors(true).\n\t\tSetText(`[green]Navigation\n\n[yellow]Left arrow[white]: Move left.\n[yellow]Right arrow[white]: Move right.\n[yellow]Down arrow[white]: Move down.\n[yellow]Up arrow[white]: Move up.\n[yellow]Ctrl-A, Home[white]: Move to the beginning of the current line.\n[yellow]Ctrl-E, End[white]: Move to the end of the current line.\n[yellow]Ctrl-F, page down[white]: Move down by one page.\n[yellow]Ctrl-B, page up[white]: Move up by one page.\n[yellow]Alt-Up arrow[white]: Scroll the page up.\n[yellow]Alt-Down arrow[white]: Scroll the page down.\n[yellow]Alt-Left arrow[white]: Scroll the page to the left.\n[yellow]Alt-Right arrow[white]:  Scroll the page to the right.\n[yellow]Alt-B, Ctrl-Left arrow[white]: Move back by one word.\n[yellow]Alt-F, Ctrl-Right arrow[white]: Move forward by one word.\n\n[blue]Press Enter for more help, press Escape to return.`)\n\thelp2 := tview.NewTextView().\n\t\tSetDynamicColors(true).\n\t\tSetText(`[green]Editing[white]\n\nType to enter text.\n[yellow]Ctrl-H, Backspace[white]: Delete the left character.\n[yellow]Ctrl-D, Delete[white]: Delete the right character.\n[yellow]Ctrl-K[white]: Delete until the end of the line.\n[yellow]Ctrl-W[white]: Delete the rest of the word.\n[yellow]Ctrl-U[white]: Delete the current line.\n\n[blue]Press Enter for more help, press Escape to return.`)\n\thelp3 := tview.NewTextView().\n\t\tSetDynamicColors(true).\n\t\tSetText(`[green]Selecting Text[white]\n\nMove while holding Shift or drag the mouse.\nDouble-click to select a word.\n[yellow]Ctrl-L[white] to select entire text.\n\n[green]Clipboard\n\n[yellow]Ctrl-Q[white]: Copy.\n[yellow]Ctrl-X[white]: Cut.\n[yellow]Ctrl-V[white]: Paste.\n\t\t\n[green]Undo\n\n[yellow]Ctrl-Z[white]: Undo.\n[yellow]Ctrl-Y[white]: Redo.\n\n[blue]Press Enter for more help, press Escape to return.`)\n\thelp := tview.NewFrame(help1).\n\t\tSetBorders(1, 1, 0, 0, 2, 2)\n\thelp.SetBorder(true).\n\t\tSetTitle(\"Help\").\n\t\tSetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {\n\t\t\tif event.Key() == tcell.KeyEscape {\n\t\t\t\tpages.SwitchToPage(\"main\")\n\t\t\t\treturn nil\n\t\t\t} else if event.Key() == tcell.KeyEnter {\n\t\t\t\tswitch {\n\t\t\t\tcase help.GetPrimitive() == help1:\n\t\t\t\t\thelp.SetPrimitive(help2)\n\t\t\t\tcase help.GetPrimitive() == help2:\n\t\t\t\t\thelp.SetPrimitive(help3)\n\t\t\t\tcase help.GetPrimitive() == help3:\n\t\t\t\t\thelp.SetPrimitive(help1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn event\n\t\t})\n\n\tpages.AddAndSwitchToPage(\"main\", mainView, true).\n\t\tAddPage(\"help\", tview.NewGrid().\n\t\t\tSetColumns(0, 64, 0).\n\t\t\tSetRows(0, 22, 0).\n\t\t\tAddItem(help, 1, 1, 1, 1, 0, 0, true), true, false)\n\n\tapp.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {\n\t\tif event.Key() == tcell.KeyF1 {\n\t\t\tpages.ShowPage(\"help\") //TODO: Check when clicking outside help window with the mouse. Then clicking help again.\n\t\t\treturn nil\n\t\t}\n\t\treturn event\n\t})\n\n\tif err := app.SetRoot(pages, true).EnableMouse(true).EnablePaste(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/textview/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/textview/chat/README.md",
    "content": "![Screenshot](screenshot.png)"
  },
  {
    "path": "demos/textview/chat/chain.txt",
    "content": "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"
  },
  {
    "path": "demos/textview/chat/main.go",
    "content": "// Demo code for a simple chat application using TextView regions.\npackage main\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t_ \"embed\"\n\t\"encoding/base64\"\n\t\"encoding/gob\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\n// MarkovChain implements a simple order of one Markov chain for text\n// generation.\ntype MarkovChain struct {\n\tWords  []string      // All unique words.\n\tStarts []int         // Indexes of starting words in Words.\n\tChain  map[int][]int // Maps word index to possible next word indexes in Words. Ordered by frequency (highest first).\n}\n\nvar (\n\t//go:embed chain.txt\n\tchainData []byte\n\n\t// The Markov chain.\n\tchain MarkovChain\n)\n\nfunc main() {\n\t// Load the Markov chain and generate chat lines.\n\tloadChain()\n\tch := generateChat()\n\n\tapp := tview.NewApplication()\n\n\t// Create a chat view.\n\tchat := tview.NewTextView().\n\t\tSetDynamicColors(true).\n\t\tSetRegions(true).\n\t\tScrollToEnd()\n\n\t// The users will be displayed separately.\n\tusers := tview.NewTextView().\n\t\tSetDynamicColors(true).\n\t\tSetTextAlign(tview.AlignRight).\n\t\tScrollToEnd()\n\n\t// Syncing the scrolling is left to the student as an exercise.\n\tchat.SetInputCapture(func(*tcell.EventKey) *tcell.EventKey {\n\t\treturn nil\n\t}).SetMouseCapture(func(tview.MouseAction, *tcell.EventMouse) (tview.MouseAction, *tcell.EventMouse) {\n\t\treturn tview.MouseConsumed, nil\n\t})\n\tusers.SetInputCapture(func(*tcell.EventKey) *tcell.EventKey {\n\t\treturn nil\n\t}).SetMouseCapture(func(tview.MouseAction, *tcell.EventMouse) (tview.MouseAction, *tcell.EventMouse) {\n\t\treturn tview.MouseConsumed, nil\n\t})\n\n\t// Add chat lines as they are generated.\n\tgo func() {\n\t\tvar (\n\t\t\tmessage     int\n\t\t\tuserColors  = [...]string{\"red\", \"green\", \"yellow\", \"blue\", \"magenta\", \"cyan\"}\n\t\t\tuserToColor = make(map[string]string)\n\t\t)\n\t\tfor line := range ch {\n\t\t\tsleep := 3 * time.Second\n\t\t\tif message < 20 {\n\t\t\t\tsleep = 0\n\t\t\t}\n\t\t\ttime.Sleep(sleep)\n\n\t\t\t// Add the line to the chat view.\n\t\t\tuserName := line[0]\n\t\t\tif _, ok := userToColor[userName]; !ok {\n\t\t\t\tuserToColor[userName] = userColors[len(userToColor)%len(userColors)]\n\t\t\t}\n\t\t\tline = line[1:] // Don't display the user name here.\n\t\t\tfmt.Fprintf(chat, \"[\\\"%d\\\"]%s\\n\\n\", message, strings.Join(line, \" \"))\n\n\t\t\t// Add the user name to the users view. Then redraw everything.\n\t\t\tapp.QueueUpdateDraw(func() {\n\t\t\t\tlines := users.GetWrappedLineCount()\n\t\t\t\tregions := chat.GetRegions(lines, true)\n\t\t\t\tif len(regions) > 0 {\n\t\t\t\t\tlastRegion := regions[len(regions)-1]\n\t\t\t\t\tfmt.Fprintf(users, \"[%s]%s\", userToColor[userName], userName)\n\t\t\t\t\tfor index := lastRegion.StartRow; index <= lastRegion.EndRow; index++ {\n\t\t\t\t\t\tfmt.Fprintln(users)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tmessage++\n\t\t}\n\t}()\n\n\t// Make a layout and start the application.\n\tgrid := tview.NewGrid().\n\t\tSetGap(0, 1).\n\t\tSetColumns(0, 8, 40, 0).\n\t\tAddItem(users, 0, 1, 1, 1, 0, 0, false).\n\t\tAddItem(chat, 0, 2, 1, 1, 0, 0, true)\n\tgrid.SetBorder(true).SetTitle(\"Chat\")\n\n\tif err := app.SetRoot(grid, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// loadChain loads the Markov chain from the embedded base64 data.\nfunc loadChain() {\n\tb := base64.NewDecoder(base64.StdEncoding, bytes.NewReader(chainData))\n\tzr, err := gzip.NewReader(b)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer zr.Close()\n\tg := gob.NewDecoder(zr)\n\tif err := g.Decode(&chain); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// generateChat generates chat lines based on the Markov chain and sends them\n// to a channel. The first word of each line is always the name of a user,\n// followed by a colon.\nfunc generateChat() <-chan []string {\n\tch := make(chan []string)\n\n\tgo func() {\n\t\tdefer close(ch)\n\n\t\tvar lastStart int\n\t\tfor {\n\t\t\tvar line []string\n\n\t\t\t// Pick a random starting word different from the previous one.\n\t\t\tvar word int\n\t\t\tfor {\n\t\t\t\tstart := chain.Starts[rand.Intn(len(chain.Starts))]\n\t\t\t\tif start != lastStart {\n\t\t\t\t\tword = start\n\t\t\t\t\tlastStart = start\n\t\t\t\t\tline = append(line, chain.Words[word])\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Generate the rest of the line.\n\t\t\tfor {\n\t\t\t\t// Pick a random next word.\n\t\t\t\tnext := chain.Chain[word][rand.Intn(len(chain.Chain[word]))]\n\n\t\t\t\t// If we hit the end token, emit the line.\n\t\t\t\tif chain.Words[next] == \"$\" {\n\t\t\t\t\tch <- line\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Otherwise, just add the word and continue.\n\t\t\t\tline = append(line, chain.Words[next])\n\t\t\t\tword = next\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn ch\n}\n"
  },
  {
    "path": "demos/textview/main.go",
    "content": "// Demo code for the TextView primitive.\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nconst corporate = `Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.\n\nBring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring.\n\nCapitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.\n\n[yellow]Press Enter, then Tab/Backtab for word selections`\n\nfunc main() {\n\tapp := tview.NewApplication()\n\ttextView := tview.NewTextView().\n\t\tSetDynamicColors(true).\n\t\tSetRegions(true).\n\t\tSetWordWrap(true).\n\t\tSetChangedFunc(func() {\n\t\t\tapp.Draw()\n\t\t})\n\tnumSelections := 0\n\tgo func() {\n\t\tfor _, word := range strings.Split(corporate, \" \") {\n\t\t\tif word == \"the\" {\n\t\t\t\tword = \"[#ff0000]the[white]\"\n\t\t\t}\n\t\t\tif word == \"to\" {\n\t\t\t\tword = fmt.Sprintf(`[\"%d\"]to[\"\"]`, numSelections)\n\t\t\t\tnumSelections++\n\t\t\t}\n\t\t\tfmt.Fprintf(textView, \"%s \", word)\n\t\t\ttime.Sleep(200 * time.Millisecond)\n\t\t}\n\t}()\n\ttextView.SetDoneFunc(func(key tcell.Key) {\n\t\tcurrentSelection := textView.GetHighlights()\n\t\tif key == tcell.KeyEnter {\n\t\t\tif len(currentSelection) > 0 {\n\t\t\t\ttextView.Highlight()\n\t\t\t} else {\n\t\t\t\ttextView.Highlight(\"0\").ScrollToHighlight()\n\t\t\t}\n\t\t} else if len(currentSelection) > 0 {\n\t\t\tindex, _ := strconv.Atoi(currentSelection[0])\n\t\t\tif key == tcell.KeyTab {\n\t\t\t\tindex = (index + 1) % numSelections\n\t\t\t} else if key == tcell.KeyBacktab {\n\t\t\t\tindex = (index - 1 + numSelections) % numSelections\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttextView.Highlight(strconv.Itoa(index)).ScrollToHighlight()\n\t\t}\n\t})\n\ttextView.SetBorder(true)\n\tif err := app.SetRoot(textView, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/treeview/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/treeview/main.go",
    "content": "// Demo code for the TreeView primitive.\npackage main\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\n// Show a navigable tree view of the current directory.\nfunc main() {\n\trootDir := \".\"\n\troot := tview.NewTreeNode(rootDir).\n\t\tSetColor(tcell.ColorRed)\n\ttree := tview.NewTreeView().\n\t\tSetRoot(root).\n\t\tSetCurrentNode(root)\n\n\t// A helper function which adds the files and directories of the given path\n\t// to the given target node.\n\tadd := func(target *tview.TreeNode, path string) {\n\t\tfiles, err := os.ReadDir(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tfor _, file := range files {\n\t\t\tnode := tview.NewTreeNode(file.Name()).\n\t\t\t\tSetReference(filepath.Join(path, file.Name())).\n\t\t\t\tSetSelectable(file.IsDir())\n\t\t\tif file.IsDir() {\n\t\t\t\tnode.SetColor(tcell.ColorGreen)\n\t\t\t}\n\t\t\ttarget.AddChild(node)\n\t\t}\n\t}\n\n\t// Add the current directory to the root node.\n\tadd(root, rootDir)\n\n\t// If a directory was selected, open it.\n\ttree.SetSelectedFunc(func(node *tview.TreeNode) {\n\t\treference := node.GetReference()\n\t\tif reference == nil {\n\t\t\treturn // Selecting the root node does nothing.\n\t\t}\n\t\tchildren := node.GetChildren()\n\t\tif len(children) == 0 {\n\t\t\t// Load and show files in this directory.\n\t\t\tpath := reference.(string)\n\t\t\tadd(node, path)\n\t\t} else {\n\t\t\t// Collapse if visible, expand if collapsed.\n\t\t\tnode.SetExpanded(!node.IsExpanded())\n\t\t}\n\t})\n\n\tif err := tview.NewApplication().SetRoot(tree, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "demos/unicode/README.md",
    "content": "![Screenshot](screenshot.png)\n"
  },
  {
    "path": "demos/unicode/main.go",
    "content": "// Demo code for unicode support (demonstrates wide Chinese characters).\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/rivo/tview\"\n)\n\nfunc main() {\n\tapp := tview.NewApplication()\n\tpages := tview.NewPages()\n\n\tform := tview.NewForm()\n\tform.AddDropDown(\"称谓\", []string{\"先生\", \"女士\", \"博士\", \"老师\", \"师傅\"}, 0, nil).\n\t\tAddInputField(\"姓名\", \"\", 20, nil, nil).\n\t\tAddCheckbox(\"年龄 18+\", false, nil).\n\t\tAddPasswordField(\"密码\", \"\", 10, '*', nil).\n\t\tAddButton(\"保存\", func() {\n\t\t\t_, title := form.GetFormItem(0).(*tview.DropDown).GetCurrentOption()\n\t\t\tuserName := form.GetFormItem(1).(*tview.InputField).GetText()\n\n\t\t\talert(pages, \"alert-dialog\", fmt.Sprintf(\"保存成功，%s %s！\", userName, title))\n\t\t}).\n\t\tAddButton(\"退出\", func() {\n\t\t\tapp.Stop()\n\t\t})\n\tform.SetBorder(true).SetTitle(\"输入一些内容\").SetTitleAlign(tview.AlignLeft)\n\tpages.AddPage(\"base\", form, true, true)\n\n\tif err := app.SetRoot(pages, true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// alert shows a confirmation dialog.\nfunc alert(pages *tview.Pages, id string, message string) *tview.Pages {\n\treturn pages.AddPage(\n\t\tid,\n\t\ttview.NewModal().\n\t\t\tSetText(message).\n\t\t\tAddButtons([]string{\"确定\"}).\n\t\t\tSetDoneFunc(func(buttonIndex int, buttonLabel string) {\n\t\t\t\tpages.HidePage(id).RemovePage(id)\n\t\t\t}),\n\t\tfalse,\n\t\ttrue,\n\t)\n}\n"
  },
  {
    "path": "doc.go",
    "content": "/*\nPackage tview implements rich widgets for terminal based user interfaces. The\nwidgets provided with this package are useful for data exploration and data\nentry.\n\n# Widgets\n\nThe package implements the following widgets:\n\n  - [TextView]: A scrollable window that display multi-colored text. Text may\n    also be highlighted.\n  - [TextArea]: An editable multi-line text area.\n  - [Table]: A scrollable display of tabular data. Table cells, rows, or columns\n    may also be highlighted.\n  - [TreeView]: A scrollable display for hierarchical data. Tree nodes can be\n    highlighted, collapsed, expanded, and more.\n  - [List]: A navigable text list with optional keyboard shortcuts.\n  - [InputField]: One-line input fields to enter text.\n  - [DropDown]: Drop-down selection fields.\n  - [Checkbox]: Selectable checkbox for boolean values.\n  - [Image]: Displays images.\n  - [Button]: Buttons which get activated when the user selects them.\n  - [Form]: Forms composed of input fields, drop down selections, checkboxes,\n    and buttons.\n  - [Modal]: A centered window with a text message and one or more buttons.\n  - [Grid]: A grid based layout manager.\n  - [Flex]: A Flexbox based layout manager.\n  - [Pages]: A page based layout manager.\n\nThe package also provides Application which is used to poll the event queue and\ndraw widgets on screen.\n\n# Hello World\n\nThe following is a very basic example showing a box with the title \"Hello,\nworld!\":\n\n\tpackage main\n\n\timport (\n\t\t\"github.com/rivo/tview\"\n\t)\n\n\tfunc main() {\n\t\tbox := tview.NewBox().SetBorder(true).SetTitle(\"Hello, world!\")\n\t\tif err := tview.NewApplication().SetRoot(box, true).Run(); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\nFirst, we create a box primitive with a border and a title. Then we create an\napplication, set the box as its root primitive, and run the event loop. The\napplication exits when the application's [Application.Stop] function is called\nor when Ctrl-C is pressed.\n\n# More Demos\n\nYou will find more demos in the \"demos\" subdirectory. It also contains a\npresentation (written using tview) which gives an overview of the different\nwidgets and how they can be used.\n\n# Styles, Colors, and Hyperlinks\n\nThroughout this package, styles are specified using the [tcell.Style] type.\nStyles specify colors with the [tcell.Color] type. Functions such as\n[tcell.GetColor], [tcell.NewHexColor], and [tcell.NewRGBColor] can be used to\ncreate colors from W3C color names or RGB values. The [tcell.Style] type also\nallows you to specify text attributes such as \"bold\" or \"italic\" or a URL\nwhich some terminals use to display hyperlinks.\n\nAlmost all strings which are displayed may contain style tags. A style tag's\ncontent is always wrapped in square brackets. In its simplest form, a style tag\nspecifies the foreground color of the text. Colors in these tags are W3C color\nnames or six hexadecimal digits following a hash tag. Examples:\n\n\tThis is a [red]warning[white]!\n\tThe sky is [#8080ff]blue[#ffffff].\n\nA style tag changes the style of the characters following that style tag. There\nis no style stack and no nesting of style tags.\n\nStyle tags are used in almost everything from box titles, list text, form item\nlabels, to table cells. In a [TextView], this functionality has to be switched\non explicitly. See the [TextView] documentation for more information.\n\nA style tag's full format looks like this:\n\n\t[<foreground>:<background>:<attribute flags>:<url>]\n\nEach of the four fields can be left blank and trailing fields can be omitted.\n(Empty square brackets \"[]\", however, are not considered style tags.) Fields\nthat are not specified will be left unchanged. A field with just a dash (\"-\")\nmeans \"reset to default\".\n\nYou can specify the following flags to turn on certain attributes (some flags\nmay not be supported by your terminal):\n\n\tl: blink\n\tb: bold\n\ti: italic\n\td: dim\n\tr: reverse (switch foreground and background color)\n\tu: underline\n\ts: strike-through\n\nUse uppercase letters to turn off the corresponding attribute, for example,\n\"B\" to turn off bold. Uppercase letters have no effect if the attribute was not\npreviously set.\n\nSetting a URL allows you to turn a piece of text into a hyperlink in some\nterminals. Specify a dash (\"-\") to specify the end of the hyperlink. Hyperlinks\nmust only contain single-byte characters (e.g. ASCII) and they may not contain\nbracket characters (\"[\" or \"]\").\n\nExamples:\n\n\t[yellow]Yellow text\n\t[yellow:red]Yellow text on red background\n\t[:red]Red background, text color unchanged\n\t[yellow::u]Yellow text underlined\n\t[::bl]Bold, blinking text\n\t[::-]Colors unchanged, flags reset\n\t[-]Reset foreground color\n\t[::i]Italic and [::I]not italic\n\tClick [:::https://example.com]here[:::-] for example.com.\n\tSend an email to [:::mailto:her@example.com]her/[:::mail:him@example.com]him/[:::mail:them@example.com]them[:::-].\n\t[-:-:-:-]Reset everything\n\t[:]No effect\n\t[]Not a valid style tag, will print square brackets as they are\n\nIn the rare event that you want to display a string such as \"[red]\" or\n\"[#00ff1a]\" without applying its effect, you need to put an opening square\nbracket before the closing square bracket. Note that the text inside the\nbrackets will be matched less strictly than region or colors tags. I.e. any\ncharacter that may be used in color or region tags will be recognized. Examples:\n\n\t[red[]      will be output as [red]\n\t[\"123\"[]    will be output as [\"123\"]\n\t[#6aff00[[] will be output as [#6aff00[]\n\t[a#\"[[[]    will be output as [a#\"[[]\n\t[]          will be output as [] (see style tags above)\n\t[[]         will be output as [[] (not an escaped tag)\n\nYou can use the Escape() function to insert brackets automatically where needed.\n\n# Styles\n\nWhen primitives are instantiated, they are initialized with colors taken from\nthe global [Styles] variable. You may change this variable to adapt the look and\nfeel of the primitives to your preferred style.\n\nNote that most terminals will not report information about their color theme.\nThis package therefore does not support using the terminal's color theme. The\ndefault style is a dark theme and you must change the [Styles] variable to\nswitch to a light (or other) theme.\n\n# Unicode Support\n\nThis package supports all unicode characters supported by your terminal.\n\n# Mouse Support\n\nIf your terminal supports mouse events, you can enable mouse support for your\napplication by calling [Application.EnableMouse]. Note that this may interfere\nwith your terminal's default mouse behavior. Mouse support is disabled by\ndefault.\n\n# Concurrency\n\nMany functions in this package are not thread-safe. For many applications, this\nis not an issue: If your code makes changes in response to key events, the\ncorresponding callback function will execute in the main goroutine and thus will\nnot cause any race conditions. (Exceptions to this are documented.)\n\nIf you access your primitives from other goroutines, however, you will need to\nsynchronize execution. The easiest way to do this is to call\n[Application.QueueUpdate] or [Application.QueueUpdateDraw] (see the function\ndocumentation for details):\n\n\tgo func() {\n\t  app.QueueUpdateDraw(func() {\n\t    table.SetCellSimple(0, 0, \"Foo bar\")\n\t  })\n\t}()\n\nOne exception to this is the io.Writer interface implemented by [TextView]. You\ncan safely write to a [TextView] from any goroutine. See the [TextView]\ndocumentation for details.\n\nYou can also call [Application.Draw] from any goroutine without having to wrap\nit in [Application.QueueUpdate]. And, as mentioned above, key event callbacks\nare executed in the main goroutine and thus should not use\n[Application.QueueUpdate] as that may lead to deadlocks. It is also not\nnecessary to call [Application.Draw] from such callbacks as it will be called\nautomatically.\n\n# Type Hierarchy\n\nAll widgets listed above contain the [Box] type. All of [Box]'s functions are\ntherefore available for all widgets, too. Please note that if you are using the\nfunctions of [Box] on a subclass, they will return a *Box, not the subclass.\nThis is a Golang limitation. So while tview supports method chaining in many\nplaces, these chains must be broken when using [Box]'s functions. Example:\n\n\t// This will cause \"textArea\" to be an empty Box.\n\ttextArea := tview.NewTextArea().\n\t\tSetMaxLength(256).\n\t\tSetPlaceholder(\"Enter text here\").\n\t\tSetBorder(true)\n\nYou will need to call [Box.SetBorder] separately:\n\n\ttextArea := tview.NewTextArea().\n\t\tSetMaxLength(256).\n\t\tSetPlaceholder(\"Enter text here\")\n\ttexArea.SetBorder(true)\n\nAll widgets also implement the [Primitive] interface.\n\nThe tview package's rendering is based on version 2 of\nhttps://github.com/gdamore/tcell. It uses types and constants from that package\n(e.g. colors, styles, and keyboard values).\n*/\npackage tview\n"
  },
  {
    "path": "dropdown.go",
    "content": "package tview\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// dropDownOption is one option that can be selected in a drop-down primitive.\ntype dropDownOption struct {\n\tText     string // The text to be displayed in the drop-down.\n\tSelected func() // The (optional) callback for when this option was selected.\n}\n\n// DropDown implements a selection widget whose options become visible in a\n// drop-down list when activated.\n//\n// See https://github.com/rivo/tview/wiki/DropDown for an example.\ntype DropDown struct {\n\t*Box\n\n\t// Whether or not this drop-down is disabled/read-only.\n\tdisabled bool\n\n\t// The options from which the user can choose.\n\toptions []*dropDownOption\n\n\t// Strings to be placed before and after each drop-down option.\n\toptionPrefix, optionSuffix string\n\n\t// The index of the currently selected option. Negative if no option is\n\t// currently selected.\n\tcurrentOption int\n\n\t// Strings to be placed before and after the current option.\n\tcurrentOptionPrefix, currentOptionSuffix string\n\n\t// The text to be displayed when no option has yet been selected.\n\tnoSelection string\n\n\t// Set to true if the options are visible and selectable.\n\topen bool\n\n\t// The input field containing the entered prefix for the current selection.\n\t// This is only visible when the drop-down is open. It never receives focus,\n\t// however. And it only receives events, we never call its Draw method.\n\tprefix *InputField\n\n\t// The list element for the options.\n\tlist *List\n\n\t// The text to be displayed before the input area.\n\tlabel string\n\n\t// The label style.\n\tlabelStyle tcell.Style\n\n\t// The field style.\n\tfieldStyle tcell.Style\n\n\t// The style of the field when it is focused and the drop-down is closed.\n\tfocusedStyle tcell.Style\n\n\t// The style of the field when it is disabled.\n\tdisabledStyle tcell.Style\n\n\t// The style of the prefix.\n\tprefixStyle tcell.Style\n\n\t// The screen width of the label area. A value of 0 means use the width of\n\t// the label text.\n\tlabelWidth int\n\n\t// The screen width of the input area. A value of 0 means extend as much as\n\t// possible.\n\tfieldWidth int\n\n\t// An optional function which is called when the user indicated that they\n\t// are done selecting options. The key which was pressed is provided (tab,\n\t// shift-tab, or escape).\n\tdone func(tcell.Key)\n\n\t// A callback function set by the Form class and called when the user leaves\n\t// this form item.\n\tfinished func(tcell.Key)\n\n\t// A callback function which is called when the user changes the drop-down's\n\t// selection.\n\tselected func(text string, index int)\n\n\tdragging bool // Set to true when mouse dragging is in progress.\n}\n\n// NewDropDown returns a new [DropDown].\nfunc NewDropDown() *DropDown {\n\tlist := NewList()\n\tlist.ShowSecondaryText(false).\n\t\tSetMainTextStyle(tcell.StyleDefault.Background(Styles.MoreContrastBackgroundColor).Foreground(Styles.PrimitiveBackgroundColor)).\n\t\tSetSelectedStyle(tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.PrimitiveBackgroundColor)).\n\t\tSetHighlightFullLine(true).\n\t\tSetBackgroundColor(Styles.MoreContrastBackgroundColor)\n\n\tprefix := NewInputField()\n\n\tbox := NewBox()\n\td := &DropDown{\n\t\tBox:           box,\n\t\tcurrentOption: -1,\n\t\tlist:          list,\n\t\tprefix:        prefix,\n\t\tlabelStyle:    tcell.StyleDefault.Foreground(Styles.SecondaryTextColor),\n\t\tfieldStyle:    tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),\n\t\tfocusedStyle:  tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.ContrastBackgroundColor),\n\t\tdisabledStyle: tcell.StyleDefault.Background(box.backgroundColor).Foreground(Styles.SecondaryTextColor),\n\t\tprefixStyle:   tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.ContrastBackgroundColor),\n\t}\n\n\td.Box.Primitive = d\n\treturn d\n}\n\n// SetCurrentOption sets the index of the currently selected option. This may\n// be a negative value to indicate that no option is currently selected. Calling\n// this function will also trigger the \"selected\" callback (if there is one).\nfunc (d *DropDown) SetCurrentOption(index int) *DropDown {\n\tif index >= 0 && index < len(d.options) {\n\t\td.currentOption = index\n\t\td.list.SetCurrentItem(index)\n\t\tif d.selected != nil {\n\t\t\td.selected(d.options[index].Text, index)\n\t\t}\n\t\tif d.options[index].Selected != nil {\n\t\t\td.options[index].Selected()\n\t\t}\n\t} else {\n\t\td.currentOption = -1\n\t\td.list.SetCurrentItem(0) // Set to 0 because -1 means \"last item\".\n\t\tif d.selected != nil {\n\t\t\td.selected(\"\", -1)\n\t\t}\n\t}\n\treturn d\n}\n\n// GetCurrentOption returns the index of the currently selected option as well\n// as its text. If no option was selected, -1 and an empty string is returned.\nfunc (d *DropDown) GetCurrentOption() (int, string) {\n\tvar text string\n\tif d.currentOption >= 0 && d.currentOption < len(d.options) {\n\t\ttext = d.options[d.currentOption].Text\n\t}\n\treturn d.currentOption, text\n}\n\n// SetTextOptions sets the text to be placed before and after each drop-down\n// option (prefix/suffix), the text placed before and after the currently\n// selected option (currentPrefix/currentSuffix) as well as the text to be\n// displayed when no option is currently selected. Per default, all of these\n// strings are empty.\nfunc (d *DropDown) SetTextOptions(prefix, suffix, currentPrefix, currentSuffix, noSelection string) *DropDown {\n\td.currentOptionPrefix = currentPrefix\n\td.currentOptionSuffix = currentSuffix\n\td.noSelection = noSelection\n\td.optionPrefix = prefix\n\td.optionSuffix = suffix\n\tfor index := 0; index < d.list.GetItemCount(); index++ {\n\t\td.list.SetItemText(index, prefix+d.options[index].Text+suffix, \"\")\n\t}\n\treturn d\n}\n\n// SetUseStyleTags sets a flag that determines whether tags found in the option\n// texts are interpreted as tview tags. By default, this flag is enabled (for\n// backwards compatibility reasons).\nfunc (d *DropDown) SetUseStyleTags(useStyleTags bool) *DropDown {\n\td.list.SetUseStyleTags(useStyleTags, useStyleTags)\n\treturn d\n}\n\n// SetLabel sets the text to be displayed before the input area.\nfunc (d *DropDown) SetLabel(label string) *DropDown {\n\td.label = label\n\treturn d\n}\n\n// GetLabel returns the text to be displayed before the input area.\nfunc (d *DropDown) GetLabel() string {\n\treturn d.label\n}\n\n// SetLabelWidth sets the screen width of the label. A value of 0 will cause the\n// primitive to use the width of the label string.\nfunc (d *DropDown) SetLabelWidth(width int) *DropDown {\n\td.labelWidth = width\n\treturn d\n}\n\n// SetLabelColor sets the color of the label.\nfunc (d *DropDown) SetLabelColor(color tcell.Color) *DropDown {\n\td.labelStyle = d.labelStyle.Foreground(color)\n\treturn d\n}\n\n// SetLabelStyle sets the style of the label.\nfunc (d *DropDown) SetLabelStyle(style tcell.Style) *DropDown {\n\td.labelStyle = style\n\treturn d\n}\n\n// SetFieldBackgroundColor sets the background color of the selected field.\n// This also overrides the prefix background color.\nfunc (d *DropDown) SetFieldBackgroundColor(color tcell.Color) *DropDown {\n\td.fieldStyle = d.fieldStyle.Background(color)\n\td.prefix.SetFieldBackgroundColor(color)\n\treturn d\n}\n\n// SetFieldTextColor sets the text color of the options area.\nfunc (d *DropDown) SetFieldTextColor(color tcell.Color) *DropDown {\n\td.fieldStyle = d.fieldStyle.Foreground(color)\n\treturn d\n}\n\n// SetFieldStyle sets the style of the options area.\nfunc (d *DropDown) SetFieldStyle(style tcell.Style) *DropDown {\n\td.fieldStyle = style\n\treturn d\n}\n\n// SetFocusedStyle sets the style of the options area when the drop-down is\n// focused and closed.\nfunc (d *DropDown) SetFocusedStyle(style tcell.Style) *DropDown {\n\td.focusedStyle = style\n\treturn d\n}\n\n// SetDisabledStyle sets the style of the options area when the drop-down is\n// disabled.\nfunc (d *DropDown) SetDisabledStyle(style tcell.Style) *DropDown {\n\td.disabledStyle = style\n\treturn d\n}\n\n// SetPrefixTextColor sets the color of the prefix string. The prefix string is\n// shown when the user starts typing text, which directly selects the first\n// option that starts with the typed string.\nfunc (d *DropDown) SetPrefixTextColor(color tcell.Color) *DropDown {\n\td.prefixStyle = d.prefixStyle.Foreground(color)\n\treturn d\n}\n\n// SetPrefixStyle sets the style of the prefix string. The prefix string is\n// shown when the user starts typing text, which directly selects the first\n// option that starts with the typed string.\nfunc (d *DropDown) SetPrefixStyle(style tcell.Style) *DropDown {\n\td.prefixStyle = style\n\treturn d\n}\n\n// SetListStyles sets the styles of the items in the drop-down list (unselected\n// as well as selected items). Style attributes are currently ignored but may be\n// used in the future.\nfunc (d *DropDown) SetListStyles(unselected, selected tcell.Style) *DropDown {\n\td.list.SetMainTextStyle(unselected).SetSelectedStyle(selected)\n\t_, bg, _ := unselected.Decompose()\n\td.list.SetBackgroundColor(bg)\n\treturn d\n}\n\n// SetFormAttributes sets attributes shared by all form items.\nfunc (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {\n\td.labelWidth = labelWidth\n\td.SetLabelColor(labelColor)\n\td.SetBackgroundColor(bgColor)\n\td.SetFieldStyle(tcell.StyleDefault.Foreground(fieldTextColor).Background(fieldBgColor))\n\treturn d\n}\n\n// SetFieldWidth sets the screen width of the options area. A value of 0 means\n// extend to as long as the longest option text.\nfunc (d *DropDown) SetFieldWidth(width int) *DropDown {\n\td.fieldWidth = width\n\treturn d\n}\n\n// GetFieldWidth returns this primitive's field screen width.\nfunc (d *DropDown) GetFieldWidth() int {\n\tif d.fieldWidth > 0 {\n\t\treturn d.fieldWidth\n\t}\n\tfieldWidth := 0\n\tfor _, option := range d.options {\n\t\twidth := TaggedStringWidth(option.Text)\n\t\tif width > fieldWidth {\n\t\t\tfieldWidth = width\n\t\t}\n\t}\n\treturn fieldWidth\n}\n\n// GetFieldHeight returns this primitive's field height.\nfunc (d *DropDown) GetFieldHeight() int {\n\treturn 1\n}\n\n// SetDisabled sets whether or not the item is disabled / read-only.\nfunc (d *DropDown) SetDisabled(disabled bool) FormItem {\n\td.disabled = disabled\n\tif d.finished != nil {\n\t\td.finished(-1)\n\t}\n\treturn d\n}\n\n// GetDisabled returns whether or not the item is disabled / read-only.\nfunc (d *DropDown) GetDisabled() bool {\n\treturn d.disabled\n}\n\n// AddOption adds a new selectable option to this drop-down. The \"selected\"\n// callback is called when this option was selected. It may be nil.\nfunc (d *DropDown) AddOption(text string, selected func()) *DropDown {\n\td.options = append(d.options, &dropDownOption{Text: text, Selected: selected})\n\td.list.AddItem(d.optionPrefix+text+d.optionSuffix, \"\", 0, nil)\n\treturn d\n}\n\n// SetOptions replaces all current options with the ones provided and installs\n// one callback function which is called when one of the options is selected.\n// It will be called with the option's text and its index into the options\n// slice. The \"selected\" parameter may be nil.\nfunc (d *DropDown) SetOptions(texts []string, selected func(text string, index int)) *DropDown {\n\td.list.Clear()\n\td.options = nil\n\tfor _, text := range texts {\n\t\td.AddOption(text, nil)\n\t}\n\td.selected = selected\n\treturn d\n}\n\n// GetOptionCount returns the number of options in the drop-down.\nfunc (d *DropDown) GetOptionCount() int {\n\treturn len(d.options)\n}\n\n// RemoveOption removes the specified option from the drop-down. Panics if the\n// index is out of range. If the currently selected option is removed, no option\n// will be selected.\nfunc (d *DropDown) RemoveOption(index int) *DropDown {\n\tif index == d.currentOption {\n\t\td.currentOption = -1\n\t}\n\td.options = append(d.options[:index], d.options[index+1:]...)\n\td.list.RemoveItem(index)\n\treturn d\n}\n\n// SetSelectedFunc sets a handler which is called when the user changes the\n// drop-down's option. This handler will be called in addition and prior to\n// an option's optional individual handler. The handler is provided with the\n// selected option's text and index. If \"no option\" was selected, these values\n// are an empty string and -1.\nfunc (d *DropDown) SetSelectedFunc(handler func(text string, index int)) *DropDown {\n\td.selected = handler\n\treturn d\n}\n\n// SetDoneFunc sets a handler which is called when the user is done selecting\n// options. The callback function is provided with the key that was pressed,\n// which is one of the following:\n//\n//   - KeyEscape: Abort selection.\n//   - KeyTab: Move to the next field.\n//   - KeyBacktab: Move to the previous field.\nfunc (d *DropDown) SetDoneFunc(handler func(key tcell.Key)) *DropDown {\n\td.done = handler\n\treturn d\n}\n\n// SetFinishedFunc sets a callback invoked when the user leaves this form item.\nfunc (d *DropDown) SetFinishedFunc(handler func(key tcell.Key)) FormItem {\n\td.finished = handler\n\treturn d\n}\n\n// Draw draws this primitive onto the screen.\nfunc (d *DropDown) Draw(screen tcell.Screen) {\n\td.Box.DrawForSubclass(screen, d)\n\n\t// Prepare.\n\tx, y, width, height := d.GetInnerRect()\n\trightLimit := x + width\n\tif height < 1 || rightLimit <= x {\n\t\treturn\n\t}\n\tuseStyleTags, _ := d.list.GetUseStyleTags()\n\n\t// Draw label.\n\tif d.labelWidth > 0 {\n\t\tlabelWidth := d.labelWidth\n\t\tif labelWidth > rightLimit-x {\n\t\t\tlabelWidth = rightLimit - x\n\t\t}\n\t\tprintWithStyle(screen, d.label, x, y, 0, labelWidth, AlignLeft, d.labelStyle, true)\n\t\tx += labelWidth\n\t} else {\n\t\t_, _, drawnWidth := printWithStyle(screen, d.label, x, y, 0, rightLimit-x, AlignLeft, d.labelStyle, true)\n\t\tx += drawnWidth\n\t}\n\n\t// What's the longest option text?\n\tmaxWidth := 0\n\tfor _, option := range d.options {\n\t\tstr := d.optionPrefix + option.Text + d.optionSuffix\n\t\tif !useStyleTags {\n\t\t\tstr = Escape(str)\n\t\t}\n\t\tstrWidth := TaggedStringWidth(str)\n\t\tif strWidth > maxWidth {\n\t\t\tmaxWidth = strWidth\n\t\t}\n\t\tstr = d.currentOptionPrefix + option.Text + d.currentOptionSuffix\n\t\tif !useStyleTags {\n\t\t\tstr = Escape(str)\n\t\t}\n\t\tstrWidth = TaggedStringWidth(str)\n\t\tif strWidth > maxWidth {\n\t\t\tmaxWidth = strWidth\n\t\t}\n\t}\n\n\t// Draw selection area.\n\tfieldWidth := d.fieldWidth\n\tif fieldWidth == 0 {\n\t\tfieldWidth = maxWidth\n\t\tif d.currentOption < 0 {\n\t\t\tnoSelectionWidth := TaggedStringWidth(d.noSelection)\n\t\t\tif noSelectionWidth > fieldWidth {\n\t\t\t\tfieldWidth = noSelectionWidth\n\t\t\t}\n\t\t} else if d.currentOption < len(d.options) {\n\t\t\tcurrentOptionWidth := TaggedStringWidth(d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix)\n\t\t\tif currentOptionWidth > fieldWidth {\n\t\t\t\tfieldWidth = currentOptionWidth\n\t\t\t}\n\t\t}\n\t}\n\tif rightLimit-x < fieldWidth {\n\t\tfieldWidth = rightLimit - x\n\t}\n\tfieldStyle := d.fieldStyle\n\tif d.disabled {\n\t\tfieldStyle = d.disabledStyle\n\t} else if d.HasFocus() && !d.open {\n\t\tfieldStyle = d.focusedStyle\n\t}\n\tfor index := 0; index < fieldWidth; index++ {\n\t\tscreen.SetContent(x+index, y, ' ', nil, fieldStyle)\n\t}\n\n\t// Draw selected text.\n\tprefix := Escape(d.prefix.GetText())\n\tif d.HasFocus() && d.open && len(prefix) > 0 {\n\t\t// The drop-down is open and we have an input prefix.\n\t\t//  Draw current option prefix first.\n\t\tcurrentOptionPrefix := d.currentOptionPrefix\n\t\tcurrentOptionSuffix := d.currentOptionSuffix\n\t\tif !useStyleTags {\n\t\t\tcurrentOptionPrefix = Escape(currentOptionPrefix)\n\t\t\tcurrentOptionSuffix = Escape(currentOptionSuffix)\n\t\t}\n\t\t_, _, copWidth := printWithStyle(screen, currentOptionPrefix, x, y, 0, fieldWidth, AlignLeft, d.fieldStyle, false)\n\t\tif copWidth < fieldWidth {\n\t\t\t// Then draw the prefix.\n\t\t\t_, _, prefixWidth := printWithStyle(screen, prefix, x+copWidth, y, 0, fieldWidth-copWidth, AlignLeft, d.prefixStyle, false)\n\t\t\tif copWidth+prefixWidth < fieldWidth {\n\t\t\t\t// Then the current option remainder.\n\t\t\t\tvar corWidth int\n\t\t\t\tcurrentItem := d.list.GetCurrentItem()\n\t\t\t\tif currentItem >= 0 && currentItem < len(d.options) {\n\t\t\t\t\ttext := d.options[currentItem].Text\n\t\t\t\t\tif !useStyleTags {\n\t\t\t\t\t\ttext = Escape(text)\n\t\t\t\t\t}\n\t\t\t\t\t_, _, corWidth = printWithStyle(screen, text, x+copWidth+prefixWidth, y, prefixWidth, fieldWidth-copWidth-prefixWidth, AlignLeft, d.fieldStyle, false)\n\t\t\t\t}\n\t\t\t\tif copWidth+prefixWidth+corWidth < fieldWidth {\n\t\t\t\t\t// And finally the current option suffix.\n\t\t\t\t\tprintWithStyle(screen, currentOptionSuffix, x+copWidth+prefixWidth+corWidth, y, 0, fieldWidth-copWidth-prefixWidth-corWidth, AlignLeft, d.fieldStyle, false)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// The drop-down is closed. Just draw the selected option.\n\t\ttext := d.noSelection\n\t\tif d.currentOption >= 0 && d.currentOption < len(d.options) {\n\t\t\ttext = d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix\n\t\t}\n\t\tif !useStyleTags {\n\t\t\ttext = Escape(text)\n\t\t}\n\t\tprintWithStyle(screen, text, x, y, 0, fieldWidth, AlignLeft, fieldStyle, false)\n\t}\n\n\t// Draw options list.\n\tif d.HasFocus() && d.open {\n\t\tlx := x\n\t\tly := y + 1\n\t\tlwidth := maxWidth\n\t\tlheight := len(d.options)\n\t\tswidth, sheight := screen.Size()\n\t\t// We prefer to align the left sides of the list and the main widget, but\n\t\t// if there is no space to the right, then shift the list to the left.\n\t\tif lx+lwidth >= swidth {\n\t\t\tlx = swidth - lwidth\n\t\t\tif lx < 0 {\n\t\t\t\tlx = 0\n\t\t\t}\n\t\t}\n\t\t// We prefer to drop down but if there is no space, maybe drop up?\n\t\tif ly+lheight >= sheight && ly-2 > lheight-ly {\n\t\t\tly = y - lheight\n\t\t\tif ly < 0 {\n\t\t\t\tly = 0\n\t\t\t}\n\t\t}\n\t\tif ly+lheight >= sheight {\n\t\t\tlheight = sheight - ly\n\t\t}\n\t\td.list.SetRect(lx, ly, lwidth, lheight)\n\t\td.list.Draw(screen)\n\t}\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tif d.disabled {\n\t\t\treturn\n\t\t}\n\n\t\t// Process key event.\n\t\tswitch key := event.Key(); key {\n\t\tcase tcell.KeyDown, tcell.KeyUp, tcell.KeyHome, tcell.KeyEnd, tcell.KeyPgDn, tcell.KeyPgUp:\n\t\t\t// Open the list and forward the event to it.\n\t\t\td.openList(setFocus)\n\t\t\tif handler := d.list.InputHandler(); handler != nil {\n\t\t\t\thandler(event, setFocus)\n\t\t\t}\n\t\t\td.prefix.SetText(\"\")\n\t\tcase tcell.KeyEnter:\n\t\t\t// If the list is closed, open it. Otherwise, forward the event to\n\t\t\t// it.\n\t\t\tif !d.open {\n\t\t\t\td.openList(setFocus)\n\t\t\t} else if handler := d.list.InputHandler(); handler != nil {\n\t\t\t\thandler(event, setFocus)\n\t\t\t}\n\t\tcase tcell.KeyEscape, tcell.KeyTab, tcell.KeyBacktab:\n\t\t\t// Done selecting.\n\t\t\tif d.done != nil {\n\t\t\t\td.done(key)\n\t\t\t}\n\t\t\tif d.finished != nil {\n\t\t\t\td.finished(key)\n\t\t\t}\n\t\t\td.closeList(setFocus)\n\t\tdefault:\n\t\t\t// Pass other key events to the input field.\n\t\t\tif handler := d.prefix.InputHandler(); handler != nil {\n\t\t\t\thandler(event, setFocus)\n\t\t\t}\n\t\t\td.evalPrefix()\n\t\t\td.openList(setFocus)\n\t\t}\n\t})\n}\n\n// evalPrefix selects an item in the drop-down list based on the current prefix.\nfunc (d *DropDown) evalPrefix() {\n\tprefix := strings.ToLower(d.prefix.GetText())\n\tif len(prefix) == 0 {\n\t\treturn\n\t}\n\tuseStyleTags, _ := d.list.GetUseStyleTags()\n\tfor index, option := range d.options {\n\t\ttext := option.Text\n\t\tif useStyleTags {\n\t\t\ttext = stripTags(text)\n\t\t}\n\t\tif strings.HasPrefix(strings.ToLower(text), prefix) {\n\t\t\td.list.SetCurrentItem(index)\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// openList hands control over to the embedded List primitive.\nfunc (d *DropDown) openList(setFocus func(Primitive)) {\n\tif d.open {\n\t\treturn\n\t}\n\n\td.open = true\n\n\td.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) {\n\t\tif d.dragging {\n\t\t\treturn // If we're dragging the mouse, we don't want to trigger any events.\n\t\t}\n\n\t\t// An option was selected. Close the list again.\n\t\td.currentOption = index\n\t\td.closeList(setFocus)\n\n\t\t// Clear the prefix input field.\n\t\td.prefix.SetText(\"\")\n\n\t\t// Trigger \"selected\" event.\n\t\tcurrentOption := d.options[d.currentOption]\n\t\tif d.selected != nil {\n\t\t\td.selected(currentOption.Text, d.currentOption)\n\t\t}\n\t\tif currentOption.Selected != nil {\n\t\t\tcurrentOption.Selected()\n\t\t}\n\t}).SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {\n\t\tswitch key := event.Key(); key {\n\t\tcase tcell.KeyDown, tcell.KeyUp, tcell.KeyPgDn, tcell.KeyPgUp, tcell.KeyHome, tcell.KeyEnd, tcell.KeyEnter: // Basic list navigation.\n\t\t\tbreak\n\t\tcase tcell.KeyEscape: // Abort selection.\n\t\t\td.closeList(setFocus)\n\t\t\treturn nil\n\t\tdefault: // All other keys are passed to the input field.\n\t\t\tif handler := d.prefix.InputHandler(); handler != nil {\n\t\t\t\thandler(event, setFocus)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\treturn event\n\t})\n\n\tsetFocus(d.list)\n}\n\n// closeList closes the embedded List element by hiding it and removing focus\n// from it.\nfunc (d *DropDown) closeList(setFocus func(Primitive)) {\n\tif !d.open {\n\t\treturn\n\t}\n\td.open = false\n\tif d.list.HasFocus() {\n\t\tsetFocus(d)\n\t}\n}\n\n// IsOpen returns true if the drop-down list is currently open.\nfunc (d *DropDown) IsOpen() bool {\n\treturn d.open\n}\n\n// Focus is called by the application when the primitive receives focus.\nfunc (d *DropDown) Focus(delegate func(p Primitive)) {\n\t// If we're part of a form and this item is disabled, there's nothing the\n\t// user can do here so we're finished.\n\tif d.finished != nil && d.disabled {\n\t\td.finished(-1)\n\t\treturn\n\t}\n\n\tif d.open {\n\t\tdelegate(d.list)\n\t} else {\n\t\td.Box.Focus(delegate)\n\t}\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (d *DropDown) focusChain(chain *[]Primitive) bool {\n\tif d.open {\n\t\tif hasFocus := d.list.focusChain(chain); hasFocus {\n\t\t\tif chain != nil {\n\t\t\t\t*chain = append(*chain, d)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\treturn d.Box.focusChain(chain)\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (d *DropDown) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn d.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif d.disabled {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Was the mouse event in the drop-down box itself (or on its label)?\n\t\tx, y := event.Position()\n\t\tinRect := d.InInnerRect(x, y)\n\t\tif !d.open && !inRect {\n\t\t\treturn d.InRect(x, y), nil // No, and it's not expanded either. Ignore.\n\t\t}\n\n\t\t// As long as the drop-down is open, we capture all mouse events.\n\t\tif d.open {\n\t\t\tcapture = d\n\t\t}\n\n\t\tswitch action {\n\t\tcase MouseLeftDown:\n\t\t\tconsumed = d.open || inRect\n\t\t\tcapture = d\n\t\t\tif !d.open {\n\t\t\t\td.openList(setFocus)\n\t\t\t\td.dragging = true\n\t\t\t} else if consumed, _ := d.list.MouseHandler()(MouseLeftClick, event, setFocus); !consumed {\n\t\t\t\td.closeList(setFocus) // Close drop-down if clicked outside of it.\n\t\t\t}\n\t\tcase MouseMove:\n\t\t\tif d.dragging {\n\t\t\t\t// We pretend it's a left click so we can see the selection during\n\t\t\t\t// dragging. Because we don't act upon it, it's not a problem.\n\t\t\t\td.list.MouseHandler()(MouseLeftClick, event, setFocus)\n\t\t\t\tconsumed = true\n\t\t\t}\n\t\tcase MouseLeftUp:\n\t\t\tif d.dragging {\n\t\t\t\td.dragging = false\n\t\t\t\td.list.MouseHandler()(MouseLeftClick, event, setFocus)\n\t\t\t\tconsumed = true\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t})\n}\n\n// PasteHandler returns the handler for this primitive.\nfunc (d *DropDown) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {\n\treturn d.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {\n\t\tif !d.open || d.disabled {\n\t\t\treturn\n\t\t}\n\n\t\t// Strip any newline characters (simple version).\n\t\tpastedText = regexp.MustCompile(`\\r?\\n`).ReplaceAllString(pastedText, \"\")\n\n\t\t// Forward the pasted text to the input field.\n\t\td.prefix.PasteHandler()(pastedText, setFocus)\n\t})\n}\n"
  },
  {
    "path": "flex.go",
    "content": "package tview\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// Flex directions.\nconst (\n\t// One item per row.\n\tFlexRow = 0\n\t// One item per column.\n\tFlexColumn = 1\n\t// As defined in CSS, items distributed along a row.\n\tFlexRowCSS = 1\n\t// As defined in CSS, items distributed within a column.\n\tFlexColumnCSS = 0\n)\n\n// flexItem holds layout options for one item.\ntype flexItem struct {\n\tItem       Primitive // The item to be positioned. May be nil for an empty item.\n\tFixedSize  int       // The item's fixed size which may not be changed, 0 if it has no fixed size.\n\tProportion int       // The item's proportion.\n\tFocus      bool      // Whether or not this item attracts the layout's focus.\n}\n\n// Flex is a basic implementation of the Flexbox layout. The contained\n// primitives are arranged horizontally or vertically. The way they are\n// distributed along that dimension depends on their layout settings, which is\n// either a fixed length or a proportional length. See AddItem() for details.\n//\n// See https://github.com/rivo/tview/wiki/Flex for an example.\ntype Flex struct {\n\t*Box\n\n\t// The items to be positioned.\n\titems []*flexItem\n\n\t// FlexRow or FlexColumn.\n\tdirection int\n\n\t// If set to true, Flex will use the entire screen as its available space\n\t// instead its box dimensions.\n\tfullScreen bool\n}\n\n// NewFlex returns a new flexbox layout container with no primitives and its\n// direction set to [FlexColumn]. To add primitives to this layout, see\n// [Flex.AddItem]. To change the direction, see [Flex.SetDirection].\n//\n// Note that [Box], the superclass of Flex, will not clear its contents so that\n// any nil flex items will leave their background unchanged. To clear a Flex's\n// background before any items are drawn, set it to a new [Box]:\n//\n//\tflex.Box = NewBox()\nfunc NewFlex() *Flex {\n\tf := &Flex{\n\t\tdirection: FlexColumn,\n\t}\n\tf.Box = NewBox()\n\tf.Box.dontClear = true\n\tf.Box.Primitive = f\n\treturn f\n}\n\n// SetDirection sets the direction in which the contained primitives are\n// distributed. This can be either FlexColumn (default) or FlexRow. Note that\n// these are the opposite of what you would expect coming from CSS. You may also\n// use FlexColumnCSS or FlexRowCSS, to remain in line with the CSS definition.\nfunc (f *Flex) SetDirection(direction int) *Flex {\n\tf.direction = direction\n\treturn f\n}\n\n// SetFullScreen sets the flag which, when true, causes the flex layout to use\n// the entire screen space instead of whatever size it is currently assigned to.\nfunc (f *Flex) SetFullScreen(fullScreen bool) *Flex {\n\tf.fullScreen = fullScreen\n\treturn f\n}\n\n// AddItem adds a new item to the container. The \"fixedSize\" argument is a width\n// or height that may not be changed by the layout algorithm. A value of 0 means\n// that its size is flexible and may be changed. The \"proportion\" argument\n// defines the relative size of the item compared to other flexible-size items.\n// For example, items with a proportion of 2 will be twice as large as items\n// with a proportion of 1. The proportion must be at least 1 if fixedSize == 0\n// (ignored otherwise).\n//\n// If \"focus\" is set to true, the item will receive focus when the Flex\n// primitive receives focus. If multiple items have the \"focus\" flag set to\n// true, the first one will receive focus.\n//\n// You can provide a nil value for the primitive. This will still consume screen\n// space but nothing will be drawn.\nfunc (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {\n\tf.items = append(f.items, &flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})\n\treturn f\n}\n\n// RemoveItem removes all items for the given primitive from the container,\n// keeping the order of the remaining items intact.\nfunc (f *Flex) RemoveItem(p Primitive) *Flex {\n\tfor index := len(f.items) - 1; index >= 0; index-- {\n\t\tif f.items[index].Item == p {\n\t\t\tf.items = append(f.items[:index], f.items[index+1:]...)\n\t\t}\n\t}\n\treturn f\n}\n\n// GetItemCount returns the number of items in this container.\nfunc (f *Flex) GetItemCount() int {\n\treturn len(f.items)\n}\n\n// GetItem returns the primitive at the given index, starting with 0 for the\n// first primitive in this container.\n//\n// This function will panic for out of range indices.\nfunc (f *Flex) GetItem(index int) Primitive {\n\treturn f.items[index].Item\n}\n\n// Clear removes all items from the container.\nfunc (f *Flex) Clear() *Flex {\n\tf.items = nil\n\treturn f\n}\n\n// ResizeItem sets a new size for the item(s) with the given primitive. If there\n// are multiple Flex items with the same primitive, they will all receive the\n// same size. For details regarding the size parameters, see AddItem().\nfunc (f *Flex) ResizeItem(p Primitive, fixedSize, proportion int) *Flex {\n\tfor _, item := range f.items {\n\t\tif item.Item == p {\n\t\t\titem.FixedSize = fixedSize\n\t\t\titem.Proportion = proportion\n\t\t}\n\t}\n\treturn f\n}\n\n// Draw draws this primitive onto the screen.\nfunc (f *Flex) Draw(screen tcell.Screen) {\n\tf.Box.DrawForSubclass(screen, f)\n\n\t// Calculate size and position of the items.\n\n\t// Do we use the entire screen?\n\tif f.fullScreen {\n\t\twidth, height := screen.Size()\n\t\tf.SetRect(0, 0, width, height)\n\t}\n\n\t// How much space can we distribute?\n\tx, y, width, height := f.GetInnerRect()\n\tvar proportionSum int\n\tdistSize := width\n\tif f.direction == FlexRow {\n\t\tdistSize = height\n\t}\n\tfor _, item := range f.items {\n\t\tif item.FixedSize > 0 {\n\t\t\tdistSize -= item.FixedSize\n\t\t} else {\n\t\t\tproportionSum += item.Proportion\n\t\t}\n\t}\n\n\t// Calculate positions and draw items.\n\tpos := x\n\tif f.direction == FlexRow {\n\t\tpos = y\n\t}\n\tfor _, item := range f.items {\n\t\tsize := item.FixedSize\n\t\tif size <= 0 {\n\t\t\tif proportionSum > 0 {\n\t\t\t\tsize = distSize * item.Proportion / proportionSum\n\t\t\t\tdistSize -= size\n\t\t\t\tproportionSum -= item.Proportion\n\t\t\t} else {\n\t\t\t\tsize = 0\n\t\t\t}\n\t\t}\n\t\tif item.Item != nil {\n\t\t\tif f.direction == FlexColumn {\n\t\t\t\titem.Item.SetRect(pos, y, size, height)\n\t\t\t} else {\n\t\t\t\titem.Item.SetRect(x, pos, width, size)\n\t\t\t}\n\t\t}\n\t\tpos += size\n\n\t\tif item.Item != nil {\n\t\t\tif item.Item.HasFocus() {\n\t\t\t\tdefer item.Item.Draw(screen)\n\t\t\t} else {\n\t\t\t\titem.Item.Draw(screen)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Focus is called when this primitive receives focus.\nfunc (f *Flex) Focus(delegate func(p Primitive)) {\n\tfor _, item := range f.items {\n\t\tif item.Item != nil && item.Focus {\n\t\t\tdelegate(item.Item)\n\t\t\treturn\n\t\t}\n\t}\n\tf.Box.Focus(delegate)\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (f *Flex) focusChain(chain *[]Primitive) bool {\n\tfor _, item := range f.items {\n\t\tif item.Item == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif hasFocus := item.Item.focusChain(chain); hasFocus {\n\t\t\tif chain != nil {\n\t\t\t\t*chain = append(*chain, f)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\treturn f.Box.focusChain(chain)\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (f *Flex) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif !f.InRect(event.Position()) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Pass mouse events along to the first child item that takes it.\n\t\tfor _, item := range f.items {\n\t\t\tif item.Item == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconsumed, capture = item.Item.MouseHandler()(action, event, setFocus)\n\t\t\tif consumed {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t})\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (f *Flex) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tfor _, i := range f.items {\n\t\t\titem := i.Item\n\t\t\tif item == nil || !item.HasFocus() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif handler := item.InputHandler(); handler != nil {\n\t\t\t\thandler(event, setFocus)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t})\n}\n\n// PasteHandler returns the handler for this primitive.\nfunc (f *Flex) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {\n\treturn f.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {\n\t\tfor _, item := range f.items {\n\t\t\tif item.Item != nil && item.Item.HasFocus() {\n\t\t\t\tif handler := item.Item.PasteHandler(); handler != nil {\n\t\t\t\t\thandler(pastedText, setFocus)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "form.go",
    "content": "package tview\n\nimport (\n\t\"image\"\n\n\t\"github.com/gdamore/tcell/v2\"\n)\n\nvar (\n\t// DefaultFormFieldWidth is the default field screen width of form elements\n\t// whose field width is flexible (0). This is used in the Form class for\n\t// horizontal layouts.\n\tDefaultFormFieldWidth = 10\n\n\t// DefaultFormFieldHeight is the default field height of multi-line form\n\t// elements whose field height is flexible (0).\n\tDefaultFormFieldHeight = 5\n)\n\n// FormItem is the interface all form items must implement to be able to be\n// included in a form.\ntype FormItem interface {\n\tPrimitive\n\n\t// GetLabel returns the item's label text.\n\tGetLabel() string\n\n\t// SetFormAttributes sets a number of item attributes at once.\n\tSetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem\n\n\t// GetFieldWidth returns the width of the form item's field (the area which\n\t// is manipulated by the user) in number of screen cells. A value of 0\n\t// indicates the field width is flexible and may use as much space as\n\t// required.\n\tGetFieldWidth() int\n\n\t// GetFieldHeight returns the height of the form item's field (the area which\n\t// is manipulated by the user). This value must be greater than 0.\n\tGetFieldHeight() int\n\n\t// SetFinishedFunc sets the handler function for when the user finished\n\t// entering data into the item. The handler may receive events for the\n\t// Enter key (we're done), the Escape key (cancel input), the Tab key (move\n\t// to next field), the Backtab key (move to previous field), or a negative\n\t// value, indicating that the action for the last known key should be\n\t// repeated.\n\tSetFinishedFunc(handler func(key tcell.Key)) FormItem\n\n\t// SetDisabled sets whether or not the item is disabled / read-only. A form\n\t// must have at least one item that is not disabled.\n\tSetDisabled(disabled bool) FormItem\n\n\t// GetDisabled returns whether or not the item is disabled / read-only.\n\tGetDisabled() bool\n}\n\n// Form allows you to combine multiple one-line form elements into a vertical\n// or horizontal layout. Form elements include types such as InputField or\n// Checkbox. These elements can be optionally followed by one or more buttons\n// for which you can define form-wide actions (e.g. Save, Clear, Cancel).\n//\n// See https://github.com/rivo/tview/wiki/Form for an example.\ntype Form struct {\n\t*Box\n\n\t// The items of the form (one row per item).\n\titems []FormItem\n\n\t// The buttons of the form.\n\tbuttons []*Button\n\n\t// If set to true, instead of position items and buttons from top to bottom,\n\t// they are positioned from left to right.\n\thorizontal bool\n\n\t// The alignment of the buttons.\n\tbuttonsAlign int\n\n\t// The number of empty cells between items.\n\titemPadding int\n\n\t// The label color.\n\tlabelColor tcell.Color\n\n\t// The style of the input area.\n\tfieldStyle tcell.Style\n\n\t// The style of the buttons when they are not focused.\n\tbuttonStyle tcell.Style\n\n\t// The style of the buttons when they are focused.\n\tbuttonActivatedStyle tcell.Style\n\n\t// The style of the buttons when they are disabled.\n\tbuttonDisabledStyle tcell.Style\n\n\t// The index of the item or button for which the user requested focus.\n\t// Applied the next time the form itself receives focus. Negative if no\n\t// specific item was requested.\n\trequestedFocus int\n\n\t// A function to set the application's current focus. Does nothing\n\t// initially.\n\tsetFocus func(Primitive)\n\n\t// The last (valid) key that wsa sent to a \"finished\" handler or -1 if no\n\t// such key is known yet.\n\tlastFinishedKey tcell.Key\n\n\t// An optional function which is called when the user hits Escape.\n\tcancel func()\n}\n\n// NewForm returns a new [Form].\nfunc NewForm() *Form {\n\tbox := NewBox().SetBorderPadding(1, 1, 1, 1)\n\n\tf := &Form{\n\t\tBox:                  box,\n\t\titemPadding:          1,\n\t\tlabelColor:           Styles.SecondaryTextColor,\n\t\tfieldStyle:           tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),\n\t\tbuttonStyle:          tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),\n\t\tbuttonActivatedStyle: tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.ContrastBackgroundColor),\n\t\tbuttonDisabledStyle:  tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.ContrastSecondaryTextColor),\n\t\trequestedFocus:       -1,\n\t\tsetFocus:             func(Primitive) {},\n\t\tlastFinishedKey:      tcell.KeyTab, // To skip over inactive elements at the beginning of the form.\n\t}\n\n\tf.Box.Primitive = f\n\treturn f\n}\n\n// SetItemPadding sets the number of empty rows between form items for vertical\n// layouts and the number of empty cells between form items for horizontal\n// layouts. In vertical layouts, there is always at least one empty line between\n// the last item and the buttons, if any.\nfunc (f *Form) SetItemPadding(padding int) *Form {\n\tf.itemPadding = padding\n\treturn f\n}\n\n// SetHorizontal sets the direction the form elements are laid out. If set to\n// true, instead of positioning them from top to bottom (the default), they are\n// positioned from left to right, moving into the next row if there is not\n// enough space.\nfunc (f *Form) SetHorizontal(horizontal bool) *Form {\n\tf.horizontal = horizontal\n\treturn f\n}\n\n// SetLabelColor sets the color of the labels.\nfunc (f *Form) SetLabelColor(color tcell.Color) *Form {\n\tf.labelColor = color\n\treturn f\n}\n\n// SetFieldBackgroundColor sets the background color of the input areas.\nfunc (f *Form) SetFieldBackgroundColor(color tcell.Color) *Form {\n\tf.fieldStyle = f.fieldStyle.Background(color)\n\treturn f\n}\n\n// SetFieldTextColor sets the text color of the input areas.\nfunc (f *Form) SetFieldTextColor(color tcell.Color) *Form {\n\tf.fieldStyle = f.fieldStyle.Foreground(color)\n\treturn f\n}\n\n// SetFieldStyle sets the style of the input areas. Attributes are currently\n// still ignored to maintain backwards compatibility.\nfunc (f *Form) SetFieldStyle(style tcell.Style) *Form {\n\tf.fieldStyle = style\n\treturn f\n}\n\n// SetButtonsAlign sets how the buttons align horizontally, one of AlignLeft\n// (the default), AlignCenter, and AlignRight. This is only\nfunc (f *Form) SetButtonsAlign(align int) *Form {\n\tf.buttonsAlign = align\n\treturn f\n}\n\n// SetButtonBackgroundColor sets the background color of the buttons. This is\n// also the text color of the buttons when they are focused.\nfunc (f *Form) SetButtonBackgroundColor(color tcell.Color) *Form {\n\tf.buttonStyle = f.buttonStyle.Background(color)\n\tf.buttonActivatedStyle = f.buttonActivatedStyle.Foreground(color)\n\treturn f\n}\n\n// SetButtonTextColor sets the color of the button texts. This is also the\n// background of the buttons when they are focused.\nfunc (f *Form) SetButtonTextColor(color tcell.Color) *Form {\n\tf.buttonStyle = f.buttonStyle.Foreground(color)\n\tf.buttonActivatedStyle = f.buttonActivatedStyle.Background(color)\n\treturn f\n}\n\n// SetButtonStyle sets the style of the buttons when they are not focused.\nfunc (f *Form) SetButtonStyle(style tcell.Style) *Form {\n\tf.buttonStyle = style\n\treturn f\n}\n\n// SetButtonActivatedStyle sets the style of the buttons when they are focused.\nfunc (f *Form) SetButtonActivatedStyle(style tcell.Style) *Form {\n\tf.buttonActivatedStyle = style\n\treturn f\n}\n\n// SetButtonDisabledStyle sets the style of the buttons when they are disabled.\nfunc (f *Form) SetButtonDisabledStyle(style tcell.Style) *Form {\n\tf.buttonDisabledStyle = style\n\treturn f\n}\n\n// SetFocus shifts the focus to the form element with the given index, counting\n// non-button items first and buttons last. This does not change the\n// application's focus immediately, but the next time the form itself receives\n// focus, the given element will be focused once. Set to a negative value to\n// focus the first (enabled) element.\nfunc (f *Form) SetFocus(index int) *Form {\n\tf.requestedFocus = index\n\treturn f\n}\n\n// AddTextArea adds a text area to the form. It has a label, an optional initial\n// text, a size (width and height) referring to the actual input area (a\n// fieldWidth of 0 extends it as far right as possible, a fieldHeight of 0 will\n// cause it to be [DefaultFormFieldHeight]), and a maximum number of bytes of\n// text allowed (0 means no limit).\n//\n// The optional callback function is invoked when the content of the text area\n// has changed. Note that especially for larger texts, this is an expensive\n// operation due to technical constraints of the [TextArea] primitive (every key\n// stroke leads to a new reallocation of the entire text).\nfunc (f *Form) AddTextArea(label, text string, fieldWidth, fieldHeight, maxLength int, changed func(text string)) *Form {\n\tif fieldHeight == 0 {\n\t\tfieldHeight = DefaultFormFieldHeight\n\t}\n\ttextArea := NewTextArea().\n\t\tSetLabel(label).\n\t\tSetSize(fieldHeight, fieldWidth).\n\t\tSetMaxLength(maxLength)\n\tif text != \"\" {\n\t\ttextArea.SetText(text, true)\n\t}\n\tif changed != nil {\n\t\ttextArea.SetChangedFunc(func() {\n\t\t\tchanged(textArea.GetText())\n\t\t})\n\t}\n\ttextArea.SetFinishedFunc(f.finished)\n\tf.items = append(f.items, textArea)\n\treturn f\n}\n\n// AddTextView adds a text view to the form. It has a label and text, a size\n// (width and height) referring to the actual text element (a fieldWidth of 0\n// extends it as far right as possible, a fieldHeight of 0 will cause it to be\n// [DefaultFormFieldHeight]), a flag to turn on/off dynamic colors, and a flag\n// to turn on/off scrolling. If scrolling is turned off, the text view will not\n// receive focus.\nfunc (f *Form) AddTextView(label, text string, fieldWidth, fieldHeight int, dynamicColors, scrollable bool) *Form {\n\tif fieldHeight == 0 {\n\t\tfieldHeight = DefaultFormFieldHeight\n\t}\n\ttextArea := NewTextView().\n\t\tSetLabel(label).\n\t\tSetSize(fieldHeight, fieldWidth).\n\t\tSetDynamicColors(dynamicColors).\n\t\tSetScrollable(scrollable).\n\t\tSetText(text)\n\ttextArea.SetFinishedFunc(f.finished)\n\tf.items = append(f.items, textArea)\n\treturn f\n}\n\n// AddInputField adds an input field to the form. It has a label, an optional\n// initial value, a field width (a value of 0 extends it as far as possible),\n// an optional accept function to validate the item's value (set to nil to\n// accept any text), and an (optional) callback function which is invoked when\n// the input field's text has changed.\nfunc (f *Form) AddInputField(label, value string, fieldWidth int, accept func(textToCheck string, lastChar rune) bool, changed func(text string)) *Form {\n\tinputField := NewInputField().\n\t\tSetLabel(label).\n\t\tSetText(value).\n\t\tSetFieldWidth(fieldWidth).\n\t\tSetAcceptanceFunc(accept).\n\t\tSetChangedFunc(changed)\n\tinputField.SetFinishedFunc(f.finished)\n\tf.items = append(f.items, inputField)\n\treturn f\n}\n\n// AddPasswordField adds a password field to the form. This is similar to an\n// input field except that the user's input not shown. Instead, a \"mask\"\n// character is displayed. The password field has a label, an optional initial\n// value, a field width (a value of 0 extends it as far as possible), and an\n// (optional) callback function which is invoked when the input field's text has\n// changed.\nfunc (f *Form) AddPasswordField(label, value string, fieldWidth int, mask rune, changed func(text string)) *Form {\n\tif mask == 0 {\n\t\tmask = '*'\n\t}\n\tpassword := NewInputField().\n\t\tSetLabel(label).\n\t\tSetText(value).\n\t\tSetFieldWidth(fieldWidth).\n\t\tSetMaskCharacter(mask).\n\t\tSetChangedFunc(changed)\n\tpassword.SetFinishedFunc(f.finished)\n\tf.items = append(f.items, password)\n\treturn f\n}\n\n// AddDropDown adds a drop-down element to the form. It has a label, options,\n// and an (optional) callback function which is invoked when an option was\n// selected. The initial option may be a negative value to indicate that no\n// option is currently selected.\nfunc (f *Form) AddDropDown(label string, options []string, initialOption int, selected func(option string, optionIndex int)) *Form {\n\tdropDown := NewDropDown().\n\t\tSetLabel(label).\n\t\tSetOptions(options, selected).\n\t\tSetCurrentOption(initialOption)\n\tdropDown.SetFinishedFunc(f.finished)\n\tf.items = append(f.items, dropDown)\n\treturn f\n}\n\n// AddCheckbox adds a checkbox to the form. It has a label, an initial state,\n// and an (optional) callback function which is invoked when the state of the\n// checkbox was changed by the user.\nfunc (f *Form) AddCheckbox(label string, checked bool, changed func(checked bool)) *Form {\n\tcheckbox := NewCheckbox().\n\t\tSetLabel(label).\n\t\tSetChecked(checked).\n\t\tSetChangedFunc(changed)\n\tcheckbox.SetFinishedFunc(f.finished)\n\tf.items = append(f.items, checkbox)\n\treturn f\n}\n\n// AddImage adds an image to the form. It has a label and the image will fit in\n// the specified width and height (its aspect ratio is preserved). See\n// [Image.SetColors] for a description of the \"colors\" parameter. Images are not\n// interactive and are skipped over in a form. The \"width\" value may be 0\n// (adjust dynamically) but \"height\" should generally be a positive value.\nfunc (f *Form) AddImage(label string, image image.Image, width, height, colors int) *Form {\n\timg := NewImage().\n\t\tSetLabel(label).\n\t\tSetImage(image).\n\t\tSetSize(height, width).\n\t\tSetAlign(AlignTop, AlignLeft).\n\t\tSetColors(colors)\n\timg.SetFinishedFunc(f.finished)\n\tf.items = append(f.items, img)\n\treturn f\n}\n\n// AddButton adds a new button to the form. The \"selected\" function is called\n// when the user selects this button. It may be nil.\nfunc (f *Form) AddButton(label string, selected func()) *Form {\n\tbutton := NewButton(label).\n\t\tSetSelectedFunc(selected).\n\t\tSetExitFunc(f.finished)\n\tf.buttons = append(f.buttons, button)\n\treturn f\n}\n\n// GetButton returns the button at the specified 0-based index. Note that\n// buttons have been specially prepared for this form and modifying some of\n// their attributes may have unintended side effects.\nfunc (f *Form) GetButton(index int) *Button {\n\treturn f.buttons[index]\n}\n\n// RemoveButton removes the button at the specified position, starting with 0\n// for the button that was added first.\nfunc (f *Form) RemoveButton(index int) *Form {\n\tf.buttons = append(f.buttons[:index], f.buttons[index+1:]...)\n\treturn f\n}\n\n// GetButtonCount returns the number of buttons in this form.\nfunc (f *Form) GetButtonCount() int {\n\treturn len(f.buttons)\n}\n\n// GetButtonIndex returns the index of the button with the given label, starting\n// with 0 for the button that was added first. If no such label was found, -1\n// is returned.\nfunc (f *Form) GetButtonIndex(label string) int {\n\tfor index, button := range f.buttons {\n\t\tif button.GetLabel() == label {\n\t\t\treturn index\n\t\t}\n\t}\n\treturn -1\n}\n\n// Clear removes all input elements from the form, including the buttons if\n// specified.\nfunc (f *Form) Clear(includeButtons bool) *Form {\n\tf.items = nil\n\tif includeButtons {\n\t\tf.ClearButtons()\n\t}\n\treturn f\n}\n\n// ClearButtons removes all buttons from the form.\nfunc (f *Form) ClearButtons() *Form {\n\tf.buttons = nil\n\treturn f\n}\n\n// AddFormItem adds a new item to the form. This can be used to add your own\n// objects to the form. Note, however, that the Form class will override some\n// of its attributes to make it work in the form context. Specifically, these\n// are:\n//\n//   - The label width\n//   - The label color\n//   - The background color\n//   - The field text color\n//   - The field background color\nfunc (f *Form) AddFormItem(item FormItem) *Form {\n\titem.SetFinishedFunc(f.finished)\n\tf.items = append(f.items, item)\n\treturn f\n}\n\n// GetFormItemCount returns the number of items in the form (not including the\n// buttons).\nfunc (f *Form) GetFormItemCount() int {\n\treturn len(f.items)\n}\n\n// GetFormItem returns the form item at the given position, starting with index\n// 0. Elements are referenced in the order they were added. Buttons are not\n// included.\nfunc (f *Form) GetFormItem(index int) FormItem {\n\treturn f.items[index]\n}\n\n// RemoveFormItem removes the form element at the given position, starting with\n// index 0. Elements are referenced in the order they were added. Buttons are\n// not included.\nfunc (f *Form) RemoveFormItem(index int) *Form {\n\tf.items = append(f.items[:index], f.items[index+1:]...)\n\treturn f\n}\n\n// GetFormItemByLabel returns the first form element with the given label. If\n// no such element is found, nil is returned. Buttons are not searched and will\n// therefore not be returned.\nfunc (f *Form) GetFormItemByLabel(label string) FormItem {\n\tfor _, item := range f.items {\n\t\tif item.GetLabel() == label {\n\t\t\treturn item\n\t\t}\n\t}\n\treturn nil\n}\n\n// GetFormItemIndex returns the index of the first form element with the given\n// label. If no such element is found, -1 is returned. Buttons are not searched\n// and will therefore not be returned.\nfunc (f *Form) GetFormItemIndex(label string) int {\n\tfor index, item := range f.items {\n\t\tif item.GetLabel() == label {\n\t\t\treturn index\n\t\t}\n\t}\n\treturn -1\n}\n\n// GetFocusedItemIndex returns the indices of the form element or button which\n// currently has focus. If they don't, -1 is returned respectively.\nfunc (f *Form) GetFocusedItemIndex() (formItem, button int) {\n\tindex := f.focusIndex()\n\tif index < 0 {\n\t\treturn -1, -1\n\t}\n\tif index < len(f.items) {\n\t\treturn index, -1\n\t}\n\treturn -1, index - len(f.items)\n}\n\n// SetCancelFunc sets a handler which is called when the user hits the Escape\n// key.\nfunc (f *Form) SetCancelFunc(callback func()) *Form {\n\tf.cancel = callback\n\treturn f\n}\n\n// Draw draws this primitive onto the screen.\nfunc (f *Form) Draw(screen tcell.Screen) {\n\tf.Box.DrawForSubclass(screen, f)\n\n\t// Determine the dimensions.\n\tx, y, width, height := f.GetInnerRect()\n\ttopLimit := y\n\tbottomLimit := y + height\n\trightLimit := x + width\n\tstartX := x\n\n\t// Find the longest label.\n\tvar maxLabelWidth int\n\tfor _, item := range f.items {\n\t\tlabelWidth := TaggedStringWidth(item.GetLabel())\n\t\tif labelWidth > maxLabelWidth {\n\t\t\tmaxLabelWidth = labelWidth\n\t\t}\n\t}\n\tmaxLabelWidth++ // Add one space.\n\n\t// Calculate positions of form items.\n\ttype position struct{ x, y, width, height int }\n\tpositions := make([]position, len(f.items)+len(f.buttons))\n\tvar (\n\t\tfocusedPosition position\n\t\tlineHeight      = 1\n\t)\n\tfor index, item := range f.items {\n\t\t// Calculate the space needed.\n\t\tlabelWidth := TaggedStringWidth(item.GetLabel())\n\t\tvar itemWidth int\n\t\tif f.horizontal {\n\t\t\tfieldWidth := item.GetFieldWidth()\n\t\t\tif fieldWidth <= 0 {\n\t\t\t\tfieldWidth = DefaultFormFieldWidth\n\t\t\t}\n\t\t\tlabelWidth++\n\t\t\titemWidth = labelWidth + fieldWidth\n\t\t} else {\n\t\t\t// We want all fields to align vertically.\n\t\t\tlabelWidth = maxLabelWidth\n\t\t\titemWidth = width\n\t\t}\n\t\titemHeight := item.GetFieldHeight()\n\t\tif itemHeight <= 0 {\n\t\t\titemHeight = DefaultFormFieldHeight\n\t\t}\n\n\t\t// Advance to next line if there is no space.\n\t\tif f.horizontal && x+labelWidth+1 >= rightLimit {\n\t\t\tx = startX\n\t\t\ty += lineHeight + 1\n\t\t\tlineHeight = itemHeight\n\t\t}\n\n\t\t// Update line height.\n\t\tif itemHeight > lineHeight {\n\t\t\tlineHeight = itemHeight\n\t\t}\n\n\t\t// Adjust the item's attributes.\n\t\tif x+itemWidth >= rightLimit {\n\t\t\titemWidth = rightLimit - x\n\t\t}\n\t\tfieldTextColor, fieldBackgroundColor, _ := f.fieldStyle.Decompose()\n\t\titem.SetFormAttributes(\n\t\t\tlabelWidth,\n\t\t\tf.labelColor,\n\t\t\tf.backgroundColor,\n\t\t\tfieldTextColor,\n\t\t\tfieldBackgroundColor,\n\t\t)\n\n\t\t// Save position.\n\t\tpositions[index].x = x\n\t\tpositions[index].y = y\n\t\tpositions[index].width = itemWidth\n\t\tpositions[index].height = itemHeight\n\t\tif item.HasFocus() {\n\t\t\tfocusedPosition = positions[index]\n\t\t}\n\n\t\t// Advance to next item.\n\t\tif f.horizontal {\n\t\t\tx += itemWidth + f.itemPadding\n\t\t} else {\n\t\t\ty += itemHeight + f.itemPadding\n\t\t}\n\t}\n\n\t// How wide are the buttons?\n\tbuttonWidths := make([]int, len(f.buttons))\n\tbuttonsWidth := 0\n\tfor index, button := range f.buttons {\n\t\tw := TaggedStringWidth(button.GetLabel()) + 4\n\t\tbuttonWidths[index] = w\n\t\tbuttonsWidth += w + 1\n\t}\n\tbuttonsWidth--\n\n\t// Where do we place them?\n\tif !f.horizontal && x+buttonsWidth < rightLimit {\n\t\tswitch f.buttonsAlign {\n\t\tcase AlignRight:\n\t\t\tx = rightLimit - buttonsWidth\n\t\tcase AlignCenter:\n\t\t\tx = (x + rightLimit - buttonsWidth) / 2\n\t\t}\n\n\t\t// In vertical layouts, buttons always appear after an empty line.\n\t\tif f.itemPadding == 0 && len(f.items) > 0 {\n\t\t\ty++\n\t\t}\n\t}\n\n\t// Calculate positions of buttons.\n\tfor index, button := range f.buttons {\n\t\tspace := rightLimit - x\n\t\tbuttonWidth := buttonWidths[index]\n\t\tif f.horizontal {\n\t\t\tif space < buttonWidth-4 {\n\t\t\t\tx = startX\n\t\t\t\ty += lineHeight + 1\n\t\t\t\tspace = width\n\t\t\t\tlineHeight = 1\n\t\t\t}\n\t\t} else {\n\t\t\tif space < 1 {\n\t\t\t\tbreak // No space for this button anymore.\n\t\t\t}\n\t\t}\n\t\tif buttonWidth > space {\n\t\t\tbuttonWidth = space\n\t\t}\n\t\tbutton.SetStyle(f.buttonStyle).\n\t\t\tSetActivatedStyle(f.buttonActivatedStyle).\n\t\t\tSetDisabledStyle(f.buttonDisabledStyle)\n\n\t\tbuttonIndex := index + len(f.items)\n\t\tpositions[buttonIndex].x = x\n\t\tpositions[buttonIndex].y = y\n\t\tpositions[buttonIndex].width = buttonWidth\n\t\tpositions[buttonIndex].height = 1\n\n\t\tif button.HasFocus() {\n\t\t\tfocusedPosition = positions[buttonIndex]\n\t\t}\n\n\t\tx += buttonWidth + 1\n\t}\n\n\t// Determine vertical offset based on the position of the focused item.\n\tvar offset int\n\tif focusedPosition.y+focusedPosition.height > bottomLimit {\n\t\toffset = focusedPosition.y + focusedPosition.height - bottomLimit\n\t\tif focusedPosition.y-offset < topLimit {\n\t\t\toffset = focusedPosition.y - topLimit\n\t\t}\n\t}\n\n\t// Draw items.\n\tfor index, item := range f.items {\n\t\t// Set position.\n\t\ty := positions[index].y - offset\n\t\theight := positions[index].height\n\t\titem.SetRect(positions[index].x, y, positions[index].width, height)\n\n\t\t// Is this item visible?\n\t\tif y+height <= topLimit || y >= bottomLimit {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Draw items with focus last (in case of overlaps).\n\t\tif item.HasFocus() {\n\t\t\tdefer item.Draw(screen)\n\t\t} else {\n\t\t\titem.Draw(screen)\n\t\t}\n\t}\n\n\t// Draw buttons.\n\tfor index, button := range f.buttons {\n\t\t// Set position.\n\t\tbuttonIndex := index + len(f.items)\n\t\ty := positions[buttonIndex].y - offset\n\t\theight := positions[buttonIndex].height\n\t\tbutton.SetRect(positions[buttonIndex].x, y, positions[buttonIndex].width, height)\n\n\t\t// Is this button visible?\n\t\tif y+height <= topLimit || y >= bottomLimit {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Draw button.\n\t\tbutton.Draw(screen)\n\t}\n}\n\n// Focus is called by the application when the primitive receives focus.\nfunc (f *Form) Focus(delegate func(p Primitive)) {\n\tf.setFocus = delegate\n\n\t// If there is no current focus, pick one.\n\tfocus := f.focusIndex()\n\tif f.requestedFocus >= 0 {\n\t\tfocus = f.requestedFocus\n\t}\n\n\t// Delegate focus.\n\tfor index, item := range f.items {\n\t\tif (focus < 0 || focus == index) && !item.GetDisabled() {\n\t\t\tf.requestedFocus = index\n\t\t\tdelegate(item)\n\t\t\treturn\n\t\t}\n\t}\n\tfor index, button := range f.buttons {\n\t\tif (focus < 0 || focus == len(f.items)+index) && !button.GetDisabled() {\n\t\t\tf.requestedFocus = len(f.items) + index\n\t\t\tdelegate(button)\n\t\t\treturn\n\t\t}\n\t}\n\n\tf.Box.Focus(delegate)\n}\n\n// finished handles a form item's \"finished\" event.\nfunc (f *Form) finished(key tcell.Key) {\n\tfocus := f.focusIndex()\n\tif key >= 0 {\n\t\tf.lastFinishedKey = key\n\t}\n\n\ttotalCount := len(f.items) + len(f.buttons)\n\tswitch key {\n\tcase tcell.KeyTab, tcell.KeyEnter:\n\t\t// Find the next focusable item.\n\t\tfor index := 0; index < totalCount; index++ {\n\t\t\tfocus = (focus + 1) % totalCount\n\t\t\tif focus < len(f.items) {\n\t\t\t\tif !f.items[focus].GetDisabled() {\n\t\t\t\t\tf.setFocus(f.items[focus])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif !f.buttons[focus-len(f.items)].GetDisabled() {\n\t\t\t\t\tf.setFocus(f.buttons[focus-len(f.items)])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase tcell.KeyBacktab:\n\t\t// Find the previous focusable item.\n\t\tfor index := 0; index < totalCount; index++ {\n\t\t\tfocus = (focus + totalCount - 1) % totalCount\n\t\t\tif focus < len(f.items) {\n\t\t\t\tif !f.items[focus].GetDisabled() {\n\t\t\t\t\tf.setFocus(f.items[focus])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif !f.buttons[focus-len(f.items)].GetDisabled() {\n\t\t\t\t\tf.setFocus(f.buttons[focus-len(f.items)])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase tcell.KeyEscape:\n\t\tif f.cancel != nil {\n\t\t\tf.cancel()\n\t\t}\n\tdefault:\n\t\tif key < 0 && f.lastFinishedKey >= 0 {\n\t\t\t// Repeat the last action.\n\t\t\tf.finished(f.lastFinishedKey)\n\t\t}\n\t}\n}\n\n// focusIndex returns the index of the currently focused item, counting form\n// items first, then buttons. A negative value indicates that no containeed item\n// has focus.\nfunc (f *Form) focusIndex() int {\n\tfor index, item := range f.items {\n\t\tif item.HasFocus() {\n\t\t\treturn index\n\t\t}\n\t}\n\tfor index, button := range f.buttons {\n\t\tif button.HasFocus() {\n\t\t\treturn len(f.items) + index\n\t\t}\n\t}\n\treturn -1\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (f *Form) focusChain(chain *[]Primitive) bool {\n\tfor _, item := range f.items {\n\t\tif hasFocus := item.focusChain(chain); hasFocus {\n\t\t\tif chain != nil {\n\t\t\t\t*chain = append(*chain, f)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\tfor _, button := range f.buttons {\n\t\tif hasFocus := button.focusChain(chain); hasFocus {\n\t\t\tif chain != nil {\n\t\t\t\t*chain = append(*chain, f)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\treturn f.Box.focusChain(chain)\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\t// Determine items to pass mouse events to.\n\t\tfor _, item := range f.items {\n\t\t\tif item.GetDisabled() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconsumed, capture = item.MouseHandler()(action, event, setFocus)\n\t\t\tif consumed {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tfor _, button := range f.buttons {\n\t\t\tif button.GetDisabled() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconsumed, capture = button.MouseHandler()(action, event, setFocus)\n\t\t\tif consumed {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// A mouse down anywhere else will focus this form.\n\t\tif action == MouseLeftDown && f.InRect(event.Position()) {\n\t\t\tf.Focus(setFocus)\n\t\t\tconsumed = true\n\t\t}\n\n\t\treturn\n\t})\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (f *Form) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tfor _, item := range f.items {\n\t\t\tif item.HasFocus() {\n\t\t\t\tif handler := item.InputHandler(); handler != nil {\n\t\t\t\t\thandler(event, setFocus)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor _, button := range f.buttons {\n\t\t\tif button.HasFocus() {\n\t\t\t\tif handler := button.InputHandler(); handler != nil {\n\t\t\t\t\thandler(event, setFocus)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n\n// PasteHandler returns the handler for this primitive.\nfunc (f *Form) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {\n\treturn f.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {\n\t\tfor _, item := range f.items {\n\t\t\tif item.HasFocus() {\n\t\t\t\tif handler := item.PasteHandler(); handler != nil {\n\t\t\t\t\thandler(pastedText, setFocus)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor _, button := range f.buttons {\n\t\t\tif button.HasFocus() {\n\t\t\t\tif handler := button.PasteHandler(); handler != nil {\n\t\t\t\t\thandler(pastedText, setFocus)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "frame.go",
    "content": "package tview\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// frameText holds information about a line of text shown in the frame.\ntype frameText struct {\n\tText   string      // The text to be displayed.\n\tHeader bool        // true = place in header, false = place in footer.\n\tAlign  int         // One of the Align constants.\n\tColor  tcell.Color // The text color.\n}\n\n// Frame is a wrapper which adds space around another primitive. In addition,\n// the top area (header) and the bottom area (footer) may also contain text.\n//\n// See https://github.com/rivo/tview/wiki/Frame for an example.\ntype Frame struct {\n\t*Box\n\n\t// The contained primitive. May be nil.\n\tprimitive Primitive\n\n\t// The lines of text to be displayed.\n\ttext []*frameText\n\n\t// Border spacing.\n\ttop, bottom, header, footer, left, right int\n\n\t// Keep a reference in case we need it when we change the primitive.\n\tsetFocus func(p Primitive)\n}\n\n// NewFrame returns a new [Frame] around the given primitive. The primitive's\n// size will be changed to fit within this frame. The primitive may be nil, in\n// which case no other primitive is embedded in the frame.\nfunc NewFrame(primitive Primitive) *Frame {\n\tbox := NewBox()\n\n\tf := &Frame{\n\t\tBox:       box,\n\t\tprimitive: primitive,\n\t\ttop:       1,\n\t\tbottom:    1,\n\t\theader:    1,\n\t\tfooter:    1,\n\t\tleft:      1,\n\t\tright:     1,\n\t}\n\n\tf.Box.Primitive = f\n\treturn f\n}\n\n// SetPrimitive replaces the contained primitive with the given one. To remove\n// a primitive, set it to nil.\nfunc (f *Frame) SetPrimitive(p Primitive) *Frame {\n\tvar hasFocus bool\n\tif f.primitive != nil {\n\t\thasFocus = f.primitive.HasFocus()\n\t}\n\tf.primitive = p\n\tif hasFocus && f.setFocus != nil {\n\t\tf.setFocus(p) // Restore focus.\n\t}\n\treturn f\n}\n\n// GetPrimitive returns the primitive contained in this frame.\nfunc (f *Frame) GetPrimitive() Primitive {\n\treturn f.primitive\n}\n\n// AddText adds text to the frame. Set \"header\" to true if the text is to appear\n// in the header, above the contained primitive. Set it to false for it to\n// appear in the footer, below the contained primitive. \"align\" must be one of\n// the Align constants. Rows in the header are printed top to bottom, rows in\n// the footer are printed bottom to top. Note that long text can overlap as\n// different alignments will be placed on the same row.\nfunc (f *Frame) AddText(text string, header bool, align int, color tcell.Color) *Frame {\n\tf.text = append(f.text, &frameText{\n\t\tText:   text,\n\t\tHeader: header,\n\t\tAlign:  align,\n\t\tColor:  color,\n\t})\n\treturn f\n}\n\n// Clear removes all text from the frame.\nfunc (f *Frame) Clear() *Frame {\n\tf.text = nil\n\treturn f\n}\n\n// SetBorders sets the width of the frame borders as well as \"header\" and\n// \"footer\", the vertical space between the header and footer text and the\n// contained primitive (does not apply if there is no text).\nfunc (f *Frame) SetBorders(top, bottom, header, footer, left, right int) *Frame {\n\tf.top, f.bottom, f.header, f.footer, f.left, f.right = top, bottom, header, footer, left, right\n\treturn f\n}\n\n// Draw draws this primitive onto the screen.\nfunc (f *Frame) Draw(screen tcell.Screen) {\n\tf.Box.DrawForSubclass(screen, f)\n\n\t// Calculate start positions.\n\tx, top, width, height := f.GetInnerRect()\n\tbottom := top + height - 1\n\tx += f.left\n\ttop += f.top\n\tbottom -= f.bottom\n\twidth -= f.left + f.right\n\tif width <= 0 || top >= bottom {\n\t\treturn // No space left.\n\t}\n\n\t// Draw text.\n\tvar rows [6]int // top-left, top-center, top-right, bottom-left, bottom-center, bottom-right.\n\ttopMax := top\n\tbottomMin := bottom\n\tfor _, text := range f.text {\n\t\t// Where do we place this text?\n\t\tvar y int\n\t\tif text.Header {\n\t\t\ty = top + rows[text.Align]\n\t\t\trows[text.Align]++\n\t\t\tif y >= bottomMin {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif y+1 > topMax {\n\t\t\t\ttopMax = y + 1\n\t\t\t}\n\t\t} else {\n\t\t\ty = bottom - rows[3+text.Align]\n\t\t\trows[3+text.Align]++\n\t\t\tif y <= topMax {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif y-1 < bottomMin {\n\t\t\t\tbottomMin = y - 1\n\t\t\t}\n\t\t}\n\n\t\t// Draw text.\n\t\tPrint(screen, text.Text, x, y, width, text.Align, text.Color)\n\t}\n\n\t// Set the size of the contained primitive.\n\tif f.primitive != nil {\n\t\tif topMax > top {\n\t\t\ttop = topMax + f.header\n\t\t}\n\t\tif bottomMin < bottom {\n\t\t\tbottom = bottomMin - f.footer\n\t\t}\n\t\tif top > bottom {\n\t\t\treturn // No space for the primitive.\n\t\t}\n\t\tf.primitive.SetRect(x, top, width, bottom+1-top)\n\n\t\t// Finally, draw the contained primitive.\n\t\tf.primitive.Draw(screen)\n\t}\n}\n\n// Focus is called when this primitive receives focus.\nfunc (f *Frame) Focus(delegate func(p Primitive)) {\n\tf.setFocus = delegate\n\tif f.primitive != nil {\n\t\tdelegate(f.primitive)\n\t} else {\n\t\tf.Box.Focus(delegate)\n\t}\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (f *Frame) focusChain(chain *[]Primitive) bool {\n\tif f.primitive != nil {\n\t\tif hasFocus := f.primitive.focusChain(chain); hasFocus {\n\t\t\tif chain != nil {\n\t\t\t\t*chain = append(*chain, f)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\treturn f.Box.focusChain(chain)\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (f *Frame) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif !f.InRect(event.Position()) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Pass mouse events on to contained primitive.\n\t\tif f.primitive != nil {\n\t\t\tconsumed, capture = f.primitive.MouseHandler()(action, event, setFocus)\n\t\t\tif consumed {\n\t\t\t\treturn true, capture\n\t\t\t}\n\t\t}\n\n\t\t// Clicking on the frame parts.\n\t\tif action == MouseLeftDown {\n\t\t\tsetFocus(f)\n\t\t\tconsumed = true\n\t\t}\n\n\t\treturn\n\t})\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (f *Frame) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tif f.primitive == nil {\n\t\t\treturn\n\t\t}\n\t\tif handler := f.primitive.InputHandler(); handler != nil {\n\t\t\thandler(event, setFocus)\n\t\t\treturn\n\t\t}\n\t})\n}\n\n// PasteHandler returns the handler for this primitive.\nfunc (f *Frame) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {\n\treturn f.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {\n\t\tif f.primitive == nil {\n\t\t\treturn\n\t\t}\n\t\tif handler := f.primitive.PasteHandler(); handler != nil {\n\t\t\thandler(pastedText, setFocus)\n\t\t\treturn\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/rivo/tview\n\ngo 1.18\n\nrequire (\n\tgithub.com/gdamore/tcell/v2 v2.8.1\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0\n\tgithub.com/rivo/uniseg v0.4.7\n)\n\nrequire (\n\tgithub.com/gdamore/encoding v1.0.1 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.16 // indirect\n\tgolang.org/x/sys v0.29.0 // indirect\n\tgolang.org/x/term v0.28.0 // indirect\n\tgolang.org/x/text v0.21.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=\ngithub.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=\ngithub.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=\ngithub.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=\ngolang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=\ngolang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=\ngolang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=\ngolang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\n"
  },
  {
    "path": "grid.go",
    "content": "package tview\n\nimport (\n\t\"math\"\n\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// gridItem represents one primitive and its possible position on a grid.\ntype gridItem struct {\n\tItem                        Primitive // The item to be positioned. May be nil for an empty item.\n\tRow, Column                 int       // The top-left grid cell where the item is placed.\n\tWidth, Height               int       // The number of rows and columns the item occupies.\n\tMinGridWidth, MinGridHeight int       // The minimum grid width/height for which this item is visible.\n\tFocus                       bool      // Whether or not this item attracts the layout's focus. Only applicable for the first item for which this is set to true.\n\n\tvisible    bool // Whether or not this item was visible the last time the grid was drawn.\n\tx, y, w, h int  // The last position of the item relative to the top-left corner of the grid. Undefined if visible is false.\n}\n\n// Grid is an implementation of a grid-based layout. It works by defining the\n// size of the rows and columns, then placing primitives into the grid.\n//\n// Some settings can lead to the grid exceeding its available space. SetOffset()\n// can then be used to scroll in steps of rows and columns. These offset values\n// can also be controlled with the arrow keys (or the \"g\",\"G\", \"j\", \"k\", \"h\",\n// and \"l\" keys) while the grid has focus and none of its contained primitives\n// do.\n//\n// See https://github.com/rivo/tview/wiki/Grid for an example.\ntype Grid struct {\n\t*Box\n\n\t// The items to be positioned.\n\titems []*gridItem\n\n\t// The definition of the rows and columns of the grid. See\n\t// [Grid.SetRows] / [Grid.SetColumns] for details.\n\trows, columns []int\n\n\t// The minimum sizes for rows and columns.\n\tminWidth, minHeight int\n\n\t// The size of the gaps between neighboring primitives. This is automatically\n\t// set to 1 if borders is true.\n\tgapRows, gapColumns int\n\n\t// The number of rows and columns skipped before drawing the top-left corner\n\t// of the grid.\n\trowOffset, columnOffset int\n\n\t// Whether or not borders are drawn around grid items. If this is set to true,\n\t// a gap size of 1 is automatically assumed (which is filled with the border\n\t// graphics).\n\tborders bool\n\n\t// The color of the borders around grid items.\n\tbordersColor tcell.Color\n}\n\n// NewGrid returns a new grid-based layout container with no initial primitives.\n//\n// Note that [Box], the superclass of Grid, will be transparent so that any grid\n// areas not covered by any primitives will leave their background unchanged. To\n// clear a [Grid]'s background before any items are drawn, reset its embedded\n// [Box]:\n//\n//\tgrid.Box = NewBox()\nfunc NewGrid() *Grid {\n\tg := &Grid{\n\t\tbordersColor: Styles.GraphicsColor,\n\t}\n\tg.Box = NewBox()\n\tg.Box.Primitive = g\n\treturn g\n}\n\n// SetColumns defines how the columns of the grid are distributed. Each value\n// defines the size of one column, starting with the leftmost column. Values\n// greater than 0 represent absolute column widths (gaps not included). Values\n// less than or equal to 0 represent proportional column widths or fractions of\n// the remaining free space, where 0 is treated the same as -1. That is, a\n// column with a value of -3 will have three times the width of a column with a\n// value of -1 (or 0). The minimum width set with SetMinSize() is always\n// observed.\n//\n// Primitives may extend beyond the columns defined explicitly with this\n// function. A value of 0 is assumed for any undefined column. In fact, if you\n// never call this function, all columns occupied by primitives will have the\n// same width. On the other hand, unoccupied columns defined with this function\n// will always take their place.\n//\n// Assuming a total width of the grid of 100 cells and a minimum width of 0, the\n// following call will result in columns with widths of 30, 10, 15, 15, and 30\n// cells:\n//\n//\tgrid.SetColumns(30, 10, -1, -1, -2)\n//\n// If a primitive were then placed in the 6th and 7th column, the resulting\n// widths would be: 30, 10, 10, 10, 20, 10, and 10 cells.\n//\n// If you then called SetMinSize() as follows:\n//\n//\tgrid.SetMinSize(15, 20)\n//\n// The resulting widths would be: 30, 15, 15, 15, 20, 15, and 15 cells, a total\n// of 125 cells, 25 cells wider than the available grid width.\nfunc (g *Grid) SetColumns(columns ...int) *Grid {\n\tg.columns = columns\n\treturn g\n}\n\n// SetRows defines how the rows of the grid are distributed. These values behave\n// the same as the column values provided with [Grid.SetColumns], see there\n// for a definition and examples.\n//\n// The provided values correspond to row heights, the first value defining\n// the height of the topmost row.\nfunc (g *Grid) SetRows(rows ...int) *Grid {\n\tg.rows = rows\n\treturn g\n}\n\n// SetSize is a shortcut for [Grid.SetRows] and [Grid.SetColumns] where\n// all row and column values are set to the given size values. See\n// [Grid.SetColumns] for details on sizes.\nfunc (g *Grid) SetSize(numRows, numColumns, rowSize, columnSize int) *Grid {\n\tg.rows = make([]int, numRows)\n\tfor index := range g.rows {\n\t\tg.rows[index] = rowSize\n\t}\n\tg.columns = make([]int, numColumns)\n\tfor index := range g.columns {\n\t\tg.columns[index] = columnSize\n\t}\n\treturn g\n}\n\n// SetMinSize sets an absolute minimum width for rows and an absolute minimum\n// height for columns. Panics if negative values are provided.\nfunc (g *Grid) SetMinSize(row, column int) *Grid {\n\tif row < 0 || column < 0 {\n\t\tpanic(\"Invalid minimum row/column size\")\n\t}\n\tg.minHeight, g.minWidth = row, column\n\treturn g\n}\n\n// SetGap sets the size of the gaps between neighboring primitives on the grid.\n// If borders are drawn (see SetBorders()), these values are ignored and a gap\n// of 1 is assumed. Panics if negative values are provided.\nfunc (g *Grid) SetGap(row, column int) *Grid {\n\tif row < 0 || column < 0 {\n\t\tpanic(\"Invalid gap size\")\n\t}\n\tg.gapRows, g.gapColumns = row, column\n\treturn g\n}\n\n// SetBorders sets whether or not borders are drawn around grid items. Setting\n// this value to true will cause the gap values (see SetGap()) to be ignored and\n// automatically assumed to be 1 where the border graphics are drawn.\nfunc (g *Grid) SetBorders(borders bool) *Grid {\n\tg.borders = borders\n\treturn g\n}\n\n// SetBordersColor sets the color of the item borders.\nfunc (g *Grid) SetBordersColor(color tcell.Color) *Grid {\n\tg.bordersColor = color\n\treturn g\n}\n\n// AddItem adds a primitive and its position to the grid. The top-left corner\n// of the primitive will be located in the top-left corner of the grid cell at\n// the given row and column and will span \"rowSpan\" rows and \"colSpan\" columns.\n// For example, for a primitive to occupy rows 2, 3, and 4 and columns 5 and 6:\n//\n//\tgrid.AddItem(p, 2, 5, 3, 2, 0, 0, true)\n//\n// If rowSpan or colSpan is 0, the primitive will not be drawn.\n//\n// You can add the same primitive multiple times with different grid positions.\n// The minGridWidth and minGridHeight values will then determine which of those\n// positions will be used. This is similar to CSS media queries. These minimum\n// values refer to the overall size of the grid. If multiple items for the same\n// primitive apply, the one with the highest minimum value (width or height,\n// whatever is higher) will be used, or the primitive added last if those values\n// are the same. Example:\n//\n//\tgrid.AddItem(p, 0, 0, 0, 0, 0, 0, true). // Hide in small grids.\n//\t  AddItem(p, 0, 0, 1, 2, 100, 0, true).  // One-column layout for medium grids.\n//\t  AddItem(p, 1, 1, 3, 2, 300, 0, true)   // Multi-column layout for large grids.\n//\n// To use the same grid layout for all sizes, simply set minGridWidth and\n// minGridHeight to 0.\n//\n// If the item's focus is set to true, it will receive focus when the grid\n// receives focus. If there are multiple items with a true focus flag, the last\n// visible one that was added will receive focus.\nfunc (g *Grid) AddItem(p Primitive, row, column, rowSpan, colSpan, minGridHeight, minGridWidth int, focus bool) *Grid {\n\tg.items = append(g.items, &gridItem{\n\t\tItem:          p,\n\t\tRow:           row,\n\t\tColumn:        column,\n\t\tHeight:        rowSpan,\n\t\tWidth:         colSpan,\n\t\tMinGridHeight: minGridHeight,\n\t\tMinGridWidth:  minGridWidth,\n\t\tFocus:         focus,\n\t})\n\treturn g\n}\n\n// RemoveItem removes all items for the given primitive from the grid, keeping\n// the order of the remaining items intact.\nfunc (g *Grid) RemoveItem(p Primitive) *Grid {\n\tfor index := len(g.items) - 1; index >= 0; index-- {\n\t\tif g.items[index].Item == p {\n\t\t\tg.items = append(g.items[:index], g.items[index+1:]...)\n\t\t}\n\t}\n\treturn g\n}\n\n// Clear removes all items from the grid.\nfunc (g *Grid) Clear() *Grid {\n\tg.items = nil\n\treturn g\n}\n\n// SetOffset sets the number of rows and columns which are skipped before\n// drawing the first grid cell in the top-left corner. As the grid will never\n// completely move off the screen, these values may be adjusted the next time\n// the grid is drawn. The actual position of the grid may also be adjusted such\n// that contained primitives that have focus remain visible.\nfunc (g *Grid) SetOffset(rows, columns int) *Grid {\n\tg.rowOffset, g.columnOffset = rows, columns\n\treturn g\n}\n\n// GetOffset returns the current row and column offset (see SetOffset() for\n// details).\nfunc (g *Grid) GetOffset() (rows, columns int) {\n\treturn g.rowOffset, g.columnOffset\n}\n\n// Focus is called when this primitive receives focus.\nfunc (g *Grid) Focus(delegate func(p Primitive)) {\n\tfor _, item := range g.items {\n\t\tif item.Focus {\n\t\t\tdelegate(item.Item)\n\t\t\treturn\n\t\t}\n\t}\n\tg.Box.Focus(delegate)\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (g *Grid) focusChain(chain *[]Primitive) bool {\n\tfor _, item := range g.items {\n\t\tif !item.visible {\n\t\t\tcontinue\n\t\t}\n\t\tif hasFocus := item.Item.focusChain(chain); hasFocus {\n\t\t\tif chain != nil {\n\t\t\t\t*chain = append(*chain, g)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\treturn g.Box.focusChain(chain)\n}\n\n// Draw draws this primitive onto the screen.\nfunc (g *Grid) Draw(screen tcell.Screen) {\n\tg.Box.dontClear = !g.Box.border // Avoid transparent parts inside a border.\n\tg.Box.DrawForSubclass(screen, g)\n\tx, y, width, height := g.GetInnerRect()\n\tscreenWidth, screenHeight := screen.Size()\n\n\t// Make a list of items which apply.\n\titems := make([]*gridItem, 0, len(g.items))\nItemLoop:\n\tfor _, item := range g.items {\n\t\titem.visible = false\n\t\tif item.Item == nil || item.Width <= 0 || item.Height <= 0 || width < item.MinGridWidth || height < item.MinGridHeight {\n\t\t\tcontinue // Disqualified.\n\t\t}\n\n\t\t// Check for overlaps and multiple layouts of the same item.\n\t\tfor index, existing := range items {\n\t\t\t// Do they overlap or are identical?\n\t\t\tif item.Item != existing.Item &&\n\t\t\t\t(item.Row >= existing.Row+existing.Height || item.Row+item.Height <= existing.Row ||\n\t\t\t\t\titem.Column >= existing.Column+existing.Width || item.Column+item.Width <= existing.Column) {\n\t\t\t\tcontinue // They don't and aren't.\n\t\t\t}\n\n\t\t\t// What's their minimum size?\n\t\t\titemMin := item.MinGridWidth\n\t\t\tif item.MinGridHeight > itemMin {\n\t\t\t\titemMin = item.MinGridHeight\n\t\t\t}\n\t\t\texistingMin := existing.MinGridWidth\n\t\t\tif existing.MinGridHeight > existingMin {\n\t\t\t\texistingMin = existing.MinGridHeight\n\t\t\t}\n\n\t\t\t// Which one is more important?\n\t\t\tif itemMin < existingMin {\n\t\t\t\tcontinue ItemLoop // This one isn't. Drop it.\n\t\t\t}\n\t\t\titems[index] = item // This one is. Replace the other.\n\t\t\tcontinue ItemLoop\n\t\t}\n\n\t\t// This item will be visible.\n\t\titems = append(items, item)\n\t}\n\n\t// How many rows and columns do we have?\n\trows := len(g.rows)\n\tcolumns := len(g.columns)\n\tfor _, item := range items {\n\t\trowEnd := item.Row + item.Height\n\t\tif rowEnd > rows {\n\t\t\trows = rowEnd\n\t\t}\n\t\tcolumnEnd := item.Column + item.Width\n\t\tif columnEnd > columns {\n\t\t\tcolumns = columnEnd\n\t\t}\n\t}\n\tif rows == 0 || columns == 0 {\n\t\treturn // No content.\n\t}\n\n\t// Where are they located?\n\trowPos := make([]int, rows)\n\trowHeight := make([]int, rows)\n\tcolumnPos := make([]int, columns)\n\tcolumnWidth := make([]int, columns)\n\n\t// How much space do we distribute?\n\tremainingWidth := width\n\tremainingHeight := height\n\tproportionalWidth := 0\n\tproportionalHeight := 0\n\tfor index, row := range g.rows {\n\t\tif row > 0 {\n\t\t\tif row < g.minHeight {\n\t\t\t\trow = g.minHeight\n\t\t\t}\n\t\t\tremainingHeight -= row\n\t\t\trowHeight[index] = row\n\t\t} else if row == 0 {\n\t\t\tproportionalHeight++\n\t\t} else {\n\t\t\tproportionalHeight += -row\n\t\t}\n\t}\n\tfor index, column := range g.columns {\n\t\tif column > 0 {\n\t\t\tif column < g.minWidth {\n\t\t\t\tcolumn = g.minWidth\n\t\t\t}\n\t\t\tremainingWidth -= column\n\t\t\tcolumnWidth[index] = column\n\t\t} else if column == 0 {\n\t\t\tproportionalWidth++\n\t\t} else {\n\t\t\tproportionalWidth += -column\n\t\t}\n\t}\n\tif g.borders {\n\t\tremainingHeight -= rows + 1\n\t\tremainingWidth -= columns + 1\n\t} else {\n\t\tremainingHeight -= (rows - 1) * g.gapRows\n\t\tremainingWidth -= (columns - 1) * g.gapColumns\n\t}\n\tif rows > len(g.rows) {\n\t\tproportionalHeight += rows - len(g.rows)\n\t}\n\tif columns > len(g.columns) {\n\t\tproportionalWidth += columns - len(g.columns)\n\t}\n\n\t// Distribute proportional rows/columns.\n\tfor index := 0; index < rows; index++ {\n\t\trow := 0\n\t\tif index < len(g.rows) {\n\t\t\trow = g.rows[index]\n\t\t}\n\t\tif row > 0 {\n\t\t\tcontinue // Not proportional. We already know the width.\n\t\t} else if row == 0 {\n\t\t\trow = 1\n\t\t} else {\n\t\t\trow = -row\n\t\t}\n\t\trowAbs := row * remainingHeight / proportionalHeight\n\t\tremainingHeight -= rowAbs\n\t\tproportionalHeight -= row\n\t\tif rowAbs < g.minHeight {\n\t\t\trowAbs = g.minHeight\n\t\t}\n\t\trowHeight[index] = rowAbs\n\t}\n\tfor index := 0; index < columns; index++ {\n\t\tcolumn := 0\n\t\tif index < len(g.columns) {\n\t\t\tcolumn = g.columns[index]\n\t\t}\n\t\tif column > 0 {\n\t\t\tcontinue // Not proportional. We already know the height.\n\t\t} else if column == 0 {\n\t\t\tcolumn = 1\n\t\t} else {\n\t\t\tcolumn = -column\n\t\t}\n\t\tcolumnAbs := column * remainingWidth / proportionalWidth\n\t\tremainingWidth -= columnAbs\n\t\tproportionalWidth -= column\n\t\tif columnAbs < g.minWidth {\n\t\t\tcolumnAbs = g.minWidth\n\t\t}\n\t\tcolumnWidth[index] = columnAbs\n\t}\n\n\t// Calculate row/column positions.\n\tvar columnX, rowY int\n\tif g.borders {\n\t\tcolumnX++\n\t\trowY++\n\t}\n\tfor index, row := range rowHeight {\n\t\trowPos[index] = rowY\n\t\tgap := g.gapRows\n\t\tif g.borders {\n\t\t\tgap = 1\n\t\t}\n\t\trowY += row + gap\n\t}\n\tfor index, column := range columnWidth {\n\t\tcolumnPos[index] = columnX\n\t\tgap := g.gapColumns\n\t\tif g.borders {\n\t\t\tgap = 1\n\t\t}\n\t\tcolumnX += column + gap\n\t}\n\n\t// Calculate primitive positions.\n\tvar focus *gridItem // The item which has focus.\n\tfor _, item := range items {\n\t\tpx := columnPos[item.Column]\n\t\tpy := rowPos[item.Row]\n\t\tvar pw, ph int\n\t\tfor index := 0; index < item.Height; index++ {\n\t\t\tph += rowHeight[item.Row+index]\n\t\t}\n\t\tfor index := 0; index < item.Width; index++ {\n\t\t\tpw += columnWidth[item.Column+index]\n\t\t}\n\t\tif g.borders {\n\t\t\tpw += item.Width - 1\n\t\t\tph += item.Height - 1\n\t\t} else {\n\t\t\tpw += (item.Width - 1) * g.gapColumns\n\t\t\tph += (item.Height - 1) * g.gapRows\n\t\t}\n\t\titem.x, item.y, item.w, item.h = px, py, pw, ph\n\t\titem.visible = true\n\t\tif item.Item.HasFocus() {\n\t\t\tfocus = item\n\t\t}\n\t}\n\n\t// Calculate screen offsets.\n\tvar offsetX, offsetY int\n\tadd := 1\n\tif !g.borders {\n\t\tadd = g.gapRows\n\t}\n\tfor index, height := range rowHeight {\n\t\tif index >= g.rowOffset {\n\t\t\tbreak\n\t\t}\n\t\toffsetY += height + add\n\t}\n\tif !g.borders {\n\t\tadd = g.gapColumns\n\t}\n\tfor index, width := range columnWidth {\n\t\tif index >= g.columnOffset {\n\t\t\tbreak\n\t\t}\n\t\toffsetX += width + add\n\t}\n\n\t// The focused item must be within the visible area.\n\tif focus != nil {\n\t\tif focus.y+focus.h-offsetY >= height {\n\t\t\toffsetY = focus.y - height + focus.h\n\t\t}\n\t\tif focus.y-offsetY < 0 {\n\t\t\toffsetY = focus.y\n\t\t}\n\t\tif focus.x+focus.w-offsetX >= width {\n\t\t\toffsetX = focus.x - width + focus.w\n\t\t}\n\t\tif focus.x-offsetX < 0 {\n\t\t\toffsetX = focus.x\n\t\t}\n\t}\n\n\t// Adjust row/column offsets based on this value.\n\tvar from, to int\n\tfor index, pos := range rowPos {\n\t\tif pos-offsetY < 0 {\n\t\t\tfrom = index + 1\n\t\t}\n\t\tif pos-offsetY < height {\n\t\t\tto = index\n\t\t}\n\t}\n\tif g.rowOffset < from {\n\t\tg.rowOffset = from\n\t}\n\tif g.rowOffset > to {\n\t\tg.rowOffset = to\n\t}\n\tfrom, to = 0, 0\n\tfor index, pos := range columnPos {\n\t\tif pos-offsetX < 0 {\n\t\t\tfrom = index + 1\n\t\t}\n\t\tif pos-offsetX < width {\n\t\t\tto = index\n\t\t}\n\t}\n\tif g.columnOffset < from {\n\t\tg.columnOffset = from\n\t}\n\tif g.columnOffset > to {\n\t\tg.columnOffset = to\n\t}\n\n\t// Draw primitives and borders.\n\tborderStyle := tcell.StyleDefault.Background(g.backgroundColor).Foreground(g.bordersColor)\n\tfor _, item := range items {\n\t\t// Final primitive position.\n\t\tif !item.visible {\n\t\t\tcontinue\n\t\t}\n\t\titem.x -= offsetX\n\t\titem.y -= offsetY\n\t\tif item.x >= width || item.x+item.w <= 0 || item.y >= height || item.y+item.h <= 0 {\n\t\t\titem.visible = false\n\t\t\tcontinue\n\t\t}\n\t\tif item.x+item.w > width {\n\t\t\titem.w = width - item.x\n\t\t}\n\t\tif item.y+item.h > height {\n\t\t\titem.h = height - item.y\n\t\t}\n\t\tif item.x < 0 {\n\t\t\titem.w += item.x\n\t\t\titem.x = 0\n\t\t}\n\t\tif item.y < 0 {\n\t\t\titem.h += item.y\n\t\t\titem.y = 0\n\t\t}\n\t\tif item.w <= 0 || item.h <= 0 {\n\t\t\titem.visible = false\n\t\t\tcontinue\n\t\t}\n\t\titem.x += x\n\t\titem.y += y\n\t\titem.Item.SetRect(item.x, item.y, item.w, item.h)\n\n\t\t// Draw primitive.\n\t\tif item == focus {\n\t\t\tdefer item.Item.Draw(screen)\n\t\t} else {\n\t\t\titem.Item.Draw(screen)\n\t\t}\n\n\t\t// Draw border around primitive.\n\t\tif g.borders {\n\t\t\tfor bx := item.x; bx < item.x+item.w; bx++ { // Top/bottom lines.\n\t\t\t\tif bx < 0 || bx >= screenWidth {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tby := item.y - 1\n\t\t\t\tif by >= 0 && by < screenHeight {\n\t\t\t\t\tPrintJoinedSemigraphics(screen, bx, by, Borders.Horizontal, borderStyle)\n\t\t\t\t}\n\t\t\t\tby = item.y + item.h\n\t\t\t\tif by >= 0 && by < screenHeight {\n\t\t\t\t\tPrintJoinedSemigraphics(screen, bx, by, Borders.Horizontal, borderStyle)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor by := item.y; by < item.y+item.h; by++ { // Left/right lines.\n\t\t\t\tif by < 0 || by >= screenHeight {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbx := item.x - 1\n\t\t\t\tif bx >= 0 && bx < screenWidth {\n\t\t\t\t\tPrintJoinedSemigraphics(screen, bx, by, Borders.Vertical, borderStyle)\n\t\t\t\t}\n\t\t\t\tbx = item.x + item.w\n\t\t\t\tif bx >= 0 && bx < screenWidth {\n\t\t\t\t\tPrintJoinedSemigraphics(screen, bx, by, Borders.Vertical, borderStyle)\n\t\t\t\t}\n\t\t\t}\n\t\t\tbx, by := item.x-1, item.y-1 // Top-left corner.\n\t\t\tif bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {\n\t\t\t\tPrintJoinedSemigraphics(screen, bx, by, Borders.TopLeft, borderStyle)\n\t\t\t}\n\t\t\tbx, by = item.x+item.w, item.y-1 // Top-right corner.\n\t\t\tif bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {\n\t\t\t\tPrintJoinedSemigraphics(screen, bx, by, Borders.TopRight, borderStyle)\n\t\t\t}\n\t\t\tbx, by = item.x-1, item.y+item.h // Bottom-left corner.\n\t\t\tif bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {\n\t\t\t\tPrintJoinedSemigraphics(screen, bx, by, Borders.BottomLeft, borderStyle)\n\t\t\t}\n\t\t\tbx, by = item.x+item.w, item.y+item.h // Bottom-right corner.\n\t\t\tif bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {\n\t\t\t\tPrintJoinedSemigraphics(screen, bx, by, Borders.BottomRight, borderStyle)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (g *Grid) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn g.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif !g.InRect(event.Position()) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Pass mouse events along to the first child item that takes it.\n\t\tfor _, item := range g.items {\n\t\t\tif item.Item == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconsumed, capture = item.Item.MouseHandler()(action, event, setFocus)\n\t\t\tif consumed {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t})\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn g.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tif !g.hasFocus {\n\t\t\t// Pass event on to child primitive.\n\t\t\tfor _, item := range g.items {\n\t\t\t\tif item != nil && item.Item.HasFocus() {\n\t\t\t\t\tif handler := item.Item.InputHandler(); handler != nil {\n\t\t\t\t\t\thandler(event, setFocus)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// Process our own key events if we have direct focus.\n\t\tswitch event.Key() {\n\t\tcase tcell.KeyRune:\n\t\t\tswitch event.Rune() {\n\t\t\tcase 'g':\n\t\t\t\tg.rowOffset, g.columnOffset = 0, 0\n\t\t\tcase 'G':\n\t\t\t\tg.rowOffset = math.MaxInt32\n\t\t\tcase 'j':\n\t\t\t\tg.rowOffset++\n\t\t\tcase 'k':\n\t\t\t\tg.rowOffset--\n\t\t\tcase 'h':\n\t\t\t\tg.columnOffset--\n\t\t\tcase 'l':\n\t\t\t\tg.columnOffset++\n\t\t\t}\n\t\tcase tcell.KeyHome:\n\t\t\tg.rowOffset, g.columnOffset = 0, 0\n\t\tcase tcell.KeyEnd:\n\t\t\tg.rowOffset = math.MaxInt32\n\t\tcase tcell.KeyUp:\n\t\t\tg.rowOffset--\n\t\tcase tcell.KeyDown:\n\t\t\tg.rowOffset++\n\t\tcase tcell.KeyLeft:\n\t\t\tg.columnOffset--\n\t\tcase tcell.KeyRight:\n\t\t\tg.columnOffset++\n\t\t}\n\t})\n}\n\n// PasteHandler returns the handler for this primitive.\nfunc (g *Grid) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {\n\treturn g.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {\n\t\tfor _, item := range g.items {\n\t\t\tif item != nil && item.Item.HasFocus() {\n\t\t\t\tif handler := item.Item.PasteHandler(); handler != nil {\n\t\t\t\t\thandler(pastedText, setFocus)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "image.go",
    "content": "package tview\n\nimport (\n\t\"image\"\n\t\"math\"\n\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// Types of dithering applied to images.\nconst (\n\tDitheringNone           = iota // No dithering.\n\tDitheringFloydSteinberg        // Floyd-Steinberg dithering (the default).\n)\n\n// The number of colors supported by true color terminals (R*G*B = 256*256*256).\nconst TrueColor = 16777216\n\n// This map describes what each block element looks like. A 1 bit represents a\n// pixel that is drawn, a 0 bit represents a pixel that is not drawn. The least\n// significant bit is the top left pixel, the most significant bit is the bottom\n// right pixel, moving row by row from left to right, top to bottom.\nvar blockElements = map[rune]uint64{\n\tBlockLowerOneEighthBlock:            0b1111111100000000000000000000000000000000000000000000000000000000,\n\tBlockLowerOneQuarterBlock:           0b1111111111111111000000000000000000000000000000000000000000000000,\n\tBlockLowerThreeEighthsBlock:         0b1111111111111111111111110000000000000000000000000000000000000000,\n\tBlockLowerHalfBlock:                 0b1111111111111111111111111111111100000000000000000000000000000000,\n\tBlockLowerFiveEighthsBlock:          0b1111111111111111111111111111111111111111000000000000000000000000,\n\tBlockLowerThreeQuartersBlock:        0b1111111111111111111111111111111111111111111111110000000000000000,\n\tBlockLowerSevenEighthsBlock:         0b1111111111111111111111111111111111111111111111111111111100000000,\n\tBlockLeftSevenEighthsBlock:          0b0111111101111111011111110111111101111111011111110111111101111111,\n\tBlockLeftThreeQuartersBlock:         0b0011111100111111001111110011111100111111001111110011111100111111,\n\tBlockLeftFiveEighthsBlock:           0b0001111100011111000111110001111100011111000111110001111100011111,\n\tBlockLeftHalfBlock:                  0b0000111100001111000011110000111100001111000011110000111100001111,\n\tBlockLeftThreeEighthsBlock:          0b0000011100000111000001110000011100000111000001110000011100000111,\n\tBlockLeftOneQuarterBlock:            0b0000001100000011000000110000001100000011000000110000001100000011,\n\tBlockLeftOneEighthBlock:             0b0000000100000001000000010000000100000001000000010000000100000001,\n\tBlockQuadrantLowerLeft:              0b0000111100001111000011110000111100000000000000000000000000000000,\n\tBlockQuadrantLowerRight:             0b1111000011110000111100001111000000000000000000000000000000000000,\n\tBlockQuadrantUpperLeft:              0b0000000000000000000000000000000000001111000011110000111100001111,\n\tBlockQuadrantUpperRight:             0b0000000000000000000000000000000011110000111100001111000011110000,\n\tBlockQuadrantUpperLeftAndLowerRight: 0b1111000011110000111100001111000000001111000011110000111100001111,\n}\n\n// pixel represents a character on screen used to draw part of an image.\ntype pixel struct {\n\tstyle   tcell.Style\n\telement rune // The block element.\n}\n\n// Image implements a widget that displays one image. The original image\n// (specified with [Image.SetImage]) is resized according to the specified size\n// (see [Image.SetSize]), using the specified number of colors (see\n// [Image.SetColors]), while applying dithering if necessary (see\n// [Image.SetDithering]).\n//\n// Images are approximated by graphical characters in the terminal. The\n// resolution is therefore limited by the number and type of characters that can\n// be drawn in the terminal and the colors available in the terminal. The\n// quality of the final image also depends on the terminal's font and spacing\n// settings, none of which are under the control of this package. Results may\n// vary.\ntype Image struct {\n\t*Box\n\n\t// The image to be displayed. If nil, the widget will be empty.\n\timage image.Image\n\n\t// The size of the image. If a value is 0, the corresponding size is chosen\n\t// automatically based on the other size while preserving the image's aspect\n\t// ratio. If both are 0, the image uses as much space as possible. A\n\t// negative value represents a percentage, e.g. -50 means 50% of the\n\t// available space.\n\twidth, height int\n\n\t// The number of colors to use. If 0, the number of colors is chosen based\n\t// on the terminal's capabilities.\n\tcolors int\n\n\t// The dithering algorithm to use, one of the constants starting with\n\t// \"ImageDithering\".\n\tdithering int\n\n\t// The width of a terminal's cell divided by its height.\n\taspectRatio float64\n\n\t// Horizontal and vertical alignment, one of the \"Align\" constants.\n\talignHorizontal, alignVertical int\n\n\t// The text to be displayed before the image.\n\tlabel string\n\n\t// The label style.\n\tlabelStyle tcell.Style\n\n\t// The screen width of the label area. A value of 0 means use the width of\n\t// the label text.\n\tlabelWidth int\n\n\t// The actual image size (in cells) when it was drawn the last time.\n\tlastWidth, lastHeight int\n\n\t// The actual image (in cells) when it was drawn the last time. The size of\n\t// this slice is lastWidth * lastHeight, indexed by y*lastWidth + x.\n\tpixels []pixel\n\n\t// A callback function set by the Form class and called when the user leaves\n\t// this form item.\n\tfinished func(tcell.Key)\n}\n\n// NewImage returns a new [Image] widget with an empty image (use\n// [Image.SetImage] to specify the image to be displayed). The image will use\n// the widget's entire available space. The default dithering algorithm is set\n// to Floyd-Steinberg dithering. The terminal's cell aspect ratio defaults to\n// 0.5.\nfunc NewImage() *Image {\n\ti := &Image{\n\t\tBox:             NewBox(),\n\t\tdithering:       DitheringFloydSteinberg,\n\t\taspectRatio:     0.5,\n\t\talignHorizontal: AlignCenter,\n\t\talignVertical:   AlignCenter,\n\t}\n\ti.Box.Primitive = i\n\treturn i\n}\n\n// SetImage sets the image to be displayed. If nil, the widget will be empty.\nfunc (i *Image) SetImage(image image.Image) *Image {\n\ti.image = image\n\ti.lastWidth, i.lastHeight = 0, 0\n\treturn i\n}\n\n// SetSize sets the size of the image. Positive values refer to cells in the\n// terminal. Negative values refer to a percentage of the available space (e.g.\n// -50 means 50%). A value of 0 means that the corresponding size is chosen\n// automatically based on the other size while preserving the image's aspect\n// ratio. If both are 0, the image uses as much space as possible while still\n// preserving the aspect ratio.\nfunc (i *Image) SetSize(rows, columns int) *Image {\n\ti.width = columns\n\ti.height = rows\n\treturn i\n}\n\n// SetColors sets the number of colors to use. This should be the number of\n// colors supported by the terminal. If 0, the number of colors is chosen based\n// on the TERM environment variable (which may or may not be reliable).\n//\n// Only the values 0, 2, 8, 256, and 16777216 ([TrueColor]) are supported. Other\n// values will be rounded up to the next supported value, to a maximum of\n// 16777216.\n//\n// The effect of using more colors than supported by the terminal is undefined.\nfunc (i *Image) SetColors(colors int) *Image {\n\ti.colors = colors\n\ti.lastWidth, i.lastHeight = 0, 0\n\treturn i\n}\n\n// GetColors returns the number of colors that will be used while drawing the\n// image. This is one of the values listed in [Image.SetColors], except 0 which\n// will be replaced by the actual number of colors used.\nfunc (i *Image) GetColors() int {\n\tswitch {\n\tcase i.colors == 0:\n\t\treturn availableColors\n\tcase i.colors <= 2:\n\t\treturn 2\n\tcase i.colors <= 8:\n\t\treturn 8\n\tcase i.colors <= 256:\n\t\treturn 256\n\t}\n\treturn TrueColor\n}\n\n// SetDithering sets the dithering algorithm to use, one of the constants\n// starting with \"Dithering\", for example [DitheringFloydSteinberg] (the\n// default). Dithering is not applied when rendering in true-color.\nfunc (i *Image) SetDithering(dithering int) *Image {\n\ti.dithering = dithering\n\ti.lastWidth, i.lastHeight = 0, 0\n\treturn i\n}\n\n// SetAspectRatio sets the width of a terminal's cell divided by its height.\n// You may change the default of 0.5 if your terminal / font has a different\n// aspect ratio. This is used to calculate the size of the image if the\n// specified width or height is 0. The function will panic if the aspect ratio\n// is 0 or less.\nfunc (i *Image) SetAspectRatio(aspectRatio float64) *Image {\n\tif aspectRatio <= 0 {\n\t\tpanic(\"aspect ratio must be greater than 0\")\n\t}\n\ti.aspectRatio = aspectRatio\n\ti.lastWidth, i.lastHeight = 0, 0\n\treturn i\n}\n\n// SetAlign sets the vertical and horizontal alignment of the image within the\n// widget's space. The possible values are [AlignTop], [AlignCenter], and\n// [AlignBottom] for vertical alignment and [AlignLeft], [AlignCenter], and\n// [AlignRight] for horizontal alignment. The default is [AlignCenter] for both\n// (or [AlignTop] and [AlignLeft] if the image is part of a [Form]).\nfunc (i *Image) SetAlign(vertical, horizontal int) *Image {\n\ti.alignHorizontal = horizontal\n\ti.alignVertical = vertical\n\treturn i\n}\n\n// SetLabel sets the text to be displayed before the image.\nfunc (i *Image) SetLabel(label string) *Image {\n\ti.label = label\n\treturn i\n}\n\n// GetLabel returns the text to be displayed before the image.\nfunc (i *Image) GetLabel() string {\n\treturn i.label\n}\n\n// SetLabelWidth sets the screen width of the label. A value of 0 will cause the\n// primitive to use the width of the label string.\nfunc (i *Image) SetLabelWidth(width int) *Image {\n\ti.labelWidth = width\n\treturn i\n}\n\n// GetFieldWidth returns this primitive's field width. This is the image's width\n// or, if the width is 0 or less, the proportional width of the image based on\n// its height as returned by [Image.GetFieldHeight]. If there is no image, 0 is\n// returned.\nfunc (i *Image) GetFieldWidth() int {\n\tif i.width <= 0 {\n\t\tif i.image == nil {\n\t\t\treturn 0\n\t\t}\n\t\tbounds := i.image.Bounds()\n\t\theight := i.GetFieldHeight()\n\t\treturn bounds.Dx() * height / bounds.Dy()\n\t}\n\treturn i.width\n}\n\n// GetFieldHeight returns this primitive's field height. This is the image's\n// height or 8 if the height is 0 or less.\nfunc (i *Image) GetFieldHeight() int {\n\tif i.height <= 0 {\n\t\treturn 8\n\t}\n\treturn i.height\n}\n\n// SetDisabled sets whether or not the item is disabled / read-only.\nfunc (i *Image) SetDisabled(disabled bool) FormItem {\n\treturn i // Images are always read-only.\n}\n\n// GetDisabled returns whether or not the item is disabled / read-only.\nfunc (i *Image) GetDisabled() bool {\n\treturn true // Images are always read-only.\n}\n\n// SetFormAttributes sets attributes shared by all form items.\nfunc (i *Image) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {\n\ti.labelWidth = labelWidth\n\ti.backgroundColor = bgColor\n\ti.SetLabelStyle(tcell.StyleDefault.Foreground(labelColor).Background(bgColor))\n\ti.lastWidth, i.lastHeight = 0, 0\n\treturn i\n}\n\n// SetLabelStyle sets the style of the label.\nfunc (i *Image) SetLabelStyle(style tcell.Style) *Image {\n\ti.labelStyle = style\n\treturn i\n}\n\n// GetLabelStyle returns the style of the label.\nfunc (i *Image) GetLabelStyle() tcell.Style {\n\treturn i.labelStyle\n}\n\n// SetFinishedFunc sets a callback invoked when the user leaves this form item.\nfunc (i *Image) SetFinishedFunc(handler func(key tcell.Key)) FormItem {\n\ti.finished = handler\n\treturn i\n}\n\n// Focus is called when this primitive receives focus.\nfunc (i *Image) Focus(delegate func(p Primitive)) {\n\t// If we're part of a form, there's nothing the user can do here so we're\n\t// finished.\n\tif i.finished != nil {\n\t\ti.finished(-1)\n\t\treturn\n\t}\n\n\ti.Box.Focus(delegate)\n}\n\n// render re-populates the [Image.pixels] slice based on the current settings,\n// if [Image.lastWidth] and [Image.lastHeight] don't match the current image's\n// size. It also sets the new image size in these two variables.\nfunc (i *Image) render() {\n\t// If there is no image, there are no pixels.\n\tif i.image == nil {\n\t\ti.pixels = nil\n\t\treturn\n\t}\n\n\t// Calculate the new (terminal-space) image size.\n\tbounds := i.image.Bounds()\n\timageWidth, imageHeight := bounds.Dx(), bounds.Dy()\n\tif i.aspectRatio != 1.0 {\n\t\timageWidth = int(float64(imageWidth) / i.aspectRatio)\n\t}\n\twidth, height := i.width, i.height\n\t_, _, innerWidth, innerHeight := i.GetInnerRect()\n\tif i.labelWidth > 0 {\n\t\tinnerWidth -= i.labelWidth\n\t} else {\n\t\tinnerWidth -= TaggedStringWidth(i.label)\n\t}\n\tif innerWidth <= 0 {\n\t\ti.pixels = nil\n\t\treturn\n\t}\n\tif width == 0 && height == 0 {\n\t\t// Use all available space.\n\t\twidth, height = innerWidth, innerHeight\n\t\tif adjustedWidth := imageWidth * height / imageHeight; adjustedWidth < width {\n\t\t\twidth = adjustedWidth\n\t\t} else {\n\t\t\theight = imageHeight * width / imageWidth\n\t\t}\n\t} else {\n\t\t// Turn percentages into absolute values.\n\t\tif width < 0 {\n\t\t\twidth = innerWidth * -width / 100\n\t\t}\n\t\tif height < 0 {\n\t\t\theight = innerHeight * -height / 100\n\t\t}\n\t\tif width == 0 {\n\t\t\t// Adjust the width.\n\t\t\twidth = imageWidth * height / imageHeight\n\t\t} else if height == 0 {\n\t\t\t// Adjust the height.\n\t\t\theight = imageHeight * width / imageWidth\n\t\t}\n\t}\n\tif width <= 0 || height <= 0 {\n\t\ti.pixels = nil\n\t\treturn\n\t}\n\n\t// If nothing has changed, we're done.\n\tif i.lastWidth == width && i.lastHeight == height {\n\t\treturn\n\t}\n\ti.lastWidth, i.lastHeight = width, height // This could still be larger than the available space but that's ok for now.\n\n\t// Generate the initial pixels by resizing the image (8x8 per cell).\n\tpixels := i.resize()\n\n\t// Turn them into block elements with background/foreground colors.\n\ti.stamp(pixels)\n}\n\n// resize resizes the image to the current size and returns the result as a\n// slice of pixels. It is assumed that [Image.lastWidth] (w) and\n// [Image.lastHeight] (h) are positive, non-zero values, and the slice has a\n// size of 64*w*h, with each pixel being represented by 3 float64 values in the\n// range of 0-1. The factor of 64 is due to the fact that we calculate 8x8\n// pixels per cell.\nfunc (i *Image) resize() [][3]float64 {\n\t// Because most of the time, we will be downsizing the image, we don't even\n\t// attempt to do any fancy interpolation. For each target pixel, we\n\t// calculate a weighted average of the source pixels using their coverage\n\t// area.\n\n\tbounds := i.image.Bounds()\n\tsrcWidth, srcHeight := bounds.Dx(), bounds.Dy()\n\ttgtWidth, tgtHeight := i.lastWidth*8, i.lastHeight*8\n\tcoverageWidth, coverageHeight := float64(tgtWidth)/float64(srcWidth), float64(tgtHeight)/float64(srcHeight)\n\tpixels := make([][3]float64, tgtWidth*tgtHeight)\n\tweights := make([]float64, tgtWidth*tgtHeight)\n\tfor srcY := bounds.Min.Y; srcY < bounds.Max.Y; srcY++ {\n\t\tfor srcX := bounds.Min.X; srcX < bounds.Max.X; srcX++ {\n\t\t\tr32, g32, b32, _ := i.image.At(srcX, srcY).RGBA()\n\t\t\tr, g, b := float64(r32)/0xffff, float64(g32)/0xffff, float64(b32)/0xffff\n\n\t\t\t// Iterate over all target pixels. Outer loop is Y.\n\t\t\tstartY := float64(srcY-bounds.Min.Y) * coverageHeight\n\t\t\tendY := startY + coverageHeight\n\t\t\tfromY, toY := int(startY), int(endY)\n\t\t\tfor tgtY := fromY; tgtY <= toY && tgtY < tgtHeight; tgtY++ {\n\t\t\t\tcoverageY := 1.0\n\t\t\t\tif tgtY == fromY {\n\t\t\t\t\tcoverageY -= math.Mod(startY, 1.0)\n\t\t\t\t}\n\t\t\t\tif tgtY == toY {\n\t\t\t\t\tcoverageY -= 1.0 - math.Mod(endY, 1.0)\n\t\t\t\t}\n\n\t\t\t\t// Inner loop is X.\n\t\t\t\tstartX := float64(srcX-bounds.Min.X) * coverageWidth\n\t\t\t\tendX := startX + coverageWidth\n\t\t\t\tfromX, toX := int(startX), int(endX)\n\t\t\t\tfor tgtX := fromX; tgtX <= toX && tgtX < tgtWidth; tgtX++ {\n\t\t\t\t\tcoverageX := 1.0\n\t\t\t\t\tif tgtX == fromX {\n\t\t\t\t\t\tcoverageX -= math.Mod(startX, 1.0)\n\t\t\t\t\t}\n\t\t\t\t\tif tgtX == toX {\n\t\t\t\t\t\tcoverageX -= 1.0 - math.Mod(endX, 1.0)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add a weighted contribution to the target pixel.\n\t\t\t\t\tindex := tgtY*tgtWidth + tgtX\n\t\t\t\t\tcoverage := coverageX * coverageY\n\t\t\t\t\tpixels[index][0] += r * coverage\n\t\t\t\t\tpixels[index][1] += g * coverage\n\t\t\t\t\tpixels[index][2] += b * coverage\n\t\t\t\t\tweights[index] += coverage\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Normalize the pixels.\n\tfor index, weight := range weights {\n\t\tif weight > 0 {\n\t\t\tpixels[index][0] /= weight\n\t\t\tpixels[index][1] /= weight\n\t\t\tpixels[index][2] /= weight\n\t\t}\n\t}\n\n\treturn pixels\n}\n\n// stamp takes the pixels generated by [Image.resize] and populates the\n// [Image.pixels] slice accordingly.\nfunc (i *Image) stamp(resized [][3]float64) {\n\t// For each 8x8 pixel block, we find the best block element to represent it,\n\t// given the available colors.\n\ti.pixels = make([]pixel, i.lastWidth*i.lastHeight)\n\tcolors := i.GetColors()\n\tfor row := 0; row < i.lastHeight; row++ {\n\t\tfor col := 0; col < i.lastWidth; col++ {\n\t\t\t// Calculate an error for each potential block element + color. Keep\n\t\t\t// the one with the lowest error.\n\n\t\t\t// Note that the values in \"resize\" may lie outside [0, 1] due to\n\t\t\t// the error distribution during dithering.\n\n\t\t\tminMSE := math.MaxFloat64 // Mean squared error.\n\t\t\tvar final [64][3]float64  // The final pixel values.\n\t\t\tfor element, bits := range blockElements {\n\t\t\t\t// Calculate the average color for the pixels covered by the set\n\t\t\t\t// bits and unset bits.\n\t\t\t\tvar (\n\t\t\t\t\tbg, fg  [3]float64\n\t\t\t\t\tsetBits float64\n\t\t\t\t\tbit     uint64 = 1\n\t\t\t\t)\n\t\t\t\tfor y := 0; y < 8; y++ {\n\t\t\t\t\tfor x := 0; x < 8; x++ {\n\t\t\t\t\t\tindex := (row*8+y)*i.lastWidth*8 + (col*8 + x)\n\t\t\t\t\t\tif bits&bit != 0 {\n\t\t\t\t\t\t\tfg[0] += resized[index][0]\n\t\t\t\t\t\t\tfg[1] += resized[index][1]\n\t\t\t\t\t\t\tfg[2] += resized[index][2]\n\t\t\t\t\t\t\tsetBits++\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbg[0] += resized[index][0]\n\t\t\t\t\t\t\tbg[1] += resized[index][1]\n\t\t\t\t\t\t\tbg[2] += resized[index][2]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbit <<= 1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor ch := 0; ch < 3; ch++ {\n\t\t\t\t\tfg[ch] /= setBits\n\t\t\t\t\tif fg[ch] < 0 {\n\t\t\t\t\t\tfg[ch] = 0\n\t\t\t\t\t} else if fg[ch] > 1 {\n\t\t\t\t\t\tfg[ch] = 1\n\t\t\t\t\t}\n\t\t\t\t\tbg[ch] /= 64 - setBits\n\t\t\t\t\tif bg[ch] < 0 {\n\t\t\t\t\t\tbg[ch] = 0\n\t\t\t\t\t}\n\t\t\t\t\tif bg[ch] > 1 {\n\t\t\t\t\t\tbg[ch] = 1\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Quantize to the nearest acceptable color.\n\t\t\t\tfor _, color := range []*[3]float64{&fg, &bg} {\n\t\t\t\t\tif colors == 2 {\n\t\t\t\t\t\t// Monochrome. The following weights correspond better\n\t\t\t\t\t\t// to human perception than the arithmetic mean.\n\t\t\t\t\t\tgray := 0.299*color[0] + 0.587*color[1] + 0.114*color[2]\n\t\t\t\t\t\tif gray < 0.5 {\n\t\t\t\t\t\t\t*color = [3]float64{0, 0, 0}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t*color = [3]float64{1, 1, 1}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfor index, ch := range color {\n\t\t\t\t\t\t\tswitch {\n\t\t\t\t\t\t\tcase colors == 8:\n\t\t\t\t\t\t\t\t// Colors vary wildly for each terminal. Expect\n\t\t\t\t\t\t\t\t// suboptimal results.\n\t\t\t\t\t\t\t\tif ch < 0.5 {\n\t\t\t\t\t\t\t\t\tcolor[index] = 0\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tcolor[index] = 1\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase colors == 256:\n\t\t\t\t\t\t\t\tcolor[index] = math.Round(ch*6) / 6\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Calculate the error (and the final pixel values).\n\t\t\t\tvar (\n\t\t\t\t\tmse         float64\n\t\t\t\t\tvalues      [64][3]float64\n\t\t\t\t\tvaluesIndex int\n\t\t\t\t)\n\t\t\t\tbit = 1\n\t\t\t\tfor y := 0; y < 8; y++ {\n\t\t\t\t\tfor x := 0; x < 8; x++ {\n\t\t\t\t\t\tif bits&bit != 0 {\n\t\t\t\t\t\t\tvalues[valuesIndex] = fg\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tvalues[valuesIndex] = bg\n\t\t\t\t\t\t}\n\t\t\t\t\t\tindex := (row*8+y)*i.lastWidth*8 + (col*8 + x)\n\t\t\t\t\t\tfor ch := 0; ch < 3; ch++ {\n\t\t\t\t\t\t\terr := resized[index][ch] - values[valuesIndex][ch]\n\t\t\t\t\t\t\tmse += err * err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbit <<= 1\n\t\t\t\t\t\tvaluesIndex++\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Do we have a better match?\n\t\t\t\tif mse < minMSE {\n\t\t\t\t\t// Yes. Save it.\n\t\t\t\t\tminMSE = mse\n\t\t\t\t\tfinal = values\n\t\t\t\t\tindex := row*i.lastWidth + col\n\t\t\t\t\ti.pixels[index].element = element\n\t\t\t\t\ti.pixels[index].style = tcell.StyleDefault.\n\t\t\t\t\t\tForeground(tcell.NewRGBColor(int32(math.Min(255, fg[0]*255)), int32(math.Min(255, fg[1]*255)), int32(math.Min(255, fg[2]*255)))).\n\t\t\t\t\t\tBackground(tcell.NewRGBColor(int32(math.Min(255, bg[0]*255)), int32(math.Min(255, bg[1]*255)), int32(math.Min(255, bg[2]*255))))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check if there is a shade block which results in a smaller error.\n\n\t\t\t// What's the overall average color?\n\t\t\tvar avg [3]float64\n\t\t\tfor y := 0; y < 8; y++ {\n\t\t\t\tfor x := 0; x < 8; x++ {\n\t\t\t\t\tindex := (row*8+y)*i.lastWidth*8 + (col*8 + x)\n\t\t\t\t\tfor ch := 0; ch < 3; ch++ {\n\t\t\t\t\t\tavg[ch] += resized[index][ch] / 64\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor ch := 0; ch < 3; ch++ {\n\t\t\t\tif avg[ch] < 0 {\n\t\t\t\t\tavg[ch] = 0\n\t\t\t\t} else if avg[ch] > 1 {\n\t\t\t\t\tavg[ch] = 1\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Quantize and choose shade element.\n\t\t\telement := BlockFullBlock\n\t\t\tvar fg, bg tcell.Color\n\t\t\tshades := []rune{' ', BlockLightShade, BlockMediumShade, BlockDarkShade, BlockFullBlock}\n\t\t\tif colors == 2 {\n\t\t\t\t// Monochrome.\n\t\t\t\tgray := 0.299*avg[0] + 0.587*avg[1] + 0.114*avg[2] // See above for details.\n\t\t\t\tshade := int(math.Round(gray * 4))\n\t\t\t\telement = shades[shade]\n\t\t\t\tfor ch := 0; ch < 3; ch++ {\n\t\t\t\t\tavg[ch] = float64(shade) / 4\n\t\t\t\t}\n\t\t\t\tbg = tcell.ColorBlack\n\t\t\t\tfg = tcell.ColorWhite\n\t\t\t} else if colors == TrueColor {\n\t\t\t\t// True color.\n\t\t\t\tfg = tcell.NewRGBColor(int32(math.Min(255, avg[0]*255)), int32(math.Min(255, avg[1]*255)), int32(math.Min(255, avg[2]*255)))\n\t\t\t\tbg = fg\n\t\t\t} else {\n\t\t\t\t// 8 or 256 colors.\n\t\t\t\tsteps := 1.0\n\t\t\t\tif colors == 256 {\n\t\t\t\t\tsteps = 6.0\n\t\t\t\t}\n\t\t\t\tvar (\n\t\t\t\t\tlo, hi, pos [3]float64\n\t\t\t\t\tshade       float64\n\t\t\t\t)\n\t\t\t\tfor ch := 0; ch < 3; ch++ {\n\t\t\t\t\tlo[ch] = math.Floor(avg[ch]*steps) / steps\n\t\t\t\t\thi[ch] = math.Ceil(avg[ch]*steps) / steps\n\t\t\t\t\tif r := hi[ch] - lo[ch]; r > 0 {\n\t\t\t\t\t\tpos[ch] = (avg[ch] - lo[ch]) / r\n\t\t\t\t\t\tif math.Abs(pos[ch]-0.5) < math.Abs(shade-0.5) {\n\t\t\t\t\t\t\tshade = pos[ch]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tshade = math.Round(shade * 4)\n\t\t\t\telement = shades[int(shade)]\n\t\t\t\tshade /= 4\n\t\t\t\tfor ch := 0; ch < 3; ch++ { // Find the closest channel value.\n\t\t\t\t\tbest := math.Abs(avg[ch] - (lo[ch] + (hi[ch]-lo[ch])*shade)) // Start shade from lo to hi.\n\t\t\t\t\tif value := math.Abs(avg[ch] - (hi[ch] - (hi[ch]-lo[ch])*shade)); value < best {\n\t\t\t\t\t\tbest = value // Swap lo and hi.\n\t\t\t\t\t\tlo[ch], hi[ch] = hi[ch], lo[ch]\n\t\t\t\t\t}\n\t\t\t\t\tif value := math.Abs(avg[ch] - lo[ch]); value < best {\n\t\t\t\t\t\tbest = value // Use lo.\n\t\t\t\t\t\thi[ch] = lo[ch]\n\t\t\t\t\t}\n\t\t\t\t\tif value := math.Abs(avg[ch] - hi[ch]); value < best {\n\t\t\t\t\t\tlo[ch] = hi[ch] // Use hi.\n\t\t\t\t\t}\n\t\t\t\t\tavg[ch] = lo[ch] + (hi[ch]-lo[ch])*shade // Quantize.\n\t\t\t\t}\n\t\t\t\tbg = tcell.NewRGBColor(int32(math.Min(255, lo[0]*255)), int32(math.Min(255, lo[1]*255)), int32(math.Min(255, lo[2]*255)))\n\t\t\t\tfg = tcell.NewRGBColor(int32(math.Min(255, hi[0]*255)), int32(math.Min(255, hi[1]*255)), int32(math.Min(255, hi[2]*255)))\n\t\t\t}\n\n\t\t\t// Calculate the error (and the final pixel values).\n\t\t\tvar (\n\t\t\t\tmse         float64\n\t\t\t\tvalues      [64][3]float64\n\t\t\t\tvaluesIndex int\n\t\t\t)\n\t\t\tfor y := 0; y < 8; y++ {\n\t\t\t\tfor x := 0; x < 8; x++ {\n\t\t\t\t\tindex := (row*8+y)*i.lastWidth*8 + (col*8 + x)\n\t\t\t\t\tfor ch := 0; ch < 3; ch++ {\n\t\t\t\t\t\terr := resized[index][ch] - avg[ch]\n\t\t\t\t\t\tmse += err * err\n\t\t\t\t\t}\n\t\t\t\t\tvalues[valuesIndex] = avg\n\t\t\t\t\tvaluesIndex++\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Is this shade element better than the block element?\n\t\t\tif mse < minMSE {\n\t\t\t\t// Yes. Save it.\n\t\t\t\tfinal = values\n\t\t\t\tindex := row*i.lastWidth + col\n\t\t\t\ti.pixels[index].element = element\n\t\t\t\ti.pixels[index].style = tcell.StyleDefault.Foreground(fg).Background(bg)\n\t\t\t}\n\n\t\t\t// Apply dithering.\n\t\t\tif colors < TrueColor && i.dithering == DitheringFloydSteinberg {\n\t\t\t\t// The dithering mask determines how the error is distributed.\n\t\t\t\t// Each element has three values: dx, dy, and weight (in 16th).\n\t\t\t\tvar mask = [4][3]int{\n\t\t\t\t\t{1, 0, 7},\n\t\t\t\t\t{-1, 1, 3},\n\t\t\t\t\t{0, 1, 5},\n\t\t\t\t\t{1, 1, 1},\n\t\t\t\t}\n\n\t\t\t\t// We dither the 8x8 block as a 2x2 block, transferring errors\n\t\t\t\t// to its 2x2 neighbors.\n\t\t\t\tfor ch := 0; ch < 3; ch++ {\n\t\t\t\t\tfor y := 0; y < 2; y++ {\n\t\t\t\t\t\tfor x := 0; x < 2; x++ {\n\t\t\t\t\t\t\t// What's the error for this 4x4 block?\n\t\t\t\t\t\t\tvar err float64\n\t\t\t\t\t\t\tfor dy := 0; dy < 4; dy++ {\n\t\t\t\t\t\t\t\tfor dx := 0; dx < 4; dx++ {\n\t\t\t\t\t\t\t\t\terr += (final[(y*4+dy)*8+(x*4+dx)][ch] - resized[(row*8+(y*4+dy))*i.lastWidth*8+(col*8+(x*4+dx))][ch]) / 16\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Distribute it to the 2x2 neighbors.\n\t\t\t\t\t\t\tfor _, dist := range mask {\n\t\t\t\t\t\t\t\tfor dy := 0; dy < 4; dy++ {\n\t\t\t\t\t\t\t\t\tfor dx := 0; dx < 4; dx++ {\n\t\t\t\t\t\t\t\t\t\ttargetX, targetY := (x+dist[0])*4+dx, (y+dist[1])*4+dy\n\t\t\t\t\t\t\t\t\t\tif targetX < 0 || col*8+targetX >= i.lastWidth*8 || targetY < 0 || row*8+targetY >= i.lastHeight*8 {\n\t\t\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tresized[(row*8+targetY)*i.lastWidth*8+(col*8+targetX)][ch] -= err * float64(dist[2]) / 16\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Draw draws this primitive onto the screen.\nfunc (i *Image) Draw(screen tcell.Screen) {\n\ti.DrawForSubclass(screen, i)\n\n\t// Regenerate image if necessary.\n\ti.render()\n\n\t// Draw label.\n\tviewX, viewY, viewWidth, viewHeight := i.GetInnerRect()\n\t_, labelBg, _ := i.labelStyle.Decompose()\n\tif i.labelWidth > 0 {\n\t\tlabelWidth := i.labelWidth\n\t\tif labelWidth > viewWidth {\n\t\t\tlabelWidth = viewWidth\n\t\t}\n\t\tprintWithStyle(screen, i.label, viewX, viewY, 0, labelWidth, AlignLeft, i.labelStyle, labelBg == tcell.ColorDefault)\n\t\tviewX += labelWidth\n\t\tviewWidth -= labelWidth\n\t} else {\n\t\t_, _, drawnWidth := printWithStyle(screen, i.label, viewX, viewY, 0, viewWidth, AlignLeft, i.labelStyle, labelBg == tcell.ColorDefault)\n\t\tviewX += drawnWidth\n\t\tviewWidth -= drawnWidth\n\t}\n\n\t// Determine image placement.\n\tx, y, width, height := viewX, viewY, i.lastWidth, i.lastHeight\n\tif i.alignHorizontal == AlignCenter {\n\t\tx += (viewWidth - width) / 2\n\t} else if i.alignHorizontal == AlignRight {\n\t\tx += viewWidth - width\n\t}\n\tif i.alignVertical == AlignCenter {\n\t\ty += (viewHeight - height) / 2\n\t} else if i.alignVertical == AlignBottom {\n\t\ty += viewHeight - height\n\t}\n\n\t// Draw the image.\n\tfor row := 0; row < height; row++ {\n\t\tif y+row < viewY || y+row >= viewY+viewHeight {\n\t\t\tcontinue\n\t\t}\n\t\tfor col := 0; col < width; col++ {\n\t\t\tif x+col < viewX || x+col >= viewX+viewWidth {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tindex := row*width + col\n\t\t\tscreen.SetContent(x+col, y+row, i.pixels[index].element, nil, i.pixels[index].style)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "inputfield.go",
    "content": "package tview\n\nimport (\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/uniseg\"\n)\n\nconst (\n\tAutocompletedNavigate = iota // The user navigated the autocomplete list (using the errow keys).\n\tAutocompletedTab             // The user selected an autocomplete entry using the tab key.\n\tAutocompletedEnter           // The user selected an autocomplete entry using the enter key.\n\tAutocompletedClick           // The user selected an autocomplete entry by clicking the mouse button on it.\n)\n\n// Predefined InputField acceptance functions.\nvar (\n\t// InputFieldInteger accepts integers.\n\tInputFieldInteger = func(text string, ch rune) bool {\n\t\tif text == \"-\" {\n\t\t\treturn true\n\t\t}\n\t\t_, err := strconv.Atoi(text)\n\t\treturn err == nil\n\t}\n\n\t// InputFieldFloat accepts floating-point numbers.\n\tInputFieldFloat = func(text string, ch rune) bool {\n\t\tif text == \"-\" || text == \".\" || text == \"-.\" {\n\t\t\treturn true\n\t\t}\n\t\t_, err := strconv.ParseFloat(text, 64)\n\t\treturn err == nil\n\t}\n\n\t// InputFieldMaxLength returns an input field accept handler which accepts\n\t// input strings up to a given length. Use it like this:\n\t//\n\t//   inputField.SetAcceptanceFunc(InputFieldMaxLength(10)) // Accept up to 10 characters.\n\tInputFieldMaxLength = func(maxLength int) func(text string, ch rune) bool {\n\t\treturn func(text string, ch rune) bool {\n\t\t\treturn len([]rune(text)) <= maxLength\n\t\t}\n\t}\n)\n\n// InputField is a one-line box into which the user can enter text. Use\n// [InputField.SetAcceptanceFunc] to accept or reject input,\n// [InputField.SetChangedFunc] to listen for changes, and\n// [InputField.SetMaskCharacter] to hide input from onlookers (e.g. for password\n// input).\n//\n// The input field also has an optional autocomplete feature. It is initialized\n// by the [InputField.SetAutocompleteFunc] function. For more control over the\n// autocomplete drop-down's behavior, you can also set the\n// [InputField.SetAutocompletedFunc].\n//\n// Navigation and editing is the same as for a [TextArea], with the following\n// exceptions:\n//\n//   - Tab, BackTab, Enter, Escape: Finish editing.\n//\n// Note that while pressing Tab or Enter is intercepted by the input field, it\n// is possible to paste such characters into the input field, possibly resulting\n// in multi-line input. You can use [InputField.SetAcceptanceFunc] to prevent\n// this.\n//\n// If autocomplete functionality is configured:\n//\n//   - Down arrow: Open the autocomplete drop-down.\n//   - Tab, Enter: Select the current autocomplete entry.\n//\n// See https://github.com/rivo/tview/wiki/InputField for an example.\ntype InputField struct {\n\t*Box\n\n\t// The text area providing the core functionality of the input field.\n\ttextArea *TextArea\n\n\t// The screen width of the input area. A value of 0 means extend as much as\n\t// possible.\n\tfieldWidth int\n\n\t// An optional autocomplete function which receives the current text of the\n\t// input field and returns a slice of strings to be displayed in a drop-down\n\t// selection.\n\tautocomplete func(text string) []string\n\n\t// The List object which shows the selectable autocomplete entries. If not\n\t// nil, the list's main texts represent the current autocomplete entries.\n\tautocompleteList      *List\n\tautocompleteListMutex sync.Mutex\n\n\t// The styles of the autocomplete entries.\n\tautocompleteStyles struct {\n\t\tmain       tcell.Style\n\t\tselected   tcell.Style\n\t\tbackground tcell.Color\n\t\tuseTags    bool\n\t}\n\n\t// An optional function which is called when the user selects an\n\t// autocomplete entry. The text and index of the selected entry (within the\n\t// list) is provided, as well as the user action causing the selection (one\n\t// of the \"Autocompleted\" values). The function should return true if the\n\t// autocomplete list should be closed. If nil, the input field will be\n\t// updated automatically when the user navigates the autocomplete list.\n\tautocompleted func(text string, index int, source int) bool\n\n\t// An optional function which may reject the last character that was entered.\n\taccept func(text string, ch rune) bool\n\n\t// An optional function which is called when the input has changed.\n\tchanged func(text string)\n\n\t// An optional function which is called when the user indicated that they\n\t// are done entering text. The key which was pressed is provided (tab,\n\t// shift-tab, enter, or escape).\n\tdone func(tcell.Key)\n\n\t// A callback function set by the Form class and called when the user leaves\n\t// this form item.\n\tfinished func(tcell.Key)\n}\n\n// NewInputField returns a new [InputField].\nfunc NewInputField() *InputField {\n\ti := &InputField{\n\t\tBox:      NewBox(),\n\t\ttextArea: NewTextArea().SetWrap(false),\n\t}\n\ti.textArea.SetChangedFunc(func() {\n\t\tif i.changed != nil {\n\t\t\ti.changed(i.textArea.GetText())\n\t\t}\n\t})\n\ti.textArea.textStyle = tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor)\n\ti.textArea.placeholderStyle = tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.ContrastSecondaryTextColor)\n\ti.autocompleteStyles.main = tcell.StyleDefault.Background(Styles.MoreContrastBackgroundColor).Foreground(Styles.PrimitiveBackgroundColor)\n\ti.autocompleteStyles.selected = tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.PrimitiveBackgroundColor)\n\ti.autocompleteStyles.background = Styles.MoreContrastBackgroundColor\n\ti.autocompleteStyles.useTags = true\n\ti.Box.Primitive = i\n\treturn i\n}\n\n// SetText sets the current text of the input field. This can be undone by the\n// user. Calling this function will also trigger a \"changed\" event.\nfunc (i *InputField) SetText(text string) *InputField {\n\ti.textArea.Replace(0, i.textArea.GetTextLength(), text)\n\treturn i\n}\n\n// GetText returns the current text of the input field.\nfunc (i *InputField) GetText() string {\n\treturn i.textArea.GetText()\n}\n\n// SetLabel sets the text to be displayed before the input area.\nfunc (i *InputField) SetLabel(label string) *InputField {\n\ti.textArea.SetLabel(label)\n\treturn i\n}\n\n// GetLabel returns the text to be displayed before the input area.\nfunc (i *InputField) GetLabel() string {\n\treturn i.textArea.GetLabel()\n}\n\n// SetLabelWidth sets the screen width of the label. A value of 0 will cause the\n// primitive to use the width of the label string.\nfunc (i *InputField) SetLabelWidth(width int) *InputField {\n\ti.textArea.SetLabelWidth(width)\n\treturn i\n}\n\n// SetPlaceholder sets the text to be displayed when the input text is empty.\nfunc (i *InputField) SetPlaceholder(text string) *InputField {\n\ti.textArea.SetPlaceholder(text)\n\treturn i\n}\n\n// SetLabelColor sets the text color of the label.\nfunc (i *InputField) SetLabelColor(color tcell.Color) *InputField {\n\ti.textArea.SetLabelStyle(i.textArea.GetLabelStyle().Foreground(color))\n\treturn i\n}\n\n// SetLabelStyle sets the style of the label.\nfunc (i *InputField) SetLabelStyle(style tcell.Style) *InputField {\n\ti.textArea.SetLabelStyle(style)\n\treturn i\n}\n\n// GetLabelStyle returns the style of the label.\nfunc (i *InputField) GetLabelStyle() tcell.Style {\n\treturn i.textArea.GetLabelStyle()\n}\n\n// SetFieldBackgroundColor sets the background color of the input area.\nfunc (i *InputField) SetFieldBackgroundColor(color tcell.Color) *InputField {\n\ti.textArea.SetTextStyle(i.textArea.GetTextStyle().Background(color))\n\treturn i\n}\n\n// SetFieldTextColor sets the text color of the input area.\nfunc (i *InputField) SetFieldTextColor(color tcell.Color) *InputField {\n\ti.textArea.SetTextStyle(i.textArea.GetTextStyle().Foreground(color))\n\treturn i\n}\n\n// SetFieldStyle sets the style of the input area (when no placeholder is\n// shown).\nfunc (i *InputField) SetFieldStyle(style tcell.Style) *InputField {\n\ti.textArea.SetTextStyle(style)\n\treturn i\n}\n\n// GetFieldStyle returns the style of the input area (when no placeholder is\n// shown).\nfunc (i *InputField) GetFieldStyle() tcell.Style {\n\treturn i.textArea.GetTextStyle()\n}\n\n// SetPlaceholderTextColor sets the text color of placeholder text.\nfunc (i *InputField) SetPlaceholderTextColor(color tcell.Color) *InputField {\n\ti.textArea.SetPlaceholderStyle(i.textArea.GetPlaceholderStyle().Foreground(color))\n\treturn i\n}\n\n// SetPlaceholderStyle sets the style of the input area (when a placeholder is\n// shown).\nfunc (i *InputField) SetPlaceholderStyle(style tcell.Style) *InputField {\n\ti.textArea.SetPlaceholderStyle(style)\n\treturn i\n}\n\n// GetPlaceholderStyle returns the style of the input area (when a placeholder\n// is shown).\nfunc (i *InputField) GetPlaceholderStyle() tcell.Style {\n\treturn i.textArea.GetPlaceholderStyle()\n}\n\n// SetAutocompleteStyles sets the colors and style of the autocomplete entries.\n// For details, see [List.SetMainTextStyle], [List.SetSelectedStyle], and\n// [Box.SetBackgroundColor].\nfunc (i *InputField) SetAutocompleteStyles(background tcell.Color, main, selected tcell.Style) *InputField {\n\ti.autocompleteStyles.background = background\n\ti.autocompleteStyles.main = main\n\ti.autocompleteStyles.selected = selected\n\treturn i\n}\n\n// SetAutocompleteUseTags sets whether or not the autocomplete entries may\n// contain style tags affecting their appearance. The default is true.\nfunc (i *InputField) SetAutocompleteUseTags(useTags bool) *InputField {\n\ti.autocompleteStyles.useTags = useTags\n\treturn i\n}\n\n// SetFormAttributes sets attributes shared by all form items.\nfunc (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {\n\ti.textArea.SetFormAttributes(labelWidth, labelColor, bgColor, fieldTextColor, fieldBgColor)\n\treturn i\n}\n\n// SetFieldWidth sets the screen width of the input area. A value of 0 means\n// extend as much as possible.\nfunc (i *InputField) SetFieldWidth(width int) *InputField {\n\ti.fieldWidth = width\n\treturn i\n}\n\n// GetFieldWidth returns this primitive's field width.\nfunc (i *InputField) GetFieldWidth() int {\n\treturn i.fieldWidth\n}\n\n// GetFieldHeight returns this primitive's field height.\nfunc (i *InputField) GetFieldHeight() int {\n\treturn 1\n}\n\n// SetDisabled sets whether or not the item is disabled / read-only.\nfunc (i *InputField) SetDisabled(disabled bool) FormItem {\n\ti.textArea.SetDisabled(disabled)\n\tif i.finished != nil {\n\t\ti.finished(-1)\n\t}\n\treturn i\n}\n\n// GetDisabled returns whether or not the item is disabled / read-only.\nfunc (i *InputField) GetDisabled() bool {\n\treturn i.textArea.GetDisabled()\n}\n\n// SetMaskCharacter sets a character that masks user input on a screen. A value\n// of 0 disables masking.\nfunc (i *InputField) SetMaskCharacter(mask rune) *InputField {\n\tif mask == 0 {\n\t\ti.textArea.setTransform(nil)\n\t\treturn i\n\t}\n\tmaskStr := string(mask)\n\tmaskWidth := uniseg.StringWidth(maskStr)\n\ti.textArea.setTransform(func(cluster, rest string, boundaries int) (newCluster string, newBoundaries int) {\n\t\treturn maskStr, maskWidth << uniseg.ShiftWidth\n\t})\n\treturn i\n}\n\n// SetAutocompleteFunc sets an autocomplete callback function which may return\n// strings to be selected from a drop-down based on the current text of the\n// input field. The drop-down appears only if len(entries) > 0. The callback is\n// invoked in this function and whenever the current text changes or when\n// [InputField.Autocomplete] is called. Entries are cleared when the user\n// selects an entry or presses Escape.\nfunc (i *InputField) SetAutocompleteFunc(callback func(currentText string) (entries []string)) *InputField {\n\ti.autocomplete = callback\n\ti.Autocomplete()\n\treturn i\n}\n\n// SetAutocompletedFunc sets a callback function which is invoked when the user\n// selects an entry from the autocomplete drop-down list. The function is passed\n// the text of the selected entry (stripped of any style tags), the index of the\n// entry, and the user action that caused the selection, for example\n// [AutocompletedNavigate]. It returns true if the autocomplete drop-down should\n// be closed after the callback returns or false if it should remain open, in\n// which case [InputField.Autocomplete] is called to update the drop-down's\n// contents.\n//\n// If no such callback is set (or nil is provided), the input field will be\n// updated with the selection any time the user navigates the autocomplete\n// drop-down list. So this function essentially gives you more control over the\n// autocomplete functionality.\nfunc (i *InputField) SetAutocompletedFunc(autocompleted func(text string, index int, source int) bool) *InputField {\n\ti.autocompleted = autocompleted\n\treturn i\n}\n\n// Autocomplete invokes the autocomplete callback (if there is one, see\n// [InputField.SetAutocompleteFunc]). If the length of the returned autocomplete\n// entries slice is greater than 0, the input field will present the user with a\n// corresponding drop-down list the next time the input field is drawn.\n//\n// It is safe to call this function from any goroutine. Note that the input\n// field is not redrawn automatically unless called from the main goroutine\n// (e.g. in response to events).\nfunc (i *InputField) Autocomplete() *InputField {\n\ti.autocompleteListMutex.Lock()\n\tdefer i.autocompleteListMutex.Unlock()\n\tif i.autocomplete == nil {\n\t\treturn i\n\t}\n\n\t// Do we have any autocomplete entries?\n\ttext := i.textArea.GetText()\n\tentries := i.autocomplete(text)\n\tif len(entries) == 0 {\n\t\t// No entries, no list.\n\t\ti.autocompleteList = nil\n\t\treturn i\n\t}\n\n\t// Make a list if we have none.\n\tif i.autocompleteList == nil {\n\t\ti.autocompleteList = NewList()\n\t\ti.autocompleteList.ShowSecondaryText(false).\n\t\t\tSetMainTextStyle(i.autocompleteStyles.main).\n\t\t\tSetSelectedStyle(i.autocompleteStyles.selected).\n\t\t\tSetUseStyleTags(i.autocompleteStyles.useTags, i.autocompleteStyles.useTags).\n\t\t\tSetHighlightFullLine(true).\n\t\t\tSetBackgroundColor(i.autocompleteStyles.background)\n\t}\n\n\t// Fill it with the entries.\n\tcurrentIndex := i.autocompleteList.GetCurrentItem()\n\tvar currentSelection string\n\tif currentIndex >= 0 && currentIndex < i.autocompleteList.GetItemCount() {\n\t\tcurrentSelection, _ = i.autocompleteList.GetItemText(currentIndex)\n\t}\n\tcurrentEntry := -1\n\tsuffixLength := math.MaxInt\n\ti.autocompleteList.Clear()\n\tfor index, entry := range entries {\n\t\ti.autocompleteList.AddItem(entry, \"\", 0, nil)\n\t\tif currentSelection != \"\" && entry == currentSelection {\n\t\t\tcurrentEntry = index\n\t\t}\n\t\tif currentSelection == \"\" && strings.HasPrefix(entry, text) && len(entry)-len(text) < suffixLength {\n\t\t\tcurrentEntry = index\n\t\t\tsuffixLength = len(text) - len(entry)\n\t\t}\n\t}\n\n\t// Set the selection if we have one.\n\tif currentEntry >= 0 {\n\t\ti.autocompleteList.SetCurrentItem(currentEntry)\n\t}\n\n\treturn i\n}\n\n// SetAcceptanceFunc sets a handler which may reject the last character that was\n// entered, by returning false. The handler receives the text as it would be\n// after the change and the last character entered. If the handler is nil, all\n// input is accepted. The function is only called when a single rune is inserted\n// at the current cursor position.\n//\n// This package defines a number of variables prefixed with InputField which may\n// be used for common input (e.g. numbers, maximum text length). See for example\n// [InputFieldInteger].\n//\n// When text is pasted, lastChar is 0.\nfunc (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {\n\ti.accept = handler\n\treturn i\n}\n\n// SetChangedFunc sets a handler which is called whenever the text of the input\n// field has changed. It receives the current text (after the change).\nfunc (i *InputField) SetChangedFunc(handler func(text string)) *InputField {\n\ti.changed = handler\n\treturn i\n}\n\n// SetDoneFunc sets a handler which is called when the user is done entering\n// text. The callback function is provided with the key that was pressed, which\n// is one of the following:\n//\n//   - KeyEnter: Done entering text.\n//   - KeyEscape: Abort text input.\n//   - KeyTab: Move to the next field.\n//   - KeyBacktab: Move to the previous field.\nfunc (i *InputField) SetDoneFunc(handler func(key tcell.Key)) *InputField {\n\ti.done = handler\n\treturn i\n}\n\n// SetFinishedFunc sets a callback invoked when the user leaves this form item.\nfunc (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem {\n\ti.finished = handler\n\treturn i\n}\n\n// Focus is called when this primitive receives focus.\nfunc (i *InputField) Focus(delegate func(p Primitive)) {\n\t// If we're part of a form and this item is disabled, there's nothing the\n\t// user can do here so we're finished.\n\tif i.finished != nil && i.textArea.GetDisabled() {\n\t\ti.finished(-1)\n\t\treturn\n\t}\n\n\tdelegate(i.textArea)\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (i *InputField) focusChain(chain *[]Primitive) bool {\n\tif hasFocus := i.textArea.focusChain(chain); hasFocus {\n\t\tif chain != nil {\n\t\t\t*chain = append(*chain, i)\n\t\t}\n\t\treturn true\n\t}\n\treturn i.Box.focusChain(chain)\n}\n\n// Blur is called when this primitive loses focus.\nfunc (i *InputField) Blur() {\n\ti.Box.Blur()\n\ti.autocompleteList = nil // Hide the autocomplete drop-down.\n}\n\n// Draw draws this primitive onto the screen.\nfunc (i *InputField) Draw(screen tcell.Screen) {\n\ti.Box.DrawForSubclass(screen, i)\n\n\t// Prepare\n\tx, y, width, height := i.GetInnerRect()\n\tif height < 1 || width < 1 {\n\t\treturn\n\t}\n\n\t// Resize text area.\n\tlabelWidth := i.textArea.GetLabelWidth()\n\tif labelWidth == 0 {\n\t\tlabelWidth = TaggedStringWidth(i.textArea.GetLabel())\n\t}\n\tfieldWidth := i.fieldWidth\n\tif fieldWidth == 0 {\n\t\tfieldWidth = width - labelWidth\n\t}\n\ti.textArea.SetRect(x, y, labelWidth+fieldWidth, 1)\n\ti.textArea.setMinCursorPadding(fieldWidth-1, 1)\n\n\t// Draw text area.\n\ti.textArea.hasFocus = i.HasFocus() // Force cursor positioning.\n\ti.textArea.Draw(screen)\n\n\t// Draw autocomplete list.\n\ti.autocompleteListMutex.Lock()\n\tdefer i.autocompleteListMutex.Unlock()\n\tif i.autocompleteList != nil && i.HasFocus() {\n\t\t// How much space do we need?\n\t\tlheight := i.autocompleteList.GetItemCount()\n\t\tlwidth := 0\n\t\tfor index := 0; index < lheight; index++ {\n\t\t\tentry, _ := i.autocompleteList.GetItemText(index)\n\t\t\twidth := TaggedStringWidth(entry)\n\t\t\tif width > lwidth {\n\t\t\t\tlwidth = width\n\t\t\t}\n\t\t}\n\n\t\t// We prefer to drop down but if there is no space, maybe drop up?\n\t\tlx := x + labelWidth\n\t\tly := y + 1\n\t\t_, sheight := screen.Size()\n\t\tif ly+lheight >= sheight && ly-2 > lheight-ly {\n\t\t\tly = y - lheight\n\t\t\tif ly < 0 {\n\t\t\t\tly = 0\n\t\t\t}\n\t\t}\n\t\tif ly+lheight >= sheight {\n\t\t\tlheight = sheight - ly\n\t\t}\n\t\ti.autocompleteList.SetRect(lx, ly, lwidth, lheight)\n\t\ti.autocompleteList.Draw(screen)\n\t}\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn i.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tif i.textArea.GetDisabled() {\n\t\t\treturn\n\t\t}\n\n\t\t// Trigger changed events.\n\t\tvar skipAutocomplete bool\n\t\tcurrentText := i.textArea.GetText()\n\t\tdefer func() {\n\t\t\tif skipAutocomplete {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif i.textArea.GetText() != currentText {\n\t\t\t\ti.Autocomplete()\n\t\t\t}\n\t\t}()\n\n\t\t// If we have an autocomplete list, there are certain keys we will\n\t\t// forward to it.\n\t\ti.autocompleteListMutex.Lock()\n\t\tdefer i.autocompleteListMutex.Unlock()\n\t\tif i.autocompleteList != nil {\n\t\t\ti.autocompleteList.SetChangedFunc(nil)\n\t\t\ti.autocompleteList.SetSelectedFunc(nil)\n\t\t\tswitch key := event.Key(); key {\n\t\t\tcase tcell.KeyEscape: // Close the list.\n\t\t\t\ti.autocompleteList = nil\n\t\t\t\treturn\n\t\t\tcase tcell.KeyEnter, tcell.KeyTab: // Intentional selection.\n\t\t\t\tindex := i.autocompleteList.GetCurrentItem()\n\t\t\t\ttext, _ := i.autocompleteList.GetItemText(index)\n\t\t\t\tif i.autocompleted != nil {\n\t\t\t\t\tsource := AutocompletedEnter\n\t\t\t\t\tif key == tcell.KeyTab {\n\t\t\t\t\t\tsource = AutocompletedTab\n\t\t\t\t\t}\n\t\t\t\t\tif i.autocompleted(stripTags(text), index, source) {\n\t\t\t\t\t\ti.autocompleteList = nil\n\t\t\t\t\t\tcurrentText = i.GetText()\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ti.SetText(text)\n\t\t\t\t\tskipAutocomplete = true\n\t\t\t\t\ti.autocompleteList = nil\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\tcase tcell.KeyDown, tcell.KeyUp, tcell.KeyPgDn, tcell.KeyPgUp:\n\t\t\t\ti.autocompleteList.SetChangedFunc(func(index int, text, secondaryText string, shortcut rune) {\n\t\t\t\t\ttext = stripTags(text)\n\t\t\t\t\tif i.autocompleted != nil {\n\t\t\t\t\t\tif i.autocompleted(text, index, AutocompletedNavigate) {\n\t\t\t\t\t\t\ti.autocompleteList = nil\n\t\t\t\t\t\t\tcurrentText = i.GetText()\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\ti.SetText(text)\n\t\t\t\t\t\tcurrentText = stripTags(text) // We want to keep the autocomplete list open and unchanged.\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\ti.autocompleteList.InputHandler()(event, setFocus)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// Finish up.\n\t\tfinish := func(key tcell.Key) {\n\t\t\tif i.done != nil {\n\t\t\t\ti.done(key)\n\t\t\t}\n\t\t\tif i.finished != nil {\n\t\t\t\ti.finished(key)\n\t\t\t}\n\t\t}\n\n\t\t// Process special key events for the input field.\n\t\tswitch key := event.Key(); key {\n\t\tcase tcell.KeyDown:\n\t\t\ti.autocompleteListMutex.Unlock() // We're still holding a lock.\n\t\t\ti.Autocomplete()\n\t\t\ti.autocompleteListMutex.Lock()\n\t\tcase tcell.KeyEnter, tcell.KeyEscape, tcell.KeyTab, tcell.KeyBacktab:\n\t\t\tfinish(key)\n\t\tcase tcell.KeyCtrlV:\n\t\t\tif i.accept != nil && !i.accept(i.textArea.getTextBeforeCursor()+i.textArea.GetClipboardText()+i.textArea.getTextAfterCursor(), 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ti.textArea.InputHandler()(event, setFocus)\n\t\tcase tcell.KeyRune:\n\t\t\tif event.Modifiers()&tcell.ModAlt == 0 && i.accept != nil {\n\t\t\t\t// Check if this rune is accepted.\n\t\t\t\tr := event.Rune()\n\t\t\t\tif !i.accept(i.textArea.getTextBeforeCursor()+string(r)+i.textArea.getTextAfterCursor(), r) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tfallthrough\n\t\tdefault:\n\t\t\t// Forward other key events to the text area.\n\t\t\ti.textArea.InputHandler()(event, setFocus)\n\t\t}\n\t})\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (i *InputField) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn i.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif i.textArea.GetDisabled() {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tvar skipAutocomplete bool\n\t\tcurrentText := i.GetText()\n\t\tdefer func() {\n\t\t\tif skipAutocomplete {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif i.textArea.GetText() != currentText {\n\t\t\t\ti.Autocomplete()\n\t\t\t}\n\t\t}()\n\n\t\t// If we have an autocomplete list, forward the mouse event to it.\n\t\ti.autocompleteListMutex.Lock()\n\t\tdefer i.autocompleteListMutex.Unlock()\n\t\tif i.autocompleteList != nil {\n\t\t\ti.autocompleteList.SetChangedFunc(nil)\n\t\t\ti.autocompleteList.SetSelectedFunc(func(index int, text, secondaryText string, shortcut rune) {\n\t\t\t\ttext = stripTags(text)\n\t\t\t\tif i.autocompleted != nil {\n\t\t\t\t\tif i.autocompleted(text, index, AutocompletedClick) {\n\t\t\t\t\t\ti.autocompleteList = nil\n\t\t\t\t\t\tcurrentText = i.GetText()\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ti.SetText(text)\n\t\t\t\tskipAutocomplete = true\n\t\t\t\ti.autocompleteList = nil\n\t\t\t})\n\t\t\tif consumed, _ = i.autocompleteList.MouseHandler()(action, event, setFocus); consumed {\n\t\t\t\tsetFocus(i)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// Is mouse event within the input field?\n\t\tx, y := event.Position()\n\t\tif !i.InRect(x, y) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Forward mouse event to the text area.\n\t\tconsumed, capture = i.textArea.MouseHandler()(action, event, setFocus)\n\n\t\t// Focus in any case.\n\t\tif action == MouseLeftDown && !consumed {\n\t\t\tsetFocus(i)\n\t\t\tconsumed = true\n\t\t}\n\n\t\treturn\n\t})\n}\n\n// PasteHandler returns the handler for this primitive.\nfunc (i *InputField) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {\n\treturn i.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {\n\t\t// Input field may be disabled.\n\t\tif i.textArea.GetDisabled() {\n\t\t\treturn\n\t\t}\n\n\t\t// The autocomplete drop down may be open.\n\t\ti.autocompleteListMutex.Lock()\n\t\tdefer i.autocompleteListMutex.Unlock()\n\t\tif i.autocompleteList != nil {\n\t\t\treturn\n\t\t}\n\n\t\t// We may not accept this text.\n\t\tif i.accept != nil && !i.accept(i.textArea.getTextBeforeCursor()+pastedText+i.textArea.getTextAfterCursor(), 0) {\n\t\t\treturn\n\t\t}\n\n\t\t// Forward the pasted text to the text area.\n\t\ti.textArea.PasteHandler()(pastedText, setFocus)\n\t})\n}\n"
  },
  {
    "path": "list.go",
    "content": "package tview\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// listItem represents one item in a List.\ntype listItem struct {\n\tMainText      string // The main text of the list item.\n\tSecondaryText string // A secondary text to be shown underneath the main text.\n\tShortcut      rune   // The key to select the list item directly, 0 if there is no shortcut.\n\tSelected      func() // The optional function which is called when the item is selected.\n}\n\n// List displays rows of items, each of which can be selected. List items can be\n// shown as a single line or as two lines. They can be selected by pressing\n// their assigned shortcut key, navigating to them and pressing Enter, or\n// clicking on them with the mouse. The following key binds are available:\n//\n//   - Down arrow / tab: Move down one item.\n//   - Up arrow / backtab: Move up one item.\n//   - Home: Move to the first item.\n//   - End: Move to the last item.\n//   - Page down: Move down one page.\n//   - Page up: Move up one page.\n//   - Enter / Space: Select the current item.\n//   - Right / left: Scroll horizontally. Only if the list is wider than the\n//     available space.\n//\n// By default, list item texts can contain style tags. Use\n// [List.SetUseStyleTags] to disable this feature.\n//\n// See [List.SetChangedFunc] for a way to be notified when the user navigates\n// to a list item. See [List.SetSelectedFunc] for a way to be notified when a\n// list item was selected.\n//\n// See https://github.com/rivo/tview/wiki/List for an example.\ntype List struct {\n\t*Box\n\n\t// The items of the list.\n\titems []*listItem\n\n\t// The index of the currently selected item.\n\tcurrentItem int\n\n\t// Whether or not to show the secondary item texts.\n\tshowSecondaryText bool\n\n\t// The item main text style.\n\tmainTextStyle tcell.Style\n\n\t// The item secondary text style.\n\tsecondaryTextStyle tcell.Style\n\n\t// The item shortcut text style.\n\tshortcutStyle tcell.Style\n\n\t// The style for selected items.\n\tselectedStyle tcell.Style\n\n\t// If true, the selection is only shown when the list has focus.\n\tselectedFocusOnly bool\n\n\t// If true, the entire row is highlighted when selected.\n\thighlightFullLine bool\n\n\t// Whether or not style tags can be used in the main text.\n\tmainStyleTags bool\n\n\t// Whether or not style tags can be used in the secondary text.\n\tsecondaryStyleTags bool\n\n\t// Whether or not navigating the list will wrap around.\n\twrapAround bool\n\n\t// The number of list items skipped at the top before the first item is\n\t// drawn.\n\titemOffset int\n\n\t// The number of cells skipped on the left side of an item text. Shortcuts\n\t// are not affected.\n\thorizontalOffset int\n\n\t// An optional function which is called when the user has navigated to a\n\t// list item.\n\tchanged func(index int, mainText, secondaryText string, shortcut rune)\n\n\t// An optional function which is called when a list item was selected. This\n\t// function will be called even if the list item defines its own callback.\n\tselected func(index int, mainText, secondaryText string, shortcut rune)\n\n\t// An optional function which is called when the user presses the Escape key.\n\tdone func()\n}\n\n// NewList returns a new [List].\nfunc NewList() *List {\n\tl := &List{\n\t\tBox:                NewBox(),\n\t\tshowSecondaryText:  true,\n\t\twrapAround:         true,\n\t\tmainTextStyle:      tcell.StyleDefault.Foreground(Styles.PrimaryTextColor).Background(Styles.PrimitiveBackgroundColor),\n\t\tsecondaryTextStyle: tcell.StyleDefault.Foreground(Styles.TertiaryTextColor).Background(Styles.PrimitiveBackgroundColor),\n\t\tshortcutStyle:      tcell.StyleDefault.Foreground(Styles.SecondaryTextColor).Background(Styles.PrimitiveBackgroundColor),\n\t\tselectedStyle:      tcell.StyleDefault.Foreground(Styles.PrimitiveBackgroundColor).Background(Styles.PrimaryTextColor),\n\t\tmainStyleTags:      true,\n\t\tsecondaryStyleTags: true,\n\t}\n\tl.Box.Primitive = l\n\treturn l\n}\n\n// SetCurrentItem sets the currently selected item by its index, starting at 0\n// for the first item. If a negative index is provided, items are referred to\n// from the back (-1 = last item, -2 = second-to-last item, and so on). Out of\n// range indices are clamped to the beginning/end.\n//\n// Calling this function triggers a \"changed\" event if the selection changes.\nfunc (l *List) SetCurrentItem(index int) *List {\n\tif index < 0 {\n\t\tindex = len(l.items) + index\n\t}\n\tif index >= len(l.items) {\n\t\tindex = len(l.items) - 1\n\t}\n\tif index < 0 {\n\t\tindex = 0\n\t}\n\n\tif index != l.currentItem && l.changed != nil {\n\t\titem := l.items[index]\n\t\tl.changed(index, item.MainText, item.SecondaryText, item.Shortcut)\n\t}\n\n\tl.currentItem = index\n\n\treturn l\n}\n\n// GetCurrentItem returns the index of the currently selected list item,\n// starting at 0 for the first item.\nfunc (l *List) GetCurrentItem() int {\n\treturn l.currentItem\n}\n\n// SetOffset sets the number of items to be skipped (vertically) as well as the\n// number of cells skipped horizontally when the list is drawn. Note that one\n// item corresponds to two rows when there are secondary texts. Shortcuts are\n// always drawn.\n//\n// These values may change when the list is drawn to ensure the currently\n// selected item is visible and item texts move out of view. Users can also\n// modify these values by interacting with the list.\nfunc (l *List) SetOffset(items, horizontal int) *List {\n\tl.itemOffset = items\n\tl.horizontalOffset = horizontal\n\treturn l\n}\n\n// GetOffset returns the number of items skipped while drawing, as well as the\n// number of cells item text is moved to the left. See also SetOffset() for more\n// information on these values.\nfunc (l *List) GetOffset() (int, int) {\n\treturn l.itemOffset, l.horizontalOffset\n}\n\n// RemoveItem removes the item with the given index (starting at 0) from the\n// list. If a negative index is provided, items are referred to from the back\n// (-1 = last item, -2 = second-to-last item, and so on). Out of range indices\n// are clamped to the beginning/end, i.e. unless the list is empty, an item is\n// always removed.\n//\n// The currently selected item is shifted accordingly. If it is the one that is\n// removed, a \"changed\" event is fired, unless no items are left.\nfunc (l *List) RemoveItem(index int) *List {\n\tif len(l.items) == 0 {\n\t\treturn l\n\t}\n\n\t// Adjust index.\n\tif index < 0 {\n\t\tindex = len(l.items) + index\n\t}\n\tif index >= len(l.items) {\n\t\tindex = len(l.items) - 1\n\t}\n\tif index < 0 {\n\t\tindex = 0\n\t}\n\n\t// Remove item.\n\tl.items = append(l.items[:index], l.items[index+1:]...)\n\n\t// If there is nothing left, we're done.\n\tif len(l.items) == 0 {\n\t\treturn l\n\t}\n\n\t// Shift current item.\n\tpreviousCurrentItem := l.currentItem\n\tif l.currentItem > index || l.currentItem == len(l.items) {\n\t\tl.currentItem--\n\t}\n\n\t// Fire \"changed\" event for removed items.\n\tif previousCurrentItem == index && l.changed != nil {\n\t\titem := l.items[l.currentItem]\n\t\tl.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)\n\t}\n\n\treturn l\n}\n\n// SetMainTextColor sets the color of the items' main text.\nfunc (l *List) SetMainTextColor(color tcell.Color) *List {\n\tl.mainTextStyle = l.mainTextStyle.Foreground(color)\n\treturn l\n}\n\n// SetMainTextStyle sets the style of the items' main text. Note that the\n// background color is ignored in order not to override the background color of\n// the list itself.\nfunc (l *List) SetMainTextStyle(style tcell.Style) *List {\n\tl.mainTextStyle = style\n\treturn l\n}\n\n// SetSecondaryTextColor sets the color of the items' secondary text.\nfunc (l *List) SetSecondaryTextColor(color tcell.Color) *List {\n\tl.secondaryTextStyle = l.secondaryTextStyle.Foreground(color)\n\treturn l\n}\n\n// SetSecondaryTextStyle sets the style of the items' secondary text. Note that\n// the background color is ignored in order not to override the background color\n// of the list itself.\nfunc (l *List) SetSecondaryTextStyle(style tcell.Style) *List {\n\tl.secondaryTextStyle = style\n\treturn l\n}\n\n// SetShortcutColor sets the color of the items' shortcut.\nfunc (l *List) SetShortcutColor(color tcell.Color) *List {\n\tl.shortcutStyle = l.shortcutStyle.Foreground(color)\n\treturn l\n}\n\n// SetShortcutStyle sets the style of the items' shortcut. Note that the\n// background color is ignored in order not to override the background color of\n// the list itself.\nfunc (l *List) SetShortcutStyle(style tcell.Style) *List {\n\tl.shortcutStyle = style\n\treturn l\n}\n\n// SetSelectedTextColor sets the text color of selected items. Note that the\n// color of main text characters that are different from the main text color\n// (e.g. style tags) is maintained.\nfunc (l *List) SetSelectedTextColor(color tcell.Color) *List {\n\tl.selectedStyle = l.selectedStyle.Foreground(color)\n\treturn l\n}\n\n// SetSelectedBackgroundColor sets the background color of selected items.\nfunc (l *List) SetSelectedBackgroundColor(color tcell.Color) *List {\n\tl.selectedStyle = l.selectedStyle.Background(color)\n\treturn l\n}\n\n// SetSelectedStyle sets the style of the selected items. Note that the color of\n// main text characters that are different from the main text color (e.g. color\n// tags) is maintained.\nfunc (l *List) SetSelectedStyle(style tcell.Style) *List {\n\tl.selectedStyle = style\n\treturn l\n}\n\n// SetUseStyleTags sets a flag which determines whether style tags are used in\n// the main and secondary texts. The default is true.\nfunc (l *List) SetUseStyleTags(mainStyleTags, secondaryStyleTags bool) *List {\n\tl.mainStyleTags = mainStyleTags\n\tl.secondaryStyleTags = secondaryStyleTags\n\treturn l\n}\n\n// GetUseStyleTags returns whether style tags are used in the main and secondary\n// texts.\nfunc (l *List) GetUseStyleTags() (mainStyleTags, secondaryStyleTags bool) {\n\treturn l.mainStyleTags, l.secondaryStyleTags\n}\n\n// SetSelectedFocusOnly sets a flag which determines when the currently selected\n// list item is highlighted. If set to true, selected items are only highlighted\n// when the list has focus. If set to false, they are always highlighted.\nfunc (l *List) SetSelectedFocusOnly(focusOnly bool) *List {\n\tl.selectedFocusOnly = focusOnly\n\treturn l\n}\n\n// SetHighlightFullLine sets a flag which determines whether the colored\n// background of selected items spans the entire width of the view. If set to\n// true, the highlight spans the entire view. If set to false, only the text of\n// the selected item from beginning to end is highlighted.\nfunc (l *List) SetHighlightFullLine(highlight bool) *List {\n\tl.highlightFullLine = highlight\n\treturn l\n}\n\n// ShowSecondaryText determines whether or not to show secondary item texts.\nfunc (l *List) ShowSecondaryText(show bool) *List {\n\tl.showSecondaryText = show\n\treturn l\n}\n\n// SetWrapAround sets the flag that determines whether navigating the list will\n// wrap around. That is, navigating downwards on the last item will move the\n// selection to the first item (similarly in the other direction). If set to\n// false, the selection won't change when navigating downwards on the last item\n// or navigating upwards on the first item.\nfunc (l *List) SetWrapAround(wrapAround bool) *List {\n\tl.wrapAround = wrapAround\n\treturn l\n}\n\n// SetChangedFunc sets the function which is called when the user navigates to\n// a list item. The function receives the item's index in the list of items\n// (starting with 0), its main text, secondary text, and its shortcut rune.\n//\n// This function is also called when the first item is added or when\n// SetCurrentItem() is called.\nfunc (l *List) SetChangedFunc(handler func(index int, mainText string, secondaryText string, shortcut rune)) *List {\n\tl.changed = handler\n\treturn l\n}\n\n// SetSelectedFunc sets the function which is called when the user selects a\n// list item by pressing Enter on the current selection. The function receives\n// the item's index in the list of items (starting with 0), its main text,\n// secondary text, and its shortcut rune.\nfunc (l *List) SetSelectedFunc(handler func(int, string, string, rune)) *List {\n\tl.selected = handler\n\treturn l\n}\n\n// GetSelectedFunc returns the function set with [List.SetSelectedFunc] or nil\n// if no such function was set.\nfunc (l *List) GetSelectedFunc() func(int, string, string, rune) {\n\treturn l.selected\n}\n\n// SetDoneFunc sets a function which is called when the user presses the Escape\n// key.\nfunc (l *List) SetDoneFunc(handler func()) *List {\n\tl.done = handler\n\treturn l\n}\n\n// AddItem calls [List.InsertItem] with an index of -1.\nfunc (l *List) AddItem(mainText, secondaryText string, shortcut rune, selected func()) *List {\n\tl.InsertItem(-1, mainText, secondaryText, shortcut, selected)\n\treturn l\n}\n\n// InsertItem adds a new item to the list at the specified index. An index of 0\n// will insert the item at the beginning, an index of 1 before the second item,\n// and so on. An index of [List.GetItemCount] or higher will insert the item at\n// the end of the list. Negative indices are also allowed: An index of -1 will\n// insert the item at the end of the list, an index of -2 before the last item,\n// and so on. An index of -GetItemCount()-1 or lower will insert the item at the\n// beginning.\n//\n// An item has a main text which will be highlighted when selected. It also has\n// a secondary text which is shown underneath the main text (if it is set to\n// visible) but which may remain empty.\n//\n// The shortcut is a key binding. If the specified rune is entered, the item\n// is selected immediately. Set to 0 for no binding.\n//\n// The \"selected\" callback will be invoked when the user selects the item. You\n// may provide nil if no such callback is needed or if all events are handled\n// through the selected callback set with [List.SetSelectedFunc].\n//\n// The currently selected item will shift its position accordingly. If the list\n// was previously empty, a \"changed\" event is fired because the new item becomes\n// selected.\nfunc (l *List) InsertItem(index int, mainText, secondaryText string, shortcut rune, selected func()) *List {\n\titem := &listItem{\n\t\tMainText:      mainText,\n\t\tSecondaryText: secondaryText,\n\t\tShortcut:      shortcut,\n\t\tSelected:      selected,\n\t}\n\n\t// Shift index to range.\n\tif index < 0 {\n\t\tindex = len(l.items) + index + 1\n\t}\n\tif index < 0 {\n\t\tindex = 0\n\t} else if index > len(l.items) {\n\t\tindex = len(l.items)\n\t}\n\n\t// Shift current item.\n\tif l.currentItem < len(l.items) && l.currentItem >= index {\n\t\tl.currentItem++\n\t}\n\n\t// Insert item (make space for the new item, then shift and insert).\n\tl.items = append(l.items, nil)\n\tif index < len(l.items)-1 { // -1 because l.items has already grown by one item.\n\t\tcopy(l.items[index+1:], l.items[index:])\n\t}\n\tl.items[index] = item\n\n\t// Fire a \"change\" event for the first item in the list.\n\tif len(l.items) == 1 && l.changed != nil {\n\t\titem := l.items[0]\n\t\tl.changed(0, item.MainText, item.SecondaryText, item.Shortcut)\n\t}\n\n\treturn l\n}\n\n// GetItemCount returns the number of items in the list.\nfunc (l *List) GetItemCount() int {\n\treturn len(l.items)\n}\n\n// GetItemSelectedFunc returns the function which is called when the user\n// selects the item with the given index, if such a function was set. If no\n// function was set, nil is returned. Panics if the index is out of range.\nfunc (l *List) GetItemSelectedFunc(index int) func() {\n\treturn l.items[index].Selected\n}\n\n// GetItemText returns an item's texts (main and secondary). Panics if the index\n// is out of range.\nfunc (l *List) GetItemText(index int) (main, secondary string) {\n\treturn l.items[index].MainText, l.items[index].SecondaryText\n}\n\n// SetItemText sets an item's main and secondary text. Panics if the index is\n// out of range.\nfunc (l *List) SetItemText(index int, main, secondary string) *List {\n\titem := l.items[index]\n\titem.MainText = main\n\titem.SecondaryText = secondary\n\treturn l\n}\n\n// FindItems searches the main and secondary texts for the given strings and\n// returns a list of item indices in which those strings are found. One of the\n// two search strings may be empty, it will then be ignored. Indices are always\n// returned in ascending order.\n//\n// If mustContainBoth is set to true, mainSearch must be contained in the main\n// text AND secondarySearch must be contained in the secondary text. If it is\n// false, only one of the two search strings must be contained.\n//\n// Set ignoreCase to true for case-insensitive search.\nfunc (l *List) FindItems(mainSearch, secondarySearch string, mustContainBoth, ignoreCase bool) (indices []int) {\n\tif mainSearch == \"\" && secondarySearch == \"\" {\n\t\treturn\n\t}\n\n\tif ignoreCase {\n\t\tmainSearch = strings.ToLower(mainSearch)\n\t\tsecondarySearch = strings.ToLower(secondarySearch)\n\t}\n\n\tfor index, item := range l.items {\n\t\tmainText := item.MainText\n\t\tsecondaryText := item.SecondaryText\n\t\tif ignoreCase {\n\t\t\tmainText = strings.ToLower(mainText)\n\t\t\tsecondaryText = strings.ToLower(secondaryText)\n\t\t}\n\n\t\t// strings.Contains() always returns true for a \"\" search.\n\t\tmainContained := strings.Contains(mainText, mainSearch)\n\t\tsecondaryContained := strings.Contains(secondaryText, secondarySearch)\n\t\tif mustContainBoth && mainContained && secondaryContained ||\n\t\t\t!mustContainBoth && (mainSearch != \"\" && mainContained || secondarySearch != \"\" && secondaryContained) {\n\t\t\tindices = append(indices, index)\n\t\t}\n\t}\n\n\treturn\n}\n\n// Clear removes all items from the list.\nfunc (l *List) Clear() *List {\n\tl.items = nil\n\tl.currentItem = 0\n\treturn l\n}\n\n// Draw draws this primitive onto the screen.\nfunc (l *List) Draw(screen tcell.Screen) {\n\tl.Box.DrawForSubclass(screen, l)\n\n\t// Determine the dimensions.\n\tx, y, width, height := l.GetInnerRect()\n\tbottomLimit := y + height\n\t_, totalHeight := screen.Size()\n\tif bottomLimit > totalHeight {\n\t\tbottomLimit = totalHeight\n\t}\n\n\t// Adjust offsets to keep the current item in view.\n\tif height == 0 {\n\t\treturn\n\t}\n\tif l.currentItem < l.itemOffset {\n\t\tl.itemOffset = l.currentItem\n\t} else if l.showSecondaryText {\n\t\tif 2*(l.currentItem-l.itemOffset) >= height-1 {\n\t\t\tl.itemOffset = (2*l.currentItem + 3 - height) / 2\n\t\t}\n\t} else {\n\t\tif l.currentItem-l.itemOffset >= height {\n\t\t\tl.itemOffset = l.currentItem + 1 - height\n\t\t}\n\t}\n\tif l.horizontalOffset < 0 {\n\t\tl.horizontalOffset = 0\n\t}\n\n\t// Do we show any shortcuts?\n\tvar showShortcuts bool\n\tfor _, item := range l.items {\n\t\tif item.Shortcut != 0 {\n\t\t\tshowShortcuts = true\n\t\t\tx += 4\n\t\t\twidth -= 4\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// Draw the list items.\n\tvar maxWidth int // The maximum printed item width.\n\tfor index, item := range l.items {\n\t\tif index < l.itemOffset {\n\t\t\tcontinue\n\t\t}\n\n\t\tif y >= bottomLimit {\n\t\t\tbreak\n\t\t}\n\n\t\t// Shortcuts.\n\t\tif showShortcuts && item.Shortcut != 0 {\n\t\t\tprintWithStyle(screen, fmt.Sprintf(\"(%s)\", string(item.Shortcut)), x-5, y, 0, 4, AlignRight, l.shortcutStyle, false)\n\t\t}\n\n\t\t// Main text.\n\t\tselected := index == l.currentItem && (!l.selectedFocusOnly || l.HasFocus())\n\t\tstyle := l.mainTextStyle\n\t\tif selected {\n\t\t\tstyle = l.selectedStyle\n\t\t}\n\t\tmainText := item.MainText\n\t\tif !l.mainStyleTags {\n\t\t\tmainText = Escape(mainText)\n\t\t}\n\t\t_, _, printedWidth := printWithStyle(screen, mainText, x, y, l.horizontalOffset, width, AlignLeft, style, false)\n\t\tif printedWidth > maxWidth {\n\t\t\tmaxWidth = printedWidth\n\t\t}\n\n\t\t// Draw until the end of the line if requested.\n\t\tif selected && l.highlightFullLine {\n\t\t\tfor bx := printedWidth; bx < width; bx++ {\n\t\t\t\tscreen.SetContent(x+bx, y, ' ', nil, style)\n\t\t\t}\n\t\t}\n\n\t\ty++\n\t\tif y >= bottomLimit {\n\t\t\tbreak\n\t\t}\n\n\t\t// Secondary text.\n\t\tif l.showSecondaryText {\n\t\t\tsecondaryText := item.SecondaryText\n\t\t\tif !l.secondaryStyleTags {\n\t\t\t\tsecondaryText = Escape(secondaryText)\n\t\t\t}\n\t\t\t_, _, printedWidth := printWithStyle(screen, secondaryText, x, y, l.horizontalOffset, width, AlignLeft, l.secondaryTextStyle, false)\n\t\t\tif printedWidth > maxWidth {\n\t\t\t\tmaxWidth = printedWidth\n\t\t\t}\n\t\t\ty++\n\t\t}\n\t}\n\n\t// We don't want the item text to get out of view. If the horizontal offset\n\t// is too high, we reset it and redraw. (That should be about as efficient\n\t// as calculating everything up front.)\n\tif l.horizontalOffset > 0 && maxWidth < width {\n\t\tl.horizontalOffset -= width - maxWidth\n\t\tl.Draw(screen)\n\t}\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn l.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tif event.Key() == tcell.KeyEscape {\n\t\t\tif l.done != nil {\n\t\t\t\tl.done()\n\t\t\t}\n\t\t\treturn\n\t\t} else if len(l.items) == 0 {\n\t\t\treturn\n\t\t}\n\n\t\tpreviousItem := l.currentItem\n\n\t\tswitch key := event.Key(); key {\n\t\tcase tcell.KeyTab, tcell.KeyDown:\n\t\t\tl.currentItem++\n\t\tcase tcell.KeyBacktab, tcell.KeyUp:\n\t\t\tl.currentItem--\n\t\tcase tcell.KeyRight:\n\t\t\tl.horizontalOffset += 2 // We shift by 2 to account for two-cell characters.\n\t\tcase tcell.KeyLeft:\n\t\t\tl.horizontalOffset -= 2\n\t\tcase tcell.KeyHome:\n\t\t\tl.currentItem = 0\n\t\tcase tcell.KeyEnd:\n\t\t\tl.currentItem = len(l.items) - 1\n\t\tcase tcell.KeyPgDn:\n\t\t\t_, _, _, height := l.GetInnerRect()\n\t\t\tl.currentItem += height\n\t\t\tif l.currentItem >= len(l.items) {\n\t\t\t\tl.currentItem = len(l.items) - 1\n\t\t\t}\n\t\tcase tcell.KeyPgUp:\n\t\t\t_, _, _, height := l.GetInnerRect()\n\t\t\tl.currentItem -= height\n\t\t\tif l.currentItem < 0 {\n\t\t\t\tl.currentItem = 0\n\t\t\t}\n\t\tcase tcell.KeyEnter:\n\t\t\tif l.currentItem >= 0 && l.currentItem < len(l.items) {\n\t\t\t\titem := l.items[l.currentItem]\n\t\t\t\tif item.Selected != nil {\n\t\t\t\t\titem.Selected()\n\t\t\t\t}\n\t\t\t\tif l.selected != nil {\n\t\t\t\t\tl.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)\n\t\t\t\t}\n\t\t\t}\n\t\tcase tcell.KeyRune:\n\t\t\tch := event.Rune()\n\t\t\tif ch != ' ' {\n\t\t\t\t// It's not a space bar. Is it a shortcut?\n\t\t\t\tvar found bool\n\t\t\t\tfor index, item := range l.items {\n\t\t\t\t\tif item.Shortcut == ch {\n\t\t\t\t\t\t// We have a shortcut.\n\t\t\t\t\t\tfound = true\n\t\t\t\t\t\tl.currentItem = index\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !found {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\titem := l.items[l.currentItem]\n\t\t\tif item.Selected != nil {\n\t\t\t\titem.Selected()\n\t\t\t}\n\t\t\tif l.selected != nil {\n\t\t\t\tl.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)\n\t\t\t}\n\t\t}\n\n\t\tif l.currentItem < 0 {\n\t\t\tif l.wrapAround {\n\t\t\t\tl.currentItem = len(l.items) - 1\n\t\t\t} else {\n\t\t\t\tl.currentItem = 0\n\t\t\t}\n\t\t} else if l.currentItem >= len(l.items) {\n\t\t\tif l.wrapAround {\n\t\t\t\tl.currentItem = 0\n\t\t\t} else {\n\t\t\t\tl.currentItem = len(l.items) - 1\n\t\t\t}\n\t\t}\n\n\t\tif l.currentItem != previousItem && l.currentItem < len(l.items) {\n\t\t\tif l.changed != nil {\n\t\t\t\titem := l.items[l.currentItem]\n\t\t\t\tl.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// indexAtPoint returns the index of the list item found at the given position\n// or a negative value if there is no such list item.\nfunc (l *List) indexAtPoint(x, y int) int {\n\trectX, rectY, width, height := l.GetInnerRect()\n\tif rectX < 0 || rectX >= rectX+width || y < rectY || y >= rectY+height {\n\t\treturn -1\n\t}\n\n\tindex := y - rectY\n\tif l.showSecondaryText {\n\t\tindex /= 2\n\t}\n\tindex += l.itemOffset\n\n\tif index >= len(l.items) {\n\t\treturn -1\n\t}\n\treturn index\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn l.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif !l.InRect(event.Position()) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Process mouse event.\n\t\tswitch action {\n\t\tcase MouseLeftClick:\n\t\t\tsetFocus(l)\n\t\t\tindex := l.indexAtPoint(event.Position())\n\t\t\tif index != -1 {\n\t\t\t\titem := l.items[index]\n\t\t\t\tif item.Selected != nil {\n\t\t\t\t\titem.Selected()\n\t\t\t\t}\n\t\t\t\tif l.selected != nil {\n\t\t\t\t\tl.selected(index, item.MainText, item.SecondaryText, item.Shortcut)\n\t\t\t\t}\n\t\t\t\tif index != l.currentItem {\n\t\t\t\t\tif l.changed != nil {\n\t\t\t\t\t\tl.changed(index, item.MainText, item.SecondaryText, item.Shortcut)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tl.currentItem = index\n\t\t\t}\n\t\t\tconsumed = true\n\t\tcase MouseScrollUp:\n\t\t\tif l.itemOffset > 0 {\n\t\t\t\tl.itemOffset--\n\t\t\t}\n\t\t\tconsumed = true\n\t\tcase MouseScrollDown:\n\t\t\tlines := len(l.items) - l.itemOffset\n\t\t\tif l.showSecondaryText {\n\t\t\t\tlines *= 2\n\t\t\t}\n\t\t\tif _, _, _, height := l.GetInnerRect(); lines > height {\n\t\t\t\tl.itemOffset++\n\t\t\t}\n\t\t\tconsumed = true\n\t\tcase MouseScrollLeft:\n\t\t\tl.horizontalOffset--\n\t\t\tconsumed = true\n\t\tcase MouseScrollRight:\n\t\t\tl.horizontalOffset++\n\t\t\tconsumed = true\n\t\t}\n\n\t\treturn\n\t})\n}\n"
  },
  {
    "path": "modal.go",
    "content": "package tview\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// Modal is a centered message window used to inform the user or prompt them\n// for an immediate decision. It needs to have at least one button (added via\n// [Modal.AddButtons]) or it will never disappear.\n//\n// See https://github.com/rivo/tview/wiki/Modal for an example.\ntype Modal struct {\n\t*Box\n\n\t// The frame embedded in the modal.\n\tframe *Frame\n\n\t// The form embedded in the modal's frame.\n\tform *Form\n\n\t// The message text (original, not word-wrapped).\n\ttext string\n\n\t// The text color.\n\ttextColor tcell.Color\n\n\t// The optional callback for when the user clicked one of the buttons. It\n\t// receives the index of the clicked button and the button's label.\n\tdone func(buttonIndex int, buttonLabel string)\n}\n\n// NewModal returns a new [Modal] message window.\nfunc NewModal() *Modal {\n\tm := &Modal{\n\t\tBox:       NewBox().SetBorder(true).SetBackgroundColor(Styles.ContrastBackgroundColor),\n\t\ttextColor: Styles.PrimaryTextColor,\n\t}\n\tm.form = NewForm().\n\t\tSetButtonsAlign(AlignCenter).\n\t\tSetButtonBackgroundColor(Styles.PrimitiveBackgroundColor).\n\t\tSetButtonTextColor(Styles.PrimaryTextColor)\n\tm.form.SetBackgroundColor(Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0)\n\tm.form.SetCancelFunc(func() {\n\t\tif m.done != nil {\n\t\t\tm.done(-1, \"\")\n\t\t}\n\t})\n\tm.frame = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0)\n\tm.frame.SetBackgroundColor(Styles.ContrastBackgroundColor).\n\t\tSetBorderPadding(1, 1, 1, 1)\n\tm.Box.Primitive = m\n\treturn m\n}\n\n// SetBackgroundColor sets the color of the modal frame background.\nfunc (m *Modal) SetBackgroundColor(color tcell.Color) *Modal {\n\tm.form.SetBackgroundColor(color)\n\tm.frame.SetBackgroundColor(color)\n\treturn m\n}\n\n// SetTextColor sets the color of the message text.\nfunc (m *Modal) SetTextColor(color tcell.Color) *Modal {\n\tm.textColor = color\n\treturn m\n}\n\n// SetButtonBackgroundColor sets the background color of the buttons.\nfunc (m *Modal) SetButtonBackgroundColor(color tcell.Color) *Modal {\n\tm.form.SetButtonBackgroundColor(color)\n\treturn m\n}\n\n// SetButtonTextColor sets the color of the button texts.\nfunc (m *Modal) SetButtonTextColor(color tcell.Color) *Modal {\n\tm.form.SetButtonTextColor(color)\n\treturn m\n}\n\n// SetButtonStyle sets the style of the buttons when they are not focused.\nfunc (m *Modal) SetButtonStyle(style tcell.Style) *Modal {\n\tm.form.SetButtonStyle(style)\n\treturn m\n}\n\n// SetButtonActivatedStyle sets the style of the buttons when they are focused.\nfunc (m *Modal) SetButtonActivatedStyle(style tcell.Style) *Modal {\n\tm.form.SetButtonActivatedStyle(style)\n\treturn m\n}\n\n// SetDoneFunc sets a handler which is called when one of the buttons was\n// pressed. It receives the index of the button as well as its label text. The\n// handler is also called when the user presses the Escape key. The index will\n// then be negative and the label text an empty string.\nfunc (m *Modal) SetDoneFunc(handler func(buttonIndex int, buttonLabel string)) *Modal {\n\tm.done = handler\n\treturn m\n}\n\n// SetText sets the message text of the window. The text may contain line\n// breaks but style tag states will not transfer to following lines. Note that\n// words are wrapped, too, based on the final size of the window.\nfunc (m *Modal) SetText(text string) *Modal {\n\tm.text = text\n\treturn m\n}\n\n// AddButtons adds buttons to the window. There must be at least one button and\n// a \"done\" handler so the window can be closed again.\nfunc (m *Modal) AddButtons(labels []string) *Modal {\n\tfor index, label := range labels {\n\t\tfunc(i int, l string) {\n\t\t\tm.form.AddButton(label, func() {\n\t\t\t\tif m.done != nil {\n\t\t\t\t\tm.done(i, l)\n\t\t\t\t}\n\t\t\t})\n\t\t\tbutton := m.form.GetButton(m.form.GetButtonCount() - 1)\n\t\t\tbutton.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {\n\t\t\t\tswitch event.Key() {\n\t\t\t\tcase tcell.KeyDown, tcell.KeyRight:\n\t\t\t\t\treturn tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)\n\t\t\t\tcase tcell.KeyUp, tcell.KeyLeft:\n\t\t\t\t\treturn tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)\n\t\t\t\t}\n\t\t\t\treturn event\n\t\t\t})\n\t\t}(index, label)\n\t}\n\treturn m\n}\n\n// ClearButtons removes all buttons from the window.\nfunc (m *Modal) ClearButtons() *Modal {\n\tm.form.ClearButtons()\n\treturn m\n}\n\n// SetFocus shifts the focus to the button with the given index.\nfunc (m *Modal) SetFocus(index int) *Modal {\n\tm.form.SetFocus(index)\n\treturn m\n}\n\n// Focus is called when this primitive receives focus.\nfunc (m *Modal) Focus(delegate func(p Primitive)) {\n\tdelegate(m.form)\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (m *Modal) focusChain(chain *[]Primitive) bool {\n\tif hasFocus := m.form.focusChain(chain); hasFocus {\n\t\tif chain != nil {\n\t\t\t*chain = append(*chain, m)\n\t\t}\n\t\treturn true\n\t}\n\treturn m.Box.focusChain(chain)\n}\n\n// Draw draws this primitive onto the screen.\nfunc (m *Modal) Draw(screen tcell.Screen) {\n\t// Calculate the width of this modal.\n\tbuttonsWidth := 0\n\tfor _, button := range m.form.buttons {\n\t\tbuttonsWidth += TaggedStringWidth(button.text) + 4 + 2\n\t}\n\tbuttonsWidth -= 2\n\tscreenWidth, screenHeight := screen.Size()\n\twidth := screenWidth / 3\n\tif width < buttonsWidth {\n\t\twidth = buttonsWidth\n\t}\n\t// width is now without the box border.\n\n\t// Reset the text and find out how wide it is.\n\tm.frame.Clear()\n\tlines := WordWrap(m.text, width)\n\tfor _, line := range lines {\n\t\tm.frame.AddText(line, true, AlignCenter, m.textColor)\n\t}\n\n\t// Set the modal's position and size.\n\theight := len(lines) + 6\n\twidth += 4\n\tx := (screenWidth - width) / 2\n\ty := (screenHeight - height) / 2\n\tm.SetRect(x, y, width, height)\n\n\t// Draw the frame.\n\tm.Box.DrawForSubclass(screen, m)\n\tx, y, width, height = m.GetInnerRect()\n\tm.frame.SetRect(x, y, width, height)\n\tm.frame.Draw(screen)\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (m *Modal) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn m.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\t// Pass mouse events on to the form.\n\t\tconsumed, capture = m.form.MouseHandler()(action, event, setFocus)\n\t\tif !consumed && action == MouseLeftDown && m.InRect(event.Position()) {\n\t\t\tsetFocus(m)\n\t\t\tconsumed = true\n\t\t}\n\t\treturn\n\t})\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (m *Modal) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tif m.frame.HasFocus() {\n\t\t\tif handler := m.frame.InputHandler(); handler != nil {\n\t\t\t\thandler(event, setFocus)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pages.go",
    "content": "package tview\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// page represents one page of a Pages object.\ntype page struct {\n\tName    string    // The page's name.\n\tItem    Primitive // The page's primitive.\n\tResize  bool      // Whether or not to resize the page when it is drawn.\n\tVisible bool      // Whether or not this page is visible.\n}\n\n// Pages is a container for other primitives laid out on top of each other,\n// overlapping or not. It is often used as the application's root primitive. It\n// allows to easily switch the visibility of the contained primitives.\n//\n// See https://github.com/rivo/tview/wiki/Pages for an example.\ntype Pages struct {\n\t*Box\n\n\t// The contained pages. (Visible) pages are drawn from back to front.\n\tpages []*page\n\n\t// We keep a reference to the function which allows us to set the focus to\n\t// a newly visible page.\n\tsetFocus func(p Primitive)\n\n\t// An optional handler which is called whenever the visibility or the order of\n\t// pages changes.\n\tchanged func()\n}\n\n// NewPages returns a new [Pages] object.\nfunc NewPages() *Pages {\n\tp := &Pages{\n\t\tBox: NewBox(),\n\t}\n\tp.Box.Primitive = p\n\treturn p\n}\n\n// SetChangedFunc sets a handler which is called whenever the visibility or the\n// order of any visible pages changes. This can be used to redraw the pages.\nfunc (p *Pages) SetChangedFunc(handler func()) *Pages {\n\tp.changed = handler\n\treturn p\n}\n\n// GetPageCount returns the number of pages currently stored in this object.\nfunc (p *Pages) GetPageCount() int {\n\treturn len(p.pages)\n}\n\n// GetPageNames returns all page names ordered from front to back,\n// optionally limited to visible pages.\nfunc (p *Pages) GetPageNames(visibleOnly bool) []string {\n\tvar names []string\n\tfor index := len(p.pages) - 1; index >= 0; index-- {\n\t\tif !visibleOnly || p.pages[index].Visible {\n\t\t\tnames = append(names, p.pages[index].Name)\n\t\t}\n\t}\n\treturn names\n}\n\n// Clear removes all pages.\nfunc (p *Pages) Clear() *Pages {\n\tp.pages = nil\n\treturn p\n}\n\n// AddPage adds a new page with the given name and primitive. If there was\n// previously a page with the same name, it is overwritten. Leaving the name\n// empty may cause conflicts in other functions so you should always specify a\n// non-empty name.\n//\n// Visible pages will be drawn in the order they were added (unless that order\n// was changed in one of the other functions). If \"resize\" is set to true, the\n// primitive will be set to the size available to the [Pages] primitive whenever\n// the pages are drawn.\nfunc (p *Pages) AddPage(name string, item Primitive, resize, visible bool) *Pages {\n\thasFocus := p.HasFocus()\n\tfor index, pg := range p.pages {\n\t\tif pg.Name == name {\n\t\t\tp.pages = append(p.pages[:index], p.pages[index+1:]...)\n\t\t\tbreak\n\t\t}\n\t}\n\tp.pages = append(p.pages, &page{Item: item, Name: name, Resize: resize, Visible: visible})\n\tif p.changed != nil {\n\t\tp.changed()\n\t}\n\tif hasFocus {\n\t\tp.Focus(p.setFocus)\n\t}\n\treturn p\n}\n\n// AddAndSwitchToPage calls [Pages.AddPage], then [Pages.SwitchToPage] on that\n// newly added page.\nfunc (p *Pages) AddAndSwitchToPage(name string, item Primitive, resize bool) *Pages {\n\tp.AddPage(name, item, resize, true)\n\tp.SwitchToPage(name)\n\treturn p\n}\n\n// RemovePage removes the page with the given name. If that page was the only\n// visible page, visibility is assigned to the last page.\nfunc (p *Pages) RemovePage(name string) *Pages {\n\tvar isVisible bool\n\thasFocus := p.HasFocus()\n\tfor index, page := range p.pages {\n\t\tif page.Name == name {\n\t\t\tisVisible = page.Visible\n\t\t\tp.pages = append(p.pages[:index], p.pages[index+1:]...)\n\t\t\tif page.Visible && p.changed != nil {\n\t\t\t\tp.changed()\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tif isVisible {\n\t\tfor index, page := range p.pages {\n\t\t\tif index < len(p.pages)-1 {\n\t\t\t\tif page.Visible {\n\t\t\t\t\tbreak // There is a remaining visible page.\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tpage.Visible = true // We need at least one visible page.\n\t\t\t}\n\t\t}\n\t}\n\tif hasFocus {\n\t\tp.Focus(p.setFocus)\n\t}\n\treturn p\n}\n\n// HasPage returns true if a page with the given name exists in this object.\nfunc (p *Pages) HasPage(name string) bool {\n\tfor _, page := range p.pages {\n\t\tif page.Name == name {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// ShowPage sets a page's visibility to \"true\" (in addition to any other pages\n// which are already visible).\nfunc (p *Pages) ShowPage(name string) *Pages {\n\tfor _, page := range p.pages {\n\t\tif page.Name == name {\n\t\t\tpage.Visible = true\n\t\t\tif p.changed != nil {\n\t\t\t\tp.changed()\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tif p.HasFocus() {\n\t\tp.Focus(p.setFocus)\n\t}\n\treturn p\n}\n\n// HidePage sets a page's visibility to \"false\".\nfunc (p *Pages) HidePage(name string) *Pages {\n\tfor _, page := range p.pages {\n\t\tif page.Name == name {\n\t\t\tpage.Visible = false\n\t\t\tif p.changed != nil {\n\t\t\t\tp.changed()\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tif p.HasFocus() {\n\t\tp.Focus(p.setFocus)\n\t}\n\treturn p\n}\n\n// SwitchToPage sets a page's visibility to \"true\" and all other pages'\n// visibility to \"false\".\nfunc (p *Pages) SwitchToPage(name string) *Pages {\n\tfor _, page := range p.pages {\n\t\tif page.Name == name {\n\t\t\tpage.Visible = true\n\t\t} else {\n\t\t\tpage.Visible = false\n\t\t}\n\t}\n\tif p.changed != nil {\n\t\tp.changed()\n\t}\n\tif p.HasFocus() {\n\t\tp.Focus(p.setFocus)\n\t}\n\treturn p\n}\n\n// SendToFront changes the order of the pages such that the page with the given\n// name comes last, causing it to be drawn last with the next update (if\n// visible).\nfunc (p *Pages) SendToFront(name string) *Pages {\n\tfor index, page := range p.pages {\n\t\tif page.Name == name {\n\t\t\tif index < len(p.pages)-1 {\n\t\t\t\tp.pages = append(append(p.pages[:index], p.pages[index+1:]...), page)\n\t\t\t}\n\t\t\tif page.Visible && p.changed != nil {\n\t\t\t\tp.changed()\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tif p.HasFocus() {\n\t\tp.Focus(p.setFocus)\n\t}\n\treturn p\n}\n\n// SendToBack changes the order of the pages such that the page with the given\n// name comes first, causing it to be drawn first with the next update (if\n// visible).\nfunc (p *Pages) SendToBack(name string) *Pages {\n\tfor index, pg := range p.pages {\n\t\tif pg.Name == name {\n\t\t\tif index > 0 {\n\t\t\t\tp.pages = append(append([]*page{pg}, p.pages[:index]...), p.pages[index+1:]...)\n\t\t\t}\n\t\t\tif pg.Visible && p.changed != nil {\n\t\t\t\tp.changed()\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tif p.HasFocus() {\n\t\tp.Focus(p.setFocus)\n\t}\n\treturn p\n}\n\n// GetFrontPage returns the front-most visible page. If there are no visible\n// pages, (\"\", nil) is returned.\nfunc (p *Pages) GetFrontPage() (name string, item Primitive) {\n\tfor index := len(p.pages) - 1; index >= 0; index-- {\n\t\tif p.pages[index].Visible {\n\t\t\treturn p.pages[index].Name, p.pages[index].Item\n\t\t}\n\t}\n\treturn\n}\n\n// GetPage returns the page with the given name. If no such page exists, nil is\n// returned.\nfunc (p *Pages) GetPage(name string) Primitive {\n\tfor _, page := range p.pages {\n\t\tif page.Name == name {\n\t\t\treturn page.Item\n\t\t}\n\t}\n\treturn nil\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (p *Pages) focusChain(chain *[]Primitive) bool {\n\tfor _, page := range p.pages {\n\t\tif hasFocus := page.Item.focusChain(chain); hasFocus {\n\t\t\tif chain != nil {\n\t\t\t\t*chain = append(*chain, p)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\treturn p.Box.focusChain(chain)\n}\n\n// Focus is called by the application when the primitive receives focus.\nfunc (p *Pages) Focus(delegate func(p Primitive)) {\n\tp.setFocus = delegate\n\tvar topItem Primitive\n\tfor _, page := range p.pages {\n\t\tif page.Visible {\n\t\t\ttopItem = page.Item\n\t\t}\n\t}\n\tif topItem != nil {\n\t\tdelegate(topItem)\n\t} else {\n\t\tp.Box.Focus(delegate)\n\t}\n}\n\n// Draw draws this primitive onto the screen.\nfunc (p *Pages) Draw(screen tcell.Screen) {\n\tp.Box.DrawForSubclass(screen, p)\n\tfor _, page := range p.pages {\n\t\tif !page.Visible {\n\t\t\tcontinue\n\t\t}\n\t\tif page.Resize {\n\t\t\tx, y, width, height := p.GetInnerRect()\n\t\t\tpage.Item.SetRect(x, y, width, height)\n\t\t}\n\t\tpage.Item.Draw(screen)\n\t}\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (p *Pages) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn p.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif !p.InRect(event.Position()) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Pass mouse events along to the last visible page item that takes it.\n\t\tfor index := len(p.pages) - 1; index >= 0; index-- {\n\t\t\tpage := p.pages[index]\n\t\t\tif page.Visible {\n\t\t\t\tconsumed, capture = page.Item.MouseHandler()(action, event, setFocus)\n\t\t\t\tif consumed {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t})\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (p *Pages) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn p.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tfor _, page := range p.pages {\n\t\t\tif page.Item.HasFocus() {\n\t\t\t\tif handler := page.Item.InputHandler(); handler != nil {\n\t\t\t\t\thandler(event, setFocus)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n\n// PasteHandler returns the handler for this primitive.\nfunc (p *Pages) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {\n\treturn p.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {\n\t\tfor _, page := range p.pages {\n\t\t\tif page.Item.HasFocus() {\n\t\t\t\tif handler := page.Item.PasteHandler(); handler != nil {\n\t\t\t\t\thandler(pastedText, setFocus)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "primitive.go",
    "content": "package tview\n\nimport \"github.com/gdamore/tcell/v2\"\n\n// Primitive is the top-most interface for all graphical primitives.\ntype Primitive interface {\n\t// Draw draws this primitive onto the screen. Implementers can call the\n\t// screen's ShowCursor() function but should only do so when they have focus.\n\t// (They will need to keep track of this themselves.)\n\tDraw(screen tcell.Screen)\n\n\t// GetRect returns the current position of the primitive, x, y, width, and\n\t// height.\n\tGetRect() (int, int, int, int)\n\n\t// SetRect sets a new position of the primitive.\n\tSetRect(x, y, width, height int)\n\n\t// InputHandler returns a handler which receives key events when it has focus.\n\t// It is called by the Application class.\n\t//\n\t// A value of nil may also be returned, in which case this primitive cannot\n\t// receive focus and will not process any key events.\n\t//\n\t// The handler will receive the key event and a function that allows it to\n\t// set the focus to a different primitive, so that future key events are sent\n\t// to that primitive.\n\t//\n\t// The Application's Draw() function will be called automatically after the\n\t// handler returns.\n\t//\n\t// The Box class provides functionality to intercept keyboard input. If you\n\t// subclass from Box, it is recommended that you wrap your handler using\n\t// Box.WrapInputHandler() so you inherit that functionality.\n\tInputHandler() func(event *tcell.EventKey, setFocus func(p Primitive))\n\n\t// Focus is called by the application when the primitive receives focus.\n\t// Implementers may call delegate() to pass the focus on to another\n\t// primitive which is usually a child primitive. This is not called on\n\t// parents of the primitive that receives focus.\n\tFocus(delegate func(p Primitive))\n\n\t// HasFocus determines if the primitive (or any of its child primitives) has\n\t// focus.\n\tHasFocus() bool\n\n\t// Blur is called by the application when the primitive loses focus. This is\n\t// not called on parents of the primitive that loses focus.\n\tBlur()\n\n\t// MouseHandler returns a handler which receives mouse events.\n\t// It is called by the Application class.\n\t//\n\t// A value of nil may also be returned to stop the downward propagation of\n\t// mouse events.\n\t//\n\t// The Box class provides functionality to intercept mouse events. If you\n\t// subclass from Box, it is recommended that you wrap your handler using\n\t// Box.WrapMouseHandler() so you inherit that functionality.\n\tMouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive)\n\n\t// PasteHandler returns a handler which receives pasted text.\n\t// It is called by the Application class.\n\t//\n\t// A value of nil may also be returned to stop the downward propagation of\n\t// paste events.\n\t//\n\t// The Box class may provide functionality to intercept paste events in the\n\t// future. If you subclass from [Box], it is recommended that you wrap your\n\t// handler using Box.WrapPasteHandler() so you inherit that functionality.\n\tPasteHandler() func(text string, setFocus func(p Primitive))\n\n\t// focusChain adds the chain of primitives that have focus to the given\n\t// slice, starting with the bottom-most primitive that has focus and ending\n\t// with this box. If this box or none of its descendents has focus, the\n\t// slice is not modified. If chain is nil, no chain is added. Returns\n\t// whether or not this box or one of its descendents has focus.\n\tfocusChain(chain *[]Primitive) bool\n\n\t// focused is called when the current input focus changes. It is called on\n\t// the primitive which newly received focus as well as on all of its\n\t// ancestors (in no defined order). The default implementation in [Box]\n\t// invokes the callback set with [Box.SetFocusFunc]. This can also happen\n\t// when the focus is set to the primitive that already has focus.\n\tfocused()\n\n\t// blurred is called when the current input focus changes. It is called on\n\t// the primitive which lost focus as well as on all of its ancestors (in no\n\t// defined order). The default implementation in [Box] invokes the callback\n\t// set with [Box.SetBlurFunc]. This can also happen when the focus is set to\n\t// the primitive that already has focus.\n\tblurred()\n}\n"
  },
  {
    "path": "semigraphics.go",
    "content": "package tview\n\nimport \"github.com/gdamore/tcell/v2\"\n\n// Semigraphics provides an easy way to access unicode characters for drawing.\n//\n// Named like the unicode characters, 'Semigraphics'-prefix used if unicode block\n// isn't prefixed itself.\nconst (\n\t// Block: General Punctuation U+2000-U+206F (http://unicode.org/charts/PDF/U2000.pdf)\n\tSemigraphicsHorizontalEllipsis rune = '\\u2026' // …\n\n\t// Block: Box Drawing U+2500-U+257F (http://unicode.org/charts/PDF/U2500.pdf)\n\tBoxDrawingsLightHorizontal                    rune = '\\u2500' // ─\n\tBoxDrawingsHeavyHorizontal                    rune = '\\u2501' // ━\n\tBoxDrawingsLightVertical                      rune = '\\u2502' // │\n\tBoxDrawingsHeavyVertical                      rune = '\\u2503' // ┃\n\tBoxDrawingsLightTripleDashHorizontal          rune = '\\u2504' // ┄\n\tBoxDrawingsHeavyTripleDashHorizontal          rune = '\\u2505' // ┅\n\tBoxDrawingsLightTripleDashVertical            rune = '\\u2506' // ┆\n\tBoxDrawingsHeavyTripleDashVertical            rune = '\\u2507' // ┇\n\tBoxDrawingsLightQuadrupleDashHorizontal       rune = '\\u2508' // ┈\n\tBoxDrawingsHeavyQuadrupleDashHorizontal       rune = '\\u2509' // ┉\n\tBoxDrawingsLightQuadrupleDashVertical         rune = '\\u250a' // ┊\n\tBoxDrawingsHeavyQuadrupleDashVertical         rune = '\\u250b' // ┋\n\tBoxDrawingsLightDownAndRight                  rune = '\\u250c' // ┌\n\tBoxDrawingsDownLightAndRightHeavy             rune = '\\u250d' // ┍\n\tBoxDrawingsDownHeavyAndRightLight             rune = '\\u250e' // ┎\n\tBoxDrawingsHeavyDownAndRight                  rune = '\\u250f' // ┏\n\tBoxDrawingsLightDownAndLeft                   rune = '\\u2510' // ┐\n\tBoxDrawingsDownLightAndLeftHeavy              rune = '\\u2511' // ┑\n\tBoxDrawingsDownHeavyAndLeftLight              rune = '\\u2512' // ┒\n\tBoxDrawingsHeavyDownAndLeft                   rune = '\\u2513' // ┓\n\tBoxDrawingsLightUpAndRight                    rune = '\\u2514' // └\n\tBoxDrawingsUpLightAndRightHeavy               rune = '\\u2515' // ┕\n\tBoxDrawingsUpHeavyAndRightLight               rune = '\\u2516' // ┖\n\tBoxDrawingsHeavyUpAndRight                    rune = '\\u2517' // ┗\n\tBoxDrawingsLightUpAndLeft                     rune = '\\u2518' // ┘\n\tBoxDrawingsUpLightAndLeftHeavy                rune = '\\u2519' // ┙\n\tBoxDrawingsUpHeavyAndLeftLight                rune = '\\u251a' // ┚\n\tBoxDrawingsHeavyUpAndLeft                     rune = '\\u251b' // ┛\n\tBoxDrawingsLightVerticalAndRight              rune = '\\u251c' // ├\n\tBoxDrawingsVerticalLightAndRightHeavy         rune = '\\u251d' // ┝\n\tBoxDrawingsUpHeavyAndRightDownLight           rune = '\\u251e' // ┞\n\tBoxDrawingsDownHeavyAndRightUpLight           rune = '\\u251f' // ┟\n\tBoxDrawingsVerticalHeavyAndRightLight         rune = '\\u2520' // ┠\n\tBoxDrawingsDownLightAndRightUpHeavy           rune = '\\u2521' // ┡\n\tBoxDrawingsUpLightAndRightDownHeavy           rune = '\\u2522' // ┢\n\tBoxDrawingsHeavyVerticalAndRight              rune = '\\u2523' // ┣\n\tBoxDrawingsLightVerticalAndLeft               rune = '\\u2524' // ┤\n\tBoxDrawingsVerticalLightAndLeftHeavy          rune = '\\u2525' // ┥\n\tBoxDrawingsUpHeavyAndLeftDownLight            rune = '\\u2526' // ┦\n\tBoxDrawingsDownHeavyAndLeftUpLight            rune = '\\u2527' // ┧\n\tBoxDrawingsVerticalHeavyAndLeftLight          rune = '\\u2528' // ┨\n\tBoxDrawingsDownLightAndLeftUpHeavy            rune = '\\u2529' // ┨\n\tBoxDrawingsUpLightAndLeftDownHeavy            rune = '\\u252a' // ┪\n\tBoxDrawingsHeavyVerticalAndLeft               rune = '\\u252b' // ┫\n\tBoxDrawingsLightDownAndHorizontal             rune = '\\u252c' // ┬\n\tBoxDrawingsLeftHeavyAndRightDownLight         rune = '\\u252d' // ┭\n\tBoxDrawingsRightHeavyAndLeftDownLight         rune = '\\u252e' // ┮\n\tBoxDrawingsDownLightAndHorizontalHeavy        rune = '\\u252f' // ┯\n\tBoxDrawingsDownHeavyAndHorizontalLight        rune = '\\u2530' // ┰\n\tBoxDrawingsRightLightAndLeftDownHeavy         rune = '\\u2531' // ┱\n\tBoxDrawingsLeftLightAndRightDownHeavy         rune = '\\u2532' // ┲\n\tBoxDrawingsHeavyDownAndHorizontal             rune = '\\u2533' // ┳\n\tBoxDrawingsLightUpAndHorizontal               rune = '\\u2534' // ┴\n\tBoxDrawingsLeftHeavyAndRightUpLight           rune = '\\u2535' // ┵\n\tBoxDrawingsRightHeavyAndLeftUpLight           rune = '\\u2536' // ┶\n\tBoxDrawingsUpLightAndHorizontalHeavy          rune = '\\u2537' // ┷\n\tBoxDrawingsUpHeavyAndHorizontalLight          rune = '\\u2538' // ┸\n\tBoxDrawingsRightLightAndLeftUpHeavy           rune = '\\u2539' // ┹\n\tBoxDrawingsLeftLightAndRightUpHeavy           rune = '\\u253a' // ┺\n\tBoxDrawingsHeavyUpAndHorizontal               rune = '\\u253b' // ┻\n\tBoxDrawingsLightVerticalAndHorizontal         rune = '\\u253c' // ┼\n\tBoxDrawingsLeftHeavyAndRightVerticalLight     rune = '\\u253d' // ┽\n\tBoxDrawingsRightHeavyAndLeftVerticalLight     rune = '\\u253e' // ┾\n\tBoxDrawingsVerticalLightAndHorizontalHeavy    rune = '\\u253f' // ┿\n\tBoxDrawingsUpHeavyAndDownHorizontalLight      rune = '\\u2540' // ╀\n\tBoxDrawingsDownHeavyAndUpHorizontalLight      rune = '\\u2541' // ╁\n\tBoxDrawingsVerticalHeavyAndHorizontalLight    rune = '\\u2542' // ╂\n\tBoxDrawingsLeftUpHeavyAndRightDownLight       rune = '\\u2543' // ╃\n\tBoxDrawingsRightUpHeavyAndLeftDownLight       rune = '\\u2544' // ╄\n\tBoxDrawingsLeftDownHeavyAndRightUpLight       rune = '\\u2545' // ╅\n\tBoxDrawingsRightDownHeavyAndLeftUpLight       rune = '\\u2546' // ╆\n\tBoxDrawingsDownLightAndUpHorizontalHeavy      rune = '\\u2547' // ╇\n\tBoxDrawingsUpLightAndDownHorizontalHeavy      rune = '\\u2548' // ╈\n\tBoxDrawingsRightLightAndLeftVerticalHeavy     rune = '\\u2549' // ╉\n\tBoxDrawingsLeftLightAndRightVerticalHeavy     rune = '\\u254a' // ╊\n\tBoxDrawingsHeavyVerticalAndHorizontal         rune = '\\u254b' // ╋\n\tBoxDrawingsLightDoubleDashHorizontal          rune = '\\u254c' // ╌\n\tBoxDrawingsHeavyDoubleDashHorizontal          rune = '\\u254d' // ╍\n\tBoxDrawingsLightDoubleDashVertical            rune = '\\u254e' // ╎\n\tBoxDrawingsHeavyDoubleDashVertical            rune = '\\u254f' // ╏\n\tBoxDrawingsDoubleHorizontal                   rune = '\\u2550' // ═\n\tBoxDrawingsDoubleVertical                     rune = '\\u2551' // ║\n\tBoxDrawingsDownSingleAndRightDouble           rune = '\\u2552' // ╒\n\tBoxDrawingsDownDoubleAndRightSingle           rune = '\\u2553' // ╓\n\tBoxDrawingsDoubleDownAndRight                 rune = '\\u2554' // ╔\n\tBoxDrawingsDownSingleAndLeftDouble            rune = '\\u2555' // ╕\n\tBoxDrawingsDownDoubleAndLeftSingle            rune = '\\u2556' // ╖\n\tBoxDrawingsDoubleDownAndLeft                  rune = '\\u2557' // ╗\n\tBoxDrawingsUpSingleAndRightDouble             rune = '\\u2558' // ╘\n\tBoxDrawingsUpDoubleAndRightSingle             rune = '\\u2559' // ╙\n\tBoxDrawingsDoubleUpAndRight                   rune = '\\u255a' // ╚\n\tBoxDrawingsUpSingleAndLeftDouble              rune = '\\u255b' // ╛\n\tBoxDrawingsUpDoubleAndLeftSingle              rune = '\\u255c' // ╜\n\tBoxDrawingsDoubleUpAndLeft                    rune = '\\u255d' // ╝\n\tBoxDrawingsVerticalSingleAndRightDouble       rune = '\\u255e' // ╞\n\tBoxDrawingsVerticalDoubleAndRightSingle       rune = '\\u255f' // ╟\n\tBoxDrawingsDoubleVerticalAndRight             rune = '\\u2560' // ╠\n\tBoxDrawingsVerticalSingleAndLeftDouble        rune = '\\u2561' // ╡\n\tBoxDrawingsVerticalDoubleAndLeftSingle        rune = '\\u2562' // ╢\n\tBoxDrawingsDoubleVerticalAndLeft              rune = '\\u2563' // ╣\n\tBoxDrawingsDownSingleAndHorizontalDouble      rune = '\\u2564' // ╤\n\tBoxDrawingsDownDoubleAndHorizontalSingle      rune = '\\u2565' // ╥\n\tBoxDrawingsDoubleDownAndHorizontal            rune = '\\u2566' // ╦\n\tBoxDrawingsUpSingleAndHorizontalDouble        rune = '\\u2567' // ╧\n\tBoxDrawingsUpDoubleAndHorizontalSingle        rune = '\\u2568' // ╨\n\tBoxDrawingsDoubleUpAndHorizontal              rune = '\\u2569' // ╩\n\tBoxDrawingsVerticalSingleAndHorizontalDouble  rune = '\\u256a' // ╪\n\tBoxDrawingsVerticalDoubleAndHorizontalSingle  rune = '\\u256b' // ╫\n\tBoxDrawingsDoubleVerticalAndHorizontal        rune = '\\u256c' // ╬\n\tBoxDrawingsLightArcDownAndRight               rune = '\\u256d' // ╭\n\tBoxDrawingsLightArcDownAndLeft                rune = '\\u256e' // ╮\n\tBoxDrawingsLightArcUpAndLeft                  rune = '\\u256f' // ╯\n\tBoxDrawingsLightArcUpAndRight                 rune = '\\u2570' // ╰\n\tBoxDrawingsLightDiagonalUpperRightToLowerLeft rune = '\\u2571' // ╱\n\tBoxDrawingsLightDiagonalUpperLeftToLowerRight rune = '\\u2572' // ╲\n\tBoxDrawingsLightDiagonalCross                 rune = '\\u2573' // ╳\n\tBoxDrawingsLightLeft                          rune = '\\u2574' // ╴\n\tBoxDrawingsLightUp                            rune = '\\u2575' // ╵\n\tBoxDrawingsLightRight                         rune = '\\u2576' // ╶\n\tBoxDrawingsLightDown                          rune = '\\u2577' // ╷\n\tBoxDrawingsHeavyLeft                          rune = '\\u2578' // ╸\n\tBoxDrawingsHeavyUp                            rune = '\\u2579' // ╹\n\tBoxDrawingsHeavyRight                         rune = '\\u257a' // ╺\n\tBoxDrawingsHeavyDown                          rune = '\\u257b' // ╻\n\tBoxDrawingsLightLeftAndHeavyRight             rune = '\\u257c' // ╼\n\tBoxDrawingsLightUpAndHeavyDown                rune = '\\u257d' // ╽\n\tBoxDrawingsHeavyLeftAndLightRight             rune = '\\u257e' // ╾\n\tBoxDrawingsHeavyUpAndLightDown                rune = '\\u257f' // ╿\n\n\t// Block Elements.\n\tBlockUpperHalfBlock                              rune = '\\u2580' // ▀\n\tBlockLowerOneEighthBlock                         rune = '\\u2581' // ▁\n\tBlockLowerOneQuarterBlock                        rune = '\\u2582' // ▂\n\tBlockLowerThreeEighthsBlock                      rune = '\\u2583' // ▃\n\tBlockLowerHalfBlock                              rune = '\\u2584' // ▄\n\tBlockLowerFiveEighthsBlock                       rune = '\\u2585' // ▅\n\tBlockLowerThreeQuartersBlock                     rune = '\\u2586' // ▆\n\tBlockLowerSevenEighthsBlock                      rune = '\\u2587' // ▇\n\tBlockFullBlock                                   rune = '\\u2588' // █\n\tBlockLeftSevenEighthsBlock                       rune = '\\u2589' // ▉\n\tBlockLeftThreeQuartersBlock                      rune = '\\u258A' // ▊\n\tBlockLeftFiveEighthsBlock                        rune = '\\u258B' // ▋\n\tBlockLeftHalfBlock                               rune = '\\u258C' // ▌\n\tBlockLeftThreeEighthsBlock                       rune = '\\u258D' // ▍\n\tBlockLeftOneQuarterBlock                         rune = '\\u258E' // ▎\n\tBlockLeftOneEighthBlock                          rune = '\\u258F' // ▏\n\tBlockRightHalfBlock                              rune = '\\u2590' // ▐\n\tBlockLightShade                                  rune = '\\u2591' // ░\n\tBlockMediumShade                                 rune = '\\u2592' // ▒\n\tBlockDarkShade                                   rune = '\\u2593' // ▓\n\tBlockUpperOneEighthBlock                         rune = '\\u2594' // ▔\n\tBlockRightOneEighthBlock                         rune = '\\u2595' // ▕\n\tBlockQuadrantLowerLeft                           rune = '\\u2596' // ▖\n\tBlockQuadrantLowerRight                          rune = '\\u2597' // ▗\n\tBlockQuadrantUpperLeft                           rune = '\\u2598' // ▘\n\tBlockQuadrantUpperLeftAndLowerLeftAndLowerRight  rune = '\\u2599' // ▙\n\tBlockQuadrantUpperLeftAndLowerRight              rune = '\\u259A' // ▚\n\tBlockQuadrantUpperLeftAndUpperRightAndLowerLeft  rune = '\\u259B' // ▛\n\tBlockQuadrantUpperLeftAndUpperRightAndLowerRight rune = '\\u259C' // ▜\n\tBlockQuadrantUpperRight                          rune = '\\u259D' // ▝\n\tBlockQuadrantUpperRightAndLowerLeft              rune = '\\u259E' // ▞\n\tBlockQuadrantUpperRightAndLowerLeftAndLowerRight rune = '\\u259F' // ▟\n)\n\n// SemigraphicJoints is a map for joining semigraphic (or otherwise) runes.\n// So far only light lines are supported but if you want to change the border\n// styling you need to provide the joints, too.\n// The matching will be sorted ascending by rune value, so you don't need to\n// provide all rune combinations,\n// e.g. (─) + (│) = (┼) will also match (│) + (─) = (┼)\nvar SemigraphicJoints = map[string]rune{\n\t// (─) + (│) = (┼)\n\tstring([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVertical}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (─) + (┌) = (┬)\n\tstring([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndRight}): BoxDrawingsLightDownAndHorizontal,\n\t// (─) + (┐) = (┬)\n\tstring([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightDownAndHorizontal,\n\t// (─) + (└) = (┴)\n\tstring([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndRight}): BoxDrawingsLightUpAndHorizontal,\n\t// (─) + (┘) = (┴)\n\tstring([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightUpAndHorizontal,\n\t// (─) + (├) = (┼)\n\tstring([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (─) + (┤) = (┼)\n\tstring([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (─) + (┬) = (┬)\n\tstring([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,\n\t// (─) + (┴) = (┴)\n\tstring([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,\n\t// (─) + (┼) = (┼)\n\tstring([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\n\t// (│) + (┌) = (├)\n\tstring([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndRight}): BoxDrawingsLightVerticalAndRight,\n\t// (│) + (┐) = (┤)\n\tstring([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightVerticalAndLeft,\n\t// (│) + (└) = (├)\n\tstring([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndRight,\n\t// (│) + (┘) = (┤)\n\tstring([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndLeft,\n\t// (│) + (├) = (├)\n\tstring([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,\n\t// (│) + (┤) = (┤)\n\tstring([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,\n\t// (│) + (┬) = (┼)\n\tstring([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (│) + (┴) = (┼)\n\tstring([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (│) + (┼) = (┼)\n\tstring([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\n\t// (┌) + (┐) = (┬)\n\tstring([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightDownAndHorizontal,\n\t// (┌) + (└) = (├)\n\tstring([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndRight,\n\t// (┌) + (┘) = (┼)\n\tstring([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┌) + (├) = (├)\n\tstring([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,\n\t// (┌) + (┤) = (┼)\n\tstring([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┌) + (┬) = (┬)\n\tstring([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,\n\t// (┌) + (┴) = (┼)\n\tstring([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┌) + (┴) = (┼)\n\tstring([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\n\t// (┐) + (└) = (┼)\n\tstring([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┐) + (┘) = (┤)\n\tstring([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndLeft,\n\t// (┐) + (├) = (┼)\n\tstring([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┐) + (┤) = (┤)\n\tstring([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,\n\t// (┐) + (┬) = (┬)\n\tstring([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,\n\t// (┐) + (┴) = (┼)\n\tstring([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┐) + (┼) = (┼)\n\tstring([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\n\t// (└) + (┘) = (┴)\n\tstring([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightUpAndHorizontal,\n\t// (└) + (├) = (├)\n\tstring([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,\n\t// (└) + (┤) = (┼)\n\tstring([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (└) + (┬) = (┼)\n\tstring([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (└) + (┴) = (┴)\n\tstring([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,\n\t// (└) + (┼) = (┼)\n\tstring([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\n\t// (┘) + (├) = (┼)\n\tstring([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┘) + (┤) = (┤)\n\tstring([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,\n\t// (┘) + (┬) = (┼)\n\tstring([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┘) + (┴) = (┴)\n\tstring([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,\n\t// (┘) + (┼) = (┼)\n\tstring([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\n\t// (├) + (┤) = (┼)\n\tstring([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (├) + (┬) = (┼)\n\tstring([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (├) + (┴) = (┼)\n\tstring([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (├) + (┼) = (┼)\n\tstring([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\n\t// (┤) + (┬) = (┼)\n\tstring([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┤) + (┴) = (┼)\n\tstring([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┤) + (┼) = (┼)\n\tstring([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\n\t// (┬) + (┴) = (┼)\n\tstring([]rune{BoxDrawingsLightDownAndHorizontal, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\t// (┬) + (┼) = (┼)\n\tstring([]rune{BoxDrawingsLightDownAndHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n\n\t// (┴) + (┼) = (┼)\n\tstring([]rune{BoxDrawingsLightUpAndHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,\n}\n\n// PrintJoinedSemigraphics prints a semigraphics rune into the screen at the given\n// position with the given style, joining it with any existing semigraphics\n// rune.At this point, only regular single line borders are supported.\nfunc PrintJoinedSemigraphics(screen tcell.Screen, x, y int, ch rune, style tcell.Style) {\n\tprevious, _, _, _ := screen.GetContent(x, y)\n\n\t// What's the resulting rune?\n\tvar result rune\n\tif ch == previous {\n\t\tresult = ch\n\t} else {\n\t\tif ch < previous {\n\t\t\tprevious, ch = ch, previous\n\t\t}\n\t\tresult = SemigraphicJoints[string([]rune{previous, ch})]\n\t}\n\tif result == 0 {\n\t\tresult = ch\n\t}\n\n\t// We only print something if we have something.\n\tscreen.SetContent(x, y, result, nil, style)\n}\n"
  },
  {
    "path": "strings.go",
    "content": "package tview\n\nimport (\n\t\"math/rand\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode/utf8\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/uniseg\"\n)\n\n// escapedTagPattern matches an escaped tag, e.g. \"[red[]\", at the beginning of\n// a string.\nvar escapedTagPattern = regexp.MustCompile(`^\\[[^\\[\\]]+\\[+\\]`)\n\n// stepOptions is a bit field of options for [step]. A value of 0 results in\n// [step] having the same behavior as uniseg.Step, i.e. no tview-related parsing\n// is performed.\ntype stepOptions int\n\n// Bit fields for [stepOptions].\nconst (\n\tstepOptionsNone   stepOptions = 0\n\tstepOptionsStyle  stepOptions = 1 << iota // Parse style tags.\n\tstepOptionsRegion                         // Parse region tags.\n)\n\n// stepState represents the current state of the parser implemented in [step].\ntype stepState struct {\n\tunisegState     int         // The state of the uniseg parser.\n\tboundaries      int         // Information about boundaries, as returned by uniseg.Step.\n\tstyle           tcell.Style // The style of the returned grapheme cluster.\n\tregion          string      // The region of the returned grapheme cluster.\n\tescapedTagState int         // States for parsing escaped tags (defined in [step]).\n\tgrossLength     int         // The length of the cluster, including any tags not returned.\n\n\t// The styles for the initial call to [step].\n\tinitialForeground tcell.Color\n\tinitialBackground tcell.Color\n\tinitialAttributes tcell.AttrMask\n}\n\n// IsWordBoundary returns true if the boundary between the returned grapheme\n// cluster and the one following it is a word boundary.\nfunc (s *stepState) IsWordBoundary() bool {\n\treturn s.boundaries&uniseg.MaskWord != 0\n}\n\n// IsSentenceBoundary returns true if the boundary between the returned grapheme\n// cluster and the one following it is a sentence boundary.\nfunc (s *stepState) IsSentenceBoundary() bool {\n\treturn s.boundaries&uniseg.MaskSentence != 0\n}\n\n// LineBreak returns whether the string can be broken into the next line after\n// the returned grapheme cluster. If optional is true, the line break is\n// optional. If false, the line break is mandatory, e.g. after a newline\n// character.\nfunc (s *stepState) LineBreak() (lineBreak, optional bool) {\n\tswitch s.boundaries & uniseg.MaskLine {\n\tcase uniseg.LineCanBreak:\n\t\treturn true, true\n\tcase uniseg.LineMustBreak:\n\t\treturn true, false\n\t}\n\treturn false, false // uniseg.LineDontBreak.\n}\n\n// Width returns the grapheme cluster's width in cells.\nfunc (s *stepState) Width() int {\n\treturn s.boundaries >> uniseg.ShiftWidth\n}\n\n// GrossLength returns the grapheme cluster's length in bytes, including any\n// tags that were parsed but not explicitly returned.\nfunc (s *stepState) GrossLength() int {\n\treturn s.grossLength\n}\n\n// Style returns the style for the grapheme cluster.\nfunc (s *stepState) Style() tcell.Style {\n\treturn s.style\n}\n\n// step uses uniseg.Step to iterate over the grapheme clusters of a string but\n// (optionally) also parses the string for style or region tags.\n//\n// This function can be called consecutively to extract all grapheme clusters\n// from str, without returning any contained (parsed) tags. The return values\n// are the first grapheme cluster, the remaining string, and the new state. Pass\n// the remaining string and the returned state to the next call. If the rest\n// string is empty, parsing is complete. Call the returned state's methods for\n// boundary and cluster width information.\n//\n// The returned cluster may be empty if the given string consists of only\n// (parsed) tags. The boundary and width information will be meaningless in\n// this case but the style will describe the style at the end of the string.\n//\n// Pass nil for state on the first call. This will assume an initial style with\n// [Styles.PrimitiveBackgroundColor] as the background color and\n// [Styles.PrimaryTextColor] as the text color, no current region. If you want\n// to start with a different style or region, you can set the state accordingly\n// but you must then set [state.unisegState] to -1.\n//\n// There is no need to call uniseg.HasTrailingLineBreakInString on the last\n// non-empty cluster as this function will do this for you and adjust the\n// returned boundaries accordingly.\nfunc step(str string, state *stepState, opts stepOptions) (cluster, rest string, newState *stepState) {\n\t// Set up initial state.\n\tif state == nil {\n\t\tstate = &stepState{\n\t\t\tunisegState: -1,\n\t\t\tstyle:       tcell.StyleDefault.Background(Styles.PrimitiveBackgroundColor).Foreground(Styles.PrimaryTextColor),\n\t\t}\n\t}\n\tif state.unisegState < 0 {\n\t\tstate.initialForeground, state.initialBackground, state.initialAttributes = state.style.Decompose()\n\t}\n\tif len(str) == 0 {\n\t\tnewState = state\n\t\treturn\n\t}\n\n\t// Get a grapheme cluster.\n\tpreState := state.unisegState\n\tcluster, rest, state.boundaries, state.unisegState = uniseg.StepString(str, preState)\n\tstate.grossLength = len(cluster)\n\tif rest == \"\" {\n\t\tif !uniseg.HasTrailingLineBreakInString(cluster) {\n\t\t\tstate.boundaries &^= uniseg.MaskLine\n\t\t}\n\t}\n\n\t// Parse tags.\n\tif opts != stepOptionsNone {\n\t\tconst (\n\t\t\tetNone int = iota\n\t\t\tetStart\n\t\t\tetChar\n\t\t\tetClosing\n\t\t)\n\n\t\t// Finite state machine for escaped tags.\n\t\tswitch state.escapedTagState {\n\t\tcase etStart:\n\t\t\tif cluster[0] == '[' || cluster[0] == ']' { // Invalid escaped tag.\n\t\t\t\tstate.escapedTagState = etNone\n\t\t\t} else { // Other characters are allowed.\n\t\t\t\tstate.escapedTagState = etChar\n\t\t\t}\n\t\tcase etChar:\n\t\t\tif cluster[0] == ']' { // In theory, this should not happen.\n\t\t\t\tstate.escapedTagState = etNone\n\t\t\t} else if cluster[0] == '[' { // Starting closing sequence.\n\t\t\t\t// Swallow the first one.\n\t\t\t\tcluster, rest, state.boundaries, state.unisegState = uniseg.StepString(rest, preState)\n\t\t\t\tstate.grossLength += len(cluster)\n\t\t\t\tif cluster[0] == ']' {\n\t\t\t\t\tstate.escapedTagState = etNone\n\t\t\t\t} else {\n\t\t\t\t\tstate.escapedTagState = etClosing\n\t\t\t\t}\n\t\t\t} // More characters. Remain in etChar.\n\t\tcase etClosing:\n\t\t\tif cluster[0] != '[' {\n\t\t\t\tstate.escapedTagState = etNone\n\t\t\t}\n\t\t}\n\n\t\t// Regular tags.\n\t\tif state.escapedTagState == etNone {\n\t\t\tif cluster[0] == '[' {\n\t\t\t\t// We've already opened a tag. Parse it.\n\t\t\t\tlength, style, region := parseTag(str, state, opts)\n\t\t\t\tif length > 0 {\n\t\t\t\t\tstate.style = style\n\t\t\t\t\tstate.region = region\n\t\t\t\t\tcluster, rest, state.boundaries, state.unisegState = uniseg.StepString(str[length:], preState)\n\t\t\t\t\tstate.grossLength = len(cluster) + length\n\t\t\t\t\tif rest == \"\" {\n\t\t\t\t\t\tif !uniseg.HasTrailingLineBreakInString(cluster) {\n\t\t\t\t\t\t\tstate.boundaries &^= uniseg.MaskLine\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Is this an escaped tag?\n\t\t\t\tif escapedTagPattern.MatchString(str[length:]) {\n\t\t\t\t\tstate.escapedTagState = etStart\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(rest) > 0 && rest[0] == '[' {\n\t\t\t\t// A tag might follow the cluster. If so, we need to fix the state\n\t\t\t\t// for the boundaries to be correct.\n\t\t\t\tif length, _, _ := parseTag(rest, state, opts); length > 0 {\n\t\t\t\t\tif len(rest) > length {\n\t\t\t\t\t\t_, l := utf8.DecodeRuneInString(rest[length:])\n\t\t\t\t\t\tcluster += rest[length : length+l]\n\t\t\t\t\t}\n\t\t\t\t\tvar taglessRest string\n\t\t\t\t\tcluster, taglessRest, state.boundaries, state.unisegState = uniseg.StepString(cluster, preState)\n\t\t\t\t\tif taglessRest == \"\" {\n\t\t\t\t\t\tif !uniseg.HasTrailingLineBreakInString(cluster) {\n\t\t\t\t\t\t\tstate.boundaries &^= uniseg.MaskLine\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tnewState = state\n\treturn\n}\n\n// parseTag parses str for consecutive style and/or region tags, assuming that\n// str starts with the opening bracket for the first tag. It returns the string\n// length of all valid tags (0 if the first tag is not valid) and the updated\n// style and region for valid tags (based on the provided state).\nfunc parseTag(str string, state *stepState, opts stepOptions) (length int, style tcell.Style, region string) {\n\tif opts == stepOptionsNone {\n\t\treturn // No tags to parse.\n\t}\n\n\t// Automata states for parsing tags.\n\tconst (\n\t\ttagStateNone = iota\n\t\ttagStateDoneTag\n\t\ttagStateStart\n\t\ttagStateRegionStart\n\t\ttagStateEndForeground\n\t\ttagStateStartBackground\n\t\ttagStateNumericForeground\n\t\ttagStateNameForeground\n\t\ttagStateEndBackground\n\t\ttagStateStartAttributes\n\t\ttagStateNumericBackground\n\t\ttagStateNameBackground\n\t\ttagStateAttributes\n\t\ttagStateRegionEnd\n\t\ttagStateRegionName\n\t\ttagStateEndAttributes\n\t\ttagStateStartURL\n\t\ttagStateEndURL\n\t\ttagStateURL\n\t)\n\n\t// Helper function which checks if the given byte is one of a list of\n\t// characters, including letters and digits.\n\tisOneOf := func(b byte, chars string) bool {\n\t\tif b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z' || b >= '0' && b <= '9' {\n\t\t\treturn true\n\t\t}\n\t\treturn strings.IndexByte(chars, b) >= 0\n\t}\n\n\t// Attribute map.\n\tattrs := map[byte]tcell.AttrMask{\n\t\t'B': tcell.AttrBold,\n\t\t'I': tcell.AttrItalic,\n\t\t'L': tcell.AttrBlink,\n\t\t'D': tcell.AttrDim,\n\t\t'S': tcell.AttrStrikeThrough,\n\t\t'R': tcell.AttrReverse,\n\t}\n\n\tvar (\n\t\ttagState, tagLength int\n\t\ttempStr             strings.Builder\n\t)\n\ttStyle := state.style\n\ttRegion := state.region\n\n\t// Process state transitions.\n\tfor len(str) > 0 {\n\t\tch := str[0]\n\t\tstr = str[1:]\n\t\ttagLength++\n\n\t\t// Transition.\n\t\tswitch tagState {\n\t\tcase tagStateNone:\n\t\t\tif ch == '[' { // Start of a tag.\n\t\t\t\ttagState = tagStateStart\n\t\t\t} else { // Not a tag. We're done.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateStart:\n\t\t\tif ch == '\"' && opts&stepOptionsRegion == 0 {\n\t\t\t\treturn // Region tags are not allowed.\n\t\t\t} else if ch != '\"' && opts&stepOptionsStyle == 0 {\n\t\t\t\treturn // Style tags are not allowed.\n\t\t\t}\n\t\t\tswitch {\n\t\t\tcase ch == '\"': // Start of a region tag.\n\t\t\t\ttempStr.Reset()\n\t\t\t\ttagState = tagStateRegionStart\n\t\t\tcase !isOneOf(ch, \"#:-\"): // Invalid style tag.\n\t\t\t\treturn\n\t\t\tcase ch == '-': // Reset foreground color.\n\t\t\t\ttStyle = tStyle.Foreground(state.initialForeground)\n\t\t\t\ttagState = tagStateEndForeground\n\t\t\tcase ch == ':': // No foreground color.\n\t\t\t\ttagState = tagStateStartBackground\n\t\t\tdefault:\n\t\t\t\ttempStr.Reset()\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\t\tif ch == '#' { // Numeric foreground color.\n\t\t\t\t\ttagState = tagStateNumericForeground\n\t\t\t\t} else { // Letters or numbers.\n\t\t\t\t\ttagState = tagStateNameForeground\n\t\t\t\t}\n\t\t\t}\n\t\tcase tagStateEndForeground:\n\t\t\tswitch ch {\n\t\t\tcase ']': // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\tcase ':':\n\t\t\t\ttagState = tagStateStartBackground\n\t\t\tdefault: // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateNumericForeground:\n\t\t\tif ch == ']' || ch == ':' {\n\t\t\t\tif tempStr.Len() != 7 { // Must be #rrggbb.\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttStyle = tStyle.Foreground(tcell.GetColor(tempStr.String()))\n\t\t\t}\n\t\t\tswitch {\n\t\t\tcase ch == ']': // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\tcase ch == ':': // Start of background color.\n\t\t\t\ttagState = tagStateStartBackground\n\t\t\tcase strings.IndexByte(\"0123456789abcdefABCDEF\", ch) >= 0: // Hex digit.\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\t\ttagState = tagStateNumericForeground\n\t\t\tdefault: // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateNameForeground:\n\t\t\tif ch == ']' || ch == ':' {\n\t\t\t\tname := tempStr.String()\n\t\t\t\tif name[0] >= '0' && name[0] <= '9' { // Must not start with a digit.\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttStyle = tStyle.Foreground(tcell.ColorNames[name])\n\t\t\t}\n\t\t\tswitch {\n\t\t\tcase !isOneOf(ch, \"]:\"): // Invalid tag.\n\t\t\t\treturn\n\t\t\tcase ch == ']': // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\tcase ch == ':': // Start of background color.\n\t\t\t\ttagState = tagStateStartBackground\n\t\t\tdefault: // Letters or numbers.\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\t}\n\t\tcase tagStateStartBackground:\n\t\t\tswitch {\n\t\t\tcase !isOneOf(ch, \"#:-]\"): // Invalid style tag.\n\t\t\t\treturn\n\t\t\tcase ch == ']': // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\tcase ch == '-': // Reset background color.\n\t\t\t\ttStyle = tStyle.Background(state.initialBackground)\n\t\t\t\ttagState = tagStateEndBackground\n\t\t\tcase ch == ':': // No background color.\n\t\t\t\ttagState = tagStateStartAttributes\n\t\t\tdefault:\n\t\t\t\ttempStr.Reset()\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\t\tif ch == '#' { // Numeric background color.\n\t\t\t\t\ttagState = tagStateNumericBackground\n\t\t\t\t} else { // Letters or numbers.\n\t\t\t\t\ttagState = tagStateNameBackground\n\t\t\t\t}\n\t\t\t}\n\t\tcase tagStateEndBackground:\n\t\t\tswitch ch {\n\t\t\tcase ']': // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\tcase ':': // Start of attributes.\n\t\t\t\ttagState = tagStateStartAttributes\n\t\t\tdefault: // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateNumericBackground:\n\t\t\tif ch == ']' || ch == ':' {\n\t\t\t\tif tempStr.Len() != 7 { // Must be #rrggbb.\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttStyle = tStyle.Background(tcell.GetColor(tempStr.String()))\n\t\t\t}\n\t\t\tif ch == ']' { // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\t} else if ch == ':' { // Start of attributes.\n\t\t\t\ttagState = tagStateStartAttributes\n\t\t\t} else if strings.IndexByte(\"0123456789abcdefABCDEF\", ch) >= 0 { // Hex digit.\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\t\ttagState = tagStateNumericBackground\n\t\t\t} else { // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateNameBackground:\n\t\t\tif ch == ']' || ch == ':' {\n\t\t\t\tname := tempStr.String()\n\t\t\t\tif name[0] >= '0' && name[0] <= '9' { // Must not start with a digit.\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttStyle = tStyle.Background(tcell.ColorNames[name])\n\t\t\t}\n\t\t\tswitch {\n\t\t\tcase !isOneOf(ch, \"]:\"): // Invalid tag.\n\t\t\t\treturn\n\t\t\tcase ch == ']': // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\tcase ch == ':': // Start of background color.\n\t\t\t\ttagState = tagStateStartAttributes\n\t\t\tdefault: // Letters or numbers.\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\t}\n\t\tcase tagStateStartAttributes:\n\t\t\tswitch {\n\t\t\tcase ch == ']': // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\tcase ch == '-': // Reset attributes.\n\t\t\t\ttStyle = tStyle.Attributes(state.initialAttributes)\n\t\t\t\ttagState = tagStateEndAttributes\n\t\t\tcase ch == ':': // Start of URL.\n\t\t\t\ttagState = tagStateStartURL\n\t\t\tcase strings.IndexByte(\"buildsrBUILDSR\", ch) >= 0: // Attribute tag.\n\t\t\t\ttempStr.Reset()\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\t\ttagState = tagStateAttributes\n\t\t\tdefault: // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateAttributes:\n\t\t\tif ch == ']' || ch == ':' {\n\t\t\t\tflags := tempStr.String()\n\t\t\t\t_, _, a := tStyle.Decompose()\n\t\t\t\tfor index := 0; index < len(flags); index++ {\n\t\t\t\t\tch := flags[index]\n\t\t\t\t\tswitch {\n\t\t\t\t\tcase ch == 'u':\n\t\t\t\t\t\ttStyle = tStyle.Underline(true)\n\t\t\t\t\tcase ch == 'U':\n\t\t\t\t\t\ttStyle = tStyle.Underline(false)\n\t\t\t\t\tcase ch >= 'a' && ch <= 'z':\n\t\t\t\t\t\ta |= attrs[ch-('a'-'A')]\n\t\t\t\t\tdefault:\n\t\t\t\t\t\ta &^= attrs[ch]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttStyle = tStyle.Attributes(a)\n\t\t\t}\n\t\t\tswitch {\n\t\t\tcase ch == ']': // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\tcase ch == ':': // Start of URL.\n\t\t\t\ttagState = tagStateStartURL\n\t\t\tcase strings.IndexByte(\"buildsrBUILDSR\", ch) >= 0: // Attribute tag.\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\tdefault: // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateEndAttributes:\n\t\t\tswitch ch {\n\t\t\tcase ']': // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\tcase ':': // Start of URL.\n\t\t\t\ttagState = tagStateStartURL\n\t\t\tdefault: // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateStartURL:\n\t\t\tswitch ch {\n\t\t\tcase ']': // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\tcase '-': // Reset URL.\n\t\t\t\ttStyle = tStyle.Url(\"\").UrlId(\"\")\n\t\t\t\ttagState = tagStateEndURL\n\t\t\tdefault: // URL character.\n\t\t\t\ttempStr.Reset()\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\t\ttStyle = tStyle.UrlId(strconv.Itoa(int(rand.Uint32()))) // Generate a unique ID for this URL.\n\t\t\t\ttagState = tagStateURL\n\t\t\t}\n\t\tcase tagStateEndURL:\n\t\t\tif ch == ']' { // End of tag.\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\t} else { // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateURL:\n\t\t\tif ch == ']' { // End of tag.\n\t\t\t\ttStyle = tStyle.Url(tempStr.String())\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\t} else { // URL character.\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\t}\n\t\tcase tagStateRegionStart:\n\t\t\tswitch {\n\t\t\tcase ch == '\"': // End of region tag.\n\t\t\t\ttagState = tagStateRegionEnd\n\t\t\tcase isOneOf(ch, \"_,;: -.\"): // Region name.\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\t\ttagState = tagStateRegionName\n\t\t\tdefault: // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateRegionEnd:\n\t\t\tif ch == ']' { // End of tag.\n\t\t\t\ttRegion = tempStr.String()\n\t\t\t\ttagState = tagStateDoneTag\n\t\t\t} else { // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tagStateRegionName:\n\t\t\tswitch {\n\t\t\tcase ch == '\"': // End of region tag.\n\t\t\t\ttagState = tagStateRegionEnd\n\t\t\tcase isOneOf(ch, \"_,;: -.\"): // Region name.\n\t\t\t\ttempStr.WriteByte(ch)\n\t\t\tdefault: // Invalid tag.\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// The last transition led to a tag end. Make the tag permanent.\n\t\tif tagState == tagStateDoneTag {\n\t\t\tlength, style, region = tagLength, tStyle, tRegion\n\t\t\ttagState = tagStateNone // Reset state.\n\t\t}\n\t}\n\n\treturn\n}\n\n// TaggedStringWidth returns the width of the given string needed to print it on\n// screen. The text may contain style tags which are not counted.\nfunc TaggedStringWidth(text string) (width int) {\n\tvar state *stepState\n\tfor len(text) > 0 {\n\t\t_, text, state = step(text, state, stepOptionsStyle)\n\t\twidth += state.Width()\n\t}\n\treturn\n}\n\n// WordWrap splits a text such that each resulting line does not exceed the\n// given screen width. Split points are determined using the algorithm described\n// in [Unicode Standard Annex #14].\n//\n// This function considers style tags to have no width.\n//\n// [Unicode Standard Annex #14]: https://www.unicode.org/reports/tr14/\nfunc WordWrap(text string, width int) (lines []string) {\n\tif width <= 0 {\n\t\treturn\n\t}\n\n\tvar (\n\t\tstate                                              *stepState\n\t\tlineWidth, lineLength, lastOption, lastOptionWidth int\n\t)\n\tstr := text\n\tfor len(str) > 0 {\n\t\t// Parse the next character.\n\t\t_, str, state = step(str, state, stepOptionsStyle)\n\t\tcWidth := state.Width()\n\n\t\t// Would it exceed the line width?\n\t\tif lineWidth+cWidth > width {\n\t\t\tif lastOptionWidth == 0 {\n\t\t\t\t// No split point so far. Just split at the current position.\n\t\t\t\tlines = append(lines, text[:lineLength])\n\t\t\t\ttext = text[lineLength:]\n\t\t\t\tlineWidth, lineLength, lastOption, lastOptionWidth = 0, 0, 0, 0\n\t\t\t} else {\n\t\t\t\t// Split at the last split point.\n\t\t\t\tlines = append(lines, text[:lastOption])\n\t\t\t\ttext = text[lastOption:]\n\t\t\t\tlineWidth -= lastOptionWidth\n\t\t\t\tlineLength -= lastOption\n\t\t\t\tlastOption, lastOptionWidth = 0, 0\n\t\t\t}\n\t\t}\n\n\t\t// Move ahead.\n\t\tlineWidth += cWidth\n\t\tlineLength += state.GrossLength()\n\n\t\t// Check for split points.\n\t\tif lineBreak, optional := state.LineBreak(); lineBreak {\n\t\t\tif optional {\n\t\t\t\t// Remember this split point.\n\t\t\t\tlastOption = lineLength\n\t\t\t\tlastOptionWidth = lineWidth\n\t\t\t} else {\n\t\t\t\t// We must split here.\n\t\t\t\tlines = append(lines, strings.TrimRight(text[:lineLength], \"\\n\\r\"))\n\t\t\t\ttext = text[lineLength:]\n\t\t\t\tlineWidth, lineLength, lastOption, lastOptionWidth = 0, 0, 0, 0\n\t\t\t}\n\t\t}\n\t}\n\tlines = append(lines, text)\n\n\treturn\n}\n\n// Escape escapes the given text such that color and/or region tags are not\n// recognized and substituted by the print functions of this package. For\n// example, to include a tag-like string in a box title or in a TextView:\n//\n//\tbox.SetTitle(tview.Escape(\"[squarebrackets]\"))\n//\tfmt.Fprint(textView, tview.Escape(`[\"quoted\"]`))\nfunc Escape(text string) string {\n\treturn escapePattern.ReplaceAllString(text, \"$1[]\")\n}\n\n// Unescape unescapes text previously escaped with [Escape].\nfunc Unescape(text string) string {\n\treturn unescapePattern.ReplaceAllString(text, \"$1]\")\n}\n\n// stripTags strips style tags from the given string. (Region tags are not\n// stripped.)\nfunc stripTags(text string) string {\n\tvar (\n\t\tstr   strings.Builder\n\t\tstate *stepState\n\t)\n\tfor len(text) > 0 {\n\t\tvar c string\n\t\tc, text, state = step(text, state, stepOptionsStyle)\n\t\tstr.WriteString(c)\n\t}\n\treturn str.String()\n}\n"
  },
  {
    "path": "styles.go",
    "content": "package tview\n\nimport \"github.com/gdamore/tcell/v2\"\n\n// Theme defines the colors used when primitives are initialized.\ntype Theme struct {\n\tPrimitiveBackgroundColor    tcell.Color // Main background color for primitives.\n\tContrastBackgroundColor     tcell.Color // Background color for contrasting elements.\n\tMoreContrastBackgroundColor tcell.Color // Background color for even more contrasting elements.\n\tBorderColor                 tcell.Color // Box borders.\n\tTitleColor                  tcell.Color // Box titles.\n\tGraphicsColor               tcell.Color // Graphics.\n\tPrimaryTextColor            tcell.Color // Primary text.\n\tSecondaryTextColor          tcell.Color // Secondary text (e.g. labels).\n\tTertiaryTextColor           tcell.Color // Tertiary text (e.g. subtitles, notes).\n\tInverseTextColor            tcell.Color // Text on primary-colored backgrounds.\n\tContrastSecondaryTextColor  tcell.Color // Secondary text on ContrastBackgroundColor-colored backgrounds.\n}\n\n// Styles defines the theme for applications. The default is for a black\n// background and some basic colors: black, white, yellow, green, cyan, and\n// blue.\nvar Styles = Theme{\n\tPrimitiveBackgroundColor:    tcell.ColorBlack,\n\tContrastBackgroundColor:     tcell.ColorBlue,\n\tMoreContrastBackgroundColor: tcell.ColorGreen,\n\tBorderColor:                 tcell.ColorWhite,\n\tTitleColor:                  tcell.ColorWhite,\n\tGraphicsColor:               tcell.ColorWhite,\n\tPrimaryTextColor:            tcell.ColorWhite,\n\tSecondaryTextColor:          tcell.ColorYellow,\n\tTertiaryTextColor:           tcell.ColorGreen,\n\tInverseTextColor:            tcell.ColorBlue,\n\tContrastSecondaryTextColor:  tcell.ColorNavy,\n}\n"
  },
  {
    "path": "table.go",
    "content": "package tview\n\nimport (\n\t\"sort\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\tcolorful \"github.com/lucasb-eyer/go-colorful\"\n)\n\n// TableCell represents one cell inside a Table. You can instantiate this type\n// directly but all colors (background and text) will be set to their default\n// which is black.\ntype TableCell struct {\n\t// The reference object.\n\tReference interface{}\n\n\t// The text to be displayed in the table cell.\n\tText string\n\n\t// The alignment of the cell text. One of AlignLeft (default), AlignCenter,\n\t// or AlignRight.\n\tAlign int\n\n\t// The maximum width of the cell in screen space. This is used to give a\n\t// column a maximum width. Any cell text whose screen width exceeds this width\n\t// is cut off. Set to 0 if there is no maximum width.\n\tMaxWidth int\n\n\t// If the total table width is less than the available width, this value is\n\t// used to add extra width to a column. See SetExpansion() for details.\n\tExpansion int\n\n\t// The color of the cell text. You should not use this anymore, it is only\n\t// here for backwards compatibility. Use the Style field instead.\n\tColor tcell.Color\n\n\t// The background color of the cell. You should not use this anymore, it is\n\t// only here for backwards compatibility. Use the Style field instead.\n\tBackgroundColor tcell.Color\n\n\t// The style attributes of the cell. You should not use this anymore, it is\n\t// only here for backwards compatibility. Use the Style field instead.\n\tAttributes tcell.AttrMask\n\n\t// The style of the cell. If this is uninitialized (tcell.StyleDefault), the\n\t// Color and BackgroundColor fields are used instead.\n\tStyle tcell.Style\n\n\t// The style of the cell when it is selected. If this is uninitialized\n\t// (tcell.StyleDefault), the table's selected style is used instead. If that\n\t// is uninitialized as well, the cell's background and text color are\n\t// swapped.\n\tSelectedStyle tcell.Style\n\n\t// If set to true, the BackgroundColor is not used and the cell will have\n\t// the background color of the table.\n\tTransparent bool\n\n\t// If set to true, this cell cannot be selected.\n\tNotSelectable bool\n\n\t// An optional handler for mouse clicks. This also fires if the cell is not\n\t// selectable. If true is returned, no additional \"selected\" event is fired\n\t// on selectable cells.\n\tClicked func() bool\n\n\t// The position and width of the cell the last time table was drawn.\n\tx, y, width int\n}\n\n// NewTableCell returns a new table cell with sensible defaults. That is, left\n// aligned text with the primary text color (see Styles) and a transparent\n// background (using the background of the Table).\nfunc NewTableCell(text string) *TableCell {\n\treturn &TableCell{\n\t\tText:        text,\n\t\tAlign:       AlignLeft,\n\t\tStyle:       tcell.StyleDefault.Foreground(Styles.PrimaryTextColor).Background(Styles.PrimitiveBackgroundColor),\n\t\tTransparent: true,\n\t}\n}\n\n// SetText sets the cell's text.\nfunc (c *TableCell) SetText(text string) *TableCell {\n\tc.Text = text\n\treturn c\n}\n\n// SetAlign sets the cell's text alignment, one of AlignLeft, AlignCenter, or\n// AlignRight.\nfunc (c *TableCell) SetAlign(align int) *TableCell {\n\tc.Align = align\n\treturn c\n}\n\n// SetMaxWidth sets maximum width of the cell in screen space. This is used to\n// give a column a maximum width. Any cell text whose screen width exceeds this\n// width is cut off. Set to 0 if there is no maximum width.\nfunc (c *TableCell) SetMaxWidth(maxWidth int) *TableCell {\n\tc.MaxWidth = maxWidth\n\treturn c\n}\n\n// SetExpansion sets the value by which the column of this cell expands if the\n// available width for the table is more than the table width (prior to applying\n// this expansion value). This is a proportional value. The amount of unused\n// horizontal space is divided into widths to be added to each column. How much\n// extra width a column receives depends on the expansion value: A value of 0\n// (the default) will not cause the column to increase in width. Other values\n// are proportional, e.g. a value of 2 will cause a column to grow by twice\n// the amount of a column with a value of 1.\n//\n// Since this value affects an entire column, the maximum over all visible cells\n// in that column is used.\n//\n// This function panics if a negative value is provided.\nfunc (c *TableCell) SetExpansion(expansion int) *TableCell {\n\tif expansion < 0 {\n\t\tpanic(\"Table cell expansion values may not be negative\")\n\t}\n\tc.Expansion = expansion\n\treturn c\n}\n\n// SetTextColor sets the cell's text color.\nfunc (c *TableCell) SetTextColor(color tcell.Color) *TableCell {\n\tif c.Style == tcell.StyleDefault {\n\t\tc.Color = color\n\t} else {\n\t\tc.Style = c.Style.Foreground(color)\n\t}\n\treturn c\n}\n\n// SetBackgroundColor sets the cell's background color. This will also cause the\n// cell's Transparent flag to be set to \"false\".\nfunc (c *TableCell) SetBackgroundColor(color tcell.Color) *TableCell {\n\tif c.Style == tcell.StyleDefault {\n\t\tc.BackgroundColor = color\n\t} else {\n\t\tc.Style = c.Style.Background(color)\n\t}\n\tc.Transparent = false\n\treturn c\n}\n\n// SetTransparency sets the background transparency of this cell. A value of\n// \"true\" will cause the cell to use the table's background color, the cell's\n// own background color will be ignored. A value of \"false\" will cause it to use\n// its own background color.\nfunc (c *TableCell) SetTransparency(transparent bool) *TableCell {\n\tc.Transparent = transparent\n\treturn c\n}\n\n// SetAttributes sets the cell's text attributes. You can combine different\n// attributes using bitmask operations:\n//\n//\tcell.SetAttributes(tcell.AttrItalic | tcell.AttrBold)\nfunc (c *TableCell) SetAttributes(attr tcell.AttrMask) *TableCell {\n\tif c.Style == tcell.StyleDefault {\n\t\tc.Attributes = attr\n\t} else {\n\t\tc.Style = c.Style.Attributes(attr)\n\t}\n\treturn c\n}\n\n// SetStyle sets the cell's style (foreground color, background color, and\n// attributes) all at once.\nfunc (c *TableCell) SetStyle(style tcell.Style) *TableCell {\n\tc.Style = style\n\treturn c\n}\n\n// SetSelectedStyle sets the cell's style when it is selected. If this is\n// uninitialized (tcell.StyleDefault), the table's selected style is used\n// instead. If that is uninitialized as well, the cell's background and text\n// color are swapped.\nfunc (c *TableCell) SetSelectedStyle(style tcell.Style) *TableCell {\n\tc.SelectedStyle = style\n\treturn c\n}\n\n// SetSelectable sets whether or not this cell can be selected by the user.\nfunc (c *TableCell) SetSelectable(selectable bool) *TableCell {\n\tc.NotSelectable = !selectable\n\treturn c\n}\n\n// SetReference allows you to store a reference of any type in this cell. This\n// will allow you to establish a mapping between the cell and your\n// actual data.\nfunc (c *TableCell) SetReference(reference interface{}) *TableCell {\n\tc.Reference = reference\n\treturn c\n}\n\n// GetReference returns this cell's reference object.\nfunc (c *TableCell) GetReference() interface{} {\n\treturn c.Reference\n}\n\n// GetLastPosition returns the position of the table cell the last time it was\n// drawn on screen. If the cell is not on screen, the return values are\n// undefined.\n//\n// Because the Table class will attempt to keep selected cells on screen, this\n// function is most useful in response to a \"selected\" event (see\n// SetSelectedFunc()) or a \"selectionChanged\" event (see\n// SetSelectionChangedFunc()).\nfunc (c *TableCell) GetLastPosition() (x, y, width int) {\n\treturn c.x, c.y, c.width\n}\n\n// SetClickedFunc sets a handler which fires when this cell is clicked. This is\n// independent of whether the cell is selectable or not. But for selectable\n// cells, if the function returns \"true\", the \"selected\" event is not fired.\nfunc (c *TableCell) SetClickedFunc(clicked func() bool) *TableCell {\n\tc.Clicked = clicked\n\treturn c\n}\n\n// TableContent defines a Table's data. You may replace a Table's default\n// implementation with your own using the Table.SetContent() function. This will\n// allow you to turn Table into a view of your own data structure. The\n// Table.Draw() function, which is called when the screen is updated, will then\n// use the (read-only) functions of this interface to update the table. The\n// write functions are only called when the corresponding functions of Table are\n// called.\n//\n// The interface's read-only functions are not called concurrently by the\n// package (provided that users of the package don't call Table.Draw() in a\n// separate goroutine, which would be uncommon and is not encouraged).\ntype TableContent interface {\n\t// Return the cell at the given position or nil if there is no cell. The\n\t// row and column arguments start at 0 and end at what GetRowCount() and\n\t// GetColumnCount() return, minus 1.\n\tGetCell(row, column int) *TableCell\n\n\t// Return the total number of rows in the table.\n\tGetRowCount() int\n\n\t// Return the total number of columns in the table.\n\tGetColumnCount() int\n\n\t// The following functions are provided for completeness reasons as the\n\t// original Table implementation was not read-only. If you do not wish to\n\t// forward modifying operations to your data, you may opt to leave these\n\t// functions empty. To make this easier, you can include the\n\t// TableContentReadOnly type in your struct. See also the\n\t// demos/table/virtualtable example.\n\n\t// Set the cell at the given position to the provided cell.\n\tSetCell(row, column int, cell *TableCell)\n\n\t// Remove the row at the given position by shifting all following rows up\n\t// by one. Out of range positions may be ignored.\n\tRemoveRow(row int)\n\n\t// Remove the column at the given position by shifting all following columns\n\t// left by one. Out of range positions may be ignored.\n\tRemoveColumn(column int)\n\n\t// Insert a new empty row at the given position by shifting all rows at that\n\t// position and below down by one. Implementers may decide what to do with\n\t// out of range positions.\n\tInsertRow(row int)\n\n\t// Insert a new empty column at the given position by shifting all columns\n\t// at that position and to the right by one to the right. Implementers may\n\t// decide what to do with out of range positions.\n\tInsertColumn(column int)\n\n\t// Remove all table data.\n\tClear()\n}\n\n// TableContentReadOnly is an empty struct which implements the write operations\n// of the TableContent interface. None of the implemented functions do anything.\n// You can embed this struct into your own structs to free yourself from having\n// to implement the empty write functions of TableContent. See\n// demos/table/virtualtable for an example.\ntype TableContentReadOnly struct{}\n\n// SetCell does not do anything.\nfunc (t TableContentReadOnly) SetCell(row, column int, cell *TableCell) {\n\t// nop.\n}\n\n// RemoveRow does not do anything.\nfunc (t TableContentReadOnly) RemoveRow(row int) {\n\t// nop.\n}\n\n// RemoveColumn does not do anything.\nfunc (t TableContentReadOnly) RemoveColumn(column int) {\n\t// nop.\n}\n\n// InsertRow does not do anything.\nfunc (t TableContentReadOnly) InsertRow(row int) {\n\t// nop.\n}\n\n// InsertColumn does not do anything.\nfunc (t TableContentReadOnly) InsertColumn(column int) {\n\t// nop.\n}\n\n// Clear does not do anything.\nfunc (t TableContentReadOnly) Clear() {\n\t// nop.\n}\n\n// tableDefaultContent implements the default TableContent interface for the\n// Table class.\ntype tableDefaultContent struct {\n\t// The cells of the table. Rows first, then columns.\n\tcells [][]*TableCell\n\n\t// The rightmost column in the data set.\n\tlastColumn int\n}\n\n// Clear clears all data.\nfunc (t *tableDefaultContent) Clear() {\n\tt.cells = nil\n\tt.lastColumn = -1\n}\n\n// SetCell sets a cell's content.\nfunc (t *tableDefaultContent) SetCell(row, column int, cell *TableCell) {\n\tif row >= len(t.cells) {\n\t\tt.cells = append(t.cells, make([][]*TableCell, row-len(t.cells)+1)...)\n\t}\n\trowLen := len(t.cells[row])\n\tif column >= rowLen {\n\t\tt.cells[row] = append(t.cells[row], make([]*TableCell, column-rowLen+1)...)\n\t\tfor c := rowLen; c < column; c++ {\n\t\t\tt.cells[row][c] = &TableCell{}\n\t\t}\n\t}\n\tt.cells[row][column] = cell\n\tif column > t.lastColumn {\n\t\tt.lastColumn = column\n\t}\n}\n\n// RemoveRow removes a row from the data.\nfunc (t *tableDefaultContent) RemoveRow(row int) {\n\tif row < 0 || row >= len(t.cells) {\n\t\treturn\n\t}\n\tt.cells = append(t.cells[:row], t.cells[row+1:]...)\n}\n\n// RemoveColumn removes a column from the data.\nfunc (t *tableDefaultContent) RemoveColumn(column int) {\n\tfor row := range t.cells {\n\t\tif column < 0 || column >= len(t.cells[row]) {\n\t\t\tcontinue\n\t\t}\n\t\tt.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...)\n\t}\n\tif column >= 0 && column <= t.lastColumn {\n\t\tt.lastColumn--\n\t}\n}\n\n// InsertRow inserts a new row at the given position.\nfunc (t *tableDefaultContent) InsertRow(row int) {\n\tif row >= len(t.cells) {\n\t\treturn\n\t}\n\tt.cells = append(t.cells, nil)       // Extend by one.\n\tcopy(t.cells[row+1:], t.cells[row:]) // Shift down.\n\tt.cells[row] = nil                   // New row is uninitialized.\n}\n\n// InsertColumn inserts a new column at the given position.\nfunc (t *tableDefaultContent) InsertColumn(column int) {\n\tfor row := range t.cells {\n\t\tif column >= len(t.cells[row]) {\n\t\t\tcontinue\n\t\t}\n\t\tt.cells[row] = append(t.cells[row], nil)             // Extend by one.\n\t\tcopy(t.cells[row][column+1:], t.cells[row][column:]) // Shift to the right.\n\t\tt.cells[row][column] = &TableCell{}                  // New element is an uninitialized table cell.\n\t}\n}\n\n// GetCell returns the cell at the given position.\nfunc (t *tableDefaultContent) GetCell(row, column int) *TableCell {\n\tif row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) {\n\t\treturn nil\n\t}\n\treturn t.cells[row][column]\n}\n\n// GetRowCount returns the number of rows in the data set.\nfunc (t *tableDefaultContent) GetRowCount() int {\n\treturn len(t.cells)\n}\n\n// GetColumnCount returns the number of columns in the data set.\nfunc (t *tableDefaultContent) GetColumnCount() int {\n\tif len(t.cells) == 0 {\n\t\treturn 0\n\t}\n\treturn t.lastColumn + 1\n}\n\n// Table visualizes two-dimensional data consisting of rows and columns. Each\n// Table cell is defined via [Table.SetCell] by the [TableCell] type. They can\n// be added dynamically to the table and changed any time.\n//\n// The most compact display of a table is without borders. Each row will then\n// occupy one row on screen and columns are separated by the rune defined via\n// [Table.SetSeparator] (a space character by default).\n//\n// When borders are turned on (via [Table.SetBorders]), each table cell is\n// surrounded by lines. Therefore one table row will require two rows on screen.\n//\n// Columns will use as much horizontal space as they need. You can constrain\n// their size with the [TableCell.MaxWidth] parameter of the [TableCell] type.\n//\n// # Fixed Columns\n//\n// You can define fixed rows and rolumns via [Table.SetFixed]. They will always\n// stay in their place, even when the table is scrolled. Fixed rows are always\n// the top rows. Fixed columns are always the leftmost columns.\n//\n// # Selections\n//\n// You can call [Table.SetSelectable] to set columns and/or rows to\n// \"selectable\". If the flag is set only for columns, entire columns can be\n// selected by the user. If it is set only for rows, entire rows can be\n// selected. If both flags are set, individual cells can be selected. The\n// \"selected\" handler set via [Table.SetSelectedFunc] is invoked when the user\n// presses Enter on a selection.\n//\n// # Navigation\n//\n// If the table extends beyond the available space, it can be navigated with\n// key bindings similar to Vim:\n//\n//   - h, left arrow: Move left by one column.\n//   - l, right arrow: Move right by one column.\n//   - j, down arrow: Move down by one row.\n//   - k, up arrow: Move up by one row.\n//   - g, home: Move to the top.\n//   - G, end: Move to the bottom.\n//   - Ctrl-F, page down: Move down by one page.\n//   - Ctrl-B, page up: Move up by one page.\n//\n// When there is no selection, this affects the entire table (except for fixed\n// rows and columns). When there is a selection, the user moves the selection.\n// The class will attempt to keep the selection from moving out of the screen.\n//\n// Use [Box.SetInputCapture] to override or modify keyboard input.\n//\n// See https://github.com/rivo/tview/wiki/Table for an example.\ntype Table struct {\n\t*Box\n\n\t// Whether or not this table has borders around each cell.\n\tborders bool\n\n\t// The color of the borders or the separator.\n\tbordersColor tcell.Color\n\n\t// If there are no borders, the column separator.\n\tseparator rune\n\n\t// The table's data structure.\n\tcontent TableContent\n\n\t// If true, when calculating the widths of the columns, all rows are evaluated\n\t// instead of only the visible ones.\n\tevaluateAllRows bool\n\n\t// The number of fixed rows / columns.\n\tfixedRows, fixedColumns int\n\n\t// Whether or not rows or columns can be selected. If both are set to true,\n\t// cells can be selected.\n\trowsSelectable, columnsSelectable bool\n\n\t// The currently selected row and column.\n\tselectedRow, selectedColumn int\n\n\t// A temporary flag which causes the next call to Draw() to force the\n\t// current selection to remain visible. It is set to false afterwards.\n\tclampToSelection bool\n\n\t// If set to true, moving the selection will wrap around horizontally (last\n\t// to first column and vice versa) or vertically (last to first row and vice\n\t// versa).\n\twrapHorizontally, wrapVertically bool\n\n\t// The number of rows/columns by which the table is scrolled down/to the\n\t// right.\n\trowOffset, columnOffset int\n\n\t// If set to true, the table's last row will always be visible.\n\ttrackEnd bool\n\n\t// The number of visible rows the last time the table was drawn.\n\tvisibleRows int\n\n\t// The indices of the visible columns as of the last time the table was drawn.\n\tvisibleColumnIndices []int\n\n\t// The net widths of the visible columns as of the last time the table was\n\t// drawn.\n\tvisibleColumnWidths []int\n\n\t// The style of the selected rows. If this value is the empty struct,\n\t// selected rows are simply inverted.\n\tselectedStyle tcell.Style\n\n\t// An optional function which gets called when the user presses Enter on a\n\t// selected cell. If entire rows selected, the column value is undefined.\n\t// Likewise for entire columns.\n\tselected func(row, column int)\n\n\t// An optional function which gets called when the user changes the selection.\n\t// If entire rows selected, the column value is undefined.\n\t// Likewise for entire columns.\n\tselectionChanged func(row, column int)\n\n\t// An optional function which gets called when the user presses Escape, Tab,\n\t// or Backtab. Also when the user presses Enter if nothing is selectable.\n\tdone func(key tcell.Key)\n}\n\n// NewTable returns a new [Table].\nfunc NewTable() *Table {\n\tt := &Table{\n\t\tBox:          NewBox(),\n\t\tbordersColor: Styles.GraphicsColor,\n\t\tseparator:    ' ',\n\t}\n\tt.SetContent(nil)\n\tt.Box.Primitive = t\n\treturn t\n}\n\n// SetContent sets a new content type for this table. This allows you to back\n// the table by a data structure of your own, for example one that cannot be\n// fully held in memory. For details, see the TableContent interface\n// documentation.\n//\n// A value of nil will return the table to its default implementation where all\n// of its table cells are kept in memory.\nfunc (t *Table) SetContent(content TableContent) *Table {\n\tif content != nil {\n\t\tt.content = content\n\t} else {\n\t\tt.content = &tableDefaultContent{\n\t\t\tlastColumn: -1,\n\t\t}\n\t}\n\treturn t\n}\n\n// Clear removes all table data.\nfunc (t *Table) Clear() *Table {\n\tt.content.Clear()\n\treturn t\n}\n\n// SetBorders sets whether or not each cell in the table is surrounded by a\n// border.\nfunc (t *Table) SetBorders(show bool) *Table {\n\tt.borders = show\n\treturn t\n}\n\n// SetBordersColor sets the color of the cell borders.\nfunc (t *Table) SetBordersColor(color tcell.Color) *Table {\n\tt.bordersColor = color\n\treturn t\n}\n\n// SetSelectedStyle sets a specific style for selected cells. If no such style\n// is set, the cell's background and text color are swapped. If a cell defines\n// its own selected style, that will be used instead.\n//\n// To reset a previous setting to its default, make the following call:\n//\n//\ttable.SetSelectedStyle(tcell.StyleDefault)\nfunc (t *Table) SetSelectedStyle(style tcell.Style) *Table {\n\tt.selectedStyle = style\n\treturn t\n}\n\n// SetSeparator sets the character used to fill the space between two\n// neighboring cells. This is a space character ' ' per default but you may\n// want to set it to Borders.Vertical (or any other rune) if the column\n// separation should be more visible. If cell borders are activated, this is\n// ignored.\n//\n// Separators have the same color as borders.\nfunc (t *Table) SetSeparator(separator rune) *Table {\n\tt.separator = separator\n\treturn t\n}\n\n// SetFixed sets the number of fixed rows and columns which are always visible\n// even when the rest of the cells are scrolled out of view. Rows are always the\n// top-most ones. Columns are always the left-most ones.\nfunc (t *Table) SetFixed(rows, columns int) *Table {\n\tt.fixedRows, t.fixedColumns = rows, columns\n\treturn t\n}\n\n// SetSelectable sets the flags which determine what can be selected in a table.\n// There are three selection modi:\n//\n//   - rows = false, columns = false: Nothing can be selected.\n//   - rows = true, columns = false: Rows can be selected.\n//   - rows = false, columns = true: Columns can be selected.\n//   - rows = true, columns = true: Individual cells can be selected.\nfunc (t *Table) SetSelectable(rows, columns bool) *Table {\n\tt.rowsSelectable, t.columnsSelectable = rows, columns\n\treturn t\n}\n\n// GetSelectable returns what can be selected in a table. Refer to\n// SetSelectable() for details.\nfunc (t *Table) GetSelectable() (rows, columns bool) {\n\treturn t.rowsSelectable, t.columnsSelectable\n}\n\n// GetSelection returns the position of the current selection.\n// If entire rows are selected, the column index is undefined.\n// Likewise for entire columns.\nfunc (t *Table) GetSelection() (row, column int) {\n\treturn t.selectedRow, t.selectedColumn\n}\n\n// Select sets the selected cell. Depending on the selection settings\n// specified via SetSelectable(), this may be an entire row or column, or even\n// ignored completely. The \"selection changed\" event is fired if such a callback\n// is available (even if the selection ends up being the same as before and even\n// if cells are not selectable).\nfunc (t *Table) Select(row, column int) *Table {\n\tt.selectedRow, t.selectedColumn = row, column\n\tt.clampToSelection = true\n\tif t.selectionChanged != nil {\n\t\tt.selectionChanged(row, column)\n\t}\n\treturn t\n}\n\n// SetOffset sets how many rows and columns should be skipped when drawing the\n// table. This is useful for large tables that do not fit on the screen.\n// Navigating a selection can change these values.\n//\n// Fixed rows and columns are never skipped.\nfunc (t *Table) SetOffset(row, column int) *Table {\n\tt.rowOffset, t.columnOffset = row, column\n\tt.trackEnd = false\n\treturn t\n}\n\n// GetOffset returns the current row and column offset. This indicates how many\n// rows and columns the table is scrolled down and to the right.\nfunc (t *Table) GetOffset() (row, column int) {\n\treturn t.rowOffset, t.columnOffset\n}\n\n// SetEvaluateAllRows sets a flag which determines the rows to be evaluated when\n// calculating the widths of the table's columns. When false, only visible rows\n// are evaluated. When true, all rows in the table are evaluated.\n//\n// Set this flag to true to avoid shifting column widths when the table is\n// scrolled. (May come with a performance penalty for large tables.)\n//\n// Use with caution on very large tables, especially those not backed by the\n// default TableContent data structure.\nfunc (t *Table) SetEvaluateAllRows(all bool) *Table {\n\tt.evaluateAllRows = all\n\treturn t\n}\n\n// SetSelectedFunc sets a handler which is called whenever the user presses the\n// Enter key on a selected cell/row/column. The handler receives the position of\n// the selection and its cell contents. If entire rows are selected, the column\n// index is undefined. Likewise for entire columns.\nfunc (t *Table) SetSelectedFunc(handler func(row, column int)) *Table {\n\tt.selected = handler\n\treturn t\n}\n\n// SetSelectionChangedFunc sets a handler which is called whenever the current\n// selection changes. The handler receives the position of the new selection.\n// If entire rows are selected, the column index is undefined. Likewise for\n// entire columns.\nfunc (t *Table) SetSelectionChangedFunc(handler func(row, column int)) *Table {\n\tt.selectionChanged = handler\n\treturn t\n}\n\n// SetDoneFunc sets a handler which is called whenever the user presses the\n// Escape, Tab, or Backtab key. If nothing is selected, it is also called when\n// user presses the Enter key (because pressing Enter on a selection triggers\n// the \"selected\" handler set via SetSelectedFunc()).\nfunc (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table {\n\tt.done = handler\n\treturn t\n}\n\n// SetCell sets the content of a cell the specified position. It is ok to\n// directly instantiate a TableCell object. If the cell has content, at least\n// the Text and Color fields should be set.\n//\n// Note that setting cells in previously unknown rows and columns will\n// automatically extend the internal table representation with empty TableCell\n// objects, e.g. starting with a row of 100,000 will immediately create 100,000\n// empty rows.\n//\n// To avoid unnecessary garbage collection, fill columns from left to right.\nfunc (t *Table) SetCell(row, column int, cell *TableCell) *Table {\n\tt.content.SetCell(row, column, cell)\n\treturn t\n}\n\n// SetCellSimple calls SetCell() with the given text, left-aligned, in white.\nfunc (t *Table) SetCellSimple(row, column int, text string) *Table {\n\tt.SetCell(row, column, NewTableCell(text))\n\treturn t\n}\n\n// GetCell returns the contents of the cell at the specified position. A valid\n// TableCell object is always returned but it will be uninitialized if the cell\n// was not previously set. Such an uninitialized object will not automatically\n// be inserted. Therefore, repeated calls to this function may return different\n// pointers for uninitialized cells.\nfunc (t *Table) GetCell(row, column int) *TableCell {\n\tcell := t.content.GetCell(row, column)\n\tif cell == nil {\n\t\tcell = &TableCell{}\n\t}\n\treturn cell\n}\n\n// RemoveRow removes the row at the given position from the table. If there is\n// no such row, this has no effect.\nfunc (t *Table) RemoveRow(row int) *Table {\n\tt.content.RemoveRow(row)\n\treturn t\n}\n\n// RemoveColumn removes the column at the given position from the table. If\n// there is no such column, this has no effect.\nfunc (t *Table) RemoveColumn(column int) *Table {\n\tt.content.RemoveColumn(column)\n\treturn t\n}\n\n// InsertRow inserts a row before the row with the given index. Cells on the\n// given row and below will be shifted to the bottom by one row. If \"row\" is\n// equal or larger than the current number of rows, this function has no effect.\nfunc (t *Table) InsertRow(row int) *Table {\n\tt.content.InsertRow(row)\n\treturn t\n}\n\n// InsertColumn inserts a column before the column with the given index. Cells\n// in the given column and to its right will be shifted to the right by one\n// column. Rows that have fewer initialized cells than \"column\" will remain\n// unchanged.\nfunc (t *Table) InsertColumn(column int) *Table {\n\tt.content.InsertColumn(column)\n\treturn t\n}\n\n// GetRowCount returns the number of rows in the table.\nfunc (t *Table) GetRowCount() int {\n\treturn t.content.GetRowCount()\n}\n\n// GetColumnCount returns the (maximum) number of columns in the table.\nfunc (t *Table) GetColumnCount() int {\n\treturn t.content.GetColumnCount()\n}\n\n// CellAt returns the row and column located at the given screen coordinates.\n// Each returned value may be negative if there is no row and/or cell. This\n// function will also process coordinates outside the table's inner rectangle so\n// callers will need to check for bounds themselves.\n//\n// The layout of the table when it was last drawn is used so if anything has\n// changed in the meantime, the results may not be reliable.\nfunc (t *Table) CellAt(x, y int) (row, column int) {\n\trectX, rectY, _, _ := t.GetInnerRect()\n\n\t// Determine row as seen on screen.\n\tif t.borders {\n\t\trow = (y - rectY - 1) / 2\n\t} else {\n\t\trow = y - rectY\n\t}\n\n\t// Respect fixed rows and row offset.\n\tif row >= 0 {\n\t\tif row >= t.fixedRows {\n\t\t\trow += t.rowOffset\n\t\t}\n\t\tif row >= t.content.GetRowCount() {\n\t\t\trow = -1\n\t\t}\n\t}\n\n\t// Saerch for the clicked column.\n\tcolumn = -1\n\tif x >= rectX {\n\t\tcolumnX := rectX\n\t\tif t.borders {\n\t\t\tcolumnX++\n\t\t}\n\t\tfor index, width := range t.visibleColumnWidths {\n\t\t\tcolumnX += width + 1\n\t\t\tif x < columnX {\n\t\t\t\tcolumn = t.visibleColumnIndices[index]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\n// ScrollToBeginning scrolls the table to the beginning to that the top left\n// corner of the table is shown. Note that this position may be corrected if\n// there is a selection.\nfunc (t *Table) ScrollToBeginning() *Table {\n\tt.trackEnd = false\n\tt.columnOffset = 0\n\tt.rowOffset = 0\n\treturn t\n}\n\n// ScrollToEnd scrolls the table to the beginning to that the bottom left corner\n// of the table is shown. Adding more rows to the table will cause it to\n// automatically scroll with the new data. Note that this position may be\n// corrected if there is a selection.\nfunc (t *Table) ScrollToEnd() *Table {\n\tt.trackEnd = true\n\tt.columnOffset = 0\n\tt.rowOffset = t.content.GetRowCount()\n\treturn t\n}\n\n// SetWrapSelection determines whether a selection wraps vertically or\n// horizontally when moved. Vertically wrapping selections will jump from the\n// last selectable row to the first selectable row and vice versa. Horizontally\n// wrapping selections will jump from the last selectable column to the first\n// selectable column (on the next selectable row) or from the first selectable\n// column to the last selectable column (on the previous selectable row). If set\n// to false, the selection is not moved when it is already on the first/last\n// selectable row/column.\n//\n// The default is for both values to be false.\nfunc (t *Table) SetWrapSelection(vertical, horizontal bool) *Table {\n\tt.wrapHorizontally = horizontal\n\tt.wrapVertically = vertical\n\treturn t\n}\n\n// Draw draws this primitive onto the screen.\nfunc (t *Table) Draw(screen tcell.Screen) {\n\tt.Box.DrawForSubclass(screen, t)\n\n\t// What's our available screen space?\n\t_, totalHeight := screen.Size()\n\tx, y, width, height := t.GetInnerRect()\n\tnetWidth := width\n\tif t.borders {\n\t\tt.visibleRows = height / 2\n\t\tnetWidth -= 2\n\t} else {\n\t\tt.visibleRows = height\n\t}\n\n\t// If this cell is not selectable, find the next one.\n\trowCount, columnCount := t.content.GetRowCount(), t.content.GetColumnCount()\n\tif t.rowsSelectable || t.columnsSelectable {\n\t\tif t.selectedColumn < 0 {\n\t\t\tt.selectedColumn = 0\n\t\t}\n\t\tif t.selectedRow < 0 {\n\t\t\tt.selectedRow = 0\n\t\t}\n\t\tfor t.selectedRow < rowCount {\n\t\t\tcell := t.content.GetCell(t.selectedRow, t.selectedColumn)\n\t\t\tif cell != nil && !cell.NotSelectable {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tt.selectedColumn++\n\t\t\tif t.selectedColumn > columnCount-1 {\n\t\t\t\tt.selectedColumn = 0\n\t\t\t\tt.selectedRow++\n\t\t\t}\n\t\t}\n\t}\n\n\t// Clamp row offsets if requested.\n\tdefer func() {\n\t\tt.clampToSelection = false // Only once.\n\t}()\n\tif t.clampToSelection && t.rowsSelectable {\n\t\tif t.selectedRow >= t.fixedRows && t.selectedRow < t.fixedRows+t.rowOffset {\n\t\t\tt.rowOffset = t.selectedRow - t.fixedRows\n\t\t\tt.trackEnd = false\n\t\t}\n\t\tif t.borders {\n\t\t\tif t.selectedRow+1-t.rowOffset >= height/2 {\n\t\t\t\tt.rowOffset = t.selectedRow + 1 - height/2\n\t\t\t\tt.trackEnd = false\n\t\t\t}\n\t\t} else {\n\t\t\tif t.selectedRow+1-t.rowOffset >= height {\n\t\t\t\tt.rowOffset = t.selectedRow + 1 - height\n\t\t\t\tt.trackEnd = false\n\t\t\t}\n\t\t}\n\t}\n\tif t.rowOffset < 0 {\n\t\tt.rowOffset = 0\n\t}\n\tif t.borders {\n\t\tif rowCount-t.rowOffset < height/2 {\n\t\t\tt.trackEnd = true\n\t\t}\n\t} else {\n\t\tif rowCount-t.rowOffset < height {\n\t\t\tt.trackEnd = true\n\t\t}\n\t}\n\tif t.trackEnd {\n\t\tif t.borders {\n\t\t\tt.rowOffset = rowCount - height/2\n\t\t} else {\n\t\t\tt.rowOffset = rowCount - height\n\t\t}\n\t}\n\tif t.rowOffset < 0 {\n\t\tt.rowOffset = 0\n\t}\n\n\t// Avoid invalid column offsets.\n\tif t.columnOffset >= columnCount-t.fixedColumns {\n\t\tt.columnOffset = columnCount - t.fixedColumns - 1\n\t}\n\tif t.columnOffset < 0 {\n\t\tt.columnOffset = 0\n\t}\n\n\t// Determine the indices of the rows which fit on the screen.\n\tvar (\n\t\trows, allRows []int\n\t\ttableHeight   int\n\t)\n\trowStep := 1\n\tif t.borders {\n\t\trowStep = 2 // With borders, every table row takes two screen rows.\n\t}\n\tif t.evaluateAllRows {\n\t\tallRows = make([]int, rowCount)\n\t\tfor row := 0; row < rowCount; row++ {\n\t\t\tallRows[row] = row\n\t\t}\n\t}\n\tindexRow := func(row int) bool { // Determine if this row is visible, store its index.\n\t\tif tableHeight >= height {\n\t\t\treturn false\n\t\t}\n\t\trows = append(rows, row)\n\t\ttableHeight += rowStep\n\t\treturn true\n\t}\n\tfor row := 0; row < t.fixedRows && row < rowCount; row++ { // Do the fixed rows first.\n\t\tif !indexRow(row) {\n\t\t\tbreak\n\t\t}\n\t}\n\tfor row := t.fixedRows + t.rowOffset; row < rowCount; row++ { // Then the remaining rows.\n\t\tif !indexRow(row) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// Determine the columns' indices, widths, and expansion values that fit on\n\t// the screen.\n\tvar (\n\t\ttableWidth, expansionTotal  int\n\t\tcolumns, widths, expansions []int\n\t)\n\tincludesSelection := !t.clampToSelection || !t.columnsSelectable\n\n\t// Helper function that evaluates one column. Returns true if the column\n\t// didn't fit at all.\n\tindexColumn := func(column int) bool {\n\t\tif netWidth == 0 || tableWidth >= netWidth {\n\t\t\treturn true\n\t\t}\n\n\t\tvar maxWidth, expansion int\n\t\tevaluationRows := rows\n\t\tif t.evaluateAllRows {\n\t\t\tevaluationRows = allRows\n\t\t}\n\t\tfor _, row := range evaluationRows {\n\t\t\tif cell := t.content.GetCell(row, column); cell != nil {\n\t\t\t\tcellWidth := TaggedStringWidth(cell.Text)\n\t\t\t\tif cell.MaxWidth > 0 && cell.MaxWidth < cellWidth {\n\t\t\t\t\tcellWidth = cell.MaxWidth\n\t\t\t\t}\n\t\t\t\tif cellWidth > maxWidth {\n\t\t\t\t\tmaxWidth = cellWidth\n\t\t\t\t}\n\t\t\t\tif cell.Expansion > expansion {\n\t\t\t\t\texpansion = cell.Expansion\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tclampedMaxWidth := maxWidth\n\t\tif tableWidth+maxWidth > netWidth {\n\t\t\tclampedMaxWidth = netWidth - tableWidth\n\t\t}\n\t\tcolumns = append(columns, column)\n\t\twidths = append(widths, clampedMaxWidth)\n\t\texpansions = append(expansions, expansion)\n\t\ttableWidth += clampedMaxWidth + 1\n\t\texpansionTotal += expansion\n\t\tif t.columnsSelectable && t.clampToSelection && column == t.selectedColumn {\n\t\t\t// We want selections to appear fully.\n\t\t\tincludesSelection = clampedMaxWidth == maxWidth\n\t\t}\n\n\t\treturn false\n\t}\n\n\t// Helper function that evaluates multiple columns, starting at \"start\" and\n\t// at most ending at \"maxEnd\". Returns first column not included anymore (or\n\t// -1 if all are included).\n\tindexColumns := func(start, maxEnd int) int {\n\t\tif start == maxEnd {\n\t\t\treturn -1\n\t\t}\n\n\t\tif start < maxEnd {\n\t\t\t// Forward-evaluate columns.\n\t\t\tfor column := start; column < maxEnd; column++ {\n\t\t\t\tif indexColumn(column) {\n\t\t\t\t\treturn column\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn -1\n\t\t}\n\n\t\t// Backward-evaluate columns.\n\t\tstartLen := len(columns)\n\t\tdefer func() {\n\t\t\t// Because we went backwards, we must reverse the partial slices.\n\t\t\tfor i, j := startLen, len(columns)-1; i < j; i, j = i+1, j-1 {\n\t\t\t\tcolumns[i], columns[j] = columns[j], columns[i]\n\t\t\t\twidths[i], widths[j] = widths[j], widths[i]\n\t\t\t\texpansions[i], expansions[j] = expansions[j], expansions[i]\n\t\t\t}\n\t\t}()\n\t\tfor column := start; column >= maxEnd; column-- {\n\t\t\tif indexColumn(column) {\n\t\t\t\treturn column\n\t\t\t}\n\t\t}\n\t\treturn -1\n\t}\n\n\t// Reset the table to only its fixed columns.\n\tvar fixedTableWidth, fixedExpansionTotal int\n\tresetColumns := func() {\n\t\ttableWidth = fixedTableWidth\n\t\texpansionTotal = fixedExpansionTotal\n\t\tcolumns = columns[:t.fixedColumns]\n\t\twidths = widths[:t.fixedColumns]\n\t\texpansions = expansions[:t.fixedColumns]\n\t}\n\n\t// Add fixed columns.\n\tif indexColumns(0, t.fixedColumns) < 0 {\n\t\tfixedTableWidth = tableWidth\n\t\tfixedExpansionTotal = expansionTotal\n\n\t\t// Add unclamped columns.\n\t\tif column := indexColumns(t.fixedColumns+t.columnOffset, columnCount); !includesSelection || column < 0 && t.columnOffset > 0 {\n\t\t\t// Offset is not optimal. Try again.\n\t\t\tif !includesSelection {\n\t\t\t\t// Clamp to selection.\n\t\t\t\tresetColumns()\n\t\t\t\tif t.selectedColumn <= t.fixedColumns+t.columnOffset {\n\t\t\t\t\t// It's on the left. Start with the selection.\n\t\t\t\t\tt.columnOffset = t.selectedColumn - t.fixedColumns\n\t\t\t\t\tindexColumns(t.fixedColumns+t.columnOffset, columnCount)\n\t\t\t\t} else {\n\t\t\t\t\t// It's on the right. End with the selection.\n\t\t\t\t\tif column := indexColumns(t.selectedColumn, t.fixedColumns); column >= 0 {\n\t\t\t\t\t\tt.columnOffset = column + 1 - t.fixedColumns\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.columnOffset = 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if tableWidth < netWidth {\n\t\t\t\t// Don't waste space. Try to fit as much on screen as possible.\n\t\t\t\tresetColumns()\n\t\t\t\tif column := indexColumns(columnCount-1, t.fixedColumns); column >= 0 {\n\t\t\t\t\tt.columnOffset = column + 1 - t.fixedColumns\n\t\t\t\t} else {\n\t\t\t\t\tt.columnOffset = 0\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// If we have space left, distribute it.\n\tif tableWidth < netWidth {\n\t\ttoDistribute := netWidth - tableWidth\n\t\tfor index, expansion := range expansions {\n\t\t\tif expansionTotal <= 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\texpWidth := toDistribute * expansion / expansionTotal\n\t\t\twidths[index] += expWidth\n\t\t\ttoDistribute -= expWidth\n\t\t\texpansionTotal -= expansion\n\t\t}\n\t}\n\n\t// Helper function which draws border runes.\n\tborderStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.bordersColor)\n\tdrawBorder := func(colX, rowY int, ch rune) {\n\t\tscreen.SetContent(x+colX, y+rowY, ch, nil, borderStyle)\n\t}\n\n\t// Draw the cells (and borders).\n\tvar columnX int\n\tif t.borders {\n\t\tcolumnX++\n\t}\n\tfor columnIndex, column := range columns {\n\t\tcolumnWidth := widths[columnIndex]\n\t\tfor rowY, row := range rows {\n\t\t\tif t.borders {\n\t\t\t\t// Draw borders.\n\t\t\t\trowY *= 2\n\t\t\t\tfor pos := 0; pos < columnWidth && columnX+pos < width; pos++ {\n\t\t\t\t\tdrawBorder(columnX+pos, rowY, Borders.Horizontal)\n\t\t\t\t}\n\t\t\t\tch := Borders.Cross\n\t\t\t\tif row == 0 {\n\t\t\t\t\tif column == 0 {\n\t\t\t\t\t\tch = Borders.TopLeft\n\t\t\t\t\t} else {\n\t\t\t\t\t\tch = Borders.TopT\n\t\t\t\t\t}\n\t\t\t\t} else if column == 0 {\n\t\t\t\t\tch = Borders.LeftT\n\t\t\t\t}\n\t\t\t\tdrawBorder(columnX-1, rowY, ch)\n\t\t\t\trowY++\n\t\t\t\tif rowY >= height || y+rowY >= totalHeight {\n\t\t\t\t\tbreak // No space for the text anymore.\n\t\t\t\t}\n\t\t\t\tdrawBorder(columnX-1, rowY, Borders.Vertical)\n\t\t\t} else if columnIndex < len(columns)-1 {\n\t\t\t\t// Draw separator.\n\t\t\t\tdrawBorder(columnX+columnWidth, rowY, t.separator)\n\t\t\t}\n\n\t\t\t// Get the cell.\n\t\t\tcell := t.content.GetCell(row, column)\n\t\t\tif cell == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Draw text.\n\t\t\tfinalWidth := columnWidth\n\t\t\tif columnX+columnWidth >= width {\n\t\t\t\tfinalWidth = width - columnX\n\t\t\t}\n\t\t\tcell.x, cell.y, cell.width = x+columnX, y+rowY, finalWidth\n\t\t\tstyle := cell.Style\n\t\t\tif style == tcell.StyleDefault {\n\t\t\t\tstyle = tcell.StyleDefault.Background(cell.BackgroundColor).Foreground(cell.Color).Attributes(cell.Attributes)\n\t\t\t}\n\t\t\tstart, end, _ := printWithStyle(screen, cell.Text, x+columnX, y+rowY, 0, finalWidth, cell.Align, style, cell.Transparent)\n\t\t\tprinted := end - start\n\t\t\tif TaggedStringWidth(cell.Text)-printed > 0 && printed > 0 {\n\t\t\t\t_, _, style, _ := screen.GetContent(x+columnX+finalWidth-1, y+rowY)\n\t\t\t\tprintWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+finalWidth-1, y+rowY, 0, 1, AlignLeft, style, false)\n\t\t\t}\n\t\t}\n\n\t\t// Draw bottom border.\n\t\tif rowY := 2 * len(rows); t.borders && rowY > 0 && rowY < height {\n\t\t\tfor pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {\n\t\t\t\tdrawBorder(columnX+pos, rowY, Borders.Horizontal)\n\t\t\t}\n\t\t\tch := Borders.Cross\n\t\t\tif rows[len(rows)-1] == rowCount-1 {\n\t\t\t\tif column == 0 {\n\t\t\t\t\tch = Borders.BottomLeft\n\t\t\t\t} else {\n\t\t\t\t\tch = Borders.BottomT\n\t\t\t\t}\n\t\t\t} else if column == 0 {\n\t\t\t\tch = Borders.BottomLeft\n\t\t\t}\n\t\t\tdrawBorder(columnX-1, rowY, ch)\n\t\t}\n\n\t\tcolumnX += columnWidth + 1\n\t}\n\n\t// Draw right border.\n\tcolumnX--\n\tif t.borders && len(rows) > 0 && len(columns) > 0 && columnX < width {\n\t\tlastColumn := columns[len(columns)-1] == columnCount-1\n\t\tfor rowY := range rows {\n\t\t\trowY *= 2\n\t\t\tif rowY+1 < height {\n\t\t\t\tdrawBorder(columnX, rowY+1, Borders.Vertical)\n\t\t\t}\n\t\t\tch := Borders.Cross\n\t\t\tif rowY == 0 {\n\t\t\t\tif lastColumn {\n\t\t\t\t\tch = Borders.TopRight\n\t\t\t\t} else {\n\t\t\t\t\tch = Borders.TopT\n\t\t\t\t}\n\t\t\t} else if lastColumn {\n\t\t\t\tch = Borders.RightT\n\t\t\t}\n\t\t\tdrawBorder(columnX, rowY, ch)\n\t\t}\n\t\tif rowY := 2 * len(rows); rowY < height {\n\t\t\tch := Borders.BottomT\n\t\t\tif lastColumn {\n\t\t\t\tch = Borders.BottomRight\n\t\t\t}\n\t\t\tdrawBorder(columnX, rowY, ch)\n\t\t}\n\t}\n\n\t// Helper function which colors the background of a box.\n\t// backgroundTransparent == true => Don't modify background color (when invert == false).\n\t// textTransparent == true => Don't modify text color (when invert == false).\n\t// attr == 0 => Don't change attributes.\n\t// invert == true => Ignore attr, set text to backgroundColor or t.backgroundColor;\n\t//                   set background to textColor.\n\tcolorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, backgroundTransparent, textTransparent bool, attr tcell.AttrMask, invert bool) {\n\t\tfor by := 0; by < h && fromY+by < y+height; by++ {\n\t\t\tfor bx := 0; bx < w && fromX+bx < x+width; bx++ {\n\t\t\t\tm, c, style, _ := screen.GetContent(fromX+bx, fromY+by)\n\t\t\t\tfg, bg, a := style.Decompose()\n\t\t\t\tif invert {\n\t\t\t\t\tstyle = style.Background(textColor).Foreground(backgroundColor)\n\t\t\t\t} else {\n\t\t\t\t\tif !backgroundTransparent {\n\t\t\t\t\t\tbg = backgroundColor\n\t\t\t\t\t}\n\t\t\t\t\tif !textTransparent {\n\t\t\t\t\t\tfg = textColor\n\t\t\t\t\t}\n\t\t\t\t\tif attr != 0 {\n\t\t\t\t\t\ta = attr\n\t\t\t\t\t}\n\t\t\t\t\tstyle = style.Background(bg).Foreground(fg).Attributes(a)\n\t\t\t\t}\n\t\t\t\tscreen.SetContent(fromX+bx, fromY+by, m, c, style)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Color the cell backgrounds. To avoid undesirable artefacts, we combine\n\t// the drawing of a cell by background color, selected cells last.\n\ttype cellInfo struct {\n\t\tx, y, w, h int\n\t\tcell       *TableCell\n\t\tselected   bool\n\t}\n\tcellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)\n\tvar backgroundColors []tcell.Color\n\tfor rowY, row := range rows {\n\t\tcolumnX := 0\n\t\trowSelected := t.rowsSelectable && !t.columnsSelectable && row == t.selectedRow\n\t\tfor columnIndex, column := range columns {\n\t\t\tcolumnWidth := widths[columnIndex]\n\t\t\tcell := t.content.GetCell(row, column)\n\t\t\tif cell == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbx, by, bw, bh := x+columnX, y+rowY, columnWidth+1, 1\n\t\t\tif t.borders {\n\t\t\t\tby = y + rowY*2\n\t\t\t\tbw++\n\t\t\t\tbh = 3\n\t\t\t}\n\t\t\tcolumnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn\n\t\t\tcellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow)\n\t\t\tbackgroundColor := cell.BackgroundColor\n\t\t\tif cell.Style != tcell.StyleDefault {\n\t\t\t\t_, backgroundColor, _ = cell.Style.Decompose()\n\t\t\t}\n\t\t\tentries, ok := cellsByBackgroundColor[backgroundColor]\n\t\t\tcellsByBackgroundColor[backgroundColor] = append(entries, &cellInfo{\n\t\t\t\tx:        bx,\n\t\t\t\ty:        by,\n\t\t\t\tw:        bw,\n\t\t\t\th:        bh,\n\t\t\t\tcell:     cell,\n\t\t\t\tselected: cellSelected,\n\t\t\t})\n\t\t\tif !ok {\n\t\t\t\tbackgroundColors = append(backgroundColors, backgroundColor)\n\t\t\t}\n\t\t\tcolumnX += columnWidth + 1\n\t\t}\n\t}\n\tsort.Slice(backgroundColors, func(i int, j int) bool {\n\t\t// Draw brightest colors last (i.e. on top).\n\t\tr, g, b := backgroundColors[i].RGB()\n\t\tc := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}\n\t\t_, _, li := c.Hcl()\n\t\tr, g, b = backgroundColors[j].RGB()\n\t\tc = colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}\n\t\t_, _, lj := c.Hcl()\n\t\treturn li < lj\n\t})\n\tfor _, bgColor := range backgroundColors {\n\t\tentries := cellsByBackgroundColor[bgColor]\n\t\tfor _, info := range entries {\n\t\t\ttextColor := info.cell.Color\n\t\t\tif info.cell.Style != tcell.StyleDefault {\n\t\t\t\ttextColor, _, _ = info.cell.Style.Decompose()\n\t\t\t}\n\t\t\tif info.selected {\n\t\t\t\tif info.cell.SelectedStyle != tcell.StyleDefault {\n\t\t\t\t\tselFg, selBg, selAttr := info.cell.SelectedStyle.Decompose()\n\t\t\t\t\tdefer colorBackground(info.x, info.y, info.w, info.h, selBg, selFg, false, false, selAttr, false)\n\t\t\t\t} else if t.selectedStyle != tcell.StyleDefault {\n\t\t\t\t\tselFg, selBg, selAttr := t.selectedStyle.Decompose()\n\t\t\t\t\tdefer colorBackground(info.x, info.y, info.w, info.h, selBg, selFg, false, false, selAttr, false)\n\t\t\t\t} else {\n\t\t\t\t\tdefer colorBackground(info.x, info.y, info.w, info.h, bgColor, textColor, false, false, 0, true)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcolorBackground(info.x, info.y, info.w, info.h, bgColor, textColor, info.cell.Transparent, true, 0, false)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Remember column infos.\n\tt.visibleColumnIndices, t.visibleColumnWidths = columns, widths\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tkey := event.Key()\n\n\t\tif (!t.rowsSelectable && !t.columnsSelectable && key == tcell.KeyEnter) ||\n\t\t\tkey == tcell.KeyEscape ||\n\t\t\tkey == tcell.KeyTab ||\n\t\t\tkey == tcell.KeyBacktab {\n\t\t\tif t.done != nil {\n\t\t\t\tt.done(key)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// Movement functions.\n\t\tpreviouslySelectedRow, previouslySelectedColumn := t.selectedRow, t.selectedColumn\n\t\tlastColumn := t.content.GetColumnCount() - 1\n\t\trowCount := t.content.GetRowCount()\n\t\tif rowCount == 0 {\n\t\t\treturn // No movement on empty tables.\n\t\t}\n\t\tvar (\n\t\t\t// Move the selection forward, don't go beyond final cell, return\n\t\t\t// true if a selection was found.\n\t\t\tforward = func(finalRow, finalColumn int) bool {\n\t\t\t\trow, column := t.selectedRow, t.selectedColumn\n\t\t\t\tfor {\n\t\t\t\t\t// Stop if the current selection is fine.\n\t\t\t\t\tcell := t.content.GetCell(row, column)\n\t\t\t\t\tif cell != nil && !cell.NotSelectable {\n\t\t\t\t\t\tt.selectedRow, t.selectedColumn = row, column\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\n\t\t\t\t\t// If we reached the final cell, stop.\n\t\t\t\t\tif row == finalRow && column == finalColumn {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\t// Move forward.\n\t\t\t\t\tcolumn++\n\t\t\t\t\tif column > lastColumn {\n\t\t\t\t\t\tcolumn = 0\n\t\t\t\t\t\trow++\n\t\t\t\t\t\tif row >= rowCount {\n\t\t\t\t\t\t\trow = 0\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Move the selection backwards, don't go beyond final cell, return\n\t\t\t// true if a selection was found.\n\t\t\tbackwards = func(finalRow, finalColumn int) bool {\n\t\t\t\trow, column := t.selectedRow, t.selectedColumn\n\t\t\t\tfor {\n\t\t\t\t\t// Stop if the current selection is fine.\n\t\t\t\t\tcell := t.content.GetCell(row, column)\n\t\t\t\t\tif cell != nil && !cell.NotSelectable {\n\t\t\t\t\t\tt.selectedRow, t.selectedColumn = row, column\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\n\t\t\t\t\t// If we reached the final cell, stop.\n\t\t\t\t\tif row == finalRow && column == finalColumn {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\t// Move backwards.\n\t\t\t\t\tcolumn--\n\t\t\t\t\tif column < 0 {\n\t\t\t\t\t\tcolumn = lastColumn\n\t\t\t\t\t\trow--\n\t\t\t\t\t\tif row < 0 {\n\t\t\t\t\t\t\trow = rowCount - 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\thome = func() {\n\t\t\t\tif t.rowsSelectable {\n\t\t\t\t\tt.selectedRow = 0\n\t\t\t\t\tt.selectedColumn = 0\n\t\t\t\t\tforward(rowCount-1, lastColumn)\n\t\t\t\t\tt.clampToSelection = true\n\t\t\t\t} else {\n\t\t\t\t\tt.trackEnd = false\n\t\t\t\t\tt.rowOffset = 0\n\t\t\t\t\tt.columnOffset = 0\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tend = func() {\n\t\t\t\tif t.rowsSelectable {\n\t\t\t\t\tt.selectedRow = rowCount - 1\n\t\t\t\t\tt.selectedColumn = lastColumn\n\t\t\t\t\tbackwards(0, 0)\n\t\t\t\t\tt.clampToSelection = true\n\t\t\t\t} else {\n\t\t\t\t\tt.trackEnd = true\n\t\t\t\t\tt.columnOffset = 0\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdown = func() {\n\t\t\t\tif t.rowsSelectable {\n\t\t\t\t\trow, column := t.selectedRow, t.selectedColumn\n\t\t\t\t\tt.selectedRow++\n\t\t\t\t\tif t.selectedRow >= rowCount {\n\t\t\t\t\t\tif t.wrapVertically {\n\t\t\t\t\t\t\tt.selectedRow = 0\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tt.selectedRow = rowCount - 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfinalRow, finalColumn := rowCount-1, lastColumn\n\t\t\t\t\tif t.wrapVertically {\n\t\t\t\t\t\tfinalRow = row\n\t\t\t\t\t\tfinalColumn = column\n\t\t\t\t\t}\n\t\t\t\t\tif !forward(finalRow, finalColumn) {\n\t\t\t\t\t\tbackwards(row, column)\n\t\t\t\t\t}\n\t\t\t\t\tt.clampToSelection = true\n\t\t\t\t} else {\n\t\t\t\t\tt.rowOffset++\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tup = func() {\n\t\t\t\tif t.rowsSelectable {\n\t\t\t\t\trow, column := t.selectedRow, t.selectedColumn\n\t\t\t\t\tt.selectedRow--\n\t\t\t\t\tif t.selectedRow < 0 {\n\t\t\t\t\t\tif t.wrapVertically {\n\t\t\t\t\t\t\tt.selectedRow = rowCount - 1\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tt.selectedRow = 0\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfinalRow, finalColumn := 0, 0\n\t\t\t\t\tif t.wrapVertically {\n\t\t\t\t\t\tfinalRow = row\n\t\t\t\t\t\tfinalColumn = column\n\t\t\t\t\t}\n\t\t\t\t\tif !backwards(finalRow, finalColumn) {\n\t\t\t\t\t\tforward(row, column)\n\t\t\t\t\t}\n\t\t\t\t\tt.clampToSelection = true\n\t\t\t\t} else {\n\t\t\t\t\tt.trackEnd = false\n\t\t\t\t\tt.rowOffset--\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tleft = func() {\n\t\t\t\tif t.columnsSelectable {\n\t\t\t\t\trow, column := t.selectedRow, t.selectedColumn\n\t\t\t\t\tt.selectedColumn--\n\t\t\t\t\tif t.selectedColumn < 0 {\n\t\t\t\t\t\tif t.wrapHorizontally {\n\t\t\t\t\t\t\tt.selectedColumn = lastColumn\n\t\t\t\t\t\t\tt.selectedRow--\n\t\t\t\t\t\t\tif t.selectedRow < 0 {\n\t\t\t\t\t\t\t\tif t.wrapVertically {\n\t\t\t\t\t\t\t\t\tt.selectedRow = rowCount - 1\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tt.selectedColumn = 0\n\t\t\t\t\t\t\t\t\tt.selectedRow = 0\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tt.selectedColumn = 0\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfinalRow, finalColumn := row, column\n\t\t\t\t\tif !t.wrapHorizontally {\n\t\t\t\t\t\tfinalColumn = 0\n\t\t\t\t\t} else if !t.wrapVertically {\n\t\t\t\t\t\tfinalRow = 0\n\t\t\t\t\t\tfinalColumn = 0\n\t\t\t\t\t}\n\t\t\t\t\tif !backwards(finalRow, finalColumn) {\n\t\t\t\t\t\tforward(row, column)\n\t\t\t\t\t}\n\t\t\t\t\tt.clampToSelection = true\n\t\t\t\t} else {\n\t\t\t\t\tt.columnOffset--\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tright = func() {\n\t\t\t\tif t.columnsSelectable {\n\t\t\t\t\trow, column := t.selectedRow, t.selectedColumn\n\t\t\t\t\tt.selectedColumn++\n\t\t\t\t\tif t.selectedColumn > lastColumn {\n\t\t\t\t\t\tif t.wrapHorizontally {\n\t\t\t\t\t\t\tt.selectedColumn = 0\n\t\t\t\t\t\t\tt.selectedRow++\n\t\t\t\t\t\t\tif t.selectedRow >= rowCount {\n\t\t\t\t\t\t\t\tif t.wrapVertically {\n\t\t\t\t\t\t\t\t\tt.selectedRow = 0\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tt.selectedColumn = lastColumn\n\t\t\t\t\t\t\t\t\tt.selectedRow = rowCount - 1\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tt.selectedColumn = lastColumn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfinalRow, finalColumn := row, column\n\t\t\t\t\tif !t.wrapHorizontally {\n\t\t\t\t\t\tfinalColumn = lastColumn\n\t\t\t\t\t} else if !t.wrapVertically {\n\t\t\t\t\t\tfinalRow = rowCount - 1\n\t\t\t\t\t\tfinalColumn = lastColumn\n\t\t\t\t\t}\n\t\t\t\t\tif !forward(finalRow, finalColumn) {\n\t\t\t\t\t\tbackwards(row, column)\n\t\t\t\t\t}\n\t\t\t\t\tt.clampToSelection = true\n\t\t\t\t} else {\n\t\t\t\t\tt.columnOffset++\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpageDown = func() {\n\t\t\t\toffsetAmount := t.visibleRows - t.fixedRows\n\t\t\t\tif offsetAmount < 0 {\n\t\t\t\t\toffsetAmount = 0\n\t\t\t\t}\n\t\t\t\tif t.rowsSelectable {\n\t\t\t\t\trow, column := t.selectedRow, t.selectedColumn\n\t\t\t\t\tt.selectedRow += offsetAmount\n\t\t\t\t\tif t.selectedRow >= rowCount {\n\t\t\t\t\t\tt.selectedRow = rowCount - 1\n\t\t\t\t\t}\n\t\t\t\t\tfinalRow, finalColumn := rowCount-1, lastColumn\n\t\t\t\t\tif !forward(finalRow, finalColumn) {\n\t\t\t\t\t\tbackwards(row, column)\n\t\t\t\t\t}\n\t\t\t\t\tt.clampToSelection = true\n\t\t\t\t} else {\n\t\t\t\t\tt.rowOffset += offsetAmount\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpageUp = func() {\n\t\t\t\toffsetAmount := t.visibleRows - t.fixedRows\n\t\t\t\tif offsetAmount < 0 {\n\t\t\t\t\toffsetAmount = 0\n\t\t\t\t}\n\t\t\t\tif t.rowsSelectable {\n\t\t\t\t\trow, column := t.selectedRow, t.selectedColumn\n\t\t\t\t\tt.selectedRow -= offsetAmount\n\t\t\t\t\tif t.selectedRow < 0 {\n\t\t\t\t\t\tt.selectedRow = 0\n\t\t\t\t\t}\n\t\t\t\t\tfinalRow, finalColumn := 0, 0\n\t\t\t\t\tif !backwards(finalRow, finalColumn) {\n\t\t\t\t\t\tforward(row, column)\n\t\t\t\t\t}\n\t\t\t\t\tt.clampToSelection = true\n\t\t\t\t} else {\n\t\t\t\t\tt.trackEnd = false\n\t\t\t\t\tt.rowOffset -= offsetAmount\n\t\t\t\t}\n\t\t\t}\n\t\t)\n\n\t\tswitch key {\n\t\tcase tcell.KeyRune:\n\t\t\tswitch event.Rune() {\n\t\t\tcase 'g':\n\t\t\t\thome()\n\t\t\tcase 'G':\n\t\t\t\tend()\n\t\t\tcase 'j':\n\t\t\t\tdown()\n\t\t\tcase 'k':\n\t\t\t\tup()\n\t\t\tcase 'h':\n\t\t\t\tleft()\n\t\t\tcase 'l':\n\t\t\t\tright()\n\t\t\t}\n\t\tcase tcell.KeyHome:\n\t\t\thome()\n\t\tcase tcell.KeyEnd:\n\t\t\tend()\n\t\tcase tcell.KeyUp:\n\t\t\tup()\n\t\tcase tcell.KeyDown:\n\t\t\tdown()\n\t\tcase tcell.KeyLeft:\n\t\t\tleft()\n\t\tcase tcell.KeyRight:\n\t\t\tright()\n\t\tcase tcell.KeyPgDn, tcell.KeyCtrlF:\n\t\t\tpageDown()\n\t\tcase tcell.KeyPgUp, tcell.KeyCtrlB:\n\t\t\tpageUp()\n\t\tcase tcell.KeyEnter:\n\t\t\tif (t.rowsSelectable || t.columnsSelectable) && t.selected != nil {\n\t\t\t\tt.selected(t.selectedRow, t.selectedColumn)\n\t\t\t}\n\t\t}\n\n\t\t// If the selection has changed, notify the handler.\n\t\tif t.selectionChanged != nil &&\n\t\t\t(t.rowsSelectable && previouslySelectedRow != t.selectedRow ||\n\t\t\t\tt.columnsSelectable && previouslySelectedColumn != t.selectedColumn) {\n\t\t\tt.selectionChanged(t.selectedRow, t.selectedColumn)\n\t\t}\n\t})\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (t *Table) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tx, y := event.Position()\n\t\tif !t.InRect(x, y) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tswitch action {\n\t\tcase MouseLeftDown:\n\t\t\tsetFocus(t)\n\t\t\tconsumed = true\n\t\tcase MouseLeftClick:\n\t\t\tselectEvent := true\n\t\t\trow, column := t.CellAt(x, y)\n\t\t\tcell := t.content.GetCell(row, column)\n\t\t\tif cell != nil && cell.Clicked != nil {\n\t\t\t\tif noSelect := cell.Clicked(); noSelect {\n\t\t\t\t\tselectEvent = false\n\t\t\t\t}\n\t\t\t}\n\t\t\tif selectEvent && (t.rowsSelectable || t.columnsSelectable) {\n\t\t\t\tt.Select(row, column)\n\t\t\t}\n\t\t\tconsumed = true\n\t\tcase MouseScrollUp:\n\t\t\tt.trackEnd = false\n\t\t\tt.rowOffset--\n\t\t\tconsumed = true\n\t\tcase MouseScrollDown:\n\t\t\tt.rowOffset++\n\t\t\tconsumed = true\n\t\t}\n\n\t\treturn\n\t})\n}\n"
  },
  {
    "path": "textarea.go",
    "content": "package tview\n\nimport (\n\t\"math\"\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/uniseg\"\n)\n\nconst (\n\t// The minimum capacity of the text area's piece chain slice.\n\tpieceChainMinCap = 10\n\n\t// The minimum capacity of the text area's edit buffer.\n\teditBufferMinCap = 200\n\n\t// The maximum number of bytes making up a grapheme cluster. In theory, this\n\t// could be longer but it would be highly unusual.\n\tmaxGraphemeClusterSize = 40\n\n\t// The default value for the [TextArea.minCursorPrefix] variable.\n\tminCursorPrefixDefault = 5\n\n\t// The default value for the [TextArea.minCursorSuffix] variable.\n\tminCursorSuffixDefault = 3\n)\n\n// Types of user actions on a text area.\ntype taAction int\n\nconst (\n\ttaActionOther        taAction = iota\n\ttaActionTypeSpace             // Typing a space character.\n\ttaActionTypeNonSpace          // Typing a non-space character.\n\ttaActionBackspace             // Deleting the previous character.\n\ttaActionDelete                // Deleting the next character.\n)\n\n// NewLine is the string sequence to be inserted when hitting the Enter key in a\n// TextArea. The default is \"\\n\" but you may change it to \"\\r\\n\" if required.\nvar NewLine = \"\\n\"\n\n// textAreaSpan represents a range of text in a text area. The text area widget\n// roughly follows the concept of Piece Chains outlined in\n// http://www.catch22.net/tuts/neatpad/piece-chains with some modifications.\n// This type represents a \"span\" (or \"piece\") and thus refers to a subset of the\n// text in the editor as part of a doubly-linked list.\n//\n// In most places where we reference a position in the text, we use a\n// three-element int array. The first element is the index of the referenced\n// span in the piece chain. The second element is the offset into the span's\n// referenced text (relative to the span's start), its value is always >= 0 and\n// < span.length. The third element is the state of the text parser at that\n// position.\n//\n// A range of text is represented by a span range which is a starting position\n// (3-int array) and an ending position (3-int array). The starting position\n// references the first character of the range, the ending position references\n// the position after the last character of the range. The end of the text is\n// therefore always [3]int{1, 0, 0}, position 0 of the ending sentinel.\n//\n// Sentinel spans are dummy spans not referring to any text. There are always\n// two sentinel spans: the starting span at index 0 of the [TextArea.spans]\n// slice and the ending span at index 1.\ntype textAreaSpan struct {\n\t// Links to the previous and next textAreaSpan objects as indices into the\n\t// [TextArea.spans] slice. The sentinel spans (index 0 and 1) have -1 as\n\t// their previous or next links, respectively.\n\tprevious, next int\n\n\t// The start index and the length of the text segment this span represents.\n\t// If \"length\" is negative, the span represents a substring of\n\t// [TextArea.initialText] and the actual length is its absolute value. If it\n\t// is positive, the span represents a substring of [TextArea.editText]. For\n\t// the sentinel spans (index 0 and 1), both values will be 0. Others will\n\t// never have a zero length.\n\toffset, length int\n}\n\n// textAreaUndoItem represents an undoable edit to the text area. It describes\n// the two spans wrapping a text change.\ntype textAreaUndoItem struct {\n\tbefore, after                 int    // The index of the copied \"before\" and \"after\" spans into the \"spans\" slice.\n\toriginalBefore, originalAfter int    // The original indices of the \"before\" and \"after\" spans.\n\tpos                           [3]int // The cursor position to be assumed after applying an undo.\n\tlength                        int    // The total text length at the time the undo item was created.\n\tcontinuation                  bool   // If true, this item is a continuation of the previous undo item. It is handled together with all other undo items in the same continuation sequence.\n}\n\n// TextArea implements a simple text editor for multi-line text. Multi-color\n// text is not supported. Word-wrapping is enabled by default but can be turned\n// off or be changed to character-wrapping.\n//\n// # Navigation and Editing\n//\n// A text area is always in editing mode and no other mode exists. The following\n// keys can be used to move the cursor (subject to what the user's terminal\n// supports and how it is configured):\n//\n//   - Left arrow: Move left.\n//   - Right arrow: Move right.\n//   - Down arrow: Move down.\n//   - Up arrow: Move up.\n//   - Ctrl-A, Home: Move to the beginning of the current line.\n//   - Ctrl-E, End: Move to the end of the current line.\n//   - Ctrl-F, page down: Move down by one page.\n//   - Ctrl-B, page up: Move up by one page.\n//   - Alt-Up arrow: Scroll the page up, leaving the cursor in its position.\n//   - Alt-Down arrow: Scroll the page down, leaving the cursor in its position.\n//   - Alt-Left arrow: Scroll the page to the left, leaving the cursor in its\n//     position. Ignored if wrapping is enabled.\n//   - Alt-Right arrow:  Scroll the page to the right, leaving the cursor in its\n//     position. Ignored if wrapping is enabled.\n//   - Alt-B, Ctrl-Left arrow: Jump to the beginning of the current or previous\n//     word.\n//   - Alt-F, Ctrl-Right arrow: Jump to the end of the current or next word.\n//\n// Words are defined according to [Unicode Standard Annex #29]. We skip any\n// words that contain only spaces or punctuation.\n//\n// Entering a character will insert it at the current cursor location.\n// Subsequent characters are shifted accordingly. If the cursor is outside the\n// visible area, any changes to the text will move it into the visible area. The\n// following keys can also be used to modify the text:\n//\n//   - Enter: Insert a newline character (see [NewLine]).\n//   - Tab: Insert a tab character (\\t). It will be rendered like [TabSize]\n//     spaces. (This may eventually be changed to behave like regular tabs.)\n//   - Ctrl-H, Backspace: Delete one character to the left of the cursor.\n//   - Ctrl-D, Delete: Delete the character under the cursor (or the first\n//     character on the next line if the cursor is at the end of a line).\n//   - Alt-Backspace: Delete the word to the left of the cursor.\n//   - Ctrl-K: Delete everything under and to the right of the cursor until the\n//     next newline character.\n//   - Ctrl-W: Delete from the start of the current word to the left of the\n//     cursor.\n//   - Ctrl-U: Delete the current line, i.e. everything after the last newline\n//     character before the cursor up until the next newline character. This may\n//     span multiple visible rows if wrapping is enabled.\n//\n// Text can be selected by moving the cursor while holding the Shift key, to the\n// extent that this is supported by the user's terminal. The Ctrl-L key can be\n// used to select the entire text. (Ctrl-A already binds to the \"Home\" key.)\n//\n// When text is selected:\n//\n//   - Entering a character will replace the selected text with the new\n//     character.\n//   - Backspace, delete, Ctrl-H, Ctrl-D: Delete the selected text.\n//   - Ctrl-Q: Copy the selected text into the clipboard, unselect the text.\n//   - Ctrl-X: Copy the selected text into the clipboard and delete it.\n//   - Ctrl-V: Replace the selected text with the clipboard text. If no text is\n//     selected, the clipboard text will be inserted at the cursor location.\n//\n// The Ctrl-Q key was chosen for the \"copy\" function because the Ctrl-C key is\n// the default key to stop the application. If your application frees up the\n// global Ctrl-C key and you want to bind it to the \"copy to clipboard\"\n// function, you may use [Box.SetInputCapture] to override the Ctrl-Q key to\n// implement copying to the clipboard. Note that using your terminal's /\n// operating system's key bindings for copy+paste functionality may not have the\n// expected effect as tview will not be able to handle these keys. Pasting text\n// using your operating system's or terminal's own methods may be very slow as\n// each character will be pasted individually. However, some terminals support\n// pasting text blocks which is supported by the text area, see\n// [Application.EnablePaste] for details.\n//\n// The default clipboard is an internal text buffer local to this text area\n// instance, i.e. the operating system's clipboard is not used. If you want to\n// implement your own clipboard (or make use of your operating system's\n// clipboard), you can use [TextArea.SetClipboard] which  provides all the\n// functionality needed to implement your own clipboard.\n//\n// The text area also supports Undo:\n//\n//   - Ctrl-Z: Undo the last change.\n//   - Ctrl-Y: Redo the last Undo change.\n//\n// Undo does not affect the clipboard.\n//\n// If the mouse is enabled, the following actions are available:\n//\n//   - Left click: Move the cursor to the clicked position or to the end of the\n//     line if past the last character.\n//   - Left double-click: Select the word under the cursor.\n//   - Left click while holding the Shift key: Select text.\n//   - Scroll wheel: Scroll the text.\n//\n// [Unicode Standard Annex #29]: https://unicode.org/reports/tr29/\ntype TextArea struct {\n\t*Box\n\n\t// Whether or not this text area is disabled/read-only.\n\tdisabled bool\n\n\t// The size of the text area. If set to 0, the text area will use the entire\n\t// available space.\n\twidth, height int\n\n\t// The text to be shown in the text area when it is empty.\n\tplaceholder string\n\n\t// The label text shown, usually when part of a form.\n\tlabel string\n\n\t// The width of the text area's label.\n\tlabelWidth int\n\n\t// Styles:\n\n\t// The label style.\n\tlabelStyle tcell.Style\n\n\t// The style of the text. Background colors different from the Box's\n\t// background color may lead to unwanted artefacts.\n\ttextStyle tcell.Style\n\n\t// The style of the selected text.\n\tselectedStyle tcell.Style\n\n\t// The style of the placeholder text.\n\tplaceholderStyle tcell.Style\n\n\t// Text manipulation related fields:\n\n\t// The text area's text prior to any editing. It is referenced by spans with\n\t// a negative length.\n\tinitialText string\n\n\t// Any text that's been added by the user at some point. We only ever append\n\t// to this buffer. It is referenced by spans with a positive length.\n\teditText strings.Builder\n\n\t// The total length of all text in the text area.\n\tlength int\n\n\t// The maximum number of bytes allowed in the text area. If 0, there is no\n\t// limit.\n\tmaxLength int\n\n\t// The piece chain. The first two spans are sentinel spans which don't\n\t// reference anything and always remain in the same place. Spans are never\n\t// deleted from this slice.\n\tspans []textAreaSpan\n\n\t// An optional function which transforms grapheme clusters. This can be used\n\t// to hide characters from the screen while preserving the original text.\n\ttransform func(cluster, rest string, boundaries int) (newCluster string, newBoundaries int)\n\n\t// Display, navigation, and cursor related fields:\n\n\t// If set to true, lines that are longer than the available width are\n\t// wrapped onto the next line. If set to false, any characters beyond the\n\t// available width are discarded.\n\twrap bool\n\n\t// If set to true and if wrap is also true, lines are split at spaces or\n\t// after punctuation characters.\n\twordWrap bool\n\n\t// The index of the first line shown in the text area.\n\trowOffset int\n\n\t// The number of cells to be skipped on each line (not used in wrap mode).\n\tcolumnOffset int\n\n\t// The inner height and width of the text area the last time it was drawn.\n\tlastHeight, lastWidth int\n\n\t// The width of the currently known widest line, as determined by\n\t// [TextArea.extendLines].\n\twidestLine int\n\n\t// Text positions and states of the start of lines. Each element is a span\n\t// position (see [textAreaSpan]). Not all lines of the text may be contained\n\t// at any time, extend as needed with the [TextArea.extendLines] function.\n\tlineStarts [][3]int\n\n\t// The cursor always points to the next position where a new character would\n\t// be placed. The selection start is the same as the cursor as long as there\n\t// is no selection. When there is one, the selection is between\n\t// selectionStart and cursor.\n\tcursor, selectionStart struct {\n\t\t// The row and column in screen space but relative to the start of the\n\t\t// text which may be outside the text area's box. The column value may\n\t\t// be larger than where the cursor actually is if the line the cursor\n\t\t// is on is shorter. The actualColumn is the position as it is seen on\n\t\t// screen. These three values may not be determined yet, in which case\n\t\t// the row is negative.\n\t\trow, column, actualColumn int\n\n\t\t// The textAreaSpan position with state for the actual next character.\n\t\tpos [3]int\n\t}\n\n\t// The minimum width of text (if available) to be shown left of the cursor.\n\tminCursorPrefix int\n\n\t// The minimum width of text (if available) to be shown right of the cursor.\n\tminCursorSuffix int\n\n\t// Set to true when the mouse is dragging to select text.\n\tdragging bool\n\n\t// Clipboard related fields:\n\n\t// The internal clipboard.\n\tclipboard string\n\n\t// The function to call when the user copies/cuts a text selection to the\n\t// clipboard.\n\tcopyToClipboard func(string)\n\n\t// The function to call when the user pastes text from the clipboard.\n\tpasteFromClipboard func() string\n\n\t// Undo/redo related fields:\n\n\t// The last action performed by the user.\n\tlastAction taAction\n\n\t// The undo stack's items. Each item is a copy of the span before the\n\t// modified span range and a copy of the span after the modified span range.\n\t// To undo an action, the two referenced spans are put back into their\n\t// original place. Undos and redos decrease or increase the nextUndo value.\n\t// Thus, the next undo action is not always the last item.\n\tundoStack []textAreaUndoItem\n\n\t// The current undo/redo position on the undo stack. If no undo or redo has\n\t// been performed yet, this is the same as len(undoStack).\n\tnextUndo int\n\n\t// Event handlers:\n\n\t// An optional function which is called when the input has changed.\n\tchanged func()\n\n\t// An optional function which is called when the position of the cursor or\n\t// the selection has changed.\n\tmoved func()\n\n\t// A callback function set by the Form class and called when the user leaves\n\t// this form item.\n\tfinished func(tcell.Key)\n}\n\n// NewTextArea returns a new [TextArea]. Use [TextArea.SetText] to set the\n// initial text.\nfunc NewTextArea() *TextArea {\n\tt := &TextArea{\n\t\tBox:              NewBox(),\n\t\twrap:             true,\n\t\twordWrap:         true,\n\t\tplaceholderStyle: tcell.StyleDefault.Background(Styles.PrimitiveBackgroundColor).Foreground(Styles.TertiaryTextColor),\n\t\tlabelStyle:       tcell.StyleDefault.Foreground(Styles.SecondaryTextColor),\n\t\ttextStyle:        tcell.StyleDefault.Background(Styles.PrimitiveBackgroundColor).Foreground(Styles.PrimaryTextColor),\n\t\tselectedStyle:    tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.PrimitiveBackgroundColor),\n\t\tspans:            make([]textAreaSpan, 2, pieceChainMinCap), // We reserve some space to avoid reallocations right when editing starts.\n\t\tlastAction:       taActionOther,\n\t\tminCursorPrefix:  minCursorPrefixDefault,\n\t\tminCursorSuffix:  minCursorSuffixDefault,\n\t\tlastWidth:        math.MaxInt / 2, // We need this so some functions work before the first draw.\n\t\tlastHeight:       1,\n\t}\n\tt.editText.Grow(editBufferMinCap)\n\tt.spans[0] = textAreaSpan{previous: -1, next: 1}\n\tt.spans[1] = textAreaSpan{previous: 0, next: -1}\n\tt.cursor.pos = [3]int{1, 0, -1}\n\tt.selectionStart = t.cursor\n\tt.SetClipboard(nil, nil)\n\tt.Box.Primitive = t\n\treturn t\n}\n\n// SetText sets the text of the text area. All existing text is deleted and\n// replaced with the new text. Any edits are discarded, no undos are available.\n// This function is typically only used to initialize the text area with a text\n// after it has been created. To clear the text area's text (again, no undos),\n// provide an empty string.\n//\n// If cursorAtTheEnd is false, the cursor is placed at the start of the text. If\n// it is true, it is placed at the end of the text. For very long texts, placing\n// the cursor at the end can be an expensive operation because the entire text\n// needs to be parsed and laid out.\n//\n// If you want to set text and preserve undo functionality, use\n// [TextArea.Replace] instead.\nfunc (t *TextArea) SetText(text string, cursorAtTheEnd bool) *TextArea {\n\tt.spans = t.spans[:2]\n\tt.initialText = text\n\tt.editText.Reset()\n\tt.lineStarts = nil\n\tt.length = len(text)\n\tt.rowOffset = 0\n\tt.columnOffset = 0\n\tt.reset()\n\tt.cursor.row, t.cursor.actualColumn, t.cursor.column = 0, 0, 0\n\tt.cursor.pos = [3]int{1, 0, -1}\n\tt.undoStack = t.undoStack[:0]\n\tt.nextUndo = 0\n\n\tif len(text) > 0 {\n\t\tt.spans = append(t.spans, textAreaSpan{\n\t\t\tprevious: 0,\n\t\t\tnext:     1,\n\t\t\toffset:   0,\n\t\t\tlength:   -len(text),\n\t\t})\n\t\tt.spans[0].next = 2\n\t\tt.spans[1].previous = 2\n\t\tif cursorAtTheEnd {\n\t\t\tt.cursor.row = -1\n\t\t\tif t.lastWidth > 0 {\n\t\t\t\tt.findCursor(true, 0)\n\t\t\t}\n\t\t} else {\n\t\t\tt.cursor.pos = [3]int{2, 0, -1}\n\t\t}\n\t} else {\n\t\tt.spans[0].next = 1\n\t\tt.spans[1].previous = 0\n\t}\n\tt.selectionStart = t.cursor\n\n\tif t.changed != nil {\n\t\tt.changed()\n\t}\n\n\tif t.lastWidth > 0 && t.moved != nil {\n\t\tt.moved()\n\t}\n\n\treturn t\n}\n\n// GetText returns the entire text of the text area. Note that this will newly\n// allocate the entire text.\nfunc (t *TextArea) GetText() string {\n\tif t.length == 0 {\n\t\treturn \"\"\n\t}\n\n\tvar text strings.Builder\n\ttext.Grow(t.length)\n\tspanIndex := t.spans[0].next\n\tfor spanIndex != 1 {\n\t\tspan := &t.spans[spanIndex]\n\t\tif span.length < 0 {\n\t\t\ttext.WriteString(t.initialText[span.offset : span.offset-span.length])\n\t\t} else {\n\t\t\ttext.WriteString(t.editText.String()[span.offset : span.offset+span.length])\n\t\t}\n\t\tspanIndex = t.spans[spanIndex].next\n\t}\n\n\treturn text.String()\n}\n\n// getTextBeforeCursor returns the text of the text area up until the cursor.\n// Note that this will result in a new allocation for the returned text.\nfunc (t *TextArea) getTextBeforeCursor() string {\n\tif t.length == 0 || t.cursor.pos[0] == t.spans[0].next && t.cursor.pos[1] == 0 {\n\t\treturn \"\"\n\t}\n\n\tvar text strings.Builder\n\tspanIndex := t.spans[0].next\n\tfor spanIndex != 1 {\n\t\tspan := &t.spans[spanIndex]\n\t\tlength := span.length\n\t\tif length < 0 {\n\t\t\tif t.cursor.pos[0] == spanIndex {\n\t\t\t\tlength = -t.cursor.pos[1]\n\t\t\t}\n\t\t\ttext.WriteString(t.initialText[span.offset : span.offset-length])\n\t\t} else {\n\t\t\tif t.cursor.pos[0] == spanIndex {\n\t\t\t\tlength = t.cursor.pos[1]\n\t\t\t}\n\t\t\ttext.WriteString(t.editText.String()[span.offset : span.offset+length])\n\t\t}\n\t\tif t.cursor.pos[0] == spanIndex {\n\t\t\tbreak\n\t\t}\n\t\tspanIndex = t.spans[spanIndex].next\n\t}\n\n\treturn text.String()\n}\n\n// getTextAfterCursor returns the text of the text area after the cursor. Note\n// that this will result in a new allocation for the returned text.\nfunc (t *TextArea) getTextAfterCursor() string {\n\tif t.length == 0 || t.cursor.pos[0] == 1 {\n\t\treturn \"\"\n\t}\n\n\tvar text strings.Builder\n\tspanIndex := t.cursor.pos[0]\n\tcursorOffset := t.cursor.pos[1]\n\tfor spanIndex != 1 {\n\t\tspan := &t.spans[spanIndex]\n\t\tlength := span.length\n\t\tif length < 0 {\n\t\t\ttext.WriteString(t.initialText[span.offset+cursorOffset : span.offset-length])\n\t\t} else {\n\t\t\ttext.WriteString(t.editText.String()[span.offset+cursorOffset : span.offset+length])\n\t\t}\n\t\tspanIndex = t.spans[spanIndex].next\n\t\tcursorOffset = 0\n\t}\n\n\treturn text.String()\n}\n\n// HasSelection returns whether the selected text is non-empty.\nfunc (t *TextArea) HasSelection() bool {\n\treturn t.selectionStart != t.cursor\n}\n\n// GetSelection returns the currently selected text and its start and end\n// positions within the entire text as a half-open interval. If the returned\n// text is an empty string, the start and end positions are the same and can be\n// interpreted as the cursor position.\n//\n// Calling this function will result in string allocations as well as a search\n// for text positions. This is expensive if the text has been edited extensively\n// already. Use [TextArea.HasSelection] first if you are only interested in\n// selected text.\nfunc (t *TextArea) GetSelection() (text string, start int, end int) {\n\tfrom, to := t.selectionStart.pos, t.cursor.pos\n\tif t.cursor.row < t.selectionStart.row || (t.cursor.row == t.selectionStart.row && t.cursor.actualColumn < t.selectionStart.actualColumn) {\n\t\tfrom, to = to, from\n\t}\n\n\tif from[0] == 1 {\n\t\tstart = t.length\n\t}\n\tif to[0] == 1 {\n\t\tend = t.length\n\t}\n\n\tvar (\n\t\tindex     int\n\t\tselection strings.Builder\n\t\tinside    bool\n\t)\n\tfor span := t.spans[0].next; span != 1; span = t.spans[span].next {\n\t\tvar spanText string\n\t\tlength := t.spans[span].length\n\t\tif length < 0 {\n\t\t\tlength = -length\n\t\t\tspanText = t.initialText\n\t\t} else {\n\t\t\tspanText = t.editText.String()\n\t\t}\n\t\tspanText = spanText[t.spans[span].offset : t.spans[span].offset+length]\n\n\t\tif from[0] == span && to[0] == span {\n\t\t\tif from != to {\n\t\t\t\tselection.WriteString(spanText[from[1]:to[1]])\n\t\t\t}\n\t\t\tstart = index + from[1]\n\t\t\tend = index + to[1]\n\t\t\tbreak\n\t\t} else if from[0] == span {\n\t\t\tif from != to {\n\t\t\t\tselection.WriteString(spanText[from[1]:])\n\t\t\t}\n\t\t\tstart = index + from[1]\n\t\t\tinside = true\n\t\t} else if to[0] == span {\n\t\t\tif from != to {\n\t\t\t\tselection.WriteString(spanText[:to[1]])\n\t\t\t}\n\t\t\tend = index + to[1]\n\t\t\tbreak\n\t\t} else if inside && from != to {\n\t\t\tselection.WriteString(spanText)\n\t\t}\n\n\t\tindex += length\n\t}\n\n\tif selection.Len() != 0 {\n\t\ttext = selection.String()\n\t}\n\treturn\n}\n\n// GetCursor returns the current cursor position where the first character of\n// the entire text is in row 0, column 0. If the user has selected text, the\n// \"from\" values will refer to the beginning of the selection and the \"to\"\n// values to the end of the selection (exclusive). They are the same if there\n// is no selection.\nfunc (t *TextArea) GetCursor() (fromRow, fromColumn, toRow, toColumn int) {\n\tfromRow, fromColumn = t.selectionStart.row, t.selectionStart.actualColumn\n\ttoRow, toColumn = t.cursor.row, t.cursor.actualColumn\n\tif toRow < fromRow || (toRow == fromRow && toColumn < fromColumn) {\n\t\tfromRow, fromColumn, toRow, toColumn = toRow, toColumn, fromRow, fromColumn\n\t}\n\tif t.length > 0 && t.wrap && fromColumn >= t.lastWidth { // This happens when a row has text all the way until the end, pushing the cursor outside the viewport.\n\t\tfromRow++\n\t\tfromColumn = 0\n\t}\n\tif t.length > 0 && t.wrap && toColumn >= t.lastWidth {\n\t\ttoRow++\n\t\ttoColumn = 0\n\t}\n\treturn\n}\n\n// GetTextLength returns the string length of the text in the text area.\nfunc (t *TextArea) GetTextLength() int {\n\treturn t.length\n}\n\n// Replace replaces a section of the text with new text. The start and end\n// positions refer to index positions within the entire text string (as a\n// half-open interval). They may be the same, in which case text is inserted at\n// the given position. If the text is an empty string, text between start and\n// end is deleted. Index positions will be shifted to line up with character\n// boundaries. A \"changed\" event will be triggered.\n//\n// Previous selections are cleared. The cursor will be located at the end of the\n// replaced text. Scroll offsets will not be changed. A \"moved\" event will be\n// triggered.\n//\n// The effects of this function can be undone (and redone) by the user.\nfunc (t *TextArea) Replace(start, end int, text string) *TextArea {\n\tt.Select(start, end)\n\trow := t.selectionStart.row\n\tt.cursor.pos = t.replace(t.selectionStart.pos, t.cursor.pos, text, false)\n\tt.cursor.row = -1\n\tt.truncateLines(row - 1)\n\tt.findCursor(false, row)\n\tt.selectionStart = t.cursor\n\tif t.moved != nil {\n\t\tt.moved()\n\t}\n\t// The \"changed\" event will have been triggered by the \"replace\" function.\n\treturn t\n}\n\n// Select selects a section of the text. The start and end positions refer to\n// index positions within the entire text string (as a half-open interval). They\n// may be the same, in which case the cursor is placed at the given position.\n// Any previous selection is removed. Scroll offsets will be preserved.\n//\n// Index positions will be shifted to line up with character boundaries.\nfunc (t *TextArea) Select(start, end int) *TextArea {\n\toldFrom, oldTo := t.selectionStart, t.cursor\n\tdefer func() {\n\t\tif (oldFrom != t.selectionStart || oldTo != t.cursor) && t.moved != nil {\n\t\t\tt.moved()\n\t\t}\n\t}()\n\n\t// Clamp input values.\n\tif start < 0 {\n\t\tstart = 0\n\t}\n\tif start > t.length {\n\t\tstart = t.length\n\t}\n\tif end < 0 {\n\t\tend = 0\n\t}\n\tif end > t.length {\n\t\tend = t.length\n\t}\n\tif end < start {\n\t\tstart, end = end, start\n\t}\n\n\t// Find the cursor positions.\n\tvar row, index int\n\tt.cursor.row, t.cursor.pos = -1, [3]int{1, 0, -1}\n\tt.selectionStart = t.cursor\nRowLoop:\n\tfor {\n\t\tif row >= len(t.lineStarts) {\n\t\t\tt.extendLines(t.lastWidth, row)\n\t\t\tif row >= len(t.lineStarts) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t// Check the spans of this row.\n\t\tpos := t.lineStarts[row]\n\t\tvar (\n\t\t\tnext      [3]int\n\t\t\tlineIndex int\n\t\t)\n\t\tif row+1 < len(t.lineStarts) {\n\t\t\tnext = t.lineStarts[row+1]\n\t\t} else {\n\t\t\tnext = [3]int{1, 0, -1}\n\t\t}\n\t\tfor {\n\t\t\tif pos[0] == next[0] {\n\t\t\t\tif start >= index+lineIndex && start < index+lineIndex+next[1]-pos[1] ||\n\t\t\t\t\tend >= index+lineIndex && end < index+lineIndex+next[1]-pos[1] ||\n\t\t\t\t\tnext[0] == 1 && (start == t.length || end == t.length) { // Special case for the end of the text.\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tindex += lineIndex + next[1] - pos[1]\n\t\t\t\trow++\n\t\t\t\tcontinue RowLoop // Move on to the next row.\n\t\t\t} else {\n\t\t\t\tlength := t.spans[pos[0]].length\n\t\t\t\tif length < 0 {\n\t\t\t\t\tlength = -length\n\t\t\t\t}\n\t\t\t\tif start >= index+lineIndex && start < index+lineIndex+length-pos[1] ||\n\t\t\t\t\tend >= index+lineIndex && end < index+lineIndex+length-pos[1] ||\n\t\t\t\t\tnext[0] == 1 && (start == t.length || end == t.length) { // Special case for the end of the text.\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tlineIndex += length - pos[1]\n\t\t\t\tpos[0], pos[1] = t.spans[pos[0]].next, 0\n\t\t\t}\n\t\t}\n\n\t\t// One of the indices is in this row. Step through it.\n\t\tpos = t.lineStarts[row]\n\t\tendPos := pos\n\t\tvar (\n\t\t\tcluster, text string\n\t\t\tcolumn, width int\n\t\t)\n\t\tfor pos != next {\n\t\t\tif t.selectionStart.row < 0 && start <= index {\n\t\t\t\tt.selectionStart.row, t.selectionStart.column, t.selectionStart.actualColumn = row, column, column\n\t\t\t\tt.selectionStart.pos = pos\n\t\t\t}\n\t\t\tif t.cursor.row < 0 && end <= index {\n\t\t\t\tt.cursor.row, t.cursor.column, t.cursor.actualColumn = row, column, column\n\t\t\t\tt.cursor.pos = pos\n\t\t\t\tbreak RowLoop\n\t\t\t}\n\t\t\tcluster, text, _, width, pos, endPos = t.step(text, pos, endPos)\n\t\t\tindex += len(cluster)\n\t\t\tcolumn += width\n\t\t}\n\t\trow++\n\t}\n\n\tif t.cursor.row < 0 {\n\t\tt.findCursor(false, 0) // This only happens if we couldn't find the locations above.\n\t\tt.selectionStart = t.cursor\n\t}\n\n\treturn t\n}\n\n// SetWrap sets the flag that, if true, leads to lines that are longer than the\n// available width being wrapped onto the next line. If false, any characters\n// beyond the available width are not displayed.\nfunc (t *TextArea) SetWrap(wrap bool) *TextArea {\n\tif t.wrap != wrap {\n\t\tt.wrap = wrap\n\t\tt.reset()\n\t}\n\treturn t\n}\n\n// SetWordWrap sets the flag that causes lines that are longer than the\n// available width to be wrapped onto the next line at spaces or after\n// punctuation marks (according to [Unicode Standard Annex #14]). This flag is\n// ignored if the flag set with [TextArea.SetWrap] is false. The text area's\n// default is word-wrapping.\n//\n// [Unicode Standard Annex #14]: https://www.unicode.org/reports/tr14/\nfunc (t *TextArea) SetWordWrap(wrapOnWords bool) *TextArea {\n\tif t.wordWrap != wrapOnWords {\n\t\tt.wordWrap = wrapOnWords\n\t\tt.reset()\n\t}\n\treturn t\n}\n\n// SetPlaceholder sets the text to be displayed when the text area is empty.\nfunc (t *TextArea) SetPlaceholder(placeholder string) *TextArea {\n\tt.placeholder = placeholder\n\treturn t\n}\n\n// SetLabel sets the text to be displayed before the text area.\nfunc (t *TextArea) SetLabel(label string) *TextArea {\n\tt.label = label\n\treturn t\n}\n\n// GetLabel returns the text to be displayed before the text area.\nfunc (t *TextArea) GetLabel() string {\n\treturn t.label\n}\n\n// SetLabelWidth sets the screen width of the label. A value of 0 will cause the\n// primitive to use the width of the label string.\nfunc (t *TextArea) SetLabelWidth(width int) *TextArea {\n\tt.labelWidth = width\n\treturn t\n}\n\n// GetLabelWidth returns the screen width of the label.\nfunc (t *TextArea) GetLabelWidth() int {\n\treturn t.labelWidth\n}\n\n// SetSize sets the screen size of the input element of the text area. The input\n// element is always located next to the label which is always located in the\n// top left corner. If any of the values are 0 or larger than the available\n// space, the available space will be used.\nfunc (t *TextArea) SetSize(rows, columns int) *TextArea {\n\tt.width = columns\n\tt.height = rows\n\treturn t\n}\n\n// GetFieldWidth returns this primitive's field width.\nfunc (t *TextArea) GetFieldWidth() int {\n\treturn t.width\n}\n\n// GetFieldHeight returns this primitive's field height.\nfunc (t *TextArea) GetFieldHeight() int {\n\treturn t.height\n}\n\n// SetDisabled sets whether or not the item is disabled / read-only.\nfunc (t *TextArea) SetDisabled(disabled bool) FormItem {\n\tt.disabled = disabled\n\tif t.finished != nil {\n\t\tt.finished(-1)\n\t}\n\treturn t\n}\n\n// GetDisabled returns whether or not the item is disabled / read-only.\nfunc (t *TextArea) GetDisabled() bool {\n\treturn t.disabled\n}\n\n// SetMaxLength sets the maximum number of bytes allowed in the text area. A\n// value of 0 means there is no limit. If the text area currently contains more\n// bytes than this, it may violate this constraint.\nfunc (t *TextArea) SetMaxLength(maxLength int) *TextArea {\n\tt.maxLength = maxLength\n\treturn t\n}\n\n// setMinCursorPadding sets a minimum width to be reserved left and right of the\n// cursor. This is ignored if wrapping is enabled.\nfunc (t *TextArea) setMinCursorPadding(prefix, suffix int) *TextArea {\n\tt.minCursorPrefix = prefix\n\tt.minCursorSuffix = suffix\n\treturn t\n}\n\n// SetLabelStyle sets the style of the label.\nfunc (t *TextArea) SetLabelStyle(style tcell.Style) *TextArea {\n\tt.labelStyle = style\n\treturn t\n}\n\n// GetLabelStyle returns the style of the label.\nfunc (t *TextArea) GetLabelStyle() tcell.Style {\n\treturn t.labelStyle\n}\n\n// SetTextStyle sets the style of the text.\nfunc (t *TextArea) SetTextStyle(style tcell.Style) *TextArea {\n\tt.textStyle = style\n\treturn t\n}\n\n// GetTextStyle returns the style of the text.\nfunc (t *TextArea) GetTextStyle() tcell.Style {\n\treturn t.textStyle\n}\n\n// SetSelectedStyle sets the style of the selected text.\nfunc (t *TextArea) SetSelectedStyle(style tcell.Style) *TextArea {\n\tt.selectedStyle = style\n\treturn t\n}\n\n// SetPlaceholderStyle sets the style of the placeholder text.\nfunc (t *TextArea) SetPlaceholderStyle(style tcell.Style) *TextArea {\n\tt.placeholderStyle = style\n\treturn t\n}\n\n// GetPlaceholderStyle returns the style of the placeholder text.\nfunc (t *TextArea) GetPlaceholderStyle() tcell.Style {\n\treturn t.placeholderStyle\n}\n\n// GetOffset returns the text's offset, that is, the number of rows and columns\n// skipped during drawing at the top or on the left, respectively. Note that the\n// column offset is ignored if wrapping is enabled.\nfunc (t *TextArea) GetOffset() (row, column int) {\n\treturn t.rowOffset, t.columnOffset\n}\n\n// SetOffset sets the text's offset, that is, the number of rows and columns\n// skipped during drawing at the top or on the left, respectively. If wrapping\n// is enabled, the column offset is ignored. These values may get adjusted\n// automatically to ensure that some text is always visible.\nfunc (t *TextArea) SetOffset(row, column int) *TextArea {\n\tt.rowOffset, t.columnOffset = row, column\n\treturn t\n}\n\n// SetClipboard allows you to implement your own clipboard by providing a\n// function that is called when the user wishes to store text in the clipboard\n// (copyToClipboard) and a function that is called when the user wishes to\n// retrieve text from the clipboard (pasteFromClipboard).\n//\n// Providing nil values will cause the default clipboard implementation to be\n// used. Note that the default clipboard is local to this text area instance.\n// Copying text to other widgets will not work.\nfunc (t *TextArea) SetClipboard(copyToClipboard func(string), pasteFromClipboard func() string) *TextArea {\n\tt.copyToClipboard = copyToClipboard\n\tif t.copyToClipboard == nil {\n\t\tt.copyToClipboard = func(text string) {\n\t\t\tt.clipboard = text\n\t\t}\n\t}\n\n\tt.pasteFromClipboard = pasteFromClipboard\n\tif t.pasteFromClipboard == nil {\n\t\tt.pasteFromClipboard = func() string {\n\t\t\treturn t.clipboard\n\t\t}\n\t}\n\n\treturn t\n}\n\n// GetClipboardText returns the current text of the clipboard by calling the\n// pasteFromClipboard function set with [TextArea.SetClipboard].\nfunc (t *TextArea) GetClipboardText() string {\n\treturn t.pasteFromClipboard()\n}\n\n// SetChangedFunc sets a handler which is called whenever the text of the text\n// area has changed.\nfunc (t *TextArea) SetChangedFunc(handler func()) *TextArea {\n\tt.changed = handler\n\treturn t\n}\n\n// SetMovedFunc sets a handler which is called whenever the cursor position or\n// the text selection has changed.\nfunc (t *TextArea) SetMovedFunc(handler func()) *TextArea {\n\tt.moved = handler\n\treturn t\n}\n\n// SetFinishedFunc sets a callback invoked when the user leaves this form item.\nfunc (t *TextArea) SetFinishedFunc(handler func(key tcell.Key)) FormItem {\n\tt.finished = handler\n\treturn t\n}\n\n// Focus is called when this primitive receives focus.\nfunc (t *TextArea) Focus(delegate func(p Primitive)) {\n\t// If we're part of a form and this item is disabled, there's nothing the\n\t// user can do here so we're finished.\n\tif t.finished != nil && t.disabled {\n\t\tt.finished(-1)\n\t\treturn\n\t}\n\n\tt.Box.Focus(delegate)\n}\n\n// SetFormAttributes sets attributes shared by all form items.\nfunc (t *TextArea) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {\n\tt.labelWidth = labelWidth\n\tt.backgroundColor = bgColor\n\tt.labelStyle = t.labelStyle.Foreground(labelColor)\n\tt.textStyle = tcell.StyleDefault.Foreground(fieldTextColor).Background(fieldBgColor)\n\treturn t\n}\n\n// replace deletes a range of text and inserts the given text at that position.\n// If the resulting text would exceed the maximum length, the function does not\n// do anything. The function returns the end position of the deleted/inserted\n// range.\n//\n// The function can hang if \"deleteStart\" is located after \"deleteEnd\".\n//\n// Undo events are always generated unless continuation is true and text is\n// either appended to the end of a span or a span is shortened at the beginning\n// or the end (and nothing else).\n//\n// This function only modifies [TextArea.lineStarts] to update span references\n// but does not change it to reflect the new layout.\n//\n// A \"changed\" event will be triggered.\nfunc (t *TextArea) replace(deleteStart, deleteEnd [3]int, insert string, continuation bool) [3]int {\n\t// Maybe nothing needs to be done?\n\tif deleteStart == deleteEnd && insert == \"\" || t.maxLength > 0 && len(insert) > 0 && t.length+len(insert) >= t.maxLength {\n\t\treturn deleteEnd\n\t}\n\n\t// Notify at the end.\n\tif t.changed != nil {\n\t\tdefer t.changed()\n\t}\n\n\t// Handle a few cases where we don't put anything onto the undo stack for\n\t// increased efficiency.\n\tif continuation {\n\t\t// Same action as the one before. An undo item was already generated for\n\t\t// this block of (same) actions. We're also only changing one character.\n\t\tswitch {\n\t\tcase insert == \"\" && deleteStart[1] != 0 && deleteEnd[1] == 0:\n\t\t\t// Simple backspace. Just shorten this span.\n\t\t\tlength := t.spans[deleteStart[0]].length\n\t\t\tif length < 0 {\n\t\t\t\tt.length -= -length - deleteStart[1]\n\t\t\t\tlength = -deleteStart[1]\n\t\t\t} else {\n\t\t\t\tt.length -= length - deleteStart[1]\n\t\t\t\tlength = deleteStart[1]\n\t\t\t}\n\t\t\tt.spans[deleteStart[0]].length = length\n\t\t\treturn deleteEnd\n\t\tcase insert == \"\" && deleteStart[1] == 0 && deleteEnd[1] != 0:\n\t\t\t// Simple delete. Just clip the beginning of this span.\n\t\t\tt.spans[deleteEnd[0]].offset += deleteEnd[1]\n\t\t\tif t.spans[deleteEnd[0]].length < 0 {\n\t\t\t\tt.spans[deleteEnd[0]].length += deleteEnd[1]\n\t\t\t} else {\n\t\t\t\tt.spans[deleteEnd[0]].length -= deleteEnd[1]\n\t\t\t}\n\t\t\tt.length -= deleteEnd[1]\n\t\t\tdeleteEnd[1] = 0\n\t\t\treturn deleteEnd\n\t\tcase insert != \"\" && deleteStart == deleteEnd && deleteEnd[1] == 0:\n\t\t\tprevious := t.spans[deleteStart[0]].previous\n\t\t\tbufferSpan := t.spans[previous]\n\t\t\tif bufferSpan.length > 0 && bufferSpan.offset+bufferSpan.length == t.editText.Len() {\n\t\t\t\t// Typing individual characters. Simply extend the edit buffer.\n\t\t\t\tlength, _ := t.editText.WriteString(insert)\n\t\t\t\tt.spans[previous].length += length\n\t\t\t\tt.length += length\n\t\t\t\treturn deleteEnd\n\t\t\t}\n\t\t}\n\t}\n\n\t// All other cases generate an undo item.\n\tbefore := t.spans[deleteStart[0]].previous\n\tafter := deleteEnd[0]\n\tif deleteEnd[1] > 0 {\n\t\tafter = t.spans[deleteEnd[0]].next\n\t}\n\tt.undoStack = t.undoStack[:t.nextUndo]\n\tt.undoStack = append(t.undoStack, textAreaUndoItem{\n\t\tbefore:         len(t.spans),\n\t\tafter:          len(t.spans) + 1,\n\t\toriginalBefore: before,\n\t\toriginalAfter:  after,\n\t\tlength:         t.length,\n\t\tpos:            t.cursor.pos,\n\t\tcontinuation:   continuation,\n\t})\n\tt.spans = append(t.spans, t.spans[before])\n\tt.spans = append(t.spans, t.spans[after])\n\tt.nextUndo++\n\n\t// Adjust total text length by subtracting everything between \"before\" and\n\t// \"after\". Inserted spans will be added back.\n\tfor index := deleteStart[0]; index != after; index = t.spans[index].next {\n\t\tif t.spans[index].length < 0 {\n\t\t\tt.length += t.spans[index].length\n\t\t} else {\n\t\t\tt.length -= t.spans[index].length\n\t\t}\n\t}\n\tt.spans[before].next = after\n\tt.spans[after].previous = before\n\n\t// We go from left to right, connecting new spans as needed. We update\n\t// \"before\" as the span to connect new spans to.\n\n\t// If we start deleting in the middle of a span, connect a partial span.\n\tif deleteStart[1] != 0 {\n\t\tspan := textAreaSpan{\n\t\t\tprevious: before,\n\t\t\tnext:     after,\n\t\t\toffset:   t.spans[deleteStart[0]].offset,\n\t\t\tlength:   deleteStart[1],\n\t\t}\n\t\tif t.spans[deleteStart[0]].length < 0 {\n\t\t\tspan.length = -span.length\n\t\t}\n\t\tt.length += deleteStart[1] // This was previously subtracted.\n\t\tt.spans[before].next = len(t.spans)\n\t\tt.spans[after].previous = len(t.spans)\n\t\tbefore = len(t.spans)\n\t\tfor row, lineStart := range t.lineStarts { // Also redirect line starts until the end of this new span.\n\t\t\tif lineStart[0] == deleteStart[0] {\n\t\t\t\tif lineStart[1] >= deleteStart[1] {\n\t\t\t\t\tt.lineStarts = t.lineStarts[:row] // Everything else is unknown at this point.\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tt.lineStarts[row][0] = len(t.spans)\n\t\t\t}\n\t\t}\n\t\tt.spans = append(t.spans, span)\n\t}\n\n\t// If we insert text, connect a new span.\n\tif insert != \"\" {\n\t\tspan := textAreaSpan{\n\t\t\tprevious: before,\n\t\t\tnext:     after,\n\t\t\toffset:   t.editText.Len(),\n\t\t}\n\t\tspan.length, _ = t.editText.WriteString(insert)\n\t\tt.length += span.length\n\t\tt.spans[before].next = len(t.spans)\n\t\tt.spans[after].previous = len(t.spans)\n\t\tbefore = len(t.spans)\n\t\tt.spans = append(t.spans, span)\n\t}\n\n\t// If we stop deleting in the middle of a span, connect a partial span.\n\tif deleteEnd[1] != 0 {\n\t\tspan := textAreaSpan{\n\t\t\tprevious: before,\n\t\t\tnext:     after,\n\t\t\toffset:   t.spans[deleteEnd[0]].offset + deleteEnd[1],\n\t\t}\n\t\tlength := t.spans[deleteEnd[0]].length\n\t\tif length < 0 {\n\t\t\tspan.length = length + deleteEnd[1]\n\t\t\tt.length -= span.length // This was previously subtracted.\n\t\t} else {\n\t\t\tspan.length = length - deleteEnd[1]\n\t\t\tt.length += span.length // This was previously subtracted.\n\t\t}\n\t\tt.spans[before].next = len(t.spans)\n\t\tt.spans[after].previous = len(t.spans)\n\t\tdeleteEnd[0], deleteEnd[1] = len(t.spans), 0\n\t\tt.spans = append(t.spans, span)\n\t}\n\n\treturn deleteEnd\n}\n\n// Draw draws this primitive onto the screen.\nfunc (t *TextArea) Draw(screen tcell.Screen) {\n\tt.Box.DrawForSubclass(screen, t)\n\n\t// Prepare\n\tx, y, width, height := t.GetInnerRect()\n\tif width <= 0 || height <= 0 {\n\t\treturn // We have no space for anything.\n\t}\n\tcolumnOffset := t.columnOffset\n\tif t.wrap {\n\t\tcolumnOffset = 0\n\t}\n\n\t// Draw label.\n\t_, labelBg, _ := t.labelStyle.Decompose()\n\tif t.labelWidth > 0 {\n\t\tlabelWidth := t.labelWidth\n\t\tif labelWidth > width {\n\t\t\tlabelWidth = width\n\t\t}\n\t\tprintWithStyle(screen, t.label, x, y, 0, labelWidth, AlignLeft, t.labelStyle, labelBg == tcell.ColorDefault)\n\t\tx += labelWidth\n\t\twidth -= labelWidth\n\t} else {\n\t\t_, _, drawnWidth := printWithStyle(screen, t.label, x, y, 0, width, AlignLeft, t.labelStyle, labelBg == tcell.ColorDefault)\n\t\tx += drawnWidth\n\t\twidth -= drawnWidth\n\t}\n\n\t// What's the space for the input element?\n\tif t.width > 0 && t.width < width {\n\t\twidth = t.width\n\t}\n\tif t.height > 0 && t.height < height {\n\t\theight = t.height\n\t}\n\tif width <= 0 {\n\t\treturn // No space left for the text area.\n\t}\n\n\t// Draw the input element if necessary.\n\t_, bg, _ := t.textStyle.Decompose()\n\tif t.disabled {\n\t\tbg = t.backgroundColor\n\t}\n\tif bg != t.backgroundColor {\n\t\tfor row := 0; row < height; row++ {\n\t\t\tfor column := 0; column < width; column++ {\n\t\t\t\tscreen.SetContent(x+column, y+row, ' ', nil, t.textStyle)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Show/hide the cursor at the end.\n\tdefer func() {\n\t\tif t.HasFocus() {\n\t\t\trow, column := t.cursor.row, t.cursor.actualColumn\n\t\t\tif t.length > 0 && t.wrap && column >= t.lastWidth { // This happens when a row has text all the way until the end, pushing the cursor outside the viewport.\n\t\t\t\trow++\n\t\t\t\tcolumn = 0\n\t\t\t}\n\t\t\tif row >= 0 &&\n\t\t\t\trow-t.rowOffset >= 0 && row-t.rowOffset < height &&\n\t\t\t\tcolumn-columnOffset >= 0 && column-columnOffset < width {\n\t\t\t\tscreen.ShowCursor(x+column-columnOffset, y+row-t.rowOffset)\n\t\t\t} else {\n\t\t\t\tscreen.HideCursor()\n\t\t\t}\n\t\t}\n\t}()\n\n\t// No text, show placeholder.\n\tif t.length == 0 {\n\t\tt.lastHeight, t.lastWidth = height, width\n\t\tt.cursor.row, t.cursor.column, t.cursor.actualColumn, t.cursor.pos = 0, 0, 0, [3]int{1, 0, -1}\n\t\tt.rowOffset, t.columnOffset = 0, 0\n\t\tif len(t.placeholder) > 0 {\n\t\t\tt.drawPlaceholder(screen, x, y, width, height)\n\t\t}\n\t\treturn // We're done already.\n\t}\n\n\t// Make sure the visible lines are broken over.\n\tfirstDrawing := t.lastWidth == 0\n\tif t.lastWidth != width && t.lineStarts != nil {\n\t\tt.reset()\n\t}\n\tt.lastHeight, t.lastWidth = height, width\n\tt.extendLines(width, t.rowOffset+height)\n\tif len(t.lineStarts) <= t.rowOffset {\n\t\treturn // It's scrolled out of view.\n\t}\n\n\t// If the cursor position is unknown, find it. This usually only happens\n\t// before the screen is drawn for the first time.\n\tif t.cursor.row < 0 {\n\t\tt.findCursor(true, 0)\n\t\tif t.selectionStart.row < 0 {\n\t\t\tt.selectionStart = t.cursor\n\t\t}\n\t\tif firstDrawing && t.moved != nil {\n\t\t\tt.moved()\n\t\t}\n\t}\n\n\t// Print the text.\n\tvar cluster, text string\n\tline := t.rowOffset\n\tpos := t.lineStarts[line]\n\tendPos := pos\n\tposX, posY := 0, 0\n\tfor pos[0] != 1 {\n\t\tvar clusterWidth int\n\t\tcluster, text, _, clusterWidth, pos, endPos = t.step(text, pos, endPos)\n\n\t\t// Prepare drawing.\n\t\trunes := []rune(cluster)\n\t\tstyle := t.selectedStyle\n\t\tfromRow, fromColumn := t.cursor.row, t.cursor.actualColumn\n\t\ttoRow, toColumn := t.selectionStart.row, t.selectionStart.actualColumn\n\t\tif fromRow > toRow || fromRow == toRow && fromColumn > toColumn {\n\t\t\tfromRow, fromColumn, toRow, toColumn = toRow, toColumn, fromRow, fromColumn\n\t\t}\n\t\tif toRow < line ||\n\t\t\ttoRow == line && toColumn <= posX ||\n\t\t\tfromRow > line ||\n\t\t\tfromRow == line && fromColumn > posX {\n\t\t\tstyle = t.textStyle\n\t\t\tif t.disabled {\n\t\t\t\tstyle = style.Background(t.backgroundColor)\n\t\t\t}\n\t\t}\n\n\t\t// Selected tabs are a bit special.\n\t\tif cluster == \"\\t\" && style == t.selectedStyle {\n\t\t\tfor colX := 0; colX < clusterWidth && posX+colX-columnOffset < width; colX++ {\n\t\t\t\tscreen.SetContent(x+posX+colX-columnOffset, y+posY, ' ', nil, style)\n\t\t\t}\n\t\t}\n\n\t\t// Draw character.\n\t\tif posX+clusterWidth-columnOffset <= width && posX-columnOffset >= 0 && clusterWidth > 0 {\n\t\t\tscreen.SetContent(x+posX-columnOffset, y+posY, runes[0], runes[1:], style)\n\t\t}\n\n\t\t// Advance.\n\t\tposX += clusterWidth\n\t\tif line+1 < len(t.lineStarts) && t.lineStarts[line+1] == pos {\n\t\t\t// We must break over.\n\t\t\tposY++\n\t\t\tif posY >= height {\n\t\t\t\tbreak // Done.\n\t\t\t}\n\t\t\tposX = 0\n\t\t\tline++\n\t\t}\n\t}\n}\n\n// drawPlaceholder draws the placeholder text into the given rectangle. It does\n// not do anything if the text area already contains text or if there is no\n// placeholder text.\nfunc (t *TextArea) drawPlaceholder(screen tcell.Screen, x, y, width, height int) {\n\t// We use a TextView to draw the placeholder. It will take care of word\n\t// wrapping etc.\n\ttextView := NewTextView().\n\t\tSetText(t.placeholder).\n\t\tSetTextStyle(t.placeholderStyle)\n\ttextView.SetRect(x, y, width, height)\n\ttextView.Draw(screen)\n}\n\n// reset resets many of the local variables of the text area because they cannot\n// be used anymore and must be recalculated, typically after the text area's\n// size has changed.\nfunc (t *TextArea) reset() {\n\tt.truncateLines(0)\n\tif t.wrap {\n\t\tt.cursor.row = -1\n\t\tt.selectionStart.row = -1\n\t}\n\tt.widestLine = 0\n}\n\n// extendLines traverses the current text and extends [TextArea.lineStarts] such\n// that it describes at least maxLines+1 lines (or less if the text is shorter).\n// Text is laid out for the given width while respecting the wrapping settings.\n// It is assumed that if [TextArea.lineStarts] already has entries, they obey\n// the same rules.\n//\n// If width is 0, nothing happens.\nfunc (t *TextArea) extendLines(width, maxLines int) {\n\tif width <= 0 {\n\t\treturn\n\t}\n\n\t// Start with the first span.\n\tif len(t.lineStarts) == 0 {\n\t\tif len(t.spans) > 2 {\n\t\t\tt.lineStarts = append(t.lineStarts, [3]int{t.spans[0].next, 0, -1})\n\t\t} else {\n\t\t\treturn // No text.\n\t\t}\n\t}\n\n\t// Determine starting positions and starting spans.\n\tpos := t.lineStarts[len(t.lineStarts)-1] // The starting position is the last known line.\n\tendPos := pos\n\tvar (\n\t\tcluster, text                       string\n\t\tlineWidth, clusterWidth, boundaries int\n\t\tlastGraphemeBreak, lastLineBreak    [3]int\n\t\twidthSinceLineBreak                 int\n\t)\n\tfor pos[0] != 1 {\n\t\t// Get the next grapheme cluster.\n\t\tcluster, text, boundaries, clusterWidth, pos, endPos = t.step(text, pos, endPos)\n\t\tlineWidth += clusterWidth\n\t\twidthSinceLineBreak += clusterWidth\n\n\t\t// Any line breaks?\n\t\tif !t.wrap || lineWidth <= width {\n\t\t\tif boundaries&uniseg.MaskLine == uniseg.LineMustBreak && (len(text) > 0 || uniseg.HasTrailingLineBreakInString(cluster)) {\n\t\t\t\t// We must break over.\n\t\t\t\tt.lineStarts = append(t.lineStarts, pos)\n\t\t\t\tif lineWidth > t.widestLine {\n\t\t\t\t\tt.widestLine = lineWidth\n\t\t\t\t}\n\t\t\t\tlineWidth = 0\n\t\t\t\tlastGraphemeBreak = [3]int{}\n\t\t\t\tlastLineBreak = [3]int{}\n\t\t\t\twidthSinceLineBreak = 0\n\t\t\t\tif len(t.lineStarts) > maxLines {\n\t\t\t\t\tbreak // We have enough lines, we can stop.\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else { // t.wrap && lineWidth > width\n\t\t\tif !t.wordWrap || lastLineBreak == [3]int{} {\n\t\t\t\tif lastGraphemeBreak != [3]int{} { // We have at least one character on each line.\n\t\t\t\t\t// Break after last grapheme.\n\t\t\t\t\tt.lineStarts = append(t.lineStarts, lastGraphemeBreak)\n\t\t\t\t\tif lineWidth > t.widestLine {\n\t\t\t\t\t\tt.widestLine = lineWidth\n\t\t\t\t\t}\n\t\t\t\t\tlineWidth = clusterWidth\n\t\t\t\t\tlastLineBreak = [3]int{}\n\t\t\t\t}\n\t\t\t} else { // t.wordWrap && lastLineBreak != [3]int{}\n\t\t\t\t// Break after last line break opportunity.\n\t\t\t\tt.lineStarts = append(t.lineStarts, lastLineBreak)\n\t\t\t\tif lineWidth > t.widestLine {\n\t\t\t\t\tt.widestLine = lineWidth\n\t\t\t\t}\n\t\t\t\tlineWidth = widthSinceLineBreak\n\t\t\t\tlastLineBreak = [3]int{}\n\t\t\t}\n\t\t}\n\n\t\t// Analyze break opportunities.\n\t\tif boundaries&uniseg.MaskLine == uniseg.LineCanBreak {\n\t\t\tlastLineBreak = pos\n\t\t\twidthSinceLineBreak = 0\n\t\t}\n\t\tlastGraphemeBreak = pos\n\n\t\t// Can we stop?\n\t\tif len(t.lineStarts) > maxLines {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif lineWidth > t.widestLine {\n\t\tt.widestLine = lineWidth\n\t}\n}\n\n// truncateLines truncates the trailing lines of the [TextArea.lineStarts]\n// slice such that len(lineStarts) <= fromLine. If fromLine is negative, a value\n// of 0 is assumed. If it is greater than the length of lineStarts, nothing\n// happens.\nfunc (t *TextArea) truncateLines(fromLine int) {\n\tif fromLine < 0 {\n\t\tfromLine = 0\n\t}\n\tif fromLine < len(t.lineStarts) {\n\t\tt.lineStarts = t.lineStarts[:fromLine]\n\t}\n}\n\n// findCursor determines the cursor position if its \"row\" value is < 0\n// (=unknown) but only its span position (\"pos\" value) is known. If the cursor\n// position is already known (row >= 0), it can also be used to modify row and\n// column offsets such that the cursor is visible during the next call to\n// [TextArea.Draw], by setting \"clamp\" to true.\n//\n// To determine the cursor position, \"startRow\" helps reduce processing time by\n// indicating the lowest row in which searching should start. Set this to 0 if\n// you don't have any information where the cursor might be (but know that this\n// is expensive for long texts).\n//\n// The cursor's desired column will be set to its actual column.\nfunc (t *TextArea) findCursor(clamp bool, startRow int) {\n\tdefer func() {\n\t\tt.cursor.column = t.cursor.actualColumn\n\t}()\n\n\tif !clamp && t.cursor.row >= 0 || t.lastWidth <= 0 {\n\t\treturn // Nothing to do.\n\t}\n\n\t// Clamp to viewport.\n\tif clamp && t.cursor.row >= 0 {\n\t\tcursorRow := t.cursor.row\n\t\tif t.wrap && t.cursor.actualColumn >= t.lastWidth {\n\t\t\tcursorRow++ // A row can push the cursor just outside the viewport. It will wrap onto the next line.\n\t\t}\n\t\tif cursorRow < t.rowOffset {\n\t\t\t// We're above the viewport.\n\t\t\tt.rowOffset = cursorRow\n\t\t} else if cursorRow >= t.rowOffset+t.lastHeight {\n\t\t\t// We're below the viewport.\n\t\t\tt.rowOffset = cursorRow - t.lastHeight + 1\n\t\t\tif t.rowOffset >= len(t.lineStarts) {\n\t\t\t\tt.extendLines(t.lastWidth, t.rowOffset)\n\t\t\t\tif t.rowOffset >= len(t.lineStarts) {\n\t\t\t\t\tt.rowOffset = len(t.lineStarts) - 1\n\t\t\t\t\tif t.rowOffset < 0 {\n\t\t\t\t\t\tt.rowOffset = 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !t.wrap {\n\t\t\tif t.cursor.actualColumn < t.columnOffset+t.minCursorPrefix {\n\t\t\t\t// We're left of the viewport.\n\t\t\t\tt.columnOffset = t.cursor.actualColumn - t.minCursorPrefix\n\t\t\t\tif t.columnOffset < 0 {\n\t\t\t\t\tt.columnOffset = 0\n\t\t\t\t}\n\t\t\t} else if t.cursor.actualColumn >= t.columnOffset+t.lastWidth-t.minCursorSuffix {\n\t\t\t\t// We're right of the viewport.\n\t\t\t\tt.columnOffset = t.cursor.actualColumn - t.lastWidth + t.minCursorSuffix\n\t\t\t\tif t.columnOffset >= t.widestLine {\n\t\t\t\t\tt.columnOffset = t.widestLine - 1\n\t\t\t\t\tif t.columnOffset < 0 {\n\t\t\t\t\t\tt.columnOffset = 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\t// The screen position of the cursor is unknown. Find it. This can be\n\t// expensive. First, find the row.\n\trow := startRow\n\tif row < 0 {\n\t\trow = 0\n\t}\nRowLoop:\n\tfor {\n\t\t// Examine the current row.\n\t\tif row+1 >= len(t.lineStarts) {\n\t\t\tt.extendLines(t.lastWidth, row+1)\n\t\t}\n\t\tif row >= len(t.lineStarts) {\n\t\t\tt.cursor.row, t.cursor.actualColumn, t.cursor.pos = row, 0, [3]int{1, 0, -1}\n\t\t\tbreak // It's the end of the text.\n\t\t}\n\n\t\t// Check this row's spans to see if the cursor is in this row.\n\t\tpos := t.lineStarts[row]\n\t\tfor pos[0] != 1 {\n\t\t\tif row+1 >= len(t.lineStarts) {\n\t\t\t\tbreak // It's the last row so the cursor must be in this row.\n\t\t\t}\n\t\t\tif t.cursor.pos[0] == pos[0] {\n\t\t\t\t// The cursor is in this span.\n\t\t\t\tif t.lineStarts[row+1][0] == pos[0] {\n\t\t\t\t\t// The next row starts with the same span.\n\t\t\t\t\tif t.cursor.pos[1] >= t.lineStarts[row+1][1] {\n\t\t\t\t\t\t// The cursor is not in this row.\n\t\t\t\t\t\trow++\n\t\t\t\t\t\tcontinue RowLoop\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// The cursor is in this row.\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// The next row starts with a different span. The cursor\n\t\t\t\t\t// must be in this row.\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// The cursor is in a different span.\n\t\t\t\tif t.lineStarts[row+1][0] == pos[0] {\n\t\t\t\t\t// The next row starts with the same span. This row is\n\t\t\t\t\t// irrelevant.\n\t\t\t\t\trow++\n\t\t\t\t\tcontinue RowLoop\n\t\t\t\t} else {\n\t\t\t\t\t// The next row starts with a different span. Move towards it.\n\t\t\t\t\tpos = [3]int{t.spans[pos[0]].next, 0, -1}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Try to find the screen position in this row.\n\t\tpos = t.lineStarts[row]\n\t\tendPos := pos\n\t\tcolumn := 0\n\t\tvar text string\n\t\tfor {\n\t\t\tif pos[0] == 1 || t.cursor.pos[0] == pos[0] && t.cursor.pos[1] == pos[1] {\n\t\t\t\t// We found the position. We're done.\n\t\t\t\tt.cursor.row, t.cursor.actualColumn, t.cursor.pos = row, column, pos\n\t\t\t\tbreak RowLoop\n\t\t\t}\n\t\t\tvar clusterWidth int\n\t\t\t_, text, _, clusterWidth, pos, endPos = t.step(text, pos, endPos)\n\t\t\tif row+1 < len(t.lineStarts) && t.lineStarts[row+1] == pos {\n\t\t\t\t// We reached the end of the line. Go to the next one.\n\t\t\t\trow++\n\t\t\t\tcontinue RowLoop\n\t\t\t}\n\t\t\tcolumn += clusterWidth\n\t\t}\n\t}\n\n\tif clamp && t.cursor.row >= 0 {\n\t\t// We know the position now. Adapt offsets.\n\t\tt.findCursor(true, startRow)\n\t}\n}\n\n// setTransform sets the transform function to be used when drawing the text.\n// This function is called for each grapheme cluster and can be used to modify\n// the cluster, the cluster's screen width, and the cluster's boundaries. The\n// function is called with the original cluster, the rest of the text, the\n// original cluster's width, and the original cluster's boundaries. The function\n// must return the new cluster, the new width, and the new boundaries. This only\n// affects the drawing of the text, not the text content itself. The boundaries\n// values correspond to the values returned by\n// [github.com/rivo/uniseg.StepString].\nfunc (t *TextArea) setTransform(transform func(cluster, rest string, boundaries int) (newCluster string, newBoundaries int)) {\n\tt.transform = transform\n}\n\n// step is similar to [github.com/rivo/uniseg.StepString] but it iterates over\n// the piece chain, starting with \"pos\", a span position plus state (which may\n// be -1 for the start of the text). The returned \"boundaries\" value is the same\n// value returned by [github.com/rivo/uniseg.StepString], \"width\" is the screen\n// width of the grapheme. The \"pos\" and \"endPos\" positions refer to the start\n// and the end of the \"text\" string, respectively. For the first call, text may\n// be empty and pos/endPos may be the same. For consecutive calls, provide\n// \"rest\" as the text and \"newPos\" and \"newEndPos\" as the new positions/states.\n// An empty \"rest\" string indicates the end of the text. The \"endPos\" state is\n// irrelevant.\nfunc (t *TextArea) step(text string, pos, endPos [3]int) (cluster, rest string, boundaries, width int, newPos, newEndPos [3]int) {\n\tif pos[0] == 1 {\n\t\treturn // We're already past the end.\n\t}\n\n\t// We want to make sure we have a text at least the size of a grapheme\n\t// cluster.\n\tspan := t.spans[pos[0]]\n\tif len(text) < maxGraphemeClusterSize &&\n\t\t(span.length < 0 && -span.length-pos[1] >= maxGraphemeClusterSize ||\n\t\t\tspan.length > 0 && t.spans[pos[0]].length-pos[1] >= maxGraphemeClusterSize) {\n\t\t// We can use a substring of one span.\n\t\tif span.length < 0 {\n\t\t\ttext = t.initialText[span.offset+pos[1] : span.offset-span.length]\n\t\t} else {\n\t\t\ttext = t.editText.String()[span.offset+pos[1] : span.offset+span.length]\n\t\t}\n\t\tendPos = [3]int{span.next, 0, -1}\n\t} else {\n\t\t// We have to compose the text from multiple spans.\n\t\tfor len(text) < maxGraphemeClusterSize && endPos[0] != 1 {\n\t\t\tendSpan := t.spans[endPos[0]]\n\t\t\tvar moreText string\n\t\t\tif endSpan.length < 0 {\n\t\t\t\tmoreText = t.initialText[endSpan.offset+endPos[1] : endSpan.offset-endSpan.length]\n\t\t\t} else {\n\t\t\t\tmoreText = t.editText.String()[endSpan.offset+endPos[1] : endSpan.offset+endSpan.length]\n\t\t\t}\n\t\t\tif len(moreText) > maxGraphemeClusterSize {\n\t\t\t\tmoreText = moreText[:maxGraphemeClusterSize]\n\t\t\t}\n\t\t\ttext += moreText\n\t\t\tendPos[1] += len(moreText)\n\t\t\tif endPos[1] >= endSpan.length {\n\t\t\t\tendPos[0], endPos[1] = endSpan.next, 0\n\t\t\t}\n\t\t}\n\t}\n\n\t// Run the grapheme cluster iterator.\n\tcluster, text, boundaries, pos[2] = uniseg.StepString(text, pos[2])\n\tpos[1] += len(cluster)\n\tfor pos[0] != 1 && (span.length < 0 && pos[1] >= -span.length || span.length >= 0 && pos[1] >= span.length) {\n\t\tpos[0] = span.next\n\t\tif span.length < 0 {\n\t\t\tpos[1] += span.length\n\t\t} else {\n\t\t\tpos[1] -= span.length\n\t\t}\n\t\tspan = t.spans[pos[0]]\n\t}\n\n\tif t.transform != nil {\n\t\tcluster, boundaries = t.transform(cluster, text, boundaries)\n\t}\n\n\tif cluster == \"\\t\" {\n\t\twidth = TabSize\n\t} else {\n\t\twidth = boundaries >> uniseg.ShiftWidth\n\t}\n\n\treturn cluster, text, boundaries, width, pos, endPos\n}\n\n// moveCursor sets the cursor's screen position and span position for the given\n// row and column which are screen space coordinates relative to the top-left\n// corner of the text area's full text (visible or not). The column value may be\n// negative, in which case, the cursor will be placed at the end of the line.\n// The cursor's actual position will be aligned with a grapheme cluster\n// boundary. The next call to [TextArea.Draw] will attempt to keep the cursor in\n// the viewport.\nfunc (t *TextArea) moveCursor(row, column int) {\n\t// Are we within the range of rows?\n\tif len(t.lineStarts) <= row {\n\t\t// No. Extent the line buffer.\n\t\tt.extendLines(t.lastWidth, row)\n\t}\n\tif len(t.lineStarts) == 0 {\n\t\treturn // No lines. Nothing to do.\n\t}\n\tif row < 0 {\n\t\t// We're at the start of the text.\n\t\trow = 0\n\t\tcolumn = 0\n\t} else if row >= len(t.lineStarts) {\n\t\t// We're already past the end.\n\t\trow = len(t.lineStarts) - 1\n\t\tcolumn = -1\n\t}\n\n\t// Iterate through this row until we find the position.\n\tt.cursor.row, t.cursor.actualColumn = row, 0\n\tif t.wrap {\n\t\tt.cursor.actualColumn = 0\n\t}\n\tpos := t.lineStarts[row]\n\tendPos := pos\n\tvar text string\n\tfor pos[0] != 1 {\n\t\tvar clusterWidth int\n\t\toldPos := pos // We may have to revert to this position.\n\t\t_, text, _, clusterWidth, pos, endPos = t.step(text, pos, endPos)\n\t\tif len(t.lineStarts) > row+1 && pos == t.lineStarts[row+1] || // We've reached the end of the line.\n\t\t\tcolumn >= 0 && t.cursor.actualColumn+clusterWidth > column { // We're past the requested column.\n\t\t\tpos = oldPos\n\t\t\tbreak\n\t\t}\n\t\tt.cursor.actualColumn += clusterWidth\n\t}\n\n\tif column < 0 {\n\t\tt.cursor.column = t.cursor.actualColumn\n\t} else {\n\t\tt.cursor.column = column\n\t}\n\tt.cursor.pos = pos\n\tt.findCursor(true, row)\n}\n\n// moveWordRight moves the cursor to the end of the current or next word. If\n// after is set to true, the cursor will be placed after the word. If false, the\n// cursor will be placed on the last character of the word. If clamp is set to\n// true, the cursor will be visible during the next call to [TextArea.Draw].\nfunc (t *TextArea) moveWordRight(after, clamp bool) {\n\t// Because we rely on clampToCursor to calculate the new screen position,\n\t// this is an expensive operation for large texts.\n\tpos := t.cursor.pos\n\tendPos := pos\n\tvar (\n\t\tcluster, text string\n\t\tinWord        bool\n\t)\n\tfor pos[0] != 0 {\n\t\tvar boundaries int\n\t\toldPos := pos\n\t\tcluster, text, boundaries, _, pos, endPos = t.step(text, pos, endPos)\n\t\tif oldPos == t.cursor.pos {\n\t\t\tcontinue // Skip the first character.\n\t\t}\n\t\tfirstRune, _ := utf8.DecodeRuneInString(cluster)\n\t\tif !unicode.IsSpace(firstRune) && !unicode.IsPunct(firstRune) {\n\t\t\tinWord = true\n\t\t}\n\t\tif inWord && boundaries&uniseg.MaskWord != 0 {\n\t\t\tif !after {\n\t\t\t\tpos = oldPos\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tstartRow := t.cursor.row\n\tt.cursor.row, t.cursor.column, t.cursor.actualColumn = -1, 0, 0\n\tt.cursor.pos = pos\n\tt.findCursor(clamp, startRow)\n}\n\n// moveWordLeft moves the cursor to the beginning of the current or previous\n// word. If clamp is true, the cursor will be visible during the next call to\n// [TextArea.Draw].\nfunc (t *TextArea) moveWordLeft(clamp bool) {\n\t// We go back row by row, trying to find the last word boundary before the\n\t// cursor.\n\trow := t.cursor.row\n\tif row+1 < len(t.lineStarts) {\n\t\tt.extendLines(t.lastWidth, row+1)\n\t}\n\tif row >= len(t.lineStarts) {\n\t\trow = len(t.lineStarts) - 1\n\t}\n\tfor row >= 0 {\n\t\tpos := t.lineStarts[row]\n\t\tendPos := pos\n\t\tvar lastWordBoundary [3]int\n\t\tvar (\n\t\t\tcluster, text string\n\t\t\tinWord        bool\n\t\t\tboundaries    int\n\t\t)\n\t\tfor pos[0] != 1 && pos != t.cursor.pos {\n\t\t\toldBoundaries := boundaries\n\t\t\toldPos := pos\n\t\t\tcluster, text, boundaries, _, pos, endPos = t.step(text, pos, endPos)\n\t\t\tfirstRune, _ := utf8.DecodeRuneInString(cluster)\n\t\t\twordRune := !unicode.IsSpace(firstRune) && !unicode.IsPunct(firstRune)\n\t\t\tif oldBoundaries&uniseg.MaskWord != 0 {\n\t\t\t\tif pos != t.cursor.pos && !inWord && wordRune {\n\t\t\t\t\t// A boundary transitioning from a space/punctuation word to\n\t\t\t\t\t// a letter word.\n\t\t\t\t\tlastWordBoundary = oldPos\n\t\t\t\t}\n\t\t\t\tinWord = false\n\t\t\t}\n\t\t\tif wordRune {\n\t\t\t\tinWord = true\n\t\t\t}\n\t\t}\n\t\tif lastWordBoundary[0] != 0 {\n\t\t\t// We found something.\n\t\t\tt.cursor.pos = lastWordBoundary\n\t\t\tbreak\n\t\t}\n\t\trow--\n\t}\n\tif row < 0 {\n\t\t// We didn't find anything. We're at the start of the text.\n\t\tt.cursor.pos = [3]int{t.spans[0].next, 0, -1}\n\t\trow = 0\n\t}\n\tt.cursor.row, t.cursor.column, t.cursor.actualColumn = -1, 0, 0\n\tt.findCursor(clamp, row)\n}\n\n// deleteLine deletes all characters between the last newline before the cursor\n// and the next newline after the cursor (inclusive).\nfunc (t *TextArea) deleteLine() {\n\t// We go back row by row, trying to find the last mandatory line break\n\t// before the cursor.\n\tstartRow := t.cursor.row\n\tif t.cursor.actualColumn == 0 && t.cursor.pos[0] == 1 {\n\t\tstartRow-- // If we're at the very end, delete the row before.\n\t}\n\tif startRow+1 < len(t.lineStarts) {\n\t\tt.extendLines(t.lastWidth, startRow+1)\n\t}\n\tif len(t.lineStarts) == 0 {\n\t\treturn // Nothing to delete.\n\t}\n\tif startRow >= len(t.lineStarts) {\n\t\tstartRow = len(t.lineStarts) - 1\n\t}\n\tfor startRow >= 0 {\n\t\t// What's the last rune before the start of the line?\n\t\tpos := t.lineStarts[startRow]\n\t\tspan := t.spans[pos[0]]\n\t\tvar text string\n\t\tif pos[1] > 0 {\n\t\t\t// Extract text from this span.\n\t\t\tif span.length < 0 {\n\t\t\t\ttext = t.initialText\n\t\t\t} else {\n\t\t\t\ttext = t.editText.String()\n\t\t\t}\n\t\t\ttext = text[:span.offset+pos[1]]\n\t\t} else {\n\t\t\t// Extract text from the previous span.\n\t\t\tif span.previous != 0 {\n\t\t\t\tspan = t.spans[span.previous]\n\t\t\t\tif span.length < 0 {\n\t\t\t\t\ttext = t.initialText[:span.offset-span.length]\n\t\t\t\t} else {\n\t\t\t\t\ttext = t.editText.String()[:span.offset+span.length]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif uniseg.HasTrailingLineBreakInString(text) {\n\t\t\t// The row before this one ends with a mandatory line break. This is\n\t\t\t// the first line we will delete.\n\t\t\tbreak\n\t\t}\n\t\tstartRow--\n\t}\n\tif startRow < 0 {\n\t\t// We didn't find anything. It'll be the first line.\n\t\tstartRow = 0\n\t}\n\n\t// Find the next line break after the cursor.\n\tpos := t.cursor.pos\n\tendPos := pos\n\tvar cluster, text string\n\tfor pos[0] != 1 {\n\t\tcluster, text, _, _, pos, endPos = t.step(text, pos, endPos)\n\t\tif uniseg.HasTrailingLineBreakInString(cluster) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// Delete the text.\n\tt.cursor.pos = t.replace(t.lineStarts[startRow], pos, \"\", false)\n\tt.cursor.row = -1\n\tt.truncateLines(startRow)\n\tt.findCursor(true, startRow)\n}\n\n// getSelection returns the current selection as span locations where the first\n// returned location is always before or the same as the second returned\n// location. This assumes that the cursor and selection positions are known. The\n// third return value is the starting row of the selection.\nfunc (t *TextArea) getSelection() ([3]int, [3]int, int) {\n\tfrom := t.selectionStart.pos\n\tto := t.cursor.pos\n\trow := t.selectionStart.row\n\tif t.cursor.row < t.selectionStart.row ||\n\t\t(t.cursor.row == t.selectionStart.row && t.cursor.actualColumn < t.selectionStart.actualColumn) {\n\t\tfrom, to = to, from\n\t\trow = t.cursor.row\n\t}\n\treturn from, to, row\n}\n\n// getSelectedText returns the text of the current selection.\nfunc (t *TextArea) getSelectedText() string {\n\tvar text strings.Builder\n\n\tfrom, to, _ := t.getSelection()\n\tfor from[0] != to[0] {\n\t\tspan := t.spans[from[0]]\n\t\tif span.length < 0 {\n\t\t\ttext.WriteString(t.initialText[span.offset+from[1] : span.offset-span.length])\n\t\t} else {\n\t\t\ttext.WriteString(t.editText.String()[span.offset+from[1] : span.offset+span.length])\n\t\t}\n\t\tfrom[0], from[1] = span.next, 0\n\t}\n\tif from[0] != 1 && from[1] < to[1] {\n\t\tspan := t.spans[from[0]]\n\t\tif span.length < 0 {\n\t\t\ttext.WriteString(t.initialText[span.offset+from[1] : span.offset+to[1]])\n\t\t} else {\n\t\t\ttext.WriteString(t.editText.String()[span.offset+from[1] : span.offset+to[1]])\n\t\t}\n\t}\n\n\treturn text.String()\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (t *TextArea) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tif t.disabled {\n\t\t\treturn\n\t\t}\n\n\t\t// All actions except a few specific ones are \"other\" actions.\n\t\tnewLastAction := taActionOther\n\t\tdefer func() {\n\t\t\tt.lastAction = newLastAction\n\t\t}()\n\n\t\t// Trigger a \"moved\" event if requested.\n\t\tif t.moved != nil {\n\t\t\tselectionStart, cursor := t.selectionStart, t.cursor\n\t\t\tdefer func() {\n\t\t\t\tif selectionStart != t.selectionStart || cursor != t.cursor {\n\t\t\t\t\tt.moved()\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\n\t\t// Process the different key events.\n\t\tswitch key := event.Key(); key {\n\t\tcase tcell.KeyLeft: // Move one grapheme cluster to the left.\n\t\t\tif event.Modifiers()&tcell.ModAlt == 0 {\n\t\t\t\t// Regular movement.\n\t\t\t\tif event.Modifiers()&tcell.ModShift == 0 && t.selectionStart.pos != t.cursor.pos {\n\t\t\t\t\t// Move to the start of the selection.\n\t\t\t\t\tif t.selectionStart.row < t.cursor.row || (t.selectionStart.row == t.cursor.row && t.selectionStart.actualColumn < t.cursor.actualColumn) {\n\t\t\t\t\t\tt.cursor = t.selectionStart\n\t\t\t\t\t}\n\t\t\t\t\tt.findCursor(true, t.cursor.row)\n\t\t\t\t} else if event.Modifiers()&tcell.ModMeta != 0 || event.Modifiers()&tcell.ModCtrl != 0 {\n\t\t\t\t\t// This captures Ctrl-Left on some systems.\n\t\t\t\t\tt.moveWordLeft(event.Modifiers()&tcell.ModShift != 0)\n\t\t\t\t} else if t.cursor.actualColumn == 0 {\n\t\t\t\t\t// Move to the end of the previous row.\n\t\t\t\t\tif t.cursor.row > 0 {\n\t\t\t\t\t\tt.moveCursor(t.cursor.row-1, -1)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Move one grapheme cluster to the left.\n\t\t\t\t\tt.moveCursor(t.cursor.row, t.cursor.actualColumn-1)\n\t\t\t\t}\n\t\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\t\tt.selectionStart = t.cursor\n\t\t\t\t}\n\t\t\t} else if !t.wrap { // This doesn't work on all terminals.\n\t\t\t\t// Just scroll.\n\t\t\t\tt.columnOffset--\n\t\t\t\tif t.columnOffset < 0 {\n\t\t\t\t\tt.columnOffset = 0\n\t\t\t\t}\n\t\t\t}\n\t\tcase tcell.KeyRight: // Move one grapheme cluster to the right.\n\t\t\tif event.Modifiers()&tcell.ModAlt == 0 {\n\t\t\t\t// Regular movement.\n\t\t\t\tif event.Modifiers()&tcell.ModShift == 0 && t.selectionStart.pos != t.cursor.pos {\n\t\t\t\t\t// Move to the end of the selection.\n\t\t\t\t\tif t.selectionStart.row > t.cursor.row || (t.selectionStart.row == t.cursor.row && t.selectionStart.actualColumn > t.cursor.actualColumn) {\n\t\t\t\t\t\tt.cursor = t.selectionStart\n\t\t\t\t\t}\n\t\t\t\t\tt.findCursor(true, t.cursor.row)\n\t\t\t\t} else if t.cursor.pos[0] != 1 {\n\t\t\t\t\tif event.Modifiers()&tcell.ModMeta != 0 || event.Modifiers()&tcell.ModCtrl != 0 {\n\t\t\t\t\t\t// This captures Ctrl-Right on some systems.\n\t\t\t\t\t\tt.moveWordRight(event.Modifiers()&tcell.ModShift != 0, true)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Move one grapheme cluster to the right.\n\t\t\t\t\t\tvar clusterWidth int\n\t\t\t\t\t\t_, _, _, clusterWidth, t.cursor.pos, _ = t.step(\"\", t.cursor.pos, t.cursor.pos)\n\t\t\t\t\t\tif len(t.lineStarts) <= t.cursor.row+1 {\n\t\t\t\t\t\t\tt.extendLines(t.lastWidth, t.cursor.row+1)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif t.cursor.row+1 < len(t.lineStarts) && t.lineStarts[t.cursor.row+1] == t.cursor.pos {\n\t\t\t\t\t\t\t// We've reached the end of the line.\n\t\t\t\t\t\t\tt.cursor.row++\n\t\t\t\t\t\t\tt.cursor.actualColumn = 0\n\t\t\t\t\t\t\tt.cursor.column = 0\n\t\t\t\t\t\t\tt.findCursor(true, t.cursor.row)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Move one character to the right.\n\t\t\t\t\t\t\tt.moveCursor(t.cursor.row, t.cursor.actualColumn+clusterWidth)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\t\tt.selectionStart = t.cursor\n\t\t\t\t}\n\t\t\t} else if !t.wrap { // This doesn't work on all terminals.\n\t\t\t\t// Just scroll.\n\t\t\t\tt.columnOffset++\n\t\t\t\tif t.columnOffset >= t.widestLine {\n\t\t\t\t\tt.columnOffset = t.widestLine - 1\n\t\t\t\t\tif t.columnOffset < 0 {\n\t\t\t\t\t\tt.columnOffset = 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tcase tcell.KeyDown: // Move one row down.\n\t\t\tif event.Modifiers()&tcell.ModAlt == 0 {\n\t\t\t\t// Regular movement.\n\t\t\t\tcolumn := t.cursor.column\n\t\t\t\tt.moveCursor(t.cursor.row+1, t.cursor.column)\n\t\t\t\tt.cursor.column = column\n\t\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\t\tt.selectionStart = t.cursor\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Just scroll.\n\t\t\t\tt.rowOffset++\n\t\t\t\tif t.rowOffset >= len(t.lineStarts) {\n\t\t\t\t\tt.extendLines(t.lastWidth, t.rowOffset)\n\t\t\t\t\tif t.rowOffset >= len(t.lineStarts) {\n\t\t\t\t\t\tt.rowOffset = len(t.lineStarts) - 1\n\t\t\t\t\t\tif t.rowOffset < 0 {\n\t\t\t\t\t\t\tt.rowOffset = 0\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tcase tcell.KeyUp: // Move one row up.\n\t\t\tif event.Modifiers()&tcell.ModAlt == 0 {\n\t\t\t\t// Regular movement.\n\t\t\t\tcolumn := t.cursor.column\n\t\t\t\tt.moveCursor(t.cursor.row-1, t.cursor.column)\n\t\t\t\tt.cursor.column = column\n\t\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\t\tt.selectionStart = t.cursor\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Just scroll.\n\t\t\t\tt.rowOffset--\n\t\t\t\tif t.rowOffset < 0 {\n\t\t\t\t\tt.rowOffset = 0\n\t\t\t\t}\n\t\t\t}\n\t\tcase tcell.KeyHome, tcell.KeyCtrlA: // Move to the start of the line.\n\t\t\tt.moveCursor(t.cursor.row, 0)\n\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\tt.selectionStart = t.cursor\n\t\t\t}\n\t\tcase tcell.KeyEnd, tcell.KeyCtrlE: // Move to the end of the line.\n\t\t\tt.moveCursor(t.cursor.row, -1)\n\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\tt.selectionStart = t.cursor\n\t\t\t}\n\t\tcase tcell.KeyPgDn, tcell.KeyCtrlF: // Move one page down.\n\t\t\tcolumn := t.cursor.column\n\t\t\tt.moveCursor(t.cursor.row+t.lastHeight, t.cursor.column)\n\t\t\tt.cursor.column = column\n\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\tt.selectionStart = t.cursor\n\t\t\t}\n\t\tcase tcell.KeyPgUp, tcell.KeyCtrlB: // Move one page up.\n\t\t\tcolumn := t.cursor.column\n\t\t\tt.moveCursor(t.cursor.row-t.lastHeight, t.cursor.column)\n\t\t\tt.cursor.column = column\n\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\tt.selectionStart = t.cursor\n\t\t\t}\n\t\tcase tcell.KeyEnter: // Insert a newline.\n\t\t\tfrom, to, row := t.getSelection()\n\t\t\tt.cursor.pos = t.replace(from, to, NewLine, t.lastAction == taActionTypeSpace)\n\t\t\tt.cursor.row = -1\n\t\t\tt.truncateLines(row - 1)\n\t\t\tt.findCursor(true, row)\n\t\t\tt.selectionStart = t.cursor\n\t\t\tnewLastAction = taActionTypeSpace\n\t\tcase tcell.KeyTab: // Insert a tab character. It will be rendered as TabSize spaces.\n\t\t\t// But forwarding takes precedence.\n\t\t\tif t.finished != nil {\n\t\t\t\tt.finished(key)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfrom, to, row := t.getSelection()\n\t\t\tt.cursor.pos = t.replace(from, to, \"\\t\", t.lastAction == taActionTypeSpace)\n\t\t\tt.cursor.row = -1\n\t\t\tt.truncateLines(row - 1)\n\t\t\tt.findCursor(true, row)\n\t\t\tt.selectionStart = t.cursor\n\t\t\tnewLastAction = taActionTypeSpace\n\t\tcase tcell.KeyBacktab, tcell.KeyEscape: // Only used in forms.\n\t\t\tif t.finished != nil {\n\t\t\t\tt.finished(key)\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tcell.KeyRune:\n\t\t\tif event.Modifiers()&tcell.ModAlt > 0 {\n\t\t\t\t// We accept some Alt- key combinations.\n\t\t\t\tswitch event.Rune() {\n\t\t\t\tcase 'f':\n\t\t\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\t\t\tt.moveWordRight(false, true)\n\t\t\t\t\t\tt.selectionStart = t.cursor\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.moveWordRight(true, true)\n\t\t\t\t\t}\n\t\t\t\tcase 'b':\n\t\t\t\t\tt.moveWordLeft(true)\n\t\t\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\t\t\tt.selectionStart = t.cursor\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Other keys are simply accepted as regular characters.\n\t\t\t\tr := event.Rune()\n\t\t\t\tfrom, to, row := t.getSelection()\n\t\t\t\tnewLastAction = taActionTypeNonSpace\n\t\t\t\tif unicode.IsSpace(r) {\n\t\t\t\t\tnewLastAction = taActionTypeSpace\n\t\t\t\t}\n\t\t\t\tt.cursor.pos = t.replace(from, to, string(r), newLastAction == t.lastAction || t.lastAction == taActionTypeNonSpace && newLastAction == taActionTypeSpace)\n\t\t\t\tt.cursor.row = -1\n\t\t\t\tt.truncateLines(row - 1)\n\t\t\t\tt.findCursor(true, row)\n\t\t\t\tt.selectionStart = t.cursor\n\t\t\t}\n\t\tcase tcell.KeyBackspace, tcell.KeyBackspace2: // Delete backwards. tcell.KeyBackspace is the same as tcell.CtrlH.\n\t\t\tfrom, to, row := t.getSelection()\n\t\t\tif from != to {\n\t\t\t\t// Simply delete the current selection.\n\t\t\t\tt.cursor.pos = t.replace(from, to, \"\", false)\n\t\t\t\tt.cursor.row = -1\n\t\t\t\tt.truncateLines(row - 1)\n\t\t\t\tt.findCursor(true, row)\n\t\t\t\tt.selectionStart = t.cursor\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tbeforeCursor := t.cursor\n\t\t\tif event.Modifiers()&tcell.ModAlt == 0 {\n\t\t\t\t// Move the cursor back by one grapheme cluster.\n\t\t\t\tif t.cursor.actualColumn == 0 {\n\t\t\t\t\t// Move to the end of the previous row.\n\t\t\t\t\tif t.cursor.row > 0 {\n\t\t\t\t\t\tt.moveCursor(t.cursor.row-1, -1)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Move one grapheme cluster to the left.\n\t\t\t\t\tt.moveCursor(t.cursor.row, t.cursor.actualColumn-1)\n\t\t\t\t}\n\t\t\t\tnewLastAction = taActionBackspace\n\t\t\t} else {\n\t\t\t\t// Move the cursor back by one word.\n\t\t\t\tt.moveWordLeft(false)\n\t\t\t}\n\n\t\t\t// Remove that last grapheme cluster.\n\t\t\tif t.cursor.pos != beforeCursor.pos {\n\t\t\t\tt.cursor, beforeCursor = beforeCursor, t.cursor                                                 // So we put the right position on the stack.\n\t\t\t\tt.cursor.pos = t.replace(beforeCursor.pos, t.cursor.pos, \"\", t.lastAction == taActionBackspace) // Delete the character.\n\t\t\t\tt.cursor.row = -1\n\t\t\t\tt.truncateLines(beforeCursor.row - 1)\n\t\t\t\tt.findCursor(true, beforeCursor.row-1)\n\t\t\t}\n\t\t\tt.selectionStart = t.cursor\n\t\tcase tcell.KeyDelete, tcell.KeyCtrlD: // Delete forward.\n\t\t\tfrom, to, row := t.getSelection()\n\t\t\tif from != to {\n\t\t\t\t// Simply delete the current selection.\n\t\t\t\tt.cursor.pos = t.replace(from, to, \"\", false)\n\t\t\t\tt.cursor.row = -1\n\t\t\t\tt.truncateLines(row - 1)\n\t\t\t\tt.findCursor(true, row)\n\t\t\t\tt.selectionStart = t.cursor\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif t.cursor.pos[0] != 1 {\n\t\t\t\t_, _, _, _, endPos, _ := t.step(\"\", t.cursor.pos, t.cursor.pos)\n\t\t\t\tt.cursor.pos = t.replace(t.cursor.pos, endPos, \"\", t.lastAction == taActionDelete) // Delete the character.\n\t\t\t\tt.cursor.pos[2] = endPos[2]\n\t\t\t\tt.truncateLines(t.cursor.row - 1)\n\t\t\t\tt.findCursor(true, t.cursor.row)\n\t\t\t\tnewLastAction = taActionDelete\n\t\t\t}\n\t\t\tt.selectionStart = t.cursor\n\t\tcase tcell.KeyCtrlK: // Delete everything under and to the right of the cursor until before the next newline character.\n\t\t\tpos := t.cursor.pos\n\t\t\tendPos := pos\n\t\t\tvar cluster, text string\n\t\t\tfor pos[0] != 1 {\n\t\t\t\tvar boundaries int\n\t\t\t\toldPos := pos\n\t\t\t\tcluster, text, boundaries, _, pos, endPos = t.step(text, pos, endPos)\n\t\t\t\tif boundaries&uniseg.MaskLine == uniseg.LineMustBreak {\n\t\t\t\t\tif uniseg.HasTrailingLineBreakInString(cluster) {\n\t\t\t\t\t\tpos = oldPos\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.cursor.pos = t.replace(t.cursor.pos, pos, \"\", false)\n\t\t\trow := t.cursor.row\n\t\t\tt.cursor.row = -1\n\t\t\tt.truncateLines(row - 1)\n\t\t\tt.findCursor(true, row)\n\t\t\tt.selectionStart = t.cursor\n\t\tcase tcell.KeyCtrlW: // Delete from the start of the current word to the left of the cursor.\n\t\t\tpos := t.cursor.pos\n\t\t\tt.moveWordLeft(true)\n\t\t\tt.cursor.pos = t.replace(t.cursor.pos, pos, \"\", false)\n\t\t\trow := t.cursor.row - 1\n\t\t\tt.cursor.row = -1\n\t\t\tt.truncateLines(row)\n\t\t\tt.findCursor(true, row)\n\t\t\tt.selectionStart = t.cursor\n\t\tcase tcell.KeyCtrlU: // Delete the current line.\n\t\t\tt.deleteLine()\n\t\t\tt.selectionStart = t.cursor\n\t\tcase tcell.KeyCtrlL: // Select everything.\n\t\t\tt.selectionStart.row, t.selectionStart.column, t.selectionStart.actualColumn = 0, 0, 0\n\t\t\tt.selectionStart.pos = [3]int{t.spans[0].next, 0, -1}\n\t\t\trow := t.cursor.row\n\t\t\tt.cursor.row = -1\n\t\t\tt.cursor.pos = [3]int{1, 0, -1}\n\t\t\tt.findCursor(false, row)\n\t\tcase tcell.KeyCtrlQ: // Copy to clipboard.\n\t\t\tif t.cursor != t.selectionStart {\n\t\t\t\tt.copyToClipboard(t.getSelectedText())\n\t\t\t\tt.selectionStart = t.cursor\n\t\t\t}\n\t\tcase tcell.KeyCtrlX: // Cut to clipboard.\n\t\t\tif t.cursor != t.selectionStart {\n\t\t\t\tt.copyToClipboard(t.getSelectedText())\n\t\t\t\tfrom, to, row := t.getSelection()\n\t\t\t\tt.cursor.pos = t.replace(from, to, \"\", false)\n\t\t\t\tt.cursor.row = -1\n\t\t\t\tt.truncateLines(row - 1)\n\t\t\t\tt.findCursor(true, row)\n\t\t\t\tt.selectionStart = t.cursor\n\t\t\t}\n\t\tcase tcell.KeyCtrlV: // Paste from clipboard.\n\t\t\tfrom, to, row := t.getSelection()\n\t\t\tt.cursor.pos = t.replace(from, to, t.pasteFromClipboard(), false)\n\t\t\tt.cursor.row = -1\n\t\t\tt.truncateLines(row - 1)\n\t\t\tt.findCursor(true, row)\n\t\t\tt.selectionStart = t.cursor\n\t\tcase tcell.KeyCtrlZ: // Undo.\n\t\t\tif t.nextUndo <= 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tfor t.nextUndo > 0 {\n\t\t\t\tt.nextUndo--\n\t\t\t\tundo := t.undoStack[t.nextUndo]\n\t\t\t\tt.spans[undo.originalBefore], t.spans[undo.before] = t.spans[undo.before], t.spans[undo.originalBefore]\n\t\t\t\tt.spans[undo.originalAfter], t.spans[undo.after] = t.spans[undo.after], t.spans[undo.originalAfter]\n\t\t\t\tt.cursor.pos, t.undoStack[t.nextUndo].pos = undo.pos, t.cursor.pos\n\t\t\t\tt.length, t.undoStack[t.nextUndo].length = undo.length, t.length\n\t\t\t\tif !undo.continuation {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.cursor.row = -1\n\t\t\tt.truncateLines(0) // This is why Undo is expensive for large texts. (t.lineStarts can get largely unusable after an undo.)\n\t\t\tt.findCursor(true, 0)\n\t\t\tt.selectionStart = t.cursor\n\t\t\tif t.changed != nil {\n\t\t\t\tdefer t.changed()\n\t\t\t}\n\t\tcase tcell.KeyCtrlY: // Redo.\n\t\t\tif t.nextUndo >= len(t.undoStack) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tfor t.nextUndo < len(t.undoStack) {\n\t\t\t\tundo := t.undoStack[t.nextUndo]\n\t\t\t\tt.spans[undo.originalBefore], t.spans[undo.before] = t.spans[undo.before], t.spans[undo.originalBefore]\n\t\t\t\tt.spans[undo.originalAfter], t.spans[undo.after] = t.spans[undo.after], t.spans[undo.originalAfter]\n\t\t\t\tt.cursor.pos, t.undoStack[t.nextUndo].pos = undo.pos, t.cursor.pos\n\t\t\t\tt.length, t.undoStack[t.nextUndo].length = undo.length, t.length\n\t\t\t\tt.nextUndo++\n\t\t\t\tif t.nextUndo < len(t.undoStack) && !t.undoStack[t.nextUndo].continuation {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.cursor.row = -1\n\t\t\tt.truncateLines(0) // This is why Redo is expensive for large texts. (t.lineStarts can get largely unusable after an undo.)\n\t\t\tt.findCursor(true, 0)\n\t\t\tt.selectionStart = t.cursor\n\t\t\tif t.changed != nil {\n\t\t\t\tdefer t.changed()\n\t\t\t}\n\t\t}\n\t})\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (t *TextArea) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tif t.disabled {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tx, y := event.Position()\n\t\trectX, rectY, _, _ := t.GetInnerRect()\n\t\tif !t.InRect(x, y) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Trigger a \"moved\" event at the end if requested.\n\t\tif t.moved != nil {\n\t\t\tselectionStart, cursor := t.selectionStart, t.cursor\n\t\t\tdefer func() {\n\t\t\t\tif selectionStart != t.selectionStart || cursor != t.cursor {\n\t\t\t\t\tt.moved()\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\n\t\t// Turn mouse coordinates into text coordinates.\n\t\tlabelWidth := t.labelWidth\n\t\tif labelWidth == 0 && t.label != \"\" {\n\t\t\tlabelWidth = TaggedStringWidth(t.label)\n\t\t}\n\t\tcolumn := x - rectX - labelWidth\n\t\trow := y - rectY\n\t\tif !t.wrap {\n\t\t\tcolumn += t.columnOffset\n\t\t}\n\t\trow += t.rowOffset\n\n\t\t// Process mouse actions.\n\t\tswitch action {\n\t\tcase MouseLeftDown:\n\t\t\tt.moveCursor(row, column)\n\t\t\tif event.Modifiers()&tcell.ModShift == 0 {\n\t\t\t\tt.selectionStart = t.cursor\n\t\t\t}\n\t\t\tsetFocus(t)\n\t\t\tconsumed = true\n\t\t\tcapture = t\n\t\t\tt.dragging = true\n\t\tcase MouseMove:\n\t\t\tif !t.dragging {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tt.moveCursor(row, column)\n\t\t\tconsumed = true\n\t\tcase MouseLeftUp:\n\t\t\tt.moveCursor(row, column)\n\t\t\tconsumed = true\n\t\t\tcapture = nil\n\t\t\tt.dragging = false\n\t\tcase MouseLeftDoubleClick: // Select word.\n\t\t\t// Left down/up was already triggered so we are at the correct\n\t\t\t// position.\n\t\t\tt.moveWordLeft(false)\n\t\t\tt.selectionStart = t.cursor\n\t\t\tt.moveWordRight(true, false)\n\t\t\tconsumed = true\n\t\tcase MouseScrollUp:\n\t\t\tif t.rowOffset > 0 {\n\t\t\t\tt.rowOffset--\n\t\t\t}\n\t\t\tconsumed = true\n\t\tcase MouseScrollDown:\n\t\t\tt.rowOffset++\n\t\t\tif t.rowOffset >= len(t.lineStarts) {\n\t\t\t\tt.rowOffset = len(t.lineStarts) - 1\n\t\t\t\tif t.rowOffset < 0 {\n\t\t\t\t\tt.rowOffset = 0\n\t\t\t\t}\n\t\t\t}\n\t\t\tconsumed = true\n\t\tcase MouseScrollLeft:\n\t\t\tif t.columnOffset > 0 {\n\t\t\t\tt.columnOffset--\n\t\t\t}\n\t\t\tconsumed = true\n\t\tcase MouseScrollRight:\n\t\t\tt.columnOffset++\n\t\t\tif t.columnOffset >= t.widestLine {\n\t\t\t\tt.columnOffset = t.widestLine - 1\n\t\t\t\tif t.columnOffset < 0 {\n\t\t\t\t\tt.columnOffset = 0\n\t\t\t\t}\n\t\t\t}\n\t\t\tconsumed = true\n\t\t}\n\n\t\treturn\n\t})\n}\n\n// PasteHandler returns the handler for this primitive.\nfunc (t *TextArea) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {\n\treturn t.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {\n\t\tfrom, to, row := t.getSelection()\n\t\tt.cursor.pos = t.replace(from, to, pastedText, false)\n\t\tt.cursor.row = -1\n\t\tt.truncateLines(row - 1)\n\t\tt.findCursor(true, row)\n\t\tt.selectionStart = t.cursor\n\t})\n}\n"
  },
  {
    "path": "textview.go",
    "content": "package tview\n\nimport (\n\t\"math\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\tcolorful \"github.com/lucasb-eyer/go-colorful\"\n)\n\n// TabSize is the number of spaces with which a tab character will be replaced.\nvar TabSize = 4\n\n// textViewLine contains information about a line displayed in the text view.\ntype textViewLine struct {\n\toffset  int        // The string position in the buffer where this line starts.\n\twidth   int        // The screen width of this line.\n\tlength  int        // The string length (in bytes) of this line.\n\tstate   *stepState // The parser state at the beginning of the line, before parsing the first character.\n\tregions []*Region  // The regions on this line, in the order of their appearance.\n}\n\n// Region represents a region in a [TextView]. Note that depending on how the\n// region is retrieved, the end positions and locations may not correspond to\n// the true end of the region but to the end of the last line that was parsed.\ntype Region struct {\n\t// The region ID.\n\tID string\n\n\t// The start and end offsets into the text string of the region. The end\n\t// points to the first byte after the region.\n\tStart, End int\n\n\t// The start and end positions of the region in screen coordinates, relative\n\t// to the position of where the first character of the text view would be\n\t// drawn.\n\tStartRow, StartColumn, EndRow, EndColumn int\n}\n\n// batchWriter is a writer that can be used to write to and clear a TextView\n// in batches, i.e. multiple writes with the lock only being acquired once.\n// Don't instantiated this class directly but use the [TextView.BatchWriter]\n// method instead.\ntype batchWriter struct {\n\t*TextView\n}\n\n// Close implements io.Closer for the writer by unlocking the original TextView.\nfunc (w *batchWriter) Close() error {\n\tw.TextView.Unlock()\n\treturn nil\n}\n\n// Clear removes all text from the buffer.\nfunc (w *batchWriter) Clear() {\n\tw.TextView.clear()\n}\n\n// Write implements the io.Writer interface. It behaves like the TextView's\n// Write() method except that it does not acquire the lock.\nfunc (w *batchWriter) Write(p []byte) (n int, err error) {\n\treturn w.TextView.write(p)\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (w *batchWriter) focusChain(chain *[]Primitive) bool {\n\treturn w.TextView.Box.focusChain(chain)\n}\n\n// TextView is a component to display read-only text. While the text to be\n// displayed can be changed or appended to, there is no functionality that\n// allows the user to edit it. For that, [TextArea] should be used.\n//\n// TextView implements the io.Writer interface so you can stream text to it,\n// appending to the existing text. This does not trigger a redraw automatically\n// but if a handler is installed via [TextView.SetChangedFunc], you can cause it\n// to be redrawn. (See [TextView.SetChangedFunc] for more details.)\n//\n// Tab characters advance the text to the next tab stop at every [TabSize]\n// screen columns, but only if the text is left-aligned. If the text is centered\n// or right-aligned, tab characters are simply replaced with [TabSize] spaces.\n//\n// Word wrapping is enabled by default. Use [TextView.SetWrap] and\n// [TextView.SetWordWrap] to change this.\n//\n// # Navigation\n//\n// If the text view is set to be scrollable (which is the default), text is kept\n// in a buffer which may be larger than the screen and can be navigated\n// with Vim-like key binds:\n//\n//   - h, left arrow: Move left.\n//   - l, right arrow: Move right.\n//   - j, down arrow: Move down.\n//   - k, up arrow: Move up.\n//   - g, home: Move to the top.\n//   - G, end: Move to the bottom.\n//   - Ctrl-F, page down: Move down by one page.\n//   - Ctrl-B, page up: Move up by one page.\n//\n// If the text is not scrollable, any text above the top visible line is\n// discarded. This can be useful when you want to continuously stream text to\n// the text view and only keep the latest lines.\n//\n// Use [Box.SetInputCapture] to override or modify keyboard input.\n//\n// # Styles / Colors\n//\n// If dynamic colors are enabled via [TextView.SetDynamicColors], text style can\n// be changed dynamically by embedding color strings in square brackets. This\n// works the same way as anywhere else. See the package documentation for more\n// information.\n//\n// # Regions and Highlights\n//\n// If regions are enabled via [TextView.SetRegions], you can define text regions\n// within the text and assign region IDs to them. Text regions start with region\n// tags. Region tags are square brackets that contain a region ID in double\n// quotes, for example:\n//\n//\tWe define a [\"rg\"]region[\"\"] here.\n//\n// A text region ends with the next region tag. Tags with no region ID ([\"\"])\n// don't start new regions. They can therefore be used to mark the end of a\n// region. Region IDs must satisfy the following regular expression:\n//\n//\t[a-zA-Z0-9_,;: \\-\\.]+\n//\n// Regions can be highlighted by calling the [TextView.Highlight] function with\n// one or more region IDs. This can be used to display search results, for\n// example.\n//\n// The [TextView.ScrollToHighlight] function can be used to jump to the\n// currently highlighted region once when the text view is drawn the next time.\n//\n// # Large Texts\n//\n// The text view can handle reasonably large texts. It will parse the text as\n// needed. For optimal performance, it is best to access or display parts of the\n// text very far down only if really needed. For example, call\n// [TextView.ScrollToBeginning] before adding the text to the text view, to\n// avoid scrolling the text all the way to the bottom, forcing a full-text\n// parse.\n//\n// For even larger texts or \"infinite\" streams of text such as log files, you\n// should consider using [TextView.SetMaxLines] to limit the number of lines in\n// the text view buffer. Or disable the text view's scrollability altogether\n// (using [TextView.SetScrollable]). This will cause the text view to discard\n// lines moving out of the visible area at the top.\n//\n// See https://github.com/rivo/tview/wiki/TextView for an example.\ntype TextView struct {\n\tsync.Mutex\n\t*Box\n\n\t// The size of the text area. If set to 0, the text view will use the entire\n\t// available space.\n\twidth, height int\n\n\t// The text buffer.\n\ttext strings.Builder\n\n\t// The line index. It is valid at any time but may not contain trailing\n\t// lines which are not visible.\n\tlineIndex []*textViewLine\n\n\t// The screen width of the longest line in the index.\n\tlongestLine int\n\n\t// Regions mapped by their ID to the line where they start. Regions which\n\t// cannot be found in [TextView.lineIndex] are not contained.\n\tregions map[string]int\n\n\t// The label text shown, usually when part of a form.\n\tlabel string\n\n\t// The width of the text area's label.\n\tlabelWidth int\n\n\t// The label style.\n\tlabelStyle tcell.Style\n\n\t// The text alignment, one of AlignLeft, AlignCenter, or AlignRight.\n\talign int\n\n\t// Currently highlighted regions.\n\thighlights map[string]struct{}\n\n\t// The last width for which the current text view was drawn.\n\tlastWidth int\n\n\t// The height of the content the last time the text view was drawn.\n\tpageSize int\n\n\t// The index of the first line shown in the text view.\n\tlineOffset int\n\n\t// If set to true, the text view will always remain at the end of the\n\t// content when text is added.\n\ttrackEnd bool\n\n\t// The width of the characters to be skipped on each line (not used in wrap\n\t// mode).\n\tcolumnOffset int\n\n\t// The maximum number of lines kept in the line index, effectively the\n\t// latest word-wrapped lines. Ignored if 0.\n\tmaxLines int\n\n\t// If set to true, the text view will keep a buffer of text which can be\n\t// navigated when the text is longer than what fits into the box.\n\tscrollable bool\n\n\t// If set to true, lines that are longer than the available width are\n\t// wrapped onto the next line. If set to false, any characters beyond the\n\t// available width are discarded.\n\twrap bool\n\n\t// If set to true and if wrap is also true, Unicode line breaking is\n\t// applied.\n\twordWrap bool\n\n\t// The (starting) style of the text. This also defines the background color\n\t// of the main text element.\n\ttextStyle tcell.Style\n\n\t// Whether or not style tags are used.\n\tstyleTags bool\n\n\t// Whether or not region tags are used.\n\tregionTags bool\n\n\t// A temporary flag which, when true, will automatically bring the current\n\t// highlight(s) into the visible screen the next time the text view is\n\t// drawn.\n\tscrollToHighlights bool\n\n\t// If true, setting new highlights will be a XOR instead of an overwrite\n\t// operation.\n\ttoggleHighlights bool\n\n\t// An optional function which is called when the content of the text view\n\t// has changed.\n\tchanged func()\n\n\t// An optional function which is called when the user presses one of the\n\t// following keys: Escape, Enter, Tab, Backtab.\n\tdone func(tcell.Key)\n\n\t// An optional function which is called when one or more regions were\n\t// highlighted.\n\thighlighted func(added, removed, remaining []string)\n\n\t// A callback function set by the Form class and called when the user leaves\n\t// this form item.\n\tfinished func(tcell.Key)\n}\n\n// NewTextView returns a new [TextView].\nfunc NewTextView() *TextView {\n\tt := &TextView{\n\t\tBox:        NewBox(),\n\t\tlabelStyle: tcell.StyleDefault.Foreground(Styles.SecondaryTextColor),\n\t\thighlights: make(map[string]struct{}),\n\t\tlineOffset: -1,\n\t\tscrollable: true,\n\t\talign:      AlignLeft,\n\t\twrap:       true,\n\t\twordWrap:   true,\n\t\ttextStyle:  tcell.StyleDefault.Background(Styles.PrimitiveBackgroundColor).Foreground(Styles.PrimaryTextColor),\n\t\tregionTags: false,\n\t\tstyleTags:  false,\n\t}\n\tt.Box.Primitive = t\n\treturn t\n}\n\n// SetLabel sets the text to be displayed before the text view.\nfunc (t *TextView) SetLabel(label string) *TextView {\n\tt.label = label\n\treturn t\n}\n\n// GetLabel returns the text to be displayed before the text view.\nfunc (t *TextView) GetLabel() string {\n\treturn t.label\n}\n\n// SetLabelWidth sets the screen width of the label. A value of 0 will cause the\n// primitive to use the width of the label string.\nfunc (t *TextView) SetLabelWidth(width int) *TextView {\n\tt.labelWidth = width\n\treturn t\n}\n\n// SetSize sets the screen size of the main text element of the text view. This\n// element is always located next to the label which is always located in the\n// top left corner. If any of the values are 0 or larger than the available\n// space, the available space will be used.\nfunc (t *TextView) SetSize(rows, columns int) *TextView {\n\tt.width = columns\n\tt.height = rows\n\treturn t\n}\n\n// GetFieldWidth returns this primitive's field width.\nfunc (t *TextView) GetFieldWidth() int {\n\treturn t.width\n}\n\n// GetFieldHeight returns this primitive's field height.\nfunc (t *TextView) GetFieldHeight() int {\n\treturn t.height\n}\n\n// SetDisabled sets whether or not the item is disabled / read-only.\nfunc (t *TextView) SetDisabled(disabled bool) FormItem {\n\treturn t // Text views are always read-only.\n}\n\n// GetDisabled returns whether or not the item is disabled / read-only.\nfunc (t *TextView) GetDisabled() bool {\n\treturn true // Text views are always read-only.\n}\n\n// SetScrollable sets the flag that decides whether or not the text view is\n// scrollable. If false, text that moves above the text view's top row will be\n// permanently deleted.\nfunc (t *TextView) SetScrollable(scrollable bool) *TextView {\n\tt.scrollable = scrollable\n\tif !scrollable {\n\t\tt.trackEnd = true\n\t}\n\treturn t\n}\n\n// SetWrap sets the flag that, if true, leads to lines that are longer than the\n// available width being wrapped onto the next line. If false, any characters\n// beyond the available width are not displayed.\nfunc (t *TextView) SetWrap(wrap bool) *TextView {\n\tif t.wrap != wrap {\n\t\tt.resetIndex() // This invalidates the entire index.\n\t}\n\tt.wrap = wrap\n\treturn t\n}\n\n// SetWordWrap sets the flag that, if true and if the \"wrap\" flag is also true\n// (see [TextView.SetWrap]), wraps according to [Unicode Standard Annex #14].\n//\n// This flag is ignored if the \"wrap\" flag is false.\nfunc (t *TextView) SetWordWrap(wrapOnWords bool) *TextView {\n\tif t.wrap && t.wordWrap != wrapOnWords {\n\t\tt.resetIndex() // This invalidates the entire index.\n\t}\n\tt.wordWrap = wrapOnWords\n\treturn t\n}\n\n// SetMaxLines sets the maximum number of lines for this text view. Lines at the\n// beginning of the text will be discarded when the text view is drawn, so as to\n// remain below this value. Only lines above the first visible line are removed.\n//\n// Broken-over lines via word/character wrapping are counted individually.\n//\n// Note that [TextView.GetText] will return the shortened text.\n//\n// A value of 0 (the default) will keep all lines in place.\nfunc (t *TextView) SetMaxLines(maxLines int) *TextView {\n\tt.maxLines = maxLines\n\treturn t\n}\n\n// SetTextAlign sets the text alignment within the text view. This must be\n// either AlignLeft, AlignCenter, or AlignRight.\nfunc (t *TextView) SetTextAlign(align int) *TextView {\n\tt.align = align\n\treturn t\n}\n\n// SetTextColor sets the initial color of the text.\nfunc (t *TextView) SetTextColor(color tcell.Color) *TextView {\n\tt.textStyle = t.textStyle.Foreground(color)\n\tt.resetIndex()\n\treturn t\n}\n\n// SetBackgroundColor overrides its implementation in Box to set the background\n// color of this primitive. For backwards compatibility reasons, it also sets\n// the background color of the main text element.\nfunc (t *TextView) SetBackgroundColor(color tcell.Color) *Box {\n\tt.Box.SetBackgroundColor(color)\n\tt.textStyle = t.textStyle.Background(color)\n\tt.resetIndex()\n\treturn t.Box\n}\n\n// SetTextStyle sets the initial style of the text. This style's background\n// color also determines the background color of the main text element.\nfunc (t *TextView) SetTextStyle(style tcell.Style) *TextView {\n\tt.textStyle = style\n\tt.resetIndex()\n\treturn t\n}\n\n// SetText sets the text of this text view to the provided string. Previously\n// contained text will be removed. As with writing to the text view io.Writer\n// interface directly, this does not trigger an automatic redraw but it will\n// trigger the \"changed\" callback if one is set.\nfunc (t *TextView) SetText(text string) *TextView {\n\tt.Lock()\n\tdefer t.Unlock()\n\tt.text.Reset()\n\tt.text.WriteString(text)\n\tt.resetIndex()\n\tif t.changed != nil {\n\t\tgo t.changed()\n\t}\n\treturn t\n}\n\n// GetText returns the current text of this text view. If \"stripAllTags\" is set\n// to true, any region/style tags are stripped from the text. Note that any text\n// that has been discarded due to [TextView.SetMaxLines] or\n// [TextView.SetScrollable] will not be part of the returned text.\nfunc (t *TextView) GetText(stripAllTags bool) string {\n\tif !stripAllTags || (!t.styleTags && !t.regionTags) {\n\t\treturn t.text.String()\n\t}\n\n\tvar (\n\t\tstr   strings.Builder\n\t\tstate *stepState\n\t\ttext  = t.text.String()\n\t\topts  stepOptions\n\t\tch    string\n\t)\n\tif t.styleTags {\n\t\topts = stepOptionsStyle\n\t}\n\tif t.regionTags {\n\t\topts |= stepOptionsRegion\n\t}\n\tfor len(text) > 0 {\n\t\tch, text, state = step(text, state, opts)\n\t\tstr.WriteString(ch)\n\t}\n\treturn str.String()\n}\n\n// GetOriginalLineCount returns the number of lines in the original text buffer,\n// without applying any wrapping. This is an expensive call as it needs to\n// iterate over the entire text. Note that any text that has been discarded due\n// to [TextView.SetMaxLines] or [TextView.SetScrollable] will not be part of the\n// count.\nfunc (t *TextView) GetOriginalLineCount() int {\n\tif t.text.Len() == 0 {\n\t\treturn 0\n\t}\n\n\tvar (\n\t\tstate *stepState\n\t\tstr       = t.text.String()\n\t\tlines int = 1\n\t)\n\tfor len(str) > 0 {\n\t\t_, str, state = step(str, state, stepOptionsNone)\n\t\tif lineBreak, optional := state.LineBreak(); lineBreak && !optional {\n\t\t\tlines++\n\t\t}\n\t}\n\n\treturn lines\n}\n\n// GetWrappedLineCount returns the number of lines in the text view, taking\n// wrapping into account (if activated). This is an even more expensive call\n// than [TextView.GetOriginalLineCount] as it needs to parse the text until the\n// end and calculate the line breaks. It will also allocate memory for each\n// line. Note that any text that has been discarded due to\n// [TextView.SetMaxLines] or [TextView.SetScrollable] will not be part of the\n// count. Calling this method before the text view was drawn for the first time\n// will assume no wrapping.\nfunc (t *TextView) GetWrappedLineCount() int {\n\tt.parseAhead(t.width, func(int, *textViewLine) bool {\n\t\treturn false\n\t})\n\treturn len(t.lineIndex)\n}\n\n// SetDynamicColors sets the flag that allows the text color to be changed\n// dynamically with style tags. See class description for details.\nfunc (t *TextView) SetDynamicColors(dynamic bool) *TextView {\n\tif t.styleTags != dynamic {\n\t\tt.resetIndex() // This invalidates the entire index.\n\t}\n\tt.styleTags = dynamic\n\treturn t\n}\n\n// SetRegions sets the flag that allows to define regions in the text. See class\n// description for details.\nfunc (t *TextView) SetRegions(regions bool) *TextView {\n\tif t.regionTags != regions {\n\t\tt.resetIndex() // This invalidates the entire index.\n\t}\n\tt.regionTags = regions\n\treturn t\n}\n\n// SetChangedFunc sets a handler function which is called when the text of the\n// text view has changed. This is useful when text is written to this\n// [io.Writer] in a separate goroutine. Doing so does not automatically cause\n// the screen to be refreshed so you may want to use the \"changed\" handler to\n// redraw the screen.\n//\n// Note that to avoid race conditions or deadlocks, there are a few rules you\n// should follow:\n//\n//   - You can call [Application.Draw] from this handler.\n//   - You can call [TextView.HasFocus] from this handler.\n//   - During the execution of this handler, access to any other variables from\n//     this primitive or any other primitive must be queued using\n//     [Application.QueueUpdate].\n//\n// See package description for details on dealing with concurrency.\nfunc (t *TextView) SetChangedFunc(handler func()) *TextView {\n\tt.changed = handler\n\treturn t\n}\n\n// SetDoneFunc sets a handler which is called when the user presses on the\n// following keys: Escape, Enter, Tab, Backtab. The key is passed to the\n// handler.\nfunc (t *TextView) SetDoneFunc(handler func(key tcell.Key)) *TextView {\n\tt.done = handler\n\treturn t\n}\n\n// SetHighlightedFunc sets a handler which is called when the list of currently\n// highlighted regions change. It receives a list of region IDs which were newly\n// highlighted, those that are not highlighted anymore, and those that remain\n// highlighted.\n//\n// Note that because regions are only determined when drawing the text view,\n// this function can only fire for regions that have existed when the text view\n// was last drawn.\nfunc (t *TextView) SetHighlightedFunc(handler func(added, removed, remaining []string)) *TextView {\n\tt.highlighted = handler\n\treturn t\n}\n\n// SetFinishedFunc sets a callback invoked when the user leaves this form item.\nfunc (t *TextView) SetFinishedFunc(handler func(key tcell.Key)) FormItem {\n\tt.finished = handler\n\treturn t\n}\n\n// SetFormAttributes sets attributes shared by all form items.\nfunc (t *TextView) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {\n\tt.labelWidth = labelWidth\n\tt.backgroundColor = bgColor\n\tt.labelStyle = t.labelStyle.Foreground(labelColor)\n\t// We ignore the field background color because this is a read-only element.\n\tt.textStyle = tcell.StyleDefault.Foreground(fieldTextColor).Background(bgColor)\n\treturn t\n}\n\n// ScrollTo scrolls to the specified row and column (both starting with 0).\nfunc (t *TextView) ScrollTo(row, column int) *TextView {\n\tif !t.scrollable {\n\t\treturn t\n\t}\n\tt.lineOffset = row\n\tt.columnOffset = column\n\tt.trackEnd = false\n\treturn t\n}\n\n// ScrollToBeginning scrolls to the top left corner of the text if the text view\n// is scrollable.\nfunc (t *TextView) ScrollToBeginning() *TextView {\n\tif !t.scrollable {\n\t\treturn t\n\t}\n\tt.trackEnd = false\n\tt.lineOffset = 0\n\tt.columnOffset = 0\n\treturn t\n}\n\n// ScrollToEnd scrolls to the bottom left corner of the text if the text view\n// is scrollable. Adding new rows to the end of the text view will cause it to\n// scroll with the new data.\nfunc (t *TextView) ScrollToEnd() *TextView {\n\tif !t.scrollable {\n\t\treturn t\n\t}\n\tt.trackEnd = true\n\tt.columnOffset = 0\n\treturn t\n}\n\n// GetScrollOffset returns the number of rows and columns that are skipped at\n// the top left corner when the text view has been scrolled.\nfunc (t *TextView) GetScrollOffset() (row, column int) {\n\treturn t.lineOffset, t.columnOffset\n}\n\n// Clear removes all text from the buffer. This triggers the \"changed\" callback.\nfunc (t *TextView) Clear() *TextView {\n\tt.Lock()\n\tdefer t.Unlock()\n\tt.clear()\n\tif t.changed != nil {\n\t\tgo t.changed()\n\t}\n\treturn t\n}\n\n// clear is the internal implementation of clear. It is used by [batchWriter]\n// and anywhere that we need to perform a write without locking the buffer.\nfunc (t *TextView) clear() {\n\tt.text.Reset()\n\tt.resetIndex()\n}\n\n// Highlight specifies which regions should be highlighted. If highlight\n// toggling is set to true (see [TextView.SetToggleHighlights]), the highlight\n// of the provided regions is toggled (i.e. highlighted regions are\n// un-highlighted and vice versa). If toggling is set to false, the provided\n// regions are highlighted and all other regions will not be highlighted (you\n// may also provide nil to turn off all highlights).\n//\n// For more information on regions, see class description. Empty region strings\n// or regions not contained in the text are ignored.\n//\n// Text in highlighted regions will be drawn inverted, i.e. with their\n// background and foreground colors swapped.\n//\n// If toggling is set to false, clicking outside of any region will remove all\n// highlights.\n//\n// This function is expensive if a specified region is in a part of the text\n// that has not yet been parsed, as is typically the case for lines below the\n// visible area.\nfunc (t *TextView) Highlight(regionIDs ...string) *TextView {\n\t// Make sure we know these regions.\n\tt.parseAhead(t.lastWidth, func(lineNumber int, line *textViewLine) bool {\n\t\tfor _, regionID := range regionIDs {\n\t\t\tif _, ok := t.regions[regionID]; !ok {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\n\t// Remove unknown regions.\n\tnewRegions := make([]string, 0, len(regionIDs))\n\tfor _, regionID := range regionIDs {\n\t\tif _, ok := t.regions[regionID]; ok {\n\t\t\tnewRegions = append(newRegions, regionID)\n\t\t}\n\t}\n\tregionIDs = newRegions\n\n\t// Toggle highlights.\n\tif t.toggleHighlights {\n\t\tvar newIDs []string\n\tHighlightLoop:\n\t\tfor regionID := range t.highlights {\n\t\t\tfor _, id := range regionIDs {\n\t\t\t\tif regionID == id {\n\t\t\t\t\tcontinue HighlightLoop\n\t\t\t\t}\n\t\t\t}\n\t\t\tnewIDs = append(newIDs, regionID)\n\t\t}\n\t\tfor _, regionID := range regionIDs {\n\t\t\tif _, ok := t.highlights[regionID]; !ok {\n\t\t\t\tnewIDs = append(newIDs, regionID)\n\t\t\t}\n\t\t}\n\t\tregionIDs = newIDs\n\t} // Now we have a list of region IDs that end up being highlighted.\n\n\t// Determine added and removed regions.\n\tvar added, removed, remaining []string\n\tif t.highlighted != nil {\n\t\tfor _, regionID := range regionIDs {\n\t\t\tif _, ok := t.highlights[regionID]; ok {\n\t\t\t\tremaining = append(remaining, regionID)\n\t\t\t\tdelete(t.highlights, regionID)\n\t\t\t} else {\n\t\t\t\tadded = append(added, regionID)\n\t\t\t}\n\t\t}\n\t\tfor regionID := range t.highlights {\n\t\t\tremoved = append(removed, regionID)\n\t\t}\n\t}\n\n\t// Make new selection.\n\tt.highlights = make(map[string]struct{})\n\tfor _, id := range regionIDs {\n\t\tif id == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tt.highlights[id] = struct{}{}\n\t}\n\n\t// Notify.\n\tif t.highlighted != nil && (len(added) > 0 || len(removed) > 0) {\n\t\tt.highlighted(added, removed, remaining)\n\t}\n\n\treturn t\n}\n\n// GetHighlights returns the IDs of all currently highlighted regions.\nfunc (t *TextView) GetHighlights() (regionIDs []string) {\n\tfor id := range t.highlights {\n\t\tregionIDs = append(regionIDs, id)\n\t}\n\treturn\n}\n\n// SetToggleHighlights sets a flag to determine how regions are highlighted.\n// When set to true, the [TextView.Highlight] function (or a mouse click) will\n// toggle the provided/selected regions. When set to false, [TextView.Highlight]\n// (or a mouse click) will simply highlight the provided regions.\nfunc (t *TextView) SetToggleHighlights(toggle bool) *TextView {\n\tt.toggleHighlights = toggle\n\treturn t\n}\n\n// ScrollToHighlight will cause the visible area to be scrolled so that the\n// highlighted regions appear in the visible area of the text view. This\n// repositioning happens the next time the text view is drawn. It happens only\n// once so you will need to call this function repeatedly to always keep\n// highlighted regions in view.\n//\n// Nothing happens if there are no highlighted regions or if the text view is\n// not scrollable.\nfunc (t *TextView) ScrollToHighlight() *TextView {\n\tif len(t.highlights) == 0 || !t.scrollable || !t.regionTags {\n\t\treturn t\n\t}\n\tt.scrollToHighlights = true\n\tt.trackEnd = false\n\treturn t\n}\n\n// GetRegionText returns the text of the first region with the given ID. If\n// dynamic colors are enabled, style tags are stripped from the text.\n//\n// If the region does not exist or if regions are turned off, an empty string\n// is returned.\n//\n// This function can be expensive if the specified region is way beyond the\n// visible area of the text view as the text needs to be parsed until the region\n// can be found, or if the region does not contain any text.\nfunc (t *TextView) GetRegionText(regionID string) string {\n\tif !t.regionTags || regionID == \"\" {\n\t\treturn \"\"\n\t}\n\n\t// Parse until we find the region.\n\tlineNumber, ok := t.regions[regionID]\n\tif !ok {\n\t\tlineNumber = -1\n\t\tt.parseAhead(t.lastWidth, func(number int, line *textViewLine) bool {\n\t\t\tlineNumber, ok = t.regions[regionID]\n\t\t\treturn ok\n\t\t})\n\t\tif lineNumber < 0 {\n\t\t\treturn \"\" // We couldn't find this region.\n\t\t}\n\t}\n\n\t// Extract text from region.\n\tvar (\n\t\tline       = t.lineIndex[lineNumber]\n\t\ttext       = t.text.String()[line.offset:]\n\t\tst         = *line.state\n\t\tstate      = &st\n\t\toptions    = stepOptionsRegion\n\t\tregionText strings.Builder\n\t)\n\tif t.styleTags {\n\t\toptions |= stepOptionsStyle\n\t}\n\tfor len(text) > 0 {\n\t\tvar ch string\n\t\tch, text, state = step(text, state, options)\n\t\tif state.region == regionID {\n\t\t\tregionText.WriteString(ch)\n\t\t} else if regionText.Len() > 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn regionText.String()\n}\n\n// GetRegions returns the regions in this [TextView]. If tail is set to false,\n// only regions from the startRow row to the end of the currently visible area\n// are returned. If tail is set to true, the regions below the visible area of\n// the view are also included. Note that the latter will require parsing the\n// entire text and can therefore be expensive. Setting startRow to a value\n// larger than the last visible row results in nil being returned. To obtain\n// only visible regions, set startRow to the current row offset (see\n// [TextView.GetScrollOffset]) and tail to false.\n//\n// Regions are returned in the order of their appearance in the text. Do not\n// modify the returned [Region] objects. Region positions change when the text\n// view is resized or when the text changes. Such changes will render the\n// returned slice invalid.\n//\n// If this function is called before the text view was drawn for the first\n// time, the return value is undefined.\nfunc (t *TextView) GetRegions(startRow int, tail bool) []*Region {\n\t_, _, _, height := t.GetInnerRect()\n\tif !t.regionTags || !tail && startRow >= t.lineOffset+height {\n\t\treturn nil\n\t}\n\n\t// Parse until we have all (complete) regions we need.\n\tvar lastRegion *Region\n\tt.parseAhead(t.lastWidth, func(lineNumber int, line *textViewLine) bool {\n\t\tif tail || lineNumber < t.lineOffset+height {\n\t\t\tif len(line.regions) > 0 {\n\t\t\t\tlastRegion = line.regions[len(line.regions)-1]\n\t\t\t}\n\t\t\treturn false // These must be parsed in any case.\n\t\t}\n\t\treturn len(line.regions) == 0 || line.regions[0] != lastRegion // Keep parsing to complete the last region.\n\t})\n\tif startRow >= len(t.lineIndex) {\n\t\treturn nil // We don't have that many lines.\n\t}\n\n\t// Merge regions into a slice.\n\tvar regions []*Region\n\tfor row, line := range t.lineIndex {\n\t\tif tail && row >= t.lineOffset+height {\n\t\t\tbreak\n\t\t}\n\t\tfor _, region := range line.regions {\n\t\t\tif len(regions) > 0 && regions[len(regions)-1] == region {\n\t\t\t\tcontinue // Already added.\n\t\t\t}\n\t\t\tregions = append(regions, region)\n\t\t}\n\t}\n\n\treturn regions\n}\n\n// Focus is called when this primitive receives focus.\nfunc (t *TextView) Focus(delegate func(p Primitive)) {\n\t// Implemented here with locking because this is used by layout primitives.\n\tt.Lock()\n\n\t// But if we're part of a form and not scrollable, there's nothing the user\n\t// can do here so we're finished.\n\tif finished := t.finished; finished != nil && !t.scrollable {\n\t\tt.Unlock()\n\t\tfinished(-1)\n\t\treturn\n\t}\n\n\tt.Box.Focus(delegate)\n\tt.Unlock()\n}\n\n// focusChain implements the [Primitive]'s focusChain method.\nfunc (t *TextView) focusChain(chain *[]Primitive) bool {\n\t// Implemented here with locking because this may be used in the \"changed\"\n\t// callback.\n\tt.Lock()\n\tdefer t.Unlock()\n\treturn t.Box.focusChain(chain)\n}\n\n// Write lets us implement the io.Writer interface.\nfunc (t *TextView) Write(p []byte) (n int, err error) {\n\tt.Lock()\n\tdefer t.Unlock()\n\n\treturn t.write(p)\n}\n\n// write is the internal implementation of Write. It is used by [batchWriter]\n// and anywhere that we need to perform a write without locking the buffer.\nfunc (t *TextView) write(p []byte) (n int, err error) {\n\t// Notify at the end.\n\tchanged := t.changed\n\tif changed != nil {\n\t\tdefer func() {\n\t\t\t// We always call the \"changed\" function in a separate goroutine to avoid\n\t\t\t// deadlocks.\n\t\t\tgo changed()\n\t\t}()\n\t}\n\n\treturn t.text.Write(p)\n}\n\n// BatchWriter returns a new writer that can be used to write into the buffer\n// but without Locking/Unlocking the buffer on every write, as [TextView.Write]\n// and [TextView.Clear] do. The lock will be acquired once when BatchWriter is\n// called, and will be released when the returned writer is closed. Example:\n//\n//\ttv := tview.NewTextView()\n//\tw := tv.BatchWriter()\n//\tdefer w.Close()\n//\tw.Clear()\n//\tfmt.Fprintln(w, \"To sit in solemn silence\")\n//\tfmt.Fprintln(w, \"on a dull, dark, dock\")\n//\tfmt.Println(tv.GetText(false))\n//\n// Note that using the batch writer requires you to manage any issues that may\n// arise from concurrency yourself. See package description for details on\n// dealing with concurrency.\nfunc (t *TextView) BatchWriter() *batchWriter {\n\tt.Lock()\n\treturn &batchWriter{\n\t\tTextView: t,\n\t}\n}\n\n// resetIndex resets all indexed data, including the line index.\nfunc (t *TextView) resetIndex() {\n\tt.lineIndex = nil\n\tt.regions = make(map[string]int)\n\tt.longestLine = 0\n}\n\n// parseAhead parses the text buffer starting at the last line in\n// [TextView.lineIndex] until either the end of the buffer or until stop returns\n// true for the last complete line that was parsed. If wrapping is enabled,\n// width will be used as the available screen width. If width is 0, it is\n// assumed that there is no wrapping. This can happen when this function is\n// called before the first time [TextView.Draw] is called.\n//\n// There is no guarantee that stop will ever be called.\n//\n// The function adds entries to the [TextView.lineIndex] slice and the\n// [TextView.regions] map and adjusts [TextView.longestLine].\nfunc (t *TextView) parseAhead(width int, stop func(lineNumber int, line *textViewLine) bool) {\n\tif t.text.Len() == 0 {\n\t\treturn // No text. Nothing to parse.\n\t}\n\n\t// If width is 0, make it infinite.\n\tif width == 0 {\n\t\twidth = math.MaxInt\n\t}\n\n\t// What kind of tags do we scan for?\n\tvar options stepOptions\n\tif t.styleTags {\n\t\toptions |= stepOptionsStyle\n\t}\n\tif t.regionTags {\n\t\toptions |= stepOptionsRegion\n\t}\n\n\t// Start parsing at the last line in the index.\n\tvar (\n\t\tlastLine *textViewLine\n\t\tregion   *Region\n\t)\n\tstr := t.text.String() // The remaining text to parse.\n\tif len(t.lineIndex) == 0 {\n\t\t// Insert the first line.\n\t\tlastLine = &textViewLine{\n\t\t\tstate: &stepState{\n\t\t\t\tunisegState: -1,\n\t\t\t\tstyle:       t.textStyle,\n\t\t\t},\n\t\t}\n\t\tt.lineIndex = append(t.lineIndex, lastLine)\n\t} else {\n\t\t// Reset the last line.\n\t\tlastLine = t.lineIndex[len(t.lineIndex)-1]\n\t\tlastLine.width = 0\n\t\tlastLine.length = 0\n\t\tstr = str[lastLine.offset:]\n\t}\n\tif len(lastLine.regions) > 0 {\n\t\tregion = lastLine.regions[0]\n\t\tlastLine.regions = lastLine.regions[:1]\n\t}\n\n\t// Parse.\n\tvar (\n\t\tlastOption       int               // Text index of the last optional split point, relative to the beginning of the line.\n\t\tlastOptionWidth  int               // Line width at last optional split point.\n\t\tlastOptionState  *stepState        // State at last optional split point.\n\t\tlastOptionRegion *Region           // Region at last optional split point or nil if none.\n\t\tleftPos          int               // The current position in the line (assuming left-alignment).\n\t\toffset           = lastLine.offset // Text index of the current position.\n\t\tst               = *lastLine.state // Current state.\n\t\tstate            = &st             // Pointer to current state.\n\t)\n\tfor len(str) > 0 {\n\t\tvar c string\n\t\tc, str, state = step(str, state, options)\n\t\tw := state.Width()\n\t\tif c == \"\\t\" {\n\t\t\tif t.align == AlignLeft {\n\t\t\t\tw = TabSize - leftPos%TabSize\n\t\t\t} else {\n\t\t\t\tw = TabSize\n\t\t\t}\n\t\t}\n\t\tlength := state.GrossLength()\n\n\t\t// Would it exceed the line width?\n\t\tif t.wrap && lastLine.width+w > width {\n\t\t\tif lastOptionWidth == 0 {\n\t\t\t\t// No split point so far. Just split at the current position.\n\t\t\t\tif stop(len(t.lineIndex)-1, lastLine) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tst := *state\n\t\t\t\tlastLine = &textViewLine{\n\t\t\t\t\toffset: offset,\n\t\t\t\t\tstate:  &st,\n\t\t\t\t}\n\t\t\t\tif state.region != \"\" {\n\t\t\t\t\tif state.region == region.ID {\n\t\t\t\t\t\tlastLine.regions = []*Region{region}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tregion = &Region{\n\t\t\t\t\t\t\tID:          state.region,\n\t\t\t\t\t\t\tStart:       offset,\n\t\t\t\t\t\t\tStartRow:    len(t.lineIndex),\n\t\t\t\t\t\t\tStartColumn: 0,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tregion = nil\n\t\t\t\t}\n\t\t\t\tlastOption, lastOptionWidth, leftPos = 0, 0, 0\n\t\t\t} else {\n\t\t\t\t// Split at the last split point.\n\t\t\t\tnewLine := &textViewLine{\n\t\t\t\t\toffset: lastLine.offset + lastOption,\n\t\t\t\t\twidth:  lastLine.width - lastOptionWidth,\n\t\t\t\t\tlength: lastLine.length - lastOption,\n\t\t\t\t\tstate:  lastOptionState,\n\t\t\t\t}\n\t\t\t\tlastLine.width = lastOptionWidth\n\t\t\t\tlastLine.length = lastOption\n\t\t\t\tif stop(len(t.lineIndex)-1, lastLine) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlastLine = newLine\n\t\t\t\tif lastOptionRegion != nil {\n\t\t\t\t\tlastLine.regions = []*Region{lastOptionRegion}\n\t\t\t\t}\n\t\t\t\tregion = lastOptionRegion\n\t\t\t\tleftPos -= lastOptionWidth\n\t\t\t\tlastOption, lastOptionWidth = 0, 0\n\t\t\t}\n\t\t\tt.lineIndex = append(t.lineIndex, lastLine)\n\t\t}\n\n\t\t// Is there a region switch?\n\t\tif state.region != \"\" && (region == nil || state.region != region.ID) {\n\t\t\t// Start a new region.\n\t\t\tregion = &Region{\n\t\t\t\tID:          state.region,\n\t\t\t\tStart:       offset,\n\t\t\t\tStartRow:    len(t.lineIndex) - 1,\n\t\t\t\tStartColumn: leftPos,\n\t\t\t}\n\t\t\tlastLine.regions = append(lastLine.regions, region)\n\t\t\tif _, ok := t.regions[state.region]; !ok {\n\t\t\t\tt.regions[state.region] = len(t.lineIndex) - 1\n\t\t\t}\n\t\t} else if state.region == \"\" && region != nil {\n\t\t\t// End the previous region.\n\t\t\tregion = nil\n\t\t}\n\n\t\t// Move ahead.\n\t\tlastLine.width += w\n\t\tlastLine.length += length\n\t\toffset += length\n\t\tleftPos += w\n\t\tif region != nil {\n\t\t\tregion.End = offset\n\t\t\tregion.EndRow = len(t.lineIndex) - 1\n\t\t\tregion.EndColumn = leftPos\n\t\t}\n\n\t\t// Do we have a new longest line?\n\t\tif lastLine.width > t.longestLine {\n\t\t\tt.longestLine = lastLine.width\n\t\t}\n\n\t\t// Check for split points.\n\t\tif lineBreak, optional := state.LineBreak(); lineBreak {\n\t\t\tif optional {\n\t\t\t\tif t.wrap && t.wordWrap {\n\t\t\t\t\t// Remember this split point.\n\t\t\t\t\tlastOption = offset - lastLine.offset\n\t\t\t\t\tlastOptionWidth = lastLine.width\n\t\t\t\t\tst := *state\n\t\t\t\t\tlastOptionState = &st\n\t\t\t\t\tlastOptionRegion = region\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// We must split here.\n\t\t\t\tif stop(len(t.lineIndex)-1, lastLine) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tst := *state\n\t\t\t\tlastLine = &textViewLine{\n\t\t\t\t\toffset: offset,\n\t\t\t\t\tstate:  &st,\n\t\t\t\t}\n\t\t\t\tif region != nil {\n\t\t\t\t\tlastLine.regions = []*Region{region}\n\t\t\t\t}\n\t\t\t\tt.lineIndex = append(t.lineIndex, lastLine)\n\t\t\t\tlastOption, lastOptionWidth, leftPos = 0, 0, 0\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Draw draws this primitive onto the screen.\nfunc (t *TextView) Draw(screen tcell.Screen) {\n\tt.Box.DrawForSubclass(screen, t)\n\tt.Lock()\n\tdefer t.Unlock()\n\n\t// Get the available size.\n\tx, y, width, height := t.GetInnerRect()\n\tt.pageSize = height\n\n\t// Draw label.\n\t_, labelBg, _ := t.labelStyle.Decompose()\n\tif t.labelWidth > 0 {\n\t\tlabelWidth := t.labelWidth\n\t\tif labelWidth > width {\n\t\t\tlabelWidth = width\n\t\t}\n\t\tprintWithStyle(screen, t.label, x, y, 0, labelWidth, AlignLeft, t.labelStyle, labelBg == tcell.ColorDefault)\n\t\tx += labelWidth\n\t\twidth -= labelWidth\n\t} else {\n\t\t_, _, drawnWidth := printWithStyle(screen, t.label, x, y, 0, width, AlignLeft, t.labelStyle, labelBg == tcell.ColorDefault)\n\t\tx += drawnWidth\n\t\twidth -= drawnWidth\n\t}\n\n\t// What's the space for the text element?\n\tif t.width > 0 && t.width < width {\n\t\twidth = t.width\n\t}\n\tif t.height > 0 && t.height < height {\n\t\theight = t.height\n\t}\n\tif width <= 0 {\n\t\treturn // No space left for the text area.\n\t}\n\n\t// Draw the text element if necessary.\n\t_, bg, _ := t.textStyle.Decompose()\n\tif bg != t.backgroundColor {\n\t\tfor row := 0; row < height; row++ {\n\t\t\tfor column := 0; column < width; column++ {\n\t\t\t\tscreen.SetContent(x+column, y+row, ' ', nil, t.textStyle)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If the width has changed, we need to reindex.\n\tif width != t.lastWidth && t.wrap {\n\t\tt.resetIndex()\n\t}\n\tt.lastWidth = width\n\n\t// What are our parse options?\n\tvar options stepOptions\n\tif t.styleTags {\n\t\toptions |= stepOptionsStyle\n\t}\n\tif t.regionTags {\n\t\toptions |= stepOptionsRegion\n\t}\n\n\t// Scroll to highlighted regions.\n\tif t.regionTags && t.scrollToHighlights {\n\t\t// Make sure we know all highlighted regions.\n\t\tt.parseAhead(width, func(lineNumber int, line *textViewLine) bool {\n\t\t\tfor regionID := range t.highlights {\n\t\t\t\tif _, ok := t.regions[regionID]; !ok {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tt.highlights[regionID] = struct{}{}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\n\t\t// What is the line range for all highlighted regions?\n\t\tvar (\n\t\t\tfirstRegion                string\n\t\t\tfromHighlight, toHighlight int\n\t\t)\n\t\tfor regionID := range t.highlights {\n\t\t\t// We can safely assume that the region is known.\n\t\t\tline := t.regions[regionID]\n\t\t\tif firstRegion == \"\" || line > toHighlight {\n\t\t\t\ttoHighlight = line\n\t\t\t}\n\t\t\tif firstRegion == \"\" || line < fromHighlight {\n\t\t\t\tfromHighlight = line\n\t\t\t\tfirstRegion = regionID\n\t\t\t}\n\t\t}\n\t\tif firstRegion != \"\" {\n\t\t\t// Do we fit the entire height?\n\t\t\tif toHighlight-fromHighlight+1 < height {\n\t\t\t\t// Yes, let's center the highlights.\n\t\t\t\tt.lineOffset = (fromHighlight + toHighlight - height) / 2\n\t\t\t} else {\n\t\t\t\t// No, let's move to the start of the highlights.\n\t\t\t\tt.lineOffset = fromHighlight\n\t\t\t}\n\n\t\t\t// If the highlight is too far to the right, move it to the middle.\n\t\t\tif t.wrap {\n\t\t\t\t// Find the first highlight's column in screen space.\n\t\t\t\tline := t.lineIndex[fromHighlight]\n\t\t\t\tst := *line.state\n\t\t\t\tstate := &st\n\t\t\t\tstr := t.text.String()[line.offset:]\n\t\t\t\tvar posHighlight int\n\t\t\t\tfor len(str) > 0 && posHighlight < line.width && state.region != firstRegion {\n\t\t\t\t\t_, str, state = step(str, state, options)\n\t\t\t\t\tposHighlight += state.Width()\n\t\t\t\t}\n\n\t\t\t\tif posHighlight-t.columnOffset > 3*width/4 {\n\t\t\t\t\tt.columnOffset = posHighlight - width/2\n\t\t\t\t}\n\n\t\t\t\t// If the highlight is off-screen on the left, move it on-screen.\n\t\t\t\tif posHighlight-t.columnOffset < 0 {\n\t\t\t\t\tt.columnOffset = posHighlight - width/4\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tt.scrollToHighlights = false\n\n\t// Make sure our index has enough lines.\n\tt.parseAhead(width, func(lineNumber int, line *textViewLine) bool {\n\t\treturn lineNumber >= t.lineOffset+height\n\t})\n\n\t// Adjust line offset.\n\tif t.trackEnd {\n\t\tt.parseAhead(width, func(lineNumber int, line *textViewLine) bool {\n\t\t\treturn false\n\t\t})\n\t\tt.lineOffset = len(t.lineIndex) - height\n\t}\n\tif t.lineOffset > len(t.lineIndex)-height {\n\t\tt.lineOffset = len(t.lineIndex) - height\n\t}\n\tif t.lineOffset < 0 {\n\t\tt.lineOffset = 0\n\t}\n\n\t// Adjust column offset.\n\tif t.align == AlignLeft || t.align == AlignRight {\n\t\tif t.columnOffset+width > t.longestLine {\n\t\t\tt.columnOffset = t.longestLine - width\n\t\t}\n\t\tif t.columnOffset < 0 {\n\t\t\tt.columnOffset = 0\n\t\t}\n\t} else { // AlignCenter.\n\t\thalf := (t.longestLine - width) / 2\n\t\tif half > 0 {\n\t\t\tif t.columnOffset > half {\n\t\t\t\tt.columnOffset = half\n\t\t\t}\n\t\t\tif t.columnOffset < -half {\n\t\t\t\tt.columnOffset = -half\n\t\t\t}\n\t\t} else {\n\t\t\tt.columnOffset = 0\n\t\t}\n\t}\n\n\t// Draw visible lines.\n\tfor line := t.lineOffset; line < len(t.lineIndex); line++ {\n\t\t// Are we done?\n\t\tif line-t.lineOffset >= height {\n\t\t\tbreak\n\t\t}\n\n\t\t// Determine starting point of the text and the screen.\n\t\tvar skipWidth, xPos int\n\t\tinfo := t.lineIndex[line]\n\t\tswitch t.align {\n\t\tcase AlignLeft:\n\t\t\tskipWidth = t.columnOffset\n\t\tcase AlignCenter:\n\t\t\tskipWidth = t.columnOffset + (info.width-width)/2\n\t\t\tif skipWidth < 0 {\n\t\t\t\tskipWidth = 0\n\t\t\t\txPos = (width-info.width)/2 - t.columnOffset\n\t\t\t}\n\t\tcase AlignRight:\n\t\t\tmaxWidth := width\n\t\t\tif t.longestLine > width {\n\t\t\t\tmaxWidth = t.longestLine\n\t\t\t}\n\t\t\tskipWidth = t.columnOffset - (maxWidth - info.width)\n\t\t\tif skipWidth < 0 {\n\t\t\t\tskipWidth = 0\n\t\t\t\txPos = maxWidth - info.width - t.columnOffset\n\t\t\t}\n\t\t}\n\n\t\t// Draw the line text.\n\t\tstr := t.text.String()[info.offset:]\n\t\tst := *info.state\n\t\tstate := &st\n\t\tvar processed int\n\t\tfor len(str) > 0 && xPos < width && processed < info.length {\n\t\t\tvar ch string\n\t\t\tch, str, state = step(str, state, options)\n\t\t\tw := state.Width()\n\t\t\tif ch == \"\\t\" {\n\t\t\t\tif t.align == AlignLeft {\n\t\t\t\t\tw = TabSize - xPos%TabSize\n\t\t\t\t} else {\n\t\t\t\t\tw = TabSize\n\t\t\t\t}\n\t\t\t}\n\t\t\tprocessed += state.GrossLength()\n\n\t\t\t// Don't draw anything while we skip characters.\n\t\t\tif skipWidth > 0 {\n\t\t\t\tskipWidth -= w\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Draw this character.\n\t\t\tif w > 0 {\n\t\t\t\tstyle := state.Style()\n\n\t\t\t\t// Do we highlight this character?\n\t\t\t\tvar highlighted bool\n\t\t\t\tif state.region != \"\" {\n\t\t\t\t\tif _, ok := t.highlights[state.region]; ok {\n\t\t\t\t\t\thighlighted = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif highlighted {\n\t\t\t\t\tfg, bg, _ := style.Decompose()\n\t\t\t\t\tif bg == t.backgroundColor {\n\t\t\t\t\t\tr, g, b := fg.RGB()\n\t\t\t\t\t\tc := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}\n\t\t\t\t\t\t_, _, li := c.Hcl()\n\t\t\t\t\t\tif li < .5 {\n\t\t\t\t\t\t\tbg = tcell.ColorWhite\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbg = tcell.ColorBlack\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tstyle = style.Background(fg).Foreground(bg)\n\t\t\t\t}\n\n\t\t\t\t// Paint on screen.\n\t\t\t\tfor offset := w - 1; offset >= 0; offset-- {\n\t\t\t\t\trunes := []rune(ch)\n\t\t\t\t\tif offset == 0 {\n\t\t\t\t\t\tscreen.SetContent(x+xPos+offset, y+line-t.lineOffset, runes[0], runes[1:], style)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tscreen.SetContent(x+xPos+offset, y+line-t.lineOffset, ' ', nil, style)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\txPos += w\n\t\t}\n\t}\n\n\t// If this view is not scrollable, we'll purge the buffer of lines that have\n\t// scrolled out of view.\n\tvar purgeStart int\n\tif !t.scrollable && t.lineOffset > 0 {\n\t\tpurgeStart = t.lineOffset\n\t}\n\n\t// If we reached the maximum number of lines, we'll purge the buffer of the\n\t// oldest lines.\n\tif t.maxLines > 0 && len(t.lineIndex) > t.maxLines {\n\t\tpurgeStart = len(t.lineIndex) - t.maxLines\n\t}\n\n\t// Purge.\n\tif purgeStart > 0 && purgeStart < len(t.lineIndex) {\n\t\tnewText := t.text.String()[t.lineIndex[purgeStart].offset:]\n\t\tt.text.Reset()\n\t\tt.text.WriteString(newText)\n\t\tt.resetIndex()\n\t\tt.lineOffset = 0\n\t}\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tkey := event.Key()\n\n\t\tif key == tcell.KeyEscape || key == tcell.KeyEnter || key == tcell.KeyTab || key == tcell.KeyBacktab {\n\t\t\tif t.done != nil {\n\t\t\t\tt.done(key)\n\t\t\t}\n\t\t\tif t.finished != nil {\n\t\t\t\tt.finished(key)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif !t.scrollable {\n\t\t\treturn\n\t\t}\n\n\t\tswitch key {\n\t\tcase tcell.KeyRune:\n\t\t\tswitch event.Rune() {\n\t\t\tcase 'g': // Home.\n\t\t\t\tt.trackEnd = false\n\t\t\t\tt.lineOffset = 0\n\t\t\t\tt.columnOffset = 0\n\t\t\tcase 'G': // End.\n\t\t\t\tt.trackEnd = true\n\t\t\t\tt.columnOffset = 0\n\t\t\tcase 'j': // Down.\n\t\t\t\tt.lineOffset++\n\t\t\tcase 'k': // Up.\n\t\t\t\tt.trackEnd = false\n\t\t\t\tt.lineOffset--\n\t\t\tcase 'h': // Left.\n\t\t\t\tt.columnOffset--\n\t\t\tcase 'l': // Right.\n\t\t\t\tt.columnOffset++\n\t\t\t}\n\t\tcase tcell.KeyHome:\n\t\t\tt.trackEnd = false\n\t\t\tt.lineOffset = 0\n\t\t\tt.columnOffset = 0\n\t\tcase tcell.KeyEnd:\n\t\t\tt.trackEnd = true\n\t\t\tt.columnOffset = 0\n\t\tcase tcell.KeyUp:\n\t\t\tt.trackEnd = false\n\t\t\tt.lineOffset--\n\t\tcase tcell.KeyDown:\n\t\t\tt.lineOffset++\n\t\tcase tcell.KeyLeft:\n\t\t\tt.columnOffset--\n\t\tcase tcell.KeyRight:\n\t\t\tt.columnOffset++\n\t\tcase tcell.KeyPgDn, tcell.KeyCtrlF:\n\t\t\tt.lineOffset += t.pageSize\n\t\tcase tcell.KeyPgUp, tcell.KeyCtrlB:\n\t\t\tt.trackEnd = false\n\t\t\tt.lineOffset -= t.pageSize\n\t\t}\n\t})\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (t *TextView) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tx, y := event.Position()\n\t\tif !t.InRect(x, y) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\trectX, rectY, width, height := t.GetInnerRect()\n\t\tswitch action {\n\t\tcase MouseLeftDown:\n\t\t\tsetFocus(t)\n\t\t\tconsumed = true\n\t\tcase MouseLeftClick:\n\t\t\tif t.regionTags && t.InInnerRect(x, y) {\n\t\t\t\t// Find a region to highlight.\n\t\t\t\tcolumn := x - rectX\n\t\t\t\trow := y - rectY + t.lineOffset\n\t\t\t\tvar highlightedID string\n\t\t\t\tif row < len(t.lineIndex) {\n\t\t\t\t\tline := t.lineIndex[row]\n\t\t\t\t\tfor _, region := range line.regions {\n\t\t\t\t\t\tif !(row < region.StartRow ||\n\t\t\t\t\t\t\trow > region.EndRow ||\n\t\t\t\t\t\t\t(row == region.StartRow && column < region.StartColumn) ||\n\t\t\t\t\t\t\t(row == region.EndRow && column >= region.EndColumn)) {\n\t\t\t\t\t\t\thighlightedID = region.ID\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif highlightedID != \"\" {\n\t\t\t\t\tt.Highlight(highlightedID)\n\t\t\t\t} else if !t.toggleHighlights {\n\t\t\t\t\tt.Highlight()\n\t\t\t\t}\n\t\t\t}\n\t\t\tconsumed = true\n\t\tcase MouseScrollUp:\n\t\t\tif !t.scrollable {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tt.trackEnd = false\n\t\t\tt.lineOffset--\n\t\t\tconsumed = true\n\t\tcase MouseScrollDown:\n\t\t\tif !t.scrollable {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tt.lineOffset++\n\t\t\tif len(t.lineIndex)-t.lineOffset < height {\n\t\t\t\t// If we scroll to the end, turn on tracking.\n\t\t\t\tt.parseAhead(width, func(lineNumber int, line *textViewLine) bool {\n\t\t\t\t\treturn len(t.lineIndex)-t.lineOffset < height\n\t\t\t\t})\n\t\t\t\tif len(t.lineIndex)-t.lineOffset < height {\n\t\t\t\t\tt.trackEnd = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tconsumed = true\n\t\t}\n\n\t\treturn\n\t})\n}\n"
  },
  {
    "path": "treeview.go",
    "content": "package tview\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// Tree navigation events.\nconst (\n\ttreeNone int = iota\n\ttreeHome\n\ttreeEnd\n\ttreeMove\n\ttreeParent\n\ttreeChild\n\ttreeScroll // Move without changing the selection, even when off screen.\n)\n\n// TreeNode represents one node in a tree view.\ntype TreeNode struct {\n\t// The reference object.\n\treference interface{}\n\n\t// This node's child nodes.\n\tchildren []*TreeNode\n\n\t// The item's text.\n\ttext string\n\n\t// The text style.\n\ttextStyle tcell.Style\n\n\t// The style of selected text.\n\tselectedTextStyle tcell.Style\n\n\t// Whether or not this node can be selected.\n\tselectable bool\n\n\t// Whether or not this node's children should be displayed.\n\texpanded bool\n\n\t// The additional horizontal indent of this node's text.\n\tindent int\n\n\t// An optional function which is called when the user selects this node.\n\tselected func()\n\n\t// The hierarchy level (0 for the root, 1 for its children, and so on). This\n\t// is only up to date immediately after a call to process() (e.g. via\n\t// Draw()).\n\tlevel int\n\n\t// Temporary member variables.\n\tparent    *TreeNode // The parent node (nil for the root).\n\tgraphicsX int       // The x-coordinate of the left-most graphics rune.\n\ttextX     int       // The x-coordinate of the first rune of the text.\n}\n\n// NewTreeNode returns a new tree node.\nfunc NewTreeNode(text string) *TreeNode {\n\treturn &TreeNode{\n\t\ttext:              text,\n\t\ttextStyle:         tcell.StyleDefault.Foreground(Styles.PrimaryTextColor).Background(Styles.PrimitiveBackgroundColor),\n\t\tselectedTextStyle: tcell.StyleDefault.Foreground(Styles.PrimitiveBackgroundColor).Background(Styles.PrimaryTextColor),\n\t\tindent:            2,\n\t\texpanded:          true,\n\t\tselectable:        true,\n\t}\n}\n\n// Walk traverses this node's subtree in depth-first, pre-order (NLR) order and\n// calls the provided callback function on each traversed node (which includes\n// this node) with the traversed node and its parent node (nil for this node).\n// The callback returns whether traversal should continue with the traversed\n// node's child nodes (true) or not recurse any deeper (false).\nfunc (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode {\n\tn.parent = nil\n\tnodes := []*TreeNode{n}\n\tfor len(nodes) > 0 {\n\t\t// Pop the top node and process it.\n\t\tnode := nodes[len(nodes)-1]\n\t\tnodes = nodes[:len(nodes)-1]\n\t\tif !callback(node, node.parent) {\n\t\t\t// Don't add any children.\n\t\t\tcontinue\n\t\t}\n\n\t\t// Add children in reverse order.\n\t\tfor index := len(node.children) - 1; index >= 0; index-- {\n\t\t\tnode.children[index].parent = node\n\t\t\tnodes = append(nodes, node.children[index])\n\t\t}\n\t}\n\n\treturn n\n}\n\n// SetReference allows you to store a reference of any type in this node. This\n// will allow you to establish a mapping between the TreeView hierarchy and your\n// internal tree structure.\nfunc (n *TreeNode) SetReference(reference interface{}) *TreeNode {\n\tn.reference = reference\n\treturn n\n}\n\n// GetReference returns this node's reference object.\nfunc (n *TreeNode) GetReference() interface{} {\n\treturn n.reference\n}\n\n// SetChildren sets this node's child nodes.\nfunc (n *TreeNode) SetChildren(childNodes []*TreeNode) *TreeNode {\n\tn.children = childNodes\n\treturn n\n}\n\n// GetText returns this node's text.\nfunc (n *TreeNode) GetText() string {\n\treturn n.text\n}\n\n// GetChildren returns this node's children.\nfunc (n *TreeNode) GetChildren() []*TreeNode {\n\treturn n.children\n}\n\n// ClearChildren removes all child nodes from this node.\nfunc (n *TreeNode) ClearChildren() *TreeNode {\n\tn.children = nil\n\treturn n\n}\n\n// AddChild adds a new child node to this node.\nfunc (n *TreeNode) AddChild(node *TreeNode) *TreeNode {\n\tn.children = append(n.children, node)\n\treturn n\n}\n\n// RemoveChild removes a child node from this node. If the child node cannot be\n// found, nothing happens.\nfunc (n *TreeNode) RemoveChild(node *TreeNode) *TreeNode {\n\tfor index, child := range n.children {\n\t\tif child == node {\n\t\t\tn.children = append(n.children[:index], n.children[index+1:]...)\n\t\t\tbreak\n\t\t}\n\t}\n\treturn n\n}\n\n// SetSelectable sets a flag indicating whether this node can be selected by\n// the user.\nfunc (n *TreeNode) SetSelectable(selectable bool) *TreeNode {\n\tn.selectable = selectable\n\treturn n\n}\n\n// SetSelectedFunc sets a function which is called when the user selects this\n// node by hitting Enter when it is selected.\nfunc (n *TreeNode) SetSelectedFunc(handler func()) *TreeNode {\n\tn.selected = handler\n\treturn n\n}\n\n// SetExpanded sets whether or not this node's child nodes should be displayed.\nfunc (n *TreeNode) SetExpanded(expanded bool) *TreeNode {\n\tn.expanded = expanded\n\treturn n\n}\n\n// Expand makes the child nodes of this node appear.\nfunc (n *TreeNode) Expand() *TreeNode {\n\tn.expanded = true\n\treturn n\n}\n\n// Collapse makes the child nodes of this node disappear.\nfunc (n *TreeNode) Collapse() *TreeNode {\n\tn.expanded = false\n\treturn n\n}\n\n// ExpandAll expands this node and all descendent nodes.\nfunc (n *TreeNode) ExpandAll() *TreeNode {\n\tn.Walk(func(node, parent *TreeNode) bool {\n\t\tnode.expanded = true\n\t\treturn true\n\t})\n\treturn n\n}\n\n// CollapseAll collapses this node and all descendent nodes.\nfunc (n *TreeNode) CollapseAll() *TreeNode {\n\tn.Walk(func(node, parent *TreeNode) bool {\n\t\tnode.expanded = false\n\t\treturn true\n\t})\n\treturn n\n}\n\n// IsExpanded returns whether the child nodes of this node are visible.\nfunc (n *TreeNode) IsExpanded() bool {\n\treturn n.expanded\n}\n\n// SetText sets the node's text which is displayed.\nfunc (n *TreeNode) SetText(text string) *TreeNode {\n\tn.text = text\n\treturn n\n}\n\n// GetColor returns the node's text color.\nfunc (n *TreeNode) GetColor() tcell.Color {\n\tcolor, _, _ := n.textStyle.Decompose()\n\treturn color\n}\n\n// SetColor sets the node's text color. For compatibility reasons, this also\n// sets the background color of the selected text style. For more control over\n// styles, use [TreeNode.SetTextStyle] and [TreeNode.SetSelectedTextStyle].\nfunc (n *TreeNode) SetColor(color tcell.Color) *TreeNode {\n\tn.textStyle = n.textStyle.Foreground(color)\n\tn.selectedTextStyle = n.selectedTextStyle.Background(color)\n\treturn n\n}\n\n// SetTextStyle sets the text style for this node.\nfunc (n *TreeNode) SetTextStyle(style tcell.Style) *TreeNode {\n\tn.textStyle = style\n\treturn n\n}\n\n// GetTextStyle returns the text style for this node.\nfunc (n *TreeNode) GetTextStyle() tcell.Style {\n\treturn n.textStyle\n}\n\n// SetSelectedTextStyle sets the text style for this node when it is selected.\nfunc (n *TreeNode) SetSelectedTextStyle(style tcell.Style) *TreeNode {\n\tn.selectedTextStyle = style\n\treturn n\n}\n\n// GetSelectedTextStyle returns the text style for this node when it is\n// selected.\nfunc (n *TreeNode) GetSelectedTextStyle() tcell.Style {\n\treturn n.selectedTextStyle\n}\n\n// SetIndent sets an additional indentation for this node's text. A value of 0\n// keeps the text as far left as possible with a minimum of line graphics. Any\n// value greater than that moves the text to the right.\nfunc (n *TreeNode) SetIndent(indent int) *TreeNode {\n\tn.indent = indent\n\treturn n\n}\n\n// GetLevel returns the node's level within the hierarchy, where 0 corresponds\n// to the root node, 1 corresponds to its children, and so on. This is only\n// guaranteed to be up to date immediately after the tree that contains this\n// node is drawn.\nfunc (n *TreeNode) GetLevel() int {\n\treturn n.level\n}\n\n// TreeView displays tree structures. A tree consists of nodes (TreeNode\n// objects) where each node has zero or more child nodes and exactly one parent\n// node (except for the root node which has no parent node).\n//\n// The SetRoot() function is used to specify the root of the tree. Other nodes\n// are added locally to the root node or any of its descendents. See the\n// TreeNode documentation for details on node attributes. (You can use\n// SetReference() to store a reference to nodes of your own tree structure.)\n//\n// Nodes can be selected by calling SetCurrentNode(). The user can navigate the\n// selection or the tree by using the following keys:\n//\n//   - j, down arrow, right arrow: Move (the selection) down by one node.\n//   - k, up arrow, left arrow: Move (the selection) up by one node.\n//   - g, home: Move (the selection) to the top.\n//   - G, end: Move (the selection) to the bottom.\n//   - J: Move (the selection) up one level (if that node is selectable).\n//   - K: Move (the selection) to the last node one level down (if any).\n//   - Ctrl-F, page down: Move (the selection) down by one page.\n//   - Ctrl-B, page up: Move (the selection) up by one page.\n//\n// Selected nodes can trigger the \"selected\" callback when the user hits Enter.\n//\n// The root node corresponds to level 0, its children correspond to level 1,\n// their children to level 2, and so on. Per default, the first level that is\n// displayed is 0, i.e. the root node. You can call SetTopLevel() to hide\n// levels.\n//\n// If graphics are turned on (see SetGraphics()), lines indicate the tree's\n// hierarchy. Alternative (or additionally), you can set different prefixes\n// using SetPrefixes() for different levels, for example to display hierarchical\n// bullet point lists.\n//\n// See https://github.com/rivo/tview/wiki/TreeView for an example.\ntype TreeView struct {\n\t*Box\n\n\t// The root node.\n\troot *TreeNode\n\n\t// The currently selected node or nil if no node is selected.\n\tcurrentNode *TreeNode\n\n\t// The last note that was selected or nil of there is no such node.\n\tlastNode *TreeNode\n\n\t// The movement to be performed during the call to Draw(), one of the\n\t// constants defined above.\n\tmovement int\n\n\t// The number of nodes to move down or up, when movement is treeMove,\n\t// excluding non-selectable nodes for selection movement, including them for\n\t// scrolling.\n\tstep int\n\n\t// The top hierarchical level shown. (0 corresponds to the root level.)\n\ttopLevel int\n\n\t// Strings drawn before the nodes, based on their level.\n\tprefixes []string\n\n\t// Vertical scroll offset.\n\toffsetY int\n\n\t// If set to true, all node texts will be aligned horizontally.\n\talign bool\n\n\t// If set to true, the tree structure is drawn using lines.\n\tgraphics bool\n\n\t// The color of the lines.\n\tgraphicsColor tcell.Color\n\n\t// An optional function which is called when the user has navigated to a new\n\t// tree node.\n\tchanged func(node *TreeNode)\n\n\t// An optional function which is called when a tree item was selected.\n\tselected func(node *TreeNode)\n\n\t// An optional function which is called when the user moves away from this\n\t// primitive.\n\tdone func(key tcell.Key)\n\n\t// The visible nodes, top-down, as set by process().\n\tnodes []*TreeNode\n\n\t// Temporarily set to true while we know that the tree has not changed and\n\t// therefore does not need to be reprocessed.\n\tstableNodes bool\n}\n\n// NewTreeView returns a new [TreeView].\nfunc NewTreeView() *TreeView {\n\tt := &TreeView{\n\t\tBox:           NewBox(),\n\t\tgraphics:      true,\n\t\tgraphicsColor: Styles.GraphicsColor,\n\t}\n\tt.Box.Primitive = t\n\treturn t\n}\n\n// SetRoot sets the root node of the tree.\nfunc (t *TreeView) SetRoot(root *TreeNode) *TreeView {\n\tt.root = root\n\treturn t\n}\n\n// GetRoot returns the root node of the tree. If no such node was previously\n// set, nil is returned.\nfunc (t *TreeView) GetRoot() *TreeNode {\n\treturn t.root\n}\n\n// SetCurrentNode sets the currently selected node. Provide nil to clear all\n// selections. Selected nodes must be visible and selectable, or else the\n// selection will be changed to the top-most selectable and visible node.\n//\n// This function does NOT trigger the \"changed\" callback because the actual node\n// that will be selected is not known until the tree is drawn. Triggering the\n// \"changed\" callback is thus deferred until the next call to [TreeView.Draw].\nfunc (t *TreeView) SetCurrentNode(node *TreeNode) *TreeView {\n\tt.currentNode = node\n\treturn t\n}\n\n// GetCurrentNode returns the currently selected node or nil of no node is\n// currently selected.\nfunc (t *TreeView) GetCurrentNode() *TreeNode {\n\treturn t.currentNode\n}\n\n// GetPath returns all nodes located on the path from the root to the given\n// node, including the root and the node itself. If there is no root node, nil\n// is returned. If there are multiple paths to the node, a random one is chosen\n// and returned.\nfunc (t *TreeView) GetPath(node *TreeNode) []*TreeNode {\n\tif t.root == nil {\n\t\treturn nil\n\t}\n\n\tvar f func(current *TreeNode, path []*TreeNode) []*TreeNode\n\tf = func(current *TreeNode, path []*TreeNode) []*TreeNode {\n\t\tif current == node {\n\t\t\treturn path\n\t\t}\n\n\t\tfor _, child := range current.children {\n\t\t\tnewPath := make([]*TreeNode, len(path), len(path)+1)\n\t\t\tcopy(newPath, path)\n\t\t\tif p := f(child, append(newPath, child)); p != nil {\n\t\t\t\treturn p\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn f(t.root, []*TreeNode{t.root})\n}\n\n// SetTopLevel sets the first tree level that is visible with 0 referring to the\n// root, 1 to the root's child nodes, and so on. Nodes above the top level are\n// not displayed.\nfunc (t *TreeView) SetTopLevel(topLevel int) *TreeView {\n\tt.topLevel = topLevel\n\treturn t\n}\n\n// SetPrefixes defines the strings drawn before the nodes' texts. This is a\n// slice of strings where each element corresponds to a node's hierarchy level,\n// i.e. 0 for the root, 1 for the root's children, and so on (levels will\n// cycle).\n//\n// For example, to display a hierarchical list with bullet points:\n//\n//\ttreeView.SetGraphics(false).\n//\t  SetPrefixes([]string{\"* \", \"- \", \"x \"})\n//\n// Deeper levels will cycle through the prefixes.\nfunc (t *TreeView) SetPrefixes(prefixes []string) *TreeView {\n\tt.prefixes = prefixes\n\treturn t\n}\n\n// SetAlign controls the horizontal alignment of the node texts. If set to true,\n// all texts except that of top-level nodes will be placed in the same column.\n// If set to false, they will indent with the hierarchy.\nfunc (t *TreeView) SetAlign(align bool) *TreeView {\n\tt.align = align\n\treturn t\n}\n\n// SetGraphics sets a flag which determines whether or not line graphics are\n// drawn to illustrate the tree's hierarchy.\nfunc (t *TreeView) SetGraphics(showGraphics bool) *TreeView {\n\tt.graphics = showGraphics\n\treturn t\n}\n\n// SetGraphicsColor sets the colors of the lines used to draw the tree structure.\nfunc (t *TreeView) SetGraphicsColor(color tcell.Color) *TreeView {\n\tt.graphicsColor = color\n\treturn t\n}\n\n// SetChangedFunc sets the function which is called when the currently selected\n// node changes, for example when the user navigates to a new tree node.\nfunc (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView {\n\tt.changed = handler\n\treturn t\n}\n\n// SetSelectedFunc sets the function which is called when the user selects a\n// node by pressing Enter on the current selection.\nfunc (t *TreeView) SetSelectedFunc(handler func(node *TreeNode)) *TreeView {\n\tt.selected = handler\n\treturn t\n}\n\n// GetSelectedFunc returns the function set with [TreeView.SetSelectedFunc]\n// or nil if no such function has been set.\nfunc (t *TreeView) GetSelectedFunc() func(node *TreeNode) {\n\treturn t.selected\n}\n\n// SetDoneFunc sets a handler which is called whenever the user presses the\n// Escape, Tab, or Backtab key.\nfunc (t *TreeView) SetDoneFunc(handler func(key tcell.Key)) *TreeView {\n\tt.done = handler\n\treturn t\n}\n\n// GetScrollOffset returns the number of node rows that were skipped at the top\n// of the tree view. Note that when the user navigates the tree view, this value\n// is only updated after the tree view has been redrawn.\nfunc (t *TreeView) GetScrollOffset() int {\n\treturn t.offsetY\n}\n\n// GetRowCount returns the number of \"visible\" nodes. This includes nodes which\n// fall outside the tree view's box but notably does not include the children\n// of collapsed nodes. Note that this value is only up to date after the tree\n// view has been drawn.\nfunc (t *TreeView) GetRowCount() int {\n\treturn len(t.nodes)\n}\n\n// Move moves the selection (if a node is currently selected) or scrolls the\n// tree view (if there is no selection), by the given offset (positive values to\n// move/scroll down, negative values to move/scroll up). For selection changes,\n// the offset refers to the number selectable, visible nodes. For scrolling, the\n// offset refers to the number of visible nodes.\n//\n// If the offset is 0, nothing happens.\nfunc (t *TreeView) Move(offset int) *TreeView {\n\tif offset == 0 {\n\t\treturn t\n\t}\n\tt.movement = treeMove\n\tt.step = offset\n\tt.process(false)\n\treturn t\n}\n\n// process builds the visible tree, populates the \"nodes\" slice, and processes\n// pending movement actions. Set \"drawingAfter\" to true if you know that\n// [TreeView.Draw] will be called immediately after this function (to avoid\n// having [TreeView.Draw] call it again).\nfunc (t *TreeView) process(drawingAfter bool) {\n\tt.stableNodes = drawingAfter\n\t_, _, _, height := t.GetInnerRect()\n\n\t// Determine visible nodes and their placement.\n\tt.nodes = nil\n\tif t.root == nil {\n\t\treturn\n\t}\n\tparentSelectedIndex, selectedIndex, topLevelGraphicsX := -1, -1, -1\n\tvar graphicsOffset, maxTextX int\n\tif t.graphics {\n\t\tgraphicsOffset = 1\n\t}\n\tt.root.Walk(func(node, parent *TreeNode) bool {\n\t\t// Set node attributes.\n\t\tnode.parent = parent\n\t\tif parent == nil {\n\t\t\tnode.level = 0\n\t\t\tnode.graphicsX = 0\n\t\t\tnode.textX = 0\n\t\t} else {\n\t\t\tnode.level = parent.level + 1\n\t\t\tnode.graphicsX = parent.textX\n\t\t\tnode.textX = node.graphicsX + graphicsOffset + node.indent\n\t\t}\n\t\tif !t.graphics && t.align {\n\t\t\t// Without graphics, we align nodes on the first column.\n\t\t\tnode.textX = 0\n\t\t}\n\t\tif node.level == t.topLevel {\n\t\t\t// No graphics for top level nodes.\n\t\t\tnode.graphicsX = 0\n\t\t\tnode.textX = 0\n\t\t}\n\n\t\t// Add the node to the list.\n\t\tif node.level >= t.topLevel {\n\t\t\t// This node will be visible.\n\t\t\tif node.textX > maxTextX {\n\t\t\t\tmaxTextX = node.textX\n\t\t\t}\n\t\t\tif node == t.currentNode && node.selectable {\n\t\t\t\tselectedIndex = len(t.nodes)\n\n\t\t\t\t// Also find parent node.\n\t\t\t\tfor index := len(t.nodes) - 1; index >= 0; index-- {\n\t\t\t\t\tif t.nodes[index] == parent && t.nodes[index].selectable {\n\t\t\t\t\t\tparentSelectedIndex = index\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Maybe we want to skip this level.\n\t\t\tif t.topLevel == node.level && (topLevelGraphicsX < 0 || node.graphicsX < topLevelGraphicsX) {\n\t\t\t\ttopLevelGraphicsX = node.graphicsX\n\t\t\t}\n\n\t\t\tt.nodes = append(t.nodes, node)\n\t\t}\n\n\t\t// Recurse if desired.\n\t\treturn node.expanded\n\t})\n\n\t// Post-process positions.\n\tfor _, node := range t.nodes {\n\t\t// If text must align, we correct the positions.\n\t\tif t.align && node.level > t.topLevel {\n\t\t\tnode.textX = maxTextX\n\t\t}\n\n\t\t// If we skipped levels, shift to the left.\n\t\tif topLevelGraphicsX > 0 {\n\t\t\tnode.graphicsX -= topLevelGraphicsX\n\t\t\tnode.textX -= topLevelGraphicsX\n\t\t}\n\t}\n\n\t// Process selection. (Also trigger events if necessary.)\n\tif selectedIndex >= 0 {\n\t\t// Move the selection.\n\t\tswitch t.movement {\n\t\tcase treeMove:\n\t\t\tfor t.step < 0 { // Going up.\n\t\t\t\tindex := selectedIndex\n\t\t\t\tfor index > 0 {\n\t\t\t\t\tindex--\n\t\t\t\t\tif t.nodes[index].selectable {\n\t\t\t\t\t\tselectedIndex = index\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tt.step++\n\t\t\t}\n\t\t\tfor t.step > 0 { // Going down.\n\t\t\t\tindex := selectedIndex\n\t\t\t\tfor index < len(t.nodes)-1 {\n\t\t\t\t\tindex++\n\t\t\t\t\tif t.nodes[index].selectable {\n\t\t\t\t\t\tselectedIndex = index\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tt.step--\n\t\t\t}\n\t\tcase treeParent:\n\t\t\tif parentSelectedIndex >= 0 {\n\t\t\t\tselectedIndex = parentSelectedIndex\n\t\t\t}\n\t\tcase treeChild:\n\t\t\tindex := selectedIndex\n\t\t\tfor index < len(t.nodes)-1 {\n\t\t\t\tindex++\n\t\t\t\tif t.nodes[index].selectable && t.nodes[index].parent == t.nodes[selectedIndex] {\n\t\t\t\t\tselectedIndex = index\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tt.currentNode = t.nodes[selectedIndex]\n\n\t\t// Move selection into viewport.\n\t\tif t.movement != treeScroll {\n\t\t\tif selectedIndex-t.offsetY >= height {\n\t\t\t\tt.offsetY = selectedIndex - height + 1\n\t\t\t}\n\t\t\tif selectedIndex < t.offsetY {\n\t\t\t\tt.offsetY = selectedIndex\n\t\t\t}\n\t\t\tif t.movement != treeHome && t.movement != treeEnd {\n\t\t\t\t// treeScroll, treeHome, and treeEnd are handled by Draw().\n\t\t\t\tt.movement = treeNone\n\t\t\t\tt.step = 0\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// If selection is not visible or selectable, select the first candidate.\n\t\tif t.currentNode != nil {\n\t\t\tfor index, node := range t.nodes {\n\t\t\t\tif node.selectable {\n\t\t\t\t\tselectedIndex = index\n\t\t\t\t\tt.currentNode = node\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif selectedIndex < 0 {\n\t\t\tt.currentNode = nil\n\t\t}\n\t}\n\n\t// Trigger \"changed\" callback.\n\tif t.changed != nil && t.currentNode != nil && t.currentNode != t.lastNode {\n\t\tt.changed(t.currentNode)\n\t}\n\tt.lastNode = t.currentNode\n}\n\n// Draw draws this primitive onto the screen.\nfunc (t *TreeView) Draw(screen tcell.Screen) {\n\tt.Box.DrawForSubclass(screen, t)\n\tif t.root == nil {\n\t\treturn\n\t}\n\t_, totalHeight := screen.Size()\n\n\tif !t.stableNodes {\n\t\tt.process(false)\n\t} else {\n\t\tt.stableNodes = false\n\t}\n\n\t// Scroll the tree, t.movement is treeNone after process() when there is a\n\t// selection, except for treeScroll, treeHome, and treeEnd.\n\tx, y, width, height := t.GetInnerRect()\n\tswitch t.movement {\n\tcase treeMove, treeScroll:\n\t\tt.offsetY += t.step\n\tcase treeHome:\n\t\tt.offsetY = 0\n\tcase treeEnd:\n\t\tt.offsetY = len(t.nodes)\n\t}\n\tt.movement = treeNone\n\n\t// Fix invalid offsets.\n\tif t.offsetY >= len(t.nodes)-height {\n\t\tt.offsetY = len(t.nodes) - height\n\t}\n\tif t.offsetY < 0 {\n\t\tt.offsetY = 0\n\t}\n\n\t// Draw the tree.\n\tposY := y\n\tlineStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.graphicsColor)\n\tfor index, node := range t.nodes {\n\t\t// Skip invisible parts.\n\t\tif posY >= y+height+1 || posY >= totalHeight {\n\t\t\tbreak\n\t\t}\n\t\tif index < t.offsetY {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Draw the graphics.\n\t\tif t.graphics {\n\t\t\t// Draw ancestor branches.\n\t\t\tancestor := node.parent\n\t\t\tfor ancestor != nil && ancestor.parent != nil && ancestor.parent.level >= t.topLevel {\n\t\t\t\tif ancestor.graphicsX >= width {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Draw a branch if this ancestor is not a last child.\n\t\t\t\tif ancestor.parent.children[len(ancestor.parent.children)-1] != ancestor {\n\t\t\t\t\tif posY-1 >= y && ancestor.textX > ancestor.graphicsX {\n\t\t\t\t\t\tPrintJoinedSemigraphics(screen, x+ancestor.graphicsX, posY-1, Borders.Vertical, lineStyle)\n\t\t\t\t\t}\n\t\t\t\t\tif posY < y+height {\n\t\t\t\t\t\tscreen.SetContent(x+ancestor.graphicsX, posY, Borders.Vertical, nil, lineStyle)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tancestor = ancestor.parent\n\t\t\t}\n\n\t\t\tif node.textX > node.graphicsX && node.graphicsX < width {\n\t\t\t\t// Connect to the node above.\n\t\t\t\tif posY-1 >= y && t.nodes[index-1].graphicsX <= node.graphicsX && t.nodes[index-1].textX > node.graphicsX {\n\t\t\t\t\tPrintJoinedSemigraphics(screen, x+node.graphicsX, posY-1, Borders.TopLeft, lineStyle)\n\t\t\t\t}\n\n\t\t\t\t// Join this node.\n\t\t\t\tif posY < y+height {\n\t\t\t\t\tscreen.SetContent(x+node.graphicsX, posY, Borders.BottomLeft, nil, lineStyle)\n\t\t\t\t\tfor pos := node.graphicsX + 1; pos < node.textX && pos < width; pos++ {\n\t\t\t\t\t\tscreen.SetContent(x+pos, posY, Borders.Horizontal, nil, lineStyle)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Draw the prefix and the text.\n\t\tif node.textX < width && posY < y+height {\n\t\t\t// Prefix.\n\t\t\tvar prefixWidth int\n\t\t\tif len(t.prefixes) > 0 {\n\t\t\t\t_, _, prefixWidth = printWithStyle(screen, t.prefixes[(node.level-t.topLevel)%len(t.prefixes)], x+node.textX, posY, 0, width-node.textX, AlignLeft, node.textStyle, true)\n\t\t\t}\n\n\t\t\t// Text.\n\t\t\tif node.textX+prefixWidth < width {\n\t\t\t\tstyle := node.textStyle\n\t\t\t\tif node == t.currentNode {\n\t\t\t\t\tstyle = node.selectedTextStyle\n\t\t\t\t}\n\t\t\t\tprintWithStyle(screen, node.text, x+node.textX+prefixWidth, posY, 0, width-node.textX-prefixWidth, AlignLeft, style, false)\n\t\t\t}\n\t\t}\n\n\t\t// Advance.\n\t\tposY++\n\t}\n}\n\n// InputHandler returns the handler for this primitive.\nfunc (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\treturn t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {\n\t\tselectNode := func() {\n\t\t\tnode := t.currentNode\n\t\t\tif node != nil {\n\t\t\t\tif t.selected != nil {\n\t\t\t\t\tt.selected(node)\n\t\t\t\t}\n\t\t\t\tif node.selected != nil {\n\t\t\t\t\tnode.selected()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Because the tree is flattened into a list only at drawing time, we also\n\t\t// postpone the (selection) movement to drawing time.\n\t\tswitch key := event.Key(); key {\n\t\tcase tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape:\n\t\t\tif t.done != nil {\n\t\t\t\tt.done(key)\n\t\t\t}\n\t\tcase tcell.KeyDown, tcell.KeyRight:\n\t\t\tt.movement = treeMove\n\t\t\tt.step = 1\n\t\tcase tcell.KeyUp, tcell.KeyLeft:\n\t\t\tt.movement = treeMove\n\t\t\tt.step = -1\n\t\tcase tcell.KeyHome:\n\t\t\tt.movement = treeHome\n\t\tcase tcell.KeyEnd:\n\t\t\tt.movement = treeEnd\n\t\tcase tcell.KeyPgDn, tcell.KeyCtrlF:\n\t\t\t_, _, _, height := t.GetInnerRect()\n\t\t\tt.movement = treeMove\n\t\t\tt.step = height\n\t\tcase tcell.KeyPgUp, tcell.KeyCtrlB:\n\t\t\t_, _, _, height := t.GetInnerRect()\n\t\t\tt.movement = treeMove\n\t\t\tt.step = -height\n\t\tcase tcell.KeyRune:\n\t\t\tswitch event.Rune() {\n\t\t\tcase 'g':\n\t\t\t\tt.movement = treeHome\n\t\t\tcase 'G':\n\t\t\t\tt.movement = treeEnd\n\t\t\tcase 'j':\n\t\t\t\tt.movement = treeMove\n\t\t\t\tt.step = 1\n\t\t\tcase 'J':\n\t\t\t\tt.movement = treeChild\n\t\t\tcase 'k':\n\t\t\t\tt.movement = treeMove\n\t\t\t\tt.step = -1\n\t\t\tcase 'K':\n\t\t\t\tt.movement = treeParent\n\t\t\tcase ' ':\n\t\t\t\tselectNode()\n\t\t\t}\n\t\tcase tcell.KeyEnter:\n\t\t\tselectNode()\n\t\t}\n\n\t\tt.process(true)\n\t})\n}\n\n// MouseHandler returns the mouse handler for this primitive.\nfunc (t *TreeView) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\treturn t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {\n\t\tx, y := event.Position()\n\t\tif !t.InRect(x, y) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tswitch action {\n\t\tcase MouseLeftDown:\n\t\t\tsetFocus(t)\n\t\t\tconsumed = true\n\t\tcase MouseLeftClick:\n\t\t\t_, rectY, _, _ := t.GetInnerRect()\n\t\t\ty += t.offsetY - rectY\n\t\t\tif y >= 0 && y < len(t.nodes) {\n\t\t\t\tnode := t.nodes[y]\n\t\t\t\tif node.selectable {\n\t\t\t\t\tpreviousNode := t.currentNode\n\t\t\t\t\tt.currentNode = node\n\t\t\t\t\tif previousNode != node && t.changed != nil {\n\t\t\t\t\t\tt.changed(node)\n\t\t\t\t\t}\n\t\t\t\t\tif t.selected != nil {\n\t\t\t\t\t\tt.selected(node)\n\t\t\t\t\t}\n\t\t\t\t\tif node.selected != nil {\n\t\t\t\t\t\tnode.selected()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tconsumed = true\n\t\tcase MouseScrollUp:\n\t\t\tt.movement = treeScroll\n\t\t\tt.step = -1\n\t\t\tconsumed = true\n\t\tcase MouseScrollDown:\n\t\t\tt.movement = treeScroll\n\t\t\tt.step = 1\n\t\t\tconsumed = true\n\t\t}\n\n\t\treturn\n\t})\n}\n"
  },
  {
    "path": "util.go",
    "content": "package tview\n\nimport (\n\t\"math\"\n\t\"os\"\n\t\"regexp\"\n\n\t\"github.com/gdamore/tcell/v2\"\n)\n\n// Text alignment within a box. Also used to align images.\nconst (\n\tAlignLeft = iota\n\tAlignCenter\n\tAlignRight\n\tAlignTop    = 0\n\tAlignBottom = 2\n)\n\nvar (\n\t// Regular expression used to escape style/region tags.\n\tescapePattern = regexp.MustCompile(`(\\[[a-zA-Z0-9_,;: \\-\\.\"#]+\\[*)\\]`)\n\n\t// Regular expression used to unescape escaped style/region tags.\n\tunescapePattern = regexp.MustCompile(`(\\[[a-zA-Z0-9_,;: \\-\\.\"#]+\\[*)\\[\\]`)\n\n\t// The number of colors available in the terminal.\n\tavailableColors = 256\n)\n\n// Package initialization.\nfunc init() {\n\t// Determine the number of colors available in the terminal.\n\tinfo, err := tcell.LookupTerminfo(os.Getenv(\"TERM\"))\n\tif err == nil {\n\t\tavailableColors = info.Colors\n\t}\n}\n\n// Print prints text onto the screen into the given box at (x,y,maxWidth,1),\n// not exceeding that box. \"align\" is one of AlignLeft, AlignCenter, or\n// AlignRight. The screen's background color will not be changed.\n//\n// You can change the colors and text styles mid-text by inserting a style tag.\n// See the package description for details.\n//\n// Returns the number of actual bytes of the text printed (including style tags)\n// and the actual width used for the printed runes.\nfunc Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) {\n\tstart, end, width := printWithStyle(screen, text, x, y, 0, maxWidth, align, tcell.StyleDefault.Foreground(color), true)\n\treturn end - start, width\n}\n\n// printWithStyle works like [Print] but it takes a style instead of just a\n// foreground color. The skipWidth parameter specifies the number of cells\n// skipped at the beginning of the text. It returns the start index, end index\n// (exclusively), and screen width of the text actually printed. If\n// maintainBackground is \"true\", the existing screen background is not changed\n// (i.e. the style's background color is ignored).\nfunc printWithStyle(screen tcell.Screen, text string, x, y, skipWidth, maxWidth, align int, style tcell.Style, maintainBackground bool) (start, end, printedWidth int) {\n\ttotalWidth, totalHeight := screen.Size()\n\tif maxWidth <= 0 || len(text) == 0 || y < 0 || y >= totalHeight {\n\t\treturn 0, 0, 0\n\t}\n\n\t// If we don't overwrite the background, we use the default color.\n\tif maintainBackground {\n\t\tstyle = style.Background(tcell.ColorDefault)\n\t}\n\n\t// Skip beginning and measure width.\n\tvar textWidth int\n\tstate := &stepState{\n\t\tunisegState: -1,\n\t\tstyle:       style,\n\t}\n\tnewState := *state\n\tstr := text\n\tfor len(str) > 0 {\n\t\t_, str, state = step(str, state, stepOptionsStyle)\n\t\tif skipWidth > 0 {\n\t\t\tskipWidth -= state.Width()\n\t\t\ttext = str\n\t\t\tnewState = *state\n\t\t\tstart += state.GrossLength()\n\t\t} else {\n\t\t\ttextWidth += state.Width()\n\t\t}\n\t}\n\tstate = &newState\n\n\t// Reduce all alignments to AlignLeft.\n\tif align == AlignRight {\n\t\t// Chop off characters on the left until it fits.\n\t\tfor len(text) > 0 && textWidth > maxWidth {\n\t\t\t_, text, state = step(text, state, stepOptionsStyle)\n\t\t\ttextWidth -= state.Width()\n\t\t\tstart += state.GrossLength()\n\t\t}\n\t\tx, maxWidth = x+maxWidth-textWidth, textWidth\n\t} else if align == AlignCenter {\n\t\t// Chop off characters on the left until it fits.\n\t\tsubtracted := (textWidth - maxWidth) / 2\n\t\tfor len(text) > 0 && subtracted > 0 {\n\t\t\t_, text, state = step(text, state, stepOptionsStyle)\n\t\t\tsubtracted -= state.Width()\n\t\t\ttextWidth -= state.Width()\n\t\t\tstart += state.GrossLength()\n\t\t}\n\t\tif textWidth < maxWidth {\n\t\t\tx, maxWidth = x+maxWidth/2-textWidth/2, textWidth\n\t\t}\n\t}\n\n\t// Draw left-aligned text.\n\tend = start\n\trightBorder := x + maxWidth\n\tfor len(text) > 0 && x < rightBorder && x < totalWidth {\n\t\tvar c string\n\t\tc, text, state = step(text, state, stepOptionsStyle)\n\t\tif c == \"\" {\n\t\t\tbreak // We don't care about the style at the end.\n\t\t}\n\t\twidth := state.Width()\n\n\t\tif width > 0 {\n\t\t\tfinalStyle := state.Style()\n\t\t\tif maintainBackground {\n\t\t\t\t_, backgroundColor, _ := finalStyle.Decompose()\n\t\t\t\tif backgroundColor == tcell.ColorDefault {\n\t\t\t\t\t_, _, existingStyle, _ := screen.GetContent(x, y)\n\t\t\t\t\t_, background, _ := existingStyle.Decompose()\n\t\t\t\t\tfinalStyle = finalStyle.Background(background)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor offset := width - 1; offset >= 0; offset-- {\n\t\t\t\t// To avoid undesired effects, we populate all cells.\n\t\t\t\trunes := []rune(c)\n\t\t\t\tif offset == 0 {\n\t\t\t\t\tscreen.SetContent(x+offset, y, runes[0], runes[1:], finalStyle)\n\t\t\t\t} else {\n\t\t\t\t\tscreen.SetContent(x+offset, y, ' ', nil, finalStyle)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tx += width\n\t\tend += state.GrossLength()\n\t\tprintedWidth += width\n\t}\n\n\treturn\n}\n\n// PrintSimple prints white text to the screen at the given position.\nfunc PrintSimple(screen tcell.Screen, text string, x, y int) {\n\tPrint(screen, text, x, y, math.MaxInt32, AlignLeft, Styles.PrimaryTextColor)\n}\n"
  }
]