mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-11-06 13:00:59 -04:00
Bump github.com/gdamore/tcell/v3 from 3.5.0 to 3.6.0 (#6126)
Bumps [github.com/gdamore/tcell/v3](https://github.com/gdamore/tcell) from 3.5.0 to 3.6.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/gdamore/tcell/releases">github.com/gdamore/tcell/v3's releases</a>.</em></p> <blockquote> <h2>Version 3.6.0 Feature Release</h2> <p>The main headline here is that the mouse is supported on the Linux console with GPM. There are performance improvements and some bug fixes, plus improved reporting of pixel sizes on window resize events as well.</p> <h2>What's Changed</h2> <ul> <li>fix(vt): let the segmenter decide clustering, not Go's unicode tables by <a href="https://github.com/dylanpulver"><code>@dylanpulver</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1170">gdamore/tcell#1170</a></li> <li>chore(deps): bump golang.org/x/term from 0.45.0 to 0.46.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/gdamore/tcell/pull/1173">gdamore/tcell#1173</a></li> <li>chore(deps): bump golang.org/x/text from 0.41.0 to 0.42.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/gdamore/tcell/pull/1174">gdamore/tcell#1174</a></li> <li>fix: invalidate blank cells after resizing CellBuffer by <a href="https://github.com/jakezwang"><code>@jakezwang</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1176">gdamore/tcell#1176</a></li> <li>fix(st): re-enable SGR mouse (1006) and focus (1004) reporting by <a href="https://github.com/akashchamp"><code>@akashchamp</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1179">gdamore/tcell#1179</a></li> <li>perf(cell): fill areas without an allocation per cell by <a href="https://github.com/ayn2op"><code>@ayn2op</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1184">gdamore/tcell#1184</a></li> <li>fix(resize): report the pixel size in resize events by <a href="https://github.com/ayn2op"><code>@ayn2op</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1186">gdamore/tcell#1186</a></li> <li>fix(input): extend bare Escape timeout on Windows by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1188">gdamore/tcell#1188</a></li> <li>feat(gpm): support Linux console mouse by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1190">gdamore/tcell#1190</a></li> <li>perf: reuse SetContent strings for Unicode glyphs by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1191">gdamore/tcell#1191</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/dylanpulver"><code>@dylanpulver</code></a> made their first contribution in <a href="https://redirect.github.com/gdamore/tcell/pull/1170">gdamore/tcell#1170</a></li> <li><a href="https://github.com/jakezwang"><code>@jakezwang</code></a> made their first contribution in <a href="https://redirect.github.com/gdamore/tcell/pull/1176">gdamore/tcell#1176</a></li> <li><a href="https://github.com/akashchamp"><code>@akashchamp</code></a> made their first contribution in <a href="https://redirect.github.com/gdamore/tcell/pull/1179">gdamore/tcell#1179</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/gdamore/tcell/compare/v3.5.0...v3.6.0">https://github.com/gdamore/tcell/compare/v3.5.0...v3.6.0</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/gdamore/tcell/commit/4bed18057fce88934c9b9a576b5f83632196de11"><code>4bed180</code></a> perf: reuse SetContent strings for Unicode glyphs (<a href="https://redirect.github.com/gdamore/tcell/issues/1191">#1191</a>)</li> <li><a href="https://github.com/gdamore/tcell/commit/e0700df7ab5dbc4484ece1a32812f6e2602afd5b"><code>e0700df</code></a> feat(gpm): support Linux console mouse (fixes <a href="https://redirect.github.com/gdamore/tcell/issues/297">#297</a>)</li> <li><a href="https://github.com/gdamore/tcell/commit/f4c25623e16610b714e698c729535d9815656536"><code>f4c2562</code></a> fix(input): tune escape delay on Windows (<a href="https://redirect.github.com/gdamore/tcell/issues/1188">#1188</a>)</li> <li><a href="https://github.com/gdamore/tcell/commit/c2d660f0fafb796cc261dcdda8451da67a209b51"><code>c2d660f</code></a> fix(resize): report the pixel size in resize events</li> <li><a href="https://github.com/gdamore/tcell/commit/5a6c8bf183d1fc9605da90128c4e603d41dc44a8"><code>5a6c8bf</code></a> perf(cell): fill areas without an allocation per cell</li> <li><a href="https://github.com/gdamore/tcell/commit/10a2e726de2401764f6f2b5fa696dfa0a7f2357e"><code>10a2e72</code></a> fix(st): Re-enable SGR mouse (1006) and focus (1004) reporting (fixes#1178) (...</li> <li><a href="https://github.com/gdamore/tcell/commit/f6b0fa8ffcf8a42601efdd8eea9dd48b0ce8af63"><code>f6b0fa8</code></a> fix: invalidate blank cells after resizing CellBuffer (<a href="https://redirect.github.com/gdamore/tcell/issues/1176">#1176</a>)</li> <li><a href="https://github.com/gdamore/tcell/commit/91f6d4df3f48dcff1a779a64b9d9d7518034d60e"><code>91f6d4d</code></a> chore(deps): bump golang.org/x/text from 0.41.0 to 0.42.0 (<a href="https://redirect.github.com/gdamore/tcell/issues/1174">#1174</a>)</li> <li><a href="https://github.com/gdamore/tcell/commit/4f108a391b6b8f75a7190b88d27295fa73e343ad"><code>4f108a3</code></a> chore(deps): bump golang.org/x/term from 0.45.0 to 0.46.0 (<a href="https://redirect.github.com/gdamore/tcell/issues/1173">#1173</a>)</li> <li><a href="https://github.com/gdamore/tcell/commit/8c45e3b4485db63a055f12892adc478af5a6fb3c"><code>8c45e3b</code></a> fix(vt): let the segmenter decide clustering, not Go's unicode tables (<a href="https://redirect.github.com/gdamore/tcell/issues/1170">#1170</a>)</li> <li>See full diff in <a href="https://github.com/gdamore/tcell/compare/v3.5.0...v3.6.0">compare view</a></li> </ul> </details> <br />
This commit is contained in:
@@ -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