diff --git a/vendor/github.com/atotto/clipboard/.travis.yml b/vendor/github.com/atotto/clipboard/.travis.yml
new file mode 100644
index 0000000..23f21d8
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/.travis.yml
@@ -0,0 +1,22 @@
+language: go
+
+os:
+ - linux
+ - osx
+ - windows
+
+go:
+ - go1.13.x
+ - go1.x
+
+services:
+ - xvfb
+
+before_install:
+ - export DISPLAY=:99.0
+
+script:
+ - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xsel; fi
+ - go test -v .
+ - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xclip; fi
+ - go test -v .
diff --git a/vendor/github.com/atotto/clipboard/LICENSE b/vendor/github.com/atotto/clipboard/LICENSE
new file mode 100644
index 0000000..dee3257
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2013 Ato Araki. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of @atotto. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/atotto/clipboard/README.md b/vendor/github.com/atotto/clipboard/README.md
new file mode 100644
index 0000000..41fdd57
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/README.md
@@ -0,0 +1,48 @@
+[](https://travis-ci.org/atotto/clipboard)
+
+[](http://godoc.org/github.com/atotto/clipboard)
+
+# Clipboard for Go
+
+Provide copying and pasting to the Clipboard for Go.
+
+Build:
+
+ $ go get github.com/atotto/clipboard
+
+Platforms:
+
+* OSX
+* Windows 7 (probably work on other Windows)
+* Linux, Unix (requires 'xclip' or 'xsel' command to be installed)
+
+
+Document:
+
+* http://godoc.org/github.com/atotto/clipboard
+
+Notes:
+
+* Text string only
+* UTF-8 text encoding only (no conversion)
+
+TODO:
+
+* Clipboard watcher(?)
+
+## Commands:
+
+paste shell command:
+
+ $ go get github.com/atotto/clipboard/cmd/gopaste
+ $ # example:
+ $ gopaste > document.txt
+
+copy shell command:
+
+ $ go get github.com/atotto/clipboard/cmd/gocopy
+ $ # example:
+ $ cat document.txt | gocopy
+
+
+
diff --git a/vendor/github.com/atotto/clipboard/clipboard.go b/vendor/github.com/atotto/clipboard/clipboard.go
new file mode 100644
index 0000000..d7907d3
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/clipboard.go
@@ -0,0 +1,20 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package clipboard read/write on clipboard
+package clipboard
+
+// ReadAll read string from clipboard
+func ReadAll() (string, error) {
+ return readAll()
+}
+
+// WriteAll write string to clipboard
+func WriteAll(text string) error {
+ return writeAll(text)
+}
+
+// Unsupported might be set true during clipboard init, to help callers decide
+// whether or not to offer clipboard options.
+var Unsupported bool
diff --git a/vendor/github.com/atotto/clipboard/clipboard_darwin.go b/vendor/github.com/atotto/clipboard/clipboard_darwin.go
new file mode 100644
index 0000000..6f33078
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/clipboard_darwin.go
@@ -0,0 +1,52 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin
+
+package clipboard
+
+import (
+ "os/exec"
+)
+
+var (
+ pasteCmdArgs = "pbpaste"
+ copyCmdArgs = "pbcopy"
+)
+
+func getPasteCommand() *exec.Cmd {
+ return exec.Command(pasteCmdArgs)
+}
+
+func getCopyCommand() *exec.Cmd {
+ return exec.Command(copyCmdArgs)
+}
+
+func readAll() (string, error) {
+ pasteCmd := getPasteCommand()
+ out, err := pasteCmd.Output()
+ if err != nil {
+ return "", err
+ }
+ return string(out), nil
+}
+
+func writeAll(text string) error {
+ copyCmd := getCopyCommand()
+ in, err := copyCmd.StdinPipe()
+ if err != nil {
+ return err
+ }
+
+ if err := copyCmd.Start(); err != nil {
+ return err
+ }
+ if _, err := in.Write([]byte(text)); err != nil {
+ return err
+ }
+ if err := in.Close(); err != nil {
+ return err
+ }
+ return copyCmd.Wait()
+}
diff --git a/vendor/github.com/atotto/clipboard/clipboard_plan9.go b/vendor/github.com/atotto/clipboard/clipboard_plan9.go
new file mode 100644
index 0000000..9d2fef4
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/clipboard_plan9.go
@@ -0,0 +1,42 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build plan9
+
+package clipboard
+
+import (
+ "os"
+ "io/ioutil"
+)
+
+func readAll() (string, error) {
+ f, err := os.Open("/dev/snarf")
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ str, err := ioutil.ReadAll(f)
+ if err != nil {
+ return "", err
+ }
+
+ return string(str), nil
+}
+
+func writeAll(text string) error {
+ f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0666)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ _, err = f.Write([]byte(text))
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/atotto/clipboard/clipboard_unix.go b/vendor/github.com/atotto/clipboard/clipboard_unix.go
new file mode 100644
index 0000000..d9f6a56
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/clipboard_unix.go
@@ -0,0 +1,149 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build freebsd linux netbsd openbsd solaris dragonfly
+
+package clipboard
+
+import (
+ "errors"
+ "os"
+ "os/exec"
+)
+
+const (
+ xsel = "xsel"
+ xclip = "xclip"
+ powershellExe = "powershell.exe"
+ clipExe = "clip.exe"
+ wlcopy = "wl-copy"
+ wlpaste = "wl-paste"
+ termuxClipboardGet = "termux-clipboard-get"
+ termuxClipboardSet = "termux-clipboard-set"
+)
+
+var (
+ Primary bool
+ trimDos bool
+
+ pasteCmdArgs []string
+ copyCmdArgs []string
+
+ xselPasteArgs = []string{xsel, "--output", "--clipboard"}
+ xselCopyArgs = []string{xsel, "--input", "--clipboard"}
+
+ xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"}
+ xclipCopyArgs = []string{xclip, "-in", "-selection", "clipboard"}
+
+ powershellExePasteArgs = []string{powershellExe, "Get-Clipboard"}
+ clipExeCopyArgs = []string{clipExe}
+
+ wlpasteArgs = []string{wlpaste, "--no-newline"}
+ wlcopyArgs = []string{wlcopy}
+
+ termuxPasteArgs = []string{termuxClipboardGet}
+ termuxCopyArgs = []string{termuxClipboardSet}
+
+ missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.")
+)
+
+func init() {
+ if os.Getenv("WAYLAND_DISPLAY") != "" {
+ pasteCmdArgs = wlpasteArgs
+ copyCmdArgs = wlcopyArgs
+
+ if _, err := exec.LookPath(wlcopy); err == nil {
+ if _, err := exec.LookPath(wlpaste); err == nil {
+ return
+ }
+ }
+ }
+
+ pasteCmdArgs = xclipPasteArgs
+ copyCmdArgs = xclipCopyArgs
+
+ if _, err := exec.LookPath(xclip); err == nil {
+ return
+ }
+
+ pasteCmdArgs = xselPasteArgs
+ copyCmdArgs = xselCopyArgs
+
+ if _, err := exec.LookPath(xsel); err == nil {
+ return
+ }
+
+ pasteCmdArgs = termuxPasteArgs
+ copyCmdArgs = termuxCopyArgs
+
+ if _, err := exec.LookPath(termuxClipboardSet); err == nil {
+ if _, err := exec.LookPath(termuxClipboardGet); err == nil {
+ return
+ }
+ }
+
+ pasteCmdArgs = powershellExePasteArgs
+ copyCmdArgs = clipExeCopyArgs
+ trimDos = true
+
+ if _, err := exec.LookPath(clipExe); err == nil {
+ if _, err := exec.LookPath(powershellExe); err == nil {
+ return
+ }
+ }
+
+ Unsupported = true
+}
+
+func getPasteCommand() *exec.Cmd {
+ if Primary {
+ pasteCmdArgs = pasteCmdArgs[:1]
+ }
+ return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...)
+}
+
+func getCopyCommand() *exec.Cmd {
+ if Primary {
+ copyCmdArgs = copyCmdArgs[:1]
+ }
+ return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...)
+}
+
+func readAll() (string, error) {
+ if Unsupported {
+ return "", missingCommands
+ }
+ pasteCmd := getPasteCommand()
+ out, err := pasteCmd.Output()
+ if err != nil {
+ return "", err
+ }
+ result := string(out)
+ if trimDos && len(result) > 1 {
+ result = result[:len(result)-2]
+ }
+ return result, nil
+}
+
+func writeAll(text string) error {
+ if Unsupported {
+ return missingCommands
+ }
+ copyCmd := getCopyCommand()
+ in, err := copyCmd.StdinPipe()
+ if err != nil {
+ return err
+ }
+
+ if err := copyCmd.Start(); err != nil {
+ return err
+ }
+ if _, err := in.Write([]byte(text)); err != nil {
+ return err
+ }
+ if err := in.Close(); err != nil {
+ return err
+ }
+ return copyCmd.Wait()
+}
diff --git a/vendor/github.com/atotto/clipboard/clipboard_windows.go b/vendor/github.com/atotto/clipboard/clipboard_windows.go
new file mode 100644
index 0000000..253bb93
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/clipboard_windows.go
@@ -0,0 +1,157 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package clipboard
+
+import (
+ "runtime"
+ "syscall"
+ "time"
+ "unsafe"
+)
+
+const (
+ cfUnicodetext = 13
+ gmemMoveable = 0x0002
+)
+
+var (
+ user32 = syscall.MustLoadDLL("user32")
+ isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable")
+ openClipboard = user32.MustFindProc("OpenClipboard")
+ closeClipboard = user32.MustFindProc("CloseClipboard")
+ emptyClipboard = user32.MustFindProc("EmptyClipboard")
+ getClipboardData = user32.MustFindProc("GetClipboardData")
+ setClipboardData = user32.MustFindProc("SetClipboardData")
+
+ kernel32 = syscall.NewLazyDLL("kernel32")
+ globalAlloc = kernel32.NewProc("GlobalAlloc")
+ globalFree = kernel32.NewProc("GlobalFree")
+ globalLock = kernel32.NewProc("GlobalLock")
+ globalUnlock = kernel32.NewProc("GlobalUnlock")
+ lstrcpy = kernel32.NewProc("lstrcpyW")
+)
+
+// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
+func waitOpenClipboard() error {
+ started := time.Now()
+ limit := started.Add(time.Second)
+ var r uintptr
+ var err error
+ for time.Now().Before(limit) {
+ r, _, err = openClipboard.Call(0)
+ if r != 0 {
+ return nil
+ }
+ time.Sleep(time.Millisecond)
+ }
+ return err
+}
+
+func readAll() (string, error) {
+ // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
+ // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+ if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
+ return "", err
+ }
+ err := waitOpenClipboard()
+ if err != nil {
+ return "", err
+ }
+
+ h, _, err := getClipboardData.Call(cfUnicodetext)
+ if h == 0 {
+ _, _, _ = closeClipboard.Call()
+ return "", err
+ }
+
+ l, _, err := globalLock.Call(h)
+ if l == 0 {
+ _, _, _ = closeClipboard.Call()
+ return "", err
+ }
+
+ text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
+
+ r, _, err := globalUnlock.Call(h)
+ if r == 0 {
+ _, _, _ = closeClipboard.Call()
+ return "", err
+ }
+
+ closed, _, err := closeClipboard.Call()
+ if closed == 0 {
+ return "", err
+ }
+ return text, nil
+}
+
+func writeAll(text string) error {
+ // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
+ // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ err := waitOpenClipboard()
+ if err != nil {
+ return err
+ }
+
+ r, _, err := emptyClipboard.Call(0)
+ if r == 0 {
+ _, _, _ = closeClipboard.Call()
+ return err
+ }
+
+ data := syscall.StringToUTF16(text)
+
+ // "If the hMem parameter identifies a memory object, the object must have
+ // been allocated using the function with the GMEM_MOVEABLE flag."
+ h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
+ if h == 0 {
+ _, _, _ = closeClipboard.Call()
+ return err
+ }
+ defer func() {
+ if h != 0 {
+ globalFree.Call(h)
+ }
+ }()
+
+ l, _, err := globalLock.Call(h)
+ if l == 0 {
+ _, _, _ = closeClipboard.Call()
+ return err
+ }
+
+ r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
+ if r == 0 {
+ _, _, _ = closeClipboard.Call()
+ return err
+ }
+
+ r, _, err = globalUnlock.Call(h)
+ if r == 0 {
+ if err.(syscall.Errno) != 0 {
+ _, _, _ = closeClipboard.Call()
+ return err
+ }
+ }
+
+ r, _, err = setClipboardData.Call(cfUnicodetext, h)
+ if r == 0 {
+ _, _, _ = closeClipboard.Call()
+ return err
+ }
+ h = 0 // suppress deferred cleanup
+ closed, _, err := closeClipboard.Call()
+ if closed == 0 {
+ return err
+ }
+ return nil
+}
diff --git a/vendor/github.com/aymanbagabas/go-osc52/v2/LICENSE b/vendor/github.com/aymanbagabas/go-osc52/v2/LICENSE
new file mode 100644
index 0000000..25cec1e
--- /dev/null
+++ b/vendor/github.com/aymanbagabas/go-osc52/v2/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Ayman Bagabas
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/aymanbagabas/go-osc52/v2/README.md b/vendor/github.com/aymanbagabas/go-osc52/v2/README.md
new file mode 100644
index 0000000..4de3a22
--- /dev/null
+++ b/vendor/github.com/aymanbagabas/go-osc52/v2/README.md
@@ -0,0 +1,83 @@
+
+# go-osc52
+
+
+
+
+
+
+A Go library to work with the [ANSI OSC52](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands) terminal sequence.
+
+## Usage
+
+You can use this small library to construct an ANSI OSC52 sequence suitable for
+your terminal.
+
+
+### Example
+
+```go
+import (
+ "os"
+ "fmt"
+
+ "github.com/aymanbagabas/go-osc52/v2"
+)
+
+func main() {
+ s := "Hello World!"
+
+ // Copy `s` to system clipboard
+ osc52.New(s).WriteTo(os.Stderr)
+
+ // Copy `s` to primary clipboard (X11)
+ osc52.New(s).Primary().WriteTo(os.Stderr)
+
+ // Query the clipboard
+ osc52.Query().WriteTo(os.Stderr)
+
+ // Clear system clipboard
+ osc52.Clear().WriteTo(os.Stderr)
+
+ // Use the fmt.Stringer interface to copy `s` to system clipboard
+ fmt.Fprint(os.Stderr, osc52.New(s))
+
+ // Or to primary clipboard
+ fmt.Fprint(os.Stderr, osc52.New(s).Primary())
+}
+```
+
+## SSH Example
+
+You can use this over SSH using [gliderlabs/ssh](https://github.com/gliderlabs/ssh) for instance:
+
+```go
+var sshSession ssh.Session
+seq := osc52.New("Hello awesome!")
+// Check if term is screen or tmux
+pty, _, _ := s.Pty()
+if pty.Term == "screen" {
+ seq = seq.Screen()
+} else if isTmux {
+ seq = seq.Tmux()
+}
+seq.WriteTo(sshSession.Stderr())
+```
+
+## Tmux
+
+Make sure you have `set-clipboard on` in your config, otherwise, tmux won't
+allow your application to access the clipboard [^1].
+
+Using the tmux option, `osc52.TmuxMode` or `osc52.New(...).Tmux()`, wraps the
+OSC52 sequence in a special tmux DCS sequence and pass it to the outer
+terminal. This requires `allow-passthrough on` in your config.
+`allow-passthrough` is no longer enabled by default
+[since tmux 3.3a](https://github.com/tmux/tmux/issues/3218#issuecomment-1153089282) [^2].
+
+[^1]: See [tmux clipboard](https://github.com/tmux/tmux/wiki/Clipboard)
+[^2]: [What is allow-passthrough](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it)
+
+## Credits
+
+* [vim-oscyank](https://github.com/ojroques/vim-oscyank) this is heavily inspired by vim-oscyank.
diff --git a/vendor/github.com/aymanbagabas/go-osc52/v2/osc52.go b/vendor/github.com/aymanbagabas/go-osc52/v2/osc52.go
new file mode 100644
index 0000000..dc758d2
--- /dev/null
+++ b/vendor/github.com/aymanbagabas/go-osc52/v2/osc52.go
@@ -0,0 +1,305 @@
+// OSC52 is a terminal escape sequence that allows copying text to the clipboard.
+//
+// The sequence consists of the following:
+//
+// OSC 52 ; Pc ; Pd BEL
+//
+// Pc is the clipboard choice:
+//
+// c: clipboard
+// p: primary
+// q: secondary (not supported)
+// s: select (not supported)
+// 0-7: cut-buffers (not supported)
+//
+// Pd is the data to copy to the clipboard. This string should be encoded in
+// base64 (RFC-4648).
+//
+// If Pd is "?", the terminal replies to the host with the current contents of
+// the clipboard.
+//
+// If Pd is neither a base64 string nor "?", the terminal clears the clipboard.
+//
+// See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+// where Ps = 52 => Manipulate Selection Data.
+//
+// Examples:
+//
+// // copy "hello world" to the system clipboard
+// fmt.Fprint(os.Stderr, osc52.New("hello world"))
+//
+// // copy "hello world" to the primary Clipboard
+// fmt.Fprint(os.Stderr, osc52.New("hello world").Primary())
+//
+// // limit the size of the string to copy 10 bytes
+// fmt.Fprint(os.Stderr, osc52.New("0123456789").Limit(10))
+//
+// // escape the OSC52 sequence for screen using DCS sequences
+// fmt.Fprint(os.Stderr, osc52.New("hello world").Screen())
+//
+// // escape the OSC52 sequence for Tmux
+// fmt.Fprint(os.Stderr, osc52.New("hello world").Tmux())
+//
+// // query the system Clipboard
+// fmt.Fprint(os.Stderr, osc52.Query())
+//
+// // query the primary clipboard
+// fmt.Fprint(os.Stderr, osc52.Query().Primary())
+//
+// // clear the system Clipboard
+// fmt.Fprint(os.Stderr, osc52.Clear())
+//
+// // clear the primary Clipboard
+// fmt.Fprint(os.Stderr, osc52.Clear().Primary())
+package osc52
+
+import (
+ "encoding/base64"
+ "fmt"
+ "io"
+ "strings"
+)
+
+// Clipboard is the clipboard buffer to use.
+type Clipboard rune
+
+const (
+ // SystemClipboard is the system clipboard buffer.
+ SystemClipboard Clipboard = 'c'
+ // PrimaryClipboard is the primary clipboard buffer (X11).
+ PrimaryClipboard = 'p'
+)
+
+// Mode is the mode to use for the OSC52 sequence.
+type Mode uint
+
+const (
+ // DefaultMode is the default OSC52 sequence mode.
+ DefaultMode Mode = iota
+ // ScreenMode escapes the OSC52 sequence for screen using DCS sequences.
+ ScreenMode
+ // TmuxMode escapes the OSC52 sequence for tmux. Not needed if tmux
+ // clipboard is set to `set-clipboard on`
+ TmuxMode
+)
+
+// Operation is the OSC52 operation.
+type Operation uint
+
+const (
+ // SetOperation is the copy operation.
+ SetOperation Operation = iota
+ // QueryOperation is the query operation.
+ QueryOperation
+ // ClearOperation is the clear operation.
+ ClearOperation
+)
+
+// Sequence is the OSC52 sequence.
+type Sequence struct {
+ str string
+ limit int
+ op Operation
+ mode Mode
+ clipboard Clipboard
+}
+
+var _ fmt.Stringer = Sequence{}
+
+var _ io.WriterTo = Sequence{}
+
+// String returns the OSC52 sequence.
+func (s Sequence) String() string {
+ var seq strings.Builder
+ // mode escape sequences start
+ seq.WriteString(s.seqStart())
+ // actual OSC52 sequence start
+ seq.WriteString(fmt.Sprintf("\x1b]52;%c;", s.clipboard))
+ switch s.op {
+ case SetOperation:
+ str := s.str
+ if s.limit > 0 && len(str) > s.limit {
+ return ""
+ }
+ b64 := base64.StdEncoding.EncodeToString([]byte(str))
+ switch s.mode {
+ case ScreenMode:
+ // Screen doesn't support OSC52 but will pass the contents of a DCS
+ // sequence to the outer terminal unchanged.
+ //
+ // Here, we split the encoded string into 76 bytes chunks and then
+ // join the chunks with sequences. Finally,
+ // wrap the whole thing in
+ // .
+ // s := strings.SplitN(b64, "", 76)
+ s := make([]string, 0, len(b64)/76+1)
+ for i := 0; i < len(b64); i += 76 {
+ end := i + 76
+ if end > len(b64) {
+ end = len(b64)
+ }
+ s = append(s, b64[i:end])
+ }
+ seq.WriteString(strings.Join(s, "\x1b\\\x1bP"))
+ default:
+ seq.WriteString(b64)
+ }
+ case QueryOperation:
+ // OSC52 queries the clipboard using "?"
+ seq.WriteString("?")
+ case ClearOperation:
+ // OSC52 clears the clipboard if the data is neither a base64 string nor "?"
+ // we're using "!" as a default
+ seq.WriteString("!")
+ }
+ // actual OSC52 sequence end
+ seq.WriteString("\x07")
+ // mode escape end
+ seq.WriteString(s.seqEnd())
+ return seq.String()
+}
+
+// WriteTo writes the OSC52 sequence to the writer.
+func (s Sequence) WriteTo(out io.Writer) (int64, error) {
+ n, err := out.Write([]byte(s.String()))
+ return int64(n), err
+}
+
+// Mode sets the mode for the OSC52 sequence.
+func (s Sequence) Mode(m Mode) Sequence {
+ s.mode = m
+ return s
+}
+
+// Tmux sets the mode to TmuxMode.
+// Used to escape the OSC52 sequence for `tmux`.
+//
+// Note: this is not needed if tmux clipboard is set to `set-clipboard on`. If
+// TmuxMode is used, tmux must have `allow-passthrough on` set.
+//
+// This is a syntactic sugar for s.Mode(TmuxMode).
+func (s Sequence) Tmux() Sequence {
+ return s.Mode(TmuxMode)
+}
+
+// Screen sets the mode to ScreenMode.
+// Used to escape the OSC52 sequence for `screen`.
+//
+// This is a syntactic sugar for s.Mode(ScreenMode).
+func (s Sequence) Screen() Sequence {
+ return s.Mode(ScreenMode)
+}
+
+// Clipboard sets the clipboard buffer for the OSC52 sequence.
+func (s Sequence) Clipboard(c Clipboard) Sequence {
+ s.clipboard = c
+ return s
+}
+
+// Primary sets the clipboard buffer to PrimaryClipboard.
+// This is the X11 primary clipboard.
+//
+// This is a syntactic sugar for s.Clipboard(PrimaryClipboard).
+func (s Sequence) Primary() Sequence {
+ return s.Clipboard(PrimaryClipboard)
+}
+
+// Limit sets the limit for the OSC52 sequence.
+// The default limit is 0 (no limit).
+//
+// Strings longer than the limit get ignored. Settting the limit to 0 or a
+// negative value disables the limit. Each terminal defines its own escapse
+// sequence limit.
+func (s Sequence) Limit(l int) Sequence {
+ if l < 0 {
+ s.limit = 0
+ } else {
+ s.limit = l
+ }
+ return s
+}
+
+// Operation sets the operation for the OSC52 sequence.
+// The default operation is SetOperation.
+func (s Sequence) Operation(o Operation) Sequence {
+ s.op = o
+ return s
+}
+
+// Clear sets the operation to ClearOperation.
+// This clears the clipboard.
+//
+// This is a syntactic sugar for s.Operation(ClearOperation).
+func (s Sequence) Clear() Sequence {
+ return s.Operation(ClearOperation)
+}
+
+// Query sets the operation to QueryOperation.
+// This queries the clipboard contents.
+//
+// This is a syntactic sugar for s.Operation(QueryOperation).
+func (s Sequence) Query() Sequence {
+ return s.Operation(QueryOperation)
+}
+
+// SetString sets the string for the OSC52 sequence. Strings are joined with a
+// space character.
+func (s Sequence) SetString(strs ...string) Sequence {
+ s.str = strings.Join(strs, " ")
+ return s
+}
+
+// New creates a new OSC52 sequence with the given string(s). Strings are
+// joined with a space character.
+func New(strs ...string) Sequence {
+ s := Sequence{
+ str: strings.Join(strs, " "),
+ limit: 0,
+ mode: DefaultMode,
+ clipboard: SystemClipboard,
+ op: SetOperation,
+ }
+ return s
+}
+
+// Query creates a new OSC52 sequence with the QueryOperation.
+// This returns a new OSC52 sequence to query the clipboard contents.
+//
+// This is a syntactic sugar for New().Query().
+func Query() Sequence {
+ return New().Query()
+}
+
+// Clear creates a new OSC52 sequence with the ClearOperation.
+// This returns a new OSC52 sequence to clear the clipboard.
+//
+// This is a syntactic sugar for New().Clear().
+func Clear() Sequence {
+ return New().Clear()
+}
+
+func (s Sequence) seqStart() string {
+ switch s.mode {
+ case TmuxMode:
+ // Write the start of a tmux escape sequence.
+ return "\x1bPtmux;\x1b"
+ case ScreenMode:
+ // Write the start of a DCS sequence.
+ return "\x1bP"
+ default:
+ return ""
+ }
+}
+
+func (s Sequence) seqEnd() string {
+ switch s.mode {
+ case TmuxMode:
+ // Terminate the tmux escape sequence.
+ return "\x1b\\"
+ case ScreenMode:
+ // Write the end of a DCS sequence.
+ return "\x1b\x5c"
+ default:
+ return ""
+ }
+}
diff --git a/vendor/github.com/charmbracelet/bubbles/LICENSE b/vendor/github.com/charmbracelet/bubbles/LICENSE
new file mode 100644
index 0000000..31d76c1
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbles/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020-2023 Charmbracelet, Inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/charmbracelet/bubbles/cursor/cursor.go b/vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
new file mode 100644
index 0000000..5abda65
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
@@ -0,0 +1,207 @@
+package cursor
+
+import (
+ "context"
+ "time"
+
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+)
+
+const defaultBlinkSpeed = time.Millisecond * 530
+
+// initialBlinkMsg initializes cursor blinking.
+type initialBlinkMsg struct{}
+
+// BlinkMsg signals that the cursor should blink. It contains metadata that
+// allows us to tell if the blink message is the one we're expecting.
+type BlinkMsg struct {
+ id int
+ tag int
+}
+
+// blinkCanceled is sent when a blink operation is canceled.
+type blinkCanceled struct{}
+
+// blinkCtx manages cursor blinking.
+type blinkCtx struct {
+ ctx context.Context
+ cancel context.CancelFunc
+}
+
+// Mode describes the behavior of the cursor.
+type Mode int
+
+// Available cursor modes.
+const (
+ CursorBlink Mode = iota
+ CursorStatic
+ CursorHide
+)
+
+// String returns the cursor mode in a human-readable format. This method is
+// provisional and for informational purposes only.
+func (c Mode) String() string {
+ return [...]string{
+ "blink",
+ "static",
+ "hidden",
+ }[c]
+}
+
+// Model is the Bubble Tea model for this cursor element.
+type Model struct {
+ BlinkSpeed time.Duration
+ // Style for styling the cursor block.
+ Style lipgloss.Style
+ // TextStyle is the style used for the cursor when it is hidden (when blinking).
+ // I.e. displaying normal text.
+ TextStyle lipgloss.Style
+
+ // char is the character under the cursor
+ char string
+ // The ID of this Model as it relates to other cursors
+ id int
+ // focus indicates whether the containing input is focused
+ focus bool
+ // Cursor Blink state.
+ Blink bool
+ // Used to manage cursor blink
+ blinkCtx *blinkCtx
+ // The ID of the blink message we're expecting to receive.
+ blinkTag int
+ // mode determines the behavior of the cursor
+ mode Mode
+}
+
+// New creates a new model with default settings.
+func New() Model {
+ return Model{
+ BlinkSpeed: defaultBlinkSpeed,
+
+ Blink: true,
+ mode: CursorBlink,
+
+ blinkCtx: &blinkCtx{
+ ctx: context.Background(),
+ },
+ }
+}
+
+// Update updates the cursor.
+func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
+ switch msg := msg.(type) {
+ case initialBlinkMsg:
+ // We accept all initialBlinkMsgs generated by the Blink command.
+
+ if m.mode != CursorBlink || !m.focus {
+ return m, nil
+ }
+
+ cmd := m.BlinkCmd()
+ return m, cmd
+
+ case BlinkMsg:
+ // We're choosy about whether to accept blinkMsgs so that our cursor
+ // only exactly when it should.
+
+ // Is this model blink-able?
+ if m.mode != CursorBlink || !m.focus {
+ return m, nil
+ }
+
+ // Were we expecting this blink message?
+ if msg.id != m.id || msg.tag != m.blinkTag {
+ return m, nil
+ }
+
+ var cmd tea.Cmd
+ if m.mode == CursorBlink {
+ m.Blink = !m.Blink
+ cmd = m.BlinkCmd()
+ }
+ return m, cmd
+
+ case blinkCanceled: // no-op
+ return m, nil
+ }
+ return m, nil
+}
+
+// Mode returns the model's cursor mode. For available cursor modes, see
+// type Mode.
+func (m Model) Mode() Mode {
+ return m.mode
+}
+
+// SetMode sets the model's cursor mode. This method returns a command.
+//
+// For available cursor modes, see type CursorMode.
+func (m *Model) SetMode(mode Mode) tea.Cmd {
+ m.mode = mode
+ m.Blink = m.mode == CursorHide || !m.focus
+ if mode == CursorBlink {
+ return Blink
+ }
+ return nil
+}
+
+// BlinkCmd is a command used to manage cursor blinking.
+func (m *Model) BlinkCmd() tea.Cmd {
+ if m.mode != CursorBlink {
+ return nil
+ }
+
+ if m.blinkCtx != nil && m.blinkCtx.cancel != nil {
+ m.blinkCtx.cancel()
+ }
+
+ ctx, cancel := context.WithTimeout(m.blinkCtx.ctx, m.BlinkSpeed)
+ m.blinkCtx.cancel = cancel
+
+ m.blinkTag++
+
+ return func() tea.Msg {
+ defer cancel()
+ <-ctx.Done()
+ if ctx.Err() == context.DeadlineExceeded {
+ return BlinkMsg{id: m.id, tag: m.blinkTag}
+ }
+ return blinkCanceled{}
+ }
+}
+
+// Blink is a command used to initialize cursor blinking.
+func Blink() tea.Msg {
+ return initialBlinkMsg{}
+}
+
+// Focus focuses the cursor to allow it to blink if desired.
+func (m *Model) Focus() tea.Cmd {
+ m.focus = true
+ m.Blink = m.mode == CursorHide // show the cursor unless we've explicitly hidden it
+
+ if m.mode == CursorBlink && m.focus {
+ return m.BlinkCmd()
+ }
+ return nil
+}
+
+// Blur blurs the cursor.
+func (m *Model) Blur() {
+ m.focus = false
+ m.Blink = true
+}
+
+// SetChar sets the character under the cursor.
+func (m *Model) SetChar(char string) {
+ m.char = char
+}
+
+// View displays the cursor.
+func (m Model) View() string {
+ if m.Blink {
+ return m.TextStyle.Inline(true).Render(m.char)
+ }
+ return m.Style.Inline(true).Reverse(true).Render(m.char)
+}
diff --git a/vendor/github.com/charmbracelet/bubbles/key/key.go b/vendor/github.com/charmbracelet/bubbles/key/key.go
new file mode 100644
index 0000000..c7888fa
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbles/key/key.go
@@ -0,0 +1,142 @@
+// Package key provides some types and functions for generating user-definable
+// keymappings useful in Bubble Tea components. There are a few different ways
+// you can define a keymapping with this package. Here's one example:
+//
+// type KeyMap struct {
+// Up key.Binding
+// Down key.Binding
+// }
+//
+// var DefaultKeyMap = KeyMap{
+// Up: key.NewBinding(
+// key.WithKeys("k", "up"), // actual keybindings
+// key.WithHelp("↑/k", "move up"), // corresponding help text
+// ),
+// Down: key.NewBinding(
+// key.WithKeys("j", "down"),
+// key.WithHelp("↓/j", "move down"),
+// ),
+// }
+//
+// func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+// switch msg := msg.(type) {
+// case tea.KeyMsg:
+// switch {
+// case key.Matches(msg, DefaultKeyMap.Up):
+// // The user pressed up
+// case key.Matches(msg, DefaultKeyMap.Down):
+// // The user pressed down
+// }
+// }
+//
+// // ...
+// }
+//
+// The help information, which is not used in the example above, can be used
+// to render help text for keystrokes in your views.
+package key
+
+import (
+ tea "github.com/charmbracelet/bubbletea"
+)
+
+// Binding describes a set of keybindings and, optionally, their associated
+// help text.
+type Binding struct {
+ keys []string
+ help Help
+ disabled bool
+}
+
+// BindingOpt is an initialization option for a keybinding. It's used as an
+// argument to NewBinding.
+type BindingOpt func(*Binding)
+
+// NewBinding returns a new keybinding from a set of BindingOpt options.
+func NewBinding(opts ...BindingOpt) Binding {
+ b := &Binding{}
+ for _, opt := range opts {
+ opt(b)
+ }
+ return *b
+}
+
+// WithKeys initializes a keybinding with the given keystrokes.
+func WithKeys(keys ...string) BindingOpt {
+ return func(b *Binding) {
+ b.keys = keys
+ }
+}
+
+// WithHelp initializes a keybinding with the given help text.
+func WithHelp(key, desc string) BindingOpt {
+ return func(b *Binding) {
+ b.help = Help{Key: key, Desc: desc}
+ }
+}
+
+// WithDisabled initializes a disabled keybinding.
+func WithDisabled() BindingOpt {
+ return func(b *Binding) {
+ b.disabled = true
+ }
+}
+
+// SetKeys sets the keys for the keybinding.
+func (b *Binding) SetKeys(keys ...string) {
+ b.keys = keys
+}
+
+// Keys returns the keys for the keybinding.
+func (b Binding) Keys() []string {
+ return b.keys
+}
+
+// SetHelp sets the help text for the keybinding.
+func (b *Binding) SetHelp(key, desc string) {
+ b.help = Help{Key: key, Desc: desc}
+}
+
+// Help returns the Help information for the keybinding.
+func (b Binding) Help() Help {
+ return b.help
+}
+
+// Enabled returns whether or not the keybinding is enabled. Disabled
+// keybindings won't be activated and won't show up in help. Keybindings are
+// enabled by default.
+func (b Binding) Enabled() bool {
+ return !b.disabled && b.keys != nil
+}
+
+// SetEnabled enables or disables the keybinding.
+func (b *Binding) SetEnabled(v bool) {
+ b.disabled = !v
+}
+
+// Unbind removes the keys and help from this binding, effectively nullifying
+// it. This is a step beyond disabling it, since applications can enable
+// or disable key bindings based on application state.
+func (b *Binding) Unbind() {
+ b.keys = nil
+ b.help = Help{}
+}
+
+// Help is help information for a given keybinding.
+type Help struct {
+ Key string
+ Desc string
+}
+
+// Matches checks if the given KeyMsg matches the given bindings.
+func Matches(k tea.KeyMsg, b ...Binding) bool {
+ keys := k.String()
+ for _, binding := range b {
+ for _, v := range binding.keys {
+ if keys == v && binding.Enabled() {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go b/vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go
new file mode 100644
index 0000000..82ea90a
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go
@@ -0,0 +1,102 @@
+// Package runeutil provides a utility function for use in Bubbles
+// that can process Key messages containing runes.
+package runeutil
+
+import (
+ "unicode"
+ "unicode/utf8"
+)
+
+// Sanitizer is a helper for bubble widgets that want to process
+// Runes from input key messages.
+type Sanitizer interface {
+ // Sanitize removes control characters from runes in a KeyRunes
+ // message, and optionally replaces newline/carriage return/tabs by a
+ // specified character.
+ //
+ // The rune array is modified in-place if possible. In that case, the
+ // returned slice is the original slice shortened after the control
+ // characters have been removed/translated.
+ Sanitize(runes []rune) []rune
+}
+
+// NewSanitizer constructs a rune sanitizer.
+func NewSanitizer(opts ...Option) Sanitizer {
+ s := sanitizer{
+ replaceNewLine: []rune("\n"),
+ replaceTab: []rune(" "),
+ }
+ for _, o := range opts {
+ s = o(s)
+ }
+ return &s
+}
+
+// Option is the type of option that can be passed to Sanitize().
+type Option func(sanitizer) sanitizer
+
+// ReplaceTabs replaces tabs by the specified string.
+func ReplaceTabs(tabRepl string) Option {
+ return func(s sanitizer) sanitizer {
+ s.replaceTab = []rune(tabRepl)
+ return s
+ }
+}
+
+// ReplaceNewlines replaces newline characters by the specified string.
+func ReplaceNewlines(nlRepl string) Option {
+ return func(s sanitizer) sanitizer {
+ s.replaceNewLine = []rune(nlRepl)
+ return s
+ }
+}
+
+func (s *sanitizer) Sanitize(runes []rune) []rune {
+ // dstrunes are where we are storing the result.
+ dstrunes := runes[:0:len(runes)]
+ // copied indicates whether dstrunes is an alias of runes
+ // or a copy. We need a copy when dst moves past src.
+ // We use this as an optimization to avoid allocating
+ // a new rune slice in the common case where the output
+ // is smaller or equal to the input.
+ copied := false
+
+ for src := 0; src < len(runes); src++ {
+ r := runes[src]
+ switch {
+ case r == utf8.RuneError:
+ // skip
+
+ case r == '\r' || r == '\n':
+ if len(dstrunes)+len(s.replaceNewLine) > src && !copied {
+ dst := len(dstrunes)
+ dstrunes = make([]rune, dst, len(runes)+len(s.replaceNewLine))
+ copy(dstrunes, runes[:dst])
+ copied = true
+ }
+ dstrunes = append(dstrunes, s.replaceNewLine...)
+
+ case r == '\t':
+ if len(dstrunes)+len(s.replaceTab) > src && !copied {
+ dst := len(dstrunes)
+ dstrunes = make([]rune, dst, len(runes)+len(s.replaceTab))
+ copy(dstrunes, runes[:dst])
+ copied = true
+ }
+ dstrunes = append(dstrunes, s.replaceTab...)
+
+ case unicode.IsControl(r):
+ // Other control characters: skip.
+
+ default:
+ // Keep the character.
+ dstrunes = append(dstrunes, runes[src])
+ }
+ }
+ return dstrunes
+}
+
+type sanitizer struct {
+ replaceNewLine []rune
+ replaceTab []rune
+}
diff --git a/vendor/github.com/charmbracelet/bubbles/spinner/spinner.go b/vendor/github.com/charmbracelet/bubbles/spinner/spinner.go
new file mode 100644
index 0000000..bb53597
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbles/spinner/spinner.go
@@ -0,0 +1,230 @@
+package spinner
+
+import (
+ "sync"
+ "time"
+
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+)
+
+// Internal ID management. Used during animating to ensure that frame messages
+// are received only by spinner components that sent them.
+var (
+ lastID int
+ idMtx sync.Mutex
+)
+
+// Return the next ID we should use on the Model.
+func nextID() int {
+ idMtx.Lock()
+ defer idMtx.Unlock()
+ lastID++
+ return lastID
+}
+
+// Spinner is a set of frames used in animating the spinner.
+type Spinner struct {
+ Frames []string
+ FPS time.Duration
+}
+
+// Some spinners to choose from. You could also make your own.
+var (
+ Line = Spinner{
+ Frames: []string{"|", "/", "-", "\\"},
+ FPS: time.Second / 10, //nolint:gomnd
+ }
+ Dot = Spinner{
+ Frames: []string{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "},
+ FPS: time.Second / 10, //nolint:gomnd
+ }
+ MiniDot = Spinner{
+ Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
+ FPS: time.Second / 12, //nolint:gomnd
+ }
+ Jump = Spinner{
+ Frames: []string{"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"},
+ FPS: time.Second / 10, //nolint:gomnd
+ }
+ Pulse = Spinner{
+ Frames: []string{"█", "▓", "▒", "░"},
+ FPS: time.Second / 8, //nolint:gomnd
+ }
+ Points = Spinner{
+ Frames: []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●"},
+ FPS: time.Second / 7, //nolint:gomnd
+ }
+ Globe = Spinner{
+ Frames: []string{"🌍", "🌎", "🌏"},
+ FPS: time.Second / 4, //nolint:gomnd
+ }
+ Moon = Spinner{
+ Frames: []string{"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"},
+ FPS: time.Second / 8, //nolint:gomnd
+ }
+ Monkey = Spinner{
+ Frames: []string{"🙈", "🙉", "🙊"},
+ FPS: time.Second / 3, //nolint:gomnd
+ }
+ Meter = Spinner{
+ Frames: []string{
+ "▱▱▱",
+ "▰▱▱",
+ "▰▰▱",
+ "▰▰▰",
+ "▰▰▱",
+ "▰▱▱",
+ "▱▱▱",
+ },
+ FPS: time.Second / 7, //nolint:gomnd
+ }
+ Hamburger = Spinner{
+ Frames: []string{"☱", "☲", "☴", "☲"},
+ FPS: time.Second / 3, //nolint:gomnd
+ }
+ Ellipsis = Spinner{
+ Frames: []string{"", ".", "..", "..."},
+ FPS: time.Second / 3, //nolint:gomnd
+ }
+)
+
+// Model contains the state for the spinner. Use New to create new models
+// rather than using Model as a struct literal.
+type Model struct {
+ // Spinner settings to use. See type Spinner.
+ Spinner Spinner
+
+ // Style sets the styling for the spinner. Most of the time you'll just
+ // want foreground and background coloring, and potentially some padding.
+ //
+ // For an introduction to styling with Lip Gloss see:
+ // https://github.com/charmbracelet/lipgloss
+ Style lipgloss.Style
+
+ frame int
+ id int
+ tag int
+}
+
+// ID returns the spinner's unique ID.
+func (m Model) ID() int {
+ return m.id
+}
+
+// New returns a model with default values.
+func New(opts ...Option) Model {
+ m := Model{
+ Spinner: Line,
+ id: nextID(),
+ }
+
+ for _, opt := range opts {
+ opt(&m)
+ }
+
+ return m
+}
+
+// NewModel returns a model with default values.
+//
+// Deprecated: use [New] instead.
+var NewModel = New
+
+// TickMsg indicates that the timer has ticked and we should render a frame.
+type TickMsg struct {
+ Time time.Time
+ tag int
+ ID int
+}
+
+// Update is the Tea update function.
+func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
+ switch msg := msg.(type) {
+ case TickMsg:
+ // If an ID is set, and the ID doesn't belong to this spinner, reject
+ // the message.
+ if msg.ID > 0 && msg.ID != m.id {
+ return m, nil
+ }
+
+ // If a tag is set, and it's not the one we expect, reject the message.
+ // This prevents the spinner from receiving too many messages and
+ // thus spinning too fast.
+ if msg.tag > 0 && msg.tag != m.tag {
+ return m, nil
+ }
+
+ m.frame++
+ if m.frame >= len(m.Spinner.Frames) {
+ m.frame = 0
+ }
+
+ m.tag++
+ return m, m.tick(m.id, m.tag)
+ default:
+ return m, nil
+ }
+}
+
+// View renders the model's view.
+func (m Model) View() string {
+ if m.frame >= len(m.Spinner.Frames) {
+ return "(error)"
+ }
+
+ return m.Style.Render(m.Spinner.Frames[m.frame])
+}
+
+// Tick is the command used to advance the spinner one frame. Use this command
+// to effectively start the spinner.
+func (m Model) Tick() tea.Msg {
+ return TickMsg{
+ // The time at which the tick occurred.
+ Time: time.Now(),
+
+ // The ID of the spinner that this message belongs to. This can be
+ // helpful when routing messages, however bear in mind that spinners
+ // will ignore messages that don't contain ID by default.
+ ID: m.id,
+
+ tag: m.tag,
+ }
+}
+
+func (m Model) tick(id, tag int) tea.Cmd {
+ return tea.Tick(m.Spinner.FPS, func(t time.Time) tea.Msg {
+ return TickMsg{
+ Time: t,
+ ID: id,
+ tag: tag,
+ }
+ })
+}
+
+// Tick is the command used to advance the spinner one frame. Use this command
+// to effectively start the spinner.
+//
+// Deprecated: Use [Model.Tick] instead.
+func Tick() tea.Msg {
+ return TickMsg{Time: time.Now()}
+}
+
+// Option is used to set options in New. For example:
+//
+// spinner := New(WithSpinner(Dot))
+type Option func(*Model)
+
+// WithSpinner is an option to set the spinner.
+func WithSpinner(spinner Spinner) Option {
+ return func(m *Model) {
+ m.Spinner = spinner
+ }
+}
+
+// WithStyle is an option to set the spinner style.
+func WithStyle(style lipgloss.Style) Option {
+ return func(m *Model) {
+ m.Style = style
+ }
+}
diff --git a/vendor/github.com/charmbracelet/bubbles/textinput/textinput.go b/vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
new file mode 100644
index 0000000..501f9a7
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
@@ -0,0 +1,882 @@
+package textinput
+
+import (
+ "reflect"
+ "strings"
+ "time"
+ "unicode"
+
+ "github.com/atotto/clipboard"
+ "github.com/charmbracelet/bubbles/cursor"
+ "github.com/charmbracelet/bubbles/key"
+ "github.com/charmbracelet/bubbles/runeutil"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+ rw "github.com/mattn/go-runewidth"
+ "github.com/rivo/uniseg"
+)
+
+// Internal messages for clipboard operations.
+type pasteMsg string
+type pasteErrMsg struct{ error }
+
+// EchoMode sets the input behavior of the text input field.
+type EchoMode int
+
+const (
+ // EchoNormal displays text as is. This is the default behavior.
+ EchoNormal EchoMode = iota
+
+ // EchoPassword displays the EchoCharacter mask instead of actual
+ // characters. This is commonly used for password fields.
+ EchoPassword
+
+ // EchoNone displays nothing as characters are entered. This is commonly
+ // seen for password fields on the command line.
+ EchoNone
+)
+
+// ValidateFunc is a function that returns an error if the input is invalid.
+type ValidateFunc func(string) error
+
+// KeyMap is the key bindings for different actions within the textinput.
+type KeyMap struct {
+ CharacterForward key.Binding
+ CharacterBackward key.Binding
+ WordForward key.Binding
+ WordBackward key.Binding
+ DeleteWordBackward key.Binding
+ DeleteWordForward key.Binding
+ DeleteAfterCursor key.Binding
+ DeleteBeforeCursor key.Binding
+ DeleteCharacterBackward key.Binding
+ DeleteCharacterForward key.Binding
+ LineStart key.Binding
+ LineEnd key.Binding
+ Paste key.Binding
+ AcceptSuggestion key.Binding
+ NextSuggestion key.Binding
+ PrevSuggestion key.Binding
+}
+
+// DefaultKeyMap is the default set of key bindings for navigating and acting
+// upon the textinput.
+var DefaultKeyMap = KeyMap{
+ CharacterForward: key.NewBinding(key.WithKeys("right", "ctrl+f")),
+ CharacterBackward: key.NewBinding(key.WithKeys("left", "ctrl+b")),
+ WordForward: key.NewBinding(key.WithKeys("alt+right", "alt+f")),
+ WordBackward: key.NewBinding(key.WithKeys("alt+left", "alt+b")),
+ DeleteWordBackward: key.NewBinding(key.WithKeys("alt+backspace", "ctrl+w")),
+ DeleteWordForward: key.NewBinding(key.WithKeys("alt+delete", "alt+d")),
+ DeleteAfterCursor: key.NewBinding(key.WithKeys("ctrl+k")),
+ DeleteBeforeCursor: key.NewBinding(key.WithKeys("ctrl+u")),
+ DeleteCharacterBackward: key.NewBinding(key.WithKeys("backspace", "ctrl+h")),
+ DeleteCharacterForward: key.NewBinding(key.WithKeys("delete", "ctrl+d")),
+ LineStart: key.NewBinding(key.WithKeys("home", "ctrl+a")),
+ LineEnd: key.NewBinding(key.WithKeys("end", "ctrl+e")),
+ Paste: key.NewBinding(key.WithKeys("ctrl+v")),
+ AcceptSuggestion: key.NewBinding(key.WithKeys("tab")),
+ NextSuggestion: key.NewBinding(key.WithKeys("down", "ctrl+n")),
+ PrevSuggestion: key.NewBinding(key.WithKeys("up", "ctrl+p")),
+}
+
+// Model is the Bubble Tea model for this text input element.
+type Model struct {
+ Err error
+
+ // General settings.
+ Prompt string
+ Placeholder string
+ EchoMode EchoMode
+ EchoCharacter rune
+ Cursor cursor.Model
+
+ // Deprecated: use [cursor.BlinkSpeed] instead.
+ BlinkSpeed time.Duration
+
+ // Styles. These will be applied as inline styles.
+ //
+ // For an introduction to styling with Lip Gloss see:
+ // https://github.com/charmbracelet/lipgloss
+ PromptStyle lipgloss.Style
+ TextStyle lipgloss.Style
+ PlaceholderStyle lipgloss.Style
+ CompletionStyle lipgloss.Style
+
+ // Deprecated: use Cursor.Style instead.
+ CursorStyle lipgloss.Style
+
+ // CharLimit is the maximum amount of characters this input element will
+ // accept. If 0 or less, there's no limit.
+ CharLimit int
+
+ // Width is the maximum number of characters that can be displayed at once.
+ // It essentially treats the text field like a horizontally scrolling
+ // viewport. If 0 or less this setting is ignored.
+ Width int
+
+ // KeyMap encodes the keybindings recognized by the widget.
+ KeyMap KeyMap
+
+ // Underlying text value.
+ value []rune
+
+ // focus indicates whether user input focus should be on this input
+ // component. When false, ignore keyboard input and hide the cursor.
+ focus bool
+
+ // Cursor position.
+ pos int
+
+ // Used to emulate a viewport when width is set and the content is
+ // overflowing.
+ offset int
+ offsetRight int
+
+ // Validate is a function that checks whether or not the text within the
+ // input is valid. If it is not valid, the `Err` field will be set to the
+ // error returned by the function. If the function is not defined, all
+ // input is considered valid.
+ Validate ValidateFunc
+
+ // rune sanitizer for input.
+ rsan runeutil.Sanitizer
+
+ // Should the input suggest to complete
+ ShowSuggestions bool
+
+ // suggestions is a list of suggestions that may be used to complete the
+ // input.
+ suggestions [][]rune
+ matchedSuggestions [][]rune
+ currentSuggestionIndex int
+}
+
+// New creates a new model with default settings.
+func New() Model {
+ return Model{
+ Prompt: "> ",
+ EchoCharacter: '*',
+ CharLimit: 0,
+ PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
+ ShowSuggestions: false,
+ CompletionStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
+ Cursor: cursor.New(),
+ KeyMap: DefaultKeyMap,
+
+ suggestions: [][]rune{},
+ value: nil,
+ focus: false,
+ pos: 0,
+ }
+}
+
+// NewModel creates a new model with default settings.
+//
+// Deprecated: Use [New] instead.
+var NewModel = New
+
+// SetValue sets the value of the text input.
+func (m *Model) SetValue(s string) {
+ // Clean up any special characters in the input provided by the
+ // caller. This avoids bugs due to e.g. tab characters and whatnot.
+ runes := m.san().Sanitize([]rune(s))
+ m.setValueInternal(runes)
+}
+
+func (m *Model) setValueInternal(runes []rune) {
+ if m.Validate != nil {
+ if err := m.Validate(string(runes)); err != nil {
+ m.Err = err
+ return
+ }
+ }
+
+ empty := len(m.value) == 0
+ m.Err = nil
+
+ if m.CharLimit > 0 && len(runes) > m.CharLimit {
+ m.value = runes[:m.CharLimit]
+ } else {
+ m.value = runes
+ }
+ if (m.pos == 0 && empty) || m.pos > len(m.value) {
+ m.SetCursor(len(m.value))
+ }
+ m.handleOverflow()
+}
+
+// Value returns the value of the text input.
+func (m Model) Value() string {
+ return string(m.value)
+}
+
+// Position returns the cursor position.
+func (m Model) Position() int {
+ return m.pos
+}
+
+// SetCursor moves the cursor to the given position. If the position is
+// out of bounds the cursor will be moved to the start or end accordingly.
+func (m *Model) SetCursor(pos int) {
+ m.pos = clamp(pos, 0, len(m.value))
+ m.handleOverflow()
+}
+
+// CursorStart moves the cursor to the start of the input field.
+func (m *Model) CursorStart() {
+ m.SetCursor(0)
+}
+
+// CursorEnd moves the cursor to the end of the input field.
+func (m *Model) CursorEnd() {
+ m.SetCursor(len(m.value))
+}
+
+// Focused returns the focus state on the model.
+func (m Model) Focused() bool {
+ return m.focus
+}
+
+// Focus sets the focus state on the model. When the model is in focus it can
+// receive keyboard input and the cursor will be shown.
+func (m *Model) Focus() tea.Cmd {
+ m.focus = true
+ return m.Cursor.Focus()
+}
+
+// Blur removes the focus state on the model. When the model is blurred it can
+// not receive keyboard input and the cursor will be hidden.
+func (m *Model) Blur() {
+ m.focus = false
+ m.Cursor.Blur()
+}
+
+// Reset sets the input to its default state with no input.
+func (m *Model) Reset() {
+ m.value = nil
+ m.SetCursor(0)
+}
+
+// SetSuggestions sets the suggestions for the input.
+func (m *Model) SetSuggestions(suggestions []string) {
+ m.suggestions = make([][]rune, len(suggestions))
+ for i, s := range suggestions {
+ m.suggestions[i] = []rune(s)
+ }
+
+ m.updateSuggestions()
+}
+
+// rsan initializes or retrieves the rune sanitizer.
+func (m *Model) san() runeutil.Sanitizer {
+ if m.rsan == nil {
+ // Textinput has all its input on a single line so collapse
+ // newlines/tabs to single spaces.
+ m.rsan = runeutil.NewSanitizer(
+ runeutil.ReplaceTabs(" "), runeutil.ReplaceNewlines(" "))
+ }
+ return m.rsan
+}
+
+func (m *Model) insertRunesFromUserInput(v []rune) {
+ // Clean up any special characters in the input provided by the
+ // clipboard. This avoids bugs due to e.g. tab characters and
+ // whatnot.
+ paste := m.san().Sanitize(v)
+
+ var availSpace int
+ if m.CharLimit > 0 {
+ availSpace = m.CharLimit - len(m.value)
+
+ // If the char limit's been reached, cancel.
+ if availSpace <= 0 {
+ return
+ }
+
+ // If there's not enough space to paste the whole thing cut the pasted
+ // runes down so they'll fit.
+ if availSpace < len(paste) {
+ paste = paste[:availSpace]
+ }
+ }
+
+ // Stuff before and after the cursor
+ head := m.value[:m.pos]
+ tailSrc := m.value[m.pos:]
+ tail := make([]rune, len(tailSrc))
+ copy(tail, tailSrc)
+
+ oldPos := m.pos
+
+ // Insert pasted runes
+ for _, r := range paste {
+ head = append(head, r)
+ m.pos++
+ if m.CharLimit > 0 {
+ availSpace--
+ if availSpace <= 0 {
+ break
+ }
+ }
+ }
+
+ // Put it all back together
+ value := append(head, tail...)
+ m.setValueInternal(value)
+
+ if m.Err != nil {
+ m.pos = oldPos
+ }
+}
+
+// If a max width is defined, perform some logic to treat the visible area
+// as a horizontally scrolling viewport.
+func (m *Model) handleOverflow() {
+ if m.Width <= 0 || uniseg.StringWidth(string(m.value)) <= m.Width {
+ m.offset = 0
+ m.offsetRight = len(m.value)
+ return
+ }
+
+ // Correct right offset if we've deleted characters
+ m.offsetRight = min(m.offsetRight, len(m.value))
+
+ if m.pos < m.offset {
+ m.offset = m.pos
+
+ w := 0
+ i := 0
+ runes := m.value[m.offset:]
+
+ for i < len(runes) && w <= m.Width {
+ w += rw.RuneWidth(runes[i])
+ if w <= m.Width+1 {
+ i++
+ }
+ }
+
+ m.offsetRight = m.offset + i
+ } else if m.pos >= m.offsetRight {
+ m.offsetRight = m.pos
+
+ w := 0
+ runes := m.value[:m.offsetRight]
+ i := len(runes) - 1
+
+ for i > 0 && w < m.Width {
+ w += rw.RuneWidth(runes[i])
+ if w <= m.Width {
+ i--
+ }
+ }
+
+ m.offset = m.offsetRight - (len(runes) - 1 - i)
+ }
+}
+
+// deleteBeforeCursor deletes all text before the cursor.
+func (m *Model) deleteBeforeCursor() {
+ m.value = m.value[m.pos:]
+ m.offset = 0
+ m.SetCursor(0)
+}
+
+// deleteAfterCursor deletes all text after the cursor. If input is masked
+// delete everything after the cursor so as not to reveal word breaks in the
+// masked input.
+func (m *Model) deleteAfterCursor() {
+ m.value = m.value[:m.pos]
+ m.SetCursor(len(m.value))
+}
+
+// deleteWordBackward deletes the word left to the cursor.
+func (m *Model) deleteWordBackward() {
+ if m.pos == 0 || len(m.value) == 0 {
+ return
+ }
+
+ if m.EchoMode != EchoNormal {
+ m.deleteBeforeCursor()
+ return
+ }
+
+ // Linter note: it's critical that we acquire the initial cursor position
+ // here prior to altering it via SetCursor() below. As such, moving this
+ // call into the corresponding if clause does not apply here.
+ oldPos := m.pos //nolint:ifshort
+
+ m.SetCursor(m.pos - 1)
+ for unicode.IsSpace(m.value[m.pos]) {
+ if m.pos <= 0 {
+ break
+ }
+ // ignore series of whitespace before cursor
+ m.SetCursor(m.pos - 1)
+ }
+
+ for m.pos > 0 {
+ if !unicode.IsSpace(m.value[m.pos]) {
+ m.SetCursor(m.pos - 1)
+ } else {
+ if m.pos > 0 {
+ // keep the previous space
+ m.SetCursor(m.pos + 1)
+ }
+ break
+ }
+ }
+
+ if oldPos > len(m.value) {
+ m.value = m.value[:m.pos]
+ } else {
+ m.value = append(m.value[:m.pos], m.value[oldPos:]...)
+ }
+}
+
+// deleteWordForward deletes the word right to the cursor. If input is masked
+// delete everything after the cursor so as not to reveal word breaks in the
+// masked input.
+func (m *Model) deleteWordForward() {
+ if m.pos >= len(m.value) || len(m.value) == 0 {
+ return
+ }
+
+ if m.EchoMode != EchoNormal {
+ m.deleteAfterCursor()
+ return
+ }
+
+ oldPos := m.pos
+ m.SetCursor(m.pos + 1)
+ for unicode.IsSpace(m.value[m.pos]) {
+ // ignore series of whitespace after cursor
+ m.SetCursor(m.pos + 1)
+
+ if m.pos >= len(m.value) {
+ break
+ }
+ }
+
+ for m.pos < len(m.value) {
+ if !unicode.IsSpace(m.value[m.pos]) {
+ m.SetCursor(m.pos + 1)
+ } else {
+ break
+ }
+ }
+
+ if m.pos > len(m.value) {
+ m.value = m.value[:oldPos]
+ } else {
+ m.value = append(m.value[:oldPos], m.value[m.pos:]...)
+ }
+
+ m.SetCursor(oldPos)
+}
+
+// wordBackward moves the cursor one word to the left. If input is masked, move
+// input to the start so as not to reveal word breaks in the masked input.
+func (m *Model) wordBackward() {
+ if m.pos == 0 || len(m.value) == 0 {
+ return
+ }
+
+ if m.EchoMode != EchoNormal {
+ m.CursorStart()
+ return
+ }
+
+ i := m.pos - 1
+ for i >= 0 {
+ if unicode.IsSpace(m.value[i]) {
+ m.SetCursor(m.pos - 1)
+ i--
+ } else {
+ break
+ }
+ }
+
+ for i >= 0 {
+ if !unicode.IsSpace(m.value[i]) {
+ m.SetCursor(m.pos - 1)
+ i--
+ } else {
+ break
+ }
+ }
+}
+
+// wordForward moves the cursor one word to the right. If the input is masked,
+// move input to the end so as not to reveal word breaks in the masked input.
+func (m *Model) wordForward() {
+ if m.pos >= len(m.value) || len(m.value) == 0 {
+ return
+ }
+
+ if m.EchoMode != EchoNormal {
+ m.CursorEnd()
+ return
+ }
+
+ i := m.pos
+ for i < len(m.value) {
+ if unicode.IsSpace(m.value[i]) {
+ m.SetCursor(m.pos + 1)
+ i++
+ } else {
+ break
+ }
+ }
+
+ for i < len(m.value) {
+ if !unicode.IsSpace(m.value[i]) {
+ m.SetCursor(m.pos + 1)
+ i++
+ } else {
+ break
+ }
+ }
+}
+
+func (m Model) echoTransform(v string) string {
+ switch m.EchoMode {
+ case EchoPassword:
+ return strings.Repeat(string(m.EchoCharacter), uniseg.StringWidth(v))
+ case EchoNone:
+ return ""
+ case EchoNormal:
+ return v
+ default:
+ return v
+ }
+}
+
+// Update is the Bubble Tea update loop.
+func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
+ if !m.focus {
+ return m, nil
+ }
+
+ // Need to check for completion before, because key is configurable and might be double assigned
+ keyMsg, ok := msg.(tea.KeyMsg)
+ if ok && key.Matches(keyMsg, m.KeyMap.AcceptSuggestion) {
+ if m.canAcceptSuggestion() {
+ m.value = append(m.value, m.matchedSuggestions[m.currentSuggestionIndex][len(m.value):]...)
+ m.CursorEnd()
+ }
+ }
+
+ // Let's remember where the position of the cursor currently is so that if
+ // the cursor position changes, we can reset the blink.
+ oldPos := m.pos //nolint
+
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ switch {
+ case key.Matches(msg, m.KeyMap.DeleteWordBackward):
+ m.Err = nil
+ m.deleteWordBackward()
+ case key.Matches(msg, m.KeyMap.DeleteCharacterBackward):
+ m.Err = nil
+ if len(m.value) > 0 {
+ m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...)
+ if m.pos > 0 {
+ m.SetCursor(m.pos - 1)
+ }
+ }
+ case key.Matches(msg, m.KeyMap.WordBackward):
+ m.wordBackward()
+ case key.Matches(msg, m.KeyMap.CharacterBackward):
+ if m.pos > 0 {
+ m.SetCursor(m.pos - 1)
+ }
+ case key.Matches(msg, m.KeyMap.WordForward):
+ m.wordForward()
+ case key.Matches(msg, m.KeyMap.CharacterForward):
+ if m.pos < len(m.value) {
+ m.SetCursor(m.pos + 1)
+ }
+ case key.Matches(msg, m.KeyMap.DeleteWordBackward):
+ m.deleteWordBackward()
+ case key.Matches(msg, m.KeyMap.LineStart):
+ m.CursorStart()
+ case key.Matches(msg, m.KeyMap.DeleteCharacterForward):
+ if len(m.value) > 0 && m.pos < len(m.value) {
+ m.value = append(m.value[:m.pos], m.value[m.pos+1:]...)
+ }
+ case key.Matches(msg, m.KeyMap.LineEnd):
+ m.CursorEnd()
+ case key.Matches(msg, m.KeyMap.DeleteAfterCursor):
+ m.deleteAfterCursor()
+ case key.Matches(msg, m.KeyMap.DeleteBeforeCursor):
+ m.deleteBeforeCursor()
+ case key.Matches(msg, m.KeyMap.Paste):
+ return m, Paste
+ case key.Matches(msg, m.KeyMap.DeleteWordForward):
+ m.deleteWordForward()
+ case key.Matches(msg, m.KeyMap.NextSuggestion):
+ m.nextSuggestion()
+ case key.Matches(msg, m.KeyMap.PrevSuggestion):
+ m.previousSuggestion()
+ default:
+ // Input one or more regular characters.
+ m.insertRunesFromUserInput(msg.Runes)
+ }
+
+ // Check again if can be completed
+ // because value might be something that does not match the completion prefix
+ m.updateSuggestions()
+
+ case pasteMsg:
+ m.insertRunesFromUserInput([]rune(msg))
+
+ case pasteErrMsg:
+ m.Err = msg
+ }
+
+ var cmds []tea.Cmd
+ var cmd tea.Cmd
+
+ m.Cursor, cmd = m.Cursor.Update(msg)
+ cmds = append(cmds, cmd)
+
+ if oldPos != m.pos && m.Cursor.Mode() == cursor.CursorBlink {
+ m.Cursor.Blink = false
+ cmds = append(cmds, m.Cursor.BlinkCmd())
+ }
+
+ m.handleOverflow()
+ return m, tea.Batch(cmds...)
+}
+
+// View renders the textinput in its current state.
+func (m Model) View() string {
+ // Placeholder text
+ if len(m.value) == 0 && m.Placeholder != "" {
+ return m.placeholderView()
+ }
+
+ styleText := m.TextStyle.Inline(true).Render
+
+ value := m.value[m.offset:m.offsetRight]
+ pos := max(0, m.pos-m.offset)
+ v := styleText(m.echoTransform(string(value[:pos])))
+
+ if pos < len(value) {
+ char := m.echoTransform(string(value[pos]))
+ m.Cursor.SetChar(char)
+ v += m.Cursor.View() // cursor and text under it
+ v += styleText(m.echoTransform(string(value[pos+1:]))) // text after cursor
+ v += m.completionView(0) // suggested completion
+ } else {
+ if m.canAcceptSuggestion() {
+ suggestion := m.matchedSuggestions[m.currentSuggestionIndex]
+ if len(value) < len(suggestion) {
+ m.Cursor.TextStyle = m.CompletionStyle
+ m.Cursor.SetChar(m.echoTransform(string(suggestion[pos])))
+ v += m.Cursor.View()
+ v += m.completionView(1)
+ } else {
+ m.Cursor.SetChar(" ")
+ v += m.Cursor.View()
+ }
+ } else {
+ m.Cursor.SetChar(" ")
+ v += m.Cursor.View()
+ }
+ }
+
+ // If a max width and background color were set fill the empty spaces with
+ // the background color.
+ valWidth := uniseg.StringWidth(string(value))
+ if m.Width > 0 && valWidth <= m.Width {
+ padding := max(0, m.Width-valWidth)
+ if valWidth+padding <= m.Width && pos < len(value) {
+ padding++
+ }
+ v += styleText(strings.Repeat(" ", padding))
+ }
+
+ return m.PromptStyle.Render(m.Prompt) + v
+}
+
+// placeholderView returns the prompt and placeholder view, if any.
+func (m Model) placeholderView() string {
+ var (
+ v string
+ p = []rune(m.Placeholder)
+ style = m.PlaceholderStyle.Inline(true).Render
+ )
+
+ m.Cursor.TextStyle = m.PlaceholderStyle
+ m.Cursor.SetChar(string(p[:1]))
+ v += m.Cursor.View()
+
+ // If the entire placeholder is already set and no padding is needed, finish
+ if m.Width < 1 && len(p) <= 1 {
+ return m.PromptStyle.Render(m.Prompt) + v
+ }
+
+ // If Width is set then size placeholder accordingly
+ if m.Width > 0 {
+ // available width is width - len + cursor offset of 1
+ minWidth := lipgloss.Width(m.Placeholder)
+ availWidth := m.Width - minWidth + 1
+
+ // if width < len, 'subtract'(add) number to len and dont add padding
+ if availWidth < 0 {
+ minWidth += availWidth
+ availWidth = 0
+ }
+ // append placeholder[len] - cursor, append padding
+ v += style(string(p[1:minWidth]))
+ v += style(strings.Repeat(" ", availWidth))
+ } else {
+ // if there is no width, the placeholder can be any length
+ v += style(string(p[1:]))
+ }
+
+ return m.PromptStyle.Render(m.Prompt) + v
+}
+
+// Blink is a command used to initialize cursor blinking.
+func Blink() tea.Msg {
+ return cursor.Blink()
+}
+
+// Paste is a command for pasting from the clipboard into the text input.
+func Paste() tea.Msg {
+ str, err := clipboard.ReadAll()
+ if err != nil {
+ return pasteErrMsg{err}
+ }
+ return pasteMsg(str)
+}
+
+func clamp(v, low, high int) int {
+ if high < low {
+ low, high = high, low
+ }
+ return min(high, max(low, v))
+}
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func max(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+// Deprecated.
+
+// Deprecated: use cursor.Mode.
+type CursorMode int
+
+const (
+ // Deprecated: use cursor.CursorBlink.
+ CursorBlink = CursorMode(cursor.CursorBlink)
+ // Deprecated: use cursor.CursorStatic.
+ CursorStatic = CursorMode(cursor.CursorStatic)
+ // Deprecated: use cursor.CursorHide.
+ CursorHide = CursorMode(cursor.CursorHide)
+)
+
+func (c CursorMode) String() string {
+ return cursor.Mode(c).String()
+}
+
+// Deprecated: use cursor.Mode().
+func (m Model) CursorMode() CursorMode {
+ return CursorMode(m.Cursor.Mode())
+}
+
+// Deprecated: use cursor.SetMode().
+func (m *Model) SetCursorMode(mode CursorMode) tea.Cmd {
+ return m.Cursor.SetMode(cursor.Mode(mode))
+}
+
+func (m Model) completionView(offset int) string {
+ var (
+ value = m.value
+ style = m.PlaceholderStyle.Inline(true).Render
+ )
+
+ if m.canAcceptSuggestion() {
+ suggestion := m.matchedSuggestions[m.currentSuggestionIndex]
+ if len(value) < len(suggestion) {
+ return style(string(suggestion[len(value)+offset:]))
+ }
+ }
+ return ""
+}
+
+// AvailableSuggestions returns the list of available suggestions.
+func (m *Model) AvailableSuggestions() []string {
+ suggestions := make([]string, len(m.suggestions))
+ for i, s := range m.suggestions {
+ suggestions[i] = string(s)
+ }
+
+ return suggestions
+}
+
+// CurrentSuggestion returns the currently selected suggestion.
+func (m *Model) CurrentSuggestion() string {
+ return string(m.matchedSuggestions[m.currentSuggestionIndex])
+}
+
+// canAcceptSuggestion returns whether there is an acceptable suggestion to
+// autocomplete the current value.
+func (m *Model) canAcceptSuggestion() bool {
+ return len(m.matchedSuggestions) > 0
+}
+
+// updateSuggestions refreshes the list of matching suggestions.
+func (m *Model) updateSuggestions() {
+ if !m.ShowSuggestions {
+ return
+ }
+
+ if len(m.value) <= 0 || len(m.suggestions) <= 0 {
+ m.matchedSuggestions = [][]rune{}
+ return
+ }
+
+ matches := [][]rune{}
+ for _, s := range m.suggestions {
+ suggestion := string(s)
+
+ if strings.HasPrefix(strings.ToLower(suggestion), strings.ToLower(string(m.value))) {
+ matches = append(matches, []rune(suggestion))
+ }
+ }
+ if !reflect.DeepEqual(matches, m.matchedSuggestions) {
+ m.currentSuggestionIndex = 0
+ }
+
+ m.matchedSuggestions = matches
+}
+
+// nextSuggestion selects the next suggestion.
+func (m *Model) nextSuggestion() {
+ m.currentSuggestionIndex = (m.currentSuggestionIndex + 1)
+ if m.currentSuggestionIndex >= len(m.matchedSuggestions) {
+ m.currentSuggestionIndex = 0
+ }
+}
+
+// previousSuggestion selects the previous suggestion.
+func (m *Model) previousSuggestion() {
+ m.currentSuggestionIndex = (m.currentSuggestionIndex - 1)
+ if m.currentSuggestionIndex < 0 {
+ m.currentSuggestionIndex = len(m.matchedSuggestions) - 1
+ }
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/.gitattributes b/vendor/github.com/charmbracelet/bubbletea/.gitattributes
new file mode 100644
index 0000000..6c929d4
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/.gitattributes
@@ -0,0 +1 @@
+*.golden -text
diff --git a/vendor/github.com/charmbracelet/bubbletea/.gitignore b/vendor/github.com/charmbracelet/bubbletea/.gitignore
new file mode 100644
index 0000000..9cc5235
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/.gitignore
@@ -0,0 +1,22 @@
+.DS_Store
+.envrc
+
+examples/fullscreen/fullscreen
+examples/help/help
+examples/http/http
+examples/list-default/list-default
+examples/list-fancy/list-fancy
+examples/list-simple/list-simple
+examples/mouse/mouse
+examples/pager/pager
+examples/progress-download/color_vortex.blend
+examples/progress-download/progress-download
+examples/simple/simple
+examples/spinner/spinner
+examples/textinput/textinput
+examples/textinputs/textinputs
+examples/views/views
+tutorials/basics/basics
+tutorials/commands/commands
+.idea
+coverage.txt
diff --git a/vendor/github.com/charmbracelet/bubbletea/.golangci-soft.yml b/vendor/github.com/charmbracelet/bubbletea/.golangci-soft.yml
new file mode 100644
index 0000000..ef456e0
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/.golangci-soft.yml
@@ -0,0 +1,47 @@
+run:
+ tests: false
+
+issues:
+ include:
+ - EXC0001
+ - EXC0005
+ - EXC0011
+ - EXC0012
+ - EXC0013
+
+ max-issues-per-linter: 0
+ max-same-issues: 0
+
+linters:
+ enable:
+ # - dupl
+ - exhaustive
+ # - exhaustivestruct
+ - goconst
+ - godot
+ - godox
+ - gomnd
+ - gomoddirectives
+ - goprintffuncname
+ - ifshort
+ # - lll
+ - misspell
+ - nakedret
+ - nestif
+ - noctx
+ - nolintlint
+ - prealloc
+ - wrapcheck
+
+ # disable default linters, they are already enabled in .golangci.yml
+ disable:
+ - deadcode
+ - errcheck
+ - gosimple
+ - govet
+ - ineffassign
+ - staticcheck
+ - structcheck
+ - typecheck
+ - unused
+ - varcheck
diff --git a/vendor/github.com/charmbracelet/bubbletea/.golangci.yml b/vendor/github.com/charmbracelet/bubbletea/.golangci.yml
new file mode 100644
index 0000000..a5a91d0
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/.golangci.yml
@@ -0,0 +1,29 @@
+run:
+ tests: false
+
+issues:
+ include:
+ - EXC0001
+ - EXC0005
+ - EXC0011
+ - EXC0012
+ - EXC0013
+
+ max-issues-per-linter: 0
+ max-same-issues: 0
+
+linters:
+ enable:
+ - bodyclose
+ - exportloopref
+ - goimports
+ - gosec
+ - nilerr
+ - predeclared
+ - revive
+ - rowserrcheck
+ - sqlclosecheck
+ - tparallel
+ - unconvert
+ - unparam
+ - whitespace
diff --git a/vendor/github.com/charmbracelet/bubbletea/CONTRIBUTING.md b/vendor/github.com/charmbracelet/bubbletea/CONTRIBUTING.md
new file mode 100644
index 0000000..19ee18c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/CONTRIBUTING.md
@@ -0,0 +1,13 @@
+# Contributing
+
+Pull requests are welcome for any changes.
+
+Consider opening an issue for larger changes to get feedback on the idea from the team.
+
+If your change touches parts of the Bubble Tea renderer or internals, make sure
+that all the examples in the `examples/` folder continue to run correctly.
+
+For commit messages, please use conventional commits[^1] to make it easier to
+generate release notes.
+
+[^1]: https://www.conventionalcommits.org/en/v1.0.0
diff --git a/vendor/github.com/charmbracelet/bubbletea/LICENSE b/vendor/github.com/charmbracelet/bubbletea/LICENSE
new file mode 100644
index 0000000..31d76c1
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020-2023 Charmbracelet, Inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/charmbracelet/bubbletea/README.md b/vendor/github.com/charmbracelet/bubbletea/README.md
new file mode 100644
index 0000000..0834246
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/README.md
@@ -0,0 +1,412 @@
+# Bubble Tea
+
+
+ 
+
+
+
+
+
+The fun, functional and stateful way to build terminal apps. A Go framework
+based on [The Elm Architecture][elm]. Bubble Tea is well-suited for simple and
+complex terminal applications, either inline, full-window, or a mix of both.
+
+
+
+
+
+Bubble Tea is in use in production and includes a number of features and
+performance optimizations we’ve added along the way. Among those is a standard
+framerate-based renderer, a renderer for high-performance scrollable
+regions which works alongside the main renderer, and mouse support.
+
+To get started, see the tutorial below, the [examples][examples], the
+[docs][docs], the [video tutorials][youtube] and some common [resources](#libraries-we-use-with-bubble-tea).
+
+[youtube]: https://charm.sh/yt
+
+## By the way
+
+Be sure to check out [Bubbles][bubbles], a library of common UI components for Bubble Tea.
+
+
+
+
+
+
+***
+
+## Tutorial
+
+Bubble Tea is based on the functional design paradigms of [The Elm
+Architecture][elm], which happens to work nicely with Go. It's a delightful way
+to build applications.
+
+This tutorial assumes you have a working knowledge of Go.
+
+By the way, the non-annotated source code for this program is available
+[on GitHub][tut-source].
+
+[elm]: https://guide.elm-lang.org/architecture/
+[tut-source]:https://github.com/charmbracelet/bubbletea/tree/master/tutorials/basics
+
+### Enough! Let's get to it.
+
+For this tutorial, we're making a shopping list.
+
+To start we'll define our package and import some libraries. Our only external
+import will be the Bubble Tea library, which we'll call `tea` for short.
+
+```go
+package main
+
+import (
+ "fmt"
+ "os"
+
+ tea "github.com/charmbracelet/bubbletea"
+)
+```
+
+Bubble Tea programs are comprised of a **model** that describes the application
+state and three simple methods on that model:
+
+* **Init**, a function that returns an initial command for the application to run.
+* **Update**, a function that handles incoming events and updates the model accordingly.
+* **View**, a function that renders the UI based on the data in the model.
+
+### The Model
+
+So let's start by defining our model which will store our application's state.
+It can be any type, but a `struct` usually makes the most sense.
+
+```go
+type model struct {
+ choices []string // items on the to-do list
+ cursor int // which to-do list item our cursor is pointing at
+ selected map[int]struct{} // which to-do items are selected
+}
+```
+
+### Initialization
+
+Next, we’ll define our application’s initial state. In this case, we’re defining
+a function to return our initial model, however, we could just as easily define
+the initial model as a variable elsewhere, too.
+
+```go
+func initialModel() model {
+ return model{
+ // Our to-do list is a grocery list
+ choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
+
+ // A map which indicates which choices are selected. We're using
+ // the map like a mathematical set. The keys refer to the indexes
+ // of the `choices` slice, above.
+ selected: make(map[int]struct{}),
+ }
+}
+```
+
+Next, we define the `Init` method. `Init` can return a `Cmd` that could perform
+some initial I/O. For now, we don't need to do any I/O, so for the command,
+we'll just return `nil`, which translates to "no command."
+
+```go
+func (m model) Init() tea.Cmd {
+ // Just return `nil`, which means "no I/O right now, please."
+ return nil
+}
+```
+
+### The Update Method
+
+Next up is the update method. The update function is called when ”things
+happen.” Its job is to look at what has happened and return an updated model in
+response. It can also return a `Cmd` to make more things happen, but for now
+don't worry about that part.
+
+In our case, when a user presses the down arrow, `Update`’s job is to notice
+that the down arrow was pressed and move the cursor accordingly (or not).
+
+The “something happened” comes in the form of a `Msg`, which can be any type.
+Messages are the result of some I/O that took place, such as a keypress, timer
+tick, or a response from a server.
+
+We usually figure out which type of `Msg` we received with a type switch, but
+you could also use a type assertion.
+
+For now, we'll just deal with `tea.KeyMsg` messages, which are automatically
+sent to the update function when keys are pressed.
+
+```go
+func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ switch msg := msg.(type) {
+
+ // Is it a key press?
+ case tea.KeyMsg:
+
+ // Cool, what was the actual key pressed?
+ switch msg.String() {
+
+ // These keys should exit the program.
+ case "ctrl+c", "q":
+ return m, tea.Quit
+
+ // The "up" and "k" keys move the cursor up
+ case "up", "k":
+ if m.cursor > 0 {
+ m.cursor--
+ }
+
+ // The "down" and "j" keys move the cursor down
+ case "down", "j":
+ if m.cursor < len(m.choices)-1 {
+ m.cursor++
+ }
+
+ // The "enter" key and the spacebar (a literal space) toggle
+ // the selected state for the item that the cursor is pointing at.
+ case "enter", " ":
+ _, ok := m.selected[m.cursor]
+ if ok {
+ delete(m.selected, m.cursor)
+ } else {
+ m.selected[m.cursor] = struct{}{}
+ }
+ }
+ }
+
+ // Return the updated model to the Bubble Tea runtime for processing.
+ // Note that we're not returning a command.
+ return m, nil
+}
+```
+
+You may have noticed that ctrl+c and q above return
+a `tea.Quit` command with the model. That’s a special command which instructs
+the Bubble Tea runtime to quit, exiting the program.
+
+### The View Method
+
+At last, it’s time to render our UI. Of all the methods, the view is the
+simplest. We look at the model in its current state and use it to return
+a `string`. That string is our UI!
+
+Because the view describes the entire UI of your application, you don’t have to
+worry about redrawing logic and stuff like that. Bubble Tea takes care of it
+for you.
+
+```go
+func (m model) View() string {
+ // The header
+ s := "What should we buy at the market?\n\n"
+
+ // Iterate over our choices
+ for i, choice := range m.choices {
+
+ // Is the cursor pointing at this choice?
+ cursor := " " // no cursor
+ if m.cursor == i {
+ cursor = ">" // cursor!
+ }
+
+ // Is this choice selected?
+ checked := " " // not selected
+ if _, ok := m.selected[i]; ok {
+ checked = "x" // selected!
+ }
+
+ // Render the row
+ s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
+ }
+
+ // The footer
+ s += "\nPress q to quit.\n"
+
+ // Send the UI for rendering
+ return s
+}
+```
+
+### All Together Now
+
+The last step is to simply run our program. We pass our initial model to
+`tea.NewProgram` and let it rip:
+
+```go
+func main() {
+ p := tea.NewProgram(initialModel())
+ if _, err := p.Run(); err != nil {
+ fmt.Printf("Alas, there's been an error: %v", err)
+ os.Exit(1)
+ }
+}
+```
+
+## What’s Next?
+
+This tutorial covers the basics of building an interactive terminal UI, but
+in the real world you'll also need to perform I/O. To learn about that have a
+look at the [Command Tutorial][cmd]. It's pretty simple.
+
+There are also several [Bubble Tea examples][examples] available and, of course,
+there are [Go Docs][docs].
+
+[cmd]: http://github.com/charmbracelet/bubbletea/tree/master/tutorials/commands/
+[examples]: http://github.com/charmbracelet/bubbletea/tree/master/examples
+[docs]: https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc
+
+## Debugging
+
+### Debugging with Delve
+
+Since Bubble Tea apps assume control of stdin and stdout, you’ll need to run
+delve in headless mode and then connect to it:
+
+```bash
+# Start the debugger
+$ dlv debug --headless .
+API server listening at: 127.0.0.1:34241
+
+# Connect to it from another terminal
+$ dlv connect 127.0.0.1:34241
+```
+
+Note that the default port used will vary on your system and per run, so
+actually watch out what address the first `dlv` run tells you to connect to.
+
+### Logging Stuff
+
+You can’t really log to stdout with Bubble Tea because your TUI is busy
+occupying that! You can, however, log to a file by including something like
+the following prior to starting your Bubble Tea program:
+
+```go
+if len(os.Getenv("DEBUG")) > 0 {
+ f, err := tea.LogToFile("debug.log", "debug")
+ if err != nil {
+ fmt.Println("fatal:", err)
+ os.Exit(1)
+ }
+ defer f.Close()
+}
+```
+
+To see what’s being logged in real time, run `tail -f debug.log` while you run
+your program in another window.
+
+## Libraries we use with Bubble Tea
+
+* [Bubbles][bubbles]: Common Bubble Tea components such as text inputs, viewports, spinners and so on
+* [Lip Gloss][lipgloss]: Style, format and layout tools for terminal applications
+* [Harmonica][harmonica]: A spring animation library for smooth, natural motion
+* [BubbleZone][bubblezone]: Easy mouse event tracking for Bubble Tea components
+* [Termenv][termenv]: Advanced ANSI styling for terminal applications
+* [Reflow][reflow]: Advanced ANSI-aware methods for working with text
+
+[bubbles]: https://github.com/charmbracelet/bubbles
+[lipgloss]: https://github.com/charmbracelet/lipgloss
+[harmonica]: https://github.com/charmbracelet/harmonica
+[bubblezone]: https://github.com/lrstanley/bubblezone
+[termenv]: https://github.com/muesli/termenv
+[reflow]: https://github.com/muesli/reflow
+
+## Bubble Tea in the Wild
+
+For some Bubble Tea programs in production, see:
+
+* [AT CLI](https://github.com/daskycodes/at_cli): execute AT Commands via serial port connections
+* [Aztify](https://github.com/Azure/aztfy): bring Microsoft Azure resources under Terraform
+* [brows](https://github.com/rubysolo/brows): a GitHub release browser
+* [Canard](https://github.com/mrusme/canard): an RSS client
+* [charm](https://github.com/charmbracelet/charm): the official Charm user account manager
+* [chezmoi](https://github.com/twpayne/chezmoi): securely manage your dotfiles across multiple machines
+* [chtop](https://github.com/chhetripradeep/chtop): monitor your ClickHouse node without leaving terminal
+* [circumflex](https://github.com/bensadeh/circumflex): read Hacker News in the terminal
+* [clidle](https://github.com/ajeetdsouza/clidle): a Wordle clone
+* [cLive](https://github.com/koki-develop/clive): automate terminal operations and view them live in a browser
+* [container-canary](https://github.com/NVIDIA/container-canary): a container validator
+* [countdown](https://github.com/aldernero/countdown): a multi-event countdown timer
+* [dns53](https://github.com/purpleclay/dns53): dynamic DNS with Amazon Route53. Expose your EC2 quickly, securely and privately
+* [eks-node-viewer](https://github.com/awslabs/eks-node-viewer): a tool for visualizing dynamic node usage within an eks cluster
+* [enola](https://github.com/sherlock-project/enola): hunt down social media accounts by username across social networks
+* [flapioca](https://github.com/kbrgl/flapioca): Flappy Bird on the CLI!
+* [fm](https://github.com/knipferrc/fm): a terminal-based file manager
+* [fork-cleaner](https://github.com/caarlos0/fork-cleaner): clean up old and inactive forks in your GitHub account
+* [fztea](https://github.com/jon4hz/fztea): a Flipper Zero TUI
+* [gambit](https://github.com/maaslalani/gambit): chess in the terminal
+* [gembro](https://git.sr.ht/~rafael/gembro): a mouse-driven Gemini browser
+* [gh-b](https://github.com/joaom00/gh-b): a GitHub CLI extension for managing branches
+* [gh-dash](https://www.github.com/dlvhdr/gh-dash): a GitHub CLI extension for PRs and issues
+* [gitflow-toolkit](https://github.com/mritd/gitflow-toolkit): a GitFlow submission tool
+* [Glow](https://github.com/charmbracelet/glow): a markdown reader, browser, and online markdown stash
+* [go-sweep](https://github.com/maxpaulus43/go-sweep): Minesweeper in the terminal
+* [gocovsh](https://github.com/orlangure/gocovsh): explore Go coverage reports from the CLI
+* [got](https://github.com/fedeztk/got): a simple translator and text-to-speech app build on top of simplytranslate's APIs
+* [hiSHtory](https://github.com/ddworken/hishtory): your shell history in context, synced, and queryable
+* [httpit](https://github.com/gonetx/httpit): a rapid http(s) benchmark tool
+* [IDNT](https://github.com/r-darwish/idnt): a batch software uninstaller
+* [kboard](https://github.com/CamiloGarciaLaRotta/kboard): a typing game
+* [mandelbrot-cli](https://github.com/MicheleFiladelfia/mandelbrot-cli): a multiplatform terminal mandelbrot set explorer
+* [mc](https://github.com/minio/mc): the official [MinIO](https://min.io) client
+* [mergestat](https://github.com/mergestat/mergestat): run SQL queries on git repositories
+* [Neon Modem Overdrive](https://github.com/mrusme/neonmodem): a BBS-style TUI client for Discourse, Lemmy, Lobste.rs and Hacker News
+* [Noted](https://github.com/torbratsberg/noted): a note viewer and manager
+* [nom](https://github.com/guyfedwards/nom): RSS reader and manager
+* [pathos](https://github.com/chip/pathos): a PATH env variable editor
+* [portal](https://github.com/ZinoKader/portal): secure transfers between computers
+* [redis-viewer](https://github.com/SaltFishPr/redis-viewer): a Redis databases browser
+* [scrabbler](https://github.com/wI2L/scrabbler): Automatic draw TUI for your duplicate Scrabble games
+* [sku](https://github.com/fedeztk/sku): Sudoku on the CLI
+* [Slides](https://github.com/maaslalani/slides): a markdown-based presentation tool
+* [SlurmCommander](https://github.com/CLIP-HPC/SlurmCommander): a Slurm workload manager TUI
+* [Soft Serve](https://github.com/charmbracelet/soft-serve): a command-line-first Git server that runs a TUI over SSH
+* [solitaire-tui](https://github.com/brianstrauch/solitaire-tui): Klondike Solitaire for the terminal
+* [StormForge Optimize Controller](https://github.com/thestormforge/optimize-controller): a tool for experimenting with application configurations in Kubernetes
+* [Storydb](https://github.com/grrlopes/storydb): a bash/zsh ctrl+r improved command history finder.
+* [STTG](https://github.com/wille1101/sttg): a teletext client for SVT, Sweden’s national public television station
+* [sttr](https://github.com/abhimanyu003/sttr): a general-purpose text transformer
+* [tasktimer](https://github.com/caarlos0/tasktimer): a dead-simple task timer
+* [termdbms](https://github.com/mathaou/termdbms): a keyboard and mouse driven database browser
+* [ticker](https://github.com/achannarasappa/ticker): a terminal stock viewer and stock position tracker
+* [tran](https://github.com/abdfnx/tran): securely transfer stuff between computers (based on [portal](https://github.com/ZinoKader/portal))
+* [Typer](https://github.com/maaslalani/typer): a typing test
+* [typioca](https://github.com/bloznelis/typioca): Cozy typing speed tester in terminal
+* [tz](https://github.com/oz/tz): an aid for scheduling across multiple time zones
+* [ugm](https://github.com/ariasmn/ugm): a unix user and group browser
+* [walk](https://github.com/antonmedv/walk): a terminal navigator
+* [wander](https://github.com/robinovitch61/wander): a HashiCorp Nomad terminal client
+* [WG Commander](https://github.com/AndrianBdn/wg-cmd): a TUI for a simple WireGuard VPN setup
+* [wishlist](https://github.com/charmbracelet/wishlist): an SSH directory
+
+## Feedback
+
+We'd love to hear your thoughts on this project. Feel free to drop us a note!
+
+* [Twitter](https://twitter.com/charmcli)
+* [The Fediverse](https://mastodon.social/@charmcli)
+* [Discord](https://charm.sh/chat)
+
+## Acknowledgments
+
+Bubble Tea is based on the paradigms of [The Elm Architecture][elm] by Evan
+Czaplicki et alia and the excellent [go-tea][gotea] by TJ Holowaychuk. It’s
+inspired by the many great [_Zeichenorientierte Benutzerschnittstellen_][zb]
+of days past.
+
+[elm]: https://guide.elm-lang.org/architecture/
+[gotea]: https://github.com/tj/go-tea
+[zb]: https://de.wikipedia.org/wiki/Zeichenorientierte_Benutzerschnittstelle
+
+## License
+
+[MIT](https://github.com/charmbracelet/bubbletea/raw/master/LICENSE)
+
+***
+
+Part of [Charm](https://charm.sh).
+
+
+
+Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة
diff --git a/vendor/github.com/charmbracelet/bubbletea/commands.go b/vendor/github.com/charmbracelet/bubbletea/commands.go
new file mode 100644
index 0000000..7b139b8
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/commands.go
@@ -0,0 +1,189 @@
+package tea
+
+import (
+ "time"
+)
+
+// Batch performs a bunch of commands concurrently with no ordering guarantees
+// about the results. Use a Batch to return several commands.
+//
+// Example:
+//
+// func (m model) Init() Cmd {
+// return tea.Batch(someCommand, someOtherCommand)
+// }
+func Batch(cmds ...Cmd) Cmd {
+ var validCmds []Cmd //nolint:prealloc
+ for _, c := range cmds {
+ if c == nil {
+ continue
+ }
+ validCmds = append(validCmds, c)
+ }
+ if len(validCmds) == 0 {
+ return nil
+ }
+ return func() Msg {
+ return BatchMsg(validCmds)
+ }
+}
+
+// BatchMsg is a message used to perform a bunch of commands concurrently with
+// no ordering guarantees. You can send a BatchMsg with Batch.
+type BatchMsg []Cmd
+
+// Sequence runs the given commands one at a time, in order. Contrast this with
+// Batch, which runs commands concurrently.
+func Sequence(cmds ...Cmd) Cmd {
+ return func() Msg {
+ return sequenceMsg(cmds)
+ }
+}
+
+// sequenceMsg is used internally to run the given commands in order.
+type sequenceMsg []Cmd
+
+// Every is a command that ticks in sync with the system clock. So, if you
+// wanted to tick with the system clock every second, minute or hour you
+// could use this. It's also handy for having different things tick in sync.
+//
+// Because we're ticking with the system clock the tick will likely not run for
+// the entire specified duration. For example, if we're ticking for one minute
+// and the clock is at 12:34:20 then the next tick will happen at 12:35:00, 40
+// seconds later.
+//
+// To produce the command, pass a duration and a function which returns
+// a message containing the time at which the tick occurred.
+//
+// type TickMsg time.Time
+//
+// cmd := Every(time.Second, func(t time.Time) Msg {
+// return TickMsg(t)
+// })
+//
+// Beginners' note: Every sends a single message and won't automatically
+// dispatch messages at an interval. To do that, you'll want to return another
+// Every command after receiving your tick message. For example:
+//
+// type TickMsg time.Time
+//
+// // Send a message every second.
+// func tickEvery() Cmd {
+// return Every(time.Second, func(t time.Time) Msg {
+// return TickMsg(t)
+// })
+// }
+//
+// func (m model) Init() Cmd {
+// // Start ticking.
+// return tickEvery()
+// }
+//
+// func (m model) Update(msg Msg) (Model, Cmd) {
+// switch msg.(type) {
+// case TickMsg:
+// // Return your Every command again to loop.
+// return m, tickEvery()
+// }
+// return m, nil
+// }
+//
+// Every is analogous to Tick in the Elm Architecture.
+func Every(duration time.Duration, fn func(time.Time) Msg) Cmd {
+ return func() Msg {
+ n := time.Now()
+ d := n.Truncate(duration).Add(duration).Sub(n)
+ t := time.NewTimer(d)
+ return fn(<-t.C)
+ }
+}
+
+// Tick produces a command at an interval independent of the system clock at
+// the given duration. That is, the timer begins precisely when invoked,
+// and runs for its entire duration.
+//
+// To produce the command, pass a duration and a function which returns
+// a message containing the time at which the tick occurred.
+//
+// type TickMsg time.Time
+//
+// cmd := Tick(time.Second, func(t time.Time) Msg {
+// return TickMsg(t)
+// })
+//
+// Beginners' note: Tick sends a single message and won't automatically
+// dispatch messages at an interval. To do that, you'll want to return another
+// Tick command after receiving your tick message. For example:
+//
+// type TickMsg time.Time
+//
+// func doTick() Cmd {
+// return Tick(time.Second, func(t time.Time) Msg {
+// return TickMsg(t)
+// })
+// }
+//
+// func (m model) Init() Cmd {
+// // Start ticking.
+// return doTick()
+// }
+//
+// func (m model) Update(msg Msg) (Model, Cmd) {
+// switch msg.(type) {
+// case TickMsg:
+// // Return your Tick command again to loop.
+// return m, doTick()
+// }
+// return m, nil
+// }
+func Tick(d time.Duration, fn func(time.Time) Msg) Cmd {
+ return func() Msg {
+ t := time.NewTimer(d)
+ return fn(<-t.C)
+ }
+}
+
+// Sequentially produces a command that sequentially executes the given
+// commands.
+// The Msg returned is the first non-nil message returned by a Cmd.
+//
+// func saveStateCmd() Msg {
+// if err := save(); err != nil {
+// return errMsg{err}
+// }
+// return nil
+// }
+//
+// cmd := Sequentially(saveStateCmd, Quit)
+//
+// Deprecated: use Sequence instead.
+func Sequentially(cmds ...Cmd) Cmd {
+ return func() Msg {
+ for _, cmd := range cmds {
+ if cmd == nil {
+ continue
+ }
+ if msg := cmd(); msg != nil {
+ return msg
+ }
+ }
+ return nil
+ }
+}
+
+// setWindowTitleMsg is an internal message used to set the window title.
+type setWindowTitleMsg string
+
+// SetWindowTitle produces a command that sets the terminal title.
+//
+// For example:
+//
+// func (m model) Init() Cmd {
+// // Set title.
+// return tea.SetWindowTitle("My App")
+// }
+func SetWindowTitle(title string) Cmd {
+ return func() Msg {
+ return setWindowTitleMsg(title)
+ }
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/exec.go b/vendor/github.com/charmbracelet/bubbletea/exec.go
new file mode 100644
index 0000000..fb6d91e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/exec.go
@@ -0,0 +1,129 @@
+package tea
+
+import (
+ "io"
+ "os"
+ "os/exec"
+)
+
+// execMsg is used internally to run an ExecCommand sent with Exec.
+type execMsg struct {
+ cmd ExecCommand
+ fn ExecCallback
+}
+
+// Exec is used to perform arbitrary I/O in a blocking fashion, effectively
+// pausing the Program while execution is running and resuming it when
+// execution has completed.
+//
+// Most of the time you'll want to use ExecProcess, which runs an exec.Cmd.
+//
+// For non-interactive i/o you should use a Cmd (that is, a tea.Cmd).
+func Exec(c ExecCommand, fn ExecCallback) Cmd {
+ return func() Msg {
+ return execMsg{cmd: c, fn: fn}
+ }
+}
+
+// ExecProcess runs the given *exec.Cmd in a blocking fashion, effectively
+// pausing the Program while the command is running. After the *exec.Cmd exists
+// the Program resumes. It's useful for spawning other interactive applications
+// such as editors and shells from within a Program.
+//
+// To produce the command, pass an *exec.Cmd and a function which returns
+// a message containing the error which may have occurred when running the
+// ExecCommand.
+//
+// type VimFinishedMsg struct { err error }
+//
+// c := exec.Command("vim", "file.txt")
+//
+// cmd := ExecProcess(c, func(err error) Msg {
+// return VimFinishedMsg{err: err}
+// })
+//
+// Or, if you don't care about errors, you could simply:
+//
+// cmd := ExecProcess(exec.Command("vim", "file.txt"), nil)
+//
+// For non-interactive i/o you should use a Cmd (that is, a tea.Cmd).
+func ExecProcess(c *exec.Cmd, fn ExecCallback) Cmd {
+ return Exec(wrapExecCommand(c), fn)
+}
+
+// ExecCallback is used when executing an *exec.Command to return a message
+// with an error, which may or may not be nil.
+type ExecCallback func(error) Msg
+
+// ExecCommand can be implemented to execute things in a blocking fashion in
+// the current terminal.
+type ExecCommand interface {
+ Run() error
+ SetStdin(io.Reader)
+ SetStdout(io.Writer)
+ SetStderr(io.Writer)
+}
+
+// wrapExecCommand wraps an exec.Cmd so that it satisfies the ExecCommand
+// interface so it can be used with Exec.
+func wrapExecCommand(c *exec.Cmd) ExecCommand {
+ return &osExecCommand{Cmd: c}
+}
+
+// osExecCommand is a layer over an exec.Cmd that satisfies the ExecCommand
+// interface.
+type osExecCommand struct{ *exec.Cmd }
+
+// SetStdin sets stdin on underlying exec.Cmd to the given io.Reader.
+func (c *osExecCommand) SetStdin(r io.Reader) {
+ // If unset, have the command use the same input as the terminal.
+ if c.Stdin == nil {
+ c.Stdin = r
+ }
+}
+
+// SetStdout sets stdout on underlying exec.Cmd to the given io.Writer.
+func (c *osExecCommand) SetStdout(w io.Writer) {
+ // If unset, have the command use the same output as the terminal.
+ if c.Stdout == nil {
+ c.Stdout = w
+ }
+}
+
+// SetStderr sets stderr on the underlying exec.Cmd to the given io.Writer.
+func (c *osExecCommand) SetStderr(w io.Writer) {
+ // If unset, use stderr for the command's stderr
+ if c.Stderr == nil {
+ c.Stderr = w
+ }
+}
+
+// exec runs an ExecCommand and delivers the results to the program as a Msg.
+func (p *Program) exec(c ExecCommand, fn ExecCallback) {
+ if err := p.ReleaseTerminal(); err != nil {
+ // If we can't release input, abort.
+ if fn != nil {
+ go p.Send(fn(err))
+ }
+ return
+ }
+
+ c.SetStdin(p.input)
+ c.SetStdout(p.output.TTY())
+ c.SetStderr(os.Stderr)
+
+ // Execute system command.
+ if err := c.Run(); err != nil {
+ _ = p.RestoreTerminal() // also try to restore the terminal.
+ if fn != nil {
+ go p.Send(fn(err))
+ }
+ return
+ }
+
+ // Have the program re-capture input.
+ err := p.RestoreTerminal()
+ if fn != nil {
+ go p.Send(fn(err))
+ }
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/key.go b/vendor/github.com/charmbracelet/bubbletea/key.go
new file mode 100644
index 0000000..f851490
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/key.go
@@ -0,0 +1,686 @@
+package tea
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "regexp"
+ "unicode/utf8"
+)
+
+// KeyMsg contains information about a keypress. KeyMsgs are always sent to
+// the program's update function. There are a couple general patterns you could
+// use to check for keypresses:
+//
+// // Switch on the string representation of the key (shorter)
+// switch msg := msg.(type) {
+// case KeyMsg:
+// switch msg.String() {
+// case "enter":
+// fmt.Println("you pressed enter!")
+// case "a":
+// fmt.Println("you pressed a!")
+// }
+// }
+//
+// // Switch on the key type (more foolproof)
+// switch msg := msg.(type) {
+// case KeyMsg:
+// switch msg.Type {
+// case KeyEnter:
+// fmt.Println("you pressed enter!")
+// case KeyRunes:
+// switch string(msg.Runes) {
+// case "a":
+// fmt.Println("you pressed a!")
+// }
+// }
+// }
+//
+// Note that Key.Runes will always contain at least one character, so you can
+// always safely call Key.Runes[0]. In most cases Key.Runes will only contain
+// one character, though certain input method editors (most notably Chinese
+// IMEs) can input multiple runes at once.
+type KeyMsg Key
+
+// String returns a string representation for a key message. It's safe (and
+// encouraged) for use in key comparison.
+func (k KeyMsg) String() (str string) {
+ return Key(k).String()
+}
+
+// Key contains information about a keypress.
+type Key struct {
+ Type KeyType
+ Runes []rune
+ Alt bool
+}
+
+// String returns a friendly string representation for a key. It's safe (and
+// encouraged) for use in key comparison.
+//
+// k := Key{Type: KeyEnter}
+// fmt.Println(k)
+// // Output: enter
+func (k Key) String() (str string) {
+ if k.Alt {
+ str += "alt+"
+ }
+ if k.Type == KeyRunes {
+ str += string(k.Runes)
+ return str
+ } else if s, ok := keyNames[k.Type]; ok {
+ str += s
+ return str
+ }
+ return ""
+}
+
+// KeyType indicates the key pressed, such as KeyEnter or KeyBreak or KeyCtrlC.
+// All other keys will be type KeyRunes. To get the rune value, check the Rune
+// method on a Key struct, or use the Key.String() method:
+//
+// k := Key{Type: KeyRunes, Runes: []rune{'a'}, Alt: true}
+// if k.Type == KeyRunes {
+//
+// fmt.Println(k.Runes)
+// // Output: a
+//
+// fmt.Println(k.String())
+// // Output: alt+a
+//
+// }
+type KeyType int
+
+func (k KeyType) String() (str string) {
+ if s, ok := keyNames[k]; ok {
+ return s
+ }
+ return ""
+}
+
+// Control keys. We could do this with an iota, but the values are very
+// specific, so we set the values explicitly to avoid any confusion.
+//
+// See also:
+// https://en.wikipedia.org/wiki/C0_and_C1_control_codes
+const (
+ keyNUL KeyType = 0 // null, \0
+ keySOH KeyType = 1 // start of heading
+ keySTX KeyType = 2 // start of text
+ keyETX KeyType = 3 // break, ctrl+c
+ keyEOT KeyType = 4 // end of transmission
+ keyENQ KeyType = 5 // enquiry
+ keyACK KeyType = 6 // acknowledge
+ keyBEL KeyType = 7 // bell, \a
+ keyBS KeyType = 8 // backspace
+ keyHT KeyType = 9 // horizontal tabulation, \t
+ keyLF KeyType = 10 // line feed, \n
+ keyVT KeyType = 11 // vertical tabulation \v
+ keyFF KeyType = 12 // form feed \f
+ keyCR KeyType = 13 // carriage return, \r
+ keySO KeyType = 14 // shift out
+ keySI KeyType = 15 // shift in
+ keyDLE KeyType = 16 // data link escape
+ keyDC1 KeyType = 17 // device control one
+ keyDC2 KeyType = 18 // device control two
+ keyDC3 KeyType = 19 // device control three
+ keyDC4 KeyType = 20 // device control four
+ keyNAK KeyType = 21 // negative acknowledge
+ keySYN KeyType = 22 // synchronous idle
+ keyETB KeyType = 23 // end of transmission block
+ keyCAN KeyType = 24 // cancel
+ keyEM KeyType = 25 // end of medium
+ keySUB KeyType = 26 // substitution
+ keyESC KeyType = 27 // escape, \e
+ keyFS KeyType = 28 // file separator
+ keyGS KeyType = 29 // group separator
+ keyRS KeyType = 30 // record separator
+ keyUS KeyType = 31 // unit separator
+ keyDEL KeyType = 127 // delete. on most systems this is mapped to backspace, I hear
+)
+
+// Control key aliases.
+const (
+ KeyNull KeyType = keyNUL
+ KeyBreak KeyType = keyETX
+ KeyEnter KeyType = keyCR
+ KeyBackspace KeyType = keyDEL
+ KeyTab KeyType = keyHT
+ KeyEsc KeyType = keyESC
+ KeyEscape KeyType = keyESC
+
+ KeyCtrlAt KeyType = keyNUL // ctrl+@
+ KeyCtrlA KeyType = keySOH
+ KeyCtrlB KeyType = keySTX
+ KeyCtrlC KeyType = keyETX
+ KeyCtrlD KeyType = keyEOT
+ KeyCtrlE KeyType = keyENQ
+ KeyCtrlF KeyType = keyACK
+ KeyCtrlG KeyType = keyBEL
+ KeyCtrlH KeyType = keyBS
+ KeyCtrlI KeyType = keyHT
+ KeyCtrlJ KeyType = keyLF
+ KeyCtrlK KeyType = keyVT
+ KeyCtrlL KeyType = keyFF
+ KeyCtrlM KeyType = keyCR
+ KeyCtrlN KeyType = keySO
+ KeyCtrlO KeyType = keySI
+ KeyCtrlP KeyType = keyDLE
+ KeyCtrlQ KeyType = keyDC1
+ KeyCtrlR KeyType = keyDC2
+ KeyCtrlS KeyType = keyDC3
+ KeyCtrlT KeyType = keyDC4
+ KeyCtrlU KeyType = keyNAK
+ KeyCtrlV KeyType = keySYN
+ KeyCtrlW KeyType = keyETB
+ KeyCtrlX KeyType = keyCAN
+ KeyCtrlY KeyType = keyEM
+ KeyCtrlZ KeyType = keySUB
+ KeyCtrlOpenBracket KeyType = keyESC // ctrl+[
+ KeyCtrlBackslash KeyType = keyFS // ctrl+\
+ KeyCtrlCloseBracket KeyType = keyGS // ctrl+]
+ KeyCtrlCaret KeyType = keyRS // ctrl+^
+ KeyCtrlUnderscore KeyType = keyUS // ctrl+_
+ KeyCtrlQuestionMark KeyType = keyDEL // ctrl+?
+)
+
+// Other keys.
+const (
+ KeyRunes KeyType = -(iota + 1)
+ KeyUp
+ KeyDown
+ KeyRight
+ KeyLeft
+ KeyShiftTab
+ KeyHome
+ KeyEnd
+ KeyPgUp
+ KeyPgDown
+ KeyCtrlPgUp
+ KeyCtrlPgDown
+ KeyDelete
+ KeyInsert
+ KeySpace
+ KeyCtrlUp
+ KeyCtrlDown
+ KeyCtrlRight
+ KeyCtrlLeft
+ KeyCtrlHome
+ KeyCtrlEnd
+ KeyShiftUp
+ KeyShiftDown
+ KeyShiftRight
+ KeyShiftLeft
+ KeyShiftHome
+ KeyShiftEnd
+ KeyCtrlShiftUp
+ KeyCtrlShiftDown
+ KeyCtrlShiftLeft
+ KeyCtrlShiftRight
+ KeyCtrlShiftHome
+ KeyCtrlShiftEnd
+ KeyF1
+ KeyF2
+ KeyF3
+ KeyF4
+ KeyF5
+ KeyF6
+ KeyF7
+ KeyF8
+ KeyF9
+ KeyF10
+ KeyF11
+ KeyF12
+ KeyF13
+ KeyF14
+ KeyF15
+ KeyF16
+ KeyF17
+ KeyF18
+ KeyF19
+ KeyF20
+)
+
+// Mappings for control keys and other special keys to friendly consts.
+var keyNames = map[KeyType]string{
+ // Control keys.
+ keyNUL: "ctrl+@", // also ctrl+` (that's ctrl+backtick)
+ keySOH: "ctrl+a",
+ keySTX: "ctrl+b",
+ keyETX: "ctrl+c",
+ keyEOT: "ctrl+d",
+ keyENQ: "ctrl+e",
+ keyACK: "ctrl+f",
+ keyBEL: "ctrl+g",
+ keyBS: "ctrl+h",
+ keyHT: "tab", // also ctrl+i
+ keyLF: "ctrl+j",
+ keyVT: "ctrl+k",
+ keyFF: "ctrl+l",
+ keyCR: "enter",
+ keySO: "ctrl+n",
+ keySI: "ctrl+o",
+ keyDLE: "ctrl+p",
+ keyDC1: "ctrl+q",
+ keyDC2: "ctrl+r",
+ keyDC3: "ctrl+s",
+ keyDC4: "ctrl+t",
+ keyNAK: "ctrl+u",
+ keySYN: "ctrl+v",
+ keyETB: "ctrl+w",
+ keyCAN: "ctrl+x",
+ keyEM: "ctrl+y",
+ keySUB: "ctrl+z",
+ keyESC: "esc",
+ keyFS: "ctrl+\\",
+ keyGS: "ctrl+]",
+ keyRS: "ctrl+^",
+ keyUS: "ctrl+_",
+ keyDEL: "backspace",
+
+ // Other keys.
+ KeyRunes: "runes",
+ KeyUp: "up",
+ KeyDown: "down",
+ KeyRight: "right",
+ KeySpace: " ", // for backwards compatibility
+ KeyLeft: "left",
+ KeyShiftTab: "shift+tab",
+ KeyHome: "home",
+ KeyEnd: "end",
+ KeyCtrlHome: "ctrl+home",
+ KeyCtrlEnd: "ctrl+end",
+ KeyShiftHome: "shift+home",
+ KeyShiftEnd: "shift+end",
+ KeyCtrlShiftHome: "ctrl+shift+home",
+ KeyCtrlShiftEnd: "ctrl+shift+end",
+ KeyPgUp: "pgup",
+ KeyPgDown: "pgdown",
+ KeyCtrlPgUp: "ctrl+pgup",
+ KeyCtrlPgDown: "ctrl+pgdown",
+ KeyDelete: "delete",
+ KeyInsert: "insert",
+ KeyCtrlUp: "ctrl+up",
+ KeyCtrlDown: "ctrl+down",
+ KeyCtrlRight: "ctrl+right",
+ KeyCtrlLeft: "ctrl+left",
+ KeyShiftUp: "shift+up",
+ KeyShiftDown: "shift+down",
+ KeyShiftRight: "shift+right",
+ KeyShiftLeft: "shift+left",
+ KeyCtrlShiftUp: "ctrl+shift+up",
+ KeyCtrlShiftDown: "ctrl+shift+down",
+ KeyCtrlShiftLeft: "ctrl+shift+left",
+ KeyCtrlShiftRight: "ctrl+shift+right",
+ KeyF1: "f1",
+ KeyF2: "f2",
+ KeyF3: "f3",
+ KeyF4: "f4",
+ KeyF5: "f5",
+ KeyF6: "f6",
+ KeyF7: "f7",
+ KeyF8: "f8",
+ KeyF9: "f9",
+ KeyF10: "f10",
+ KeyF11: "f11",
+ KeyF12: "f12",
+ KeyF13: "f13",
+ KeyF14: "f14",
+ KeyF15: "f15",
+ KeyF16: "f16",
+ KeyF17: "f17",
+ KeyF18: "f18",
+ KeyF19: "f19",
+ KeyF20: "f20",
+}
+
+// Sequence mappings.
+var sequences = map[string]Key{
+ // Arrow keys
+ "\x1b[A": {Type: KeyUp},
+ "\x1b[B": {Type: KeyDown},
+ "\x1b[C": {Type: KeyRight},
+ "\x1b[D": {Type: KeyLeft},
+ "\x1b[1;2A": {Type: KeyShiftUp},
+ "\x1b[1;2B": {Type: KeyShiftDown},
+ "\x1b[1;2C": {Type: KeyShiftRight},
+ "\x1b[1;2D": {Type: KeyShiftLeft},
+ "\x1b[OA": {Type: KeyShiftUp}, // DECCKM
+ "\x1b[OB": {Type: KeyShiftDown}, // DECCKM
+ "\x1b[OC": {Type: KeyShiftRight}, // DECCKM
+ "\x1b[OD": {Type: KeyShiftLeft}, // DECCKM
+ "\x1b[a": {Type: KeyShiftUp}, // urxvt
+ "\x1b[b": {Type: KeyShiftDown}, // urxvt
+ "\x1b[c": {Type: KeyShiftRight}, // urxvt
+ "\x1b[d": {Type: KeyShiftLeft}, // urxvt
+ "\x1b[1;3A": {Type: KeyUp, Alt: true},
+ "\x1b[1;3B": {Type: KeyDown, Alt: true},
+ "\x1b[1;3C": {Type: KeyRight, Alt: true},
+ "\x1b[1;3D": {Type: KeyLeft, Alt: true},
+
+ "\x1b[1;4A": {Type: KeyShiftUp, Alt: true},
+ "\x1b[1;4B": {Type: KeyShiftDown, Alt: true},
+ "\x1b[1;4C": {Type: KeyShiftRight, Alt: true},
+ "\x1b[1;4D": {Type: KeyShiftLeft, Alt: true},
+
+ "\x1b[1;5A": {Type: KeyCtrlUp},
+ "\x1b[1;5B": {Type: KeyCtrlDown},
+ "\x1b[1;5C": {Type: KeyCtrlRight},
+ "\x1b[1;5D": {Type: KeyCtrlLeft},
+ "\x1b[Oa": {Type: KeyCtrlUp, Alt: true}, // urxvt
+ "\x1b[Ob": {Type: KeyCtrlDown, Alt: true}, // urxvt
+ "\x1b[Oc": {Type: KeyCtrlRight, Alt: true}, // urxvt
+ "\x1b[Od": {Type: KeyCtrlLeft, Alt: true}, // urxvt
+ "\x1b[1;6A": {Type: KeyCtrlShiftUp},
+ "\x1b[1;6B": {Type: KeyCtrlShiftDown},
+ "\x1b[1;6C": {Type: KeyCtrlShiftRight},
+ "\x1b[1;6D": {Type: KeyCtrlShiftLeft},
+ "\x1b[1;7A": {Type: KeyCtrlUp, Alt: true},
+ "\x1b[1;7B": {Type: KeyCtrlDown, Alt: true},
+ "\x1b[1;7C": {Type: KeyCtrlRight, Alt: true},
+ "\x1b[1;7D": {Type: KeyCtrlLeft, Alt: true},
+ "\x1b[1;8A": {Type: KeyCtrlShiftUp, Alt: true},
+ "\x1b[1;8B": {Type: KeyCtrlShiftDown, Alt: true},
+ "\x1b[1;8C": {Type: KeyCtrlShiftRight, Alt: true},
+ "\x1b[1;8D": {Type: KeyCtrlShiftLeft, Alt: true},
+
+ // Miscellaneous keys
+ "\x1b[Z": {Type: KeyShiftTab},
+
+ "\x1b[2~": {Type: KeyInsert},
+ "\x1b[3;2~": {Type: KeyInsert, Alt: true},
+
+ "\x1b[3~": {Type: KeyDelete},
+ "\x1b[3;3~": {Type: KeyDelete, Alt: true},
+
+ "\x1b[5~": {Type: KeyPgUp},
+ "\x1b[5;3~": {Type: KeyPgUp, Alt: true},
+ "\x1b[5;5~": {Type: KeyCtrlPgUp},
+ "\x1b[5^": {Type: KeyCtrlPgUp}, // urxvt
+ "\x1b[5;7~": {Type: KeyCtrlPgUp, Alt: true},
+
+ "\x1b[6~": {Type: KeyPgDown},
+ "\x1b[6;3~": {Type: KeyPgDown, Alt: true},
+ "\x1b[6;5~": {Type: KeyCtrlPgDown},
+ "\x1b[6^": {Type: KeyCtrlPgDown}, // urxvt
+ "\x1b[6;7~": {Type: KeyCtrlPgDown, Alt: true},
+
+ "\x1b[1~": {Type: KeyHome},
+ "\x1b[H": {Type: KeyHome}, // xterm, lxterm
+ "\x1b[1;3H": {Type: KeyHome, Alt: true}, // xterm, lxterm
+ "\x1b[1;5H": {Type: KeyCtrlHome}, // xterm, lxterm
+ "\x1b[1;7H": {Type: KeyCtrlHome, Alt: true}, // xterm, lxterm
+ "\x1b[1;2H": {Type: KeyShiftHome}, // xterm, lxterm
+ "\x1b[1;4H": {Type: KeyShiftHome, Alt: true}, // xterm, lxterm
+ "\x1b[1;6H": {Type: KeyCtrlShiftHome}, // xterm, lxterm
+ "\x1b[1;8H": {Type: KeyCtrlShiftHome, Alt: true}, // xterm, lxterm
+
+ "\x1b[4~": {Type: KeyEnd},
+ "\x1b[F": {Type: KeyEnd}, // xterm, lxterm
+ "\x1b[1;3F": {Type: KeyEnd, Alt: true}, // xterm, lxterm
+ "\x1b[1;5F": {Type: KeyCtrlEnd}, // xterm, lxterm
+ "\x1b[1;7F": {Type: KeyCtrlEnd, Alt: true}, // xterm, lxterm
+ "\x1b[1;2F": {Type: KeyShiftEnd}, // xterm, lxterm
+ "\x1b[1;4F": {Type: KeyShiftEnd, Alt: true}, // xterm, lxterm
+ "\x1b[1;6F": {Type: KeyCtrlShiftEnd}, // xterm, lxterm
+ "\x1b[1;8F": {Type: KeyCtrlShiftEnd, Alt: true}, // xterm, lxterm
+
+ "\x1b[7~": {Type: KeyHome}, // urxvt
+ "\x1b[7^": {Type: KeyCtrlHome}, // urxvt
+ "\x1b[7$": {Type: KeyShiftHome}, // urxvt
+ "\x1b[7@": {Type: KeyCtrlShiftHome}, // urxvt
+
+ "\x1b[8~": {Type: KeyEnd}, // urxvt
+ "\x1b[8^": {Type: KeyCtrlEnd}, // urxvt
+ "\x1b[8$": {Type: KeyShiftEnd}, // urxvt
+ "\x1b[8@": {Type: KeyCtrlShiftEnd}, // urxvt
+
+ // Function keys, Linux console
+ "\x1b[[A": {Type: KeyF1}, // linux console
+ "\x1b[[B": {Type: KeyF2}, // linux console
+ "\x1b[[C": {Type: KeyF3}, // linux console
+ "\x1b[[D": {Type: KeyF4}, // linux console
+ "\x1b[[E": {Type: KeyF5}, // linux console
+
+ // Function keys, X11
+ "\x1bOP": {Type: KeyF1}, // vt100, xterm
+ "\x1bOQ": {Type: KeyF2}, // vt100, xterm
+ "\x1bOR": {Type: KeyF3}, // vt100, xterm
+ "\x1bOS": {Type: KeyF4}, // vt100, xterm
+
+ "\x1b[1;3P": {Type: KeyF1, Alt: true}, // vt100, xterm
+ "\x1b[1;3Q": {Type: KeyF2, Alt: true}, // vt100, xterm
+ "\x1b[1;3R": {Type: KeyF3, Alt: true}, // vt100, xterm
+ "\x1b[1;3S": {Type: KeyF4, Alt: true}, // vt100, xterm
+
+ "\x1b[11~": {Type: KeyF1}, // urxvt
+ "\x1b[12~": {Type: KeyF2}, // urxvt
+ "\x1b[13~": {Type: KeyF3}, // urxvt
+ "\x1b[14~": {Type: KeyF4}, // urxvt
+
+ "\x1b[15~": {Type: KeyF5}, // vt100, xterm, also urxvt
+
+ "\x1b[15;3~": {Type: KeyF5, Alt: true}, // vt100, xterm, also urxvt
+
+ "\x1b[17~": {Type: KeyF6}, // vt100, xterm, also urxvt
+ "\x1b[18~": {Type: KeyF7}, // vt100, xterm, also urxvt
+ "\x1b[19~": {Type: KeyF8}, // vt100, xterm, also urxvt
+ "\x1b[20~": {Type: KeyF9}, // vt100, xterm, also urxvt
+ "\x1b[21~": {Type: KeyF10}, // vt100, xterm, also urxvt
+
+ "\x1b[17;3~": {Type: KeyF6, Alt: true}, // vt100, xterm
+ "\x1b[18;3~": {Type: KeyF7, Alt: true}, // vt100, xterm
+ "\x1b[19;3~": {Type: KeyF8, Alt: true}, // vt100, xterm
+ "\x1b[20;3~": {Type: KeyF9, Alt: true}, // vt100, xterm
+ "\x1b[21;3~": {Type: KeyF10, Alt: true}, // vt100, xterm
+
+ "\x1b[23~": {Type: KeyF11}, // vt100, xterm, also urxvt
+ "\x1b[24~": {Type: KeyF12}, // vt100, xterm, also urxvt
+
+ "\x1b[23;3~": {Type: KeyF11, Alt: true}, // vt100, xterm
+ "\x1b[24;3~": {Type: KeyF12, Alt: true}, // vt100, xterm
+
+ "\x1b[1;2P": {Type: KeyF13},
+ "\x1b[1;2Q": {Type: KeyF14},
+
+ "\x1b[25~": {Type: KeyF13}, // vt100, xterm, also urxvt
+ "\x1b[26~": {Type: KeyF14}, // vt100, xterm, also urxvt
+
+ "\x1b[25;3~": {Type: KeyF13, Alt: true}, // vt100, xterm
+ "\x1b[26;3~": {Type: KeyF14, Alt: true}, // vt100, xterm
+
+ "\x1b[1;2R": {Type: KeyF15},
+ "\x1b[1;2S": {Type: KeyF16},
+
+ "\x1b[28~": {Type: KeyF15}, // vt100, xterm, also urxvt
+ "\x1b[29~": {Type: KeyF16}, // vt100, xterm, also urxvt
+
+ "\x1b[28;3~": {Type: KeyF15, Alt: true}, // vt100, xterm
+ "\x1b[29;3~": {Type: KeyF16, Alt: true}, // vt100, xterm
+
+ "\x1b[15;2~": {Type: KeyF17},
+ "\x1b[17;2~": {Type: KeyF18},
+ "\x1b[18;2~": {Type: KeyF19},
+ "\x1b[19;2~": {Type: KeyF20},
+
+ "\x1b[31~": {Type: KeyF17},
+ "\x1b[32~": {Type: KeyF18},
+ "\x1b[33~": {Type: KeyF19},
+ "\x1b[34~": {Type: KeyF20},
+
+ // Powershell sequences.
+ "\x1bOA": {Type: KeyUp, Alt: false},
+ "\x1bOB": {Type: KeyDown, Alt: false},
+ "\x1bOC": {Type: KeyRight, Alt: false},
+ "\x1bOD": {Type: KeyLeft, Alt: false},
+}
+
+// unknownInputByteMsg is reported by the input reader when an invalid
+// utf-8 byte is detected on the input. Currently, it is not handled
+// further by bubbletea. However, having this event makes it possible
+// to troubleshoot invalid inputs.
+type unknownInputByteMsg byte
+
+func (u unknownInputByteMsg) String() string {
+ return fmt.Sprintf("?%#02x?", int(u))
+}
+
+// unknownCSISequenceMsg is reported by the input reader when an
+// unrecognized CSI sequence is detected on the input. Currently, it
+// is not handled further by bubbletea. However, having this event
+// makes it possible to troubleshoot invalid inputs.
+type unknownCSISequenceMsg []byte
+
+func (u unknownCSISequenceMsg) String() string {
+ return fmt.Sprintf("?CSI%+v?", []byte(u)[2:])
+}
+
+var spaceRunes = []rune{' '}
+
+// readInputs reads keypress and mouse inputs from a TTY and produces messages
+// containing information about the key or mouse events accordingly.
+func readInputs(ctx context.Context, msgs chan<- Msg, input io.Reader) error {
+ var buf [256]byte
+
+ var leftOverFromPrevIteration []byte
+loop:
+ for {
+ // Read and block.
+ numBytes, err := input.Read(buf[:])
+ if err != nil {
+ return fmt.Errorf("error reading input: %w", err)
+ }
+ b := buf[:numBytes]
+ if leftOverFromPrevIteration != nil {
+ b = append(leftOverFromPrevIteration, b...)
+ }
+
+ // If we had a short read (numBytes < len(buf)), we're sure that
+ // the end of this read is an event boundary, so there is no doubt
+ // if we are encountering the end of the buffer while parsing a message.
+ // However, if we've succeeded in filling up the buffer, there may
+ // be more data in the OS buffer ready to be read in, to complete
+ // the last message in the input. In that case, we will retry with
+ // the left over data in the next iteration.
+ canHaveMoreData := numBytes == len(buf)
+
+ var i, w int
+ for i, w = 0, 07; i < len(b); i += w {
+ var msg Msg
+ w, msg = detectOneMsg(b[i:], canHaveMoreData)
+ if w == 0 {
+ // Expecting more bytes beyond the current buffer. Try waiting
+ // for more input.
+ leftOverFromPrevIteration = make([]byte, 0, len(b[i:])+len(buf))
+ leftOverFromPrevIteration = append(leftOverFromPrevIteration, b[i:]...)
+ continue loop
+ }
+
+ select {
+ case msgs <- msg:
+ case <-ctx.Done():
+ err := ctx.Err()
+ if err != nil {
+ err = fmt.Errorf("found context error while reading input: %w", err)
+ }
+ return err
+ }
+ }
+ leftOverFromPrevIteration = nil
+ }
+}
+
+var (
+ unknownCSIRe = regexp.MustCompile(`^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]`)
+ mouseSGRRegex = regexp.MustCompile(`(\d+);(\d+);(\d+)([Mm])`)
+)
+
+func detectOneMsg(b []byte, canHaveMoreData bool) (w int, msg Msg) {
+ // Detect mouse events.
+ // X10 mouse events have a length of 6 bytes
+ const mouseEventX10Len = 6
+ if len(b) >= mouseEventX10Len && b[0] == '\x1b' && b[1] == '[' {
+ switch b[2] {
+ case 'M':
+ return mouseEventX10Len, MouseMsg(parseX10MouseEvent(b))
+ case '<':
+ if matchIndices := mouseSGRRegex.FindSubmatchIndex(b[3:]); matchIndices != nil {
+ // SGR mouse events length is the length of the match plus the length of the escape sequence
+ mouseEventSGRLen := matchIndices[1] + 3
+ return mouseEventSGRLen, MouseMsg(parseSGRMouseEvent(b))
+ }
+ }
+ }
+
+ // Detect escape sequence and control characters other than NUL,
+ // possibly with an escape character in front to mark the Alt
+ // modifier.
+ var foundSeq bool
+ foundSeq, w, msg = detectSequence(b)
+ if foundSeq {
+ return
+ }
+
+ // No non-NUL control character or escape sequence.
+ // If we are seeing at least an escape character, remember it for later below.
+ alt := false
+ i := 0
+ if b[0] == '\x1b' {
+ alt = true
+ i++
+ }
+
+ // Are we seeing a standalone NUL? This is not handled by detectSequence().
+ if i < len(b) && b[i] == 0 {
+ return i + 1, KeyMsg{Type: keyNUL, Alt: alt}
+ }
+
+ // Find the longest sequence of runes that are not control
+ // characters from this point.
+ var runes []rune
+ for rw := 0; i < len(b); i += rw {
+ var r rune
+ r, rw = utf8.DecodeRune(b[i:])
+ if r == utf8.RuneError || r <= rune(keyUS) || r == rune(keyDEL) || r == ' ' {
+ // Rune errors are handled below; control characters and spaces will
+ // be handled by detectSequence in the next call to detectOneMsg.
+ break
+ }
+ runes = append(runes, r)
+ if alt {
+ // We only support a single rune after an escape alt modifier.
+ i += rw
+ break
+ }
+ }
+ if i >= len(b) && canHaveMoreData {
+ // We have encountered the end of the input buffer. Alas, we can't
+ // be sure whether the data in the remainder of the buffer is
+ // complete (maybe there was a short read). Instead of sending anything
+ // dumb to the message channel, do a short read. The outer loop will
+ // handle this case by extending the buffer as necessary.
+ return 0, nil
+ }
+
+ // If we found at least one rune, we report the bunch of them as
+ // a single KeyRunes or KeySpace event.
+ if len(runes) > 0 {
+ k := Key{Type: KeyRunes, Runes: runes, Alt: alt}
+ if len(runes) == 1 && runes[0] == ' ' {
+ k.Type = KeySpace
+ }
+ return i, KeyMsg(k)
+ }
+
+ // We didn't find an escape sequence, nor a valid rune. Was this a
+ // lone escape character at the end of the input?
+ if alt && len(b) == 1 {
+ return 1, KeyMsg(Key{Type: KeyEscape})
+ }
+
+ // The character at the current position is neither an escape
+ // sequence, a valid rune start or a sole escape character. Report
+ // it as an invalid byte.
+ return 1, unknownInputByteMsg(b[0])
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/key_sequences.go b/vendor/github.com/charmbracelet/bubbletea/key_sequences.go
new file mode 100644
index 0000000..cc200f8
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/key_sequences.go
@@ -0,0 +1,71 @@
+package tea
+
+import "sort"
+
+// extSequences is used by the map-based algorithm below. It contains
+// the sequences plus their alternatives with an escape character
+// prefixed, plus the control chars, plus the space.
+// It does not contain the NUL character, which is handled specially
+// by detectOneMsg.
+var extSequences = func() map[string]Key {
+ s := map[string]Key{}
+ for seq, key := range sequences {
+ key := key
+ s[seq] = key
+ if !key.Alt {
+ key.Alt = true
+ s["\x1b"+seq] = key
+ }
+ }
+ for i := keyNUL + 1; i <= keyDEL; i++ {
+ if i == keyESC {
+ continue
+ }
+ s[string([]byte{byte(i)})] = Key{Type: i}
+ s[string([]byte{'\x1b', byte(i)})] = Key{Type: i, Alt: true}
+ if i == keyUS {
+ i = keyDEL - 1
+ }
+ }
+ s[" "] = Key{Type: KeySpace, Runes: spaceRunes}
+ s["\x1b "] = Key{Type: KeySpace, Alt: true, Runes: spaceRunes}
+ s["\x1b\x1b"] = Key{Type: KeyEscape, Alt: true}
+ return s
+}()
+
+// seqLengths is the sizes of valid sequences, starting with the
+// largest size.
+var seqLengths = func() []int {
+ sizes := map[int]struct{}{}
+ for seq := range extSequences {
+ sizes[len(seq)] = struct{}{}
+ }
+ lsizes := make([]int, 0, len(sizes))
+ for sz := range sizes {
+ lsizes = append(lsizes, sz)
+ }
+ sort.Slice(lsizes, func(i, j int) bool { return lsizes[i] > lsizes[j] })
+ return lsizes
+}()
+
+// detectSequence uses a longest prefix match over the input
+// sequence and a hash map.
+func detectSequence(input []byte) (hasSeq bool, width int, msg Msg) {
+ seqs := extSequences
+ for _, sz := range seqLengths {
+ if sz > len(input) {
+ continue
+ }
+ prefix := input[:sz]
+ key, ok := seqs[string(prefix)]
+ if ok {
+ return true, sz, KeyMsg(key)
+ }
+ }
+ // Is this an unknown CSI sequence?
+ if loc := unknownCSIRe.FindIndex(input); loc != nil {
+ return true, loc[1], unknownCSISequenceMsg(input[:loc[1]])
+ }
+
+ return false, 0, nil
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/logging.go b/vendor/github.com/charmbracelet/bubbletea/logging.go
new file mode 100644
index 0000000..a531181
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/logging.go
@@ -0,0 +1,53 @@
+package tea
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "unicode"
+)
+
+// LogToFile sets up default logging to log to a file. This is helpful as we
+// can't print to the terminal since our TUI is occupying it. If the file
+// doesn't exist it will be created.
+//
+// Don't forget to close the file when you're done with it.
+//
+// f, err := LogToFile("debug.log", "debug")
+// if err != nil {
+// fmt.Println("fatal:", err)
+// os.Exit(1)
+// }
+// defer f.Close()
+func LogToFile(path string, prefix string) (*os.File, error) {
+ return LogToFileWith(path, prefix, log.Default())
+}
+
+// LogOptionsSetter is an interface implemented by stdlib's log and charm's log
+// libraries.
+type LogOptionsSetter interface {
+ SetOutput(io.Writer)
+ SetPrefix(string)
+}
+
+// LogToFileWith does allows to call LogToFile with a custom LogOptionsSetter.
+func LogToFileWith(path string, prefix string, log LogOptionsSetter) (*os.File, error) {
+ f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) //nolint:gomnd
+ if err != nil {
+ return nil, fmt.Errorf("error opening file for logging: %w", err)
+ }
+ log.SetOutput(f)
+
+ // Add a space after the prefix if a prefix is being specified and it
+ // doesn't already have a trailing space.
+ if len(prefix) > 0 {
+ finalChar := prefix[len(prefix)-1]
+ if !unicode.IsSpace(rune(finalChar)) {
+ prefix += " "
+ }
+ }
+ log.SetPrefix(prefix)
+
+ return f, nil
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/mouse.go b/vendor/github.com/charmbracelet/bubbletea/mouse.go
new file mode 100644
index 0000000..add8d02
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/mouse.go
@@ -0,0 +1,308 @@
+package tea
+
+import "strconv"
+
+// MouseMsg contains information about a mouse event and are sent to a programs
+// update function when mouse activity occurs. Note that the mouse must first
+// be enabled in order for the mouse events to be received.
+type MouseMsg MouseEvent
+
+// String returns a string representation of a mouse event.
+func (m MouseMsg) String() string {
+ return MouseEvent(m).String()
+}
+
+// MouseEvent represents a mouse event, which could be a click, a scroll wheel
+// movement, a cursor movement, or a combination.
+type MouseEvent struct {
+ X int
+ Y int
+ Shift bool
+ Alt bool
+ Ctrl bool
+ Action MouseAction
+ Button MouseButton
+
+ // Deprecated: Use MouseAction & MouseButton instead.
+ Type MouseEventType
+}
+
+// IsWheel returns true if the mouse event is a wheel event.
+func (m MouseEvent) IsWheel() bool {
+ return m.Button == MouseButtonWheelUp || m.Button == MouseButtonWheelDown ||
+ m.Button == MouseButtonWheelLeft || m.Button == MouseButtonWheelRight
+}
+
+// String returns a string representation of a mouse event.
+func (m MouseEvent) String() (s string) {
+ if m.Ctrl {
+ s += "ctrl+"
+ }
+ if m.Alt {
+ s += "alt+"
+ }
+ if m.Shift {
+ s += "shift+"
+ }
+
+ if m.Button == MouseButtonNone {
+ if m.Action == MouseActionMotion || m.Action == MouseActionRelease {
+ s += mouseActions[m.Action]
+ } else {
+ s += "unknown"
+ }
+ } else if m.IsWheel() {
+ s += mouseButtons[m.Button]
+ } else {
+ btn := mouseButtons[m.Button]
+ if btn != "" {
+ s += btn
+ }
+ act := mouseActions[m.Action]
+ if act != "" {
+ s += " " + act
+ }
+ }
+
+ return s
+}
+
+// MouseAction represents the action that occurred during a mouse event.
+type MouseAction int
+
+// Mouse event actions.
+const (
+ MouseActionPress MouseAction = iota
+ MouseActionRelease
+ MouseActionMotion
+)
+
+var mouseActions = map[MouseAction]string{
+ MouseActionPress: "press",
+ MouseActionRelease: "release",
+ MouseActionMotion: "motion",
+}
+
+// MouseButton represents the button that was pressed during a mouse event.
+type MouseButton int
+
+// Mouse event buttons
+//
+// This is based on X11 mouse button codes.
+//
+// 1 = left button
+// 2 = middle button (pressing the scroll wheel)
+// 3 = right button
+// 4 = turn scroll wheel up
+// 5 = turn scroll wheel down
+// 6 = push scroll wheel left
+// 7 = push scroll wheel right
+// 8 = 4th button (aka browser backward button)
+// 9 = 5th button (aka browser forward button)
+// 10
+// 11
+//
+// Other buttons are not supported.
+const (
+ MouseButtonNone MouseButton = iota
+ MouseButtonLeft
+ MouseButtonMiddle
+ MouseButtonRight
+ MouseButtonWheelUp
+ MouseButtonWheelDown
+ MouseButtonWheelLeft
+ MouseButtonWheelRight
+ MouseButtonBackward
+ MouseButtonForward
+ MouseButton10
+ MouseButton11
+)
+
+var mouseButtons = map[MouseButton]string{
+ MouseButtonNone: "none",
+ MouseButtonLeft: "left",
+ MouseButtonMiddle: "middle",
+ MouseButtonRight: "right",
+ MouseButtonWheelUp: "wheel up",
+ MouseButtonWheelDown: "wheel down",
+ MouseButtonWheelLeft: "wheel left",
+ MouseButtonWheelRight: "wheel right",
+ MouseButtonBackward: "backward",
+ MouseButtonForward: "forward",
+ MouseButton10: "button 10",
+ MouseButton11: "button 11",
+}
+
+// MouseEventType indicates the type of mouse event occurring.
+//
+// Deprecated: Use MouseAction & MouseButton instead.
+type MouseEventType int
+
+// Mouse event types.
+//
+// Deprecated: Use MouseAction & MouseButton instead.
+const (
+ MouseUnknown MouseEventType = iota
+ MouseLeft
+ MouseRight
+ MouseMiddle
+ MouseRelease // mouse button release (X10 only)
+ MouseWheelUp
+ MouseWheelDown
+ MouseWheelLeft
+ MouseWheelRight
+ MouseBackward
+ MouseForward
+ MouseMotion
+)
+
+// Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events
+// look like:
+//
+// ESC [ < Cb ; Cx ; Cy (M or m)
+//
+// where:
+//
+// Cb is the encoded button code
+// Cx is the x-coordinate of the mouse
+// Cy is the y-coordinate of the mouse
+// M is for button press, m is for button release
+//
+// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
+func parseSGRMouseEvent(buf []byte) MouseEvent {
+ str := string(buf[3:])
+ matches := mouseSGRRegex.FindStringSubmatch(str)
+ if len(matches) != 5 {
+ // Unreachable, we already checked the regex in `detectOneMsg`.
+ panic("invalid mouse event")
+ }
+
+ b, _ := strconv.Atoi(matches[1])
+ px := matches[2]
+ py := matches[3]
+ release := matches[4] == "m"
+ m := parseMouseButton(b, true)
+
+ // Wheel buttons don't have release events
+ // Motion can be reported as a release event in some terminals (Windows Terminal)
+ if m.Action != MouseActionMotion && !m.IsWheel() && release {
+ m.Action = MouseActionRelease
+ m.Type = MouseRelease
+ }
+
+ x, _ := strconv.Atoi(px)
+ y, _ := strconv.Atoi(py)
+
+ // (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
+ m.X = x - 1
+ m.Y = y - 1
+
+ return m
+}
+
+const x10MouseByteOffset = 32
+
+// Parse X10-encoded mouse events; the simplest kind. The last release of X10
+// was December 1986, by the way. The original X10 mouse protocol limits the Cx
+// and Cy coordinates to 223 (=255-032).
+//
+// X10 mouse events look like:
+//
+// ESC [M Cb Cx Cy
+//
+// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
+func parseX10MouseEvent(buf []byte) MouseEvent {
+ v := buf[3:6]
+ m := parseMouseButton(int(v[0]), false)
+
+ // (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
+ m.X = int(v[1]) - x10MouseByteOffset - 1
+ m.Y = int(v[2]) - x10MouseByteOffset - 1
+
+ return m
+}
+
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
+func parseMouseButton(b int, isSGR bool) MouseEvent {
+ var m MouseEvent
+ e := b
+ if !isSGR {
+ e -= x10MouseByteOffset
+ }
+
+ const (
+ bitShift = 0b0000_0100
+ bitAlt = 0b0000_1000
+ bitCtrl = 0b0001_0000
+ bitMotion = 0b0010_0000
+ bitWheel = 0b0100_0000
+ bitAdd = 0b1000_0000 // additional buttons 8-11
+
+ bitsMask = 0b0000_0011
+ )
+
+ if e&bitAdd != 0 {
+ m.Button = MouseButtonBackward + MouseButton(e&bitsMask)
+ } else if e&bitWheel != 0 {
+ m.Button = MouseButtonWheelUp + MouseButton(e&bitsMask)
+ } else {
+ m.Button = MouseButtonLeft + MouseButton(e&bitsMask)
+ // X10 reports a button release as 0b0000_0011 (3)
+ if e&bitsMask == bitsMask {
+ m.Action = MouseActionRelease
+ m.Button = MouseButtonNone
+ }
+ }
+
+ // Motion bit doesn't get reported for wheel events.
+ if e&bitMotion != 0 && !m.IsWheel() {
+ m.Action = MouseActionMotion
+ }
+
+ // Modifiers
+ m.Alt = e&bitAlt != 0
+ m.Ctrl = e&bitCtrl != 0
+ m.Shift = e&bitShift != 0
+
+ // backward compatibility
+ switch {
+ case m.Button == MouseButtonLeft && m.Action == MouseActionPress:
+ m.Type = MouseLeft
+ case m.Button == MouseButtonMiddle && m.Action == MouseActionPress:
+ m.Type = MouseMiddle
+ case m.Button == MouseButtonRight && m.Action == MouseActionPress:
+ m.Type = MouseRight
+ case m.Button == MouseButtonNone && m.Action == MouseActionRelease:
+ m.Type = MouseRelease
+ case m.Button == MouseButtonWheelUp && m.Action == MouseActionPress:
+ m.Type = MouseWheelUp
+ case m.Button == MouseButtonWheelDown && m.Action == MouseActionPress:
+ m.Type = MouseWheelDown
+ case m.Button == MouseButtonWheelLeft && m.Action == MouseActionPress:
+ m.Type = MouseWheelLeft
+ case m.Button == MouseButtonWheelRight && m.Action == MouseActionPress:
+ m.Type = MouseWheelRight
+ case m.Button == MouseButtonBackward && m.Action == MouseActionPress:
+ m.Type = MouseBackward
+ case m.Button == MouseButtonForward && m.Action == MouseActionPress:
+ m.Type = MouseForward
+ case m.Action == MouseActionMotion:
+ m.Type = MouseMotion
+ switch m.Button {
+ case MouseButtonLeft:
+ m.Type = MouseLeft
+ case MouseButtonMiddle:
+ m.Type = MouseMiddle
+ case MouseButtonRight:
+ m.Type = MouseRight
+ case MouseButtonBackward:
+ m.Type = MouseBackward
+ case MouseButtonForward:
+ m.Type = MouseForward
+ }
+ default:
+ m.Type = MouseUnknown
+ }
+
+ return m
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/nil_renderer.go b/vendor/github.com/charmbracelet/bubbletea/nil_renderer.go
new file mode 100644
index 0000000..1b1d440
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/nil_renderer.go
@@ -0,0 +1,21 @@
+package tea
+
+type nilRenderer struct{}
+
+func (n nilRenderer) start() {}
+func (n nilRenderer) stop() {}
+func (n nilRenderer) kill() {}
+func (n nilRenderer) write(_ string) {}
+func (n nilRenderer) repaint() {}
+func (n nilRenderer) clearScreen() {}
+func (n nilRenderer) altScreen() bool { return false }
+func (n nilRenderer) enterAltScreen() {}
+func (n nilRenderer) exitAltScreen() {}
+func (n nilRenderer) showCursor() {}
+func (n nilRenderer) hideCursor() {}
+func (n nilRenderer) enableMouseCellMotion() {}
+func (n nilRenderer) disableMouseCellMotion() {}
+func (n nilRenderer) enableMouseAllMotion() {}
+func (n nilRenderer) disableMouseAllMotion() {}
+func (n nilRenderer) enableMouseSGRMode() {}
+func (n nilRenderer) disableMouseSGRMode() {}
diff --git a/vendor/github.com/charmbracelet/bubbletea/options.go b/vendor/github.com/charmbracelet/bubbletea/options.go
new file mode 100644
index 0000000..71e9449
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/options.go
@@ -0,0 +1,218 @@
+package tea
+
+import (
+ "context"
+ "io"
+ "sync/atomic"
+
+ "github.com/muesli/termenv"
+)
+
+// ProgramOption is used to set options when initializing a Program. Program can
+// accept a variable number of options.
+//
+// Example usage:
+//
+// p := NewProgram(model, WithInput(someInput), WithOutput(someOutput))
+type ProgramOption func(*Program)
+
+// WithContext lets you specify a context in which to run the Program. This is
+// useful if you want to cancel the execution from outside. When a Program gets
+// cancelled it will exit with an error ErrProgramKilled.
+func WithContext(ctx context.Context) ProgramOption {
+ return func(p *Program) {
+ p.ctx = ctx
+ }
+}
+
+// WithOutput sets the output which, by default, is stdout. In most cases you
+// won't need to use this.
+func WithOutput(output io.Writer) ProgramOption {
+ return func(p *Program) {
+ if o, ok := output.(*termenv.Output); ok {
+ p.output = o
+ } else {
+ p.output = termenv.NewOutput(output, termenv.WithColorCache(true))
+ }
+ }
+}
+
+// WithInput sets the input which, by default, is stdin. In most cases you
+// won't need to use this. To disable input entirely pass nil.
+//
+// p := NewProgram(model, WithInput(nil))
+func WithInput(input io.Reader) ProgramOption {
+ return func(p *Program) {
+ p.input = input
+ p.inputType = customInput
+ }
+}
+
+// WithInputTTY opens a new TTY for input (or console input device on Windows).
+func WithInputTTY() ProgramOption {
+ return func(p *Program) {
+ p.inputType = ttyInput
+ }
+}
+
+// WithoutSignalHandler disables the signal handler that Bubble Tea sets up for
+// Programs. This is useful if you want to handle signals yourself.
+func WithoutSignalHandler() ProgramOption {
+ return func(p *Program) {
+ p.startupOptions |= withoutSignalHandler
+ }
+}
+
+// WithoutCatchPanics disables the panic catching that Bubble Tea does by
+// default. If panic catching is disabled the terminal will be in a fairly
+// unusable state after a panic because Bubble Tea will not perform its usual
+// cleanup on exit.
+func WithoutCatchPanics() ProgramOption {
+ return func(p *Program) {
+ p.startupOptions |= withoutCatchPanics
+ }
+}
+
+// WithoutSignals will ignore OS signals.
+// This is mainly useful for testing.
+func WithoutSignals() ProgramOption {
+ return func(p *Program) {
+ atomic.StoreUint32(&p.ignoreSignals, 1)
+ }
+}
+
+// WithAltScreen starts the program with the alternate screen buffer enabled
+// (i.e. the program starts in full window mode). Note that the altscreen will
+// be automatically exited when the program quits.
+//
+// Example:
+//
+// p := tea.NewProgram(Model{}, tea.WithAltScreen())
+// if _, err := p.Run(); err != nil {
+// fmt.Println("Error running program:", err)
+// os.Exit(1)
+// }
+//
+// To enter the altscreen once the program has already started running use the
+// EnterAltScreen command.
+func WithAltScreen() ProgramOption {
+ return func(p *Program) {
+ p.startupOptions |= withAltScreen
+ }
+}
+
+// WithMouseCellMotion starts the program with the mouse enabled in "cell
+// motion" mode.
+//
+// Cell motion mode enables mouse click, release, and wheel events. Mouse
+// movement events are also captured if a mouse button is pressed (i.e., drag
+// events). Cell motion mode is better supported than all motion mode.
+//
+// This will try to enable the mouse in extended mode (SGR), if that is not
+// supported by the terminal it will fall back to normal mode (X10).
+//
+// To enable mouse cell motion once the program has already started running use
+// the EnableMouseCellMotion command. To disable the mouse when the program is
+// running use the DisableMouse command.
+//
+// The mouse will be automatically disabled when the program exits.
+func WithMouseCellMotion() ProgramOption {
+ return func(p *Program) {
+ p.startupOptions |= withMouseCellMotion // set
+ p.startupOptions &^= withMouseAllMotion // clear
+ }
+}
+
+// WithMouseAllMotion starts the program with the mouse enabled in "all motion"
+// mode.
+//
+// EnableMouseAllMotion is a special command that enables mouse click, release,
+// wheel, and motion events, which are delivered regardless of whether a mouse
+// button is pressed, effectively enabling support for hover interactions.
+//
+// This will try to enable the mouse in extended mode (SGR), if that is not
+// supported by the terminal it will fall back to normal mode (X10).
+//
+// Many modern terminals support this, but not all. If in doubt, use
+// EnableMouseCellMotion instead.
+//
+// To enable the mouse once the program has already started running use the
+// EnableMouseAllMotion command. To disable the mouse when the program is
+// running use the DisableMouse command.
+//
+// The mouse will be automatically disabled when the program exits.
+func WithMouseAllMotion() ProgramOption {
+ return func(p *Program) {
+ p.startupOptions |= withMouseAllMotion // set
+ p.startupOptions &^= withMouseCellMotion // clear
+ }
+}
+
+// WithoutRenderer disables the renderer. When this is set output and log
+// statements will be plainly sent to stdout (or another output if one is set)
+// without any rendering and redrawing logic. In other words, printing and
+// logging will behave the same way it would in a non-TUI commandline tool.
+// This can be useful if you want to use the Bubble Tea framework for a non-TUI
+// application, or to provide an additional non-TUI mode to your Bubble Tea
+// programs. For example, your program could behave like a daemon if output is
+// not a TTY.
+func WithoutRenderer() ProgramOption {
+ return func(p *Program) {
+ p.renderer = &nilRenderer{}
+ }
+}
+
+// WithANSICompressor removes redundant ANSI sequences to produce potentially
+// smaller output, at the cost of some processing overhead.
+//
+// This feature is provisional, and may be changed or removed in a future version
+// of this package.
+func WithANSICompressor() ProgramOption {
+ return func(p *Program) {
+ p.startupOptions |= withANSICompressor
+ }
+}
+
+// WithFilter supplies an event filter that will be invoked before Bubble Tea
+// processes a tea.Msg. The event filter can return any tea.Msg which will then
+// get handled by Bubble Tea instead of the original event. If the event filter
+// returns nil, the event will be ignored and Bubble Tea will not process it.
+//
+// As an example, this could be used to prevent a program from shutting down if
+// there are unsaved changes.
+//
+// Example:
+//
+// func filter(m tea.Model, msg tea.Msg) tea.Msg {
+// if _, ok := msg.(tea.QuitMsg); !ok {
+// return msg
+// }
+//
+// model := m.(myModel)
+// if model.hasChanges {
+// return nil
+// }
+//
+// return msg
+// }
+//
+// p := tea.NewProgram(Model{}, tea.WithFilter(filter));
+//
+// if _,err := p.Run(); err != nil {
+// fmt.Println("Error running program:", err)
+// os.Exit(1)
+// }
+func WithFilter(filter func(Model, Msg) Msg) ProgramOption {
+ return func(p *Program) {
+ p.filter = filter
+ }
+}
+
+// WithFPS sets a custom maximum FPS at which the renderer should run. If
+// less than 1, the default value of 60 will be used. If over 120, the FPS
+// will be capped at 120.
+func WithFPS(fps int) ProgramOption {
+ return func(p *Program) {
+ p.fps = fps
+ }
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/renderer.go b/vendor/github.com/charmbracelet/bubbletea/renderer.go
new file mode 100644
index 0000000..5a3ee3c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/renderer.go
@@ -0,0 +1,62 @@
+package tea
+
+// renderer is the interface for Bubble Tea renderers.
+type renderer interface {
+ // Start the renderer.
+ start()
+
+ // Stop the renderer, but render the final frame in the buffer, if any.
+ stop()
+
+ // Stop the renderer without doing any final rendering.
+ kill()
+
+ // Write a frame to the renderer. The renderer can write this data to
+ // output at its discretion.
+ write(string)
+
+ // Request a full re-render. Note that this will not trigger a render
+ // immediately. Rather, this method causes the next render to be a full
+ // repaint. Because of this, it's safe to call this method multiple times
+ // in succession.
+ repaint()
+
+ // Clears the terminal.
+ clearScreen()
+
+ // Whether or not the alternate screen buffer is enabled.
+ altScreen() bool
+ // Enable the alternate screen buffer.
+ enterAltScreen()
+ // Disable the alternate screen buffer.
+ exitAltScreen()
+
+ // Show the cursor.
+ showCursor()
+ // Hide the cursor.
+ hideCursor()
+
+ // enableMouseCellMotion enables mouse click, release, wheel and motion
+ // events if a mouse button is pressed (i.e., drag events).
+ enableMouseCellMotion()
+
+ // disableMouseCellMotion disables Mouse Cell Motion tracking.
+ disableMouseCellMotion()
+
+ // enableMouseAllMotion enables mouse click, release, wheel and motion
+ // events, regardless of whether a mouse button is pressed. Many modern
+ // terminals support this, but not all.
+ enableMouseAllMotion()
+
+ // disableMouseAllMotion disables All Motion mouse tracking.
+ disableMouseAllMotion()
+
+ // enableMouseSGRMode enables mouse extended mode (SGR).
+ enableMouseSGRMode()
+
+ // disableMouseSGRMode disables mouse extended mode (SGR).
+ disableMouseSGRMode()
+}
+
+// repaintMsg forces a full repaint.
+type repaintMsg struct{}
diff --git a/vendor/github.com/charmbracelet/bubbletea/screen.go b/vendor/github.com/charmbracelet/bubbletea/screen.go
new file mode 100644
index 0000000..d064222
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/screen.go
@@ -0,0 +1,174 @@
+package tea
+
+// WindowSizeMsg is used to report the terminal size. It's sent to Update once
+// initially and then on every terminal resize. Note that Windows does not
+// have support for reporting when resizes occur as it does not support the
+// SIGWINCH signal.
+type WindowSizeMsg struct {
+ Width int
+ Height int
+}
+
+// ClearScreen is a special command that tells the program to clear the screen
+// before the next update. This can be used to move the cursor to the top left
+// of the screen and clear visual clutter when the alt screen is not in use.
+//
+// Note that it should never be necessary to call ClearScreen() for regular
+// redraws.
+func ClearScreen() Msg {
+ return clearScreenMsg{}
+}
+
+// clearScreenMsg is an internal message that signals to clear the screen.
+// You can send a clearScreenMsg with ClearScreen.
+type clearScreenMsg struct{}
+
+// EnterAltScreen is a special command that tells the Bubble Tea program to
+// enter the alternate screen buffer.
+//
+// Because commands run asynchronously, this command should not be used in your
+// model's Init function. To initialize your program with the altscreen enabled
+// use the WithAltScreen ProgramOption instead.
+func EnterAltScreen() Msg {
+ return enterAltScreenMsg{}
+}
+
+// enterAltScreenMsg in an internal message signals that the program should
+// enter alternate screen buffer. You can send a enterAltScreenMsg with
+// EnterAltScreen.
+type enterAltScreenMsg struct{}
+
+// ExitAltScreen is a special command that tells the Bubble Tea program to exit
+// the alternate screen buffer. This command should be used to exit the
+// alternate screen buffer while the program is running.
+//
+// Note that the alternate screen buffer will be automatically exited when the
+// program quits.
+func ExitAltScreen() Msg {
+ return exitAltScreenMsg{}
+}
+
+// exitAltScreenMsg in an internal message signals that the program should exit
+// alternate screen buffer. You can send a exitAltScreenMsg with ExitAltScreen.
+type exitAltScreenMsg struct{}
+
+// EnableMouseCellMotion is a special command that enables mouse click,
+// release, and wheel events. Mouse movement events are also captured if
+// a mouse button is pressed (i.e., drag events).
+//
+// Because commands run asynchronously, this command should not be used in your
+// model's Init function. Use the WithMouseCellMotion ProgramOption instead.
+func EnableMouseCellMotion() Msg {
+ return enableMouseCellMotionMsg{}
+}
+
+// enableMouseCellMotionMsg is a special command that signals to start
+// listening for "cell motion" type mouse events (ESC[?1002l). To send an
+// enableMouseCellMotionMsg, use the EnableMouseCellMotion command.
+type enableMouseCellMotionMsg struct{}
+
+// EnableMouseAllMotion is a special command that enables mouse click, release,
+// wheel, and motion events, which are delivered regardless of whether a mouse
+// button is pressed, effectively enabling support for hover interactions.
+//
+// Many modern terminals support this, but not all. If in doubt, use
+// EnableMouseCellMotion instead.
+//
+// Because commands run asynchronously, this command should not be used in your
+// model's Init function. Use the WithMouseAllMotion ProgramOption instead.
+func EnableMouseAllMotion() Msg {
+ return enableMouseAllMotionMsg{}
+}
+
+// enableMouseAllMotionMsg is a special command that signals to start listening
+// for "all motion" type mouse events (ESC[?1003l). To send an
+// enableMouseAllMotionMsg, use the EnableMouseAllMotion command.
+type enableMouseAllMotionMsg struct{}
+
+// DisableMouse is a special command that stops listening for mouse events.
+func DisableMouse() Msg {
+ return disableMouseMsg{}
+}
+
+// disableMouseMsg is an internal message that signals to stop listening
+// for mouse events. To send a disableMouseMsg, use the DisableMouse command.
+type disableMouseMsg struct{}
+
+// HideCursor is a special command for manually instructing Bubble Tea to hide
+// the cursor. In some rare cases, certain operations will cause the terminal
+// to show the cursor, which is normally hidden for the duration of a Bubble
+// Tea program's lifetime. You will most likely not need to use this command.
+func HideCursor() Msg {
+ return hideCursorMsg{}
+}
+
+// hideCursorMsg is an internal command used to hide the cursor. You can send
+// this message with HideCursor.
+type hideCursorMsg struct{}
+
+// ShowCursor is a special command for manually instructing Bubble Tea to show
+// the cursor.
+func ShowCursor() Msg {
+ return showCursorMsg{}
+}
+
+// showCursorMsg is an internal command used to show the cursor. You can send
+// this message with ShowCursor.
+type showCursorMsg struct{}
+
+// EnterAltScreen enters the alternate screen buffer, which consumes the entire
+// terminal window. ExitAltScreen will return the terminal to its former state.
+//
+// Deprecated: Use the WithAltScreen ProgramOption instead.
+func (p *Program) EnterAltScreen() {
+ if p.renderer != nil {
+ p.renderer.enterAltScreen()
+ }
+}
+
+// ExitAltScreen exits the alternate screen buffer.
+//
+// Deprecated: The altscreen will exited automatically when the program exits.
+func (p *Program) ExitAltScreen() {
+ if p.renderer != nil {
+ p.renderer.exitAltScreen()
+ }
+}
+
+// EnableMouseCellMotion enables mouse click, release, wheel and motion events
+// if a mouse button is pressed (i.e., drag events).
+//
+// Deprecated: Use the WithMouseCellMotion ProgramOption instead.
+func (p *Program) EnableMouseCellMotion() {
+ p.renderer.enableMouseCellMotion()
+}
+
+// DisableMouseCellMotion disables Mouse Cell Motion tracking. This will be
+// called automatically when exiting a Bubble Tea program.
+//
+// Deprecated: The mouse will automatically be disabled when the program exits.
+func (p *Program) DisableMouseCellMotion() {
+ p.renderer.disableMouseCellMotion()
+}
+
+// EnableMouseAllMotion enables mouse click, release, wheel and motion events,
+// regardless of whether a mouse button is pressed. Many modern terminals
+// support this, but not all.
+//
+// Deprecated: Use the WithMouseAllMotion ProgramOption instead.
+func (p *Program) EnableMouseAllMotion() {
+ p.renderer.enableMouseAllMotion()
+}
+
+// DisableMouseAllMotion disables All Motion mouse tracking. This will be
+// called automatically when exiting a Bubble Tea program.
+//
+// Deprecated: The mouse will automatically be disabled when the program exits.
+func (p *Program) DisableMouseAllMotion() {
+ p.renderer.disableMouseAllMotion()
+}
+
+// SetWindowTitle sets the terminal window title.
+func (p *Program) SetWindowTitle(title string) {
+ p.output.SetWindowTitle(title)
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/signals_unix.go b/vendor/github.com/charmbracelet/bubbletea/signals_unix.go
new file mode 100644
index 0000000..826f58b
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/signals_unix.go
@@ -0,0 +1,33 @@
+//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || aix
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris aix
+
+package tea
+
+import (
+ "os"
+ "os/signal"
+ "syscall"
+)
+
+// listenForResize sends messages (or errors) when the terminal resizes.
+// Argument output should be the file descriptor for the terminal; usually
+// os.Stdout.
+func (p *Program) listenForResize(done chan struct{}) {
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, syscall.SIGWINCH)
+
+ defer func() {
+ signal.Stop(sig)
+ close(done)
+ }()
+
+ for {
+ select {
+ case <-p.ctx.Done():
+ return
+ case <-sig:
+ }
+
+ p.checkResize()
+ }
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/signals_windows.go b/vendor/github.com/charmbracelet/bubbletea/signals_windows.go
new file mode 100644
index 0000000..2fc6f8a
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/signals_windows.go
@@ -0,0 +1,10 @@
+//go:build windows
+// +build windows
+
+package tea
+
+// listenForResize is not available on windows because windows does not
+// implement syscall.SIGWINCH.
+func (p *Program) listenForResize(done chan struct{}) {
+ close(done)
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/standard_renderer.go b/vendor/github.com/charmbracelet/bubbletea/standard_renderer.go
new file mode 100644
index 0000000..1573a1c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/standard_renderer.go
@@ -0,0 +1,679 @@
+package tea
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/muesli/ansi/compressor"
+ "github.com/muesli/reflow/truncate"
+ "github.com/muesli/termenv"
+)
+
+const (
+ // defaultFramerate specifies the maximum interval at which we should
+ // update the view.
+ defaultFPS = 60
+ maxFPS = 120
+)
+
+// standardRenderer is a framerate-based terminal renderer, updating the view
+// at a given framerate to avoid overloading the terminal emulator.
+//
+// In cases where very high performance is needed the renderer can be told
+// to exclude ranges of lines, allowing them to be written to directly.
+type standardRenderer struct {
+ mtx *sync.Mutex
+ out *termenv.Output
+
+ buf bytes.Buffer
+ queuedMessageLines []string
+ framerate time.Duration
+ ticker *time.Ticker
+ done chan struct{}
+ lastRender string
+ linesRendered int
+ useANSICompressor bool
+ once sync.Once
+
+ // cursor visibility state
+ cursorHidden bool
+
+ // essentially whether or not we're using the full size of the terminal
+ altScreenActive bool
+
+ // renderer dimensions; usually the size of the window
+ width int
+ height int
+
+ // lines explicitly set not to render
+ ignoreLines map[int]struct{}
+}
+
+// newRenderer creates a new renderer. Normally you'll want to initialize it
+// with os.Stdout as the first argument.
+func newRenderer(out *termenv.Output, useANSICompressor bool, fps int) renderer {
+ if fps < 1 {
+ fps = defaultFPS
+ } else if fps > maxFPS {
+ fps = maxFPS
+ }
+ r := &standardRenderer{
+ out: out,
+ mtx: &sync.Mutex{},
+ done: make(chan struct{}),
+ framerate: time.Second / time.Duration(fps),
+ useANSICompressor: useANSICompressor,
+ queuedMessageLines: []string{},
+ }
+ if r.useANSICompressor {
+ r.out = termenv.NewOutput(&compressor.Writer{Forward: out})
+ }
+ return r
+}
+
+// start starts the renderer.
+func (r *standardRenderer) start() {
+ if r.ticker == nil {
+ r.ticker = time.NewTicker(r.framerate)
+ } else {
+ // If the ticker already exists, it has been stopped and we need to
+ // reset it.
+ r.ticker.Reset(r.framerate)
+ }
+
+ // Since the renderer can be restarted after a stop, we need to reset
+ // the done channel and its corresponding sync.Once.
+ r.once = sync.Once{}
+
+ go r.listen()
+}
+
+// stop permanently halts the renderer, rendering the final frame.
+func (r *standardRenderer) stop() {
+ // Stop the renderer before acquiring the mutex to avoid a deadlock.
+ r.once.Do(func() {
+ r.done <- struct{}{}
+ })
+
+ // flush locks the mutex
+ r.flush()
+
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.out.ClearLine()
+
+ if r.useANSICompressor {
+ if w, ok := r.out.TTY().(io.WriteCloser); ok {
+ _ = w.Close()
+ }
+ }
+}
+
+// kill halts the renderer. The final frame will not be rendered.
+func (r *standardRenderer) kill() {
+ // Stop the renderer before acquiring the mutex to avoid a deadlock.
+ r.once.Do(func() {
+ r.done <- struct{}{}
+ })
+
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.out.ClearLine()
+}
+
+// listen waits for ticks on the ticker, or a signal to stop the renderer.
+func (r *standardRenderer) listen() {
+ for {
+ select {
+ case <-r.done:
+ r.ticker.Stop()
+ return
+
+ case <-r.ticker.C:
+ r.flush()
+ }
+ }
+}
+
+// flush renders the buffer.
+func (r *standardRenderer) flush() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ if r.buf.Len() == 0 || r.buf.String() == r.lastRender {
+ // Nothing to do
+ return
+ }
+
+ // Output buffer
+ buf := &bytes.Buffer{}
+ out := termenv.NewOutput(buf)
+
+ newLines := strings.Split(r.buf.String(), "\n")
+
+ // If we know the output's height, we can use it to determine how many
+ // lines we can render. We drop lines from the top of the render buffer if
+ // necessary, as we can't navigate the cursor into the terminal's scrollback
+ // buffer.
+ if r.height > 0 && len(newLines) > r.height {
+ newLines = newLines[len(newLines)-r.height:]
+ }
+
+ numLinesThisFlush := len(newLines)
+ oldLines := strings.Split(r.lastRender, "\n")
+ skipLines := make(map[int]struct{})
+ flushQueuedMessages := len(r.queuedMessageLines) > 0 && !r.altScreenActive
+
+ // Add any queued messages to this render
+ if flushQueuedMessages {
+ newLines = append(r.queuedMessageLines, newLines...)
+ r.queuedMessageLines = []string{}
+ }
+
+ // Clear any lines we painted in the last render.
+ if r.linesRendered > 0 {
+ for i := r.linesRendered - 1; i > 0; i-- {
+ // If the number of lines we want to render hasn't increased and
+ // new line is the same as the old line we can skip rendering for
+ // this line as a performance optimization.
+ if (len(newLines) <= len(oldLines)) && (len(newLines) > i && len(oldLines) > i) && (newLines[i] == oldLines[i]) {
+ skipLines[i] = struct{}{}
+ } else if _, exists := r.ignoreLines[i]; !exists {
+ out.ClearLine()
+ }
+
+ out.CursorUp(1)
+ }
+
+ if _, exists := r.ignoreLines[0]; !exists {
+ // We need to return to the start of the line here to properly
+ // erase it. Going back the entire width of the terminal will
+ // usually be farther than we need to go, but terminal emulators
+ // will stop the cursor at the start of the line as a rule.
+ //
+ // We use this sequence in particular because it's part of the ANSI
+ // standard (whereas others are proprietary to, say, VT100/VT52).
+ // If cursor previous line (ESC[ + + F) were better supported
+ // we could use that above to eliminate this step.
+ out.CursorBack(r.width)
+ out.ClearLine()
+ }
+ }
+
+ // Merge the set of lines we're skipping as a rendering optimization with
+ // the set of lines we've explicitly asked the renderer to ignore.
+ for k, v := range r.ignoreLines {
+ skipLines[k] = v
+ }
+
+ // Paint new lines
+ for i := 0; i < len(newLines); i++ {
+ if _, skip := skipLines[i]; skip {
+ // Unless this is the last line, move the cursor down.
+ if i < len(newLines)-1 {
+ out.CursorDown(1)
+ }
+ } else {
+ line := newLines[i]
+
+ // Truncate lines wider than the width of the window to avoid
+ // wrapping, which will mess up rendering. If we don't have the
+ // width of the window this will be ignored.
+ //
+ // Note that on Windows we only get the width of the window on
+ // program initialization, so after a resize this won't perform
+ // correctly (signal SIGWINCH is not supported on Windows).
+ if r.width > 0 {
+ line = truncate.String(line, uint(r.width))
+ }
+
+ _, _ = out.WriteString(line)
+
+ if i < len(newLines)-1 {
+ _, _ = out.WriteString("\r\n")
+ }
+ }
+ }
+ r.linesRendered = numLinesThisFlush
+
+ // Make sure the cursor is at the start of the last line to keep rendering
+ // behavior consistent.
+ if r.altScreenActive {
+ // This case fixes a bug in macOS terminal. In other terminals the
+ // other case seems to do the job regardless of whether or not we're
+ // using the full terminal window.
+ out.MoveCursor(r.linesRendered, 0)
+ } else {
+ out.CursorBack(r.width)
+ }
+
+ _, _ = r.out.Write(buf.Bytes())
+ r.lastRender = r.buf.String()
+ r.buf.Reset()
+}
+
+// write writes to the internal buffer. The buffer will be outputted via the
+// ticker which calls flush().
+func (r *standardRenderer) write(s string) {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+ r.buf.Reset()
+
+ // If an empty string was passed we should clear existing output and
+ // rendering nothing. Rather than introduce additional state to manage
+ // this, we render a single space as a simple (albeit less correct)
+ // solution.
+ if s == "" {
+ s = " "
+ }
+
+ _, _ = r.buf.WriteString(s)
+}
+
+func (r *standardRenderer) repaint() {
+ r.lastRender = ""
+}
+
+func (r *standardRenderer) clearScreen() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.out.ClearScreen()
+ r.out.MoveCursor(1, 1)
+
+ r.repaint()
+}
+
+func (r *standardRenderer) altScreen() bool {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ return r.altScreenActive
+}
+
+func (r *standardRenderer) enterAltScreen() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ if r.altScreenActive {
+ return
+ }
+
+ r.altScreenActive = true
+ r.out.AltScreen()
+
+ // Ensure that the terminal is cleared, even when it doesn't support
+ // alt screen (or alt screen support is disabled, like GNU screen by
+ // default).
+ //
+ // Note: we can't use r.clearScreen() here because the mutex is already
+ // locked.
+ r.out.ClearScreen()
+ r.out.MoveCursor(1, 1)
+
+ // cmd.exe and other terminals keep separate cursor states for the AltScreen
+ // and the main buffer. We have to explicitly reset the cursor visibility
+ // whenever we enter AltScreen.
+ if r.cursorHidden {
+ r.out.HideCursor()
+ } else {
+ r.out.ShowCursor()
+ }
+
+ r.repaint()
+}
+
+func (r *standardRenderer) exitAltScreen() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ if !r.altScreenActive {
+ return
+ }
+
+ r.altScreenActive = false
+ r.out.ExitAltScreen()
+
+ // cmd.exe and other terminals keep separate cursor states for the AltScreen
+ // and the main buffer. We have to explicitly reset the cursor visibility
+ // whenever we exit AltScreen.
+ if r.cursorHidden {
+ r.out.HideCursor()
+ } else {
+ r.out.ShowCursor()
+ }
+
+ r.repaint()
+}
+
+func (r *standardRenderer) showCursor() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.cursorHidden = false
+ r.out.ShowCursor()
+}
+
+func (r *standardRenderer) hideCursor() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.cursorHidden = true
+ r.out.HideCursor()
+}
+
+func (r *standardRenderer) enableMouseCellMotion() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.out.EnableMouseCellMotion()
+}
+
+func (r *standardRenderer) disableMouseCellMotion() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.out.DisableMouseCellMotion()
+}
+
+func (r *standardRenderer) enableMouseAllMotion() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.out.EnableMouseAllMotion()
+}
+
+func (r *standardRenderer) disableMouseAllMotion() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.out.DisableMouseAllMotion()
+}
+
+func (r *standardRenderer) enableMouseSGRMode() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.out.EnableMouseExtendedMode()
+}
+
+func (r *standardRenderer) disableMouseSGRMode() {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.out.DisableMouseExtendedMode()
+}
+
+// setIgnoredLines specifies lines not to be touched by the standard Bubble Tea
+// renderer.
+func (r *standardRenderer) setIgnoredLines(from int, to int) {
+ // Lock if we're going to be clearing some lines since we don't want
+ // anything jacking our cursor.
+ if r.linesRendered > 0 {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+ }
+
+ if r.ignoreLines == nil {
+ r.ignoreLines = make(map[int]struct{})
+ }
+ for i := from; i < to; i++ {
+ r.ignoreLines[i] = struct{}{}
+ }
+
+ // Erase ignored lines
+ if r.linesRendered > 0 {
+ buf := &bytes.Buffer{}
+ out := termenv.NewOutput(buf)
+
+ for i := r.linesRendered - 1; i >= 0; i-- {
+ if _, exists := r.ignoreLines[i]; exists {
+ out.ClearLine()
+ }
+ out.CursorUp(1)
+ }
+ out.MoveCursor(r.linesRendered, 0) // put cursor back
+ _, _ = r.out.Write(buf.Bytes())
+ }
+}
+
+// clearIgnoredLines returns control of any ignored lines to the standard
+// Bubble Tea renderer. That is, any lines previously set to be ignored can be
+// rendered to again.
+func (r *standardRenderer) clearIgnoredLines() {
+ r.ignoreLines = nil
+}
+
+// insertTop effectively scrolls up. It inserts lines at the top of a given
+// area designated to be a scrollable region, pushing everything else down.
+// This is roughly how ncurses does it.
+//
+// To call this function use command ScrollUp().
+//
+// For this to work renderer.ignoreLines must be set to ignore the scrollable
+// region since we are bypassing the normal Bubble Tea renderer here.
+//
+// Because this method relies on the terminal dimensions, it's only valid for
+// full-window applications (generally those that use the alternate screen
+// buffer).
+//
+// This method bypasses the normal rendering buffer and is philosophically
+// different than the normal way we approach rendering in Bubble Tea. It's for
+// use in high-performance rendering, such as a pager that could potentially
+// be rendering very complicated ansi. In cases where the content is simpler
+// standard Bubble Tea rendering should suffice.
+func (r *standardRenderer) insertTop(lines []string, topBoundary, bottomBoundary int) {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ buf := &bytes.Buffer{}
+ out := termenv.NewOutput(buf)
+
+ out.ChangeScrollingRegion(topBoundary, bottomBoundary)
+ out.MoveCursor(topBoundary, 0)
+ out.InsertLines(len(lines))
+ _, _ = out.WriteString(strings.Join(lines, "\r\n"))
+ out.ChangeScrollingRegion(0, r.height)
+
+ // Move cursor back to where the main rendering routine expects it to be
+ out.MoveCursor(r.linesRendered, 0)
+
+ _, _ = r.out.Write(buf.Bytes())
+}
+
+// insertBottom effectively scrolls down. It inserts lines at the bottom of
+// a given area designated to be a scrollable region, pushing everything else
+// up. This is roughly how ncurses does it.
+//
+// To call this function use the command ScrollDown().
+//
+// See note in insertTop() for caveats, how this function only makes sense for
+// full-window applications, and how it differs from the normal way we do
+// rendering in Bubble Tea.
+func (r *standardRenderer) insertBottom(lines []string, topBoundary, bottomBoundary int) {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ buf := &bytes.Buffer{}
+ out := termenv.NewOutput(buf)
+
+ out.ChangeScrollingRegion(topBoundary, bottomBoundary)
+ out.MoveCursor(bottomBoundary, 0)
+ _, _ = out.WriteString("\r\n" + strings.Join(lines, "\r\n"))
+ out.ChangeScrollingRegion(0, r.height)
+
+ // Move cursor back to where the main rendering routine expects it to be
+ out.MoveCursor(r.linesRendered, 0)
+
+ _, _ = r.out.Write(buf.Bytes())
+}
+
+// handleMessages handles internal messages for the renderer.
+func (r *standardRenderer) handleMessages(msg Msg) {
+ switch msg := msg.(type) {
+ case repaintMsg:
+ // Force a repaint by clearing the render cache as we slide into a
+ // render.
+ r.mtx.Lock()
+ r.repaint()
+ r.mtx.Unlock()
+
+ case WindowSizeMsg:
+ r.mtx.Lock()
+ r.width = msg.Width
+ r.height = msg.Height
+ r.repaint()
+ r.mtx.Unlock()
+
+ case clearScrollAreaMsg:
+ r.clearIgnoredLines()
+
+ // Force a repaint on the area where the scrollable stuff was in this
+ // update cycle
+ r.mtx.Lock()
+ r.repaint()
+ r.mtx.Unlock()
+
+ case syncScrollAreaMsg:
+ // Re-render scrolling area
+ r.clearIgnoredLines()
+ r.setIgnoredLines(msg.topBoundary, msg.bottomBoundary)
+ r.insertTop(msg.lines, msg.topBoundary, msg.bottomBoundary)
+
+ // Force non-scrolling stuff to repaint in this update cycle
+ r.mtx.Lock()
+ r.repaint()
+ r.mtx.Unlock()
+
+ case scrollUpMsg:
+ r.insertTop(msg.lines, msg.topBoundary, msg.bottomBoundary)
+
+ case scrollDownMsg:
+ r.insertBottom(msg.lines, msg.topBoundary, msg.bottomBoundary)
+
+ case printLineMessage:
+ if !r.altScreenActive {
+ lines := strings.Split(msg.messageBody, "\n")
+ r.mtx.Lock()
+ r.queuedMessageLines = append(r.queuedMessageLines, lines...)
+ r.repaint()
+ r.mtx.Unlock()
+ }
+ }
+}
+
+// HIGH-PERFORMANCE RENDERING STUFF
+
+type syncScrollAreaMsg struct {
+ lines []string
+ topBoundary int
+ bottomBoundary int
+}
+
+// SyncScrollArea performs a paint of the entire region designated to be the
+// scrollable area. This is required to initialize the scrollable region and
+// should also be called on resize (WindowSizeMsg).
+//
+// For high-performance, scroll-based rendering only.
+func SyncScrollArea(lines []string, topBoundary int, bottomBoundary int) Cmd {
+ return func() Msg {
+ return syncScrollAreaMsg{
+ lines: lines,
+ topBoundary: topBoundary,
+ bottomBoundary: bottomBoundary,
+ }
+ }
+}
+
+type clearScrollAreaMsg struct{}
+
+// ClearScrollArea deallocates the scrollable region and returns the control of
+// those lines to the main rendering routine.
+//
+// For high-performance, scroll-based rendering only.
+func ClearScrollArea() Msg {
+ return clearScrollAreaMsg{}
+}
+
+type scrollUpMsg struct {
+ lines []string
+ topBoundary int
+ bottomBoundary int
+}
+
+// ScrollUp adds lines to the top of the scrollable region, pushing existing
+// lines below down. Lines that are pushed out the scrollable region disappear
+// from view.
+//
+// For high-performance, scroll-based rendering only.
+func ScrollUp(newLines []string, topBoundary, bottomBoundary int) Cmd {
+ return func() Msg {
+ return scrollUpMsg{
+ lines: newLines,
+ topBoundary: topBoundary,
+ bottomBoundary: bottomBoundary,
+ }
+ }
+}
+
+type scrollDownMsg struct {
+ lines []string
+ topBoundary int
+ bottomBoundary int
+}
+
+// ScrollDown adds lines to the bottom of the scrollable region, pushing
+// existing lines above up. Lines that are pushed out of the scrollable region
+// disappear from view.
+//
+// For high-performance, scroll-based rendering only.
+func ScrollDown(newLines []string, topBoundary, bottomBoundary int) Cmd {
+ return func() Msg {
+ return scrollDownMsg{
+ lines: newLines,
+ topBoundary: topBoundary,
+ bottomBoundary: bottomBoundary,
+ }
+ }
+}
+
+type printLineMessage struct {
+ messageBody string
+}
+
+// Println prints above the Program. This output is unmanaged by the program and
+// will persist across renders by the Program.
+//
+// Unlike fmt.Println (but similar to log.Println) the message will be print on
+// its own line.
+//
+// If the altscreen is active no output will be printed.
+func Println(args ...interface{}) Cmd {
+ return func() Msg {
+ return printLineMessage{
+ messageBody: fmt.Sprint(args...),
+ }
+ }
+}
+
+// Printf prints above the Program. It takes a format template followed by
+// values similar to fmt.Printf. This output is unmanaged by the program and
+// will persist across renders by the Program.
+//
+// Unlike fmt.Printf (but similar to log.Printf) the message will be print on
+// its own line.
+//
+// If the altscreen is active no output will be printed.
+func Printf(template string, args ...interface{}) Cmd {
+ return func() Msg {
+ return printLineMessage{
+ messageBody: fmt.Sprintf(template, args...),
+ }
+ }
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/tea.go b/vendor/github.com/charmbracelet/bubbletea/tea.go
new file mode 100644
index 0000000..f18cb87
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/tea.go
@@ -0,0 +1,716 @@
+// Package tea provides a framework for building rich terminal user interfaces
+// based on the paradigms of The Elm Architecture. It's well-suited for simple
+// and complex terminal applications, either inline, full-window, or a mix of
+// both. It's been battle-tested in several large projects and is
+// production-ready.
+//
+// A tutorial is available at https://github.com/charmbracelet/bubbletea/tree/master/tutorials
+//
+// Example programs can be found at https://github.com/charmbracelet/bubbletea/tree/master/examples
+package tea
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "os/signal"
+ "runtime/debug"
+ "sync"
+ "sync/atomic"
+ "syscall"
+
+ "github.com/containerd/console"
+ isatty "github.com/mattn/go-isatty"
+ "github.com/muesli/cancelreader"
+ "github.com/muesli/termenv"
+ "golang.org/x/sync/errgroup"
+)
+
+// ErrProgramKilled is returned by [Program.Run] when the program got killed.
+var ErrProgramKilled = errors.New("program was killed")
+
+// Msg contain data from the result of a IO operation. Msgs trigger the update
+// function and, henceforth, the UI.
+type Msg interface{}
+
+// Model contains the program's state as well as its core functions.
+type Model interface {
+ // Init is the first function that will be called. It returns an optional
+ // initial command. To not perform an initial command return nil.
+ Init() Cmd
+
+ // Update is called when a message is received. Use it to inspect messages
+ // and, in response, update the model and/or send a command.
+ Update(Msg) (Model, Cmd)
+
+ // View renders the program's UI, which is just a string. The view is
+ // rendered after every Update.
+ View() string
+}
+
+// Cmd is an IO operation that returns a message when it's complete. If it's
+// nil it's considered a no-op. Use it for things like HTTP requests, timers,
+// saving and loading from disk, and so on.
+//
+// Note that there's almost never a reason to use a command to send a message
+// to another part of your program. That can almost always be done in the
+// update function.
+type Cmd func() Msg
+
+type inputType int
+
+const (
+ defaultInput inputType = iota
+ ttyInput
+ customInput
+)
+
+// String implements the stringer interface for [inputType]. It is inteded to
+// be used in testing.
+func (i inputType) String() string {
+ return [...]string{
+ "default input",
+ "tty input",
+ "custom input",
+ }[i]
+}
+
+// Options to customize the program during its initialization. These are
+// generally set with ProgramOptions.
+//
+// The options here are treated as bits.
+type startupOptions byte
+
+func (s startupOptions) has(option startupOptions) bool {
+ return s&option != 0
+}
+
+const (
+ withAltScreen startupOptions = 1 << iota
+ withMouseCellMotion
+ withMouseAllMotion
+ withANSICompressor
+ withoutSignalHandler
+
+ // Catching panics is incredibly useful for restoring the terminal to a
+ // usable state after a panic occurs. When this is set, Bubble Tea will
+ // recover from panics, print the stack trace, and disable raw mode. This
+ // feature is on by default.
+ withoutCatchPanics
+)
+
+// handlers manages series of channels returned by various processes. It allows
+// us to wait for those processes to terminate before exiting the program.
+type handlers []chan struct{}
+
+// Adds a channel to the list of handlers. We wait for all handlers to terminate
+// gracefully on shutdown.
+func (h *handlers) add(ch chan struct{}) {
+ *h = append(*h, ch)
+}
+
+// shutdown waits for all handlers to terminate.
+func (h handlers) shutdown() {
+ var wg sync.WaitGroup
+ for _, ch := range h {
+ wg.Add(1)
+ go func(ch chan struct{}) {
+ <-ch
+ wg.Done()
+ }(ch)
+ }
+ wg.Wait()
+}
+
+// Program is a terminal user interface.
+type Program struct {
+ initialModel Model
+
+ // Configuration options that will set as the program is initializing,
+ // treated as bits. These options can be set via various ProgramOptions.
+ startupOptions startupOptions
+
+ inputType inputType
+
+ ctx context.Context
+ cancel context.CancelFunc
+
+ msgs chan Msg
+ errs chan error
+ finished chan struct{}
+
+ // where to send output, this will usually be os.Stdout.
+ output *termenv.Output
+ restoreOutput func() error
+ renderer renderer
+
+ // where to read inputs from, this will usually be os.Stdin.
+ input io.Reader
+ cancelReader cancelreader.CancelReader
+ readLoopDone chan struct{}
+ console console.Console
+
+ // was the altscreen active before releasing the terminal?
+ altScreenWasActive bool
+ ignoreSignals uint32
+
+ // Stores the original reference to stdin for cases where input is not a
+ // TTY on windows and we've automatically opened CONIN$ to receive input.
+ // When the program exits this will be restored.
+ //
+ // Lint ignore note: the linter will find false positive on unix systems
+ // as this value only comes into play on Windows, hence the ignore comment
+ // below.
+ windowsStdin *os.File //nolint:golint,structcheck,unused
+
+ filter func(Model, Msg) Msg
+
+ // fps is the frames per second we should set on the renderer, if
+ // applicable,
+ fps int
+}
+
+// Quit is a special command that tells the Bubble Tea program to exit.
+func Quit() Msg {
+ return QuitMsg{}
+}
+
+// QuitMsg signals that the program should quit. You can send a QuitMsg with
+// Quit.
+type QuitMsg struct{}
+
+// NewProgram creates a new Program.
+func NewProgram(model Model, opts ...ProgramOption) *Program {
+ p := &Program{
+ initialModel: model,
+ msgs: make(chan Msg),
+ }
+
+ // Apply all options to the program.
+ for _, opt := range opts {
+ opt(p)
+ }
+
+ // A context can be provided with a ProgramOption, but if none was provided
+ // we'll use the default background context.
+ if p.ctx == nil {
+ p.ctx = context.Background()
+ }
+ // Initialize context and teardown channel.
+ p.ctx, p.cancel = context.WithCancel(p.ctx)
+
+ // if no output was set, set it to stdout
+ if p.output == nil {
+ p.output = termenv.DefaultOutput()
+
+ // cache detected color values
+ termenv.WithColorCache(true)(p.output)
+ }
+
+ p.restoreOutput, _ = termenv.EnableVirtualTerminalProcessing(p.output)
+
+ return p
+}
+
+func (p *Program) handleSignals() chan struct{} {
+ ch := make(chan struct{})
+
+ // Listen for SIGINT and SIGTERM.
+ //
+ // In most cases ^C will not send an interrupt because the terminal will be
+ // in raw mode and ^C will be captured as a keystroke and sent along to
+ // Program.Update as a KeyMsg. When input is not a TTY, however, ^C will be
+ // caught here.
+ //
+ // SIGTERM is sent by unix utilities (like kill) to terminate a process.
+ go func() {
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
+ defer func() {
+ signal.Stop(sig)
+ close(ch)
+ }()
+
+ for {
+ select {
+ case <-p.ctx.Done():
+ return
+
+ case <-sig:
+ if atomic.LoadUint32(&p.ignoreSignals) == 0 {
+ p.msgs <- QuitMsg{}
+ return
+ }
+ }
+ }
+ }()
+
+ return ch
+}
+
+// handleResize handles terminal resize events.
+func (p *Program) handleResize() chan struct{} {
+ ch := make(chan struct{})
+
+ if f, ok := p.output.TTY().(*os.File); ok && isatty.IsTerminal(f.Fd()) {
+ // Get the initial terminal size and send it to the program.
+ go p.checkResize()
+
+ // Listen for window resizes.
+ go p.listenForResize(ch)
+ } else {
+ close(ch)
+ }
+
+ return ch
+}
+
+// handleCommands runs commands in a goroutine and sends the result to the
+// program's message channel.
+func (p *Program) handleCommands(cmds chan Cmd) chan struct{} {
+ ch := make(chan struct{})
+
+ go func() {
+ defer close(ch)
+
+ for {
+ select {
+ case <-p.ctx.Done():
+ return
+
+ case cmd := <-cmds:
+ if cmd == nil {
+ continue
+ }
+
+ // Don't wait on these goroutines, otherwise the shutdown
+ // latency would get too large as a Cmd can run for some time
+ // (e.g. tick commands that sleep for half a second). It's not
+ // possible to cancel them so we'll have to leak the goroutine
+ // until Cmd returns.
+ go func() {
+ msg := cmd() // this can be long.
+ p.Send(msg)
+ }()
+ }
+ }
+ }()
+
+ return ch
+}
+
+func (p *Program) disableMouse() {
+ p.renderer.disableMouseCellMotion()
+ p.renderer.disableMouseAllMotion()
+ p.renderer.disableMouseSGRMode()
+}
+
+// eventLoop is the central message loop. It receives and handles the default
+// Bubble Tea messages, update the model and triggers redraws.
+func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
+ for {
+ select {
+ case <-p.ctx.Done():
+ return model, nil
+
+ case err := <-p.errs:
+ return model, err
+
+ case msg := <-p.msgs:
+ // Filter messages.
+ if p.filter != nil {
+ msg = p.filter(model, msg)
+ }
+ if msg == nil {
+ continue
+ }
+
+ // Handle special internal messages.
+ switch msg := msg.(type) {
+ case QuitMsg:
+ return model, nil
+
+ case clearScreenMsg:
+ p.renderer.clearScreen()
+
+ case enterAltScreenMsg:
+ p.renderer.enterAltScreen()
+
+ case exitAltScreenMsg:
+ p.renderer.exitAltScreen()
+
+ case enableMouseCellMotionMsg, enableMouseAllMotionMsg:
+ switch msg.(type) {
+ case enableMouseCellMotionMsg:
+ p.renderer.enableMouseCellMotion()
+ case enableMouseAllMotionMsg:
+ p.renderer.enableMouseAllMotion()
+ }
+ // mouse mode (1006) is a no-op if the terminal doesn't support it.
+ p.renderer.enableMouseSGRMode()
+
+ case disableMouseMsg:
+ p.disableMouse()
+
+ case showCursorMsg:
+ p.renderer.showCursor()
+
+ case hideCursorMsg:
+ p.renderer.hideCursor()
+
+ case execMsg:
+ // NB: this blocks.
+ p.exec(msg.cmd, msg.fn)
+
+ case BatchMsg:
+ for _, cmd := range msg {
+ cmds <- cmd
+ }
+ continue
+
+ case sequenceMsg:
+ go func() {
+ // Execute commands one at a time, in order.
+ for _, cmd := range msg {
+ if cmd == nil {
+ continue
+ }
+
+ msg := cmd()
+ if batchMsg, ok := msg.(BatchMsg); ok {
+ g, _ := errgroup.WithContext(p.ctx)
+ for _, cmd := range batchMsg {
+ cmd := cmd
+ g.Go(func() error {
+ p.Send(cmd())
+ return nil
+ })
+ }
+
+ //nolint:errcheck
+ g.Wait() // wait for all commands from batch msg to finish
+ continue
+ }
+
+ p.Send(msg)
+ }
+ }()
+
+ case setWindowTitleMsg:
+ p.SetWindowTitle(string(msg))
+ }
+
+ // Process internal messages for the renderer.
+ if r, ok := p.renderer.(*standardRenderer); ok {
+ r.handleMessages(msg)
+ }
+
+ var cmd Cmd
+ model, cmd = model.Update(msg) // run update
+ cmds <- cmd // process command (if any)
+ p.renderer.write(model.View()) // send view to renderer
+ }
+ }
+}
+
+// Run initializes the program and runs its event loops, blocking until it gets
+// terminated by either [Program.Quit], [Program.Kill], or its signal handler.
+// Returns the final model.
+func (p *Program) Run() (Model, error) {
+ handlers := handlers{}
+ cmds := make(chan Cmd)
+ p.errs = make(chan error)
+ p.finished = make(chan struct{}, 1)
+
+ defer p.cancel()
+
+ switch p.inputType {
+ case defaultInput:
+ p.input = os.Stdin
+
+ // The user has not set a custom input, so we need to check whether or
+ // not standard input is a terminal. If it's not, we open a new TTY for
+ // input. This will allow things to "just work" in cases where data was
+ // piped in or redirected to the application.
+ //
+ // To disable input entirely pass nil to the [WithInput] program option.
+ f, isFile := p.input.(*os.File)
+ if !isFile {
+ break
+ }
+ if isatty.IsTerminal(f.Fd()) {
+ break
+ }
+
+ f, err := openInputTTY()
+ if err != nil {
+ return p.initialModel, err
+ }
+ defer f.Close() //nolint:errcheck
+ p.input = f
+
+ case ttyInput:
+ // Open a new TTY, by request
+ f, err := openInputTTY()
+ if err != nil {
+ return p.initialModel, err
+ }
+ defer f.Close() //nolint:errcheck
+ p.input = f
+
+ case customInput:
+ // (There is nothing extra to do.)
+ }
+
+ // Handle signals.
+ if !p.startupOptions.has(withoutSignalHandler) {
+ handlers.add(p.handleSignals())
+ }
+
+ // Recover from panics.
+ if !p.startupOptions.has(withoutCatchPanics) {
+ defer func() {
+ if r := recover(); r != nil {
+ p.shutdown(true)
+ fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r)
+ debug.PrintStack()
+ return
+ }
+ }()
+ }
+
+ // If no renderer is set use the standard one.
+ if p.renderer == nil {
+ p.renderer = newRenderer(p.output, p.startupOptions.has(withANSICompressor), p.fps)
+ }
+
+ // Check if output is a TTY before entering raw mode, hiding the cursor and
+ // so on.
+ if err := p.initTerminal(); err != nil {
+ return p.initialModel, err
+ }
+
+ // Honor program startup options.
+ if p.startupOptions&withAltScreen != 0 {
+ p.renderer.enterAltScreen()
+ }
+ if p.startupOptions&withMouseCellMotion != 0 {
+ p.renderer.enableMouseCellMotion()
+ p.renderer.enableMouseSGRMode()
+ } else if p.startupOptions&withMouseAllMotion != 0 {
+ p.renderer.enableMouseAllMotion()
+ p.renderer.enableMouseSGRMode()
+ }
+
+ // Initialize the program.
+ model := p.initialModel
+ if initCmd := model.Init(); initCmd != nil {
+ ch := make(chan struct{})
+ handlers.add(ch)
+
+ go func() {
+ defer close(ch)
+
+ select {
+ case cmds <- initCmd:
+ case <-p.ctx.Done():
+ }
+ }()
+ }
+
+ // Start the renderer.
+ p.renderer.start()
+
+ // Render the initial view.
+ p.renderer.write(model.View())
+
+ // Subscribe to user input.
+ if p.input != nil {
+ if err := p.initCancelReader(); err != nil {
+ return model, err
+ }
+ }
+
+ // Handle resize events.
+ handlers.add(p.handleResize())
+
+ // Process commands.
+ handlers.add(p.handleCommands(cmds))
+
+ // Run event loop, handle updates and draw.
+ model, err := p.eventLoop(model, cmds)
+ killed := p.ctx.Err() != nil
+ if killed {
+ err = ErrProgramKilled
+ } else {
+ // Ensure we rendered the final state of the model.
+ p.renderer.write(model.View())
+ }
+
+ // Tear down.
+ p.cancel()
+
+ // Check if the cancel reader has been setup before waiting and closing.
+ if p.cancelReader != nil {
+ // Wait for input loop to finish.
+ if p.cancelReader.Cancel() {
+ p.waitForReadLoop()
+ }
+ _ = p.cancelReader.Close()
+ }
+
+ // Wait for all handlers to finish.
+ handlers.shutdown()
+
+ // Restore terminal state.
+ p.shutdown(killed)
+
+ return model, err
+}
+
+// StartReturningModel initializes the program and runs its event loops,
+// blocking until it gets terminated by either [Program.Quit], [Program.Kill],
+// or its signal handler. Returns the final model.
+//
+// Deprecated: please use [Program.Run] instead.
+func (p *Program) StartReturningModel() (Model, error) {
+ return p.Run()
+}
+
+// Start initializes the program and runs its event loops, blocking until it
+// gets terminated by either [Program.Quit], [Program.Kill], or its signal
+// handler.
+//
+// Deprecated: please use [Program.Run] instead.
+func (p *Program) Start() error {
+ _, err := p.Run()
+ return err
+}
+
+// Send sends a message to the main update function, effectively allowing
+// messages to be injected from outside the program for interoperability
+// purposes.
+//
+// If the program hasn't started yet this will be a blocking operation.
+// If the program has already been terminated this will be a no-op, so it's safe
+// to send messages after the program has exited.
+func (p *Program) Send(msg Msg) {
+ select {
+ case <-p.ctx.Done():
+ case p.msgs <- msg:
+ }
+}
+
+// Quit is a convenience function for quitting Bubble Tea programs. Use it
+// when you need to shut down a Bubble Tea program from the outside.
+//
+// If you wish to quit from within a Bubble Tea program use the Quit command.
+//
+// If the program is not running this will be a no-op, so it's safe to call
+// if the program is unstarted or has already exited.
+func (p *Program) Quit() {
+ p.Send(Quit())
+}
+
+// Kill stops the program immediately and restores the former terminal state.
+// The final render that you would normally see when quitting will be skipped.
+// [program.Run] returns a [ErrProgramKilled] error.
+func (p *Program) Kill() {
+ p.cancel()
+}
+
+// Wait waits/blocks until the underlying Program finished shutting down.
+func (p *Program) Wait() {
+ <-p.finished
+}
+
+// shutdown performs operations to free up resources and restore the terminal
+// to its original state.
+func (p *Program) shutdown(kill bool) {
+ if p.renderer != nil {
+ if kill {
+ p.renderer.kill()
+ } else {
+ p.renderer.stop()
+ }
+ }
+
+ _ = p.restoreTerminalState()
+ if p.restoreOutput != nil {
+ _ = p.restoreOutput()
+ }
+ p.finished <- struct{}{}
+}
+
+// ReleaseTerminal restores the original terminal state and cancels the input
+// reader. You can return control to the Program with RestoreTerminal.
+func (p *Program) ReleaseTerminal() error {
+ atomic.StoreUint32(&p.ignoreSignals, 1)
+ p.cancelReader.Cancel()
+ p.waitForReadLoop()
+
+ if p.renderer != nil {
+ p.renderer.stop()
+ }
+
+ p.altScreenWasActive = p.renderer.altScreen()
+ return p.restoreTerminalState()
+}
+
+// RestoreTerminal reinitializes the Program's input reader, restores the
+// terminal to the former state when the program was running, and repaints.
+// Use it to reinitialize a Program after running ReleaseTerminal.
+func (p *Program) RestoreTerminal() error {
+ atomic.StoreUint32(&p.ignoreSignals, 0)
+
+ if err := p.initTerminal(); err != nil {
+ return err
+ }
+ if err := p.initCancelReader(); err != nil {
+ return err
+ }
+
+ if p.altScreenWasActive {
+ p.renderer.enterAltScreen()
+ } else {
+ // entering alt screen already causes a repaint.
+ go p.Send(repaintMsg{})
+ }
+ if p.renderer != nil {
+ p.renderer.start()
+ }
+
+ // If the output is a terminal, it may have been resized while another
+ // process was at the foreground, in which case we may not have received
+ // SIGWINCH. Detect any size change now and propagate the new size as
+ // needed.
+ go p.checkResize()
+
+ return nil
+}
+
+// Println prints above the Program. This output is unmanaged by the program
+// and will persist across renders by the Program.
+//
+// If the altscreen is active no output will be printed.
+func (p *Program) Println(args ...interface{}) {
+ p.msgs <- printLineMessage{
+ messageBody: fmt.Sprint(args...),
+ }
+}
+
+// Printf prints above the Program. It takes a format template followed by
+// values similar to fmt.Printf. This output is unmanaged by the program and
+// will persist across renders by the Program.
+//
+// Unlike fmt.Printf (but similar to log.Printf) the message will be print on
+// its own line.
+//
+// If the altscreen is active no output will be printed.
+func (p *Program) Printf(template string, args ...interface{}) {
+ p.msgs <- printLineMessage{
+ messageBody: fmt.Sprintf(template, args...),
+ }
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/tty.go b/vendor/github.com/charmbracelet/bubbletea/tty.go
new file mode 100644
index 0000000..01f084d
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/tty.go
@@ -0,0 +1,119 @@
+package tea
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "time"
+
+ isatty "github.com/mattn/go-isatty"
+ localereader "github.com/mattn/go-localereader"
+ "github.com/muesli/cancelreader"
+ "golang.org/x/term"
+)
+
+func (p *Program) initTerminal() error {
+ err := p.initInput()
+ if err != nil {
+ return err
+ }
+
+ if p.console != nil {
+ err = p.console.SetRaw()
+ if err != nil {
+ return fmt.Errorf("error entering raw mode: %w", err)
+ }
+ }
+
+ p.renderer.hideCursor()
+ return nil
+}
+
+// restoreTerminalState restores the terminal to the state prior to running the
+// Bubble Tea program.
+func (p *Program) restoreTerminalState() error {
+ if p.renderer != nil {
+ p.renderer.showCursor()
+ p.disableMouse()
+
+ if p.renderer.altScreen() {
+ p.renderer.exitAltScreen()
+
+ // give the terminal a moment to catch up
+ time.Sleep(time.Millisecond * 10) //nolint:gomnd
+ }
+ }
+
+ if p.console != nil {
+ err := p.console.Reset()
+ if err != nil {
+ return fmt.Errorf("error restoring terminal state: %w", err)
+ }
+ }
+
+ return p.restoreInput()
+}
+
+// initCancelReader (re)commences reading inputs.
+func (p *Program) initCancelReader() error {
+ var err error
+ p.cancelReader, err = cancelreader.NewReader(p.input)
+ if err != nil {
+ return fmt.Errorf("error creating cancelreader: %w", err)
+ }
+
+ p.readLoopDone = make(chan struct{})
+ go p.readLoop()
+
+ return nil
+}
+
+func (p *Program) readLoop() {
+ defer close(p.readLoopDone)
+
+ input := localereader.NewReader(p.cancelReader)
+ err := readInputs(p.ctx, p.msgs, input)
+ if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) {
+ select {
+ case <-p.ctx.Done():
+ case p.errs <- err:
+ }
+ }
+}
+
+// waitForReadLoop waits for the cancelReader to finish its read loop.
+func (p *Program) waitForReadLoop() {
+ select {
+ case <-p.readLoopDone:
+ case <-time.After(500 * time.Millisecond): //nolint:gomnd
+ // The read loop hangs, which means the input
+ // cancelReader's cancel function has returned true even
+ // though it was not able to cancel the read.
+ }
+}
+
+// checkResize detects the current size of the output and informs the program
+// via a WindowSizeMsg.
+func (p *Program) checkResize() {
+ f, ok := p.output.TTY().(*os.File)
+ if !ok || !isatty.IsTerminal(f.Fd()) {
+ // can't query window size
+ return
+ }
+
+ w, h, err := term.GetSize(int(f.Fd()))
+ if err != nil {
+ select {
+ case <-p.ctx.Done():
+ case p.errs <- err:
+ }
+
+ return
+ }
+
+ p.Send(WindowSizeMsg{
+ Width: w,
+ Height: h,
+ })
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/tty_unix.go b/vendor/github.com/charmbracelet/bubbletea/tty_unix.go
new file mode 100644
index 0000000..a3a25b8
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/tty_unix.go
@@ -0,0 +1,45 @@
+//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || aix
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris aix
+
+package tea
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/containerd/console"
+)
+
+func (p *Program) initInput() error {
+ // If input's a file, use console to manage it
+ if f, ok := p.input.(*os.File); ok {
+ c, err := console.ConsoleFromFile(f)
+ if err != nil {
+ return nil //nolint:nilerr // ignore error, this was just a test
+ }
+ p.console = c
+ }
+
+ return nil
+}
+
+// On unix systems, RestoreInput closes any TTYs we opened for input. Note that
+// we don't do this on Windows as it causes the prompt to not be drawn until
+// the terminal receives a keypress rather than appearing promptly after the
+// program exits.
+func (p *Program) restoreInput() error {
+ if p.console != nil {
+ if err := p.console.Reset(); err != nil {
+ return fmt.Errorf("error restoring console: %w", err)
+ }
+ }
+ return nil
+}
+
+func openInputTTY() (*os.File, error) {
+ f, err := os.Open("/dev/tty")
+ if err != nil {
+ return nil, fmt.Errorf("could not open a new TTY: %w", err)
+ }
+ return f, nil
+}
diff --git a/vendor/github.com/charmbracelet/bubbletea/tty_windows.go b/vendor/github.com/charmbracelet/bubbletea/tty_windows.go
new file mode 100644
index 0000000..be415ae
--- /dev/null
+++ b/vendor/github.com/charmbracelet/bubbletea/tty_windows.go
@@ -0,0 +1,47 @@
+//go:build windows
+// +build windows
+
+package tea
+
+import (
+ "os"
+
+ "github.com/containerd/console"
+)
+
+func (p *Program) initInput() error {
+ // If input's a file, use console to manage it
+ if f, ok := p.input.(*os.File); ok {
+ // Save a reference to the current stdin then replace stdin with our
+ // input. We do this so we can hand input off to containerd/console to
+ // set raw mode, and do it in this fashion because the method
+ // console.ConsoleFromFile isn't supported on Windows.
+ p.windowsStdin = os.Stdin
+ os.Stdin = f
+
+ // Note: this will panic if it fails.
+ c := console.Current()
+ p.console = c
+ }
+
+ return nil
+}
+
+// restoreInput restores stdout in the event that we placed it aside to handle
+// input with CONIN$, above.
+func (p *Program) restoreInput() error {
+ if p.windowsStdin != nil {
+ os.Stdin = p.windowsStdin
+ }
+
+ return nil
+}
+
+// Open the Windows equivalent of a TTY.
+func openInputTTY() (*os.File, error) {
+ f, err := os.OpenFile("CONIN$", os.O_RDWR, 0644)
+ if err != nil {
+ return nil, err
+ }
+ return f, nil
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/.gitignore b/vendor/github.com/charmbracelet/lipgloss/.gitignore
new file mode 100644
index 0000000..53e1c2b
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/.gitignore
@@ -0,0 +1 @@
+ssh_example_ed25519*
diff --git a/vendor/github.com/charmbracelet/lipgloss/.golangci-soft.yml b/vendor/github.com/charmbracelet/lipgloss/.golangci-soft.yml
new file mode 100644
index 0000000..ef456e0
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/.golangci-soft.yml
@@ -0,0 +1,47 @@
+run:
+ tests: false
+
+issues:
+ include:
+ - EXC0001
+ - EXC0005
+ - EXC0011
+ - EXC0012
+ - EXC0013
+
+ max-issues-per-linter: 0
+ max-same-issues: 0
+
+linters:
+ enable:
+ # - dupl
+ - exhaustive
+ # - exhaustivestruct
+ - goconst
+ - godot
+ - godox
+ - gomnd
+ - gomoddirectives
+ - goprintffuncname
+ - ifshort
+ # - lll
+ - misspell
+ - nakedret
+ - nestif
+ - noctx
+ - nolintlint
+ - prealloc
+ - wrapcheck
+
+ # disable default linters, they are already enabled in .golangci.yml
+ disable:
+ - deadcode
+ - errcheck
+ - gosimple
+ - govet
+ - ineffassign
+ - staticcheck
+ - structcheck
+ - typecheck
+ - unused
+ - varcheck
diff --git a/vendor/github.com/charmbracelet/lipgloss/.golangci.yml b/vendor/github.com/charmbracelet/lipgloss/.golangci.yml
new file mode 100644
index 0000000..a5a91d0
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/.golangci.yml
@@ -0,0 +1,29 @@
+run:
+ tests: false
+
+issues:
+ include:
+ - EXC0001
+ - EXC0005
+ - EXC0011
+ - EXC0012
+ - EXC0013
+
+ max-issues-per-linter: 0
+ max-same-issues: 0
+
+linters:
+ enable:
+ - bodyclose
+ - exportloopref
+ - goimports
+ - gosec
+ - nilerr
+ - predeclared
+ - revive
+ - rowserrcheck
+ - sqlclosecheck
+ - tparallel
+ - unconvert
+ - unparam
+ - whitespace
diff --git a/vendor/github.com/charmbracelet/lipgloss/LICENSE b/vendor/github.com/charmbracelet/lipgloss/LICENSE
new file mode 100644
index 0000000..6f5b1fa
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021-2023 Charmbracelet, Inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/charmbracelet/lipgloss/README.md b/vendor/github.com/charmbracelet/lipgloss/README.md
new file mode 100644
index 0000000..f3aa745
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/README.md
@@ -0,0 +1,562 @@
+Lip Gloss
+=========
+
+
+ 
+
+
+
+
+
+Style definitions for nice terminal layouts. Built with TUIs in mind.
+
+
+
+Lip Gloss takes an expressive, declarative approach to terminal rendering.
+Users familiar with CSS will feel at home with Lip Gloss.
+
+```go
+
+import "github.com/charmbracelet/lipgloss"
+
+var style = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("#FAFAFA")).
+ Background(lipgloss.Color("#7D56F4")).
+ PaddingTop(2).
+ PaddingLeft(4).
+ Width(22)
+
+fmt.Println(style.Render("Hello, kitty"))
+```
+
+## Colors
+
+Lip Gloss supports the following color profiles:
+
+### ANSI 16 colors (4-bit)
+
+```go
+lipgloss.Color("5") // magenta
+lipgloss.Color("9") // red
+lipgloss.Color("12") // light blue
+```
+
+### ANSI 256 Colors (8-bit)
+
+```go
+lipgloss.Color("86") // aqua
+lipgloss.Color("201") // hot pink
+lipgloss.Color("202") // orange
+```
+
+### True Color (16,777,216 colors; 24-bit)
+
+```go
+lipgloss.Color("#0000FF") // good ol' 100% blue
+lipgloss.Color("#04B575") // a green
+lipgloss.Color("#3C3C3C") // a dark gray
+```
+
+...as well as a 1-bit ASCII profile, which is black and white only.
+
+The terminal's color profile will be automatically detected, and colors outside
+the gamut of the current palette will be automatically coerced to their closest
+available value.
+
+
+### Adaptive Colors
+
+You can also specify color options for light and dark backgrounds:
+
+```go
+lipgloss.AdaptiveColor{Light: "236", Dark: "248"}
+```
+
+The terminal's background color will automatically be detected and the
+appropriate color will be chosen at runtime.
+
+### Complete Colors
+
+CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color
+profiles.
+
+```go
+lipgloss.CompleteColor{True: "#0000FF", ANSI256: "86", ANSI: "5"}
+```
+
+Automatic color degradation will not be performed in this case and it will be
+based on the color specified.
+
+### Complete Adaptive Colors
+
+You can use CompleteColor with AdaptiveColor to specify the exact values for
+light and dark backgrounds without automatic color degradation.
+
+```go
+lipgloss.CompleteAdaptiveColor{
+ Light: CompleteColor{TrueColor: "#d7ffae", ANSI256: "193", ANSI: "11"},
+ Dark: CompleteColor{TrueColor: "#d75fee", ANSI256: "163", ANSI: "5"},
+}
+```
+
+## Inline Formatting
+
+Lip Gloss supports the usual ANSI text formatting options:
+
+```go
+var style = lipgloss.NewStyle().
+ Bold(true).
+ Italic(true).
+ Faint(true).
+ Blink(true).
+ Strikethrough(true).
+ Underline(true).
+ Reverse(true)
+```
+
+
+## Block-Level Formatting
+
+Lip Gloss also supports rules for block-level formatting:
+
+```go
+// Padding
+var style = lipgloss.NewStyle().
+ PaddingTop(2).
+ PaddingRight(4).
+ PaddingBottom(2).
+ PaddingLeft(4)
+
+// Margins
+var style = lipgloss.NewStyle().
+ MarginTop(2).
+ MarginRight(4).
+ MarginBottom(2).
+ MarginLeft(4)
+```
+
+There is also shorthand syntax for margins and padding, which follows the same
+format as CSS:
+
+```go
+// 2 cells on all sides
+lipgloss.NewStyle().Padding(2)
+
+// 2 cells on the top and bottom, 4 cells on the left and right
+lipgloss.NewStyle().Margin(2, 4)
+
+// 1 cell on the top, 4 cells on the sides, 2 cells on the bottom
+lipgloss.NewStyle().Padding(1, 4, 2)
+
+// Clockwise, starting from the top: 2 cells on the top, 4 on the right, 3 on
+// the bottom, and 1 on the left
+lipgloss.NewStyle().Margin(2, 4, 3, 1)
+```
+
+
+## Aligning Text
+
+You can align paragraphs of text to the left, right, or center.
+
+```go
+var style = lipgloss.NewStyle().
+ Width(24).
+ Align(lipgloss.Left). // align it left
+ Align(lipgloss.Right). // no wait, align it right
+ Align(lipgloss.Center) // just kidding, align it in the center
+```
+
+
+## Width and Height
+
+Setting a minimum width and height is simple and straightforward.
+
+```go
+var style = lipgloss.NewStyle().
+ SetString("What’s for lunch?").
+ Width(24).
+ Height(32).
+ Foreground(lipgloss.Color("63"))
+```
+
+
+## Borders
+
+Adding borders is easy:
+
+```go
+// Add a purple, rectangular border
+var style = lipgloss.NewStyle().
+ BorderStyle(lipgloss.NormalBorder()).
+ BorderForeground(lipgloss.Color("63"))
+
+// Set a rounded, yellow-on-purple border to the top and left
+var anotherStyle = lipgloss.NewStyle().
+ BorderStyle(lipgloss.RoundedBorder()).
+ BorderForeground(lipgloss.Color("228")).
+ BorderBackground(lipgloss.Color("63")).
+ BorderTop(true).
+ BorderLeft(true)
+
+// Make your own border
+var myCuteBorder = lipgloss.Border{
+ Top: "._.:*:",
+ Bottom: "._.:*:",
+ Left: "|*",
+ Right: "|*",
+ TopLeft: "*",
+ TopRight: "*",
+ BottomLeft: "*",
+ BottomRight: "*",
+}
+```
+
+There are also shorthand functions for defining borders, which follow a similar
+pattern to the margin and padding shorthand functions.
+
+```go
+// Add a thick border to the top and bottom
+lipgloss.NewStyle().
+ Border(lipgloss.ThickBorder(), true, false)
+
+// Add a thick border to the right and bottom sides. Rules are set clockwise
+// from top.
+lipgloss.NewStyle().
+ Border(lipgloss.DoubleBorder(), true, false, false, true)
+```
+
+For more on borders see [the docs][docs].
+
+
+## Copying Styles
+
+Just use `Copy()`:
+
+```go
+var style = lipgloss.NewStyle().Foreground(lipgloss.Color("219"))
+
+var wildStyle = style.Copy().Blink(true)
+```
+
+`Copy()` performs a copy on the underlying data structure ensuring that you get
+a true, dereferenced copy of a style. Without copying, it's possible to mutate
+styles.
+
+
+## Inheritance
+
+Styles can inherit rules from other styles. When inheriting, only unset rules
+on the receiver are inherited.
+
+```go
+var styleA = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("229")).
+ Background(lipgloss.Color("63"))
+
+// Only the background color will be inherited here, because the foreground
+// color will have been already set:
+var styleB = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("201")).
+ Inherit(styleA)
+```
+
+
+## Unsetting Rules
+
+All rules can be unset:
+
+```go
+var style = lipgloss.NewStyle().
+ Bold(true). // make it bold
+ UnsetBold(). // jk don't make it bold
+ Background(lipgloss.Color("227")). // yellow background
+ UnsetBackground() // never mind
+```
+
+When a rule is unset, it won't be inherited or copied.
+
+
+## Enforcing Rules
+
+Sometimes, such as when developing a component, you want to make sure style
+definitions respect their intended purpose in the UI. This is where `Inline`
+and `MaxWidth`, and `MaxHeight` come in:
+
+```go
+// Force rendering onto a single line, ignoring margins, padding, and borders.
+someStyle.Inline(true).Render("yadda yadda")
+
+// Also limit rendering to five cells
+someStyle.Inline(true).MaxWidth(5).Render("yadda yadda")
+
+// Limit rendering to a 5x5 cell block
+someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda")
+```
+
+## Tabs
+
+The tab character (`\t`) is rendered differently in different terminals (often
+as 8 spaces, sometimes 4). Because of this inconsistency, Lip Gloss converts
+tabs to 4 spaces at render time. This behavior can be changed on a per-style
+basis, however:
+
+```go
+style := lipgloss.NewStyle() // tabs will render as 4 spaces, the default
+style = style.TabWidth(2) // render tabs as 2 spaces
+style = style.TabWidth(0) // remove tabs entirely
+style = style.TabWidth(lipgloss.NoTabConversion) // leave tabs intact
+```
+
+## Rendering
+
+Generally, you just call the `Render(string...)` method on a `lipgloss.Style`:
+
+```go
+style := lipgloss.NewStyle().Bold(true).SetString("Hello,")
+fmt.Println(style.Render("kitty.")) // Hello, kitty.
+fmt.Println(style.Render("puppy.")) // Hello, puppy.
+```
+
+But you could also use the Stringer interface:
+
+```go
+var style = lipgloss.NewStyle().SetString("你好,猫咪。").Bold(true)
+fmt.Println(style) // 你好,猫咪。
+```
+
+### Custom Renderers
+
+Custom renderers allow you to render to a specific outputs. This is
+particularly important when you want to render to different outputs and
+correctly detect the color profile and dark background status for each, such as
+in a server-client situation.
+
+```go
+func myLittleHandler(sess ssh.Session) {
+ // Create a renderer for the client.
+ renderer := lipgloss.NewRenderer(sess)
+
+ // Create a new style on the renderer.
+ style := renderer.NewStyle().Background(lipgloss.AdaptiveColor{Light: "63", Dark: "228"})
+
+ // Render. The color profile and dark background state will be correctly detected.
+ io.WriteString(sess, style.Render("Heyyyyyyy"))
+}
+```
+
+For an example on using a custom renderer over SSH with [Wish][wish] see the
+[SSH example][ssh-example].
+
+## Utilities
+
+In addition to pure styling, Lip Gloss also ships with some utilities to help
+assemble your layouts.
+
+
+### Joining Paragraphs
+
+Horizontally and vertically joining paragraphs is a cinch.
+
+```go
+// Horizontally join three paragraphs along their bottom edges
+lipgloss.JoinHorizontal(lipgloss.Bottom, paragraphA, paragraphB, paragraphC)
+
+// Vertically join two paragraphs along their center axes
+lipgloss.JoinVertical(lipgloss.Center, paragraphA, paragraphB)
+
+// Horizontally join three paragraphs, with the shorter ones aligning 20%
+// from the top of the tallest
+lipgloss.JoinHorizontal(0.2, paragraphA, paragraphB, paragraphC)
+```
+
+
+### Measuring Width and Height
+
+Sometimes you’ll want to know the width and height of text blocks when building
+your layouts.
+
+```go
+// Render a block of text.
+var style = lipgloss.NewStyle().
+ Width(40).
+ Padding(2)
+var block string = style.Render(someLongString)
+
+// Get the actual, physical dimensions of the text block.
+width := lipgloss.Width(block)
+height := lipgloss.Height(block)
+
+// Here's a shorthand function.
+w, h := lipgloss.Size(block)
+```
+
+### Placing Text in Whitespace
+
+Sometimes you’ll simply want to place a block of text in whitespace.
+
+```go
+// Center a paragraph horizontally in a space 80 cells wide. The height of
+// the block returned will be as tall as the input paragraph.
+block := lipgloss.PlaceHorizontal(80, lipgloss.Center, fancyStyledParagraph)
+
+// Place a paragraph at the bottom of a space 30 cells tall. The width of
+// the text block returned will be as wide as the input paragraph.
+block := lipgloss.PlaceVertical(30, lipgloss.Bottom, fancyStyledParagraph)
+
+// Place a paragraph in the bottom right corner of a 30x80 cell space.
+block := lipgloss.Place(30, 80, lipgloss.Right, lipgloss.Bottom, fancyStyledParagraph)
+```
+
+You can also style the whitespace. For details, see [the docs][docs].
+
+### Rendering Tables
+
+Lip Gloss ships with a table rendering sub-package.
+
+```go
+import "github.com/charmbracelet/lipgloss/table"
+```
+
+Define some rows of data.
+
+```go
+rows := [][]string{
+ {"Chinese", "您好", "你好"},
+ {"Japanese", "こんにちは", "やあ"},
+ {"Arabic", "أهلين", "أهلا"},
+ {"Russian", "Здравствуйте", "Привет"},
+ {"Spanish", "Hola", "¿Qué tal?"},
+}
+```
+
+Use the table package to style and render the table.
+
+```go
+t := table.New().
+ Border(lipgloss.NormalBorder()).
+ BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))).
+ StyleFunc(func(row, col int) lipgloss.Style {
+ switch {
+ case row == 0:
+ return HeaderStyle
+ case row%2 == 0:
+ return EvenRowStyle
+ default:
+ return OddRowStyle
+ }
+ }).
+ Headers("LANGUAGE", "FORMAL", "INFORMAL").
+ Rows(rows...)
+
+// You can also add tables row-by-row
+t.Row("English", "You look absolutely fabulous.", "How's it going?")
+```
+
+Print the table.
+
+```go
+fmt.Println(t)
+```
+
+
+
+For more on tables see [the docs](https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc).
+
+***
+
+## FAQ
+
+
+
+Why are things misaligning? Why are borders at the wrong widths?
+
+This is most likely due to your locale and encoding, particularly with
+regard to Chinese, Japanese, and Korean (for example, zh_CN.UTF-8
+or ja_JP.UTF-8
). The most direct way to fix this is to set
+RUNEWIDTH_EASTASIAN=0
in your environment.
+
+For details see https://github.com/charmbracelet/lipgloss/issues/40.
+
+
+
+
+Why isn't Lip Gloss displaying colors?
+
+Lip Gloss automatically degrades colors to the best available option in the
+given terminal, and if output's not a TTY it will remove color output entirely.
+This is common when running tests, CI, or when piping output elsewhere.
+
+If necessary, you can force a color profile in your tests with
+SetColorProfile
.
+
+```go
+import (
+ "github.com/charmbracelet/lipgloss"
+ "github.com/muesli/termenv"
+)
+
+lipgloss.SetColorProfile(termenv.TrueColor)
+```
+
+*Note:* this option limits the flexibility of your application and can cause
+ANSI escape codes to be output in cases where that might not be desired. Take
+careful note of your use case and environment before choosing to force a color
+profile.
+
+
+## What about [Bubble Tea][tea]?
+
+Lip Gloss doesn’t replace Bubble Tea. Rather, it is an excellent Bubble Tea
+companion. It was designed to make assembling terminal user interface views as
+simple and fun as possible so that you can focus on building your application
+instead of concerning yourself with low-level layout details.
+
+In simple terms, you can use Lip Gloss to help build your Bubble Tea views.
+
+[tea]: https://github.com/charmbracelet/tea
+
+
+## Under the Hood
+
+Lip Gloss is built on the excellent [Termenv][termenv] and [Reflow][reflow]
+libraries which deal with color and ANSI-aware text operations, respectively.
+For many use cases Termenv and Reflow will be sufficient for your needs.
+
+[termenv]: https://github.com/muesli/termenv
+[reflow]: https://github.com/muesli/reflow
+
+
+## Rendering Markdown
+
+For a more document-centric rendering solution with support for things like
+lists, tables, and syntax-highlighted code have a look at [Glamour][glamour],
+the stylesheet-based Markdown renderer.
+
+[glamour]: https://github.com/charmbracelet/glamour
+
+
+## Feedback
+
+We’d love to hear your thoughts on this project. Feel free to drop us a note!
+
+* [Twitter](https://twitter.com/charmcli)
+* [The Fediverse](https://mastodon.social/@charmcli)
+* [Discord](https://charm.sh/chat)
+
+## License
+
+[MIT](https://github.com/charmbracelet/lipgloss/raw/master/LICENSE)
+
+***
+
+Part of [Charm](https://charm.sh).
+
+
+
+Charm热爱开源 • Charm loves open source
+
+
+[docs]: https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc
+[wish]: https://github.com/charmbracelet/wish
+[ssh-example]: examples/ssh
diff --git a/vendor/github.com/charmbracelet/lipgloss/align.go b/vendor/github.com/charmbracelet/lipgloss/align.go
new file mode 100644
index 0000000..6c0fb2d
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/align.go
@@ -0,0 +1,83 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/muesli/reflow/ansi"
+ "github.com/muesli/termenv"
+)
+
+// Perform text alignment. If the string is multi-lined, we also make all lines
+// the same width by padding them with spaces. If a termenv style is passed,
+// use that to style the spaces added.
+func alignTextHorizontal(str string, pos Position, width int, style *termenv.Style) string {
+ lines, widestLine := getLines(str)
+ var b strings.Builder
+
+ for i, l := range lines {
+ lineWidth := ansi.PrintableRuneWidth(l)
+
+ shortAmount := widestLine - lineWidth // difference from the widest line
+ shortAmount += max(0, width-(shortAmount+lineWidth)) // difference from the total width, if set
+
+ if shortAmount > 0 {
+ switch pos { //nolint:exhaustive
+ case Right:
+ s := strings.Repeat(" ", shortAmount)
+ if style != nil {
+ s = style.Styled(s)
+ }
+ l = s + l
+ case Center:
+ // Note: remainder goes on the right.
+ left := shortAmount / 2 //nolint:gomnd
+ right := left + shortAmount%2 //nolint:gomnd
+
+ leftSpaces := strings.Repeat(" ", left)
+ rightSpaces := strings.Repeat(" ", right)
+
+ if style != nil {
+ leftSpaces = style.Styled(leftSpaces)
+ rightSpaces = style.Styled(rightSpaces)
+ }
+ l = leftSpaces + l + rightSpaces
+ default: // Left
+ s := strings.Repeat(" ", shortAmount)
+ if style != nil {
+ s = style.Styled(s)
+ }
+ l += s
+ }
+ }
+
+ b.WriteString(l)
+ if i < len(lines)-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ return b.String()
+}
+
+func alignTextVertical(str string, pos Position, height int, _ *termenv.Style) string {
+ strHeight := strings.Count(str, "\n") + 1
+ if height < strHeight {
+ return str
+ }
+
+ switch pos {
+ case Top:
+ return str + strings.Repeat("\n", height-strHeight)
+ case Center:
+ var topPadding, bottomPadding = (height - strHeight) / 2, (height - strHeight) / 2
+ if strHeight+topPadding+bottomPadding > height {
+ topPadding--
+ } else if strHeight+topPadding+bottomPadding < height {
+ bottomPadding++
+ }
+ return strings.Repeat("\n", topPadding) + str + strings.Repeat("\n", bottomPadding)
+ case Bottom:
+ return strings.Repeat("\n", height-strHeight) + str
+ }
+ return str
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/ansi_unix.go b/vendor/github.com/charmbracelet/lipgloss/ansi_unix.go
new file mode 100644
index 0000000..d416b8c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/ansi_unix.go
@@ -0,0 +1,7 @@
+//go:build !windows
+// +build !windows
+
+package lipgloss
+
+// enableLegacyWindowsANSI is only needed on Windows.
+func enableLegacyWindowsANSI() {}
diff --git a/vendor/github.com/charmbracelet/lipgloss/ansi_windows.go b/vendor/github.com/charmbracelet/lipgloss/ansi_windows.go
new file mode 100644
index 0000000..0cf56e4
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/ansi_windows.go
@@ -0,0 +1,22 @@
+//go:build windows
+// +build windows
+
+package lipgloss
+
+import (
+ "sync"
+
+ "github.com/muesli/termenv"
+)
+
+var enableANSI sync.Once
+
+// enableANSIColors enables support for ANSI color sequences in the Windows
+// default console (cmd.exe and the PowerShell application). Note that this
+// only works with Windows 10. Also note that Windows Terminal supports colors
+// by default.
+func enableLegacyWindowsANSI() {
+ enableANSI.Do(func() {
+ _, _ = termenv.EnableWindowsANSIConsole()
+ })
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/borders.go b/vendor/github.com/charmbracelet/lipgloss/borders.go
new file mode 100644
index 0000000..f244b53
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/borders.go
@@ -0,0 +1,442 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/mattn/go-runewidth"
+ "github.com/muesli/reflow/ansi"
+ "github.com/muesli/termenv"
+)
+
+// Border contains a series of values which comprise the various parts of a
+// border.
+type Border struct {
+ Top string
+ Bottom string
+ Left string
+ Right string
+ TopLeft string
+ TopRight string
+ BottomLeft string
+ BottomRight string
+ MiddleLeft string
+ MiddleRight string
+ Middle string
+ MiddleTop string
+ MiddleBottom string
+}
+
+// GetTopSize returns the width of the top border. If borders contain runes of
+// varying widths, the widest rune is returned. If no border exists on the top
+// edge, 0 is returned.
+func (b Border) GetTopSize() int {
+ return getBorderEdgeWidth(b.TopLeft, b.Top, b.TopRight)
+}
+
+// GetRightSize returns the width of the right border. If borders contain
+// runes of varying widths, the widest rune is returned. If no border exists on
+// the right edge, 0 is returned.
+func (b Border) GetRightSize() int {
+ return getBorderEdgeWidth(b.TopRight, b.Right, b.BottomRight)
+}
+
+// GetBottomSize returns the width of the bottom border. If borders contain
+// runes of varying widths, the widest rune is returned. If no border exists on
+// the bottom edge, 0 is returned.
+func (b Border) GetBottomSize() int {
+ return getBorderEdgeWidth(b.BottomLeft, b.Bottom, b.BottomRight)
+}
+
+// GetLeftSize returns the width of the left border. If borders contain runes
+// of varying widths, the widest rune is returned. If no border exists on the
+// left edge, 0 is returned.
+func (b Border) GetLeftSize() int {
+ return getBorderEdgeWidth(b.TopLeft, b.Left, b.BottomLeft)
+}
+
+func getBorderEdgeWidth(borderParts ...string) (maxWidth int) {
+ for _, piece := range borderParts {
+ w := maxRuneWidth(piece)
+ if w > maxWidth {
+ maxWidth = w
+ }
+ }
+ return maxWidth
+}
+
+var (
+ noBorder = Border{}
+
+ normalBorder = Border{
+ Top: "─",
+ Bottom: "─",
+ Left: "│",
+ Right: "│",
+ TopLeft: "┌",
+ TopRight: "┐",
+ BottomLeft: "└",
+ BottomRight: "┘",
+ MiddleLeft: "├",
+ MiddleRight: "┤",
+ Middle: "┼",
+ MiddleTop: "┬",
+ MiddleBottom: "┴",
+ }
+
+ roundedBorder = Border{
+ Top: "─",
+ Bottom: "─",
+ Left: "│",
+ Right: "│",
+ TopLeft: "╭",
+ TopRight: "╮",
+ BottomLeft: "╰",
+ BottomRight: "╯",
+ MiddleLeft: "├",
+ MiddleRight: "┤",
+ Middle: "┼",
+ MiddleTop: "┬",
+ MiddleBottom: "┴",
+ }
+
+ blockBorder = Border{
+ Top: "█",
+ Bottom: "█",
+ Left: "█",
+ Right: "█",
+ TopLeft: "█",
+ TopRight: "█",
+ BottomLeft: "█",
+ BottomRight: "█",
+ }
+
+ outerHalfBlockBorder = Border{
+ Top: "▀",
+ Bottom: "▄",
+ Left: "▌",
+ Right: "▐",
+ TopLeft: "▛",
+ TopRight: "▜",
+ BottomLeft: "▙",
+ BottomRight: "▟",
+ }
+
+ innerHalfBlockBorder = Border{
+ Top: "▄",
+ Bottom: "▀",
+ Left: "▐",
+ Right: "▌",
+ TopLeft: "▗",
+ TopRight: "▖",
+ BottomLeft: "▝",
+ BottomRight: "▘",
+ }
+
+ thickBorder = Border{
+ Top: "━",
+ Bottom: "━",
+ Left: "┃",
+ Right: "┃",
+ TopLeft: "┏",
+ TopRight: "┓",
+ BottomLeft: "┗",
+ BottomRight: "┛",
+ MiddleLeft: "┣",
+ MiddleRight: "┫",
+ Middle: "╋",
+ MiddleTop: "┳",
+ MiddleBottom: "┻",
+ }
+
+ doubleBorder = Border{
+ Top: "═",
+ Bottom: "═",
+ Left: "║",
+ Right: "║",
+ TopLeft: "╔",
+ TopRight: "╗",
+ BottomLeft: "╚",
+ BottomRight: "╝",
+ MiddleLeft: "╠",
+ MiddleRight: "╣",
+ Middle: "╬",
+ MiddleTop: "╦",
+ MiddleBottom: "╩",
+ }
+
+ hiddenBorder = Border{
+ Top: " ",
+ Bottom: " ",
+ Left: " ",
+ Right: " ",
+ TopLeft: " ",
+ TopRight: " ",
+ BottomLeft: " ",
+ BottomRight: " ",
+ MiddleLeft: " ",
+ MiddleRight: " ",
+ Middle: " ",
+ MiddleTop: " ",
+ MiddleBottom: " ",
+ }
+)
+
+// NormalBorder returns a standard-type border with a normal weight and 90
+// degree corners.
+func NormalBorder() Border {
+ return normalBorder
+}
+
+// RoundedBorder returns a border with rounded corners.
+func RoundedBorder() Border {
+ return roundedBorder
+}
+
+// BlockBorder returns a border that takes the whole block.
+func BlockBorder() Border {
+ return blockBorder
+}
+
+// OuterHalfBlockBorder returns a half-block border that sits outside the frame.
+func OuterHalfBlockBorder() Border {
+ return outerHalfBlockBorder
+}
+
+// InnerHalfBlockBorder returns a half-block border that sits inside the frame.
+func InnerHalfBlockBorder() Border {
+ return innerHalfBlockBorder
+}
+
+// ThickBorder returns a border that's thicker than the one returned by
+// NormalBorder.
+func ThickBorder() Border {
+ return thickBorder
+}
+
+// DoubleBorder returns a border comprised of two thin strokes.
+func DoubleBorder() Border {
+ return doubleBorder
+}
+
+// HiddenBorder returns a border that renders as a series of single-cell
+// spaces. It's useful for cases when you want to remove a standard border but
+// maintain layout positioning. This said, you can still apply a background
+// color to a hidden border.
+func HiddenBorder() Border {
+ return hiddenBorder
+}
+
+func (s Style) applyBorder(str string) string {
+ var (
+ topSet = s.isSet(borderTopKey)
+ rightSet = s.isSet(borderRightKey)
+ bottomSet = s.isSet(borderBottomKey)
+ leftSet = s.isSet(borderLeftKey)
+
+ border = s.getBorderStyle()
+ hasTop = s.getAsBool(borderTopKey, false)
+ hasRight = s.getAsBool(borderRightKey, false)
+ hasBottom = s.getAsBool(borderBottomKey, false)
+ hasLeft = s.getAsBool(borderLeftKey, false)
+
+ topFG = s.getAsColor(borderTopForegroundKey)
+ rightFG = s.getAsColor(borderRightForegroundKey)
+ bottomFG = s.getAsColor(borderBottomForegroundKey)
+ leftFG = s.getAsColor(borderLeftForegroundKey)
+
+ topBG = s.getAsColor(borderTopBackgroundKey)
+ rightBG = s.getAsColor(borderRightBackgroundKey)
+ bottomBG = s.getAsColor(borderBottomBackgroundKey)
+ leftBG = s.getAsColor(borderLeftBackgroundKey)
+ )
+
+ // If a border is set and no sides have been specifically turned on or off
+ // render borders on all sides.
+ if border != noBorder && !(topSet || rightSet || bottomSet || leftSet) {
+ hasTop = true
+ hasRight = true
+ hasBottom = true
+ hasLeft = true
+ }
+
+ // If no border is set or all borders are been disabled, abort.
+ if border == noBorder || (!hasTop && !hasRight && !hasBottom && !hasLeft) {
+ return str
+ }
+
+ lines, width := getLines(str)
+
+ if hasLeft {
+ if border.Left == "" {
+ border.Left = " "
+ }
+ width += maxRuneWidth(border.Left)
+ }
+
+ if hasRight && border.Right == "" {
+ border.Right = " "
+ }
+
+ // If corners should be rendered but are set with the empty string, fill them
+ // with a single space.
+ if hasTop && hasLeft && border.TopLeft == "" {
+ border.TopLeft = " "
+ }
+ if hasTop && hasRight && border.TopRight == "" {
+ border.TopRight = " "
+ }
+ if hasBottom && hasLeft && border.BottomLeft == "" {
+ border.BottomLeft = " "
+ }
+ if hasBottom && hasRight && border.BottomRight == "" {
+ border.BottomRight = " "
+ }
+
+ // Figure out which corners we should actually be using based on which
+ // sides are set to show.
+ if hasTop {
+ switch {
+ case !hasLeft && !hasRight:
+ border.TopLeft = ""
+ border.TopRight = ""
+ case !hasLeft:
+ border.TopLeft = ""
+ case !hasRight:
+ border.TopRight = ""
+ }
+ }
+ if hasBottom {
+ switch {
+ case !hasLeft && !hasRight:
+ border.BottomLeft = ""
+ border.BottomRight = ""
+ case !hasLeft:
+ border.BottomLeft = ""
+ case !hasRight:
+ border.BottomRight = ""
+ }
+ }
+
+ // For now, limit corners to one rune.
+ border.TopLeft = getFirstRuneAsString(border.TopLeft)
+ border.TopRight = getFirstRuneAsString(border.TopRight)
+ border.BottomRight = getFirstRuneAsString(border.BottomRight)
+ border.BottomLeft = getFirstRuneAsString(border.BottomLeft)
+
+ var out strings.Builder
+
+ // Render top
+ if hasTop {
+ top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width)
+ top = s.styleBorder(top, topFG, topBG)
+ out.WriteString(top)
+ out.WriteRune('\n')
+ }
+
+ leftRunes := []rune(border.Left)
+ leftIndex := 0
+
+ rightRunes := []rune(border.Right)
+ rightIndex := 0
+
+ // Render sides
+ for i, l := range lines {
+ if hasLeft {
+ r := string(leftRunes[leftIndex])
+ leftIndex++
+ if leftIndex >= len(leftRunes) {
+ leftIndex = 0
+ }
+ out.WriteString(s.styleBorder(r, leftFG, leftBG))
+ }
+ out.WriteString(l)
+ if hasRight {
+ r := string(rightRunes[rightIndex])
+ rightIndex++
+ if rightIndex >= len(rightRunes) {
+ rightIndex = 0
+ }
+ out.WriteString(s.styleBorder(r, rightFG, rightBG))
+ }
+ if i < len(lines)-1 {
+ out.WriteRune('\n')
+ }
+ }
+
+ // Render bottom
+ if hasBottom {
+ bottom := renderHorizontalEdge(border.BottomLeft, border.Bottom, border.BottomRight, width)
+ bottom = s.styleBorder(bottom, bottomFG, bottomBG)
+ out.WriteRune('\n')
+ out.WriteString(bottom)
+ }
+
+ return out.String()
+}
+
+// Render the horizontal (top or bottom) portion of a border.
+func renderHorizontalEdge(left, middle, right string, width int) string {
+ if width < 1 {
+ return ""
+ }
+
+ if middle == "" {
+ middle = " "
+ }
+
+ leftWidth := ansi.PrintableRuneWidth(left)
+ rightWidth := ansi.PrintableRuneWidth(right)
+
+ runes := []rune(middle)
+ j := 0
+
+ out := strings.Builder{}
+ out.WriteString(left)
+ for i := leftWidth + rightWidth; i < width+rightWidth; {
+ out.WriteRune(runes[j])
+ j++
+ if j >= len(runes) {
+ j = 0
+ }
+ i += ansi.PrintableRuneWidth(string(runes[j]))
+ }
+ out.WriteString(right)
+
+ return out.String()
+}
+
+// Apply foreground and background styling to a border.
+func (s Style) styleBorder(border string, fg, bg TerminalColor) string {
+ if fg == noColor && bg == noColor {
+ return border
+ }
+
+ var style = termenv.Style{}
+
+ if fg != noColor {
+ style = style.Foreground(fg.color(s.r))
+ }
+ if bg != noColor {
+ style = style.Background(bg.color(s.r))
+ }
+
+ return style.Styled(border)
+}
+
+func maxRuneWidth(str string) (width int) {
+ for _, r := range str {
+ w := runewidth.RuneWidth(r)
+ if w > width {
+ width = w
+ }
+ }
+ return width
+}
+
+func getFirstRuneAsString(str string) string {
+ if str == "" {
+ return str
+ }
+ r := []rune(str)
+ return string(r[0])
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/color.go b/vendor/github.com/charmbracelet/lipgloss/color.go
new file mode 100644
index 0000000..43f5b43
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/color.go
@@ -0,0 +1,172 @@
+package lipgloss
+
+import (
+ "strconv"
+
+ "github.com/muesli/termenv"
+)
+
+// TerminalColor is a color intended to be rendered in the terminal.
+type TerminalColor interface {
+ color(*Renderer) termenv.Color
+ RGBA() (r, g, b, a uint32)
+}
+
+var noColor = NoColor{}
+
+// NoColor is used to specify the absence of color styling. When this is active
+// foreground colors will be rendered with the terminal's default text color,
+// and background colors will not be drawn at all.
+//
+// Example usage:
+//
+// var style = someStyle.Copy().Background(lipgloss.NoColor{})
+type NoColor struct{}
+
+func (NoColor) color(*Renderer) termenv.Color {
+ return termenv.NoColor{}
+}
+
+// RGBA returns the RGBA value of this color. Because we have to return
+// something, despite this color being the absence of color, we're returning
+// black with 100% opacity.
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+//
+// Deprecated.
+func (n NoColor) RGBA() (r, g, b, a uint32) {
+ return 0x0, 0x0, 0x0, 0xFFFF //nolint:gomnd
+}
+
+// Color specifies a color by hex or ANSI value. For example:
+//
+// ansiColor := lipgloss.Color("21")
+// hexColor := lipgloss.Color("#0000ff")
+type Color string
+
+func (c Color) color(r *Renderer) termenv.Color {
+ return r.ColorProfile().Color(string(c))
+}
+
+// RGBA returns the RGBA value of this color. This satisfies the Go Color
+// interface. Note that on error we return black with 100% opacity, or:
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+//
+// Deprecated.
+func (c Color) RGBA() (r, g, b, a uint32) {
+ return termenv.ConvertToRGB(c.color(renderer)).RGBA()
+}
+
+// ANSIColor is a color specified by an ANSI color value. It's merely syntactic
+// sugar for the more general Color function. Invalid colors will render as
+// black.
+//
+// Example usage:
+//
+// // These two statements are equivalent.
+// colorA := lipgloss.ANSIColor(21)
+// colorB := lipgloss.Color("21")
+type ANSIColor uint
+
+func (ac ANSIColor) color(r *Renderer) termenv.Color {
+ return Color(strconv.FormatUint(uint64(ac), 10)).color(r)
+}
+
+// RGBA returns the RGBA value of this color. This satisfies the Go Color
+// interface. Note that on error we return black with 100% opacity, or:
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+//
+// Deprecated.
+func (ac ANSIColor) RGBA() (r, g, b, a uint32) {
+ cf := Color(strconv.FormatUint(uint64(ac), 10))
+ return cf.RGBA()
+}
+
+// AdaptiveColor provides color options for light and dark backgrounds. The
+// appropriate color will be returned at runtime based on the darkness of the
+// terminal background color.
+//
+// Example usage:
+//
+// color := lipgloss.AdaptiveColor{Light: "#0000ff", Dark: "#000099"}
+type AdaptiveColor struct {
+ Light string
+ Dark string
+}
+
+func (ac AdaptiveColor) color(r *Renderer) termenv.Color {
+ if r.HasDarkBackground() {
+ return Color(ac.Dark).color(r)
+ }
+ return Color(ac.Light).color(r)
+}
+
+// RGBA returns the RGBA value of this color. This satisfies the Go Color
+// interface. Note that on error we return black with 100% opacity, or:
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+//
+// Deprecated.
+func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) {
+ return termenv.ConvertToRGB(ac.color(renderer)).RGBA()
+}
+
+// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color
+// profiles. Automatic color degradation will not be performed.
+type CompleteColor struct {
+ TrueColor string
+ ANSI256 string
+ ANSI string
+}
+
+func (c CompleteColor) color(r *Renderer) termenv.Color {
+ p := r.ColorProfile()
+ switch p { //nolint:exhaustive
+ case termenv.TrueColor:
+ return p.Color(c.TrueColor)
+ case termenv.ANSI256:
+ return p.Color(c.ANSI256)
+ case termenv.ANSI:
+ return p.Color(c.ANSI)
+ default:
+ return termenv.NoColor{}
+ }
+}
+
+// RGBA returns the RGBA value of this color. This satisfies the Go Color
+// interface. Note that on error we return black with 100% opacity, or:
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color
+//
+// Deprecated.
+func (c CompleteColor) RGBA() (r, g, b, a uint32) {
+ return termenv.ConvertToRGB(c.color(renderer)).RGBA()
+}
+
+// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color
+// profiles, with separate options for light and dark backgrounds. Automatic
+// color degradation will not be performed.
+type CompleteAdaptiveColor struct {
+ Light CompleteColor
+ Dark CompleteColor
+}
+
+func (cac CompleteAdaptiveColor) color(r *Renderer) termenv.Color {
+ if r.HasDarkBackground() {
+ return cac.Dark.color(r)
+ }
+ return cac.Light.color(r)
+}
+
+// RGBA returns the RGBA value of this color. This satisfies the Go Color
+// interface. Note that on error we return black with 100% opacity, or:
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+//
+// Deprecated.
+func (cac CompleteAdaptiveColor) RGBA() (r, g, b, a uint32) {
+ return termenv.ConvertToRGB(cac.color(renderer)).RGBA()
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/get.go b/vendor/github.com/charmbracelet/lipgloss/get.go
new file mode 100644
index 0000000..d2623c4
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/get.go
@@ -0,0 +1,485 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/muesli/reflow/ansi"
+)
+
+// GetBold returns the style's bold value. If no value is set false is returned.
+func (s Style) GetBold() bool {
+ return s.getAsBool(boldKey, false)
+}
+
+// GetItalic returns the style's italic value. If no value is set false is
+// returned.
+func (s Style) GetItalic() bool {
+ return s.getAsBool(italicKey, false)
+}
+
+// GetUnderline returns the style's underline value. If no value is set false is
+// returned.
+func (s Style) GetUnderline() bool {
+ return s.getAsBool(underlineKey, false)
+}
+
+// GetStrikethrough returns the style's strikethrough value. If no value is set false
+// is returned.
+func (s Style) GetStrikethrough() bool {
+ return s.getAsBool(strikethroughKey, false)
+}
+
+// GetReverse returns the style's reverse value. If no value is set false is
+// returned.
+func (s Style) GetReverse() bool {
+ return s.getAsBool(reverseKey, false)
+}
+
+// GetBlink returns the style's blink value. If no value is set false is
+// returned.
+func (s Style) GetBlink() bool {
+ return s.getAsBool(blinkKey, false)
+}
+
+// GetFaint returns the style's faint value. If no value is set false is
+// returned.
+func (s Style) GetFaint() bool {
+ return s.getAsBool(faintKey, false)
+}
+
+// GetForeground returns the style's foreground color. If no value is set
+// NoColor{} is returned.
+func (s Style) GetForeground() TerminalColor {
+ return s.getAsColor(foregroundKey)
+}
+
+// GetBackground returns the style's background color. If no value is set
+// NoColor{} is returned.
+func (s Style) GetBackground() TerminalColor {
+ return s.getAsColor(backgroundKey)
+}
+
+// GetWidth returns the style's width setting. If no width is set 0 is
+// returned.
+func (s Style) GetWidth() int {
+ return s.getAsInt(widthKey)
+}
+
+// GetHeight returns the style's height setting. If no height is set 0 is
+// returned.
+func (s Style) GetHeight() int {
+ return s.getAsInt(heightKey)
+}
+
+// GetAlign returns the style's implicit horizontal alignment setting.
+// If no alignment is set Position.Left is returned.
+func (s Style) GetAlign() Position {
+ v := s.getAsPosition(alignHorizontalKey)
+ if v == Position(0) {
+ return Left
+ }
+ return v
+}
+
+// GetAlignHorizontal returns the style's implicit horizontal alignment setting.
+// If no alignment is set Position.Left is returned.
+func (s Style) GetAlignHorizontal() Position {
+ v := s.getAsPosition(alignHorizontalKey)
+ if v == Position(0) {
+ return Left
+ }
+ return v
+}
+
+// GetAlignVertical returns the style's implicit vertical alignment setting.
+// If no alignment is set Position.Top is returned.
+func (s Style) GetAlignVertical() Position {
+ v := s.getAsPosition(alignVerticalKey)
+ if v == Position(0) {
+ return Top
+ }
+ return v
+}
+
+// GetPadding returns the style's top, right, bottom, and left padding values,
+// in that order. 0 is returned for unset values.
+func (s Style) GetPadding() (top, right, bottom, left int) {
+ return s.getAsInt(paddingTopKey),
+ s.getAsInt(paddingRightKey),
+ s.getAsInt(paddingBottomKey),
+ s.getAsInt(paddingLeftKey)
+}
+
+// GetPaddingTop returns the style's top padding. If no value is set 0 is
+// returned.
+func (s Style) GetPaddingTop() int {
+ return s.getAsInt(paddingTopKey)
+}
+
+// GetPaddingRight returns the style's right padding. If no value is set 0 is
+// returned.
+func (s Style) GetPaddingRight() int {
+ return s.getAsInt(paddingRightKey)
+}
+
+// GetPaddingBottom returns the style's bottom padding. If no value is set 0 is
+// returned.
+func (s Style) GetPaddingBottom() int {
+ return s.getAsInt(paddingBottomKey)
+}
+
+// GetPaddingLeft returns the style's left padding. If no value is set 0 is
+// returned.
+func (s Style) GetPaddingLeft() int {
+ return s.getAsInt(paddingLeftKey)
+}
+
+// GetHorizontalPadding returns the style's left and right padding. Unset
+// values are measured as 0.
+func (s Style) GetHorizontalPadding() int {
+ return s.getAsInt(paddingLeftKey) + s.getAsInt(paddingRightKey)
+}
+
+// GetVerticalPadding returns the style's top and bottom padding. Unset values
+// are measured as 0.
+func (s Style) GetVerticalPadding() int {
+ return s.getAsInt(paddingTopKey) + s.getAsInt(paddingBottomKey)
+}
+
+// GetColorWhitespace returns the style's whitespace coloring setting. If no
+// value is set false is returned.
+func (s Style) GetColorWhitespace() bool {
+ return s.getAsBool(colorWhitespaceKey, false)
+}
+
+// GetMargin returns the style's top, right, bottom, and left margins, in that
+// order. 0 is returned for unset values.
+func (s Style) GetMargin() (top, right, bottom, left int) {
+ return s.getAsInt(marginTopKey),
+ s.getAsInt(marginRightKey),
+ s.getAsInt(marginBottomKey),
+ s.getAsInt(marginLeftKey)
+}
+
+// GetMarginTop returns the style's top margin. If no value is set 0 is
+// returned.
+func (s Style) GetMarginTop() int {
+ return s.getAsInt(marginTopKey)
+}
+
+// GetMarginRight returns the style's right margin. If no value is set 0 is
+// returned.
+func (s Style) GetMarginRight() int {
+ return s.getAsInt(marginRightKey)
+}
+
+// GetMarginBottom returns the style's bottom margin. If no value is set 0 is
+// returned.
+func (s Style) GetMarginBottom() int {
+ return s.getAsInt(marginBottomKey)
+}
+
+// GetMarginLeft returns the style's left margin. If no value is set 0 is
+// returned.
+func (s Style) GetMarginLeft() int {
+ return s.getAsInt(marginLeftKey)
+}
+
+// GetHorizontalMargins returns the style's left and right margins. Unset
+// values are measured as 0.
+func (s Style) GetHorizontalMargins() int {
+ return s.getAsInt(marginLeftKey) + s.getAsInt(marginRightKey)
+}
+
+// GetVerticalMargins returns the style's top and bottom margins. Unset values
+// are measured as 0.
+func (s Style) GetVerticalMargins() int {
+ return s.getAsInt(marginTopKey) + s.getAsInt(marginBottomKey)
+}
+
+// GetBorder returns the style's border style (type Border) and value for the
+// top, right, bottom, and left in that order. If no value is set for the
+// border style, Border{} is returned. For all other unset values false is
+// returned.
+func (s Style) GetBorder() (b Border, top, right, bottom, left bool) {
+ return s.getBorderStyle(),
+ s.getAsBool(borderTopKey, false),
+ s.getAsBool(borderRightKey, false),
+ s.getAsBool(borderBottomKey, false),
+ s.getAsBool(borderLeftKey, false)
+}
+
+// GetBorderStyle returns the style's border style (type Border). If no value
+// is set Border{} is returned.
+func (s Style) GetBorderStyle() Border {
+ return s.getBorderStyle()
+}
+
+// GetBorderTop returns the style's top border setting. If no value is set
+// false is returned.
+func (s Style) GetBorderTop() bool {
+ return s.getAsBool(borderTopKey, false)
+}
+
+// GetBorderRight returns the style's right border setting. If no value is set
+// false is returned.
+func (s Style) GetBorderRight() bool {
+ return s.getAsBool(borderRightKey, false)
+}
+
+// GetBorderBottom returns the style's bottom border setting. If no value is
+// set false is returned.
+func (s Style) GetBorderBottom() bool {
+ return s.getAsBool(borderBottomKey, false)
+}
+
+// GetBorderLeft returns the style's left border setting. If no value is
+// set false is returned.
+func (s Style) GetBorderLeft() bool {
+ return s.getAsBool(borderLeftKey, false)
+}
+
+// GetBorderTopForeground returns the style's border top foreground color. If
+// no value is set NoColor{} is returned.
+func (s Style) GetBorderTopForeground() TerminalColor {
+ return s.getAsColor(borderTopForegroundKey)
+}
+
+// GetBorderRightForeground returns the style's border right foreground color.
+// If no value is set NoColor{} is returned.
+func (s Style) GetBorderRightForeground() TerminalColor {
+ return s.getAsColor(borderRightForegroundKey)
+}
+
+// GetBorderBottomForeground returns the style's border bottom foreground
+// color. If no value is set NoColor{} is returned.
+func (s Style) GetBorderBottomForeground() TerminalColor {
+ return s.getAsColor(borderBottomForegroundKey)
+}
+
+// GetBorderLeftForeground returns the style's border left foreground
+// color. If no value is set NoColor{} is returned.
+func (s Style) GetBorderLeftForeground() TerminalColor {
+ return s.getAsColor(borderLeftForegroundKey)
+}
+
+// GetBorderTopBackground returns the style's border top background color. If
+// no value is set NoColor{} is returned.
+func (s Style) GetBorderTopBackground() TerminalColor {
+ return s.getAsColor(borderTopBackgroundKey)
+}
+
+// GetBorderRightBackground returns the style's border right background color.
+// If no value is set NoColor{} is returned.
+func (s Style) GetBorderRightBackground() TerminalColor {
+ return s.getAsColor(borderRightBackgroundKey)
+}
+
+// GetBorderBottomBackground returns the style's border bottom background
+// color. If no value is set NoColor{} is returned.
+func (s Style) GetBorderBottomBackground() TerminalColor {
+ return s.getAsColor(borderBottomBackgroundKey)
+}
+
+// GetBorderLeftBackground returns the style's border left background
+// color. If no value is set NoColor{} is returned.
+func (s Style) GetBorderLeftBackground() TerminalColor {
+ return s.getAsColor(borderLeftBackgroundKey)
+}
+
+// GetBorderTopWidth returns the width of the top border. If borders contain
+// runes of varying widths, the widest rune is returned. If no border exists on
+// the top edge, 0 is returned.
+//
+// Deprecated: This function simply calls Style.GetBorderTopSize.
+func (s Style) GetBorderTopWidth() int {
+ return s.GetBorderTopSize()
+}
+
+// GetBorderTopSize returns the width of the top border. If borders contain
+// runes of varying widths, the widest rune is returned. If no border exists on
+// the top edge, 0 is returned.
+func (s Style) GetBorderTopSize() int {
+ if !s.getAsBool(borderTopKey, false) {
+ return 0
+ }
+ return s.getBorderStyle().GetTopSize()
+}
+
+// GetBorderLeftSize returns the width of the left border. If borders contain
+// runes of varying widths, the widest rune is returned. If no border exists on
+// the left edge, 0 is returned.
+func (s Style) GetBorderLeftSize() int {
+ if !s.getAsBool(borderLeftKey, false) {
+ return 0
+ }
+ return s.getBorderStyle().GetLeftSize()
+}
+
+// GetBorderBottomSize returns the width of the bottom border. If borders
+// contain runes of varying widths, the widest rune is returned. If no border
+// exists on the left edge, 0 is returned.
+func (s Style) GetBorderBottomSize() int {
+ if !s.getAsBool(borderBottomKey, false) {
+ return 0
+ }
+ return s.getBorderStyle().GetBottomSize()
+}
+
+// GetBorderRightSize returns the width of the right border. If borders
+// contain runes of varying widths, the widest rune is returned. If no border
+// exists on the right edge, 0 is returned.
+func (s Style) GetBorderRightSize() int {
+ if !s.getAsBool(borderRightKey, false) {
+ return 0
+ }
+ return s.getBorderStyle().GetRightSize()
+}
+
+// GetHorizontalBorderSize returns the width of the horizontal borders. If
+// borders contain runes of varying widths, the widest rune is returned. If no
+// border exists on the horizontal edges, 0 is returned.
+func (s Style) GetHorizontalBorderSize() int {
+ return s.GetBorderLeftSize() + s.GetBorderRightSize()
+}
+
+// GetVerticalBorderSize returns the width of the vertical borders. If
+// borders contain runes of varying widths, the widest rune is returned. If no
+// border exists on the vertical edges, 0 is returned.
+func (s Style) GetVerticalBorderSize() int {
+ return s.GetBorderTopSize() + s.GetBorderBottomSize()
+}
+
+// GetInline returns the style's inline setting. If no value is set false is
+// returned.
+func (s Style) GetInline() bool {
+ return s.getAsBool(inlineKey, false)
+}
+
+// GetMaxWidth returns the style's max width setting. If no value is set 0 is
+// returned.
+func (s Style) GetMaxWidth() int {
+ return s.getAsInt(maxWidthKey)
+}
+
+// GetMaxHeight returns the style's max height setting. If no value is set 0 is
+// returned.
+func (s Style) GetMaxHeight() int {
+ return s.getAsInt(maxHeightKey)
+}
+
+// GetTabWidth returns the style's tab width setting. If no value is set 4 is
+// returned which is the implicit default.
+func (s Style) GetTabWidth() int {
+ return s.getAsInt(tabWidthKey)
+}
+
+// GetUnderlineSpaces returns whether or not the style is set to underline
+// spaces. If not value is set false is returned.
+func (s Style) GetUnderlineSpaces() bool {
+ return s.getAsBool(underlineSpacesKey, false)
+}
+
+// GetStrikethroughSpaces returns whether or not the style is set to strikethrough
+// spaces. If not value is set false is returned.
+func (s Style) GetStrikethroughSpaces() bool {
+ return s.getAsBool(strikethroughSpacesKey, false)
+}
+
+// GetHorizontalFrameSize returns the sum of the style's horizontal margins, padding
+// and border widths.
+//
+// Provisional: this method may be renamed.
+func (s Style) GetHorizontalFrameSize() int {
+ return s.GetHorizontalMargins() + s.GetHorizontalPadding() + s.GetHorizontalBorderSize()
+}
+
+// GetVerticalFrameSize returns the sum of the style's vertical margins, padding
+// and border widths.
+//
+// Provisional: this method may be renamed.
+func (s Style) GetVerticalFrameSize() int {
+ return s.GetVerticalMargins() + s.GetVerticalPadding() + s.GetVerticalBorderSize()
+}
+
+// GetFrameSize returns the sum of the margins, padding and border width for
+// both the horizontal and vertical margins.
+func (s Style) GetFrameSize() (x, y int) {
+ return s.GetHorizontalFrameSize(), s.GetVerticalFrameSize()
+}
+
+// Returns whether or not the given property is set.
+func (s Style) isSet(k propKey) bool {
+ _, exists := s.rules[k]
+ return exists
+}
+
+func (s Style) getAsBool(k propKey, defaultVal bool) bool {
+ v, ok := s.rules[k]
+ if !ok {
+ return defaultVal
+ }
+ if b, ok := v.(bool); ok {
+ return b
+ }
+ return defaultVal
+}
+
+func (s Style) getAsColor(k propKey) TerminalColor {
+ v, ok := s.rules[k]
+ if !ok {
+ return noColor
+ }
+ if c, ok := v.(TerminalColor); ok {
+ return c
+ }
+ return noColor
+}
+
+func (s Style) getAsInt(k propKey) int {
+ v, ok := s.rules[k]
+ if !ok {
+ return 0
+ }
+ if i, ok := v.(int); ok {
+ return i
+ }
+ return 0
+}
+
+func (s Style) getAsPosition(k propKey) Position {
+ v, ok := s.rules[k]
+ if !ok {
+ return Position(0)
+ }
+ if p, ok := v.(Position); ok {
+ return p
+ }
+ return Position(0)
+}
+
+func (s Style) getBorderStyle() Border {
+ v, ok := s.rules[borderStyleKey]
+ if !ok {
+ return noBorder
+ }
+ if b, ok := v.(Border); ok {
+ return b
+ }
+ return noBorder
+}
+
+// Split a string into lines, additionally returning the size of the widest
+// line.
+func getLines(s string) (lines []string, widest int) {
+ lines = strings.Split(s, "\n")
+
+ for _, l := range lines {
+ w := ansi.PrintableRuneWidth(l)
+ if widest < w {
+ widest = w
+ }
+ }
+
+ return lines, widest
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/join.go b/vendor/github.com/charmbracelet/lipgloss/join.go
new file mode 100644
index 0000000..f265976
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/join.go
@@ -0,0 +1,175 @@
+package lipgloss
+
+import (
+ "math"
+ "strings"
+
+ "github.com/muesli/reflow/ansi"
+)
+
+// JoinHorizontal is a utility function for horizontally joining two
+// potentially multi-lined strings along a vertical axis. The first argument is
+// the position, with 0 being all the way at the top and 1 being all the way
+// at the bottom.
+//
+// If you just want to align to the left, right or center you may as well just
+// use the helper constants Top, Center, and Bottom.
+//
+// Example:
+//
+// blockB := "...\n...\n..."
+// blockA := "...\n...\n...\n...\n..."
+//
+// // Join 20% from the top
+// str := lipgloss.JoinHorizontal(0.2, blockA, blockB)
+//
+// // Join on the top edge
+// str := lipgloss.JoinHorizontal(lipgloss.Top, blockA, blockB)
+func JoinHorizontal(pos Position, strs ...string) string {
+ if len(strs) == 0 {
+ return ""
+ }
+ if len(strs) == 1 {
+ return strs[0]
+ }
+
+ var (
+ // Groups of strings broken into multiple lines
+ blocks = make([][]string, len(strs))
+
+ // Max line widths for the above text blocks
+ maxWidths = make([]int, len(strs))
+
+ // Height of the tallest block
+ maxHeight int
+ )
+
+ // Break text blocks into lines and get max widths for each text block
+ for i, str := range strs {
+ blocks[i], maxWidths[i] = getLines(str)
+ if len(blocks[i]) > maxHeight {
+ maxHeight = len(blocks[i])
+ }
+ }
+
+ // Add extra lines to make each side the same height
+ for i := range blocks {
+ if len(blocks[i]) >= maxHeight {
+ continue
+ }
+
+ extraLines := make([]string, maxHeight-len(blocks[i]))
+
+ switch pos { //nolint:exhaustive
+ case Top:
+ blocks[i] = append(blocks[i], extraLines...)
+
+ case Bottom:
+ blocks[i] = append(extraLines, blocks[i]...)
+
+ default: // Somewhere in the middle
+ n := len(extraLines)
+ split := int(math.Round(float64(n) * pos.value()))
+ top := n - split
+ bottom := n - top
+
+ blocks[i] = append(extraLines[top:], blocks[i]...)
+ blocks[i] = append(blocks[i], extraLines[bottom:]...)
+ }
+ }
+
+ // Merge lines
+ var b strings.Builder
+ for i := range blocks[0] { // remember, all blocks have the same number of members now
+ for j, block := range blocks {
+ b.WriteString(block[i])
+
+ // Also make lines the same length
+ b.WriteString(strings.Repeat(" ", maxWidths[j]-ansi.PrintableRuneWidth(block[i])))
+ }
+ if i < len(blocks[0])-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ return b.String()
+}
+
+// JoinVertical is a utility function for vertically joining two potentially
+// multi-lined strings along a horizontal axis. The first argument is the
+// position, with 0 being all the way to the left and 1 being all the way to
+// the right.
+//
+// If you just want to align to the left, right or center you may as well just
+// use the helper constants Left, Center, and Right.
+//
+// Example:
+//
+// blockB := "...\n...\n..."
+// blockA := "...\n...\n...\n...\n..."
+//
+// // Join 20% from the top
+// str := lipgloss.JoinVertical(0.2, blockA, blockB)
+//
+// // Join on the right edge
+// str := lipgloss.JoinVertical(lipgloss.Right, blockA, blockB)
+func JoinVertical(pos Position, strs ...string) string {
+ if len(strs) == 0 {
+ return ""
+ }
+ if len(strs) == 1 {
+ return strs[0]
+ }
+
+ var (
+ blocks = make([][]string, len(strs))
+ maxWidth int
+ )
+
+ for i := range strs {
+ var w int
+ blocks[i], w = getLines(strs[i])
+ if w > maxWidth {
+ maxWidth = w
+ }
+ }
+
+ var b strings.Builder
+ for i, block := range blocks {
+ for j, line := range block {
+ w := maxWidth - ansi.PrintableRuneWidth(line)
+
+ switch pos { //nolint:exhaustive
+ case Left:
+ b.WriteString(line)
+ b.WriteString(strings.Repeat(" ", w))
+
+ case Right:
+ b.WriteString(strings.Repeat(" ", w))
+ b.WriteString(line)
+
+ default: // Somewhere in the middle
+ if w < 1 {
+ b.WriteString(line)
+ break
+ }
+
+ split := int(math.Round(float64(w) * pos.value()))
+ right := w - split
+ left := w - right
+
+ b.WriteString(strings.Repeat(" ", left))
+ b.WriteString(line)
+ b.WriteString(strings.Repeat(" ", right))
+ }
+
+ // Write a newline as long as we're not on the last line of the
+ // last block.
+ if !(i == len(blocks)-1 && j == len(block)-1) {
+ b.WriteRune('\n')
+ }
+ }
+ }
+
+ return b.String()
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/position.go b/vendor/github.com/charmbracelet/lipgloss/position.go
new file mode 100644
index 0000000..7d229e0
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/position.go
@@ -0,0 +1,154 @@
+package lipgloss
+
+import (
+ "math"
+ "strings"
+
+ "github.com/muesli/reflow/ansi"
+)
+
+// Position represents a position along a horizontal or vertical axis. It's in
+// situations where an axis is involved, like alignment, joining, placement and
+// so on.
+//
+// A value of 0 represents the start (the left or top) and 1 represents the end
+// (the right or bottom). 0.5 represents the center.
+//
+// There are constants Top, Bottom, Center, Left and Right in this package that
+// can be used to aid readability.
+type Position float64
+
+func (p Position) value() float64 {
+ return math.Min(1, math.Max(0, float64(p)))
+}
+
+// Position aliases.
+const (
+ Top Position = 0.0
+ Bottom Position = 1.0
+ Center Position = 0.5
+ Left Position = 0.0
+ Right Position = 1.0
+)
+
+// Place places a string or text block vertically in an unstyled box of a given
+// width or height.
+func Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
+ return renderer.Place(width, height, hPos, vPos, str, opts...)
+}
+
+// Place places a string or text block vertically in an unstyled box of a given
+// width or height.
+func (r *Renderer) Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
+ return r.PlaceVertical(height, vPos, r.PlaceHorizontal(width, hPos, str, opts...), opts...)
+}
+
+// PlaceHorizontal places a string or text block horizontally in an unstyled
+// block of a given width. If the given width is shorter than the max width of
+// the string (measured by its longest line) this will be a noop.
+func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
+ return renderer.PlaceHorizontal(width, pos, str, opts...)
+}
+
+// PlaceHorizontal places a string or text block horizontally in an unstyled
+// block of a given width. If the given width is shorter than the max width of
+// the string (measured by its longest line) this will be a noöp.
+func (r *Renderer) PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
+ lines, contentWidth := getLines(str)
+ gap := width - contentWidth
+
+ if gap <= 0 {
+ return str
+ }
+
+ ws := newWhitespace(r, opts...)
+
+ var b strings.Builder
+ for i, l := range lines {
+ // Is this line shorter than the longest line?
+ short := max(0, contentWidth-ansi.PrintableRuneWidth(l))
+
+ switch pos { //nolint:exhaustive
+ case Left:
+ b.WriteString(l)
+ b.WriteString(ws.render(gap + short))
+
+ case Right:
+ b.WriteString(ws.render(gap + short))
+ b.WriteString(l)
+
+ default: // somewhere in the middle
+ totalGap := gap + short
+
+ split := int(math.Round(float64(totalGap) * pos.value()))
+ left := totalGap - split
+ right := totalGap - left
+
+ b.WriteString(ws.render(left))
+ b.WriteString(l)
+ b.WriteString(ws.render(right))
+ }
+
+ if i < len(lines)-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ return b.String()
+}
+
+// PlaceVertical places a string or text block vertically in an unstyled block
+// of a given height. If the given height is shorter than the height of the
+// string (measured by its newlines) then this will be a noop.
+func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
+ return renderer.PlaceVertical(height, pos, str, opts...)
+}
+
+// PlaceVertical places a string or text block vertically in an unstyled block
+// of a given height. If the given height is shorter than the height of the
+// string (measured by its newlines) then this will be a noöp.
+func (r *Renderer) PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
+ contentHeight := strings.Count(str, "\n") + 1
+ gap := height - contentHeight
+
+ if gap <= 0 {
+ return str
+ }
+
+ ws := newWhitespace(r, opts...)
+
+ _, width := getLines(str)
+ emptyLine := ws.render(width)
+ b := strings.Builder{}
+
+ switch pos { //nolint:exhaustive
+ case Top:
+ b.WriteString(str)
+ b.WriteRune('\n')
+ for i := 0; i < gap; i++ {
+ b.WriteString(emptyLine)
+ if i < gap-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ case Bottom:
+ b.WriteString(strings.Repeat(emptyLine+"\n", gap))
+ b.WriteString(str)
+
+ default: // Somewhere in the middle
+ split := int(math.Round(float64(gap) * pos.value()))
+ top := gap - split
+ bottom := gap - top
+
+ b.WriteString(strings.Repeat(emptyLine+"\n", top))
+ b.WriteString(str)
+
+ for i := 0; i < bottom; i++ {
+ b.WriteRune('\n')
+ b.WriteString(emptyLine)
+ }
+ }
+
+ return b.String()
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/renderer.go b/vendor/github.com/charmbracelet/lipgloss/renderer.go
new file mode 100644
index 0000000..85ffd25
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/renderer.go
@@ -0,0 +1,184 @@
+package lipgloss
+
+import (
+ "io"
+ "sync"
+
+ "github.com/muesli/termenv"
+)
+
+// We're manually creating the struct here to avoid initializing the output and
+// query the terminal multiple times.
+var renderer = &Renderer{
+ output: termenv.DefaultOutput(),
+}
+
+// Renderer is a lipgloss terminal renderer.
+type Renderer struct {
+ output *termenv.Output
+ colorProfile termenv.Profile
+ hasDarkBackground bool
+
+ getColorProfile sync.Once
+ explicitColorProfile bool
+
+ getBackgroundColor sync.Once
+ explicitBackgroundColor bool
+
+ mtx sync.RWMutex
+}
+
+// RendererOption is a function that can be used to configure a [Renderer].
+type RendererOption func(r *Renderer)
+
+// DefaultRenderer returns the default renderer.
+func DefaultRenderer() *Renderer {
+ return renderer
+}
+
+// SetDefaultRenderer sets the default global renderer.
+func SetDefaultRenderer(r *Renderer) {
+ renderer = r
+}
+
+// NewRenderer creates a new Renderer.
+//
+// w will be used to determine the terminal's color capabilities.
+func NewRenderer(w io.Writer, opts ...termenv.OutputOption) *Renderer {
+ r := &Renderer{
+ output: termenv.NewOutput(w, opts...),
+ }
+ return r
+}
+
+// Output returns the termenv output.
+func (r *Renderer) Output() *termenv.Output {
+ r.mtx.RLock()
+ defer r.mtx.RUnlock()
+ return r.output
+}
+
+// SetOutput sets the termenv output.
+func (r *Renderer) SetOutput(o *termenv.Output) {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+ r.output = o
+}
+
+// ColorProfile returns the detected termenv color profile.
+func (r *Renderer) ColorProfile() termenv.Profile {
+ r.mtx.RLock()
+ defer r.mtx.RUnlock()
+
+ if !r.explicitColorProfile {
+ r.getColorProfile.Do(func() {
+ // NOTE: we don't need to lock here because sync.Once provides its
+ // own locking mechanism.
+ r.colorProfile = r.output.EnvColorProfile()
+ })
+ }
+
+ return r.colorProfile
+}
+
+// ColorProfile returns the detected termenv color profile.
+func ColorProfile() termenv.Profile {
+ return renderer.ColorProfile()
+}
+
+// SetColorProfile sets the color profile on the renderer. This function exists
+// mostly for testing purposes so that you can assure you're testing against
+// a specific profile.
+//
+// Outside of testing you likely won't want to use this function as the color
+// profile will detect and cache the terminal's color capabilities and choose
+// the best available profile.
+//
+// Available color profiles are:
+//
+// termenv.Ascii // no color, 1-bit
+// termenv.ANSI //16 colors, 4-bit
+// termenv.ANSI256 // 256 colors, 8-bit
+// termenv.TrueColor // 16,777,216 colors, 24-bit
+//
+// This function is thread-safe.
+func (r *Renderer) SetColorProfile(p termenv.Profile) {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.colorProfile = p
+ r.explicitColorProfile = true
+}
+
+// SetColorProfile sets the color profile on the default renderer. This
+// function exists mostly for testing purposes so that you can assure you're
+// testing against a specific profile.
+//
+// Outside of testing you likely won't want to use this function as the color
+// profile will detect and cache the terminal's color capabilities and choose
+// the best available profile.
+//
+// Available color profiles are:
+//
+// termenv.Ascii // no color, 1-bit
+// termenv.ANSI //16 colors, 4-bit
+// termenv.ANSI256 // 256 colors, 8-bit
+// termenv.TrueColor // 16,777,216 colors, 24-bit
+//
+// This function is thread-safe.
+func SetColorProfile(p termenv.Profile) {
+ renderer.SetColorProfile(p)
+}
+
+// HasDarkBackground returns whether or not the terminal has a dark background.
+func HasDarkBackground() bool {
+ return renderer.HasDarkBackground()
+}
+
+// HasDarkBackground returns whether or not the renderer will render to a dark
+// background. A dark background can either be auto-detected, or set explicitly
+// on the renderer.
+func (r *Renderer) HasDarkBackground() bool {
+ r.mtx.RLock()
+ defer r.mtx.RUnlock()
+
+ if !r.explicitBackgroundColor {
+ r.getBackgroundColor.Do(func() {
+ // NOTE: we don't need to lock here because sync.Once provides its
+ // own locking mechanism.
+ r.hasDarkBackground = r.output.HasDarkBackground()
+ })
+ }
+
+ return r.hasDarkBackground
+}
+
+// SetHasDarkBackground sets the background color detection value for the
+// default renderer. This function exists mostly for testing purposes so that
+// you can assure you're testing against a specific background color setting.
+//
+// Outside of testing you likely won't want to use this function as the
+// backgrounds value will be automatically detected and cached against the
+// terminal's current background color setting.
+//
+// This function is thread-safe.
+func SetHasDarkBackground(b bool) {
+ renderer.SetHasDarkBackground(b)
+}
+
+// SetHasDarkBackground sets the background color detection value on the
+// renderer. This function exists mostly for testing purposes so that you can
+// assure you're testing against a specific background color setting.
+//
+// Outside of testing you likely won't want to use this function as the
+// backgrounds value will be automatically detected and cached against the
+// terminal's current background color setting.
+//
+// This function is thread-safe.
+func (r *Renderer) SetHasDarkBackground(b bool) {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.hasDarkBackground = b
+ r.explicitBackgroundColor = true
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/runes.go b/vendor/github.com/charmbracelet/lipgloss/runes.go
new file mode 100644
index 0000000..7a49e32
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/runes.go
@@ -0,0 +1,43 @@
+package lipgloss
+
+import (
+ "strings"
+)
+
+// StyleRunes apply a given style to runes at the given indices in the string.
+// Note that you must provide styling options for both matched and unmatched
+// runes. Indices out of bounds will be ignored.
+func StyleRunes(str string, indices []int, matched, unmatched Style) string {
+ // Convert slice of indices to a map for easier lookups
+ m := make(map[int]struct{})
+ for _, i := range indices {
+ m[i] = struct{}{}
+ }
+
+ var (
+ out strings.Builder
+ group strings.Builder
+ style Style
+ runes = []rune(str)
+ )
+
+ for i, r := range runes {
+ group.WriteRune(r)
+
+ _, matches := m[i]
+ _, nextMatches := m[i+1]
+
+ if matches != nextMatches || i == len(runes)-1 {
+ // Flush
+ if matches {
+ style = matched
+ } else {
+ style = unmatched
+ }
+ out.WriteString(style.Render(group.String()))
+ group.Reset()
+ }
+ }
+
+ return out.String()
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/set.go b/vendor/github.com/charmbracelet/lipgloss/set.go
new file mode 100644
index 0000000..432eac5
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/set.go
@@ -0,0 +1,658 @@
+package lipgloss
+
+// This could (should) probably just be moved into NewStyle(). We've broken it
+// out, so we can call it in a lazy way.
+func (s *Style) init() {
+ if s.rules == nil {
+ s.rules = make(rules)
+ }
+}
+
+// Set a value on the underlying rules map.
+func (s *Style) set(key propKey, value interface{}) {
+ s.init()
+
+ switch v := value.(type) {
+ case int:
+ // TabWidth is the only property that may have a negative value (and
+ // that negative value can be no less than -1).
+ if key == tabWidthKey {
+ s.rules[key] = v
+ break
+ }
+
+ // We don't allow negative integers on any of our other values, so just keep
+ // them at zero or above. We could use uints instead, but the
+ // conversions are a little tedious, so we're sticking with ints for
+ // sake of usability.
+ s.rules[key] = max(0, v)
+ default:
+ s.rules[key] = v
+ }
+}
+
+// Bold sets a bold formatting rule.
+func (s Style) Bold(v bool) Style {
+ s.set(boldKey, v)
+ return s
+}
+
+// Italic sets an italic formatting rule. In some terminal emulators this will
+// render with "reverse" coloring if not italic font variant is available.
+func (s Style) Italic(v bool) Style {
+ s.set(italicKey, v)
+ return s
+}
+
+// Underline sets an underline rule. By default, underlines will not be drawn on
+// whitespace like margins and padding. To change this behavior set
+// UnderlineSpaces.
+func (s Style) Underline(v bool) Style {
+ s.set(underlineKey, v)
+ return s
+}
+
+// Strikethrough sets a strikethrough rule. By default, strikes will not be
+// drawn on whitespace like margins and padding. To change this behavior set
+// StrikethroughSpaces.
+func (s Style) Strikethrough(v bool) Style {
+ s.set(strikethroughKey, v)
+ return s
+}
+
+// Reverse sets a rule for inverting foreground and background colors.
+func (s Style) Reverse(v bool) Style {
+ s.set(reverseKey, v)
+ return s
+}
+
+// Blink sets a rule for blinking foreground text.
+func (s Style) Blink(v bool) Style {
+ s.set(blinkKey, v)
+ return s
+}
+
+// Faint sets a rule for rendering the foreground color in a dimmer shade.
+func (s Style) Faint(v bool) Style {
+ s.set(faintKey, v)
+ return s
+}
+
+// Foreground sets a foreground color.
+//
+// // Sets the foreground to blue
+// s := lipgloss.NewStyle().Foreground(lipgloss.Color("#0000ff"))
+//
+// // Removes the foreground color
+// s.Foreground(lipgloss.NoColor)
+func (s Style) Foreground(c TerminalColor) Style {
+ s.set(foregroundKey, c)
+ return s
+}
+
+// Background sets a background color.
+func (s Style) Background(c TerminalColor) Style {
+ s.set(backgroundKey, c)
+ return s
+}
+
+// Width sets the width of the block before applying margins. The width, if
+// set, also determines where text will wrap.
+func (s Style) Width(i int) Style {
+ s.set(widthKey, i)
+ return s
+}
+
+// Height sets the height of the block before applying margins. If the height of
+// the text block is less than this value after applying padding (or not), the
+// block will be set to this height.
+func (s Style) Height(i int) Style {
+ s.set(heightKey, i)
+ return s
+}
+
+// Align is a shorthand method for setting horizontal and vertical alignment.
+//
+// With one argument, the position value is applied to the horizontal alignment.
+//
+// With two arguments, the value is applied to the vertical and horizontal
+// alignments, in that order.
+func (s Style) Align(p ...Position) Style {
+ if len(p) > 0 {
+ s.set(alignHorizontalKey, p[0])
+ }
+ if len(p) > 1 {
+ s.set(alignVerticalKey, p[1])
+ }
+ return s
+}
+
+// AlignHorizontal sets a horizontal text alignment rule.
+func (s Style) AlignHorizontal(p Position) Style {
+ s.set(alignHorizontalKey, p)
+ return s
+}
+
+// AlignVertical sets a vertical text alignment rule.
+func (s Style) AlignVertical(p Position) Style {
+ s.set(alignVerticalKey, p)
+ return s
+}
+
+// Padding is a shorthand method for setting padding on all sides at once.
+//
+// With one argument, the value is applied to all sides.
+//
+// With two arguments, the value is applied to the vertical and horizontal
+// sides, in that order.
+//
+// With three arguments, the value is applied to the top side, the horizontal
+// sides, and the bottom side, in that order.
+//
+// With four arguments, the value is applied clockwise starting from the top
+// side, followed by the right side, then the bottom, and finally the left.
+//
+// With more than four arguments no padding will be added.
+func (s Style) Padding(i ...int) Style {
+ top, right, bottom, left, ok := whichSidesInt(i...)
+ if !ok {
+ return s
+ }
+
+ s.set(paddingTopKey, top)
+ s.set(paddingRightKey, right)
+ s.set(paddingBottomKey, bottom)
+ s.set(paddingLeftKey, left)
+ return s
+}
+
+// PaddingLeft adds padding on the left.
+func (s Style) PaddingLeft(i int) Style {
+ s.set(paddingLeftKey, i)
+ return s
+}
+
+// PaddingRight adds padding on the right.
+func (s Style) PaddingRight(i int) Style {
+ s.set(paddingRightKey, i)
+ return s
+}
+
+// PaddingTop adds padding to the top of the block.
+func (s Style) PaddingTop(i int) Style {
+ s.set(paddingTopKey, i)
+ return s
+}
+
+// PaddingBottom adds padding to the bottom of the block.
+func (s Style) PaddingBottom(i int) Style {
+ s.set(paddingBottomKey, i)
+ return s
+}
+
+// ColorWhitespace determines whether or not the background color should be
+// applied to the padding. This is true by default as it's more than likely the
+// desired and expected behavior, but it can be disabled for certain graphic
+// effects.
+func (s Style) ColorWhitespace(v bool) Style {
+ s.set(colorWhitespaceKey, v)
+ return s
+}
+
+// Margin is a shorthand method for setting margins on all sides at once.
+//
+// With one argument, the value is applied to all sides.
+//
+// With two arguments, the value is applied to the vertical and horizontal
+// sides, in that order.
+//
+// With three arguments, the value is applied to the top side, the horizontal
+// sides, and the bottom side, in that order.
+//
+// With four arguments, the value is applied clockwise starting from the top
+// side, followed by the right side, then the bottom, and finally the left.
+//
+// With more than four arguments no margin will be added.
+func (s Style) Margin(i ...int) Style {
+ top, right, bottom, left, ok := whichSidesInt(i...)
+ if !ok {
+ return s
+ }
+
+ s.set(marginTopKey, top)
+ s.set(marginRightKey, right)
+ s.set(marginBottomKey, bottom)
+ s.set(marginLeftKey, left)
+ return s
+}
+
+// MarginLeft sets the value of the left margin.
+func (s Style) MarginLeft(i int) Style {
+ s.set(marginLeftKey, i)
+ return s
+}
+
+// MarginRight sets the value of the right margin.
+func (s Style) MarginRight(i int) Style {
+ s.set(marginRightKey, i)
+ return s
+}
+
+// MarginTop sets the value of the top margin.
+func (s Style) MarginTop(i int) Style {
+ s.set(marginTopKey, i)
+ return s
+}
+
+// MarginBottom sets the value of the bottom margin.
+func (s Style) MarginBottom(i int) Style {
+ s.set(marginBottomKey, i)
+ return s
+}
+
+// MarginBackground sets the background color of the margin. Note that this is
+// also set when inheriting from a style with a background color. In that case
+// the background color on that style will set the margin color on this style.
+func (s Style) MarginBackground(c TerminalColor) Style {
+ s.set(marginBackgroundKey, c)
+ return s
+}
+
+// Border is shorthand for setting the border style and which sides should
+// have a border at once. The variadic argument sides works as follows:
+//
+// With one value, the value is applied to all sides.
+//
+// With two values, the values are applied to the vertical and horizontal
+// sides, in that order.
+//
+// With three values, the values are applied to the top side, the horizontal
+// sides, and the bottom side, in that order.
+//
+// With four values, the values are applied clockwise starting from the top
+// side, followed by the right side, then the bottom, and finally the left.
+//
+// With more than four arguments the border will be applied to all sides.
+//
+// Examples:
+//
+// // Applies borders to the top and bottom only
+// lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false)
+//
+// // Applies rounded borders to the right and bottom only
+// lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false)
+func (s Style) Border(b Border, sides ...bool) Style {
+ s.set(borderStyleKey, b)
+
+ top, right, bottom, left, ok := whichSidesBool(sides...)
+ if !ok {
+ top = true
+ right = true
+ bottom = true
+ left = true
+ }
+
+ s.set(borderTopKey, top)
+ s.set(borderRightKey, right)
+ s.set(borderBottomKey, bottom)
+ s.set(borderLeftKey, left)
+
+ return s
+}
+
+// BorderStyle defines the Border on a style. A Border contains a series of
+// definitions for the sides and corners of a border.
+//
+// Note that if border visibility has not been set for any sides when setting
+// the border style, the border will be enabled for all sides during rendering.
+//
+// You can define border characters as you'd like, though several default
+// styles are included: NormalBorder(), RoundedBorder(), BlockBorder(),
+// OuterHalfBlockBorder(), InnerHalfBlockBorder(), ThickBorder(),
+// and DoubleBorder().
+//
+// Example:
+//
+// lipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder())
+func (s Style) BorderStyle(b Border) Style {
+ s.set(borderStyleKey, b)
+ return s
+}
+
+// BorderTop determines whether or not to draw a top border.
+func (s Style) BorderTop(v bool) Style {
+ s.set(borderTopKey, v)
+ return s
+}
+
+// BorderRight determines whether or not to draw a right border.
+func (s Style) BorderRight(v bool) Style {
+ s.set(borderRightKey, v)
+ return s
+}
+
+// BorderBottom determines whether or not to draw a bottom border.
+func (s Style) BorderBottom(v bool) Style {
+ s.set(borderBottomKey, v)
+ return s
+}
+
+// BorderLeft determines whether or not to draw a left border.
+func (s Style) BorderLeft(v bool) Style {
+ s.set(borderLeftKey, v)
+ return s
+}
+
+// BorderForeground is a shorthand function for setting all of the
+// foreground colors of the borders at once. The arguments work as follows:
+//
+// With one argument, the argument is applied to all sides.
+//
+// With two arguments, the arguments are applied to the vertical and horizontal
+// sides, in that order.
+//
+// With three arguments, the arguments are applied to the top side, the
+// horizontal sides, and the bottom side, in that order.
+//
+// With four arguments, the arguments are applied clockwise starting from the
+// top side, followed by the right side, then the bottom, and finally the left.
+//
+// With more than four arguments nothing will be set.
+func (s Style) BorderForeground(c ...TerminalColor) Style {
+ if len(c) == 0 {
+ return s
+ }
+
+ top, right, bottom, left, ok := whichSidesColor(c...)
+ if !ok {
+ return s
+ }
+
+ s.set(borderTopForegroundKey, top)
+ s.set(borderRightForegroundKey, right)
+ s.set(borderBottomForegroundKey, bottom)
+ s.set(borderLeftForegroundKey, left)
+
+ return s
+}
+
+// BorderTopForeground set the foreground color for the top of the border.
+func (s Style) BorderTopForeground(c TerminalColor) Style {
+ s.set(borderTopForegroundKey, c)
+ return s
+}
+
+// BorderRightForeground sets the foreground color for the right side of the
+// border.
+func (s Style) BorderRightForeground(c TerminalColor) Style {
+ s.set(borderRightForegroundKey, c)
+ return s
+}
+
+// BorderBottomForeground sets the foreground color for the bottom of the
+// border.
+func (s Style) BorderBottomForeground(c TerminalColor) Style {
+ s.set(borderBottomForegroundKey, c)
+ return s
+}
+
+// BorderLeftForeground sets the foreground color for the left side of the
+// border.
+func (s Style) BorderLeftForeground(c TerminalColor) Style {
+ s.set(borderLeftForegroundKey, c)
+ return s
+}
+
+// BorderBackground is a shorthand function for setting all of the
+// background colors of the borders at once. The arguments work as follows:
+//
+// With one argument, the argument is applied to all sides.
+//
+// With two arguments, the arguments are applied to the vertical and horizontal
+// sides, in that order.
+//
+// With three arguments, the arguments are applied to the top side, the
+// horizontal sides, and the bottom side, in that order.
+//
+// With four arguments, the arguments are applied clockwise starting from the
+// top side, followed by the right side, then the bottom, and finally the left.
+//
+// With more than four arguments nothing will be set.
+func (s Style) BorderBackground(c ...TerminalColor) Style {
+ if len(c) == 0 {
+ return s
+ }
+
+ top, right, bottom, left, ok := whichSidesColor(c...)
+ if !ok {
+ return s
+ }
+
+ s.set(borderTopBackgroundKey, top)
+ s.set(borderRightBackgroundKey, right)
+ s.set(borderBottomBackgroundKey, bottom)
+ s.set(borderLeftBackgroundKey, left)
+
+ return s
+}
+
+// BorderTopBackground sets the background color of the top of the border.
+func (s Style) BorderTopBackground(c TerminalColor) Style {
+ s.set(borderTopBackgroundKey, c)
+ return s
+}
+
+// BorderRightBackground sets the background color of right side the border.
+func (s Style) BorderRightBackground(c TerminalColor) Style {
+ s.set(borderRightBackgroundKey, c)
+ return s
+}
+
+// BorderBottomBackground sets the background color of the bottom of the
+// border.
+func (s Style) BorderBottomBackground(c TerminalColor) Style {
+ s.set(borderBottomBackgroundKey, c)
+ return s
+}
+
+// BorderLeftBackground set the background color of the left side of the
+// border.
+func (s Style) BorderLeftBackground(c TerminalColor) Style {
+ s.set(borderLeftBackgroundKey, c)
+ return s
+}
+
+// Inline makes rendering output one line and disables the rendering of
+// margins, padding and borders. This is useful when you need a style to apply
+// only to font rendering and don't want it to change any physical dimensions.
+// It works well with Style.MaxWidth.
+//
+// Because this in intended to be used at the time of render, this method will
+// not mutate the style and instead return a copy.
+//
+// Example:
+//
+// var userInput string = "..."
+// var userStyle = text.Style{ /* ... */ }
+// fmt.Println(userStyle.Inline(true).Render(userInput))
+func (s Style) Inline(v bool) Style {
+ o := s.Copy()
+ o.set(inlineKey, v)
+ return o
+}
+
+// MaxWidth applies a max width to a given style. This is useful in enforcing
+// a certain width at render time, particularly with arbitrary strings and
+// styles.
+//
+// Because this in intended to be used at the time of render, this method will
+// not mutate the style and instead return a copy.
+//
+// Example:
+//
+// var userInput string = "..."
+// var userStyle = text.Style{ /* ... */ }
+// fmt.Println(userStyle.MaxWidth(16).Render(userInput))
+func (s Style) MaxWidth(n int) Style {
+ o := s.Copy()
+ o.set(maxWidthKey, n)
+ return o
+}
+
+// MaxHeight applies a max height to a given style. This is useful in enforcing
+// a certain height at render time, particularly with arbitrary strings and
+// styles.
+//
+// Because this in intended to be used at the time of render, this method will
+// not mutate the style and instead returns a copy.
+func (s Style) MaxHeight(n int) Style {
+ o := s.Copy()
+ o.set(maxHeightKey, n)
+ return o
+}
+
+// NoTabConversion can be passed to [Style.TabWidth] to disable the replacement
+// of tabs with spaces at render time.
+const NoTabConversion = -1
+
+// TabWidth sets the number of spaces that a tab (/t) should be rendered as.
+// When set to 0, tabs will be removed. To disable the replacement of tabs with
+// spaces entirely, set this to [NoTabConversion].
+//
+// By default, tabs will be replaced with 4 spaces.
+func (s Style) TabWidth(n int) Style {
+ if n <= -1 {
+ n = -1
+ }
+ s.set(tabWidthKey, n)
+ return s
+}
+
+// UnderlineSpaces determines whether to underline spaces between words. By
+// default, this is true. Spaces can also be underlined without underlining the
+// text itself.
+func (s Style) UnderlineSpaces(v bool) Style {
+ s.set(underlineSpacesKey, v)
+ return s
+}
+
+// StrikethroughSpaces determines whether to apply strikethroughs to spaces
+// between words. By default, this is true. Spaces can also be struck without
+// underlining the text itself.
+func (s Style) StrikethroughSpaces(v bool) Style {
+ s.set(strikethroughSpacesKey, v)
+ return s
+}
+
+// Renderer sets the renderer for the style. This is useful for changing the
+// renderer for a style that is being used in a different context.
+func (s Style) Renderer(r *Renderer) Style {
+ s.r = r
+ return s
+}
+
+// whichSidesInt is a helper method for setting values on sides of a block based
+// on the number of arguments. It follows the CSS shorthand rules for blocks
+// like margin, padding. and borders. Here are how the rules work:
+//
+// 0 args: do nothing
+// 1 arg: all sides
+// 2 args: top -> bottom
+// 3 args: top -> horizontal -> bottom
+// 4 args: top -> right -> bottom -> left
+// 5+ args: do nothing.
+func whichSidesInt(i ...int) (top, right, bottom, left int, ok bool) {
+ switch len(i) {
+ case 1:
+ top = i[0]
+ bottom = i[0]
+ left = i[0]
+ right = i[0]
+ ok = true
+ case 2: //nolint:gomnd
+ top = i[0]
+ bottom = i[0]
+ left = i[1]
+ right = i[1]
+ ok = true
+ case 3: //nolint:gomnd
+ top = i[0]
+ left = i[1]
+ right = i[1]
+ bottom = i[2]
+ ok = true
+ case 4: //nolint:gomnd
+ top = i[0]
+ right = i[1]
+ bottom = i[2]
+ left = i[3]
+ ok = true
+ }
+ return top, right, bottom, left, ok
+}
+
+// whichSidesBool is like whichSidesInt, except it operates on a series of
+// boolean values. See the comment on whichSidesInt for details on how this
+// works.
+func whichSidesBool(i ...bool) (top, right, bottom, left bool, ok bool) {
+ switch len(i) {
+ case 1:
+ top = i[0]
+ bottom = i[0]
+ left = i[0]
+ right = i[0]
+ ok = true
+ case 2: //nolint:gomnd
+ top = i[0]
+ bottom = i[0]
+ left = i[1]
+ right = i[1]
+ ok = true
+ case 3: //nolint:gomnd
+ top = i[0]
+ left = i[1]
+ right = i[1]
+ bottom = i[2]
+ ok = true
+ case 4: //nolint:gomnd
+ top = i[0]
+ right = i[1]
+ bottom = i[2]
+ left = i[3]
+ ok = true
+ }
+ return top, right, bottom, left, ok
+}
+
+// whichSidesColor is like whichSides, except it operates on a series of
+// boolean values. See the comment on whichSidesInt for details on how this
+// works.
+func whichSidesColor(i ...TerminalColor) (top, right, bottom, left TerminalColor, ok bool) {
+ switch len(i) {
+ case 1:
+ top = i[0]
+ bottom = i[0]
+ left = i[0]
+ right = i[0]
+ ok = true
+ case 2: //nolint:gomnd
+ top = i[0]
+ bottom = i[0]
+ left = i[1]
+ right = i[1]
+ ok = true
+ case 3: //nolint:gomnd
+ top = i[0]
+ left = i[1]
+ right = i[1]
+ bottom = i[2]
+ ok = true
+ case 4: //nolint:gomnd
+ top = i[0]
+ right = i[1]
+ bottom = i[2]
+ left = i[3]
+ ok = true
+ }
+ return top, right, bottom, left, ok
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/size.go b/vendor/github.com/charmbracelet/lipgloss/size.go
new file mode 100644
index 0000000..439a5cb
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/size.go
@@ -0,0 +1,41 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/muesli/reflow/ansi"
+)
+
+// Width returns the cell width of characters in the string. ANSI sequences are
+// ignored and characters wider than one cell (such as Chinese characters and
+// emojis) are appropriately measured.
+//
+// You should use this instead of len(string) len([]rune(string) as neither
+// will give you accurate results.
+func Width(str string) (width int) {
+ for _, l := range strings.Split(str, "\n") {
+ w := ansi.PrintableRuneWidth(l)
+ if w > width {
+ width = w
+ }
+ }
+
+ return width
+}
+
+// Height returns height of a string in cells. This is done simply by
+// counting \n characters. If your strings use \r\n for newlines you should
+// convert them to \n first, or simply write a separate function for measuring
+// height.
+func Height(str string) int {
+ return strings.Count(str, "\n") + 1
+}
+
+// Size returns the width and height of the string in cells. ANSI sequences are
+// ignored and characters wider than one cell (such as Chinese characters and
+// emojis) are appropriately measured.
+func Size(str string) (width, height int) {
+ width = Width(str)
+ height = Height(str)
+ return width, height
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/style.go b/vendor/github.com/charmbracelet/lipgloss/style.go
new file mode 100644
index 0000000..ee50bcf
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/style.go
@@ -0,0 +1,519 @@
+package lipgloss
+
+import (
+ "strings"
+ "unicode"
+
+ "github.com/muesli/reflow/truncate"
+ "github.com/muesli/reflow/wordwrap"
+ "github.com/muesli/reflow/wrap"
+ "github.com/muesli/termenv"
+)
+
+const tabWidthDefault = 4
+
+// Property for a key.
+type propKey int
+
+// Available properties.
+const (
+ boldKey propKey = iota
+ italicKey
+ underlineKey
+ strikethroughKey
+ reverseKey
+ blinkKey
+ faintKey
+ foregroundKey
+ backgroundKey
+ widthKey
+ heightKey
+ alignHorizontalKey
+ alignVerticalKey
+
+ // Padding.
+ paddingTopKey
+ paddingRightKey
+ paddingBottomKey
+ paddingLeftKey
+
+ colorWhitespaceKey
+
+ // Margins.
+ marginTopKey
+ marginRightKey
+ marginBottomKey
+ marginLeftKey
+ marginBackgroundKey
+
+ // Border runes.
+ borderStyleKey
+
+ // Border edges.
+ borderTopKey
+ borderRightKey
+ borderBottomKey
+ borderLeftKey
+
+ // Border foreground colors.
+ borderTopForegroundKey
+ borderRightForegroundKey
+ borderBottomForegroundKey
+ borderLeftForegroundKey
+
+ // Border background colors.
+ borderTopBackgroundKey
+ borderRightBackgroundKey
+ borderBottomBackgroundKey
+ borderLeftBackgroundKey
+
+ inlineKey
+ maxWidthKey
+ maxHeightKey
+ tabWidthKey
+ underlineSpacesKey
+ strikethroughSpacesKey
+)
+
+// A set of properties.
+type rules map[propKey]interface{}
+
+// NewStyle returns a new, empty Style. While it's syntactic sugar for the
+// Style{} primitive, it's recommended to use this function for creating styles
+// in case the underlying implementation changes. It takes an optional string
+// value to be set as the underlying string value for this style.
+func NewStyle() Style {
+ return renderer.NewStyle()
+}
+
+// NewStyle returns a new, empty Style. While it's syntactic sugar for the
+// Style{} primitive, it's recommended to use this function for creating styles
+// in case the underlying implementation changes. It takes an optional string
+// value to be set as the underlying string value for this style.
+func (r *Renderer) NewStyle() Style {
+ s := Style{r: r}
+ return s
+}
+
+// Style contains a set of rules that comprise a style as a whole.
+type Style struct {
+ r *Renderer
+ rules map[propKey]interface{}
+ value string
+}
+
+// joinString joins a list of strings into a single string separated with a
+// space.
+func joinString(strs ...string) string {
+ return strings.Join(strs, " ")
+}
+
+// SetString sets the underlying string value for this style. To render once
+// the underlying string is set, use the Style.String. This method is
+// a convenience for cases when having a stringer implementation is handy, such
+// as when using fmt.Sprintf. You can also simply define a style and render out
+// strings directly with Style.Render.
+func (s Style) SetString(strs ...string) Style {
+ s.value = joinString(strs...)
+ return s
+}
+
+// Value returns the raw, unformatted, underlying string value for this style.
+func (s Style) Value() string {
+ return s.value
+}
+
+// String implements stringer for a Style, returning the rendered result based
+// on the rules in this style. An underlying string value must be set with
+// Style.SetString prior to using this method.
+func (s Style) String() string {
+ return s.Render()
+}
+
+// Copy returns a copy of this style, including any underlying string values.
+func (s Style) Copy() Style {
+ o := NewStyle()
+ o.init()
+ for k, v := range s.rules {
+ o.rules[k] = v
+ }
+ o.r = s.r
+ o.value = s.value
+ return o
+}
+
+// Inherit overlays the style in the argument onto this style by copying each explicitly
+// set value from the argument style onto this style if it is not already explicitly set.
+// Existing set values are kept intact and not overwritten.
+//
+// Margins, padding, and underlying string values are not inherited.
+func (s Style) Inherit(i Style) Style {
+ s.init()
+
+ for k, v := range i.rules {
+ switch k { //nolint:exhaustive
+ case marginTopKey, marginRightKey, marginBottomKey, marginLeftKey:
+ // Margins are not inherited
+ continue
+ case paddingTopKey, paddingRightKey, paddingBottomKey, paddingLeftKey:
+ // Padding is not inherited
+ continue
+ case backgroundKey:
+ // The margins also inherit the background color
+ if !s.isSet(marginBackgroundKey) && !i.isSet(marginBackgroundKey) {
+ s.rules[marginBackgroundKey] = v
+ }
+ }
+
+ if _, exists := s.rules[k]; exists {
+ continue
+ }
+ s.rules[k] = v
+ }
+ return s
+}
+
+// Render applies the defined style formatting to a given string.
+func (s Style) Render(strs ...string) string {
+ if s.r == nil {
+ s.r = renderer
+ }
+ if s.value != "" {
+ strs = append([]string{s.value}, strs...)
+ }
+
+ var (
+ str = joinString(strs...)
+
+ p = s.r.ColorProfile()
+ te = p.String()
+ teSpace = p.String()
+ teWhitespace = p.String()
+
+ bold = s.getAsBool(boldKey, false)
+ italic = s.getAsBool(italicKey, false)
+ underline = s.getAsBool(underlineKey, false)
+ strikethrough = s.getAsBool(strikethroughKey, false)
+ reverse = s.getAsBool(reverseKey, false)
+ blink = s.getAsBool(blinkKey, false)
+ faint = s.getAsBool(faintKey, false)
+
+ fg = s.getAsColor(foregroundKey)
+ bg = s.getAsColor(backgroundKey)
+
+ width = s.getAsInt(widthKey)
+ height = s.getAsInt(heightKey)
+ horizontalAlign = s.getAsPosition(alignHorizontalKey)
+ verticalAlign = s.getAsPosition(alignVerticalKey)
+
+ topPadding = s.getAsInt(paddingTopKey)
+ rightPadding = s.getAsInt(paddingRightKey)
+ bottomPadding = s.getAsInt(paddingBottomKey)
+ leftPadding = s.getAsInt(paddingLeftKey)
+
+ colorWhitespace = s.getAsBool(colorWhitespaceKey, true)
+ inline = s.getAsBool(inlineKey, false)
+ maxWidth = s.getAsInt(maxWidthKey)
+ maxHeight = s.getAsInt(maxHeightKey)
+
+ underlineSpaces = underline && s.getAsBool(underlineSpacesKey, true)
+ strikethroughSpaces = strikethrough && s.getAsBool(strikethroughSpacesKey, true)
+
+ // Do we need to style whitespace (padding and space outside
+ // paragraphs) separately?
+ styleWhitespace = reverse
+
+ // Do we need to style spaces separately?
+ useSpaceStyler = underlineSpaces || strikethroughSpaces
+ )
+
+ if len(s.rules) == 0 {
+ return s.maybeConvertTabs(str)
+ }
+
+ // Enable support for ANSI on the legacy Windows cmd.exe console. This is a
+ // no-op on non-Windows systems and on Windows runs only once.
+ enableLegacyWindowsANSI()
+
+ if bold {
+ te = te.Bold()
+ }
+ if italic {
+ te = te.Italic()
+ }
+ if underline {
+ te = te.Underline()
+ }
+ if reverse {
+ if reverse {
+ teWhitespace = teWhitespace.Reverse()
+ }
+ te = te.Reverse()
+ }
+ if blink {
+ te = te.Blink()
+ }
+ if faint {
+ te = te.Faint()
+ }
+
+ if fg != noColor {
+ te = te.Foreground(fg.color(s.r))
+ if styleWhitespace {
+ teWhitespace = teWhitespace.Foreground(fg.color(s.r))
+ }
+ if useSpaceStyler {
+ teSpace = teSpace.Foreground(fg.color(s.r))
+ }
+ }
+
+ if bg != noColor {
+ te = te.Background(bg.color(s.r))
+ if colorWhitespace {
+ teWhitespace = teWhitespace.Background(bg.color(s.r))
+ }
+ if useSpaceStyler {
+ teSpace = teSpace.Background(bg.color(s.r))
+ }
+ }
+
+ if underline {
+ te = te.Underline()
+ }
+ if strikethrough {
+ te = te.CrossOut()
+ }
+
+ if underlineSpaces {
+ teSpace = teSpace.Underline()
+ }
+ if strikethroughSpaces {
+ teSpace = teSpace.CrossOut()
+ }
+
+ // Potentially convert tabs to spaces
+ str = s.maybeConvertTabs(str)
+
+ // Strip newlines in single line mode
+ if inline {
+ str = strings.ReplaceAll(str, "\n", "")
+ }
+
+ // Word wrap
+ if !inline && width > 0 {
+ wrapAt := width - leftPadding - rightPadding
+ str = wordwrap.String(str, wrapAt)
+ str = wrap.String(str, wrapAt) // force-wrap long strings
+ }
+
+ // Render core text
+ {
+ var b strings.Builder
+
+ l := strings.Split(str, "\n")
+ for i := range l {
+ if useSpaceStyler {
+ // Look for spaces and apply a different styler
+ for _, r := range l[i] {
+ if unicode.IsSpace(r) {
+ b.WriteString(teSpace.Styled(string(r)))
+ continue
+ }
+ b.WriteString(te.Styled(string(r)))
+ }
+ } else {
+ b.WriteString(te.Styled(l[i]))
+ }
+ if i != len(l)-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ str = b.String()
+ }
+
+ // Padding
+ if !inline {
+ if leftPadding > 0 {
+ var st *termenv.Style
+ if colorWhitespace || styleWhitespace {
+ st = &teWhitespace
+ }
+ str = padLeft(str, leftPadding, st)
+ }
+
+ if rightPadding > 0 {
+ var st *termenv.Style
+ if colorWhitespace || styleWhitespace {
+ st = &teWhitespace
+ }
+ str = padRight(str, rightPadding, st)
+ }
+
+ if topPadding > 0 {
+ str = strings.Repeat("\n", topPadding) + str
+ }
+
+ if bottomPadding > 0 {
+ str += strings.Repeat("\n", bottomPadding)
+ }
+ }
+
+ // Height
+ if height > 0 {
+ str = alignTextVertical(str, verticalAlign, height, nil)
+ }
+
+ // Set alignment. This will also pad short lines with spaces so that all
+ // lines are the same length, so we run it under a few different conditions
+ // beyond alignment.
+ {
+ numLines := strings.Count(str, "\n")
+
+ if !(numLines == 0 && width == 0) {
+ var st *termenv.Style
+ if colorWhitespace || styleWhitespace {
+ st = &teWhitespace
+ }
+ str = alignTextHorizontal(str, horizontalAlign, width, st)
+ }
+ }
+
+ if !inline {
+ str = s.applyBorder(str)
+ str = s.applyMargins(str, inline)
+ }
+
+ // Truncate according to MaxWidth
+ if maxWidth > 0 {
+ lines := strings.Split(str, "\n")
+
+ for i := range lines {
+ lines[i] = truncate.String(lines[i], uint(maxWidth))
+ }
+
+ str = strings.Join(lines, "\n")
+ }
+
+ // Truncate according to MaxHeight
+ if maxHeight > 0 {
+ lines := strings.Split(str, "\n")
+ str = strings.Join(lines[:min(maxHeight, len(lines))], "\n")
+ }
+
+ return str
+}
+
+func (s Style) maybeConvertTabs(str string) string {
+ tw := tabWidthDefault
+ if s.isSet(tabWidthKey) {
+ tw = s.getAsInt(tabWidthKey)
+ }
+ switch tw {
+ case -1:
+ return str
+ case 0:
+ return strings.ReplaceAll(str, "\t", "")
+ default:
+ return strings.ReplaceAll(str, "\t", strings.Repeat(" ", tw))
+ }
+}
+
+func (s Style) applyMargins(str string, inline bool) string {
+ var (
+ topMargin = s.getAsInt(marginTopKey)
+ rightMargin = s.getAsInt(marginRightKey)
+ bottomMargin = s.getAsInt(marginBottomKey)
+ leftMargin = s.getAsInt(marginLeftKey)
+
+ styler termenv.Style
+ )
+
+ bgc := s.getAsColor(marginBackgroundKey)
+ if bgc != noColor {
+ styler = styler.Background(bgc.color(s.r))
+ }
+
+ // Add left and right margin
+ str = padLeft(str, leftMargin, &styler)
+ str = padRight(str, rightMargin, &styler)
+
+ // Top/bottom margin
+ if !inline {
+ _, width := getLines(str)
+ spaces := strings.Repeat(" ", width)
+
+ if topMargin > 0 {
+ str = styler.Styled(strings.Repeat(spaces+"\n", topMargin)) + str
+ }
+ if bottomMargin > 0 {
+ str += styler.Styled(strings.Repeat("\n"+spaces, bottomMargin))
+ }
+ }
+
+ return str
+}
+
+// Apply left padding.
+func padLeft(str string, n int, style *termenv.Style) string {
+ if n == 0 {
+ return str
+ }
+
+ sp := strings.Repeat(" ", n)
+ if style != nil {
+ sp = style.Styled(sp)
+ }
+
+ b := strings.Builder{}
+ l := strings.Split(str, "\n")
+
+ for i := range l {
+ b.WriteString(sp)
+ b.WriteString(l[i])
+ if i != len(l)-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ return b.String()
+}
+
+// Apply right padding.
+func padRight(str string, n int, style *termenv.Style) string {
+ if n == 0 || str == "" {
+ return str
+ }
+
+ sp := strings.Repeat(" ", n)
+ if style != nil {
+ sp = style.Styled(sp)
+ }
+
+ b := strings.Builder{}
+ l := strings.Split(str, "\n")
+
+ for i := range l {
+ b.WriteString(l[i])
+ b.WriteString(sp)
+ if i != len(l)-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ return b.String()
+}
+
+func max(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/unset.go b/vendor/github.com/charmbracelet/lipgloss/unset.go
new file mode 100644
index 0000000..770734c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/unset.go
@@ -0,0 +1,312 @@
+package lipgloss
+
+// UnsetBold removes the bold style rule, if set.
+func (s Style) UnsetBold() Style {
+ delete(s.rules, boldKey)
+ return s
+}
+
+// UnsetItalic removes the italic style rule, if set.
+func (s Style) UnsetItalic() Style {
+ delete(s.rules, italicKey)
+ return s
+}
+
+// UnsetUnderline removes the underline style rule, if set.
+func (s Style) UnsetUnderline() Style {
+ delete(s.rules, underlineKey)
+ return s
+}
+
+// UnsetStrikethrough removes the strikethrough style rule, if set.
+func (s Style) UnsetStrikethrough() Style {
+ delete(s.rules, strikethroughKey)
+ return s
+}
+
+// UnsetReverse removes the reverse style rule, if set.
+func (s Style) UnsetReverse() Style {
+ delete(s.rules, reverseKey)
+ return s
+}
+
+// UnsetBlink removes the blink style rule, if set.
+func (s Style) UnsetBlink() Style {
+ delete(s.rules, blinkKey)
+ return s
+}
+
+// UnsetFaint removes the faint style rule, if set.
+func (s Style) UnsetFaint() Style {
+ delete(s.rules, faintKey)
+ return s
+}
+
+// UnsetForeground removes the foreground style rule, if set.
+func (s Style) UnsetForeground() Style {
+ delete(s.rules, foregroundKey)
+ return s
+}
+
+// UnsetBackground removes the background style rule, if set.
+func (s Style) UnsetBackground() Style {
+ delete(s.rules, backgroundKey)
+ return s
+}
+
+// UnsetWidth removes the width style rule, if set.
+func (s Style) UnsetWidth() Style {
+ delete(s.rules, widthKey)
+ return s
+}
+
+// UnsetHeight removes the height style rule, if set.
+func (s Style) UnsetHeight() Style {
+ delete(s.rules, heightKey)
+ return s
+}
+
+// UnsetAlign removes the horizontal and vertical text alignment style rule, if set.
+func (s Style) UnsetAlign() Style {
+ delete(s.rules, alignHorizontalKey)
+ delete(s.rules, alignVerticalKey)
+ return s
+}
+
+// UnsetAlignHorizontal removes the horizontal text alignment style rule, if set.
+func (s Style) UnsetAlignHorizontal() Style {
+ delete(s.rules, alignHorizontalKey)
+ return s
+}
+
+// UnsetAlignVertical removes the vertical text alignment style rule, if set.
+func (s Style) UnsetAlignVertical() Style {
+ delete(s.rules, alignVerticalKey)
+ return s
+}
+
+// UnsetPadding removes all padding style rules.
+func (s Style) UnsetPadding() Style {
+ delete(s.rules, paddingLeftKey)
+ delete(s.rules, paddingRightKey)
+ delete(s.rules, paddingTopKey)
+ delete(s.rules, paddingBottomKey)
+ return s
+}
+
+// UnsetPaddingLeft removes the left padding style rule, if set.
+func (s Style) UnsetPaddingLeft() Style {
+ delete(s.rules, paddingLeftKey)
+ return s
+}
+
+// UnsetPaddingRight removes the right padding style rule, if set.
+func (s Style) UnsetPaddingRight() Style {
+ delete(s.rules, paddingRightKey)
+ return s
+}
+
+// UnsetPaddingTop removes the top padding style rule, if set.
+func (s Style) UnsetPaddingTop() Style {
+ delete(s.rules, paddingTopKey)
+ return s
+}
+
+// UnsetPaddingBottom removes the bottom padding style rule, if set.
+func (s Style) UnsetPaddingBottom() Style {
+ delete(s.rules, paddingBottomKey)
+ return s
+}
+
+// UnsetColorWhitespace removes the rule for coloring padding, if set.
+func (s Style) UnsetColorWhitespace() Style {
+ delete(s.rules, colorWhitespaceKey)
+ return s
+}
+
+// UnsetMargins removes all margin style rules.
+func (s Style) UnsetMargins() Style {
+ delete(s.rules, marginLeftKey)
+ delete(s.rules, marginRightKey)
+ delete(s.rules, marginTopKey)
+ delete(s.rules, marginBottomKey)
+ return s
+}
+
+// UnsetMarginLeft removes the left margin style rule, if set.
+func (s Style) UnsetMarginLeft() Style {
+ delete(s.rules, marginLeftKey)
+ return s
+}
+
+// UnsetMarginRight removes the right margin style rule, if set.
+func (s Style) UnsetMarginRight() Style {
+ delete(s.rules, marginRightKey)
+ return s
+}
+
+// UnsetMarginTop removes the top margin style rule, if set.
+func (s Style) UnsetMarginTop() Style {
+ delete(s.rules, marginTopKey)
+ return s
+}
+
+// UnsetMarginBottom removes the bottom margin style rule, if set.
+func (s Style) UnsetMarginBottom() Style {
+ delete(s.rules, marginBottomKey)
+ return s
+}
+
+// UnsetMarginBackground removes the margin's background color. Note that the
+// margin's background color can be set from the background color of another
+// style during inheritance.
+func (s Style) UnsetMarginBackground() Style {
+ delete(s.rules, marginBackgroundKey)
+ return s
+}
+
+// UnsetBorderStyle removes the border style rule, if set.
+func (s Style) UnsetBorderStyle() Style {
+ delete(s.rules, borderStyleKey)
+ return s
+}
+
+// UnsetBorderTop removes the border top style rule, if set.
+func (s Style) UnsetBorderTop() Style {
+ delete(s.rules, borderTopKey)
+ return s
+}
+
+// UnsetBorderRight removes the border right style rule, if set.
+func (s Style) UnsetBorderRight() Style {
+ delete(s.rules, borderRightKey)
+ return s
+}
+
+// UnsetBorderBottom removes the border bottom style rule, if set.
+func (s Style) UnsetBorderBottom() Style {
+ delete(s.rules, borderBottomKey)
+ return s
+}
+
+// UnsetBorderLeft removes the border left style rule, if set.
+func (s Style) UnsetBorderLeft() Style {
+ delete(s.rules, borderLeftKey)
+ return s
+}
+
+// UnsetBorderForeground removes all border foreground color styles, if set.
+func (s Style) UnsetBorderForeground() Style {
+ delete(s.rules, borderTopForegroundKey)
+ delete(s.rules, borderRightForegroundKey)
+ delete(s.rules, borderBottomForegroundKey)
+ delete(s.rules, borderLeftForegroundKey)
+ return s
+}
+
+// UnsetBorderTopForeground removes the top border foreground color rule,
+// if set.
+func (s Style) UnsetBorderTopForeground() Style {
+ delete(s.rules, borderTopForegroundKey)
+ return s
+}
+
+// UnsetBorderRightForeground removes the right border foreground color rule,
+// if set.
+func (s Style) UnsetBorderRightForeground() Style {
+ delete(s.rules, borderRightForegroundKey)
+ return s
+}
+
+// UnsetBorderBottomForeground removes the bottom border foreground color
+// rule, if set.
+func (s Style) UnsetBorderBottomForeground() Style {
+ delete(s.rules, borderBottomForegroundKey)
+ return s
+}
+
+// UnsetBorderLeftForeground removes the left border foreground color rule,
+// if set.
+func (s Style) UnsetBorderLeftForeground() Style {
+ delete(s.rules, borderLeftForegroundKey)
+ return s
+}
+
+// UnsetBorderBackground removes all border background color styles, if
+// set.
+func (s Style) UnsetBorderBackground() Style {
+ delete(s.rules, borderTopBackgroundKey)
+ delete(s.rules, borderRightBackgroundKey)
+ delete(s.rules, borderBottomBackgroundKey)
+ delete(s.rules, borderLeftBackgroundKey)
+ return s
+}
+
+// UnsetBorderTopBackgroundColor removes the top border background color rule,
+// if set.
+func (s Style) UnsetBorderTopBackgroundColor() Style {
+ delete(s.rules, borderTopBackgroundKey)
+ return s
+}
+
+// UnsetBorderRightBackground removes the right border background color
+// rule, if set.
+func (s Style) UnsetBorderRightBackground() Style {
+ delete(s.rules, borderRightBackgroundKey)
+ return s
+}
+
+// UnsetBorderBottomBackground removes the bottom border background color
+// rule, if set.
+func (s Style) UnsetBorderBottomBackground() Style {
+ delete(s.rules, borderBottomBackgroundKey)
+ return s
+}
+
+// UnsetBorderLeftBackground removes the left border color rule, if set.
+func (s Style) UnsetBorderLeftBackground() Style {
+ delete(s.rules, borderLeftBackgroundKey)
+ return s
+}
+
+// UnsetInline removes the inline style rule, if set.
+func (s Style) UnsetInline() Style {
+ delete(s.rules, inlineKey)
+ return s
+}
+
+// UnsetMaxWidth removes the max width style rule, if set.
+func (s Style) UnsetMaxWidth() Style {
+ delete(s.rules, maxWidthKey)
+ return s
+}
+
+// UnsetMaxHeight removes the max height style rule, if set.
+func (s Style) UnsetMaxHeight() Style {
+ delete(s.rules, maxHeightKey)
+ return s
+}
+
+// UnsetTabWidth removes the tab width style rule, if set.
+func (s Style) UnsetTabWidth() Style {
+ delete(s.rules, tabWidthKey)
+ return s
+}
+
+// UnsetUnderlineSpaces removes the value set by UnderlineSpaces.
+func (s Style) UnsetUnderlineSpaces() Style {
+ delete(s.rules, underlineSpacesKey)
+ return s
+}
+
+// UnsetStrikethroughSpaces removes the value set by StrikethroughSpaces.
+func (s Style) UnsetStrikethroughSpaces() Style {
+ delete(s.rules, strikethroughSpacesKey)
+ return s
+}
+
+// UnsetString sets the underlying string value to the empty string.
+func (s Style) UnsetString() Style {
+ s.value = ""
+ return s
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/whitespace.go b/vendor/github.com/charmbracelet/lipgloss/whitespace.go
new file mode 100644
index 0000000..78815fe
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/whitespace.go
@@ -0,0 +1,83 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/muesli/reflow/ansi"
+ "github.com/muesli/termenv"
+)
+
+// whitespace is a whitespace renderer.
+type whitespace struct {
+ re *Renderer
+ style termenv.Style
+ chars string
+}
+
+// newWhitespace creates a new whitespace renderer. The order of the options
+// matters, if you're using WithWhitespaceRenderer, make sure it comes first as
+// other options might depend on it.
+func newWhitespace(r *Renderer, opts ...WhitespaceOption) *whitespace {
+ w := &whitespace{
+ re: r,
+ style: r.ColorProfile().String(),
+ }
+ for _, opt := range opts {
+ opt(w)
+ }
+ return w
+}
+
+// Render whitespaces.
+func (w whitespace) render(width int) string {
+ if w.chars == "" {
+ w.chars = " "
+ }
+
+ r := []rune(w.chars)
+ j := 0
+ b := strings.Builder{}
+
+ // Cycle through runes and print them into the whitespace.
+ for i := 0; i < width; {
+ b.WriteRune(r[j])
+ j++
+ if j >= len(r) {
+ j = 0
+ }
+ i += ansi.PrintableRuneWidth(string(r[j]))
+ }
+
+ // Fill any extra gaps white spaces. This might be necessary if any runes
+ // are more than one cell wide, which could leave a one-rune gap.
+ short := width - ansi.PrintableRuneWidth(b.String())
+ if short > 0 {
+ b.WriteString(strings.Repeat(" ", short))
+ }
+
+ return w.style.Styled(b.String())
+}
+
+// WhitespaceOption sets a styling rule for rendering whitespace.
+type WhitespaceOption func(*whitespace)
+
+// WithWhitespaceForeground sets the color of the characters in the whitespace.
+func WithWhitespaceForeground(c TerminalColor) WhitespaceOption {
+ return func(w *whitespace) {
+ w.style = w.style.Foreground(c.color(w.re))
+ }
+}
+
+// WithWhitespaceBackground sets the background color of the whitespace.
+func WithWhitespaceBackground(c TerminalColor) WhitespaceOption {
+ return func(w *whitespace) {
+ w.style = w.style.Background(c.color(w.re))
+ }
+}
+
+// WithWhitespaceChars sets the characters to be rendered in the whitespace.
+func WithWhitespaceChars(s string) WhitespaceOption {
+ return func(w *whitespace) {
+ w.chars = s
+ }
+}
diff --git a/vendor/github.com/containerd/console/.golangci.yml b/vendor/github.com/containerd/console/.golangci.yml
new file mode 100644
index 0000000..abe3d84
--- /dev/null
+++ b/vendor/github.com/containerd/console/.golangci.yml
@@ -0,0 +1,20 @@
+linters:
+ enable:
+ - gofmt
+ - goimports
+ - ineffassign
+ - misspell
+ - revive
+ - staticcheck
+ - structcheck
+ - unconvert
+ - unused
+ - varcheck
+ - vet
+ disable:
+ - errcheck
+
+run:
+ timeout: 3m
+ skip-dirs:
+ - vendor
diff --git a/vendor/github.com/containerd/console/LICENSE b/vendor/github.com/containerd/console/LICENSE
new file mode 100644
index 0000000..584149b
--- /dev/null
+++ b/vendor/github.com/containerd/console/LICENSE
@@ -0,0 +1,191 @@
+
+ Apache License
+ Version 2.0, January 2004
+ https://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright The containerd Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/containerd/console/README.md b/vendor/github.com/containerd/console/README.md
new file mode 100644
index 0000000..a849a72
--- /dev/null
+++ b/vendor/github.com/containerd/console/README.md
@@ -0,0 +1,29 @@
+# console
+
+[](https://pkg.go.dev/github.com/containerd/console)
+[](https://github.com/containerd/console/actions?query=workflow%3ACI)
+[](https://goreportcard.com/report/github.com/containerd/console)
+
+Golang package for dealing with consoles. Light on deps and a simple API.
+
+## Modifying the current process
+
+```go
+current := console.Current()
+defer current.Reset()
+
+if err := current.SetRaw(); err != nil {
+}
+ws, err := current.Size()
+current.Resize(ws)
+```
+
+## Project details
+
+console is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
+As a containerd sub-project, you will find the:
+ * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md),
+ * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS),
+ * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md)
+
+information in our [`containerd/project`](https://github.com/containerd/project) repository.
diff --git a/vendor/github.com/containerd/console/console.go b/vendor/github.com/containerd/console/console.go
new file mode 100644
index 0000000..dd587d8
--- /dev/null
+++ b/vendor/github.com/containerd/console/console.go
@@ -0,0 +1,90 @@
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "errors"
+ "io"
+ "os"
+)
+
+var (
+ ErrNotAConsole = errors.New("provided file is not a console")
+ ErrNotImplemented = errors.New("not implemented")
+)
+
+type File interface {
+ io.ReadWriteCloser
+
+ // Fd returns its file descriptor
+ Fd() uintptr
+ // Name returns its file name
+ Name() string
+}
+
+type Console interface {
+ File
+
+ // Resize resizes the console to the provided window size
+ Resize(WinSize) error
+ // ResizeFrom resizes the calling console to the size of the
+ // provided console
+ ResizeFrom(Console) error
+ // SetRaw sets the console in raw mode
+ SetRaw() error
+ // DisableEcho disables echo on the console
+ DisableEcho() error
+ // Reset restores the console to its original state
+ Reset() error
+ // Size returns the window size of the console
+ Size() (WinSize, error)
+}
+
+// WinSize specifies the window size of the console
+type WinSize struct {
+ // Height of the console
+ Height uint16
+ // Width of the console
+ Width uint16
+ x uint16
+ y uint16
+}
+
+// Current returns the current process' console
+func Current() (c Console) {
+ var err error
+ // Usually all three streams (stdin, stdout, and stderr)
+ // are open to the same console, but some might be redirected,
+ // so try all three.
+ for _, s := range []*os.File{os.Stderr, os.Stdout, os.Stdin} {
+ if c, err = ConsoleFromFile(s); err == nil {
+ return c
+ }
+ }
+ // One of the std streams should always be a console
+ // for the design of this function.
+ panic(err)
+}
+
+// ConsoleFromFile returns a console using the provided file
+// nolint:revive
+func ConsoleFromFile(f File) (Console, error) {
+ if err := checkConsole(f); err != nil {
+ return nil, err
+ }
+ return newMaster(f)
+}
diff --git a/vendor/github.com/containerd/console/console_linux.go b/vendor/github.com/containerd/console/console_linux.go
new file mode 100644
index 0000000..28b77b7
--- /dev/null
+++ b/vendor/github.com/containerd/console/console_linux.go
@@ -0,0 +1,281 @@
+//go:build linux
+// +build linux
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "io"
+ "os"
+ "sync"
+
+ "golang.org/x/sys/unix"
+)
+
+const (
+ maxEvents = 128
+)
+
+// Epoller manages multiple epoll consoles using edge-triggered epoll api so we
+// dont have to deal with repeated wake-up of EPOLLER or EPOLLHUP.
+// For more details, see:
+// - https://github.com/systemd/systemd/pull/4262
+// - https://github.com/moby/moby/issues/27202
+//
+// Example usage of Epoller and EpollConsole can be as follow:
+//
+// epoller, _ := NewEpoller()
+// epollConsole, _ := epoller.Add(console)
+// go epoller.Wait()
+// var (
+// b bytes.Buffer
+// wg sync.WaitGroup
+// )
+// wg.Add(1)
+// go func() {
+// io.Copy(&b, epollConsole)
+// wg.Done()
+// }()
+// // perform I/O on the console
+// epollConsole.Shutdown(epoller.CloseConsole)
+// wg.Wait()
+// epollConsole.Close()
+type Epoller struct {
+ efd int
+ mu sync.Mutex
+ fdMapping map[int]*EpollConsole
+ closeOnce sync.Once
+}
+
+// NewEpoller returns an instance of epoller with a valid epoll fd.
+func NewEpoller() (*Epoller, error) {
+ efd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC)
+ if err != nil {
+ return nil, err
+ }
+ return &Epoller{
+ efd: efd,
+ fdMapping: make(map[int]*EpollConsole),
+ }, nil
+}
+
+// Add creates an epoll console based on the provided console. The console will
+// be registered with EPOLLET (i.e. using edge-triggered notification) and its
+// file descriptor will be set to non-blocking mode. After this, user should use
+// the return console to perform I/O.
+func (e *Epoller) Add(console Console) (*EpollConsole, error) {
+ sysfd := int(console.Fd())
+ // Set sysfd to non-blocking mode
+ if err := unix.SetNonblock(sysfd, true); err != nil {
+ return nil, err
+ }
+
+ ev := unix.EpollEvent{
+ Events: unix.EPOLLIN | unix.EPOLLOUT | unix.EPOLLRDHUP | unix.EPOLLET,
+ Fd: int32(sysfd),
+ }
+ if err := unix.EpollCtl(e.efd, unix.EPOLL_CTL_ADD, sysfd, &ev); err != nil {
+ return nil, err
+ }
+ ef := &EpollConsole{
+ Console: console,
+ sysfd: sysfd,
+ readc: sync.NewCond(&sync.Mutex{}),
+ writec: sync.NewCond(&sync.Mutex{}),
+ }
+ e.mu.Lock()
+ e.fdMapping[sysfd] = ef
+ e.mu.Unlock()
+ return ef, nil
+}
+
+// Wait starts the loop to wait for its consoles' notifications and signal
+// appropriate console that it can perform I/O.
+func (e *Epoller) Wait() error {
+ events := make([]unix.EpollEvent, maxEvents)
+ for {
+ n, err := unix.EpollWait(e.efd, events, -1)
+ if err != nil {
+ // EINTR: The call was interrupted by a signal handler before either
+ // any of the requested events occurred or the timeout expired
+ if err == unix.EINTR {
+ continue
+ }
+ return err
+ }
+ for i := 0; i < n; i++ {
+ ev := &events[i]
+ // the console is ready to be read from
+ if ev.Events&(unix.EPOLLIN|unix.EPOLLHUP|unix.EPOLLERR) != 0 {
+ if epfile := e.getConsole(int(ev.Fd)); epfile != nil {
+ epfile.signalRead()
+ }
+ }
+ // the console is ready to be written to
+ if ev.Events&(unix.EPOLLOUT|unix.EPOLLHUP|unix.EPOLLERR) != 0 {
+ if epfile := e.getConsole(int(ev.Fd)); epfile != nil {
+ epfile.signalWrite()
+ }
+ }
+ }
+ }
+}
+
+// CloseConsole unregisters the console's file descriptor from epoll interface
+func (e *Epoller) CloseConsole(fd int) error {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ delete(e.fdMapping, fd)
+ return unix.EpollCtl(e.efd, unix.EPOLL_CTL_DEL, fd, &unix.EpollEvent{})
+}
+
+func (e *Epoller) getConsole(sysfd int) *EpollConsole {
+ e.mu.Lock()
+ f := e.fdMapping[sysfd]
+ e.mu.Unlock()
+ return f
+}
+
+// Close closes the epoll fd
+func (e *Epoller) Close() error {
+ closeErr := os.ErrClosed // default to "file already closed"
+ e.closeOnce.Do(func() {
+ closeErr = unix.Close(e.efd)
+ })
+ return closeErr
+}
+
+// EpollConsole acts like a console but registers its file descriptor with an
+// epoll fd and uses epoll API to perform I/O.
+type EpollConsole struct {
+ Console
+ readc *sync.Cond
+ writec *sync.Cond
+ sysfd int
+ closed bool
+}
+
+// Read reads up to len(p) bytes into p. It returns the number of bytes read
+// (0 <= n <= len(p)) and any error encountered.
+//
+// If the console's read returns EAGAIN or EIO, we assume that it's a
+// temporary error because the other side went away and wait for the signal
+// generated by epoll event to continue.
+func (ec *EpollConsole) Read(p []byte) (n int, err error) {
+ var read int
+ ec.readc.L.Lock()
+ defer ec.readc.L.Unlock()
+ for {
+ read, err = ec.Console.Read(p[n:])
+ n += read
+ if err != nil {
+ var hangup bool
+ if perr, ok := err.(*os.PathError); ok {
+ hangup = (perr.Err == unix.EAGAIN || perr.Err == unix.EIO)
+ } else {
+ hangup = (err == unix.EAGAIN || err == unix.EIO)
+ }
+ // if the other end disappear, assume this is temporary and wait for the
+ // signal to continue again. Unless we didnt read anything and the
+ // console is already marked as closed then we should exit
+ if hangup && !(n == 0 && len(p) > 0 && ec.closed) {
+ ec.readc.Wait()
+ continue
+ }
+ }
+ break
+ }
+ // if we didnt read anything then return io.EOF to end gracefully
+ if n == 0 && len(p) > 0 && err == nil {
+ err = io.EOF
+ }
+ // signal for others that we finished the read
+ ec.readc.Signal()
+ return n, err
+}
+
+// Writes len(p) bytes from p to the console. It returns the number of bytes
+// written from p (0 <= n <= len(p)) and any error encountered that caused
+// the write to stop early.
+//
+// If writes to the console returns EAGAIN or EIO, we assume that it's a
+// temporary error because the other side went away and wait for the signal
+// generated by epoll event to continue.
+func (ec *EpollConsole) Write(p []byte) (n int, err error) {
+ var written int
+ ec.writec.L.Lock()
+ defer ec.writec.L.Unlock()
+ for {
+ written, err = ec.Console.Write(p[n:])
+ n += written
+ if err != nil {
+ var hangup bool
+ if perr, ok := err.(*os.PathError); ok {
+ hangup = (perr.Err == unix.EAGAIN || perr.Err == unix.EIO)
+ } else {
+ hangup = (err == unix.EAGAIN || err == unix.EIO)
+ }
+ // if the other end disappears, assume this is temporary and wait for the
+ // signal to continue again.
+ if hangup {
+ ec.writec.Wait()
+ continue
+ }
+ }
+ // unrecoverable error, break the loop and return the error
+ break
+ }
+ if n < len(p) && err == nil {
+ err = io.ErrShortWrite
+ }
+ // signal for others that we finished the write
+ ec.writec.Signal()
+ return n, err
+}
+
+// Shutdown closes the file descriptor and signals call waiters for this fd.
+// It accepts a callback which will be called with the console's fd. The
+// callback typically will be used to do further cleanup such as unregister the
+// console's fd from the epoll interface.
+// User should call Shutdown and wait for all I/O operation to be finished
+// before closing the console.
+func (ec *EpollConsole) Shutdown(close func(int) error) error {
+ ec.readc.L.Lock()
+ defer ec.readc.L.Unlock()
+ ec.writec.L.Lock()
+ defer ec.writec.L.Unlock()
+
+ ec.readc.Broadcast()
+ ec.writec.Broadcast()
+ ec.closed = true
+ return close(ec.sysfd)
+}
+
+// signalRead signals that the console is readable.
+func (ec *EpollConsole) signalRead() {
+ ec.readc.L.Lock()
+ ec.readc.Signal()
+ ec.readc.L.Unlock()
+}
+
+// signalWrite signals that the console is writable.
+func (ec *EpollConsole) signalWrite() {
+ ec.writec.L.Lock()
+ ec.writec.Signal()
+ ec.writec.L.Unlock()
+}
diff --git a/vendor/github.com/containerd/console/console_other.go b/vendor/github.com/containerd/console/console_other.go
new file mode 100644
index 0000000..933dfad
--- /dev/null
+++ b/vendor/github.com/containerd/console/console_other.go
@@ -0,0 +1,36 @@
+//go:build !darwin && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows && !zos
+// +build !darwin,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows,!zos
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+// NewPty creates a new pty pair
+// The master is returned as the first console and a string
+// with the path to the pty slave is returned as the second
+func NewPty() (Console, string, error) {
+ return nil, "", ErrNotImplemented
+}
+
+// checkConsole checks if the provided file is a console
+func checkConsole(f File) error {
+ return ErrNotAConsole
+}
+
+func newMaster(f File) (Console, error) {
+ return nil, ErrNotImplemented
+}
diff --git a/vendor/github.com/containerd/console/console_unix.go b/vendor/github.com/containerd/console/console_unix.go
new file mode 100644
index 0000000..161f5d1
--- /dev/null
+++ b/vendor/github.com/containerd/console/console_unix.go
@@ -0,0 +1,157 @@
+//go:build darwin || freebsd || linux || netbsd || openbsd || zos
+// +build darwin freebsd linux netbsd openbsd zos
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "golang.org/x/sys/unix"
+)
+
+// NewPty creates a new pty pair
+// The master is returned as the first console and a string
+// with the path to the pty slave is returned as the second
+func NewPty() (Console, string, error) {
+ f, err := openpt()
+ if err != nil {
+ return nil, "", err
+ }
+ slave, err := ptsname(f)
+ if err != nil {
+ return nil, "", err
+ }
+ if err := unlockpt(f); err != nil {
+ return nil, "", err
+ }
+ m, err := newMaster(f)
+ if err != nil {
+ return nil, "", err
+ }
+ return m, slave, nil
+}
+
+type master struct {
+ f File
+ original *unix.Termios
+}
+
+func (m *master) Read(b []byte) (int, error) {
+ return m.f.Read(b)
+}
+
+func (m *master) Write(b []byte) (int, error) {
+ return m.f.Write(b)
+}
+
+func (m *master) Close() error {
+ return m.f.Close()
+}
+
+func (m *master) Resize(ws WinSize) error {
+ return tcswinsz(m.f.Fd(), ws)
+}
+
+func (m *master) ResizeFrom(c Console) error {
+ ws, err := c.Size()
+ if err != nil {
+ return err
+ }
+ return m.Resize(ws)
+}
+
+func (m *master) Reset() error {
+ if m.original == nil {
+ return nil
+ }
+ return tcset(m.f.Fd(), m.original)
+}
+
+func (m *master) getCurrent() (unix.Termios, error) {
+ var termios unix.Termios
+ if err := tcget(m.f.Fd(), &termios); err != nil {
+ return unix.Termios{}, err
+ }
+ return termios, nil
+}
+
+func (m *master) SetRaw() error {
+ rawState, err := m.getCurrent()
+ if err != nil {
+ return err
+ }
+ rawState = cfmakeraw(rawState)
+ rawState.Oflag = rawState.Oflag | unix.OPOST
+ return tcset(m.f.Fd(), &rawState)
+}
+
+func (m *master) DisableEcho() error {
+ rawState, err := m.getCurrent()
+ if err != nil {
+ return err
+ }
+ rawState.Lflag = rawState.Lflag &^ unix.ECHO
+ return tcset(m.f.Fd(), &rawState)
+}
+
+func (m *master) Size() (WinSize, error) {
+ return tcgwinsz(m.f.Fd())
+}
+
+func (m *master) Fd() uintptr {
+ return m.f.Fd()
+}
+
+func (m *master) Name() string {
+ return m.f.Name()
+}
+
+// checkConsole checks if the provided file is a console
+func checkConsole(f File) error {
+ var termios unix.Termios
+ if tcget(f.Fd(), &termios) != nil {
+ return ErrNotAConsole
+ }
+ return nil
+}
+
+func newMaster(f File) (Console, error) {
+ m := &master{
+ f: f,
+ }
+ t, err := m.getCurrent()
+ if err != nil {
+ return nil, err
+ }
+ m.original = &t
+ return m, nil
+}
+
+// ClearONLCR sets the necessary tty_ioctl(4)s to ensure that a pty pair
+// created by us acts normally. In particular, a not-very-well-known default of
+// Linux unix98 ptys is that they have +onlcr by default. While this isn't a
+// problem for terminal emulators, because we relay data from the terminal we
+// also relay that funky line discipline.
+func ClearONLCR(fd uintptr) error {
+ return setONLCR(fd, false)
+}
+
+// SetONLCR sets the necessary tty_ioctl(4)s to ensure that a pty pair
+// created by us acts as intended for a terminal emulator.
+func SetONLCR(fd uintptr) error {
+ return setONLCR(fd, true)
+}
diff --git a/vendor/github.com/containerd/console/console_windows.go b/vendor/github.com/containerd/console/console_windows.go
new file mode 100644
index 0000000..6896db1
--- /dev/null
+++ b/vendor/github.com/containerd/console/console_windows.go
@@ -0,0 +1,219 @@
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "errors"
+ "fmt"
+ "os"
+
+ "golang.org/x/sys/windows"
+)
+
+var vtInputSupported bool
+
+func (m *master) initStdios() {
+ // Note: We discard console mode warnings, because in/out can be redirected.
+ //
+ // TODO: Investigate opening CONOUT$/CONIN$ to handle this correctly
+
+ m.in = windows.Handle(os.Stdin.Fd())
+ if err := windows.GetConsoleMode(m.in, &m.inMode); err == nil {
+ // Validate that windows.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it.
+ if err = windows.SetConsoleMode(m.in, m.inMode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err == nil {
+ vtInputSupported = true
+ }
+ // Unconditionally set the console mode back even on failure because SetConsoleMode
+ // remembers invalid bits on input handles.
+ windows.SetConsoleMode(m.in, m.inMode)
+ }
+
+ m.out = windows.Handle(os.Stdout.Fd())
+ if err := windows.GetConsoleMode(m.out, &m.outMode); err == nil {
+ if err := windows.SetConsoleMode(m.out, m.outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
+ m.outMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
+ } else {
+ windows.SetConsoleMode(m.out, m.outMode)
+ }
+ }
+
+ m.err = windows.Handle(os.Stderr.Fd())
+ if err := windows.GetConsoleMode(m.err, &m.errMode); err == nil {
+ if err := windows.SetConsoleMode(m.err, m.errMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
+ m.errMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
+ } else {
+ windows.SetConsoleMode(m.err, m.errMode)
+ }
+ }
+}
+
+type master struct {
+ in windows.Handle
+ inMode uint32
+
+ out windows.Handle
+ outMode uint32
+
+ err windows.Handle
+ errMode uint32
+}
+
+func (m *master) SetRaw() error {
+ if err := makeInputRaw(m.in, m.inMode); err != nil {
+ return err
+ }
+
+ // Set StdOut and StdErr to raw mode, we ignore failures since
+ // windows.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this version of
+ // Windows.
+
+ windows.SetConsoleMode(m.out, m.outMode|windows.DISABLE_NEWLINE_AUTO_RETURN)
+
+ windows.SetConsoleMode(m.err, m.errMode|windows.DISABLE_NEWLINE_AUTO_RETURN)
+
+ return nil
+}
+
+func (m *master) Reset() error {
+ var errs []error
+
+ for _, s := range []struct {
+ fd windows.Handle
+ mode uint32
+ }{
+ {m.in, m.inMode},
+ {m.out, m.outMode},
+ {m.err, m.errMode},
+ } {
+ if err := windows.SetConsoleMode(s.fd, s.mode); err != nil {
+ // we can't just abort on the first error, otherwise we might leave
+ // the console in an unexpected state.
+ errs = append(errs, fmt.Errorf("unable to restore console mode: %w", err))
+ }
+ }
+
+ if len(errs) > 0 {
+ return errs[0]
+ }
+
+ return nil
+}
+
+func (m *master) Size() (WinSize, error) {
+ var info windows.ConsoleScreenBufferInfo
+ err := windows.GetConsoleScreenBufferInfo(m.out, &info)
+ if err != nil {
+ return WinSize{}, fmt.Errorf("unable to get console info: %w", err)
+ }
+
+ winsize := WinSize{
+ Width: uint16(info.Window.Right - info.Window.Left + 1),
+ Height: uint16(info.Window.Bottom - info.Window.Top + 1),
+ }
+
+ return winsize, nil
+}
+
+func (m *master) Resize(ws WinSize) error {
+ return ErrNotImplemented
+}
+
+func (m *master) ResizeFrom(c Console) error {
+ return ErrNotImplemented
+}
+
+func (m *master) DisableEcho() error {
+ mode := m.inMode &^ windows.ENABLE_ECHO_INPUT
+ mode |= windows.ENABLE_PROCESSED_INPUT
+ mode |= windows.ENABLE_LINE_INPUT
+
+ if err := windows.SetConsoleMode(m.in, mode); err != nil {
+ return fmt.Errorf("unable to set console to disable echo: %w", err)
+ }
+
+ return nil
+}
+
+func (m *master) Close() error {
+ return nil
+}
+
+func (m *master) Read(b []byte) (int, error) {
+ return os.Stdin.Read(b)
+}
+
+func (m *master) Write(b []byte) (int, error) {
+ return os.Stdout.Write(b)
+}
+
+func (m *master) Fd() uintptr {
+ return uintptr(m.in)
+}
+
+// on windows, console can only be made from os.Std{in,out,err}, hence there
+// isnt a single name here we can use. Return a dummy "console" value in this
+// case should be sufficient.
+func (m *master) Name() string {
+ return "console"
+}
+
+// makeInputRaw puts the terminal (Windows Console) connected to the given
+// file descriptor into raw mode
+func makeInputRaw(fd windows.Handle, mode uint32) error {
+ // See
+ // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
+ // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
+
+ // Disable these modes
+ mode &^= windows.ENABLE_ECHO_INPUT
+ mode &^= windows.ENABLE_LINE_INPUT
+ mode &^= windows.ENABLE_MOUSE_INPUT
+ mode &^= windows.ENABLE_WINDOW_INPUT
+ mode &^= windows.ENABLE_PROCESSED_INPUT
+
+ // Enable these modes
+ mode |= windows.ENABLE_EXTENDED_FLAGS
+ mode |= windows.ENABLE_INSERT_MODE
+ mode |= windows.ENABLE_QUICK_EDIT_MODE
+
+ if vtInputSupported {
+ mode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
+ }
+
+ if err := windows.SetConsoleMode(fd, mode); err != nil {
+ return fmt.Errorf("unable to set console to raw mode: %w", err)
+ }
+
+ return nil
+}
+
+func checkConsole(f File) error {
+ var mode uint32
+ if err := windows.GetConsoleMode(windows.Handle(f.Fd()), &mode); err != nil {
+ return err
+ }
+ return nil
+}
+
+func newMaster(f File) (Console, error) {
+ if f != os.Stdin && f != os.Stdout && f != os.Stderr {
+ return nil, errors.New("creating a console from a file is not supported on windows")
+ }
+ m := &master{}
+ m.initStdios()
+ return m, nil
+}
diff --git a/vendor/github.com/containerd/console/pty_freebsd_cgo.go b/vendor/github.com/containerd/console/pty_freebsd_cgo.go
new file mode 100644
index 0000000..2236862
--- /dev/null
+++ b/vendor/github.com/containerd/console/pty_freebsd_cgo.go
@@ -0,0 +1,46 @@
+//go:build freebsd && cgo
+// +build freebsd,cgo
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "fmt"
+ "os"
+)
+
+/*
+#include
+#include
+#include
+*/
+import "C"
+
+// openpt allocates a new pseudo-terminal and establishes a connection with its
+// control device.
+func openpt() (*os.File, error) {
+ fd, err := C.posix_openpt(C.O_RDWR)
+ if err != nil {
+ return nil, fmt.Errorf("posix_openpt: %w", err)
+ }
+ if _, err := C.grantpt(fd); err != nil {
+ C.close(fd)
+ return nil, fmt.Errorf("grantpt: %w", err)
+ }
+ return os.NewFile(uintptr(fd), ""), nil
+}
diff --git a/vendor/github.com/containerd/console/pty_freebsd_nocgo.go b/vendor/github.com/containerd/console/pty_freebsd_nocgo.go
new file mode 100644
index 0000000..ceb90a4
--- /dev/null
+++ b/vendor/github.com/containerd/console/pty_freebsd_nocgo.go
@@ -0,0 +1,37 @@
+//go:build freebsd && !cgo
+// +build freebsd,!cgo
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "os"
+)
+
+//
+// Implementing the functions below requires cgo support. Non-cgo stubs
+// versions are defined below to enable cross-compilation of source code
+// that depends on these functions, but the resultant cross-compiled
+// binaries cannot actually be used. If the stub function(s) below are
+// actually invoked they will display an error message and cause the
+// calling process to exit.
+//
+
+func openpt() (*os.File, error) {
+ panic("openpt() support requires cgo.")
+}
diff --git a/vendor/github.com/containerd/console/pty_unix.go b/vendor/github.com/containerd/console/pty_unix.go
new file mode 100644
index 0000000..f5a5b80
--- /dev/null
+++ b/vendor/github.com/containerd/console/pty_unix.go
@@ -0,0 +1,31 @@
+//go:build darwin || linux || netbsd || openbsd
+// +build darwin linux netbsd openbsd
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+// openpt allocates a new pseudo-terminal by opening the /dev/ptmx device
+func openpt() (*os.File, error) {
+ return os.OpenFile("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY|unix.O_CLOEXEC, 0)
+}
diff --git a/vendor/github.com/containerd/console/pty_zos.go b/vendor/github.com/containerd/console/pty_zos.go
new file mode 100644
index 0000000..58f59ab
--- /dev/null
+++ b/vendor/github.com/containerd/console/pty_zos.go
@@ -0,0 +1,43 @@
+//go:build zos
+// +build zos
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "fmt"
+ "os"
+)
+
+// openpt allocates a new pseudo-terminal by opening the first available /dev/ptypXX device
+func openpt() (*os.File, error) {
+ var f *os.File
+ var err error
+ for i := 0; ; i++ {
+ ptyp := fmt.Sprintf("/dev/ptyp%04d", i)
+ f, err = os.OpenFile(ptyp, os.O_RDWR, 0600)
+ if err == nil {
+ break
+ }
+ if os.IsNotExist(err) {
+ return nil, err
+ }
+ // else probably Resource Busy
+ }
+ return f, nil
+}
diff --git a/vendor/github.com/containerd/console/tc_darwin.go b/vendor/github.com/containerd/console/tc_darwin.go
new file mode 100644
index 0000000..7871545
--- /dev/null
+++ b/vendor/github.com/containerd/console/tc_darwin.go
@@ -0,0 +1,44 @@
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "fmt"
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+const (
+ cmdTcGet = unix.TIOCGETA
+ cmdTcSet = unix.TIOCSETA
+)
+
+// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
+// unlockpt should be called before opening the slave side of a pty.
+func unlockpt(f *os.File) error {
+ return unix.IoctlSetPointerInt(int(f.Fd()), unix.TIOCPTYUNLK, 0)
+}
+
+// ptsname retrieves the name of the first available pts for the given master.
+func ptsname(f *os.File) (string, error) {
+ n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCPTYGNAME)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("/dev/pts/%d", n), nil
+}
diff --git a/vendor/github.com/containerd/console/tc_freebsd_cgo.go b/vendor/github.com/containerd/console/tc_freebsd_cgo.go
new file mode 100644
index 0000000..3328257
--- /dev/null
+++ b/vendor/github.com/containerd/console/tc_freebsd_cgo.go
@@ -0,0 +1,58 @@
+//go:build freebsd && cgo
+// +build freebsd,cgo
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "fmt"
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+/*
+#include
+#include
+*/
+import "C"
+
+const (
+ cmdTcGet = unix.TIOCGETA
+ cmdTcSet = unix.TIOCSETA
+)
+
+// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
+// unlockpt should be called before opening the slave side of a pty.
+func unlockpt(f *os.File) error {
+ fd := C.int(f.Fd())
+ if _, err := C.unlockpt(fd); err != nil {
+ C.close(fd)
+ return fmt.Errorf("unlockpt: %w", err)
+ }
+ return nil
+}
+
+// ptsname retrieves the name of the first available pts for the given master.
+func ptsname(f *os.File) (string, error) {
+ n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCGPTN)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("/dev/pts/%d", n), nil
+}
diff --git a/vendor/github.com/containerd/console/tc_freebsd_nocgo.go b/vendor/github.com/containerd/console/tc_freebsd_nocgo.go
new file mode 100644
index 0000000..18a9b9c
--- /dev/null
+++ b/vendor/github.com/containerd/console/tc_freebsd_nocgo.go
@@ -0,0 +1,56 @@
+//go:build freebsd && !cgo
+// +build freebsd,!cgo
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "fmt"
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+const (
+ cmdTcGet = unix.TIOCGETA
+ cmdTcSet = unix.TIOCSETA
+)
+
+//
+// Implementing the functions below requires cgo support. Non-cgo stubs
+// versions are defined below to enable cross-compilation of source code
+// that depends on these functions, but the resultant cross-compiled
+// binaries cannot actually be used. If the stub function(s) below are
+// actually invoked they will display an error message and cause the
+// calling process to exit.
+//
+
+// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
+// unlockpt should be called before opening the slave side of a pty.
+func unlockpt(f *os.File) error {
+ panic("unlockpt() support requires cgo.")
+}
+
+// ptsname retrieves the name of the first available pts for the given master.
+func ptsname(f *os.File) (string, error) {
+ n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCGPTN)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("/dev/pts/%d", n), nil
+}
diff --git a/vendor/github.com/containerd/console/tc_linux.go b/vendor/github.com/containerd/console/tc_linux.go
new file mode 100644
index 0000000..7d552ea
--- /dev/null
+++ b/vendor/github.com/containerd/console/tc_linux.go
@@ -0,0 +1,51 @@
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "fmt"
+ "os"
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+const (
+ cmdTcGet = unix.TCGETS
+ cmdTcSet = unix.TCSETS
+)
+
+// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
+// unlockpt should be called before opening the slave side of a pty.
+func unlockpt(f *os.File) error {
+ var u int32
+ // XXX do not use unix.IoctlSetPointerInt here, see commit dbd69c59b81.
+ if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))); err != 0 {
+ return err
+ }
+ return nil
+}
+
+// ptsname retrieves the name of the first available pts for the given master.
+func ptsname(f *os.File) (string, error) {
+ var u uint32
+ // XXX do not use unix.IoctlGetInt here, see commit dbd69c59b81.
+ if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&u))); err != 0 {
+ return "", err
+ }
+ return fmt.Sprintf("/dev/pts/%d", u), nil
+}
diff --git a/vendor/github.com/containerd/console/tc_netbsd.go b/vendor/github.com/containerd/console/tc_netbsd.go
new file mode 100644
index 0000000..71227ae
--- /dev/null
+++ b/vendor/github.com/containerd/console/tc_netbsd.go
@@ -0,0 +1,45 @@
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "bytes"
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+const (
+ cmdTcGet = unix.TIOCGETA
+ cmdTcSet = unix.TIOCSETA
+)
+
+// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
+// unlockpt should be called before opening the slave side of a pty.
+// This does not exist on NetBSD, it does not allocate controlling terminals on open
+func unlockpt(f *os.File) error {
+ return nil
+}
+
+// ptsname retrieves the name of the first available pts for the given master.
+func ptsname(f *os.File) (string, error) {
+ ptm, err := unix.IoctlGetPtmget(int(f.Fd()), unix.TIOCPTSNAME)
+ if err != nil {
+ return "", err
+ }
+ return string(ptm.Sn[:bytes.IndexByte(ptm.Sn[:], 0)]), nil
+}
diff --git a/vendor/github.com/containerd/console/tc_openbsd_cgo.go b/vendor/github.com/containerd/console/tc_openbsd_cgo.go
new file mode 100644
index 0000000..0e76f6c
--- /dev/null
+++ b/vendor/github.com/containerd/console/tc_openbsd_cgo.go
@@ -0,0 +1,52 @@
+//go:build openbsd && cgo
+// +build openbsd,cgo
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+//#include
+import "C"
+
+const (
+ cmdTcGet = unix.TIOCGETA
+ cmdTcSet = unix.TIOCSETA
+)
+
+// ptsname retrieves the name of the first available pts for the given master.
+func ptsname(f *os.File) (string, error) {
+ ptspath, err := C.ptsname(C.int(f.Fd()))
+ if err != nil {
+ return "", err
+ }
+ return C.GoString(ptspath), nil
+}
+
+// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
+// unlockpt should be called before opening the slave side of a pty.
+func unlockpt(f *os.File) error {
+ if _, err := C.grantpt(C.int(f.Fd())); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/vendor/github.com/containerd/console/tc_openbsd_nocgo.go b/vendor/github.com/containerd/console/tc_openbsd_nocgo.go
new file mode 100644
index 0000000..dca9241
--- /dev/null
+++ b/vendor/github.com/containerd/console/tc_openbsd_nocgo.go
@@ -0,0 +1,48 @@
+//go:build openbsd && !cgo
+// +build openbsd,!cgo
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+//
+// Implementing the functions below requires cgo support. Non-cgo stubs
+// versions are defined below to enable cross-compilation of source code
+// that depends on these functions, but the resultant cross-compiled
+// binaries cannot actually be used. If the stub function(s) below are
+// actually invoked they will display an error message and cause the
+// calling process to exit.
+//
+
+package console
+
+import (
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+const (
+ cmdTcGet = unix.TIOCGETA
+ cmdTcSet = unix.TIOCSETA
+)
+
+func ptsname(f *os.File) (string, error) {
+ panic("ptsname() support requires cgo.")
+}
+
+func unlockpt(f *os.File) error {
+ panic("unlockpt() support requires cgo.")
+}
diff --git a/vendor/github.com/containerd/console/tc_unix.go b/vendor/github.com/containerd/console/tc_unix.go
new file mode 100644
index 0000000..2ecf188
--- /dev/null
+++ b/vendor/github.com/containerd/console/tc_unix.go
@@ -0,0 +1,92 @@
+//go:build darwin || freebsd || linux || netbsd || openbsd || zos
+// +build darwin freebsd linux netbsd openbsd zos
+
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "golang.org/x/sys/unix"
+)
+
+func tcget(fd uintptr, p *unix.Termios) error {
+ termios, err := unix.IoctlGetTermios(int(fd), cmdTcGet)
+ if err != nil {
+ return err
+ }
+ *p = *termios
+ return nil
+}
+
+func tcset(fd uintptr, p *unix.Termios) error {
+ return unix.IoctlSetTermios(int(fd), cmdTcSet, p)
+}
+
+func tcgwinsz(fd uintptr) (WinSize, error) {
+ var ws WinSize
+
+ uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
+ if err != nil {
+ return ws, err
+ }
+
+ // Translate from unix.Winsize to console.WinSize
+ ws.Height = uws.Row
+ ws.Width = uws.Col
+ ws.x = uws.Xpixel
+ ws.y = uws.Ypixel
+ return ws, nil
+}
+
+func tcswinsz(fd uintptr, ws WinSize) error {
+ // Translate from console.WinSize to unix.Winsize
+
+ var uws unix.Winsize
+ uws.Row = ws.Height
+ uws.Col = ws.Width
+ uws.Xpixel = ws.x
+ uws.Ypixel = ws.y
+
+ return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, &uws)
+}
+
+func setONLCR(fd uintptr, enable bool) error {
+ var termios unix.Termios
+ if err := tcget(fd, &termios); err != nil {
+ return err
+ }
+ if enable {
+ // Set +onlcr so we can act like a real terminal
+ termios.Oflag |= unix.ONLCR
+ } else {
+ // Set -onlcr so we don't have to deal with \r.
+ termios.Oflag &^= unix.ONLCR
+ }
+ return tcset(fd, &termios)
+}
+
+func cfmakeraw(t unix.Termios) unix.Termios {
+ t.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON)
+ t.Oflag &^= unix.OPOST
+ t.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN)
+ t.Cflag &^= (unix.CSIZE | unix.PARENB)
+ t.Cflag |= unix.CS8
+ t.Cc[unix.VMIN] = 1
+ t.Cc[unix.VTIME] = 0
+
+ return t
+}
diff --git a/vendor/github.com/containerd/console/tc_zos.go b/vendor/github.com/containerd/console/tc_zos.go
new file mode 100644
index 0000000..fc90ba5
--- /dev/null
+++ b/vendor/github.com/containerd/console/tc_zos.go
@@ -0,0 +1,39 @@
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package console
+
+import (
+ "os"
+ "strings"
+
+ "golang.org/x/sys/unix"
+)
+
+const (
+ cmdTcGet = unix.TCGETS
+ cmdTcSet = unix.TCSETS
+)
+
+// unlockpt is a no-op on zos.
+func unlockpt(_ *os.File) error {
+ return nil
+}
+
+// ptsname retrieves the name of the first available pts for the given master.
+func ptsname(f *os.File) (string, error) {
+ return "/dev/ttyp" + strings.TrimPrefix(f.Name(), "/dev/ptyp"), nil
+}
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md b/vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md
new file mode 100644
index 0000000..1cade6c
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Brian Goff
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go
new file mode 100644
index 0000000..42bf32a
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go
@@ -0,0 +1,16 @@
+package md2man
+
+import (
+ "github.com/russross/blackfriday/v2"
+)
+
+// Render converts a markdown document into a roff formatted document.
+func Render(doc []byte) []byte {
+ renderer := NewRoffRenderer()
+
+ return blackfriday.Run(doc,
+ []blackfriday.Option{
+ blackfriday.WithRenderer(renderer),
+ blackfriday.WithExtensions(renderer.GetExtensions()),
+ }...)
+}
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go
new file mode 100644
index 0000000..4b19188
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go
@@ -0,0 +1,348 @@
+package md2man
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/russross/blackfriday/v2"
+)
+
+// roffRenderer implements the blackfriday.Renderer interface for creating
+// roff format (manpages) from markdown text
+type roffRenderer struct {
+ extensions blackfriday.Extensions
+ listCounters []int
+ firstHeader bool
+ firstDD bool
+ listDepth int
+}
+
+const (
+ titleHeader = ".TH "
+ topLevelHeader = "\n\n.SH "
+ secondLevelHdr = "\n.SH "
+ otherHeader = "\n.SS "
+ crTag = "\n"
+ emphTag = "\\fI"
+ emphCloseTag = "\\fP"
+ strongTag = "\\fB"
+ strongCloseTag = "\\fP"
+ breakTag = "\n.br\n"
+ paraTag = "\n.PP\n"
+ hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
+ linkTag = "\n\\[la]"
+ linkCloseTag = "\\[ra]"
+ codespanTag = "\\fB"
+ codespanCloseTag = "\\fR"
+ codeTag = "\n.EX\n"
+ codeCloseTag = "\n.EE\n"
+ quoteTag = "\n.PP\n.RS\n"
+ quoteCloseTag = "\n.RE\n"
+ listTag = "\n.RS\n"
+ listCloseTag = "\n.RE\n"
+ dtTag = "\n.TP\n"
+ dd2Tag = "\n"
+ tableStart = "\n.TS\nallbox;\n"
+ tableEnd = ".TE\n"
+ tableCellStart = "T{\n"
+ tableCellEnd = "\nT}\n"
+)
+
+// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
+// from markdown
+func NewRoffRenderer() *roffRenderer { // nolint: golint
+ var extensions blackfriday.Extensions
+
+ extensions |= blackfriday.NoIntraEmphasis
+ extensions |= blackfriday.Tables
+ extensions |= blackfriday.FencedCode
+ extensions |= blackfriday.SpaceHeadings
+ extensions |= blackfriday.Footnotes
+ extensions |= blackfriday.Titleblock
+ extensions |= blackfriday.DefinitionLists
+ return &roffRenderer{
+ extensions: extensions,
+ }
+}
+
+// GetExtensions returns the list of extensions used by this renderer implementation
+func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
+ return r.extensions
+}
+
+// RenderHeader handles outputting the header at document start
+func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
+ // disable hyphenation
+ out(w, ".nh\n")
+}
+
+// RenderFooter handles outputting the footer at the document end; the roff
+// renderer has no footer information
+func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
+}
+
+// RenderNode is called for each node in a markdown document; based on the node
+// type the equivalent roff output is sent to the writer
+func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
+ walkAction := blackfriday.GoToNext
+
+ switch node.Type {
+ case blackfriday.Text:
+ escapeSpecialChars(w, node.Literal)
+ case blackfriday.Softbreak:
+ out(w, crTag)
+ case blackfriday.Hardbreak:
+ out(w, breakTag)
+ case blackfriday.Emph:
+ if entering {
+ out(w, emphTag)
+ } else {
+ out(w, emphCloseTag)
+ }
+ case blackfriday.Strong:
+ if entering {
+ out(w, strongTag)
+ } else {
+ out(w, strongCloseTag)
+ }
+ case blackfriday.Link:
+ // Don't render the link text for automatic links, because this
+ // will only duplicate the URL in the roff output.
+ // See https://daringfireball.net/projects/markdown/syntax#autolink
+ if !bytes.Equal(node.LinkData.Destination, node.FirstChild.Literal) {
+ out(w, string(node.FirstChild.Literal))
+ }
+ // Hyphens in a link must be escaped to avoid word-wrap in the rendered man page.
+ escapedLink := strings.ReplaceAll(string(node.LinkData.Destination), "-", "\\-")
+ out(w, linkTag+escapedLink+linkCloseTag)
+ walkAction = blackfriday.SkipChildren
+ case blackfriday.Image:
+ // ignore images
+ walkAction = blackfriday.SkipChildren
+ case blackfriday.Code:
+ out(w, codespanTag)
+ escapeSpecialChars(w, node.Literal)
+ out(w, codespanCloseTag)
+ case blackfriday.Document:
+ break
+ case blackfriday.Paragraph:
+ // roff .PP markers break lists
+ if r.listDepth > 0 {
+ return blackfriday.GoToNext
+ }
+ if entering {
+ out(w, paraTag)
+ } else {
+ out(w, crTag)
+ }
+ case blackfriday.BlockQuote:
+ if entering {
+ out(w, quoteTag)
+ } else {
+ out(w, quoteCloseTag)
+ }
+ case blackfriday.Heading:
+ r.handleHeading(w, node, entering)
+ case blackfriday.HorizontalRule:
+ out(w, hruleTag)
+ case blackfriday.List:
+ r.handleList(w, node, entering)
+ case blackfriday.Item:
+ r.handleItem(w, node, entering)
+ case blackfriday.CodeBlock:
+ out(w, codeTag)
+ escapeSpecialChars(w, node.Literal)
+ out(w, codeCloseTag)
+ case blackfriday.Table:
+ r.handleTable(w, node, entering)
+ case blackfriday.TableHead:
+ case blackfriday.TableBody:
+ case blackfriday.TableRow:
+ // no action as cell entries do all the nroff formatting
+ return blackfriday.GoToNext
+ case blackfriday.TableCell:
+ r.handleTableCell(w, node, entering)
+ case blackfriday.HTMLSpan:
+ // ignore other HTML tags
+ case blackfriday.HTMLBlock:
+ if bytes.HasPrefix(node.Literal, []byte("|"
+ processingInstruction = "[<][?].*?[?][>]"
+ singleQuotedValue = "'[^']*'"
+ tagName = "[A-Za-z][A-Za-z0-9-]*"
+ unquotedValue = "[^\"'=<>`\\x00-\\x20]+"
+)
+
+// HTMLRendererParameters is a collection of supplementary parameters tweaking
+// the behavior of various parts of HTML renderer.
+type HTMLRendererParameters struct {
+ // Prepend this text to each relative URL.
+ AbsolutePrefix string
+ // Add this text to each footnote anchor, to ensure uniqueness.
+ FootnoteAnchorPrefix string
+ // Show this text inside the tag for a footnote return link, if the
+ // HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
+ // [return] is used.
+ FootnoteReturnLinkContents string
+ // If set, add this text to the front of each Heading ID, to ensure
+ // uniqueness.
+ HeadingIDPrefix string
+ // If set, add this text to the back of each Heading ID, to ensure uniqueness.
+ HeadingIDSuffix string
+ // Increase heading levels: if the offset is 1, becomes etc.
+ // Negative offset is also valid.
+ // Resulting levels are clipped between 1 and 6.
+ HeadingLevelOffset int
+
+ Title string // Document title (used if CompletePage is set)
+ CSS string // Optional CSS file URL (used if CompletePage is set)
+ Icon string // Optional icon file URL (used if CompletePage is set)
+
+ Flags HTMLFlags // Flags allow customizing this renderer's behavior
+}
+
+// HTMLRenderer is a type that implements the Renderer interface for HTML output.
+//
+// Do not create this directly, instead use the NewHTMLRenderer function.
+type HTMLRenderer struct {
+ HTMLRendererParameters
+
+ closeTag string // how to end singleton tags: either " />" or ">"
+
+ // Track heading IDs to prevent ID collision in a single generation.
+ headingIDs map[string]int
+
+ lastOutputLen int
+ disableTags int
+
+ sr *SPRenderer
+}
+
+const (
+ xhtmlClose = " />"
+ htmlClose = ">"
+)
+
+// NewHTMLRenderer creates and configures an HTMLRenderer object, which
+// satisfies the Renderer interface.
+func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
+ // configure the rendering engine
+ closeTag := htmlClose
+ if params.Flags&UseXHTML != 0 {
+ closeTag = xhtmlClose
+ }
+
+ if params.FootnoteReturnLinkContents == "" {
+ // U+FE0E is VARIATION SELECTOR-15.
+ // It suppresses automatic emoji presentation of the preceding
+ // U+21A9 LEFTWARDS ARROW WITH HOOK on iOS and iPadOS.
+ params.FootnoteReturnLinkContents = "↩\ufe0e"
+ }
+
+ return &HTMLRenderer{
+ HTMLRendererParameters: params,
+
+ closeTag: closeTag,
+ headingIDs: make(map[string]int),
+
+ sr: NewSmartypantsRenderer(params.Flags),
+ }
+}
+
+func isHTMLTag(tag []byte, tagname string) bool {
+ found, _ := findHTMLTagPos(tag, tagname)
+ return found
+}
+
+// Look for a character, but ignore it when it's in any kind of quotes, it
+// might be JavaScript
+func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
+ inSingleQuote := false
+ inDoubleQuote := false
+ inGraveQuote := false
+ i := start
+ for i < len(html) {
+ switch {
+ case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
+ return i
+ case html[i] == '\'':
+ inSingleQuote = !inSingleQuote
+ case html[i] == '"':
+ inDoubleQuote = !inDoubleQuote
+ case html[i] == '`':
+ inGraveQuote = !inGraveQuote
+ }
+ i++
+ }
+ return start
+}
+
+func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
+ i := 0
+ if i < len(tag) && tag[0] != '<' {
+ return false, -1
+ }
+ i++
+ i = skipSpace(tag, i)
+
+ if i < len(tag) && tag[i] == '/' {
+ i++
+ }
+
+ i = skipSpace(tag, i)
+ j := 0
+ for ; i < len(tag); i, j = i+1, j+1 {
+ if j >= len(tagname) {
+ break
+ }
+
+ if strings.ToLower(string(tag[i]))[0] != tagname[j] {
+ return false, -1
+ }
+ }
+
+ if i == len(tag) {
+ return false, -1
+ }
+
+ rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
+ if rightAngle >= i {
+ return true, rightAngle
+ }
+
+ return false, -1
+}
+
+func skipSpace(tag []byte, i int) int {
+ for i < len(tag) && isspace(tag[i]) {
+ i++
+ }
+ return i
+}
+
+func isRelativeLink(link []byte) (yes bool) {
+ // a tag begin with '#'
+ if link[0] == '#' {
+ return true
+ }
+
+ // link begin with '/' but not '//', the second maybe a protocol relative link
+ if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
+ return true
+ }
+
+ // only the root '/'
+ if len(link) == 1 && link[0] == '/' {
+ return true
+ }
+
+ // current directory : begin with "./"
+ if bytes.HasPrefix(link, []byte("./")) {
+ return true
+ }
+
+ // parent directory : begin with "../"
+ if bytes.HasPrefix(link, []byte("../")) {
+ return true
+ }
+
+ return false
+}
+
+func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string {
+ for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
+ tmp := fmt.Sprintf("%s-%d", id, count+1)
+
+ if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
+ r.headingIDs[id] = count + 1
+ id = tmp
+ } else {
+ id = id + "-1"
+ }
+ }
+
+ if _, found := r.headingIDs[id]; !found {
+ r.headingIDs[id] = 0
+ }
+
+ return id
+}
+
+func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte {
+ if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
+ newDest := r.AbsolutePrefix
+ if link[0] != '/' {
+ newDest += "/"
+ }
+ newDest += string(link)
+ return []byte(newDest)
+ }
+ return link
+}
+
+func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
+ if isRelativeLink(link) {
+ return attrs
+ }
+ val := []string{}
+ if flags&NofollowLinks != 0 {
+ val = append(val, "nofollow")
+ }
+ if flags&NoreferrerLinks != 0 {
+ val = append(val, "noreferrer")
+ }
+ if flags&NoopenerLinks != 0 {
+ val = append(val, "noopener")
+ }
+ if flags&HrefTargetBlank != 0 {
+ attrs = append(attrs, "target=\"_blank\"")
+ }
+ if len(val) == 0 {
+ return attrs
+ }
+ attr := fmt.Sprintf("rel=%q", strings.Join(val, " "))
+ return append(attrs, attr)
+}
+
+func isMailto(link []byte) bool {
+ return bytes.HasPrefix(link, []byte("mailto:"))
+}
+
+func needSkipLink(flags HTMLFlags, dest []byte) bool {
+ if flags&SkipLinks != 0 {
+ return true
+ }
+ return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
+}
+
+func isSmartypantable(node *Node) bool {
+ pt := node.Parent.Type
+ return pt != Link && pt != CodeBlock && pt != Code
+}
+
+func appendLanguageAttr(attrs []string, info []byte) []string {
+ if len(info) == 0 {
+ return attrs
+ }
+ endOfLang := bytes.IndexAny(info, "\t ")
+ if endOfLang < 0 {
+ endOfLang = len(info)
+ }
+ return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang]))
+}
+
+func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
+ w.Write(name)
+ if len(attrs) > 0 {
+ w.Write(spaceBytes)
+ w.Write([]byte(strings.Join(attrs, " ")))
+ }
+ w.Write(gtBytes)
+ r.lastOutputLen = 1
+}
+
+func footnoteRef(prefix string, node *Node) []byte {
+ urlFrag := prefix + string(slugify(node.Destination))
+ anchor := fmt.Sprintf(`%d`, urlFrag, node.NoteID)
+ return []byte(fmt.Sprintf(``, urlFrag, anchor))
+}
+
+func footnoteItem(prefix string, slug []byte) []byte {
+ return []byte(fmt.Sprintf(`
`, prefix, slug))
+}
+
+func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
+ const format = ` `
+ return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
+}
+
+func itemOpenCR(node *Node) bool {
+ if node.Prev == nil {
+ return false
+ }
+ ld := node.Parent.ListData
+ return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0
+}
+
+func skipParagraphTags(node *Node) bool {
+ grandparent := node.Parent.Parent
+ if grandparent == nil || grandparent.Type != List {
+ return false
+ }
+ tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0
+ return grandparent.Type == List && tightOrTerm
+}
+
+func cellAlignment(align CellAlignFlags) string {
+ switch align {
+ case TableAlignmentLeft:
+ return "left"
+ case TableAlignmentRight:
+ return "right"
+ case TableAlignmentCenter:
+ return "center"
+ default:
+ return ""
+ }
+}
+
+func (r *HTMLRenderer) out(w io.Writer, text []byte) {
+ if r.disableTags > 0 {
+ w.Write(htmlTagRe.ReplaceAll(text, []byte{}))
+ } else {
+ w.Write(text)
+ }
+ r.lastOutputLen = len(text)
+}
+
+func (r *HTMLRenderer) cr(w io.Writer) {
+ if r.lastOutputLen > 0 {
+ r.out(w, nlBytes)
+ }
+}
+
+var (
+ nlBytes = []byte{'\n'}
+ gtBytes = []byte{'>'}
+ spaceBytes = []byte{' '}
+)
+
+var (
+ brTag = []byte("
")
+ brXHTMLTag = []byte("
")
+ emTag = []byte("")
+ emCloseTag = []byte("")
+ strongTag = []byte("")
+ strongCloseTag = []byte("")
+ delTag = []byte("")
+ delCloseTag = []byte("")
+ ttTag = []byte("")
+ ttCloseTag = []byte("")
+ aTag = []byte("")
+ preTag = []byte("")
+ preCloseTag = []byte("
")
+ codeTag = []byte("")
+ codeCloseTag = []byte("
")
+ pTag = []byte("")
+ pCloseTag = []byte("
")
+ blockquoteTag = []byte("")
+ blockquoteCloseTag = []byte("
")
+ hrTag = []byte("
")
+ hrXHTMLTag = []byte("
")
+ ulTag = []byte("")
+ ulCloseTag = []byte("
")
+ olTag = []byte("")
+ olCloseTag = []byte("
")
+ dlTag = []byte("")
+ dlCloseTag = []byte("
")
+ liTag = []byte("")
+ liCloseTag = []byte("")
+ ddTag = []byte("")
+ ddCloseTag = []byte("")
+ dtTag = []byte("")
+ dtCloseTag = []byte("")
+ tableTag = []byte("")
+ tableCloseTag = []byte("
")
+ tdTag = []byte("")
+ thTag = []byte(" | ")
+ theadTag = []byte("")
+ theadCloseTag = []byte("")
+ tbodyTag = []byte(" | ")
+ tbodyCloseTag = []byte("")
+ trTag = []byte("")
+ trCloseTag = []byte("
")
+ h1Tag = []byte("")
+ h2Tag = []byte("")
+ h3Tag = []byte("")
+ h4Tag = []byte("")
+ h5Tag = []byte("")
+ h6Tag = []byte("")
+
+ footnotesDivBytes = []byte("\n\n")
+)
+
+func headingTagsFromLevel(level int) ([]byte, []byte) {
+ if level <= 1 {
+ return h1Tag, h1CloseTag
+ }
+ switch level {
+ case 2:
+ return h2Tag, h2CloseTag
+ case 3:
+ return h3Tag, h3CloseTag
+ case 4:
+ return h4Tag, h4CloseTag
+ case 5:
+ return h5Tag, h5CloseTag
+ }
+ return h6Tag, h6CloseTag
+}
+
+func (r *HTMLRenderer) outHRTag(w io.Writer) {
+ if r.Flags&UseXHTML == 0 {
+ r.out(w, hrTag)
+ } else {
+ r.out(w, hrXHTMLTag)
+ }
+}
+
+// RenderNode is a default renderer of a single node of a syntax tree. For
+// block nodes it will be called twice: first time with entering=true, second
+// time with entering=false, so that it could know when it's working on an open
+// tag and when on close. It writes the result to w.
+//
+// The return value is a way to tell the calling walker to adjust its walk
+// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
+// can ask the walker to skip a subtree of this node by returning SkipChildren.
+// The typical behavior is to return GoToNext, which asks for the usual
+// traversal to the next node.
+func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
+ attrs := []string{}
+ switch node.Type {
+ case Text:
+ if r.Flags&Smartypants != 0 {
+ var tmp bytes.Buffer
+ escapeHTML(&tmp, node.Literal)
+ r.sr.Process(w, tmp.Bytes())
+ } else {
+ if node.Parent.Type == Link {
+ escLink(w, node.Literal)
+ } else {
+ escapeHTML(w, node.Literal)
+ }
+ }
+ case Softbreak:
+ r.cr(w)
+ // TODO: make it configurable via out(renderer.softbreak)
+ case Hardbreak:
+ if r.Flags&UseXHTML == 0 {
+ r.out(w, brTag)
+ } else {
+ r.out(w, brXHTMLTag)
+ }
+ r.cr(w)
+ case Emph:
+ if entering {
+ r.out(w, emTag)
+ } else {
+ r.out(w, emCloseTag)
+ }
+ case Strong:
+ if entering {
+ r.out(w, strongTag)
+ } else {
+ r.out(w, strongCloseTag)
+ }
+ case Del:
+ if entering {
+ r.out(w, delTag)
+ } else {
+ r.out(w, delCloseTag)
+ }
+ case HTMLSpan:
+ if r.Flags&SkipHTML != 0 {
+ break
+ }
+ r.out(w, node.Literal)
+ case Link:
+ // mark it but don't link it if it is not a safe link: no smartypants
+ dest := node.LinkData.Destination
+ if needSkipLink(r.Flags, dest) {
+ if entering {
+ r.out(w, ttTag)
+ } else {
+ r.out(w, ttCloseTag)
+ }
+ } else {
+ if entering {
+ dest = r.addAbsPrefix(dest)
+ var hrefBuf bytes.Buffer
+ hrefBuf.WriteString("href=\"")
+ escLink(&hrefBuf, dest)
+ hrefBuf.WriteByte('"')
+ attrs = append(attrs, hrefBuf.String())
+ if node.NoteID != 0 {
+ r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node))
+ break
+ }
+ attrs = appendLinkAttrs(attrs, r.Flags, dest)
+ if len(node.LinkData.Title) > 0 {
+ var titleBuff bytes.Buffer
+ titleBuff.WriteString("title=\"")
+ escapeHTML(&titleBuff, node.LinkData.Title)
+ titleBuff.WriteByte('"')
+ attrs = append(attrs, titleBuff.String())
+ }
+ r.tag(w, aTag, attrs)
+ } else {
+ if node.NoteID != 0 {
+ break
+ }
+ r.out(w, aCloseTag)
+ }
+ }
+ case Image:
+ if r.Flags&SkipImages != 0 {
+ return SkipChildren
+ }
+ if entering {
+ dest := node.LinkData.Destination
+ dest = r.addAbsPrefix(dest)
+ if r.disableTags == 0 {
+ //if options.safe && potentiallyUnsafe(dest) {
+ //out(w, `
`))
+ }
+ }
+ case Code:
+ r.out(w, codeTag)
+ escapeAllHTML(w, node.Literal)
+ r.out(w, codeCloseTag)
+ case Document:
+ break
+ case Paragraph:
+ if skipParagraphTags(node) {
+ break
+ }
+ if entering {
+ // TODO: untangle this clusterfuck about when the newlines need
+ // to be added and when not.
+ if node.Prev != nil {
+ switch node.Prev.Type {
+ case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
+ r.cr(w)
+ }
+ }
+ if node.Parent.Type == BlockQuote && node.Prev == nil {
+ r.cr(w)
+ }
+ r.out(w, pTag)
+ } else {
+ r.out(w, pCloseTag)
+ if !(node.Parent.Type == Item && node.Next == nil) {
+ r.cr(w)
+ }
+ }
+ case BlockQuote:
+ if entering {
+ r.cr(w)
+ r.out(w, blockquoteTag)
+ } else {
+ r.out(w, blockquoteCloseTag)
+ r.cr(w)
+ }
+ case HTMLBlock:
+ if r.Flags&SkipHTML != 0 {
+ break
+ }
+ r.cr(w)
+ r.out(w, node.Literal)
+ r.cr(w)
+ case Heading:
+ headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
+ openTag, closeTag := headingTagsFromLevel(headingLevel)
+ if entering {
+ if node.IsTitleblock {
+ attrs = append(attrs, `class="title"`)
+ }
+ if node.HeadingID != "" {
+ id := r.ensureUniqueHeadingID(node.HeadingID)
+ if r.HeadingIDPrefix != "" {
+ id = r.HeadingIDPrefix + id
+ }
+ if r.HeadingIDSuffix != "" {
+ id = id + r.HeadingIDSuffix
+ }
+ attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
+ }
+ r.cr(w)
+ r.tag(w, openTag, attrs)
+ } else {
+ r.out(w, closeTag)
+ if !(node.Parent.Type == Item && node.Next == nil) {
+ r.cr(w)
+ }
+ }
+ case HorizontalRule:
+ r.cr(w)
+ r.outHRTag(w)
+ r.cr(w)
+ case List:
+ openTag := ulTag
+ closeTag := ulCloseTag
+ if node.ListFlags&ListTypeOrdered != 0 {
+ openTag = olTag
+ closeTag = olCloseTag
+ }
+ if node.ListFlags&ListTypeDefinition != 0 {
+ openTag = dlTag
+ closeTag = dlCloseTag
+ }
+ if entering {
+ if node.IsFootnotesList {
+ r.out(w, footnotesDivBytes)
+ r.outHRTag(w)
+ r.cr(w)
+ }
+ r.cr(w)
+ if node.Parent.Type == Item && node.Parent.Parent.Tight {
+ r.cr(w)
+ }
+ r.tag(w, openTag[:len(openTag)-1], attrs)
+ r.cr(w)
+ } else {
+ r.out(w, closeTag)
+ //cr(w)
+ //if node.parent.Type != Item {
+ // cr(w)
+ //}
+ if node.Parent.Type == Item && node.Next != nil {
+ r.cr(w)
+ }
+ if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
+ r.cr(w)
+ }
+ if node.IsFootnotesList {
+ r.out(w, footnotesCloseDivBytes)
+ }
+ }
+ case Item:
+ openTag := liTag
+ closeTag := liCloseTag
+ if node.ListFlags&ListTypeDefinition != 0 {
+ openTag = ddTag
+ closeTag = ddCloseTag
+ }
+ if node.ListFlags&ListTypeTerm != 0 {
+ openTag = dtTag
+ closeTag = dtCloseTag
+ }
+ if entering {
+ if itemOpenCR(node) {
+ r.cr(w)
+ }
+ if node.ListData.RefLink != nil {
+ slug := slugify(node.ListData.RefLink)
+ r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
+ break
+ }
+ r.out(w, openTag)
+ } else {
+ if node.ListData.RefLink != nil {
+ slug := slugify(node.ListData.RefLink)
+ if r.Flags&FootnoteReturnLinks != 0 {
+ r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
+ }
+ }
+ r.out(w, closeTag)
+ r.cr(w)
+ }
+ case CodeBlock:
+ attrs = appendLanguageAttr(attrs, node.Info)
+ r.cr(w)
+ r.out(w, preTag)
+ r.tag(w, codeTag[:len(codeTag)-1], attrs)
+ escapeAllHTML(w, node.Literal)
+ r.out(w, codeCloseTag)
+ r.out(w, preCloseTag)
+ if node.Parent.Type != Item {
+ r.cr(w)
+ }
+ case Table:
+ if entering {
+ r.cr(w)
+ r.out(w, tableTag)
+ } else {
+ r.out(w, tableCloseTag)
+ r.cr(w)
+ }
+ case TableCell:
+ openTag := tdTag
+ closeTag := tdCloseTag
+ if node.IsHeader {
+ openTag = thTag
+ closeTag = thCloseTag
+ }
+ if entering {
+ align := cellAlignment(node.Align)
+ if align != "" {
+ attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
+ }
+ if node.Prev == nil {
+ r.cr(w)
+ }
+ r.tag(w, openTag, attrs)
+ } else {
+ r.out(w, closeTag)
+ r.cr(w)
+ }
+ case TableHead:
+ if entering {
+ r.cr(w)
+ r.out(w, theadTag)
+ } else {
+ r.out(w, theadCloseTag)
+ r.cr(w)
+ }
+ case TableBody:
+ if entering {
+ r.cr(w)
+ r.out(w, tbodyTag)
+ // XXX: this is to adhere to a rather silly test. Should fix test.
+ if node.FirstChild == nil {
+ r.cr(w)
+ }
+ } else {
+ r.out(w, tbodyCloseTag)
+ r.cr(w)
+ }
+ case TableRow:
+ if entering {
+ r.cr(w)
+ r.out(w, trTag)
+ } else {
+ r.out(w, trCloseTag)
+ r.cr(w)
+ }
+ default:
+ panic("Unknown node type " + node.Type.String())
+ }
+ return GoToNext
+}
+
+// RenderHeader writes HTML document preamble and TOC if requested.
+func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) {
+ r.writeDocumentHeader(w)
+ if r.Flags&TOC != 0 {
+ r.writeTOC(w, ast)
+ }
+}
+
+// RenderFooter writes HTML document footer.
+func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) {
+ if r.Flags&CompletePage == 0 {
+ return
+ }
+ io.WriteString(w, "\n