mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-28 10:45:41 +08:00
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:
1 parent
c0fa952a94
commit
948fbcc1d6
22 files changed
+2643
-2060
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+962
-962
File diff suppressed because it is too large.
Load diff
+3
-2
@@ -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
|
||||
|
||||
Vendored
+9
-9
@@ -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
|
||||
|
||||
Reference in new issue
Block a user