Bump github.com/gdamore/tcell/v3 from 3.5.0 to 3.6.0

Bumps [github.com/gdamore/tcell/v3](https://github.com/gdamore/tcell) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/gdamore/tcell/releases)
- [Changelog](https://github.com/gdamore/tcell/blob/main/CHANGESv3.md)
- [Commits](https://github.com/gdamore/tcell/compare/v3.5.0...v3.6.0)

---
updated-dependencies:
- dependency-name: github.com/gdamore/tcell/v3
  dependency-version: 3.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot] authored and GitHub committed 2026-10-16 07:39:11 +00:00
1 parent c0fa952a94
commit 948fbcc1d6
22 files changed
+2643 -2060

No files matched your search

+5 -5
View File
@@ -16,7 +16,7 @@ require (
github.com/cli/go-gh/v2 v2.16.2
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.24
github.com/gdamore/tcell/v3 v3.5.0
github.com/gdamore/tcell/v3 v3.6.0
github.com/go-errors/errors v1.5.1
github.com/gookit/color v1.6.1
github.com/integrii/flaggy v1.8.0
@@ -66,10 +66,10 @@ require (
github.com/onsi/gomega v1.34.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/mod v0.38.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.41.0 // indirect
golang.org/x/tools v0.48.0 // indirect
golang.org/x/mod v0.41.0 // indirect
golang.org/x/term v0.46.0 // indirect
golang.org/x/text v0.42.0 // indirect
golang.org/x/tools v0.49.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
+12 -12
View File
@@ -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.5.0 h1:SCp9czLv2K2aPORgD6+4fjV0xNAzKIkiezCkp6bHLe4=
github.com/gdamore/tcell/v3 v3.5.0/go.mod h1:Oe5U3S3jm3NzypswDNUhe+LUnF5CoFq2b4sepD++QHo=
github.com/gdamore/tcell/v3 v3.6.0 h1:jW20qS6Tm38KVDqLHq9KWSvyjNl00FbhbD8rIAnHBws=
github.com/gdamore/tcell/v3 v3.6.0/go.mod h1:CBZxkl2T+XxEyuBfOh4zaahqm27dngvhfBfCCZxJsgc=
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=
@@ -138,14 +138,14 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -167,21 +167,21 @@ golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+16 -4
View File
@@ -131,11 +131,15 @@ variables provide user escape hatches when the automatic path is not reliable:
terminal responses themselves are problematic.
- `TCELL_MOUSE=auto|disable` prevents applications from enabling terminal mouse
reporting.
- `TCELL_ESCDELAY=<milliseconds>` controls how long tcell waits for a possible
escape sequence after receiving Escape. This defaults to 50 ms (125 ms on
Windows); increasing it avoids split-sequence input at the cost of delaying a
standalone Escape.
Applications can also choose a keyboard protocol with `OptKeyboardProtocol` or
disable startup negotiation with `OptNegotiation`. Environment variables take
precedence so users can recover from bad terminal behavior without modifying an
application.
Applications can also choose a keyboard protocol with `OptKeyboardProtocol`,
disable startup negotiation with `OptNegotiation`, or set an escape delay with
`OptEscapeTimeout`. Environment variables take precedence so users can recover
from bad terminal behavior without modifying an application.
## Performance
@@ -147,6 +151,14 @@ avoiding repeated sequences or drawing the same cell on refresh updates.
Mouse tracking, buttons, and even wheel mice are supported on most terminal
emulators, as well as Windows.
On the Linux virtual console, _Tcell_ can receive mouse events through GPM
when the `gpm` daemon is installed and running. For example, on Debian or
Ubuntu systems install it with `apt install gpm`; the daemon provides
`/dev/gpmctl`, which _Tcell_ uses automatically when available. If GPM is
not installed, not running, or the application is not attached to a virtual
console, mouse enablement remains best-effort and falls back to the usual
terminal mouse protocols.
## Bracketed Paste
Terminals that support support it, can use bracketed paste.
+17 -11
View File
@@ -224,12 +224,12 @@ func (cb *CellBuffer) Resize(w, h int) {
nc.currStr = oc.currStr
nc.currStyle = oc.currStyle
nc.width = oc.width
nc.lastStr = ""
}
}
cb.cells = newc
cb.h = h
cb.w = w
cb.Invalidate()
}
// Fill fills the entire cell buffer array with the specified character
@@ -238,9 +238,10 @@ func (cb *CellBuffer) Resize(w, h int) {
// If either the foreground or background are ColorNone, then the respective
// color is unchanged.
func (cb *CellBuffer) Fill(r rune, style Style) {
str := string(r)
for i := range cb.cells {
c := &cb.cells[i]
c.currStr = string(r)
c.currStr = str
cs := style
if cs.fg == ColorNone {
cs.fg = c.currStyle.fg
@@ -278,18 +279,23 @@ func (cb *CellBuffer) FillArea(x, y, w, h int, r rune, style Style) {
if y < cb.h-h {
y1 = y + h
}
if x0 >= x1 {
return
}
str := string(r)
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
cells := cb.cells[row*cb.w+x0 : row*cb.w+x1]
for i := range cells {
c := &cells[i]
c.currStr = str
fg, bg := c.currStyle.fg, c.currStyle.bg
c.currStyle = style
if style.fg == ColorNone {
c.currStyle.fg = fg
}
if cs.bg == ColorNone {
cs.bg = c.currStyle.bg
if style.bg == ColorNone {
c.currStyle.bg = bg
}
c.currStyle = cs
c.width = 1
}
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2026 The TCell 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 tcell
import "errors"
var errNoConsoleMouse = errors.New("native console mouse unavailable")
type consoleMouse interface {
Start(MouseFlags) error
Stop()
SetSize(int, int)
}
type consoleMouseFactory func(chan<- Event) (consoleMouse, error)
var newConsoleMouse consoleMouseFactory = func(chan<- Event) (consoleMouse, error) {
return nil, errNoConsoleMouse
}
+353
View File
@@ -0,0 +1,353 @@
// Copyright 2026 The TCell 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.
// This file implements a client for the GPM (general purpose mouse daemon)
// used to orchestrate mouse access and deliver mouse events to applications
// using the Linux console. This deliberately speaks the small public GPM ABI
// directly rather than linking against libgpm, so tcell does not acquire GPL
// licensing obligations from the library.
//go:build linux
package tcell
import (
"encoding/binary"
"fmt"
"io"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
)
const (
gpmConnectSize = 16
gpmEventSize = 28
gpmButtonDown = 32
gpmButtonUp = 16
gpmButtonFourth = 8
gpmButtonLeft = 4
gpmButtonMiddle = 2
gpmButtonRight = 1
gpmModShift = 1
gpmModAltGr = 2
gpmModCtrl = 4
gpmModAlt = 8
gpmEventMove = 1
gpmEventDrag = 2
gpmEventDown = 4
gpmEventUp = 8
gpmEventSingle = 16
gpmEventDouble = 32
gpmEventTriple = 64
gpmEventMFlag = 128
gpmEventHard = 256
gpmEventEnter = 512
gpmEventLeave = 1024
gpmMaxInt = 1<<31 - 1
)
var (
gpmCtlPath = "/dev/gpmctl"
gpmFDLink = func(fd string) (string, error) {
return os.Readlink(filepath.Join("/proc/self/fd", fd))
}
)
type gpmConsoleMouse struct {
sock net.Conn
eventQ chan<- Event
mu sync.Mutex
flags MouseFlags
pressed ButtonMask
w int
h int
stopOnce sync.Once
stopQ chan struct{}
done chan struct{}
}
func init() {
newConsoleMouse = newGPMConsoleMouse
}
func newGPMConsoleMouse(eventQ chan<- Event) (consoleMouse, error) {
tty, err := gpmConsoleTTY()
if err != nil {
return nil, err
}
sock, err := net.Dial("unix", gpmCtlPath)
if err != nil {
return nil, fmt.Errorf("failed opening gpmctl: %w", err)
}
cm := &gpmConsoleMouse{sock: sock, eventQ: eventQ}
if _, err = sock.Write(gpmConnectPacket(tty)); err != nil {
_ = sock.Close()
return nil, fmt.Errorf("failed sending gpmctl connect: %w", err)
}
return cm, nil
}
func gpmConsoleTTY() (uint32, error) {
var last error
for _, fd := range []string{"2", "0", "1"} {
link, err := gpmFDLink(fd)
if err != nil {
last = err
continue
}
if tty, ok := parseGPMConsoleTTY(link); ok {
return tty, nil
}
last = fmt.Errorf("not a virtual console: %s", link)
}
if last == nil {
last = errNoConsoleMouse
}
return 0, fmt.Errorf("cannot determine Linux virtual console: %w", last)
}
func parseGPMConsoleTTY(link string) (uint32, bool) {
link = filepath.Clean(link)
base := filepath.Base(link)
dir := filepath.Dir(link)
var s string
switch {
case dir == "/dev" && strings.HasPrefix(base, "tty"):
s = strings.TrimPrefix(base, "tty")
case filepath.Base(dir) == "vc" && filepath.Dir(dir) == "/dev":
s = base
default:
return 0, false
}
tty, err := strconv.ParseUint(s, 10, 32)
if err != nil || tty == 0 || tty > gpmMaxInt {
return 0, false
}
return uint32(tty), true
}
func gpmConnectPacket(tty uint32) []byte {
b := make([]byte, gpmConnectSize)
ne := binary.NativeEndian
ne.PutUint16(b[0:], 0xffff) // eventMask
ne.PutUint16(b[2:], 0xffff) // defaultMask
ne.PutUint16(b[4:], 0) // minMod
ne.PutUint16(b[6:], 0xffff) // maxMod
ne.PutUint32(b[8:], gpmProcessID()) // pid
ne.PutUint32(b[12:], tty) // vc
return b
}
func gpmProcessID() uint32 {
pid := os.Getpid()
if pid < 0 || pid > gpmMaxInt {
return 0
}
return uint32(pid)
}
func (cm *gpmConsoleMouse) Start(flags MouseFlags) error {
if flags&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) == 0 {
return errNoConsoleMouse
}
cm.mu.Lock()
cm.flags = flags
cm.stopQ = make(chan struct{})
cm.done = make(chan struct{})
cm.mu.Unlock()
go cm.loop()
return nil
}
func (cm *gpmConsoleMouse) Stop() {
cm.mu.Lock()
stopQ := cm.stopQ
done := cm.done
cm.mu.Unlock()
cm.stopOnce.Do(func() {
if stopQ != nil {
close(stopQ)
}
_ = cm.sock.Close()
})
if done != nil {
<-done
}
}
func (cm *gpmConsoleMouse) SetSize(w, h int) {
cm.mu.Lock()
cm.w = w
cm.h = h
cm.mu.Unlock()
}
func (cm *gpmConsoleMouse) loop() {
defer func() {
cm.mu.Lock()
done := cm.done
cm.mu.Unlock()
if done != nil {
close(done)
}
}()
cm.mu.Lock()
stopQ := cm.stopQ
cm.mu.Unlock()
var b [gpmEventSize]byte
for {
if _, err := io.ReadFull(cm.sock, b[:]); err != nil {
return
}
if ev, ok := cm.eventFromPacket(b[:]); ok {
select {
case cm.eventQ <- ev:
case <-stopQ:
return
}
}
}
}
func (cm *gpmConsoleMouse) eventFromPacket(b []byte) (*EventMouse, bool) {
if len(b) < gpmEventSize {
return nil, false
}
ne := binary.NativeEndian
buttons := b[0]
mods := b[1]
x := int(int16(ne.Uint16(b[8:]))) - 1
y := int(int16(ne.Uint16(b[10:]))) - 1
typ := int(ne.Uint32(b[12:]))
wdx := int(int16(ne.Uint16(b[24:])))
wdy := int(int16(ne.Uint16(b[26:])))
cm.mu.Lock()
defer cm.mu.Unlock()
button := gpmButtonMask(buttons, wdx, wdy)
bare := typ & (gpmEventMove | gpmEventDrag | gpmEventDown | gpmEventUp)
flags := cm.flags
if button&(WheelUp|WheelDown|WheelLeft|WheelRight) != 0 {
if flags&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) == 0 {
return nil, false
}
return NewEventMouse(cm.clipX(x), cm.clipY(y), button, gpmModMask(mods)), true
}
switch bare {
case gpmEventMove:
if flags&MouseMotionEvents == 0 {
return nil, false
}
cm.pressed = ButtonNone
case gpmEventDrag:
if flags&(MouseDragEvents|MouseMotionEvents) == 0 {
return nil, false
}
if button != ButtonNone {
cm.pressed |= button
}
case gpmEventDown:
if flags&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) == 0 {
return nil, false
}
cm.pressed |= button
case gpmEventUp:
if flags&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) == 0 {
return nil, false
}
cm.pressed &^= button
default:
return nil, false
}
return NewEventMouse(cm.clipX(x), cm.clipY(y), cm.pressed, gpmModMask(mods)), true
}
func (cm *gpmConsoleMouse) clipX(x int) int {
if cm.w <= 0 {
return max(x, 0)
}
return max(min(x, cm.w-1), 0)
}
func (cm *gpmConsoleMouse) clipY(y int) int {
if cm.h <= 0 {
return max(y, 0)
}
return max(min(y, cm.h-1), 0)
}
func gpmButtonMask(buttons byte, wdx, wdy int) ButtonMask {
var mask ButtonMask
if buttons&gpmButtonLeft != 0 {
mask |= Button1
}
if buttons&gpmButtonRight != 0 {
mask |= Button2
}
if buttons&gpmButtonMiddle != 0 {
mask |= Button3
}
if buttons&gpmButtonFourth != 0 {
mask |= Button4
}
if buttons&gpmButtonUp != 0 || wdy > 0 {
mask |= WheelUp
}
if buttons&gpmButtonDown != 0 || wdy < 0 {
mask |= WheelDown
}
if wdx < 0 {
mask |= WheelLeft
}
if wdx > 0 {
mask |= WheelRight
}
return mask
}
func gpmModMask(mods byte) ModMask {
var mask ModMask
if mods&gpmModShift != 0 {
mask |= ModShift
}
if mods&gpmModAltGr != 0 {
mask |= ModAlt
}
if mods&gpmModCtrl != 0 {
mask |= ModCtrl
}
if mods&gpmModAlt != 0 {
mask |= ModAlt
}
return mask
}
+72 -33
View File
@@ -30,6 +30,7 @@ package tcell
import (
"encoding/base64"
"os"
"runtime"
"strconv"
"strings"
"sync"
@@ -64,10 +65,16 @@ const (
const defaultControlStringLimit = 64 * 1024
const (
// loneEscapeTimeout keeps bare Escape responsive. A lone ESC byte is
// defaultEscapeTimeout 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
defaultEscapeTimeout = 50 * time.Millisecond
// windowsEscapeTimeout allows for the delayed VT input batches observed
// from ConPTY. It is deliberately shorter than the previous 200 ms
// default, while leaving enough headroom for the 80-90 ms gaps observed
// between an ESC byte and the rest of a mouse report.
windowsEscapeTimeout = 125 * time.Millisecond
// escapeSequenceTimeout bounds incomplete escape sequences. Once a
// sequence introducer has arrived, it is no longer ambiguous with a lone
@@ -75,38 +82,51 @@ const (
escapeSequenceTimeout = time.Second
)
func escapeTimeoutForPlatform(goos string) time.Duration {
if goos == "windows" {
return windowsEscapeTimeout
}
return defaultEscapeTimeout
}
func platformEscapeTimeout() time.Duration {
return escapeTimeoutForPlatform(runtime.GOOS)
}
func newInputParser(eq chan<- Event) *inputParser {
return &inputParser{
evch: eq,
buf: make([]rune, 0, 128),
legacy: true,
controlStringMax: defaultControlStringLimit,
escapeTimeout: platformEscapeTimeout(),
}
}
type inputParser struct {
buf []rune // bytes to process (ingest data)
utfBuf []byte // accrued UTF8 bytes
strBuf []byte // accrued string data (for ST, OSC, etc.)
csiParams []byte // accrued parameter bytes for CSI (and SS3)
csiInterm []byte // accrued intermediate bytes for CSI
escChar byte // last byte for escape
escaped bool // true if next key should be modified by ESC
btnsDown ButtonMask // mouse buttons down (excludes wheel buttons)
state inputState // tracks processor state
strState inputState // saved str state (needed for ST)
l sync.Mutex // protects local state
evch chan<- Event // where events are routed
rows int // used for clipping mouse coordinates
cols int // used for clipping mouse coordinates
pixelMouse bool // mouse reports in pixels (CSI ?1016h); skip cell clipping
keyTime time.Time // time of last key press / byte ingested
nested *inputParser // for buggy win32-input-mode implementations
surrogate rune // high surrogate pair seen (for Win32 input mode)
advanced bool // use advanced key reporting semantics
legacy bool // keyboard protocol has ambiguous ESC prefixes
controlStringMax int // maximum inbound OSC/XDA payload size; 0 means unlimited
discardString bool // drop the rest of an over-limit OSC/XDA sequence
buf []rune // bytes to process (ingest data)
utfBuf []byte // accrued UTF8 bytes
strBuf []byte // accrued string data (for ST, OSC, etc.)
csiParams []byte // accrued parameter bytes for CSI (and SS3)
csiInterm []byte // accrued intermediate bytes for CSI
escChar byte // last byte for escape
escaped bool // true if next key should be modified by ESC
btnsDown ButtonMask // mouse buttons down (excludes wheel buttons)
state inputState // tracks processor state
strState inputState // saved str state (needed for ST)
l sync.Mutex // protects local state
evch chan<- Event // where events are routed
rows int // used for clipping mouse coordinates
cols int // used for clipping mouse coordinates
pixelMouse bool // mouse reports in pixels (CSI ?1016h); skip cell clipping
keyTime time.Time // time of last key press / byte ingested
escapeTimeout time.Duration // time to wait for a possible ESC sequence
nested *inputParser // for buggy win32-input-mode implementations
surrogate rune // high surrogate pair seen (for Win32 input mode)
advanced bool // use advanced key reporting semantics
legacy bool // keyboard protocol has ambiguous ESC prefixes
controlStringMax int // maximum inbound OSC/XDA payload size; 0 means unlimited
discardString bool // drop the rest of an over-limit OSC/XDA sequence
}
func keyFromInt(n int) (Key, bool) {
@@ -139,7 +159,7 @@ func (ip *inputParser) waitDuration() time.Duration {
return 0
}
if ip.state == istEsc {
return loneEscapeTimeout
return ip.escapeTimeout
}
return escapeSequenceTimeout
}
@@ -175,19 +195,20 @@ func (ip *inputParser) SetPixelMouse(on bool) {
}
}
func (ip *inputParser) SetSize(w, h int) {
func (ip *inputParser) SetSize(ws WindowSize) {
if ip.nested != nil {
ip.nested.SetSize(w, h)
ip.nested.SetSize(ws)
return
}
go func() {
ip.l.Lock()
ip.rows = h
ip.cols = w
ip.post(NewEventResize(w, h))
ip.rows = ws.Height
ip.cols = ws.Width
ip.post(newEventResize(ws))
ip.l.Unlock()
}()
}
func (ip *inputParser) post(ev Event) {
if ip.escaped {
ip.escaped = false
@@ -821,6 +842,19 @@ func (ip *inputParser) scan() {
}
}
func (ip *inputParser) SetEscapeTimeout(timeout time.Duration) {
if timeout <= 0 {
timeout = platformEscapeTimeout()
}
ip.l.Lock()
ip.escapeTimeout = timeout
nested := ip.nested
ip.l.Unlock()
if nested != nil {
nested.SetEscapeTimeout(timeout)
}
}
func (ip *inputParser) appendStringBytes(bs ...byte) {
if ip.controlStringMax > 0 && len(ip.strBuf)+len(bs) > ip.controlStringMax {
ip.strBuf = nil
@@ -1134,6 +1168,7 @@ func (ip *inputParser) handleWinKey(P []int) {
legacy: ip.legacy,
pixelMouse: ip.pixelMouse,
controlStringMax: ip.controlStringMax,
escapeTimeout: ip.escapeTimeout,
}
}
ip.nested.ScanUTF8([]byte{b})
@@ -1462,14 +1497,18 @@ func (ip *inputParser) handleCsi(mode rune, params []byte, intermediate []byte)
h := P[1]
w := P[2]
if h != ip.rows || w != ip.cols {
ip.SetSize(w, h)
ip.SetSize(WindowSize{Width: w, Height: h})
}
return
}
case 48:
if len(P) > 2 {
// window resize report
ip.post(NewEventResize(P[2], P[1]))
// window resize report, with the size in pixels if given
ws := WindowSize{Width: P[2], Height: P[1]}
if len(P) > 4 {
ws.PixelWidth, ws.PixelHeight = P[4], P[3]
}
ip.post(newEventResize(ws))
return
}
}
+4 -4
View File
@@ -25,10 +25,10 @@ type EventResize struct {
// NewEventResize creates an EventResize with the new updated window size,
// which is given in character cells.
func NewEventResize(width, height int) *EventResize {
ws := WindowSize{
Width: width,
Height: height,
}
return newEventResize(WindowSize{Width: width, Height: height})
}
func newEventResize(ws WindowSize) *EventResize {
ev := &EventResize{ws: ws}
ev.SetEventNow()
return ev
+36 -1
View File
@@ -16,6 +16,7 @@ package tcell
import (
"sync"
"unicode/utf8"
"github.com/gdamore/tcell/v3/color"
)
@@ -392,6 +393,8 @@ type screenImpl interface {
type baseScreen struct {
screenImpl
// Content strings are shared by cells and protected by the screen lock.
contentCache map[string]string
}
func (b *baseScreen) Put(x int, y int, str string, style Style) (remain string, width int) {
@@ -442,7 +445,39 @@ func (b *baseScreen) FillArea(x, y, width, height int, r rune, style Style) {
}
func (b *baseScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
b.Put(x, y, string(append([]rune{mainc}, combc...)), style)
cells := b.GetCells()
b.Lock()
defer b.Unlock()
if x < 0 || y < 0 || x >= cells.w || y >= cells.h {
return
}
var scratch [64]byte
buf := utf8.AppendRune(scratch[:0], mainc)
for _, r := range combc {
buf = utf8.AppendRune(buf, r)
}
var str string
if curr := cells.cells[y*cells.w+x].currStr; curr == string(buf) {
// Comparing a temporary byte-to-string conversion does not allocate.
// Reuse the cell's string without looking it up in the cache.
str = curr
} else if len(buf) == 1 {
// Go shares static storage for one-byte string conversions, so ASCII
// needs neither a cache entry nor an explicit lookup table.
str = string(buf)
} else if cached, ok := b.contentCache[string(buf)]; ok {
// A temporary byte-to-string map lookup also avoids allocation.
str = cached
} else {
str = string(buf)
if b.contentCache == nil {
b.contentCache = make(map[string]string)
}
b.contentCache[str] = str
}
cells.Put(x, y, str, style)
}
func (b *baseScreen) Get(x, y int) (string, Style, int) {
+70 -8
View File
@@ -119,6 +119,20 @@ func (o OptNegotiation) apply(t *tScreen) {
t.negotiate = bool(o)
}
// OptEscapeTimeout sets how long the input parser waits for bytes that may
// follow an Escape. The default is 50 ms, or 125 ms on Windows. Increasing
// this can accommodate terminals that split escape sequences across delayed
// reads, at the cost of delaying a standalone Escape by the same amount. A
// positive TCELL_ESCDELAY environment variable, expressed in milliseconds,
// overrides this setting. Values less than or equal to zero use the default.
type OptEscapeTimeout time.Duration
func (o OptEscapeTimeout) apply(t *tScreen) {
if o > 0 {
t.escapeTimeout = time.Duration(o)
}
}
// OptControlStringLimit sets the maximum inbound control-string payload size
// accepted from the terminal before the parser drops the sequence. This limits
// OSC and XDA strings, including OSC 52 clipboard strings; OSC 52 is the
@@ -196,6 +210,7 @@ func NewTerminfoScreenFromTty(tty Tty, opts ...TerminfoScreenOption) (Screen, er
altScreen: true,
negotiate: true,
controlStringLimit: defaultControlStringLimit,
escapeTimeout: platformEscapeTimeout(),
}
t.prepareCursorStyles()
@@ -261,6 +276,7 @@ type tScreen struct {
wg sync.WaitGroup
eventWg sync.WaitGroup
mouseFlags MouseFlags
consoleMouse consoleMouse
pasteEnabled bool
focusEnabled bool
setTitle string
@@ -285,9 +301,11 @@ type tScreen struct {
mouseDisabled bool
advancedKeys bool
controlStringLimit int
escapeTimeout time.Duration
input *inputParser
compat struct {
mouseUnsupported bool
mousePixelUnsupported bool
focusUnsupported bool
clipboardReadUnsupported bool
}
@@ -341,6 +359,10 @@ func (t *tScreen) applyKeyboardProtocolOverride() {
}
func (t *tScreen) applyEnvironmentOverrides() {
if delay, err := strconv.Atoi(os.Getenv("TCELL_ESCDELAY")); err == nil && delay > 0 {
t.escapeTimeout = time.Duration(delay) * time.Millisecond
}
switch os.Getenv("TCELL_KEYBOARD_PROTOCOL") {
case "auto":
t.forceKbd = false
@@ -459,6 +481,7 @@ func (t *tScreen) Init() error {
t.input = newInputParser(t.filterEvents())
t.input.advanced = t.advancedKeys
t.input.controlStringMax = t.controlStringLimit
t.input.SetEscapeTimeout(t.escapeTimeout)
t.Lock()
t.cx = -1
@@ -1097,6 +1120,11 @@ func (t *tScreen) EnableMouse(flags ...MouseFlags) {
}
func (t *tScreen) enableMouse(f MouseFlags) {
t.stopConsoleMouse()
if f != 0 && !t.mouseDisabled && t.startConsoleMouse(f) {
return
}
// Rather than using terminfo to find mouse escape sequences, we rely on the fact that
// pretty much *every* terminal that supports mouse tracking follows the
// XTerm standards (the modern ones). It is expected that all terminals understand
@@ -1124,9 +1152,11 @@ func (t *tScreen) enableMouse(f MouseFlags) {
t.Print(vt.PmMouseDrag.Disable())
t.Print(vt.PmMouseMotion.Disable())
t.Print(vt.PmMouseSgr.Disable())
t.Print(vt.PmMouseSgrPixel.Disable())
if !t.compat.mousePixelUnsupported {
t.Print(vt.PmMouseSgrPixel.Disable())
}
pixel := f&MousePixelEvents != 0
pixel := f&MousePixelEvents != 0 && !t.compat.mousePixelUnsupported
t.input.SetPixelMouse(pixel)
if f&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) != 0 {
@@ -1147,6 +1177,31 @@ func (t *tScreen) enableMouse(f MouseFlags) {
}
}
func (t *tScreen) startConsoleMouse(f MouseFlags) bool {
if t.eventQ == nil || f&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) == 0 {
return false
}
cm, err := newConsoleMouse(t.eventQ)
if err != nil {
return false
}
cm.SetSize(t.w, t.h)
if err = cm.Start(f); err != nil {
cm.Stop()
return false
}
t.consoleMouse = cm
t.input.SetPixelMouse(false)
return true
}
func (t *tScreen) stopConsoleMouse() {
if t.consoleMouse != nil {
t.consoleMouse.Stop()
t.consoleMouse = nil
}
}
func (t *tScreen) DisableMouse() {
t.Lock()
if t.fini {
@@ -1250,7 +1305,10 @@ func (t *tScreen) resize() {
t.cells.Invalidate()
t.h = ws.Height
t.w = ws.Width
t.input.SetSize(ws.Width, ws.Height)
t.input.SetSize(ws)
if t.consoleMouse != nil {
t.consoleMouse.SetSize(ws.Width, ws.Height)
}
}
func (t *tScreen) Colors() int {
@@ -1494,12 +1552,16 @@ func isSTTerminal(term string) bool {
func (t *tScreen) applyKnownTerminalProfile(goos, term, termProgram string) bool {
if isSTTerminal(term) {
// st implements a small subset of xterm extensions. In particular,
// it has neither an advanced keyboard protocol nor SGR mouse or focus
// reporting. It also reports unsupported CSI and OSC sequences to
// stderr, so avoid probing or using extensions it does not implement.
// it has neither an advanced keyboard protocol nor pixel-precision
// (SGR-pixel) mouse reporting. It does, however, implement ordinary
// SGR mouse reporting (1006) and focus reporting (1004), so declare
// those as supported rather than disabling them. It also reports
// unsupported CSI and OSC sequences to stderr, so avoid probing or
// using extensions it does not implement.
t.legacy = true
t.compat.mouseUnsupported = true
t.compat.focusUnsupported = true
t.haveMouse = true
t.haveMouseSgr = true
t.compat.mousePixelUnsupported = true
t.enterUrl = ""
t.exitUrl = ""
t.setWinSize = ""
+8 -30
View File
@@ -24,7 +24,6 @@ import (
"strconv"
"strings"
"sync"
"unicode"
"unicode/utf8"
"github.com/clipperhouse/uax29/v2/graphemes"
@@ -1970,16 +1969,9 @@ func (em *emulator) putRune(r rune) {
if em.graphemeIter.Next() && len(em.graphemeIter.Value()) == len(buf) {
// we are adding to a cluster
cluster := em.graphemeIter.Value()
width := em.cells[lastIdx].W
if w := textWidthOptions.Rune(r); w > width {
width = w
}
if isRegionalIndicator(r) && width < 2 {
width = 2
}
if r == '\uFE0F' && width < 2 {
width = 2
}
// not the same as the widest rune in the cluster: a flag
// is two width-1 runes but occupies two columns.
width := textWidthOptions.Bytes(cluster)
em.cells[lastIdx].C = em.clusterString(cluster)
em.cells[lastIdx].W = width
col := Col(lastIdx) % dim.X
@@ -2052,29 +2044,15 @@ func (em *emulator) clusterString(cluster []byte) string {
return em.clusterStrings.stringFor(cluster)
}
// shouldCheckGrapheme reports whether r might extend a cluster ending in the
// ASCII byte prev. It may say yes needlessly, but never no wrongly.
func shouldCheckGrapheme(prev byte, r rune) bool {
if r < utf8.RuneSelf {
// an ASCII pair never clusters, except CR LF
return prev == '\r' && r == '\n'
}
if unicode.Is(unicode.M, r) {
return true
}
if r == '\u200d' {
return true
}
if r >= 0xFE00 && r <= 0xFE0F {
return true
}
if r >= 0xE0100 && r <= 0xE01EF {
return true
}
return false
}
func isRegionalIndicator(r rune) bool {
return r >= 0x1F1E6 && r <= 0x1F1FF
b := uint32(r) >> 6
return graphemeJoinerBlocks[b>>6]&(1<<(b&63)) != 0
}
// eraseCell erases a single cell at the given offset.
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2026 The TCell 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 vt
// graphemeJoinerBlocks has one bit per 64 runes, set if any rune in that
// block can extend a grapheme cluster. A clear bit means the segmenter
// cannot join anything in the block, so putRune may skip it. Only 2619 of
// the 1113984 runes above U+007F can join, so nearly every bit is clear.
//
// The table is derived from the segmenter, not from Go's unicode tables,
// and TestGraphemeJoinerBlocks re-derives it to catch drift.
var graphemeJoinerBlocks = [272]uint64{
0: 0xfffffffffbc43000,
1: 0x0089ff15f0002007,
2: 0x00a8000000000009,
3: 0x0000000000000005,
10: 0x00008ffd0e000000,
15: 0x4100100000000000,
16: 0x6c30090000002880,
17: 0x387527b117cffbff,
19: 0x0000000000020000,
22: 0xe000180000000010,
27: 0x0004000000000000,
28: 0x3000000000000000,
29: 0x0000070000000260,
30: 0x0000002808880c15,
31: 0x0000000000008000,
224: 0x00000000000000f3,
}
+1 -1
View File
@@ -1678,7 +1678,7 @@ func (f *File) AddTool(path string) error {
return nil
}
// RemoveTool removes a tool directive with the given path.
// DropTool removes a tool directive with the given path.
// It does nothing if no such tool directive exists.
func (f *File) DropTool(path string) error {
for _, t := range f.Tool {
+1 -1
View File
@@ -84,7 +84,7 @@ func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) {
}
// Cleanup cleans up the file f after any edit operations.
// To avoid quadratic behavior, modifications like [WorkFile.DropRequire]
// To avoid quadratic behavior, modifications like [WorkFile.DropUse]
// clear the entry but do not remove it from the slice.
// Cleanup cleans out all the cleared entries.
func (f *WorkFile) Cleanup() {
+17 -7
View File
@@ -106,6 +106,9 @@ type Terminal struct {
// a read. It aliases into inBuf.
remainder []byte
inBuf [256]byte
// readErr is an error returned by a read that also returned data. It is
// reported after all of that data has been processed.
readErr error
// History records and retrieves lines of input read by [ReadLine] which
// a user can retrieve and navigate using the up and down arrow keys.
@@ -788,7 +791,11 @@ func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
return
}
// ReadLine returns a line of input from the terminal.
// ReadLine returns a line of input from the terminal, excluding the
// trailing newline. It may return partial data along with an error.
// An [io.EOF] error indicates the end of the stream. For other errors,
// such as [ErrPasteIndicator] in bracketed paste mode, a subsequent
// call may return more data.
func (t *Terminal) ReadLine() (line string, err error) {
t.lock.Lock()
defer t.lock.Unlock()
@@ -863,21 +870,24 @@ func (t *Terminal) readLine() (line string, err error) {
}
return
}
if t.readErr != nil {
err = t.readErr
t.readErr = nil
return
}
// t.remainder is a slice at the beginning of t.inBuf
// containing a partial key sequence
readBuf := t.inBuf[len(t.remainder):]
var n int
t.lock.Unlock()
n, err = t.c.Read(readBuf)
n, readErr := t.c.Read(readBuf)
t.lock.Lock()
if err != nil {
return
}
t.remainder = t.inBuf[:n+len(t.remainder)]
if readErr != nil {
t.readErr = readErr
}
}
}
+27 -15
View File
@@ -449,6 +449,16 @@ func (rb *reorderBuffer) combineHangul(s, i, k int) {
// ACxx plus 11Ax to LVT
rb.assignRune(s, l+v-jamoTBase)
default:
// Not a Hangul composition. The segment may still
// contain regular canonical compositions, such as
// combining marks following a Hangul syllable, so
// fall back to the composition table.
if b[i].combinesBackward() {
if combined := combine(l, v); combined != 0 {
rb.assignRune(s, combined)
continue
}
}
b[k] = b[i]
k++
}
@@ -484,25 +494,27 @@ func (rb *reorderBuffer) compose() {
return
}
ii := b[i]
// Track the last starter unconditionally: b[i] must be blocked by
// any starter between it and s, even one that b[i] itself cannot
// combine with, and even if the runes in between never enter the
// combinesBackward branch below.
cccB := b[k-1].ccc
cccC := ii.ccc
blocked := false // b[i] blocked by starter or greater or equal CCC?
if cccB == 0 {
s = k - 1
} else {
blocked = s != k-1 && cccB >= cccC
}
// We can only use combineForward as a filter if we later
// get the info for the combined character. This is more
// expensive than using the filter. Using combinesBackward()
// is safe.
if ii.combinesBackward() {
cccB := b[k-1].ccc
cccC := ii.ccc
blocked := false // b[i] blocked by starter or greater or equal CCC?
if cccB == 0 {
s = k - 1
} else {
blocked = s != k-1 && cccB >= cccC
}
if !blocked {
combined := combine(rb.runeAt(s), rb.runeAt(i))
if combined != 0 {
rb.assignRune(s, combined)
continue
}
if ii.combinesBackward() && !blocked {
combined := combine(rb.runeAt(s), rb.runeAt(i))
if combined != 0 {
rb.assignRune(s, combined)
continue
}
}
b[k] = b[i]
+12 -12
View File
@@ -191,30 +191,30 @@ func (p Properties) TrailCCC() uint8 {
return ccc[p.tccc]
}
// Recomposition
// Each entry of recompMapPacked holds the two runes of the key and the
// composed rune of the value packed into a big-endian uint64 as three
// 21-bit fields, which is wide enough for any rune.
// Note that the recomposition map for NFC and NFKC are identical.
const recompShift = 21
func buildRecompMap() {
recompMap = make(map[uint32]rune, len(recompMapPacked)/8)
recompMap = make(map[uint64]rune, len(recompMapPacked)/8)
var buf [8]byte
for i := 0; i < len(recompMapPacked); i += 8 {
copy(buf[:], recompMapPacked[i:i+8])
key := binary.BigEndian.Uint32(buf[:4])
val := binary.BigEndian.Uint32(buf[4:])
recompMap[key] = rune(val)
e := binary.BigEndian.Uint64(buf[:])
recompMap[e>>recompShift] = rune(e & (1<<recompShift - 1))
}
}
// Recomposition
// We use 32-bit keys instead of 64-bit for the two codepoint keys.
// This clips off the bits of three entries, but we know this will not
// result in a collision. In the unlikely event that changes to
// UnicodeData.txt introduce collisions, the compiler will catch it.
// Note that the recomposition map for NFC and NFKC are identical.
// combine returns the combined rune or 0 if it doesn't exist.
//
// The caller is responsible for calling
// recompMapOnce.Do(buildRecompMap) sometime before this is called.
func combine(a, b rune) rune {
key := uint32(uint16(a))<<16 + uint32(uint16(b))
key := uint64(a)<<recompShift | uint64(b)
if recompMap == nil {
panic("caller error") // see func comment
}
+4 -1
View File
@@ -505,7 +505,10 @@ func decomposeSegment(rb *reorderBuffer, sp int, atEOF bool) int {
// Force one character to be consumed.
info := rb.f.info(rb.src, sp)
if info.isInvalid() {
return 0
// Consume the invalid character.
// Perhaps this should return iShortSrc instead?
// May not matter, this path does not appear to be reachable in practice.
return sp + 1
}
if s := rb.ss.next(info); s == ssStarter {
// TODO: this could be removed if we don't support merging.
+942 -942
View File
File diff suppressed because it is too large. Load diff
+962 -962
View File
File diff suppressed because it is too large. Load diff
+3 -2
View File
@@ -33,7 +33,7 @@ func (f Form) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
return nDst + n, nSrc + n, err
}
if err == nil && n < len(src) && !atEOF {
if err == nil && n < len(src) {
err = transform.ErrShortSrc
}
return n, n, err
@@ -67,6 +67,7 @@ func (f Form) transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
nSrc = end
// Next quickSpan.
var err error
end = rb.nsrc
eof := atEOF
if n := nSrc + len(dst) - nDst; n < end {
@@ -79,7 +80,7 @@ func (f Form) transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
nSrc += n
nDst += n
if ok {
if err == nil && n < rb.nsrc && !atEOF {
if err == nil && nSrc < rb.nsrc {
err = transform.ErrShortSrc
}
return nDst, nSrc, err
+9 -9
View File
@@ -45,8 +45,8 @@ 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.5.0
## explicit; go 1.25.0
# github.com/gdamore/tcell/v3 v3.6.0
## explicit; go 1.26.0
github.com/gdamore/tcell/v3
github.com/gdamore/tcell/v3/color
github.com/gdamore/tcell/v3/internal/widthutil
@@ -167,8 +167,8 @@ go.yaml.in/yaml/v3
## explicit; go 1.20
golang.org/x/exp/constraints
golang.org/x/exp/slices
# golang.org/x/mod v0.38.0
## explicit; go 1.25.0
# golang.org/x/mod v0.41.0
## explicit; go 1.26.0
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile
golang.org/x/mod/module
@@ -182,11 +182,11 @@ golang.org/x/sync/semaphore
golang.org/x/sys/plan9
golang.org/x/sys/unix
golang.org/x/sys/windows
# golang.org/x/term v0.45.0
## explicit; go 1.25.0
# golang.org/x/term v0.46.0
## explicit; go 1.26.0
golang.org/x/term
# golang.org/x/text v0.41.0
## explicit; go 1.25.0
# golang.org/x/text v0.42.0
## explicit; go 1.26.0
golang.org/x/text/cases
golang.org/x/text/encoding
golang.org/x/text/encoding/internal/identifier
@@ -198,7 +198,7 @@ golang.org/x/text/language
golang.org/x/text/runes
golang.org/x/text/transform
golang.org/x/text/unicode/norm
# golang.org/x/tools v0.48.0
# golang.org/x/tools v0.49.0
## explicit; go 1.25.0
golang.org/x/tools/go/ast/astutil
# gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c