From 70cfc565e74dbe5d33b09eddaabdbf5c9902ea93 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 11 Sep 2026 19:13:17 +0200 Subject: [PATCH] Bump tcell to v3.5.0 This fixes a regression with Esc being handled with a significant delay on some terminals. --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/gdamore/tcell/v3/cell.go | 42 ++++++++++++++ vendor/github.com/gdamore/tcell/v3/input.go | 56 ++++++++++++------- vendor/github.com/gdamore/tcell/v3/screen.go | 15 +++++ vendor/github.com/gdamore/tcell/v3/tscreen.go | 17 +++++- vendor/modules.txt | 2 +- 7 files changed, 112 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 6612d0756..e822034ca 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/cli/go-gh/v2 v2.13.0 github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 github.com/creack/pty v1.1.24 - github.com/gdamore/tcell/v3 v3.4.2 + github.com/gdamore/tcell/v3 v3.5.0 github.com/go-errors/errors v1.5.1 github.com/gookit/color v1.6.1 github.com/integrii/flaggy v1.8.0 diff --git a/go.sum b/go.sum index 3c4b8793b..2198ae1bd 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= -github.com/gdamore/tcell/v3 v3.4.2 h1:gGW+6z2Bz5Wl2mNwFlm9+eRmg2JQrWcKjSkL1LRfpNU= -github.com/gdamore/tcell/v3 v3.4.2/go.mod h1:Oe5U3S3jm3NzypswDNUhe+LUnF5CoFq2b4sepD++QHo= +github.com/gdamore/tcell/v3 v3.5.0 h1:SCp9czLv2K2aPORgD6+4fjV0xNAzKIkiezCkp6bHLe4= +github.com/gdamore/tcell/v3 v3.5.0/go.mod h1:Oe5U3S3jm3NzypswDNUhe+LUnF5CoFq2b4sepD++QHo= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= diff --git a/vendor/github.com/gdamore/tcell/v3/cell.go b/vendor/github.com/gdamore/tcell/v3/cell.go index 73756b37d..8f5ed0c78 100644 --- a/vendor/github.com/gdamore/tcell/v3/cell.go +++ b/vendor/github.com/gdamore/tcell/v3/cell.go @@ -252,3 +252,45 @@ func (cb *CellBuffer) Fill(r rune, style Style) { c.width = 1 } } + +// FillArea fills a rectangular region of the cell buffer with the specified +// character and style. The region starts at column x, row y and extends w +// columns to the right and h rows down; any part of it lying outside the +// buffer is simply skipped, so callers do not need to clip coordinates +// themselves. A zero or negative width or height fills nothing. As with +// Fill, this doesn't support combining characters or wide runes, and a +// ColorNone foreground or background leaves that color unchanged. +func (cb *CellBuffer) FillArea(x, y, w, h int, r rune, style Style) { + if w <= 0 || h <= 0 { + return + } + x0 := max(x, 0) + y0 := max(y, 0) + // Clip the far edge to the buffer without ever evaluating x+w or y+h when + // they would overflow a signed int. Because w and h are positive here, + // x < cb.w-w is equivalent to x+w < cb.w but cannot overflow, and it is + // only true when x+w is small enough to compute safely. + x1 := cb.w + if x < cb.w-w { + x1 = x + w + } + y1 := cb.h + if y < cb.h-h { + y1 = y + h + } + for row := y0; row < y1; row++ { + for col := x0; col < x1; col++ { + c := &cb.cells[(row*cb.w)+col] + c.currStr = string(r) + cs := style + if cs.fg == ColorNone { + cs.fg = c.currStyle.fg + } + if cs.bg == ColorNone { + cs.bg = c.currStyle.bg + } + c.currStyle = cs + c.width = 1 + } + } +} diff --git a/vendor/github.com/gdamore/tcell/v3/input.go b/vendor/github.com/gdamore/tcell/v3/input.go index 0c279f998..b1e0ff54b 100644 --- a/vendor/github.com/gdamore/tcell/v3/input.go +++ b/vendor/github.com/gdamore/tcell/v3/input.go @@ -64,9 +64,10 @@ const ( const defaultControlStringLimit = 64 * 1024 const ( - // loneEscapeTimeout keeps bare Escape responsive when using legacy - // keyboard reporting, where ESC can also prefix an Alt-modified key. - loneEscapeTimeout = 200 * time.Millisecond + // loneEscapeTimeout keeps bare Escape responsive. A lone ESC byte is + // always ambiguous, because it can also prefix an Alt-modified key or a + // longer sequence, so it cannot be resolved until this expires. + loneEscapeTimeout = 50 * time.Millisecond // escapeSequenceTimeout bounds incomplete escape sequences. Once a // sequence introducer has arrived, it is no longer ambiguous with a lone @@ -129,27 +130,15 @@ func asciiByteFromInt(n int) (byte, bool) { return byte(n), true } -// Waiting returns true if the processor is waiting for -// some more input (i.e. we are not in in the initial state.) -// This can occur when we have ambiguous escape sequences, such -// as the lone escape. If this is typed, we expect at least a minimal -// inter-key delay before the next stroke occurs, and the caller -// should check for waiting, and call Scan() or ScanUTF8() to -// finish the processing. (Typically after a delay of around 100ms.) -func (ip *inputParser) Waiting() bool { - ip.l.Lock() - defer ip.l.Unlock() - return ip.state != istInit -} - // waitDuration reports how long to wait for the next byte before resetting an -// incomplete escape sequence. A bare ESC is only ambiguous with legacy -// keyboard reporting; other protocols can use the longer sequence deadline. +// incomplete escape sequence. A bare ESC is ambiguous under every keyboard +// protocol, so it gets the short deadline; only once an introducer has arrived +// is the longer sequence deadline used. func (ip *inputParser) waitDuration() time.Duration { if ip.state == istInit { return 0 } - if ip.state == istEsc && ip.legacy { + if ip.state == istEsc { return loneEscapeTimeout } return escapeSequenceTimeout @@ -867,6 +856,9 @@ func (ip *inputParser) handleXda(str string) { } func calcModifier(n int) ModMask { + if n < 1 { + return ModNone + } n-- m := ModNone if n&1 != 0 { @@ -987,6 +979,26 @@ func kittyModifierKey(code int) ModMask { } } +// kittyKeyText extracts the associated text (kitty mode 16) from a csi-u +// event's params: the third ;-field, codepoints :separated. Empty when +// the event carries no text (control keys, specials, terminals without +// mode 16), so callers fall back to the base key. +func kittyKeyText(params string) string { + fields := strings.Split(params, ";") + if len(fields) < 3 || fields[2] == "" { + return "" + } + var b strings.Builder + // Reject C0 control chars, DEL, and C1 control chars: they must never + // surface as key text. + for cp := range strings.SplitSeq(fields[2], ":") { + if n, err := strconv.ParseInt(cp, 10, 32); err == nil && n >= 0x20 && (n < 0x7f || n > 0x9f) && utf8.ValidRune(rune(n)) { + b.WriteRune(rune(n)) + } + } + return b.String() +} + func (ip *inputParser) handleMouse(mode rune, params []int) { // XTerm mouse events only report at most one button at a time, @@ -1421,6 +1433,12 @@ func (ip *inputParser) handleCsi(mode rune, params []byte, intermediate []byte) if mod1 := kittyModifierKey(P0); mod1 != ModNone { mod |= mod1 } + // kitty mode 16: text is the layout-correct output, sent with its + // modifiers - keep both. No text falls through to the base key. + if text := kittyKeyText(pstr); text != "" { + ip.postKeyEx(KeyRune, text, mod, pressed, physical, repeat) + return + } if key != KeyRune { ip.postKeyEx(key, "", mod, pressed, physical, repeat) } else if chr != 0 { diff --git a/vendor/github.com/gdamore/tcell/v3/screen.go b/vendor/github.com/gdamore/tcell/v3/screen.go index 553709c4d..1274fd93b 100644 --- a/vendor/github.com/gdamore/tcell/v3/screen.go +++ b/vendor/github.com/gdamore/tcell/v3/screen.go @@ -39,6 +39,14 @@ type Screen interface { // is called (or Sync). Fill(rune, Style) + // FillArea fills a rectangular region of the screen with the given + // character and style. The region starts at column x, row y and + // extends width columns to the right and height rows down. Any part + // of the region outside the screen is ignored, so it's safe to pass + // coordinates that overflow the screen. Like Fill, the change is not + // visible until Show (or Sync) is called. + FillArea(x int, y int, width int, height int, r rune, style Style) + // Put writes the first grapheme of the given string with th // given style at the given coordinates. (Only the first grapheme // occupying either one or two cells is stored.) It returns the @@ -426,6 +434,13 @@ func (b *baseScreen) Fill(r rune, style Style) { b.Unlock() } +func (b *baseScreen) FillArea(x, y, width, height int, r rune, style Style) { + cb := b.GetCells() + b.Lock() + cb.FillArea(x, y, width, height, r, style) + b.Unlock() +} + func (b *baseScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) { b.Put(x, y, string(append([]rune{mainc}, combc...)), style) } diff --git a/vendor/github.com/gdamore/tcell/v3/tscreen.go b/vendor/github.com/gdamore/tcell/v3/tscreen.go index bbd31e316..940207fe2 100644 --- a/vendor/github.com/gdamore/tcell/v3/tscreen.go +++ b/vendor/github.com/gdamore/tcell/v3/tscreen.go @@ -179,7 +179,7 @@ const ( notifyDesktop777 = "\x1b]777;notify;%s;%s\x1b\\" // Most commonly supported queryKittyKbd = "\x1b[?u" // Query for Kitty keyboard support enableKittyKbd = "\x1b[=1u" // Technically this pushes - enableKittyKbdAdv = "\x1b[=15u" // disambiguation, events, alternate keys, all keys + enableKittyKbdAdv = "\x1b[=31u" // disambiguation, events, alternate keys, all keys, text disableKittyKbd = "\x1b[=0u" // Technically this means pop previous mode queryXTermKbd = "\x1b[?4m" // Query for XTerm modify other keys support enableXTermKbd = "\x1b[>4;2m" // Enable modify other keys protocol @@ -1374,13 +1374,24 @@ func (t *tScreen) inputLoop(stopQ chan struct{}) { defer t.wg.Done() for { + readDone := make(chan bool) + chunk := make([]byte, 128) + var n int + var e error select { case <-stopQ: return default: + go func() { + n, e = t.tty.Read(chunk) + close(readDone) + }() + select { + case <-stopQ: + return + case <-readDone: + } } - chunk := make([]byte, 128) - n, e := t.tty.Read(chunk) switch e { case nil: default: diff --git a/vendor/modules.txt b/vendor/modules.txt index e4d6e564f..8e401aeb5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -45,7 +45,7 @@ github.com/fatih/color # github.com/gdamore/encoding v1.0.1 ## explicit; go 1.9 github.com/gdamore/encoding -# github.com/gdamore/tcell/v3 v3.4.2 +# github.com/gdamore/tcell/v3 v3.5.0 ## explicit; go 1.25.0 github.com/gdamore/tcell/v3 github.com/gdamore/tcell/v3/color