diff --git a/vendor/github.com/charmbracelet/bubbletea/v2/.golangci.yml b/vendor/github.com/charmbracelet/bubbletea/v2/.golangci.yml index be61d89ba130fbb50371386e01017eab20854930..4fac29c26d828a7aed03ddc2b8b0a469c0a85a2d 100644 --- a/vendor/github.com/charmbracelet/bubbletea/v2/.golangci.yml +++ b/vendor/github.com/charmbracelet/bubbletea/v2/.golangci.yml @@ -7,7 +7,6 @@ linters: - exhaustive - goconst - godot - - godox - gomoddirectives - goprintffuncname - gosec diff --git a/vendor/github.com/charmbracelet/bubbletea/v2/README.md b/vendor/github.com/charmbracelet/bubbletea/v2/README.md index c9339d90e0b86ddfa75d73ae07a0c2347a3cc1ae..c548fbead273b8263643d10823c589ed11d97e00 100644 --- a/vendor/github.com/charmbracelet/bubbletea/v2/README.md +++ b/vendor/github.com/charmbracelet/bubbletea/v2/README.md @@ -10,7 +10,6 @@ Latest Release GoDoc Build Status - phorm.ai

The fun, functional and stateful way to build terminal apps. A Go framework diff --git a/vendor/github.com/charmbracelet/bubbletea/v2/cursed_renderer.go b/vendor/github.com/charmbracelet/bubbletea/v2/cursed_renderer.go index 0680988e84305084c98aa16d20aee7954b2b3106..2ebe3cfcfd84f7f18a31edc273c94760ffb9d34f 100644 --- a/vendor/github.com/charmbracelet/bubbletea/v2/cursed_renderer.go +++ b/vendor/github.com/charmbracelet/bubbletea/v2/cursed_renderer.go @@ -104,6 +104,21 @@ func (s *cursedRenderer) writeString(str string) (int, error) { return s.scr.WriteString(str) } +// resetLinesRendered implements renderer. +func (s *cursedRenderer) resetLinesRendered() { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.altScreen { + var frameHeight int + if s.lastFrame != nil { + frameHeight = strings.Count(*s.lastFrame, "\n") + 1 + } + + io.WriteString(s.w, strings.Repeat("\n", max(0, frameHeight-1))) //nolint:errcheck,gosec + } +} + // flush implements renderer. func (s *cursedRenderer) flush(p *Program) error { s.mu.Lock() diff --git a/vendor/github.com/charmbracelet/bubbletea/v2/exec.go b/vendor/github.com/charmbracelet/bubbletea/v2/exec.go index e8af5c42fe3ed918bd20fd3903ee0e2b89042505..cd18011f8ca458f1f5e21af6696b7aa70b1eb872 100644 --- a/vendor/github.com/charmbracelet/bubbletea/v2/exec.go +++ b/vendor/github.com/charmbracelet/bubbletea/v2/exec.go @@ -114,6 +114,7 @@ func (p *Program) exec(c ExecCommand, fn ExecCallback) { // Execute system command. if err := c.Run(); err != nil { + p.renderer.resetLinesRendered() _ = p.RestoreTerminal() // also try to restore the terminal. if fn != nil { go p.Send(fn(err)) @@ -121,6 +122,9 @@ func (p *Program) exec(c ExecCommand, fn ExecCallback) { return } + // Maintain the existing output from the command + p.renderer.resetLinesRendered() + // Have the program re-capture input. err := p.RestoreTerminal() if fn != nil { diff --git a/vendor/github.com/charmbracelet/bubbletea/v2/nil_renderer.go b/vendor/github.com/charmbracelet/bubbletea/v2/nil_renderer.go index b4a04e04b40dc1c7939780216ebdbb1de5b36419..99d4aa694b0101e41da295c883e088593f70e2bf 100644 --- a/vendor/github.com/charmbracelet/bubbletea/v2/nil_renderer.go +++ b/vendor/github.com/charmbracelet/bubbletea/v2/nil_renderer.go @@ -67,4 +67,5 @@ func (n nilRenderer) setBackgroundColor(color.Color) {} func (n nilRenderer) setWindowTitle(string) {} // hit implements the Renderer interface. -func (n nilRenderer) hit(MouseMsg) []Msg { return nil } +func (n nilRenderer) hit(MouseMsg) []Msg { return nil } +func (n nilRenderer) resetLinesRendered() {} diff --git a/vendor/github.com/charmbracelet/bubbletea/v2/renderer.go b/vendor/github.com/charmbracelet/bubbletea/v2/renderer.go index 97c7c741fb1659f300cb8d08018f25c0f8b50da0..086860d636c2223fcb0c3c3737f10a91d3d69070 100644 --- a/vendor/github.com/charmbracelet/bubbletea/v2/renderer.go +++ b/vendor/github.com/charmbracelet/bubbletea/v2/renderer.go @@ -71,6 +71,9 @@ type renderer interface { repaint() writeString(string) (int, error) + + // resetLinesRendered ensures exec output remains on screen on exit + resetLinesRendered() } // repaintMsg forces a full repaint. diff --git a/vendor/github.com/charmbracelet/bubbletea/v2/standard_renderer.go b/vendor/github.com/charmbracelet/bubbletea/v2/standard_renderer.go index b7858eb01cbf1beddb994b6b0567ff8e6d4a059f..4b59cee3eccca9d691f18dcdd57cdcd8e05bae9d 100644 --- a/vendor/github.com/charmbracelet/bubbletea/v2/standard_renderer.go +++ b/vendor/github.com/charmbracelet/bubbletea/v2/standard_renderer.go @@ -382,3 +382,6 @@ func (r *standardRenderer) setForegroundColor(c color.Color) {} func (r *standardRenderer) setBackgroundColor(c color.Color) {} func (r *standardRenderer) setWindowTitle(s string) {} func (r *standardRenderer) hit(MouseMsg) []Msg { return nil } +func (r *standardRenderer) resetLinesRendered() { + r.linesRendered = 0 +} diff --git a/vendor/github.com/charmbracelet/bubbletea/v2/tea.go b/vendor/github.com/charmbracelet/bubbletea/v2/tea.go index 6f403cc79ad3a0b26eb37705417aa8d7ec277fdf..5320e4463a3b673a2a980009ed2fb25bc2853c71 100644 --- a/vendor/github.com/charmbracelet/bubbletea/v2/tea.go +++ b/vendor/github.com/charmbracelet/bubbletea/v2/tea.go @@ -1220,13 +1220,22 @@ func (p *Program) recoverFromPanic(r any) { default: } p.cancel() // Just in case a previous shutdown has failed. - s := strings.ReplaceAll( - fmt.Sprintf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r), - "\n", "\r\n") - fmt.Fprintln(os.Stderr, s) - stack := strings.ReplaceAll(fmt.Sprintf("%s", debug.Stack()), "\n", "\r\n") - fmt.Fprintln(os.Stderr, stack) p.shutdown(true) + // We use "\r\n" to ensure the output is formatted even when restoring the + // terminal does not work or when raw mode is still active. + rec := strings.ReplaceAll(fmt.Sprintf("%s", r), "\n", "\r\n") + fmt.Fprintf(os.Stderr, "Caught panic:\r\n\r\n%s\r\n\r\nRestoring terminal...\r\n\r\n", rec) + stack := strings.ReplaceAll(fmt.Sprintf("%s\n", debug.Stack()), "\n", "\r\n") + fmt.Fprint(os.Stderr, stack) + if v, err := strconv.ParseBool(os.Getenv("TEA_DEBUG")); err == nil && v { + f, err := os.Create(fmt.Sprintf("bubbletea-panic-%d.log", time.Now().Unix())) + if err == nil { + defer f.Close() //nolint:errcheck + fmt.Fprintln(f, rec) //nolint:errcheck + fmt.Fprintln(f) //nolint:errcheck + fmt.Fprintln(f, stack) //nolint:errcheck + } + } } // ReleaseTerminal restores the original terminal state and cancels the input diff --git a/vendor/github.com/charmbracelet/ultraviolet/event.go b/vendor/github.com/charmbracelet/ultraviolet/event.go index 35174d748665ec4d8c34ee5fd5098b205aee632d..68aba250988e3504bcfccc6e86f694b1e3c8809d 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/event.go +++ b/vendor/github.com/charmbracelet/ultraviolet/event.go @@ -131,6 +131,24 @@ func (k KeyPressEvent) String() string { return Key(k).String() } +// Keystroke returns the keystroke representation of the [Key]. While less type +// safe than looking at the individual fields, it will usually be more +// convenient and readable to use this method when matching against keys. +// +// Note that modifier keys are always printed in the following order: +// - ctrl +// - alt +// - shift +// - meta +// - hyper +// - super +// +// For example, you'll always see "ctrl+shift+alt+a" and never +// "shift+ctrl+alt+a". +func (k KeyPressEvent) Keystroke() string { + return Key(k).Keystroke() +} + // Key returns the underlying key event. This is a syntactic sugar for casting // the key event to a [Key]. func (k KeyPressEvent) Key() Key { @@ -163,6 +181,24 @@ func (k KeyReleaseEvent) String() string { return Key(k).String() } +// Keystroke returns the keystroke representation of the [Key]. While less type +// safe than looking at the individual fields, it will usually be more +// convenient and readable to use this method when matching against keys. +// +// Note that modifier keys are always printed in the following order: +// - ctrl +// - alt +// - shift +// - meta +// - hyper +// - super +// +// For example, you'll always see "ctrl+shift+alt+a" and never +// "shift+ctrl+alt+a". +func (k KeyReleaseEvent) Keystroke() string { + return Key(k).Keystroke() +} + // Key returns the underlying key event. This is a convenience method and // syntactic sugar to satisfy the [KeyEvent] interface, and cast the key event to // [Key]. diff --git a/vendor/github.com/charmbracelet/ultraviolet/parse.go b/vendor/github.com/charmbracelet/ultraviolet/parse.go index f845200813cf7f64ef0aac450c12a82122546c9b..3ec41659c81e6315f88631478f2078e1c6252d7f 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/parse.go +++ b/vendor/github.com/charmbracelet/ultraviolet/parse.go @@ -748,10 +748,12 @@ func (p *SequenceParser) parseSs3(b []byte) (int, Event) { } func (p *SequenceParser) parseOsc(b []byte) (int, Event) { - defaultKey := KeyPressEvent{Code: rune(b[1]), Mod: ModAlt} + defaultKey := func() KeyPressEvent { + return KeyPressEvent{Code: rune(b[1]), Mod: ModAlt} + } if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+] key - return 2, defaultKey + return 2, defaultKey() } var i int @@ -802,7 +804,7 @@ func (p *SequenceParser) parseOsc(b []byte) (int, Event) { case ansi.ESC: if i >= len(b) || b[i] != '\\' { if cmd == -1 || (start == 0 && end == 2) { - return 2, defaultKey + return 2, defaultKey() } // If we don't have a valid ST terminator, then this is a diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go index aa08ef964974003899a390e9708e04743d5befa5..987b3c6f8e3902c2b8f48936dee1991ffc6926f7 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go @@ -55,14 +55,15 @@ type TerminalReader struct { // Windows Console API. MouseMode *MouseMode - // Timeout is the escape character timeout duration. Most escape sequences - // start with an escape character [ansi.ESC] and are followed by one or - // more characters. If the next character is not received within this - // timeout, the reader will assume that the escape sequence is complete and - // will process the received characters as a complete escape sequence. + // EscTimeout is the escape character timeout duration. Most escape + // sequences start with an escape character [ansi.ESC] and are followed by + // one or more characters. If the next character is not received within + // this timeout, the reader will assume that the escape sequence is + // complete and will process the received characters as a complete escape + // sequence. // // By default, this is set to [DefaultEscTimeout] (50 milliseconds). - Timeout time.Duration + EscTimeout time.Duration r io.Reader rd cancelreader.CancelReader @@ -85,6 +86,7 @@ type TerminalReader struct { // prevent multiple calls to the Close() method. closed bool started bool // started indicates whether the reader has been started. + runOnce sync.Once // runOnce is used to ensure that the reader is only started once. close chan struct{} // close is a channel used to signal the reader to close. closeOnce sync.Once notify chan []byte // notify is a channel used to notify the reader of new input events. @@ -113,10 +115,10 @@ type TerminalReader struct { // } func NewTerminalReader(r io.Reader, termType string) *TerminalReader { return &TerminalReader{ - Timeout: DefaultEscTimeout, - r: r, - term: termType, - lookup: true, // Use lookup table by default. + EscTimeout: DefaultEscTimeout, + r: r, + term: termType, + lookup: true, // Use lookup table by default. } } @@ -131,22 +133,20 @@ func (d *TerminalReader) SetLogger(logger Logger) { // lookup table for key sequences if it is not already set. This function // should be called before reading input events. func (d *TerminalReader) Start() (err error) { - if d.rd == nil { - d.rd, err = newCancelreader(d.r) - if err != nil { - return err - } + d.rd, err = newCancelreader(d.r) + if err != nil { + return err } if d.table == nil { d.table = buildKeysTable(d.Legacy, d.term, d.UseTerminfo) } d.started = true d.esc.Store(false) - d.timeout = time.NewTimer(d.Timeout) + d.timeout = time.NewTimer(d.EscTimeout) d.notify = make(chan []byte) d.close = make(chan struct{}, 1) d.closeOnce = sync.Once{} - go d.run() + d.runOnce = sync.Once{} return nil } @@ -194,6 +194,11 @@ func (d *TerminalReader) receiveEvents(ctx context.Context, events chan<- Event) return ErrReaderNotStarted } + // Start the reader loop if it hasn't been started yet. + d.runOnce.Do(func() { + go d.run() + }) + closingFunc := func() error { // If we're closing, make sure to send any remaining events even if // they are incomplete. @@ -255,7 +260,7 @@ func (d *TerminalReader) run() { esc := n > 0 && n <= 2 && readBuf[0] == ansi.ESC if esc { d.esc.Store(true) - d.timeout.Reset(d.Timeout) + d.timeout.Reset(d.EscTimeout) } d.notify <- readBuf[:n] @@ -305,7 +310,7 @@ LOOP: ansi.ESC, ansi.CSI, ansi.OSC, ansi.DCS, ansi.APC, ansi.SOS, ansi.PM, }, d.buf[0]) { d.esc.Store(true) - d.timeout.Reset(d.Timeout) + d.timeout.Reset(d.EscTimeout) } } // If this is the entire buffer, we can break and assume this diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go index 802b0d001f6e9e41a16dea1fc705234f965ef96f..8c01d8af69aeb0360757ed7b2c34c683f20b37a6 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "strings" + "time" "unicode" "unicode/utf16" "unicode/utf8" @@ -22,7 +23,7 @@ import ( // given event channel. func (d *TerminalReader) ReceiveEvents(ctx context.Context, events chan<- Event) error { for { - evs, err := d.handleConInput(readConsoleInput) + evs, err := d.handleConInput() if errors.Is(err, errNotConInputReader) { return d.receiveEvents(ctx, events) } @@ -41,31 +42,45 @@ func (d *TerminalReader) ReceiveEvents(ctx context.Context, events chan<- Event) var errNotConInputReader = fmt.Errorf("handleConInput: not a conInputReader") -func (d *TerminalReader) handleConInput( - finput func(windows.Handle, []xwindows.InputRecord) (uint32, error), -) ([]Event, error) { +func (d *TerminalReader) handleConInput() ([]Event, error) { cc, ok := d.rd.(*conInputReader) if !ok { return nil, errNotConInputReader } - // read up to 256 events, this is to allow for sequences events reported as - // key events. - var events [256]xwindows.InputRecord - _, err := finput(cc.conin, events[:]) - if err != nil { + var ( + events []xwindows.InputRecord + err error + ) + for { + // Peek up to 256 events, this is to allow for sequences events reported as + // key events. + events, err = peekNConsoleInputs(cc.conin, 256) if cc.isCanceled() { return nil, cancelreader.ErrCanceled } + if err != nil { + return nil, fmt.Errorf("peek coninput events: %w", err) + } + if len(events) > 0 { + break + } + + // Sleep for a bit to avoid busy waiting. + time.Sleep(10 * time.Millisecond) + } + + events, err = readNConsoleInputs(cc.conin, uint32(len(events))) + if cc.isCanceled() { + return nil, cancelreader.ErrCanceled + } + if err != nil { return nil, fmt.Errorf("read coninput events: %w", err) } var evs []Event for _, event := range events { if e := d.SequenceParser.parseConInputEvent(event, &d.keyState, d.MouseMode, d.logger); e != nil { - if e == nil { - continue - } if multi, ok := e.(MultiEvent); ok { if d.logger != nil { for _, ev := range multi { @@ -223,6 +238,16 @@ func highWord(data uint32) uint16 { return uint16((data & 0xFFFF0000) >> 16) //nolint:gosec } +func readNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.InputRecord, error) { + if maxEvents == 0 { + return nil, fmt.Errorf("maxEvents cannot be zero") + } + + records := make([]xwindows.InputRecord, maxEvents) + n, err := readConsoleInput(console, records) + return records[:n], err +} + func readConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) { if len(inputRecords) == 0 { return 0, fmt.Errorf("size of input record buffer cannot be zero") @@ -235,7 +260,6 @@ func readConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecor return read, err //nolint:wrapcheck } -//nolint:unused func peekConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) { if len(inputRecords) == 0 { return 0, fmt.Errorf("size of input record buffer cannot be zero") @@ -248,6 +272,16 @@ func peekConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecor return read, err //nolint:wrapcheck } +func peekNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.InputRecord, error) { + if maxEvents == 0 { + return nil, fmt.Errorf("maxEvents cannot be zero") + } + + records := make([]xwindows.InputRecord, maxEvents) + n, err := peekConsoleInput(console, records) + return records[:n], err +} + // parseWin32InputKeyEvent parses a single key event from either the Windows // Console API or win32-input-mode events. When state is nil, it means this is // an event from win32-input-mode. Otherwise, it's a key event from the Windows @@ -506,7 +540,9 @@ func (p *SequenceParser) parseWin32InputKeyEvent(state *win32InputState, vkc uin var text string keyCode := baseCode - if !unicode.IsControl(r) { + if unicode.IsControl(r) { + return p.parseControl(byte(r)) + } else { rw := utf8.EncodeRune(utf8Buf[:], r) keyCode, _ = utf8.DecodeRune(utf8Buf[:rw]) if unicode.IsPrint(keyCode) && (cks == 0 || @@ -517,18 +553,18 @@ func (p *SequenceParser) parseWin32InputKeyEvent(state *win32InputState, vkc uin // then the key event is a printable event i.e. [text] is not empty. text = string(keyCode) } - } - key.Code = keyCode - key.Text = text - key.Mod = translateControlKeyState(cks) - key.BaseCode = baseCode - key = ensureKeyCase(key, cks) - if keyDown { - return KeyPressEvent(key) - } + key.Code = keyCode + key.Text = text + key.Mod = translateControlKeyState(cks) + key.BaseCode = baseCode + key = ensureKeyCase(key, cks) + if keyDown { + return KeyPressEvent(key) + } - return KeyReleaseEvent(key) + return KeyReleaseEvent(key) + } } // ensureKeyCase ensures that the key's text is in the correct case based on the diff --git a/vendor/modules.txt b/vendor/modules.txt index 23004dc9bdd70ee344dd69ca28acd5f0696b51e3..b1683ffa4cc62d09d5ccd8f26b382f2b349ba9fe 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -254,7 +254,7 @@ github.com/charmbracelet/bubbles/v2/spinner github.com/charmbracelet/bubbles/v2/textarea github.com/charmbracelet/bubbles/v2/textinput github.com/charmbracelet/bubbles/v2/viewport -# github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1 => github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250703182356-a42fb608faaf +# github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1 => github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250708145940-f4b2ad3636f9 ## explicit; go 1.24.3 github.com/charmbracelet/bubbletea/v2 # github.com/charmbracelet/colorprofile v0.3.1 @@ -269,7 +269,7 @@ github.com/charmbracelet/glamour/v2 github.com/charmbracelet/glamour/v2/ansi github.com/charmbracelet/glamour/v2/internal/autolink github.com/charmbracelet/glamour/v2/styles -# github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71 => github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250703152138-ff346e83e819 +# github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71 => github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250708150236-b6de769f3a51 ## explicit; go 1.24.2 github.com/charmbracelet/lipgloss/v2 github.com/charmbracelet/lipgloss/v2/table @@ -277,7 +277,7 @@ github.com/charmbracelet/lipgloss/v2/tree # github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 ## explicit; go 1.19 github.com/charmbracelet/log/v2 -# github.com/charmbracelet/ultraviolet v0.0.0-20250707134318-0fdaa64b8c5e +# github.com/charmbracelet/ultraviolet v0.0.0-20250708144633-4a8e4329a1a0 ## explicit; go 1.24.0 github.com/charmbracelet/ultraviolet # github.com/charmbracelet/x/ansi v0.9.3 @@ -838,5 +838,5 @@ mvdan.cc/sh/v3/fileutil mvdan.cc/sh/v3/interp mvdan.cc/sh/v3/pattern mvdan.cc/sh/v3/syntax -# github.com/charmbracelet/bubbletea/v2 => github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250703182356-a42fb608faaf -# github.com/charmbracelet/lipgloss/v2 => github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250703152138-ff346e83e819 +# github.com/charmbracelet/bubbletea/v2 => github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250708145940-f4b2ad3636f9 +# github.com/charmbracelet/lipgloss/v2 => github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250708150236-b6de769f3a51