mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Bump github.com/gdamore/tcell/v3 from 3.3.0 to 3.4.0 (#5618)
Bumps [github.com/gdamore/tcell/v3](https://github.com/gdamore/tcell) from 3.3.0 to 3.4.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.4.0 Feature Release</h2> <p>This release has quite a number of bug fixes, but it <em>also</em> introduces some substantial new capabilities. Mostly the new support for advanced key reporting, along with backing support in the mouse demo, and enhanced configurability and overrides both for the application and for the user (environment variables).</p> <p>This also replaces the old WASM terminal with a new version based on the libghostty implementation. This should be much faster, and nicer to work with, with a larger set of full features such as advanced key reporting, better support for resizing, etc.</p> <h2>What's Changed</h2> <ul> <li>fix: possible panic in getConsoleInput if no event returned by <a href="https://github.com/AntoineGS"><code>@AntoineGS</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1068">gdamore/tcell#1068</a></li> <li>Sanitize titles and notifications (fixes <a href="https://redirect.github.com/gdamore/tcell/issues/1066">#1066</a>) by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1069">gdamore/tcell#1069</a></li> <li>feat: Optionally sanitize content before putting to the terminal (fix… by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1071">gdamore/tcell#1071</a></li> <li>feat: Add reporting of keyboard protocol (fixes <a href="https://redirect.github.com/gdamore/tcell/issues/967">#967</a>) by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1073">gdamore/tcell#1073</a></li> <li>C1 handling improvements by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1074">gdamore/tcell#1074</a></li> <li>feat(keys): Add support for advanced key reporting by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1075">gdamore/tcell#1075</a></li> <li>feat(wasm): Introduce ghostty-wasm as backing WASM terminal (fixes <a href="https://redirect.github.com/gdamore/tcell/issues/7">#7</a>… by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1076">gdamore/tcell#1076</a></li> <li>fix: close OSC8 hyperlinks (fixes <a href="https://redirect.github.com/gdamore/tcell/issues/1078">#1078</a>) by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1080">gdamore/tcell#1080</a></li> <li>feat: KeyBacktab updates for advanced mode (fixes <a href="https://redirect.github.com/gdamore/tcell/issues/1018">#1018</a>) by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1081">gdamore/tcell#1081</a></li> <li>feat: support pixel-precision mouse reporting (CSI ?1016h) by <a href="https://github.com/ImGajeed76"><code>@ImGajeed76</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1077">gdamore/tcell#1077</a></li> <li>fix(input): accept Unicode modifyOtherKeys codepoints by <a href="https://github.com/ayn2op"><code>@ayn2op</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1083">gdamore/tcell#1083</a></li> <li>Fix reporting of key releases by <a href="https://github.com/tihirvon"><code>@tihirvon</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1088">gdamore/tcell#1088</a></li> <li>chore(deps): bump golang.org/x/term from 0.42.0 to 0.43.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/gdamore/tcell/pull/1085">gdamore/tcell#1085</a></li> <li>chore(deps): bump golang.org/x/text from 0.36.0 to 0.37.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/gdamore/tcell/pull/1087">gdamore/tcell#1087</a></li> <li>MInor bug fixes by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1097">gdamore/tcell#1097</a></li> <li>Preserve orphaned UTF-16 surrogates on Windows by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1104">gdamore/tcell#1104</a></li> <li>vt: prune tab stops after resize by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1105">gdamore/tcell#1105</a></li> <li>vt: ignore malformed SGR parameters by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1106">gdamore/tcell#1106</a></li> <li>Limit inbound control strings by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1107">gdamore/tcell#1107</a></li> <li>fix: cells should be marked dirty even if they have no content (fixes… by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1108">gdamore/tcell#1108</a></li> <li>fix: lock SetSize screen mutations by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1109">gdamore/tcell#1109</a></li> <li>tscreen: prevent terminal mutation after Fini by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1110">gdamore/tcell#1110</a></li> <li>Avoid extra keyboard protocols on Windows by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1111">gdamore/tcell#1111</a></li> <li>[codex] Fix OSC 8 hyperlink state transitions by <a href="https://github.com/gdamore"><code>@gdamore</code></a> in <a href="https://redirect.github.com/gdamore/tcell/pull/1112">gdamore/tcell#1112</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/ImGajeed76"><code>@ImGajeed76</code></a> made their first contribution in <a href="https://redirect.github.com/gdamore/tcell/pull/1077">gdamore/tcell#1077</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/gdamore/tcell/compare/v3.3.0...v3.4.0">https://github.com/gdamore/tcell/compare/v3.3.0...v3.4.0</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="c67165c6c2"><code>c67165c</code></a> [codex] Fix OSC 8 hyperlink state transitions (<a href="https://redirect.github.com/gdamore/tcell/issues/1112">#1112</a>)</li> <li><a href="3f24a1a195"><code>3f24a1a</code></a> test: Include test case for Apple Terminal</li> <li><a href="0af1f67667"><code>0af1f67</code></a> feat: add terminal override controls</li> <li><a href="d3371c05d8"><code>d3371c0</code></a> fix: remove the 25ms startup delay for primary DA</li> <li><a href="7da1880519"><code>7da1880</code></a> feat: report WezTerm and Terminal.app when using env var detection</li> <li><a href="defbc72dec"><code>defbc72</code></a> fix: avoid extra keyboard protocols on windows and hard code WezTerm capabili...</li> <li><a href="a8373181b0"><code>a837318</code></a> tscreen: prevent terminal mutation after Fini (<a href="https://redirect.github.com/gdamore/tcell/issues/1110">#1110</a>)</li> <li><a href="d7fff268b3"><code>d7fff26</code></a> fix: lock SetSize screen mutations (fixes <a href="https://redirect.github.com/gdamore/tcell/issues/1098">#1098</a>) (<a href="https://redirect.github.com/gdamore/tcell/issues/1109">#1109</a>)</li> <li><a href="b91ae6def9"><code>b91ae6d</code></a> fix: cells should be marked dirty even if they have no content (fixes <a href="https://redirect.github.com/gdamore/tcell/issues/1089">#1089</a>) ...</li> <li><a href="c01e1075d5"><code>c01e107</code></a> fix: limit inbound control strings (<a href="https://redirect.github.com/gdamore/tcell/issues/1107">#1107</a>)</li> <li>Additional commits viewable in <a href="https://github.com/gdamore/tcell/compare/v3.3.0...v3.4.0">compare view</a></li> </ul> </details> <br />
This commit is contained in:
commit
8bdbfbd928
6
go.mod
6
go.mod
|
|
@ -13,7 +13,7 @@ require (
|
|||
github.com/cli/go-gh/v2 v2.13.0
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/gdamore/tcell/v3 v3.3.0
|
||||
github.com/gdamore/tcell/v3 v3.4.0
|
||||
github.com/go-errors/errors v1.5.1
|
||||
github.com/gookit/color v1.6.1
|
||||
github.com/integrii/flaggy v1.8.0
|
||||
|
|
@ -68,8 +68,8 @@ require (
|
|||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/term v0.42.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
|
|
|
|||
12
go.sum
12
go.sum
|
|
@ -33,8 +33,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.3.0 h1:lGo3VwiV8iYMH4TdwBDwEOoDctnOLns6QfrXvZkKNe8=
|
||||
github.com/gdamore/tcell/v3 v3.3.0/go.mod h1:8CJpEjUiAlFIrs7jUhzobiZsSLBAtXo8Dq9mCTrVnqo=
|
||||
github.com/gdamore/tcell/v3 v3.4.0 h1:VUym1HQZiYodA5PGQrqLxF7QwqQndcAUwQD7G7XUy5E=
|
||||
github.com/gdamore/tcell/v3 v3.4.0/go.mod h1:fjKxNiIFwbzTxDU+i+AAMz+xPOgXVaZq5tbShsKseHc=
|
||||
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=
|
||||
|
|
@ -167,15 +167,15 @@ golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|||
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.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
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.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
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=
|
||||
|
|
|
|||
4
vendor/github.com/gdamore/tcell/v3/CHANGESv3.md
generated
vendored
4
vendor/github.com/gdamore/tcell/v3/CHANGESv3.md
generated
vendored
|
|
@ -45,6 +45,10 @@ the associated lower case rune (e.g. "a", "b", etc.) and `ModCtrl`.
|
|||
The `KeyBackspace2` key is no longer delivered, but is converted to
|
||||
`KeyBackspace`. (This resolves some inconsistency around e.g. CTRL-H vs DELETE.)
|
||||
|
||||
When advanced key reporting is enabled, Shift-Tab is reported as `KeyTab` with
|
||||
`ModShift`, not as `KeyBacktab`. Legacy key reporting still reports Shift-Tab
|
||||
as `KeyBacktab`.
|
||||
|
||||
### Termbox Compatibility Removed
|
||||
|
||||
The `termbox` compatibility package is removed. Few applications were using it,
|
||||
|
|
|
|||
37
vendor/github.com/gdamore/tcell/v3/README-wasm.md
generated
vendored
37
vendor/github.com/gdamore/tcell/v3/README-wasm.md
generated
vendored
|
|
@ -11,11 +11,30 @@ GOOS=js GOARCH=wasm go build -o yourfile.wasm
|
|||
|
||||
## Additional files
|
||||
|
||||
You also need 5 other files in the same directory as the wasm. Four (`tcell.html`, `tcell.js`, `termstyle.css`, and `beep.wav`) are provided in the `webfiles` directory. The last one, `wasm_exec.js`, can be copied from GOROOT into the current directory by executing
|
||||
You also need the supporting web files in the same directory as the wasm. The files `tcell.html`, `tcell.js`, `termstyle.css`, and `beep.wav`, plus the `ghostty-web` directory, are provided in the `webfiles` directory. The last file, `wasm_exec.js`, can be copied from GOROOT into the current directory by executing
|
||||
```sh
|
||||
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ./
|
||||
```
|
||||
|
||||
The web frontend uses `ghostty-web`. The required browser runtime files are vendored in `webfiles/ghostty-web` and must be copied alongside `tcell.js`; no npm, bundler, or external CDN is required. The vendored `ghostty-web` files are MIT licensed; see `webfiles/ghostty-web/LICENSE`.
|
||||
|
||||
```sh
|
||||
cp -R webfiles/ghostty-web /path/to/dir/to/serve/
|
||||
```
|
||||
|
||||
The vendored `ghostty-web.js` is intentionally browser-only. Its upstream Node `readFile` fallback import is removed so browser-oriented servers and bundlers such as Vite do not try to resolve a Node file-system shim; the bundled code loads `ghostty-vt.wasm` with `fetch`.
|
||||
|
||||
For example:
|
||||
|
||||
```sh
|
||||
mkdir -p /tmp/tcell-wasm
|
||||
cp webfiles/tcell.html webfiles/tcell.js webfiles/termstyle.css webfiles/beep.wav /tmp/tcell-wasm/
|
||||
cp -R webfiles/ghostty-web /tmp/tcell-wasm/
|
||||
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" /tmp/tcell-wasm/
|
||||
GOOS=js GOARCH=wasm go build -o /tmp/tcell-wasm/main.wasm ./demos/unicode
|
||||
python3 -m http.server -d /tmp/tcell-wasm 8080
|
||||
```
|
||||
|
||||
In `tcell.js`, you also need to change the constant
|
||||
```js
|
||||
const wasmFilePath = "yourfile.wasm"
|
||||
|
|
@ -54,8 +73,24 @@ It is recommended to use an iframe if you want to embed the app into a webpage:
|
|||
<iframe src="tcell.html" title="Tcell app"></iframe>
|
||||
```
|
||||
|
||||
### Sizing
|
||||
|
||||
By default the web terminal fits itself to the size of the `#terminal` element and reacts to container resizes. The bundled `termstyle.css` makes this full-page by default.
|
||||
|
||||
You can override the terminal cell dimensions explicitly in HTML:
|
||||
|
||||
```html
|
||||
<pre id="terminal" data-cols="100" data-rows="30"></pre>
|
||||
```
|
||||
|
||||
If only one of `data-cols` or `data-rows` is set, the other dimension remains reactive.
|
||||
|
||||
## Other considerations
|
||||
|
||||
### Accessing files
|
||||
|
||||
`io.Open(filename)` and other related functions for reading file systems do not work; use `http.Get(filename)` instead.
|
||||
|
||||
### Keyboard shortcuts
|
||||
|
||||
The browser may reserve some key combinations before JavaScript can see or cancel them. This is especially common for Meta/Command shortcuts on macOS, such as Command-L. Standalone Meta key events can be reported, but Meta-modified key combinations are browser-dependent and should not be relied upon in WASM web mode.
|
||||
|
|
|
|||
18
vendor/github.com/gdamore/tcell/v3/README.md
generated
vendored
18
vendor/github.com/gdamore/tcell/v3/README.md
generated
vendored
|
|
@ -119,6 +119,24 @@ than respecting themes. For other cases, such as typical text apps that
|
|||
only use a few colors, its more desirable to respect the themes that
|
||||
the user has established.)
|
||||
|
||||
## Terminal Overrides
|
||||
|
||||
_Tcell_ normally negotiates terminal capabilities automatically, but some
|
||||
terminal emulators answer those queries incorrectly. These environment
|
||||
variables provide user escape hatches when the automatic path is not reliable:
|
||||
|
||||
- `TCELL_KEYBOARD_PROTOCOL=auto|legacy|kitty|win32|xterm` forces the keyboard
|
||||
reporting protocol.
|
||||
- `TCELL_NEGOTIATE=auto|disable` disables startup capability negotiation when
|
||||
terminal responses themselves are problematic.
|
||||
- `TCELL_MOUSE=auto|disable` prevents applications from enabling terminal mouse
|
||||
reporting.
|
||||
|
||||
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.
|
||||
|
||||
## Performance
|
||||
|
||||
Reasonable attempts have been made to minimize sending data to terminals,
|
||||
|
|
|
|||
26
vendor/github.com/gdamore/tcell/v3/cell.go
generated
vendored
26
vendor/github.com/gdamore/tcell/v3/cell.go
generated
vendored
|
|
@ -25,7 +25,15 @@ type cell struct {
|
|||
|
||||
func (c *cell) setDirty(dirty bool) {
|
||||
if dirty {
|
||||
c.lastStr = ""
|
||||
// Empty cells use currStr == "" until they are first drawn, at which
|
||||
// point SetDirty(false) normalizes them to a space. Using "" as the
|
||||
// dirty marker for an untouched empty cell would therefore leave
|
||||
// lastStr == currStr and fail to force a redraw.
|
||||
if c.currStr == "" {
|
||||
c.lastStr = " "
|
||||
} else {
|
||||
c.lastStr = ""
|
||||
}
|
||||
} else {
|
||||
if c.currStr == "" {
|
||||
c.currStr = " "
|
||||
|
|
@ -42,9 +50,10 @@ func (c *cell) setDirty(dirty bool) {
|
|||
//
|
||||
// CellBuffer is not thread safe.
|
||||
type CellBuffer struct {
|
||||
w int
|
||||
h int
|
||||
cells []cell
|
||||
w int
|
||||
h int
|
||||
cells []cell
|
||||
sanitizeContent bool
|
||||
}
|
||||
|
||||
// Put a single styled grapheme using the given string and style
|
||||
|
|
@ -52,6 +61,13 @@ type CellBuffer struct {
|
|||
// will be displayed, using only the 1 or 2 (depending on width) cells
|
||||
// located at x, y. It returns the rest of the string, and the width used.
|
||||
func (cb *CellBuffer) Put(x int, y int, str string, style Style) (string, int) {
|
||||
if cb.sanitizeContent {
|
||||
str = stripOSCControlsIfNeeded(str)
|
||||
}
|
||||
return cb.put(x, y, str, style)
|
||||
}
|
||||
|
||||
func (cb *CellBuffer) put(x int, y int, str string, style Style) (string, int) {
|
||||
var width int = 0
|
||||
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
|
||||
var cl string
|
||||
|
|
@ -118,7 +134,7 @@ func (cb *CellBuffer) Size() (int, int) {
|
|||
// Invalidate marks all characters within the buffer as dirty.
|
||||
func (cb *CellBuffer) Invalidate() {
|
||||
for i := range cb.cells {
|
||||
cb.cells[i].lastStr = ""
|
||||
cb.cells[i].setDirty(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
475
vendor/github.com/gdamore/tcell/v3/input.go
generated
vendored
475
vendor/github.com/gdamore/tcell/v3/input.go
generated
vendored
|
|
@ -22,8 +22,8 @@
|
|||
// There is unfortunately *one* conflict, with aixterm, for CSI-P - which is KeyDelete
|
||||
// in aixterm, but F1 in others.
|
||||
|
||||
//go:build !js && !wasm
|
||||
// +build !js,!wasm
|
||||
//go:build (!js && !wasm) || (js && wasm)
|
||||
// +build !js,!wasm js,wasm
|
||||
|
||||
package tcell
|
||||
|
||||
|
|
@ -59,31 +59,61 @@ const (
|
|||
istXda // extended device attributes (ESC P Ps ST)
|
||||
)
|
||||
|
||||
// defaultControlStringLimit caps inbound OSC/XDA control-string payloads
|
||||
// before they can grow without bound while waiting for a string terminator.
|
||||
const defaultControlStringLimit = 64 * 1024
|
||||
|
||||
func newInputParser(eq chan<- Event) *inputParser {
|
||||
return &inputParser{
|
||||
evch: eq,
|
||||
buf: make([]rune, 0, 128),
|
||||
evch: eq,
|
||||
buf: make([]rune, 0, 128),
|
||||
controlStringMax: defaultControlStringLimit,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
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
|
||||
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) {
|
||||
if n < 0 || n > 32767 {
|
||||
return 0, false
|
||||
}
|
||||
return Key(n), true
|
||||
}
|
||||
|
||||
func keyFromRune(r rune) (Key, bool) {
|
||||
if r < 0 || r > 32767 {
|
||||
return 0, false
|
||||
}
|
||||
return Key(r), true
|
||||
}
|
||||
|
||||
func asciiByteFromInt(n int) (byte, bool) {
|
||||
if n <= 0 || n >= 0x80 {
|
||||
return 0, false
|
||||
}
|
||||
return byte(n), true
|
||||
}
|
||||
|
||||
// Waiting returns true if the processor is waiting for
|
||||
|
|
@ -99,6 +129,21 @@ func (ip *inputParser) Waiting() bool {
|
|||
return ip.state != istInit
|
||||
}
|
||||
|
||||
// SetPixelMouse toggles whether SGR mouse reports are interpreted as
|
||||
// pixel coordinates (CSI ?1016h) rather than character cells (CSI ?1006h).
|
||||
// When enabled, mouse coordinates are not clipped to the screen size.
|
||||
// The setting is also forwarded to the lazily-created nested parser used
|
||||
// for win32-input-mode, if one exists, so both stay in sync.
|
||||
func (ip *inputParser) SetPixelMouse(on bool) {
|
||||
ip.l.Lock()
|
||||
ip.pixelMouse = on
|
||||
nested := ip.nested
|
||||
ip.l.Unlock()
|
||||
if nested != nil {
|
||||
nested.SetPixelMouse(on)
|
||||
}
|
||||
}
|
||||
|
||||
func (ip *inputParser) SetSize(w, h int) {
|
||||
if ip.nested != nil {
|
||||
ip.nested.SetSize(w, h)
|
||||
|
|
@ -116,7 +161,7 @@ func (ip *inputParser) post(ev Event) {
|
|||
if ip.escaped {
|
||||
ip.escaped = false
|
||||
if ke, ok := ev.(*EventKey); ok {
|
||||
ev = NewEventKey(ke.Key(), ke.Str(), ke.Modifiers()|ModAlt)
|
||||
ev = ip.newKey(ke.Key(), ke.Str(), ke.Modifiers()|ModAlt, ke.Pressed(), ke.Physical(), ke.Repeat())
|
||||
}
|
||||
} else if ke, ok := ev.(*EventKey); ok {
|
||||
switch ke.Key() {
|
||||
|
|
@ -130,6 +175,31 @@ func (ip *inputParser) post(ev Event) {
|
|||
ip.evch <- ev
|
||||
}
|
||||
|
||||
func (ip *inputParser) newKey(k Key, str string, mod ModMask, pressed bool, physical Key, repeat int) *EventKey {
|
||||
if ip.advanced {
|
||||
return NewEventKeyEx(k, str, mod, pressed, physical, repeat)
|
||||
}
|
||||
return NewEventKey(k, str, mod)
|
||||
}
|
||||
|
||||
func (ip *inputParser) postKey(k Key, str string, mod ModMask) {
|
||||
ip.post(ip.newKey(k, str, mod, true, 0, 1))
|
||||
}
|
||||
|
||||
func (ip *inputParser) postKeyEx(k Key, str string, mod ModMask, pressed bool, physical Key, repeat int) {
|
||||
ip.post(ip.newKey(k, str, mod, pressed, physical, repeat))
|
||||
}
|
||||
|
||||
func (ip *inputParser) postControlKey(r rune, mod ModMask) {
|
||||
if r == 0 {
|
||||
ip.postKeyEx(KeyRune, " ", mod|ModCtrl, true, Key(' '), 1)
|
||||
} else if ip.advanced && r >= 1 && r <= 26 {
|
||||
ip.postKeyEx(KeyRune, string('a'+r-1), mod|ModCtrl, true, Key('a'+r-1), 1)
|
||||
} else {
|
||||
ip.postKey(KeyRune, string(r+0x40), mod|ModCtrl)
|
||||
}
|
||||
}
|
||||
|
||||
type csiParamMode struct {
|
||||
M rune // Mode
|
||||
P int // Parameter (first)
|
||||
|
|
@ -358,6 +428,14 @@ var csiUKeys = map[int]keyMap{
|
|||
57425: {Key: KeyInsert}, // KP_INSERT
|
||||
57426: {Key: KeyDelete}, // KP_DELETE
|
||||
// 57427: {Key: KeyBegin}, // KP_BEGIN
|
||||
57441: {Key: KeyShift}, // LEFT_SHIFT
|
||||
57442: {Key: KeyCtrl}, // LEFT_CONTROL
|
||||
57443: {Key: KeyAlt}, // LEFT_ALT
|
||||
57444: {Key: KeyMeta}, // LEFT_SUPER
|
||||
57447: {Key: KeyShift}, // RIGHT_SHIFT
|
||||
57448: {Key: KeyCtrl}, // RIGHT_CONTROL
|
||||
57449: {Key: KeyAlt}, // RIGHT_ALT
|
||||
57450: {Key: KeyMeta}, // RIGHT_SUPER
|
||||
|
||||
// TODO: Media keys
|
||||
}
|
||||
|
|
@ -447,7 +525,8 @@ func (ip *inputParser) scan() {
|
|||
if r >= 0xA0 {
|
||||
// 8-bit extended Unicode we just treat as such - this will swallow anything else queued up
|
||||
ip.state = istInit
|
||||
ip.post(NewEventKey(KeyRune, string(r), ModNone))
|
||||
physical, _ := keyFromRune(r)
|
||||
ip.postKeyEx(KeyRune, string(r), ModNone, true, physical, 1)
|
||||
continue
|
||||
} else if r >= 0x80 {
|
||||
// ISO 2022 control chars
|
||||
|
|
@ -463,19 +542,20 @@ func (ip *inputParser) scan() {
|
|||
ip.state = istEsc
|
||||
ip.escChar = 0
|
||||
case '\t':
|
||||
ip.post(NewEventKey(KeyTab, "", ModNone))
|
||||
ip.postKey(KeyTab, "", ModNone)
|
||||
case '\b', '\x7F':
|
||||
ip.post(NewEventKey(KeyBackspace, "", ModNone))
|
||||
ip.postKey(KeyBackspace, "", ModNone)
|
||||
case '\r':
|
||||
ip.post(NewEventKey(KeyEnter, "", ModNone))
|
||||
ip.postKey(KeyEnter, "", ModNone)
|
||||
default:
|
||||
// Control keys - legacy handling
|
||||
if r == 0 {
|
||||
ip.post(NewEventKey(KeyRune, " ", ModCtrl))
|
||||
ip.postControlKey(r, ModNone)
|
||||
} else if r < ' ' {
|
||||
ip.post(NewEventKey(KeyRune, string(r+0x40), ModCtrl))
|
||||
ip.postControlKey(r, ModNone)
|
||||
} else {
|
||||
ip.post(NewEventKey(KeyRune, string(r), ModNone))
|
||||
physical, _ := keyFromRune(r)
|
||||
ip.postKeyEx(KeyRune, string(r), ModNone, true, physical, 1)
|
||||
}
|
||||
}
|
||||
case istEsc:
|
||||
|
|
@ -488,6 +568,7 @@ func (ip *inputParser) scan() {
|
|||
case ']':
|
||||
ip.state = istOsc
|
||||
ip.strBuf = nil
|
||||
ip.discardString = false
|
||||
ip.escChar = byte(r)
|
||||
case 'N':
|
||||
ip.state = istSs2 // no known uses
|
||||
|
|
@ -502,6 +583,7 @@ func (ip *inputParser) scan() {
|
|||
ip.state = istXda
|
||||
ip.csiParams = nil
|
||||
ip.strBuf = nil
|
||||
ip.discardString = false
|
||||
ip.escChar = byte(r)
|
||||
case 'X':
|
||||
ip.state = istSos
|
||||
|
|
@ -521,7 +603,7 @@ func (ip *inputParser) scan() {
|
|||
case '\t':
|
||||
// Linux console only, does not conform to ECMA
|
||||
ip.state = istInit
|
||||
ip.post(NewEventKey(KeyBacktab, "", ModNone))
|
||||
ip.postKey(KeyBacktab, "", ModNone)
|
||||
default:
|
||||
if r == '\x1b' {
|
||||
// leading ESC to capture alt
|
||||
|
|
@ -535,7 +617,8 @@ func (ip *inputParser) scan() {
|
|||
mod |= ModCtrl
|
||||
r += 0x60
|
||||
}
|
||||
ip.post(NewEventKey(KeyRune, string(r), mod))
|
||||
physical, _ := keyFromRune(r)
|
||||
ip.postKeyEx(KeyRune, string(r), mod, true, physical, 1)
|
||||
}
|
||||
}
|
||||
case istCsi:
|
||||
|
|
@ -585,16 +668,16 @@ func (ip *inputParser) scan() {
|
|||
// parameters that do not match one of these forms, we just discard it.
|
||||
if len(ip.csiParams) == 0 {
|
||||
// simple SS3 case
|
||||
ip.post(NewEventKey(k, "", ModNone))
|
||||
ip.postKey(k, "", ModNone)
|
||||
} else if parts := strings.Split(string(ip.csiParams), ";"); len(parts) >= 1 {
|
||||
// SS3 with modifier (old style). Note old terminfo would declare these as high
|
||||
// numbered function keys, but we encode as modified since that's how they are entered.
|
||||
if len(parts) >= 2 {
|
||||
if m, err := strconv.Atoi(parts[1]); err == nil && (parts[0] == "1" || parts[0] == "") {
|
||||
ip.post(NewEventKey(k, "", calcModifier(m)))
|
||||
ip.postKey(k, "", calcModifier(m))
|
||||
}
|
||||
} else if m, err := strconv.Atoi(parts[0]); err == nil {
|
||||
ip.post(NewEventKey(k, "", calcModifier(m)))
|
||||
ip.postKey(k, "", calcModifier(m))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -614,9 +697,16 @@ func (ip *inputParser) scan() {
|
|||
ip.strState = ip.state
|
||||
ip.state = istSt
|
||||
case '\x07':
|
||||
ip.handleXda(string(ip.strBuf))
|
||||
if ip.discardString {
|
||||
ip.discardString = false
|
||||
ip.state = istInit
|
||||
} else {
|
||||
ip.handleXda(string(ip.strBuf))
|
||||
}
|
||||
default:
|
||||
ip.strBuf = append(ip.strBuf, byte(r&0x7f))
|
||||
if !ip.discardString {
|
||||
ip.appendStringBytes(byte(r & 0x7f))
|
||||
}
|
||||
}
|
||||
|
||||
case istOsc: // not sure if used
|
||||
|
|
@ -625,29 +715,42 @@ func (ip *inputParser) scan() {
|
|||
ip.strState = ip.state
|
||||
ip.state = istSt
|
||||
case '\x07':
|
||||
ip.handleOsc(string(ip.strBuf))
|
||||
if ip.discardString {
|
||||
ip.discardString = false
|
||||
ip.state = istInit
|
||||
} else {
|
||||
ip.handleOsc(string(ip.strBuf))
|
||||
}
|
||||
default:
|
||||
ip.strBuf = append(ip.strBuf, byte(r&0x7f))
|
||||
if !ip.discardString {
|
||||
ip.appendStringBytes(byte(r & 0x7f))
|
||||
}
|
||||
}
|
||||
case istSt:
|
||||
if r == '\\' || r == '\x07' {
|
||||
ip.state = istInit
|
||||
switch ip.strState {
|
||||
case istOsc:
|
||||
ip.handleOsc(string(ip.strBuf))
|
||||
case istXda:
|
||||
ip.handleXda(string(ip.strBuf))
|
||||
case istPm, istApc, istSos, istDcs:
|
||||
ip.state = istInit
|
||||
if ip.discardString {
|
||||
ip.discardString = false
|
||||
} else {
|
||||
switch ip.strState {
|
||||
case istOsc:
|
||||
ip.handleOsc(string(ip.strBuf))
|
||||
case istXda:
|
||||
ip.handleXda(string(ip.strBuf))
|
||||
case istPm, istApc, istSos, istDcs:
|
||||
ip.state = istInit
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ip.strBuf = append(ip.strBuf, '\x1b', byte(r))
|
||||
if !ip.discardString {
|
||||
ip.appendStringBytes('\x1b', byte(r))
|
||||
}
|
||||
ip.state = ip.strState
|
||||
}
|
||||
case istLnx:
|
||||
// linux console does not follow ECMA
|
||||
if k, ok := linuxFKeys[r]; ok {
|
||||
ip.post(NewEventKey(k, "", ModNone))
|
||||
ip.postKey(k, "", ModNone)
|
||||
}
|
||||
ip.state = istInit
|
||||
}
|
||||
|
|
@ -655,15 +758,25 @@ func (ip *inputParser) scan() {
|
|||
|
||||
if ip.state != istInit && time.Since(ip.keyTime) > time.Millisecond*50 {
|
||||
if ip.state == istEsc {
|
||||
ip.post(NewEventKey(KeyEscape, "", ModNone))
|
||||
ip.postKey(KeyEscape, "", ModNone)
|
||||
} else if ec := ip.escChar; ec != 0 {
|
||||
ip.post(NewEventKey(KeyRune, string(ec), ModAlt))
|
||||
ip.postKey(KeyRune, string(ec), ModAlt)
|
||||
}
|
||||
// if we take too long between bytes, reset the state machine.
|
||||
ip.state = istInit
|
||||
ip.discardString = false
|
||||
}
|
||||
}
|
||||
|
||||
func (ip *inputParser) appendStringBytes(bs ...byte) {
|
||||
if ip.controlStringMax > 0 && len(ip.strBuf)+len(bs) > ip.controlStringMax {
|
||||
ip.strBuf = nil
|
||||
ip.discardString = true
|
||||
return
|
||||
}
|
||||
ip.strBuf = append(ip.strBuf, bs...)
|
||||
}
|
||||
|
||||
func (ip *inputParser) handleOsc(str string) {
|
||||
ip.state = istInit
|
||||
if content, ok := strings.CutPrefix(str, "52;c;"); ok {
|
||||
|
|
@ -717,6 +830,99 @@ func calcModifier(n int) ModMask {
|
|||
return m
|
||||
}
|
||||
|
||||
func calcWinModifier(n int, advanced bool) ModMask {
|
||||
m := ModNone
|
||||
if n&0x010 != 0 {
|
||||
m |= ModShift
|
||||
}
|
||||
if advanced {
|
||||
// Bits through 0x0100 match Win32 dwControlKeyState. 0x0040 and
|
||||
// 0x0080 are ScrollLock and CapsLock, not Meta. The 0x0200 and
|
||||
// 0x0400 bits are tcell extensions used by the WASM browser shim,
|
||||
// which has Meta keys but no native Win32 bit assignment for them.
|
||||
if n&0x0008 != 0 {
|
||||
m |= ModLCtrl
|
||||
}
|
||||
if n&0x0004 != 0 {
|
||||
m |= ModRCtrl
|
||||
}
|
||||
if n&0x0002 != 0 {
|
||||
m |= ModLAlt
|
||||
}
|
||||
if n&0x0001 != 0 {
|
||||
m |= ModRAlt
|
||||
}
|
||||
if n&0x0200 != 0 {
|
||||
m |= ModLMeta
|
||||
}
|
||||
if n&0x0400 != 0 {
|
||||
m |= ModRMeta
|
||||
}
|
||||
} else {
|
||||
if n&0x000c != 0 {
|
||||
m |= ModCtrl
|
||||
}
|
||||
if n&0x0003 != 0 {
|
||||
m |= ModAlt
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func winModifierKey(vk int) (Key, ModMask, bool) {
|
||||
switch vk {
|
||||
case 0x10:
|
||||
return KeyShift, ModShift, true
|
||||
case 0xa0:
|
||||
return KeyShift, ModLShift, true
|
||||
case 0xa1:
|
||||
return KeyShift, ModRShift, true
|
||||
case 0x11:
|
||||
return KeyCtrl, ModCtrl, true
|
||||
case 0xa2:
|
||||
return KeyCtrl, ModLCtrl, true
|
||||
case 0xa3:
|
||||
return KeyCtrl, ModRCtrl, true
|
||||
case 0x12:
|
||||
return KeyAlt, ModAlt, true
|
||||
case 0xa4:
|
||||
return KeyAlt, ModLAlt, true
|
||||
case 0xa5:
|
||||
return KeyAlt, ModRAlt, true
|
||||
case 0x5b:
|
||||
return KeyMeta, ModLMeta, true
|
||||
case 0x5c:
|
||||
return KeyMeta, ModRMeta, true
|
||||
case 0x14:
|
||||
return KeyCapsLock, ModNone, true
|
||||
default:
|
||||
return 0, ModNone, false
|
||||
}
|
||||
}
|
||||
|
||||
func kittyModifierKey(code int) ModMask {
|
||||
switch code {
|
||||
case 57441:
|
||||
return ModLShift
|
||||
case 57447:
|
||||
return ModRShift
|
||||
case 57442:
|
||||
return ModLCtrl
|
||||
case 57448:
|
||||
return ModRCtrl
|
||||
case 57443:
|
||||
return ModLAlt
|
||||
case 57449:
|
||||
return ModRAlt
|
||||
case 57444:
|
||||
return ModLMeta
|
||||
case 57450:
|
||||
return ModRMeta
|
||||
default:
|
||||
return ModNone
|
||||
}
|
||||
}
|
||||
|
||||
func (ip *inputParser) handleMouse(mode rune, params []int) {
|
||||
|
||||
// XTerm mouse events only report at most one button at a time,
|
||||
|
|
@ -729,9 +935,15 @@ func (ip *inputParser) handleMouse(mode rune, params []int) {
|
|||
btn := params[0]
|
||||
// Some terminals will report mouse coordinates outside the
|
||||
// screen, especially with click-drag events. Clip the coordinates
|
||||
// to the screen in that case.
|
||||
x := max(min(params[1]-1, ip.cols-1), 0)
|
||||
y := max(min(params[2]-1, ip.rows-1), 0)
|
||||
// to the screen in that case. In pixel-reporting mode (CSI ?1016h)
|
||||
// the values are already pixels rather than cells, so skip the clip
|
||||
// and pass them through unchanged for the application to interpret.
|
||||
x := params[1] - 1
|
||||
y := params[2] - 1
|
||||
if !ip.pixelMouse {
|
||||
x = max(min(x, ip.cols-1), 0)
|
||||
y = max(min(y, ip.rows-1), 0)
|
||||
}
|
||||
|
||||
button := ButtonNone
|
||||
mod := ModNone
|
||||
|
|
@ -827,7 +1039,7 @@ func (ip *inputParser) handleWinKey(P []int) {
|
|||
for len(P) < 6 {
|
||||
P = append(P, 0) // ensure sufficient length
|
||||
}
|
||||
if P[3] == 0 {
|
||||
if P[3] == 0 && !ip.advanced {
|
||||
// key up event ignore ignore
|
||||
return
|
||||
}
|
||||
|
|
@ -835,58 +1047,81 @@ func (ip *inputParser) handleWinKey(P []int) {
|
|||
// these terminals never send ambiguous escapes
|
||||
ip.escaped = false
|
||||
|
||||
if P[0] == 0 && P[1] == 0 && P[2] > 0 && P[2] < 0x80 { // only ASCII in win32-input-mode
|
||||
if ip.nested == nil {
|
||||
ip.nested = &inputParser{
|
||||
evch: ip.evch,
|
||||
rows: ip.rows,
|
||||
cols: ip.cols,
|
||||
if P[0] == 0 && P[1] == 0 { // only ASCII in win32-input-mode
|
||||
if b, ok := asciiByteFromInt(P[2]); ok {
|
||||
if ip.nested == nil {
|
||||
ip.nested = &inputParser{
|
||||
evch: ip.evch,
|
||||
rows: ip.rows,
|
||||
cols: ip.cols,
|
||||
advanced: ip.advanced,
|
||||
pixelMouse: ip.pixelMouse,
|
||||
controlStringMax: ip.controlStringMax,
|
||||
}
|
||||
}
|
||||
ip.nested.ScanUTF8([]byte{b})
|
||||
return
|
||||
}
|
||||
if P[2] > 0 {
|
||||
ip.nested.ScanUTF8([]byte{byte(P[2])})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
key := KeyRune
|
||||
chr := rune(P[2])
|
||||
mod := ModNone
|
||||
rpt := max(1, P[5])
|
||||
decoded := false
|
||||
if k1, ok := winKeys[P[0]]; ok {
|
||||
chr = 0
|
||||
key = k1
|
||||
decoded = true
|
||||
} else if ip.advanced {
|
||||
if k1, mod1, ok := winModifierKey(P[0]); ok {
|
||||
key = k1
|
||||
mod = mod1
|
||||
chr = 0
|
||||
decoded = true
|
||||
}
|
||||
}
|
||||
if decoded {
|
||||
// Already decoded.
|
||||
} else if chr == 0 && P[0] >= 0x30 && P[0] <= 0x39 {
|
||||
chr = rune(P[0])
|
||||
} else if chr < ' ' && P[0] >= 0x41 && P[0] <= 0x5a {
|
||||
key = Key(P[0])
|
||||
chr = 0
|
||||
if ip.advanced {
|
||||
key = KeyRune
|
||||
chr = rune(P[0] + 0x20)
|
||||
} else {
|
||||
var ok bool
|
||||
if key, ok = keyFromInt(P[0]); !ok {
|
||||
return
|
||||
}
|
||||
chr = 0
|
||||
}
|
||||
} else if chr >= 0xD800 && chr <= 0xDBFF {
|
||||
// high surrogate pair
|
||||
if ip.surrogate != 0 {
|
||||
ip.postKeyEx(KeyRune, string(utf8.RuneError), mod, P[3] != 0, 0, rpt)
|
||||
}
|
||||
ip.surrogate = chr
|
||||
return
|
||||
} else if chr >= 0xDC00 && chr <= 0xDFFF {
|
||||
// low surrogate pair
|
||||
chr = utf16.DecodeRune(ip.surrogate, chr)
|
||||
} else if P[0] == 0x10 || P[0] == 0x11 || P[0] == 0x12 || P[0] == 0x14 {
|
||||
// lone modifiers
|
||||
if ip.surrogate == 0 {
|
||||
chr = utf8.RuneError
|
||||
} else {
|
||||
chr = utf16.DecodeRune(ip.surrogate, chr)
|
||||
}
|
||||
} else if ip.surrogate != 0 {
|
||||
ip.postKeyEx(KeyRune, string(utf8.RuneError), mod, P[3] != 0, 0, rpt)
|
||||
} else if _, _, ok := winModifierKey(P[0]); ok {
|
||||
// Lone modifier releases are ignored unless advanced mode is enabled.
|
||||
ip.surrogate = 0
|
||||
return
|
||||
}
|
||||
|
||||
ip.surrogate = 0
|
||||
|
||||
// Modifiers
|
||||
if P[4]&0x010 != 0 {
|
||||
mod |= ModShift
|
||||
}
|
||||
if P[4]&0x000c != 0 {
|
||||
mod |= ModCtrl
|
||||
}
|
||||
if P[4]&0x0003 != 0 {
|
||||
mod |= ModAlt
|
||||
}
|
||||
if key == KeyRune && chr > ' ' && mod == ModShift {
|
||||
mod |= calcWinModifier(P[4], ip.advanced)
|
||||
if key == KeyRune && chr > ' ' && mod == ModShift && !ip.advanced {
|
||||
// filter out lone shift for printable chars
|
||||
mod = ModNone
|
||||
}
|
||||
|
|
@ -895,13 +1130,18 @@ func (ip *inputParser) handleWinKey(P []int) {
|
|||
mod = ModNone
|
||||
}
|
||||
|
||||
for range rpt {
|
||||
if key != KeyRune {
|
||||
ip.post(NewEventKey(key, "", mod))
|
||||
} else if chr != 0 {
|
||||
ip.post(NewEventKey(KeyRune, string(chr), mod))
|
||||
physical := key
|
||||
if key == KeyRune && chr != 0 {
|
||||
physical, _ = keyFromRune(chr)
|
||||
if ip.advanced && P[0] >= 0x41 && P[0] <= 0x5a {
|
||||
physical, _ = keyFromInt(P[0] + 0x20)
|
||||
}
|
||||
}
|
||||
if key != KeyRune {
|
||||
ip.postKeyEx(key, "", mod, P[3] != 0, physical, rpt)
|
||||
} else if chr != 0 {
|
||||
ip.postKeyEx(KeyRune, string(chr), mod, P[3] != 0, physical, rpt)
|
||||
}
|
||||
}
|
||||
|
||||
func (ip *inputParser) handlePrimaryDA(params []int) {
|
||||
|
|
@ -976,7 +1216,6 @@ func (ip *inputParser) handleCsi(mode rune, params []byte, intermediate []byte)
|
|||
// reset state
|
||||
ip.state = istInit
|
||||
|
||||
var parts []string
|
||||
var P []int
|
||||
hasLT := false
|
||||
hasQM := false
|
||||
|
|
@ -994,16 +1233,54 @@ func (ip *inputParser) handleCsi(mode rune, params []byte, intermediate []byte)
|
|||
pstr = pstr[1:]
|
||||
}
|
||||
|
||||
pressed := true
|
||||
repeat := 1
|
||||
physical := Key(0)
|
||||
if pstr != "" && pstr[0] >= '0' && pstr[0] <= '9' {
|
||||
parts = strings.Split(pstr, ";")
|
||||
var PSubs [][]int
|
||||
|
||||
parts := strings.Split(pstr, ";")
|
||||
for i := range parts {
|
||||
if parts[i] != "" {
|
||||
if n, e := strconv.ParseInt(parts[i], 10, 32); e == nil {
|
||||
subparts := strings.Split(parts[i], ":")
|
||||
if subparts[0] != "" {
|
||||
if n, e := strconv.ParseInt(subparts[0], 10, 32); e == nil {
|
||||
P = append(P, int(n))
|
||||
} else {
|
||||
P = append(P, 0)
|
||||
}
|
||||
} else {
|
||||
P = append(P, 0)
|
||||
}
|
||||
subs := []int{}
|
||||
for _, sub := range subparts[1:] {
|
||||
if sub != "" {
|
||||
if n, e := strconv.ParseInt(sub, 10, 32); e == nil {
|
||||
subs = append(subs, int(n))
|
||||
}
|
||||
} else {
|
||||
subs = append(subs, 0)
|
||||
}
|
||||
}
|
||||
PSubs = append(PSubs, subs)
|
||||
}
|
||||
if len(PSubs) > 1 && len(PSubs[1]) > 0 {
|
||||
switch PSubs[1][0] {
|
||||
case 2:
|
||||
repeat = 2
|
||||
case 3:
|
||||
pressed = false
|
||||
}
|
||||
}
|
||||
if len(PSubs) > 0 && len(PSubs[0]) > 0 {
|
||||
base := PSubs[0][0]
|
||||
if baseKey, ok := csiUKeys[base]; ok {
|
||||
physical = baseKey.Key
|
||||
if physical == KeyRune && baseKey.Rune != 0 {
|
||||
physical, _ = keyFromRune(baseKey.Rune)
|
||||
}
|
||||
} else if base != 0 {
|
||||
physical, _ = keyFromInt(base)
|
||||
}
|
||||
}
|
||||
}
|
||||
var P0 int
|
||||
|
|
@ -1076,10 +1353,13 @@ func (ip *inputParser) handleCsi(mode rune, params []byte, intermediate []byte)
|
|||
if len(P) > 1 {
|
||||
mod = calcModifier(P[1])
|
||||
}
|
||||
if mod1 := kittyModifierKey(P0); mod1 != ModNone {
|
||||
mod |= mod1
|
||||
}
|
||||
if key != KeyRune {
|
||||
ip.post(NewEventKey(key, "", mod))
|
||||
ip.postKeyEx(key, "", mod, pressed, physical, repeat)
|
||||
} else if chr != 0 {
|
||||
ip.post(NewEventKey(KeyRune, string(chr), mod))
|
||||
ip.postKeyEx(KeyRune, string(chr), mod, pressed, physical, repeat)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -1114,14 +1394,17 @@ func (ip *inputParser) handleCsi(mode rune, params []byte, intermediate []byte)
|
|||
if len(P) >= 2 {
|
||||
mod := calcModifier(P[1])
|
||||
if ks, ok := csiAllKeys[csiParamMode{M: mode, P: P0}]; ok {
|
||||
ip.post(NewEventKey(ks.Key, "", mod))
|
||||
ip.postKeyEx(ks.Key, "", mod, pressed, 0, repeat)
|
||||
return
|
||||
}
|
||||
if P0 == 27 && len(P) > 2 && P[2] > 0 && P[2] <= 0xff {
|
||||
if P0 == 27 && len(P) > 2 && P[2] > 0 && P[2] <= utf8.MaxRune {
|
||||
if P[2] < ' ' || P[2] == 0x7F {
|
||||
ip.post(NewEventKey(Key(P[2]), "", mod))
|
||||
if key, ok := keyFromInt(P[2]); ok {
|
||||
ip.postKey(key, "", mod)
|
||||
}
|
||||
} else {
|
||||
ip.post(NewEventKey(KeyRune, string(rune(P[2])), mod))
|
||||
physical, _ := keyFromRune(rune(P[2]))
|
||||
ip.postKeyEx(KeyRune, string(rune(P[2])), mod, true, physical, 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -1135,13 +1418,13 @@ func (ip *inputParser) handleCsi(mode rune, params []byte, intermediate []byte)
|
|||
} else if mode == 'P' && os.Getenv("TERM") == "aixterm" {
|
||||
ks.Key = KeyDelete // aixterm hack - conflicts with kitty protocol
|
||||
}
|
||||
ip.post(NewEventKey(ks.Key, "", ks.Mod))
|
||||
ip.postKey(ks.Key, "", ks.Mod)
|
||||
return
|
||||
}
|
||||
|
||||
// this might have been an SS3 style key with modifiers applied
|
||||
if k, ok := ss3Keys[mode]; ok && P0 == 1 && len(P) > 1 {
|
||||
ip.post(NewEventKey(k, "", calcModifier(P[1])))
|
||||
ip.postKeyEx(k, "", calcModifier(P[1]), pressed, 0, repeat)
|
||||
return
|
||||
}
|
||||
// if we got here we just swallow the unknown sequence
|
||||
|
|
|
|||
270
vendor/github.com/gdamore/tcell/v3/key.go
generated
vendored
270
vendor/github.com/gdamore/tcell/v3/key.go
generated
vendored
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2025 The TCell Authors
|
||||
// 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.
|
||||
|
|
@ -45,9 +45,12 @@ import (
|
|||
// specific keys.
|
||||
type EventKey struct {
|
||||
EventTime
|
||||
mod ModMask
|
||||
key Key
|
||||
str string // string for key, usually just one character, but may be composed sequence
|
||||
mod ModMask
|
||||
key Key
|
||||
physical Key
|
||||
str string // string for key, usually just one character, but may be composed sequence
|
||||
pressed bool
|
||||
repeat int
|
||||
}
|
||||
|
||||
// Str returns the string corresponding to the key press, if it makes sense.
|
||||
|
|
@ -66,6 +69,38 @@ func (ev *EventKey) Key() Key {
|
|||
return ev.key
|
||||
}
|
||||
|
||||
// Physical returns the physical key that was pressed, when known.
|
||||
//
|
||||
// This is different from Key() and Str(), which describe the logical key result
|
||||
// delivered to the application. For example, on a US keyboard Shift-/ may
|
||||
// produce Str() == "?", while Physical() reports KeySlash. Most applications
|
||||
// should use Key() and Str(); Physical is intended for layout-independent uses
|
||||
// such as keyboard remappers, embedded terminal emulators, and games that care
|
||||
// about key location rather than the printed character.
|
||||
//
|
||||
// For letter keys, compare physical values against the lowercase aliases
|
||||
// KeyA through KeyZ. The legacy KeyCtrlA through KeyCtrlZ constants occupy
|
||||
// the same numeric range as Key('A') through Key('Z'), so Key('A') is not a
|
||||
// physical "A" key identifier.
|
||||
//
|
||||
// If the physical key is unknown, this returns zero.
|
||||
func (ev *EventKey) Physical() Key {
|
||||
return ev.physical
|
||||
}
|
||||
|
||||
// Pressed returns true for key press events, and false for key release events.
|
||||
// Legacy keyboard reporting only reports presses.
|
||||
func (ev *EventKey) Pressed() bool {
|
||||
return ev.pressed
|
||||
}
|
||||
|
||||
// Repeat returns the repeat count for this key event. Legacy keyboard
|
||||
// reporting synthesizes repeated key presses as separate events, so this will
|
||||
// normally be 1.
|
||||
func (ev *EventKey) Repeat() int {
|
||||
return ev.repeat
|
||||
}
|
||||
|
||||
// Modifiers returns the modifiers that were present with the key press. Note
|
||||
// that not all platforms and terminals support this equally well, and some
|
||||
// cases we will not not know for sure. Hence, applications should avoid
|
||||
|
|
@ -74,6 +109,19 @@ func (ev *EventKey) Modifiers() ModMask {
|
|||
return ev.mod
|
||||
}
|
||||
|
||||
// KeyProtocol identifies the keyboard reporting protocol that the terminal
|
||||
// is currently using. More capable protocols allow disambiguating modifier
|
||||
// combinations, distinguishing key release events, etc.
|
||||
type KeyProtocol int
|
||||
|
||||
// These are the keyboard protocols that tcell can report.
|
||||
const (
|
||||
LegacyKeyboard KeyProtocol = iota // basic VT100 style reports
|
||||
KittyKeyboard // kitty supports events, unambiguous keys modulo left/right modifiers
|
||||
Win32Keyboard // win32 supports the full feature set
|
||||
XTermKeyboard // xterm modify other keys, disambiguation only, no release events
|
||||
)
|
||||
|
||||
// KeyNames holds the written names of special keys. Useful to echo back a key
|
||||
// name, or to look up a key from a string value.
|
||||
var KeyNames = map[Key]string{
|
||||
|
|
@ -171,6 +219,11 @@ var KeyNames = map[Key]string{
|
|||
KeyCapsLock: "CapsLock",
|
||||
KeyScrollLock: "ScrollLock",
|
||||
KeyNumLock: "NumLock",
|
||||
KeyShift: "Shift",
|
||||
KeyCtrl: "Ctrl",
|
||||
KeyAlt: "Alt",
|
||||
KeyMeta: "Meta",
|
||||
KeyHyper: "Hyper",
|
||||
KeyCtrlA: "Ctrl-A",
|
||||
KeyCtrlB: "Ctrl-B",
|
||||
KeyCtrlC: "Ctrl-C",
|
||||
|
|
@ -204,19 +257,49 @@ var KeyNames = map[Key]string{
|
|||
func (ev *EventKey) Name() string {
|
||||
s := ""
|
||||
m := []string{}
|
||||
if ev.mod&ModShift != 0 {
|
||||
if ev.mod&modLShift != 0 {
|
||||
m = append(m, "LeftShift")
|
||||
}
|
||||
if ev.mod&modRShift != 0 {
|
||||
m = append(m, "RightShift")
|
||||
}
|
||||
if ev.mod&ModShift != 0 && ev.mod&(modLShift|modRShift) == 0 {
|
||||
m = append(m, "Shift")
|
||||
}
|
||||
if ev.mod&ModAlt != 0 {
|
||||
if ev.mod&modLAlt != 0 {
|
||||
m = append(m, "LeftAlt")
|
||||
}
|
||||
if ev.mod&modRAlt != 0 {
|
||||
m = append(m, "RightAlt")
|
||||
}
|
||||
if ev.mod&ModAlt != 0 && ev.mod&(modLAlt|modRAlt) == 0 {
|
||||
m = append(m, "Alt")
|
||||
}
|
||||
if ev.mod&ModMeta != 0 {
|
||||
if ev.mod&modLMeta != 0 {
|
||||
m = append(m, "LeftMeta")
|
||||
}
|
||||
if ev.mod&modRMeta != 0 {
|
||||
m = append(m, "RightMeta")
|
||||
}
|
||||
if ev.mod&ModMeta != 0 && ev.mod&(modLMeta|modRMeta) == 0 {
|
||||
m = append(m, "Meta")
|
||||
}
|
||||
if ev.mod&ModCtrl != 0 {
|
||||
if ev.mod&modLCtrl != 0 {
|
||||
m = append(m, "LeftCtrl")
|
||||
}
|
||||
if ev.mod&modRCtrl != 0 {
|
||||
m = append(m, "RightCtrl")
|
||||
}
|
||||
if ev.mod&ModCtrl != 0 && ev.mod&(modLCtrl|modRCtrl) == 0 {
|
||||
m = append(m, "Ctrl")
|
||||
}
|
||||
if ev.mod&ModHyper != 0 {
|
||||
if ev.mod&modLHyper != 0 {
|
||||
m = append(m, "LeftHyper")
|
||||
}
|
||||
if ev.mod&modRHyper != 0 {
|
||||
m = append(m, "RightHyper")
|
||||
}
|
||||
if ev.mod&ModHyper != 0 && ev.mod&(modLHyper|modRHyper) == 0 {
|
||||
m = append(m, "Hyper")
|
||||
}
|
||||
|
||||
|
|
@ -229,6 +312,28 @@ func (ev *EventKey) Name() string {
|
|||
}
|
||||
}
|
||||
if len(m) != 0 {
|
||||
switch ev.key {
|
||||
case KeyShift:
|
||||
if ev.mod&(modLShift|modRShift|ModShift) != 0 {
|
||||
return strings.Join(m, "+")
|
||||
}
|
||||
case KeyCtrl:
|
||||
if ev.mod&(modLCtrl|modRCtrl|ModCtrl) != 0 {
|
||||
return strings.Join(m, "+")
|
||||
}
|
||||
case KeyAlt:
|
||||
if ev.mod&(modLAlt|modRAlt|ModAlt) != 0 {
|
||||
return strings.Join(m, "+")
|
||||
}
|
||||
case KeyMeta:
|
||||
if ev.mod&(modLMeta|modRMeta|ModMeta) != 0 {
|
||||
return strings.Join(m, "+")
|
||||
}
|
||||
case KeyHyper:
|
||||
if ev.mod&(modLHyper|modRHyper|ModHyper) != 0 {
|
||||
return strings.Join(m, "+")
|
||||
}
|
||||
}
|
||||
if ev.mod&ModCtrl != 0 && strings.HasPrefix(s, "Ctrl-") {
|
||||
s = s[5:]
|
||||
}
|
||||
|
|
@ -242,10 +347,25 @@ func (ev *EventKey) Name() string {
|
|||
// has more precise information it should set that specifically. Callers
|
||||
// that aren't sure about modifier state (most) should just pass ModNone.
|
||||
func NewEventKey(k Key, str string, mod ModMask) *EventKey {
|
||||
return newEventKey(k, str, mod, true, 0, 1, false)
|
||||
}
|
||||
|
||||
// NewEventKeyEx creates an extended key event with press/release, physical key,
|
||||
// and repeat metadata. It also uses the newer key normalization rules: ASCII
|
||||
// control letters are reported as KeyRune plus ModCtrl instead of legacy
|
||||
// KeyCtrlA through KeyCtrlZ values.
|
||||
func NewEventKeyEx(k Key, str string, mod ModMask, pressed bool, physical Key, repeat int) *EventKey {
|
||||
return newEventKey(k, str, mod, pressed, physical, repeat, true)
|
||||
}
|
||||
|
||||
func newEventKey(k Key, str string, mod ModMask, pressed bool, physical Key, repeat int, advanced bool) *EventKey {
|
||||
ch := rune(0)
|
||||
if len(str) == 1 {
|
||||
ch = []rune(str)[0]
|
||||
}
|
||||
if repeat <= 0 {
|
||||
repeat = 1
|
||||
}
|
||||
|
||||
if k == KeyRune {
|
||||
if ch != 0 && (ch < ' ' || ch == 0x7f) {
|
||||
|
|
@ -267,7 +387,7 @@ func NewEventKey(k Key, str string, mod ModMask) *EventKey {
|
|||
|
||||
// For legacy reasons, if Ctrl is pressed with an ASCII alphabetic, then we
|
||||
// emit it as a KeyCtrlXX symbol.
|
||||
if mod == ModCtrl {
|
||||
if mod == ModCtrl && !advanced {
|
||||
// We don't do Ctrl-[ or backslash or those specially.
|
||||
if ch >= 'A' && ch <= 'Z' { // upper case
|
||||
k = KeyCtrlA + Key(ch-'A')
|
||||
|
|
@ -280,7 +400,7 @@ func NewEventKey(k Key, str string, mod ModMask) *EventKey {
|
|||
|
||||
// Windows reports ModShift for shifted keys. This is inconsistent
|
||||
// with UNIX, lets harmonize this.
|
||||
if mod == ModShift && str != "" {
|
||||
if mod == ModShift && str != "" && !advanced {
|
||||
mod = ModNone
|
||||
}
|
||||
}
|
||||
|
|
@ -290,19 +410,29 @@ func NewEventKey(k Key, str string, mod ModMask) *EventKey {
|
|||
k = KeyBackspace
|
||||
}
|
||||
|
||||
// Advanced key reporting exposes Shift-Tab directly. Backtab is a legacy
|
||||
// alias from terminals that cannot distinguish a physical Backtab key.
|
||||
if k == KeyBacktab && advanced {
|
||||
k = KeyTab
|
||||
mod |= ModShift
|
||||
if physical == 0 || physical == KeyBacktab {
|
||||
physical = KeyTab
|
||||
}
|
||||
}
|
||||
|
||||
// Shift-Tab should be Backtab.
|
||||
if k == KeyTab && (mod&ModShift) != 0 {
|
||||
if k == KeyTab && (mod&ModShift) != 0 && !advanced {
|
||||
k = KeyBacktab
|
||||
mod &^= ModShift
|
||||
}
|
||||
ev := &EventKey{key: k, str: str, mod: mod}
|
||||
ev := &EventKey{key: k, str: str, mod: mod, pressed: pressed, physical: physical, repeat: repeat}
|
||||
ev.SetEventNow()
|
||||
return ev
|
||||
}
|
||||
|
||||
// ModMask is a mask of modifier keys. Note that it will not always be
|
||||
// possible to report modifier keys.
|
||||
type ModMask int16
|
||||
type ModMask int32
|
||||
|
||||
// These are the modifiers keys that can be sent either with a key press,
|
||||
// or a mouse event. Note that as of now, due to the confusion associated
|
||||
|
|
@ -318,6 +448,103 @@ const (
|
|||
ModNone ModMask = 0
|
||||
)
|
||||
|
||||
const (
|
||||
modLShift ModMask = 1 << (iota + 5)
|
||||
modRShift
|
||||
modLCtrl
|
||||
modRCtrl
|
||||
modLAlt
|
||||
modRAlt
|
||||
modLMeta
|
||||
modRMeta
|
||||
modLHyper
|
||||
modRHyper
|
||||
)
|
||||
|
||||
// These modifiers identify a specific side when the keyboard protocol reports
|
||||
// one. They include the aggregate modifier bit, so ModLCtrl also satisfies
|
||||
// checks for ModCtrl.
|
||||
const (
|
||||
ModLShift = ModShift | modLShift
|
||||
ModRShift = ModShift | modRShift
|
||||
ModLCtrl = ModCtrl | modLCtrl
|
||||
ModRCtrl = ModCtrl | modRCtrl
|
||||
ModLAlt = ModAlt | modLAlt
|
||||
ModRAlt = ModAlt | modRAlt
|
||||
ModLMeta = ModMeta | modLMeta
|
||||
ModRMeta = ModMeta | modRMeta
|
||||
ModLHyper = ModHyper | modLHyper
|
||||
ModRHyper = ModHyper | modRHyper
|
||||
)
|
||||
|
||||
// These keys are aliases for printable physical keys. They are primarily
|
||||
// useful with EventKey.Physical, which may report a base key location separately
|
||||
// from the generated text.
|
||||
//
|
||||
// These names identify the unshifted base key on a US-style keyboard layout.
|
||||
// They do not identify logical characters produced by modifiers or other
|
||||
// layouts. For example, the physical key named KeySlash may produce "/" or
|
||||
// "?" on a US keyboard depending on Shift, and may produce different text on
|
||||
// other layouts. Applications interested in the logical key sequence should
|
||||
// use EventKey.Key and EventKey.Str instead.
|
||||
const (
|
||||
KeySpace Key = ' '
|
||||
Key0 Key = '0'
|
||||
Key1 Key = '1'
|
||||
Key2 Key = '2'
|
||||
Key3 Key = '3'
|
||||
Key4 Key = '4'
|
||||
Key5 Key = '5'
|
||||
Key6 Key = '6'
|
||||
Key7 Key = '7'
|
||||
Key8 Key = '8'
|
||||
Key9 Key = '9'
|
||||
|
||||
KeyGrave Key = '`'
|
||||
KeyBacktick Key = KeyGrave
|
||||
KeyMinus Key = '-'
|
||||
KeyEqual Key = '='
|
||||
KeyLBrace Key = '['
|
||||
KeyLBracket Key = KeyLBrace
|
||||
KeyRBrace Key = ']'
|
||||
KeyRBracket Key = KeyRBrace
|
||||
KeyBackslash Key = '\\'
|
||||
KeySemi Key = ';'
|
||||
KeySemicolon Key = KeySemi
|
||||
KeyQuote Key = '\''
|
||||
KeyApostrophe Key = KeyQuote
|
||||
KeyComma Key = ','
|
||||
KeyPeriod Key = '.'
|
||||
KeySlash Key = '/'
|
||||
|
||||
KeyA Key = 'a'
|
||||
KeyB Key = 'b'
|
||||
KeyC Key = 'c'
|
||||
KeyD Key = 'd'
|
||||
KeyE Key = 'e'
|
||||
KeyF Key = 'f'
|
||||
KeyG Key = 'g'
|
||||
KeyH Key = 'h'
|
||||
KeyI Key = 'i'
|
||||
KeyJ Key = 'j'
|
||||
KeyK Key = 'k'
|
||||
KeyL Key = 'l'
|
||||
KeyM Key = 'm'
|
||||
KeyN Key = 'n'
|
||||
KeyO Key = 'o'
|
||||
KeyP Key = 'p'
|
||||
KeyQ Key = 'q'
|
||||
KeyR Key = 'r'
|
||||
KeyS Key = 's'
|
||||
KeyT Key = 't'
|
||||
KeyU Key = 'u'
|
||||
KeyV Key = 'v'
|
||||
KeyW Key = 'w'
|
||||
KeyX Key = 'x'
|
||||
KeyY Key = 'y'
|
||||
KeyZ Key = 'z'
|
||||
)
|
||||
|
||||
// Key is a generic value for representing keys, and especially special
|
||||
// keys (function keys, cursor movement keys, etc.) For normal keys, like
|
||||
// ASCII letters, we use KeyRune, and then expect the application to
|
||||
|
|
@ -351,6 +578,8 @@ const (
|
|||
KeyCancel
|
||||
KeyPrint
|
||||
KeyPause
|
||||
// KeyBacktab is used for legacy Shift-Tab reporting. In advanced key
|
||||
// reporting mode, Shift-Tab is reported as KeyTab with ModShift instead.
|
||||
KeyBacktab
|
||||
KeyF1
|
||||
KeyF2
|
||||
|
|
@ -420,6 +649,11 @@ const (
|
|||
KeyCapsLock
|
||||
KeyScrollLock
|
||||
KeyNumLock
|
||||
KeyShift
|
||||
KeyCtrl
|
||||
KeyAlt
|
||||
KeyMeta
|
||||
KeyHyper
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -432,6 +666,10 @@ const (
|
|||
// rune (lower case) and control modifier. If the shift key
|
||||
// or other modifiers are present then these will *NOT* be reported,
|
||||
// but reported instead as KeyRune.
|
||||
//
|
||||
// Note that these are not reported in advanced key reporting mode.
|
||||
// Instead, for advanced keys, expect KeyRune and a modifier with the
|
||||
// associated rune to be sent.
|
||||
const (
|
||||
KeyCtrlA Key = iota + 65
|
||||
KeyCtrlB
|
||||
|
|
@ -466,6 +704,10 @@ const (
|
|||
|
||||
// These are the defined ASCII values for key codes. They generally match
|
||||
// with KeyCtrl values.
|
||||
//
|
||||
// Most of these will not be reported in advanced key reporting mode, as they
|
||||
// are not possible to type directly. Some notable exceptions are KeyESC, KeyBS,
|
||||
// KeyTAB, and KeyCR, which have aliases below.
|
||||
const (
|
||||
KeyNUL Key = iota
|
||||
KeySOH
|
||||
|
|
|
|||
5
vendor/github.com/gdamore/tcell/v3/mouse.go
generated
vendored
5
vendor/github.com/gdamore/tcell/v3/mouse.go
generated
vendored
|
|
@ -49,8 +49,9 @@ func (ev *EventMouse) Modifiers() ModMask {
|
|||
return ev.mod
|
||||
}
|
||||
|
||||
// Position returns the mouse position in character cells. The origin
|
||||
// 0, 0 is at the upper left corner.
|
||||
// Position returns the mouse position. The origin 0, 0 is at the upper
|
||||
// left corner. The unit is character cells unless the screen was started
|
||||
// with MousePixelEvents, in which case the unit is terminal pixels.
|
||||
func (ev *EventMouse) Position() (int, int) {
|
||||
return ev.x, ev.y
|
||||
}
|
||||
|
|
|
|||
25
vendor/github.com/gdamore/tcell/v3/screen.go
generated
vendored
25
vendor/github.com/gdamore/tcell/v3/screen.go
generated
vendored
|
|
@ -249,6 +249,9 @@ type Screen interface {
|
|||
// supports it. Right now only terminals supporting OSC 777 support this.
|
||||
ShowNotification(title string, body string)
|
||||
|
||||
// KeyboardProtocol returns the keyboard protocol currently in use.
|
||||
KeyboardProtocol() KeyProtocol
|
||||
|
||||
// Terminal returns the terminal name and version if known. If either of these
|
||||
// are unknown, then empty strings are returned in their place. This is intended
|
||||
// to facilitate debug, and also applications that wish to enable very specific
|
||||
|
|
@ -260,7 +263,8 @@ var overrideScreen chan Screen
|
|||
var overrideOnce sync.Once
|
||||
|
||||
// NewScreen returns a default Screen suitable for the user's terminal environment.
|
||||
func NewScreen() (Screen, error) {
|
||||
// Any options are passed through to NewTerminfoScreen.
|
||||
func NewScreen(opts ...TerminfoScreenOption) (Screen, error) {
|
||||
|
||||
// Allow an application (presumably test code) to inject a replacement default
|
||||
// screen. This could also be used to create shims for things like nesting screens.
|
||||
|
|
@ -270,7 +274,7 @@ func NewScreen() (Screen, error) {
|
|||
default:
|
||||
}
|
||||
|
||||
if s, e := NewTerminfoScreen(); s != nil {
|
||||
if s, e := NewTerminfoScreen(opts...); s != nil {
|
||||
return s, nil
|
||||
} else {
|
||||
return nil, e
|
||||
|
|
@ -296,6 +300,15 @@ const (
|
|||
MouseButtonEvents = MouseFlags(1) // Click events only
|
||||
MouseDragEvents = MouseFlags(2) // Click-drag events (includes button events)
|
||||
MouseMotionEvents = MouseFlags(4) // All mouse events (includes click and drag events)
|
||||
// MousePixelEvents requests that mouse coordinates be reported in
|
||||
// terminal pixels rather than character cells (xterm SGR-Pixel mode,
|
||||
// CSI ?1016h). It is a modifier on the other mouse flags: at least one
|
||||
// of MouseButtonEvents, MouseDragEvents, or MouseMotionEvents must also
|
||||
// be set for any events to be delivered. When this mode is active,
|
||||
// EventMouse.Position() returns coordinates in pixels; the application
|
||||
// is responsible for mapping those to its own grid (e.g. via the
|
||||
// terminal's reported cell-pixel size).
|
||||
MousePixelEvents = MouseFlags(8)
|
||||
)
|
||||
|
||||
// CursorStyle represents a given cursor style, which can include the shape and
|
||||
|
|
@ -345,6 +358,7 @@ type screenImpl interface {
|
|||
GetClipboard()
|
||||
HasClipboard() bool
|
||||
ShowNotification(string, string)
|
||||
KeyboardProtocol() KeyProtocol
|
||||
Terminal() (string, string)
|
||||
|
||||
// Following methods are not part of the Screen api, but are used for interaction with
|
||||
|
|
@ -382,16 +396,19 @@ func (b *baseScreen) Put(x int, y int, str string, style Style) (remain string,
|
|||
func (b *baseScreen) PutStrStyled(x int, y int, str string, style Style) {
|
||||
cells := b.GetCells()
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
cols, rows := cells.Size()
|
||||
if cells.sanitizeContent {
|
||||
str = stripOSCControlsIfNeeded(str)
|
||||
}
|
||||
width := 0
|
||||
for str != "" && x < cols && y < rows {
|
||||
str, width = cells.Put(x, y, str, style)
|
||||
str, width = cells.put(x, y, str, style)
|
||||
if width == 0 {
|
||||
break
|
||||
}
|
||||
x += width
|
||||
}
|
||||
defer b.Unlock()
|
||||
}
|
||||
|
||||
func (b *baseScreen) PutStr(x, y int, str string) {
|
||||
|
|
|
|||
35
vendor/github.com/gdamore/tcell/v3/style.go
generated
vendored
35
vendor/github.com/gdamore/tcell/v3/style.go
generated
vendored
|
|
@ -43,6 +43,7 @@ type urlInfo struct {
|
|||
id string
|
||||
}
|
||||
|
||||
// stripOSCControls removes control bytes that can terminate OSC payloads early.
|
||||
func stripOSCControls(s string) string {
|
||||
var b strings.Builder
|
||||
b.Grow(len(s))
|
||||
|
|
@ -68,6 +69,18 @@ func stripOSCControls(s string) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
// stripOSCControlsIfNeeded returns the original string when it contains no
|
||||
// control bytes and only allocates when stripping is required.
|
||||
func stripOSCControlsIfNeeded(s string) string {
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c <= 0x1f || c == 0x7f || (c >= 0x80 && c <= 0x9f) {
|
||||
return stripOSCControls(s)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// StyleDefault represents a default style, based upon the context.
|
||||
// It is the zero value.
|
||||
var StyleDefault Style
|
||||
|
|
@ -102,10 +115,15 @@ func (s Style) setAttrs(attrs AttrMask, on bool) Style {
|
|||
}
|
||||
|
||||
// Normal returns the style with all attributes disabled.
|
||||
// Colors are preserved, as are hyperlinks. (Underline color
|
||||
// will also be preserved, but no underline is currently shown.
|
||||
// Apart from color, the underline style is reset as well.)
|
||||
func (s Style) Normal() Style {
|
||||
return Style{
|
||||
fg: s.fg,
|
||||
bg: s.bg,
|
||||
fg: s.fg,
|
||||
bg: s.bg,
|
||||
ulColor: s.ulColor,
|
||||
url: s.url,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -227,10 +245,13 @@ func (s Style) GetAttributes() AttrMask {
|
|||
func (s Style) Url(url string) Style {
|
||||
|
||||
s2 := s
|
||||
s2.url = &urlInfo{url: stripOSCControls(url)}
|
||||
s2.url = &urlInfo{url: stripOSCControlsIfNeeded(url)}
|
||||
if s.url != nil {
|
||||
s2.url.id = s.url.id
|
||||
}
|
||||
if s2.url.url == "" && s2.url.id == "" {
|
||||
s2.url = nil
|
||||
}
|
||||
return s2
|
||||
}
|
||||
|
||||
|
|
@ -240,12 +261,16 @@ func (s Style) Url(url string) Style {
|
|||
// were one Url, even if it spans multiple lines.
|
||||
func (s Style) UrlId(id string) Style {
|
||||
s2 := s
|
||||
s2.url = &urlInfo{
|
||||
id: "id=" + stripOSCControls(id),
|
||||
s2.url = &urlInfo{}
|
||||
if id = stripOSCControlsIfNeeded(id); id != "" {
|
||||
s2.url.id = "id=" + id
|
||||
}
|
||||
if s.url != nil {
|
||||
s2.url.url = s.url.url
|
||||
}
|
||||
if s2.url.url == "" && s2.url.id == "" {
|
||||
s2.url = nil
|
||||
}
|
||||
return s2
|
||||
}
|
||||
|
||||
|
|
|
|||
504
vendor/github.com/gdamore/tcell/v3/tscreen.go
generated
vendored
504
vendor/github.com/gdamore/tcell/v3/tscreen.go
generated
vendored
|
|
@ -12,8 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !js && !wasm
|
||||
// +build !js,!wasm
|
||||
//go:build (!js && !wasm) || (js && wasm)
|
||||
// +build !js,!wasm js,wasm
|
||||
|
||||
package tcell
|
||||
|
||||
|
|
@ -83,6 +83,53 @@ func (o OptAltScreen) apply(t *tScreen) {
|
|||
t.altScreen = bool(o)
|
||||
}
|
||||
|
||||
// OptSanitizeContent enables stripping control characters from content passed
|
||||
// to Put and PutStr. This is safer, but a little slower than leaving content
|
||||
// unsanitized.
|
||||
type OptSanitizeContent bool
|
||||
|
||||
func (o OptSanitizeContent) apply(t *tScreen) {
|
||||
t.cells.sanitizeContent = bool(o)
|
||||
}
|
||||
|
||||
// OptAdvancedKeys enables richer key reporting where supported. In this mode
|
||||
// key events may include release state, repeat counts, and physical keys, and
|
||||
// ASCII control letters are reported as KeyRune with ModCtrl instead of
|
||||
// KeyCtrlA through KeyCtrlZ. Shift-Tab is reported as KeyTab with ModShift,
|
||||
// rather than KeyBacktab.
|
||||
type OptAdvancedKeys bool
|
||||
|
||||
func (o OptAdvancedKeys) apply(t *tScreen) {
|
||||
t.advancedKeys = bool(o)
|
||||
}
|
||||
|
||||
// OptKeyboardProtocol forces the keyboard reporting protocol instead of using
|
||||
// startup negotiation. The zero value forces legacy keyboard reporting.
|
||||
type OptKeyboardProtocol KeyProtocol
|
||||
|
||||
func (o OptKeyboardProtocol) apply(t *tScreen) {
|
||||
t.forceKeyboardProtocol(KeyProtocol(o))
|
||||
}
|
||||
|
||||
// OptNegotiation controls whether terminal capabilities are negotiated during
|
||||
// startup. The default is true.
|
||||
type OptNegotiation bool
|
||||
|
||||
func (o OptNegotiation) apply(t *tScreen) {
|
||||
t.negotiate = bool(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
|
||||
// protocol used for writing clipboard data through the terminal. The default is
|
||||
// 64 KiB; a value of 0 disables the limit.
|
||||
type OptControlStringLimit int
|
||||
|
||||
func (o OptControlStringLimit) apply(t *tScreen) {
|
||||
t.controlStringLimit = max(int(o), 0)
|
||||
}
|
||||
|
||||
// Some terminal escapes that are basically universal.
|
||||
// We would really like to be able to use private mode queries for some of
|
||||
// these but generally we've found that support for queries is not always present,
|
||||
|
|
@ -132,6 +179,7 @@ const (
|
|||
notifyDesktop777 = "\x1b]777;notify;%s;%s\x1b\\" // Most commonly supported
|
||||
queryKittyKbd = "\x1b[?u" // Query for Kitty keyboard support
|
||||
enableKittyKbd = "\x1b[=1u" // Technically this pushes
|
||||
enableKittyKbdAdv = "\x1b[=15u" // disambiguation, events, alternate keys, all keys
|
||||
disableKittyKbd = "\x1b[=0u" // Technically this means pop previous mode
|
||||
queryXTermKbd = "\x1b[?4m" // Query for XTerm modify other keys support
|
||||
enableXTermKbd = "\x1b[>4;2m" // Enable modify other keys protocol
|
||||
|
|
@ -143,7 +191,12 @@ const (
|
|||
// is presumed, at least on UNIX hosts. (Windows hosts will typically fail this
|
||||
// call altogether.)
|
||||
func NewTerminfoScreenFromTty(tty Tty, opts ...TerminfoScreenOption) (Screen, error) {
|
||||
t := &tScreen{tty: tty, altScreen: true}
|
||||
t := &tScreen{
|
||||
tty: tty,
|
||||
altScreen: true,
|
||||
negotiate: true,
|
||||
controlStringLimit: defaultControlStringLimit,
|
||||
}
|
||||
|
||||
t.prepareCursorStyles()
|
||||
t.prepareExtendedOSC()
|
||||
|
|
@ -160,71 +213,77 @@ func NewTerminfoScreenFromTty(tty Tty, opts ...TerminfoScreenOption) (Screen, er
|
|||
|
||||
// tScreen represents a screen backed by a terminfo implementation.
|
||||
type tScreen struct {
|
||||
tty Tty
|
||||
h int
|
||||
w int
|
||||
fini bool
|
||||
cells CellBuffer
|
||||
buffering bool // true if we are collecting writes to buf instead of sending directly to out
|
||||
buf bytes.Buffer
|
||||
curstyle Style
|
||||
style Style
|
||||
resizeQ chan bool
|
||||
quit chan struct{}
|
||||
keyQ chan []byte
|
||||
cx int
|
||||
cy int
|
||||
cls bool // clear screen
|
||||
cursorx int
|
||||
cursory int
|
||||
acs map[rune]string
|
||||
charset string
|
||||
encoder transform.Transformer
|
||||
decoder transform.Transformer
|
||||
fallback map[rune]string
|
||||
ncolor int
|
||||
colors map[color.Color]color.Color
|
||||
palette []color.Color
|
||||
truecolor bool
|
||||
noColor bool
|
||||
legacy bool
|
||||
hasClipboard bool // true if OSC 52 reported via DA1
|
||||
finiOnce sync.Once
|
||||
enterUrl string
|
||||
exitUrl string
|
||||
setWinSize string
|
||||
cursorStyles map[CursorStyle]string
|
||||
cursorStyle CursorStyle
|
||||
cursorColor color.Color
|
||||
cursorRGB string
|
||||
cursorFg string
|
||||
stopQ chan struct{}
|
||||
eventQ chan Event
|
||||
initQ chan Event
|
||||
initted bool
|
||||
running bool
|
||||
startTime time.Time
|
||||
wg sync.WaitGroup
|
||||
mouseFlags MouseFlags
|
||||
pasteEnabled bool
|
||||
focusEnabled bool
|
||||
setTitle string
|
||||
saveTitle string
|
||||
restoreTitle string
|
||||
title string
|
||||
setClipboard string
|
||||
notifyDesktop string
|
||||
termName string
|
||||
termVers string
|
||||
term string // value from $TERM
|
||||
altScreen bool
|
||||
inlineResize bool
|
||||
haveMouse bool
|
||||
haveMouseSgr bool
|
||||
haveKittyKbd bool
|
||||
haveWin32Kbd bool
|
||||
haveXTermKbd bool
|
||||
input *inputParser
|
||||
tty Tty
|
||||
h int
|
||||
w int
|
||||
fini bool
|
||||
cells CellBuffer
|
||||
buffering bool // true if we are collecting writes to buf instead of sending directly to out
|
||||
buf bytes.Buffer
|
||||
curstyle Style
|
||||
style Style
|
||||
resizeQ chan bool
|
||||
quit chan struct{}
|
||||
keyQ chan []byte
|
||||
cx int
|
||||
cy int
|
||||
cls bool // clear screen
|
||||
cursorx int
|
||||
cursory int
|
||||
acs map[rune]string
|
||||
charset string
|
||||
encoder transform.Transformer
|
||||
decoder transform.Transformer
|
||||
fallback map[rune]string
|
||||
ncolor int
|
||||
colors map[color.Color]color.Color
|
||||
palette []color.Color
|
||||
truecolor bool
|
||||
noColor bool
|
||||
legacy bool
|
||||
hasClipboard bool // true if OSC 52 reported via DA1
|
||||
finiOnce sync.Once
|
||||
enterUrl string
|
||||
exitUrl string
|
||||
setWinSize string
|
||||
cursorStyles map[CursorStyle]string
|
||||
cursorStyle CursorStyle
|
||||
cursorColor color.Color
|
||||
cursorRGB string
|
||||
cursorFg string
|
||||
stopQ chan struct{}
|
||||
eventQ chan Event
|
||||
initQ chan Event
|
||||
initted bool
|
||||
running bool
|
||||
startTime time.Time
|
||||
wg sync.WaitGroup
|
||||
mouseFlags MouseFlags
|
||||
pasteEnabled bool
|
||||
focusEnabled bool
|
||||
setTitle string
|
||||
saveTitle string
|
||||
restoreTitle string
|
||||
title string
|
||||
setClipboard string
|
||||
notifyDesktop string
|
||||
termName string
|
||||
termVers string
|
||||
term string // value from $TERM
|
||||
altScreen bool
|
||||
inlineResize bool
|
||||
haveMouse bool
|
||||
haveMouseSgr bool
|
||||
haveKittyKbd bool
|
||||
haveWin32Kbd bool
|
||||
haveXTermKbd bool
|
||||
forcedKbd KeyProtocol
|
||||
forceKbd bool
|
||||
negotiate bool
|
||||
mouseDisabled bool
|
||||
advancedKeys bool
|
||||
controlStringLimit int
|
||||
input *inputParser
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
|
|
@ -232,6 +291,69 @@ func (t *tScreen) useAltScreen() bool {
|
|||
return t.altScreen && os.Getenv("TCELL_ALTSCREEN") != "disable"
|
||||
}
|
||||
|
||||
func validKeyboardProtocol(p KeyProtocol) bool {
|
||||
switch p {
|
||||
case LegacyKeyboard, KittyKeyboard, Win32Keyboard, XTermKeyboard:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func parseKeyboardProtocol(s string) (KeyProtocol, bool) {
|
||||
switch s {
|
||||
case "legacy":
|
||||
return LegacyKeyboard, true
|
||||
case "kitty":
|
||||
return KittyKeyboard, true
|
||||
case "win32":
|
||||
return Win32Keyboard, true
|
||||
case "xterm":
|
||||
return XTermKeyboard, true
|
||||
default:
|
||||
return LegacyKeyboard, false
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tScreen) forceKeyboardProtocol(p KeyProtocol) bool {
|
||||
if !validKeyboardProtocol(p) {
|
||||
return false
|
||||
}
|
||||
t.forcedKbd = p
|
||||
t.forceKbd = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *tScreen) applyKeyboardProtocolOverride() {
|
||||
if !t.forceKbd {
|
||||
return
|
||||
}
|
||||
t.haveKittyKbd = t.forcedKbd == KittyKeyboard
|
||||
t.haveWin32Kbd = t.forcedKbd == Win32Keyboard
|
||||
t.haveXTermKbd = t.forcedKbd == XTermKeyboard
|
||||
}
|
||||
|
||||
func (t *tScreen) applyEnvironmentOverrides() {
|
||||
switch os.Getenv("TCELL_KEYBOARD_PROTOCOL") {
|
||||
case "auto":
|
||||
t.forceKbd = false
|
||||
case "":
|
||||
default:
|
||||
if p, ok := parseKeyboardProtocol(os.Getenv("TCELL_KEYBOARD_PROTOCOL")); ok {
|
||||
t.forceKeyboardProtocol(p)
|
||||
}
|
||||
}
|
||||
|
||||
switch os.Getenv("TCELL_NEGOTIATE") {
|
||||
case "auto":
|
||||
t.negotiate = true
|
||||
case "disable":
|
||||
t.negotiate = false
|
||||
}
|
||||
|
||||
t.mouseDisabled = os.Getenv("TCELL_MOUSE") == "disable"
|
||||
}
|
||||
|
||||
func (t *tScreen) Init() error {
|
||||
if e := t.initialize(); e != nil {
|
||||
return e
|
||||
|
|
@ -307,11 +429,15 @@ func (t *tScreen) Init() error {
|
|||
t.legacy = true
|
||||
}
|
||||
|
||||
t.applyEnvironmentOverrides()
|
||||
|
||||
t.initted = false
|
||||
t.quit = make(chan struct{})
|
||||
t.initQ = make(chan Event, 32)
|
||||
t.eventQ = make(chan Event, 128)
|
||||
t.input = newInputParser(t.filterEvents())
|
||||
t.input.advanced = t.advancedKeys
|
||||
t.input.controlStringMax = t.controlStringLimit
|
||||
|
||||
t.Lock()
|
||||
t.cx = -1
|
||||
|
|
@ -480,6 +606,9 @@ func (t *tScreen) Fini() {
|
|||
}
|
||||
|
||||
func (t *tScreen) finish() {
|
||||
t.Lock()
|
||||
t.fini = true
|
||||
t.Unlock()
|
||||
close(t.quit)
|
||||
t.finalize()
|
||||
}
|
||||
|
|
@ -664,6 +793,13 @@ func (t *tScreen) emitUrl(u urlInfo) {
|
|||
}
|
||||
}
|
||||
|
||||
// urlNeedsEmission reports whether a hyperlink transition has any wire effect.
|
||||
// Url ids can be staged before the Url itself, and id-only transitions have no
|
||||
// OSC 8 representation of their own.
|
||||
func urlNeedsEmission(oldUrl, newUrl urlInfo) bool {
|
||||
return oldUrl != newUrl && (oldUrl.url != "" || newUrl.url != "")
|
||||
}
|
||||
|
||||
func (t *tScreen) drawCell(x, y int) int {
|
||||
|
||||
str, style, width := t.cells.Get(x, y)
|
||||
|
|
@ -697,8 +833,8 @@ func (t *tScreen) drawCell(x, y int) int {
|
|||
if style.url != nil {
|
||||
newUrl = *style.url
|
||||
}
|
||||
// URL string can be long, so don't send it unless we really need to
|
||||
if newUrl != oldUrl {
|
||||
// URL string can be long, so don't send it unless we really need to.
|
||||
if urlNeedsEmission(oldUrl, newUrl) {
|
||||
t.emitUrl(newUrl)
|
||||
}
|
||||
|
||||
|
|
@ -746,6 +882,10 @@ func (t *tScreen) drawCell(x, y int) int {
|
|||
|
||||
func (t *tScreen) ShowCursor(x, y int) {
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
t.cursorx = x
|
||||
t.cursory = y
|
||||
t.Unlock()
|
||||
|
|
@ -753,6 +893,10 @@ func (t *tScreen) ShowCursor(x, y int) {
|
|||
|
||||
func (t *tScreen) SetCursor(cs CursorStyle, cc Color) {
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
t.cursorStyle = cs
|
||||
t.cursorColor = cc
|
||||
t.Unlock()
|
||||
|
|
@ -876,6 +1020,10 @@ func (t *tScreen) draw() {
|
|||
}
|
||||
}
|
||||
|
||||
if t.curstyle.url != nil && t.curstyle.url.url != "" {
|
||||
t.emitUrl(urlInfo{})
|
||||
}
|
||||
|
||||
// restore the cursor
|
||||
t.showCursor()
|
||||
|
||||
|
|
@ -894,6 +1042,10 @@ func (t *tScreen) EnableMouse(flags ...MouseFlags) {
|
|||
}
|
||||
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
t.mouseFlags = f
|
||||
t.enableMouse(f)
|
||||
t.Unlock()
|
||||
|
|
@ -912,7 +1064,10 @@ func (t *tScreen) enableMouse(f MouseFlags) {
|
|||
// so we enable the mouse unconditionally unless we get a report
|
||||
// that says we have mouse, but not SGR mouse. This is suboptimal, but
|
||||
// a concession forced by the sorry state of terminal emulators.
|
||||
if t.haveMouse && !t.haveMouseSgr {
|
||||
if t.mouseDisabled {
|
||||
f = 0
|
||||
}
|
||||
if f != 0 && t.haveMouse && !t.haveMouseSgr {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -921,6 +1076,10 @@ 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())
|
||||
|
||||
pixel := f&MousePixelEvents != 0
|
||||
t.input.SetPixelMouse(pixel)
|
||||
|
||||
if f&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) != 0 {
|
||||
t.Print(vt.PmMouseButton.Enable())
|
||||
|
|
@ -932,12 +1091,20 @@ func (t *tScreen) enableMouse(f MouseFlags) {
|
|||
t.Print(vt.PmMouseMotion.Enable())
|
||||
}
|
||||
if f&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) != 0 {
|
||||
t.Print(vt.PmMouseSgr.Enable())
|
||||
if pixel {
|
||||
t.Print(vt.PmMouseSgrPixel.Enable())
|
||||
} else {
|
||||
t.Print(vt.PmMouseSgr.Enable())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tScreen) DisableMouse() {
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
t.mouseFlags = 0
|
||||
t.enableMouse(0)
|
||||
t.Unlock()
|
||||
|
|
@ -945,6 +1112,10 @@ func (t *tScreen) DisableMouse() {
|
|||
|
||||
func (t *tScreen) EnablePaste() {
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
t.pasteEnabled = true
|
||||
t.enablePasting(true)
|
||||
t.Unlock()
|
||||
|
|
@ -952,6 +1123,10 @@ func (t *tScreen) EnablePaste() {
|
|||
|
||||
func (t *tScreen) DisablePaste() {
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
t.pasteEnabled = false
|
||||
t.enablePasting(false)
|
||||
t.Unlock()
|
||||
|
|
@ -971,6 +1146,10 @@ func (t *tScreen) enablePasting(on bool) {
|
|||
|
||||
func (t *tScreen) EnableFocus() {
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
t.focusEnabled = true
|
||||
t.enableFocusReporting()
|
||||
t.Unlock()
|
||||
|
|
@ -978,6 +1157,10 @@ func (t *tScreen) EnableFocus() {
|
|||
|
||||
func (t *tScreen) DisableFocus() {
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
t.focusEnabled = false
|
||||
t.disableFocusReporting()
|
||||
t.Unlock()
|
||||
|
|
@ -1189,6 +1372,11 @@ func (t *tScreen) UnregisterRuneFallback(orig rune) {
|
|||
}
|
||||
|
||||
func (t *tScreen) SetSize(w, h int) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if t.fini {
|
||||
return
|
||||
}
|
||||
if t.setWinSize != "" {
|
||||
t.Printf(t.setWinSize, w, h)
|
||||
}
|
||||
|
|
@ -1199,24 +1387,86 @@ func (t *tScreen) SetSize(w, h int) {
|
|||
func (t *tScreen) Resize(int, int, int, int) {}
|
||||
|
||||
func (t *tScreen) Suspend() error {
|
||||
t.disengage()
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return nil
|
||||
}
|
||||
finish := t.disengageStart()
|
||||
t.Unlock()
|
||||
if finish {
|
||||
t.disengageFinish()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tScreen) Resume() error {
|
||||
return t.engage()
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if t.fini {
|
||||
return nil
|
||||
}
|
||||
return t.engageLocked()
|
||||
}
|
||||
|
||||
func (t *tScreen) Tty() (Tty, bool) {
|
||||
return t.tty, true
|
||||
}
|
||||
|
||||
func (t *tScreen) applyKnownTerminalProfile(goos, termProgram string) bool {
|
||||
switch termProgram {
|
||||
case "Apple_Terminal":
|
||||
// macOS Terminal.app cannot handle the startup queries, but it does
|
||||
// support modern mouse reporting.
|
||||
t.haveMouse = true
|
||||
t.haveMouseSgr = true
|
||||
t.termName = "Terminal.app"
|
||||
t.termVers = os.Getenv("TERM_PROGRAM_VERSION")
|
||||
return true
|
||||
case "WezTerm":
|
||||
// The WezTerm keyboard protocol to use is in theory driven by its
|
||||
// own configuration, but we have found this unreliable because it
|
||||
// does not mask unsupported capabilities. Furthermore, on Windows
|
||||
// builds the kitty protocol implementation is broken, while on other
|
||||
// builds win32-input-mode is broken. This is a best effort to make
|
||||
// WezTerm work reasonably; our stronger advice is to choose another
|
||||
// terminal program altogether. This workaround will probably not
|
||||
// apply to ssh sessions, as TERM_PROGRAM is not normally propagated.
|
||||
if goos == "windows" {
|
||||
t.haveWin32Kbd = true
|
||||
} else {
|
||||
t.haveKittyKbd = true
|
||||
t.haveWin32Kbd = false
|
||||
}
|
||||
t.haveMouse = true
|
||||
t.haveMouseSgr = true
|
||||
t.initted = true
|
||||
t.termName = "WezTerm"
|
||||
t.termVers = os.Getenv("TERM_PROGRAM_VERSION")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func useVTWindowSizeQuery(goos string) bool {
|
||||
return goos != "windows"
|
||||
}
|
||||
|
||||
func useXTermKeyboardQuery(goos string) bool {
|
||||
return goos != "windows"
|
||||
}
|
||||
|
||||
// engage is used to place the terminal in raw mode and establish screen size, etc.
|
||||
// Think of this is as tcell "engaging" the clutch, as it's going to be driving the
|
||||
// terminal interface.
|
||||
func (t *tScreen) engage() error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
return t.engageLocked()
|
||||
}
|
||||
|
||||
// engageLocked is engage's implementation when t's lock is already held.
|
||||
func (t *tScreen) engageLocked() error {
|
||||
if t.tty == nil {
|
||||
return ErrNoScreen
|
||||
}
|
||||
|
|
@ -1234,24 +1484,37 @@ func (t *tScreen) engage() error {
|
|||
go t.mainLoop(stopQ)
|
||||
|
||||
if !t.initted {
|
||||
t.Print(requestWindowSize)
|
||||
// macOS Terminal.app is brain damaged
|
||||
// https://garrett.damore.org/2025/12/macos-terminal-still-missing-mark-apple.html
|
||||
// Eventually they'll hopefully fix this. As the environment variable
|
||||
// does not convey by default via ssh, remote sessions might see spurious characters
|
||||
// emitted during startup. See the blog post for alternatives.
|
||||
if os.Getenv("TERM_PROGRAM") != "Apple_Terminal" {
|
||||
if !t.applyKnownTerminalProfile(runtime.GOOS, os.Getenv("TERM_PROGRAM")) && t.negotiate {
|
||||
if useVTWindowSizeQuery(runtime.GOOS) {
|
||||
t.Print(requestWindowSize)
|
||||
}
|
||||
t.Print(vt.PmResizeReports.Query())
|
||||
t.Print(vt.PmMouseButton.Query())
|
||||
t.Print(vt.PmMouseSgr.Query())
|
||||
t.Print(vt.PmWin32Input.Query())
|
||||
t.Print(queryKittyKbd)
|
||||
t.Print(queryXTermKbd)
|
||||
if !t.forceKbd {
|
||||
t.Print(vt.PmWin32Input.Query())
|
||||
t.Print(queryKittyKbd)
|
||||
if useXTermKeyboardQuery(runtime.GOOS) {
|
||||
// XTerm's modifyOtherKeys mode is mainly useful for XTerm
|
||||
// itself, and we do not use it on Windows.
|
||||
t.Print(queryXTermKbd)
|
||||
}
|
||||
}
|
||||
t.Print(requestExtAttr)
|
||||
}
|
||||
t.Print(requestPrimaryDA) // NB: MUST BE LAST
|
||||
if !t.negotiate {
|
||||
t.initted = true
|
||||
} else if !t.initted {
|
||||
t.Print(requestPrimaryDA) // NB: MUST BE LAST
|
||||
}
|
||||
}
|
||||
t.processInitQ()
|
||||
t.applyKeyboardProtocolOverride()
|
||||
if t.useAltScreen() {
|
||||
// Technically this may not be right, but every terminal we know about
|
||||
// (even Wyse 60) uses this to enter the alternate screen buffer, and
|
||||
|
|
@ -1264,7 +1527,11 @@ func (t *tScreen) engage() error {
|
|||
if t.haveWin32Kbd {
|
||||
t.Print(vt.PmWin32Input.Enable())
|
||||
} else if t.haveKittyKbd {
|
||||
t.Print(enableKittyKbd)
|
||||
if t.advancedKeys {
|
||||
t.Print(enableKittyKbdAdv)
|
||||
} else {
|
||||
t.Print(enableKittyKbd)
|
||||
}
|
||||
} else if t.haveXTermKbd {
|
||||
t.Print(enableXTermKbd)
|
||||
}
|
||||
|
|
@ -1286,17 +1553,9 @@ func (t *tScreen) engage() error {
|
|||
if t.title != "" && t.setTitle != "" {
|
||||
t.Printf(t.setTitle, t.title)
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
// This workaround exists because of what we believe to be bugs in the
|
||||
// interaction between ConPTY, the VT-Input layer, and some terminal emulators
|
||||
// such as WezTerm. Note that it is *not* needed for Windows Terminal, but
|
||||
// should be benign there. As another note, we have observed that at least Alacritty
|
||||
// and WezTerm do not properly handle the primaryDA query on these platforms.
|
||||
// (WezTerm performs much better when running a remote shell or on macOS.)
|
||||
t.Print(enableKittyKbd)
|
||||
t.Print(vt.PmWin32Input.Enable())
|
||||
if t.negotiate && useVTWindowSizeQuery(runtime.GOOS) {
|
||||
t.Print(requestWindowSize)
|
||||
}
|
||||
t.Print(requestWindowSize)
|
||||
|
||||
if t.inlineResize {
|
||||
t.Print(vt.PmResizeReports.Enable())
|
||||
|
|
@ -1311,11 +1570,19 @@ func (t *tScreen) engage() error {
|
|||
// can take over the terminal interface. This restores the TTY mode that was
|
||||
// present when the application was first started.
|
||||
func (t *tScreen) disengage() {
|
||||
|
||||
t.Lock()
|
||||
finish := t.disengageStart()
|
||||
t.Unlock()
|
||||
if finish {
|
||||
t.disengageFinish()
|
||||
}
|
||||
}
|
||||
|
||||
// disengageStart begins a disengage operation while t's lock is already held.
|
||||
// It returns true when disengageFinish must be called after releasing the lock.
|
||||
func (t *tScreen) disengageStart() bool {
|
||||
if !t.running {
|
||||
t.Unlock()
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
t.running = false
|
||||
|
|
@ -1327,8 +1594,12 @@ func (t *tScreen) disengage() {
|
|||
stopQ := t.stopQ
|
||||
close(stopQ)
|
||||
_ = t.tty.Drain()
|
||||
t.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
// disengageFinish completes a disengage operation after disengageStart has
|
||||
// released the running loops.
|
||||
func (t *tScreen) disengageFinish() {
|
||||
// wait for everything to shut down
|
||||
t.wg.Wait()
|
||||
|
||||
|
|
@ -1357,7 +1628,6 @@ func (t *tScreen) disengage() {
|
|||
// Hack for Windows.
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Print(vt.PmWin32Input.Disable())
|
||||
t.Print(disableKittyKbd)
|
||||
}
|
||||
|
||||
// t.Print(t.disableCsiU)
|
||||
|
|
@ -1375,6 +1645,11 @@ func (t *tScreen) disengage() {
|
|||
|
||||
// Beep emits a beep to the terminal.
|
||||
func (t *tScreen) Beep() error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if t.fini {
|
||||
return nil
|
||||
}
|
||||
t.Print(string(byte(7)))
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1401,9 +1676,13 @@ func (t *tScreen) GetCells() *CellBuffer {
|
|||
|
||||
func (t *tScreen) SetTitle(title string) {
|
||||
t.Lock()
|
||||
t.title = title
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
t.title = stripOSCControlsIfNeeded(title)
|
||||
if t.setTitle != "" && t.running {
|
||||
t.Printf(t.setTitle, title)
|
||||
t.Printf(t.setTitle, t.title)
|
||||
}
|
||||
t.Unlock()
|
||||
}
|
||||
|
|
@ -1411,6 +1690,10 @@ func (t *tScreen) SetTitle(title string) {
|
|||
func (t *tScreen) SetClipboard(data []byte) {
|
||||
// Post binary data to the system clipboard. It might be UTF-8, it might not be.
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
if t.setClipboard != "" {
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
t.Printf(t.setClipboard, encoded)
|
||||
|
|
@ -1420,6 +1703,10 @@ func (t *tScreen) SetClipboard(data []byte) {
|
|||
|
||||
func (t *tScreen) GetClipboard() {
|
||||
t.Lock()
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
if t.setClipboard != "" {
|
||||
t.Printf(t.setClipboard, "?")
|
||||
}
|
||||
|
|
@ -1432,7 +1719,11 @@ func (t *tScreen) HasClipboard() bool {
|
|||
|
||||
func (t *tScreen) ShowNotification(title string, body string) {
|
||||
t.Lock()
|
||||
t.Printf(t.notifyDesktop, title, body)
|
||||
if t.fini {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
t.Printf(t.notifyDesktop, stripOSCControlsIfNeeded(title), stripOSCControlsIfNeeded(body))
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
|
|
@ -1441,3 +1732,18 @@ func (t *tScreen) Terminal() (string, string) {
|
|||
defer t.Unlock()
|
||||
return t.termName, t.termVers
|
||||
}
|
||||
|
||||
func (t *tScreen) KeyboardProtocol() KeyProtocol {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if t.haveWin32Kbd {
|
||||
return Win32Keyboard
|
||||
}
|
||||
if t.haveKittyKbd {
|
||||
return KittyKeyboard
|
||||
}
|
||||
if t.haveXTermKbd {
|
||||
return XTermKeyboard
|
||||
}
|
||||
return LegacyKeyboard
|
||||
}
|
||||
|
|
|
|||
29
vendor/github.com/gdamore/tcell/v3/tty/tty_win.go
generated
vendored
29
vendor/github.com/gdamore/tcell/v3/tty/tty_win.go
generated
vendored
|
|
@ -189,6 +189,10 @@ func (w *winTty) getConsoleInput() error {
|
|||
rv, _, er := procGetNumberOfConsoleInputEvents.Call(
|
||||
uintptr(w.in),
|
||||
uintptr(unsafe.Pointer(&nrec)))
|
||||
if rv == 0 {
|
||||
return er
|
||||
}
|
||||
|
||||
rec := make([]inputRecord, max(nrec, 1))
|
||||
rv, _, er = procReadConsoleInput.Call(
|
||||
uintptr(w.in),
|
||||
|
|
@ -205,22 +209,15 @@ func (w *winTty) getConsoleInput() error {
|
|||
case keyEvent:
|
||||
// we normally only expect to see ascii, but paste data may come in as UTF-16.
|
||||
wc := rune(binary.LittleEndian.Uint16(ir.data[10:]))
|
||||
if wc >= 0xD800 && wc <= 0xDBFF {
|
||||
// if it was a high surrogate, which happens for pasted UTF-16,
|
||||
// then save it until we get the low and can decode it.
|
||||
w.surrogate = wc
|
||||
continue
|
||||
} else if wc >= 0xDC00 && wc <= 0xDFFF {
|
||||
wc = utf16.DecodeRune(w.surrogate, wc)
|
||||
}
|
||||
w.surrogate = 0
|
||||
for _, chr := range []byte(string(wc)) {
|
||||
// We normally expect only to see ASCII (win32-input-mode),
|
||||
// but apparently pasted data can arrive in UTF-16 here.
|
||||
select {
|
||||
case w.buf <- chr:
|
||||
case <-w.stopQ:
|
||||
break loop
|
||||
for _, decoded := range decodeUTF16Rune(&w.surrogate, wc) {
|
||||
for _, chr := range []byte(string(decoded)) {
|
||||
// We normally expect only to see ASCII (win32-input-mode),
|
||||
// but apparently pasted data can arrive in UTF-16 here.
|
||||
select {
|
||||
case w.buf <- chr:
|
||||
case <-w.stopQ:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
47
vendor/github.com/gdamore/tcell/v3/tty/utf16.go
generated
vendored
Normal file
47
vendor/github.com/gdamore/tcell/v3/tty/utf16.go
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// 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 tty
|
||||
|
||||
import (
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// decodeUTF16Rune decodes one UTF-16 code unit at a time while preserving
|
||||
// malformed input as replacement characters instead of silently discarding it.
|
||||
func decodeUTF16Rune(surrogate *rune, wc rune) []rune {
|
||||
switch {
|
||||
case wc >= 0xD800 && wc <= 0xDBFF:
|
||||
if *surrogate != 0 {
|
||||
*surrogate = wc
|
||||
return []rune{utf8.RuneError}
|
||||
}
|
||||
*surrogate = wc
|
||||
return nil
|
||||
case wc >= 0xDC00 && wc <= 0xDFFF:
|
||||
if *surrogate == 0 {
|
||||
return []rune{utf8.RuneError}
|
||||
}
|
||||
decoded := utf16.DecodeRune(*surrogate, wc)
|
||||
*surrogate = 0
|
||||
return []rune{decoded}
|
||||
default:
|
||||
if *surrogate != 0 {
|
||||
*surrogate = 0
|
||||
return []rune{utf8.RuneError, wc}
|
||||
}
|
||||
return []rune{wc}
|
||||
}
|
||||
}
|
||||
97
vendor/github.com/gdamore/tcell/v3/vt/emulate.go
generated
vendored
97
vendor/github.com/gdamore/tcell/v3/vt/emulate.go
generated
vendored
|
|
@ -220,10 +220,25 @@ type Cell struct {
|
|||
W int // Display width (0, 1, or 2)
|
||||
}
|
||||
|
||||
// EmulatorOpt configures an Emulator.
|
||||
type EmulatorOpt interface {
|
||||
setEmulatorOpt(*emulator)
|
||||
}
|
||||
|
||||
// EmulatorOpt8BitControls enables parsing of C1 controls, such as CSI and OSC,
|
||||
// when they are presented as raw 8-bit bytes or UTF-8 encoded C1 controls.
|
||||
// The default is to only accept the 7-bit ESC-prefixed forms.
|
||||
type EmulatorOpt8BitControls struct{}
|
||||
|
||||
func (EmulatorOpt8BitControls) setEmulatorOpt(em *emulator) {
|
||||
em.c1Allowed = true
|
||||
em.c1Enabled = true
|
||||
}
|
||||
|
||||
// NewEmulator creates an emulator instance on top of the given backend.
|
||||
// The input is relative to the emulator, so it receives data from the host,
|
||||
// whereas the emulator sends data to the application through the output.
|
||||
func NewEmulator(be Backend) Emulator {
|
||||
func NewEmulator(be Backend, opts ...EmulatorOpt) Emulator {
|
||||
stopQ := make(chan bool)
|
||||
defStyle := BaseStyle.WithFg(color.Silver).WithBg(color.Black)
|
||||
em := &emulator{
|
||||
|
|
@ -249,6 +264,9 @@ func NewEmulator(be Backend) Emulator {
|
|||
},
|
||||
mouseReports: MouseDisabled,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.setEmulatorOpt(em)
|
||||
}
|
||||
if _, ok := be.(Resizer); ok {
|
||||
em.localModes[PmResizeReports] = ModeOff
|
||||
}
|
||||
|
|
@ -297,7 +315,9 @@ type emulator struct {
|
|||
pos Coord
|
||||
buffering uint // reference count - number of (re-entrant) buffering calls
|
||||
autoWrap bool // next character will wrap (auto margin, deferred until char emitted)
|
||||
sevenOnly bool // only allow 7-bit escapes (needed for KOI8, ShiftJIS, etc.)
|
||||
c1Allowed bool // allow C1 controls in raw 8-bit and UTF-8 encodings
|
||||
c1Enabled bool // C1 controls are currently enabled
|
||||
c1Prefix bool // string parser has seen the first byte of a UTF-8 encoded C1 control
|
||||
appKeyPad bool // use application key pad keys?
|
||||
name string // name of this emulator (used for extended attributes)
|
||||
vers string // version string of this emulator (used for extended attributes)
|
||||
|
|
@ -372,13 +392,13 @@ func (em *emulator) inbInit(b byte) {
|
|||
return
|
||||
}
|
||||
|
||||
// For 8-bit encodings, we treat these as Fe sequences.
|
||||
// Basically the same as ESC followed by (b - 0x40).
|
||||
// TODO: condition this so that we do not do this if
|
||||
// the encoding cannot support it (UTF, 8859, and EUC encodings
|
||||
// are all fine here, but others like ShiftJIS or KOI8 might not be).
|
||||
if b >= 0x80 && b <= 0x9F && !em.sevenOnly {
|
||||
em.inbEsc(b - 0x40)
|
||||
// For C1 controls, the raw 8-bit form is the same as ESC followed by
|
||||
// (b - 0x40). This is disabled by default because modern protocols
|
||||
// generally treat these forms as insecure.
|
||||
if b >= 0x80 && b <= 0x9f {
|
||||
if em.c1Enabled {
|
||||
em.inbEsc(b - 0x40)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -529,6 +549,12 @@ func (em *emulator) inbNF(b byte) {
|
|||
// case "(Q", "(9": // TODO: select G0 as French Canadian
|
||||
// case "(R", "(f": // TODO: select G0 as French
|
||||
// case "(Y": // TODO: select G0 as Italian
|
||||
case " F": // S7C1T - send/use 7-bit C1 controls
|
||||
em.c1Enabled = false
|
||||
case " G": // S8C1T - send/use 8-bit C1 controls
|
||||
if em.c1Allowed {
|
||||
em.c1Enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -550,8 +576,17 @@ func (em *emulator) inbCSI(b byte) {
|
|||
|
||||
// inbOSC handles bytes that are part of on OSC sequences (operating system command).
|
||||
func (em *emulator) inbOSC(b byte) {
|
||||
if em.inbStringC1(b, em.processOSC) {
|
||||
return
|
||||
}
|
||||
|
||||
switch b {
|
||||
case 0x9c, 0x07:
|
||||
case 0x9c:
|
||||
if em.c1Enabled {
|
||||
em.inb = em.inbInit
|
||||
em.processOSC()
|
||||
}
|
||||
case 0x07:
|
||||
em.inb = em.inbInit
|
||||
em.processOSC()
|
||||
case '\\':
|
||||
|
|
@ -569,8 +604,16 @@ func (em *emulator) inbOSC(b byte) {
|
|||
|
||||
// inbStr handles PM, SOS, and any other string we want to consume and discard.
|
||||
func (em *emulator) inbStr(b byte) {
|
||||
if em.inbStringC1(b, nil) {
|
||||
return
|
||||
}
|
||||
|
||||
switch b {
|
||||
case 0x9c, 0x07:
|
||||
case 0x9c:
|
||||
if em.c1Enabled {
|
||||
em.inb = em.inbInit
|
||||
}
|
||||
case 0x07:
|
||||
em.inb = em.inbInit
|
||||
case '\\':
|
||||
if buf := em.inBuf.Bytes(); len(buf) > 0 && buf[len(buf)-1] == 0x1b {
|
||||
|
|
@ -584,6 +627,27 @@ func (em *emulator) inbStr(b byte) {
|
|||
}
|
||||
}
|
||||
|
||||
func (em *emulator) inbStringC1(b byte, done func()) bool {
|
||||
if em.c1Prefix {
|
||||
em.c1Prefix = false
|
||||
if b >= 0x80 && b <= 0x9f {
|
||||
if em.c1Enabled && b == 0x9c {
|
||||
em.inb = em.inbInit
|
||||
if done != nil {
|
||||
done()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
em.inBuf.WriteByte(0xc2)
|
||||
}
|
||||
if b == 0xc2 {
|
||||
em.c1Prefix = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// inbUTF handles continuation bytes for UTF-8 sequences.
|
||||
func (em *emulator) inbUTF(b byte) {
|
||||
if b&0xC0 == 0x80 {
|
||||
|
|
@ -595,7 +659,13 @@ func (em *emulator) inbUTF(b byte) {
|
|||
if err != nil {
|
||||
em.beep()
|
||||
} else {
|
||||
em.putRune(r)
|
||||
if r >= 0x80 && r <= 0x9f {
|
||||
if em.c1Enabled {
|
||||
em.inbEsc(byte(r) - 0x40)
|
||||
}
|
||||
} else {
|
||||
em.putRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -730,7 +800,7 @@ func (em *emulator) processSgr(str string) {
|
|||
v, err := strconv.Atoi(word)
|
||||
if err != nil {
|
||||
// just swallow it for now
|
||||
return
|
||||
continue
|
||||
}
|
||||
switch v {
|
||||
case 0:
|
||||
|
|
@ -2333,6 +2403,7 @@ func (em *emulator) ResizeEvent(size Coord) {
|
|||
func (em *emulator) applyResize(size Coord) {
|
||||
// resize clobbers our content, until it is redrawn
|
||||
em.size = size
|
||||
em.tabStops = slices.DeleteFunc(em.tabStops, func(x Col) bool { return x >= em.size.X })
|
||||
// resizing resets the margins
|
||||
em.topMargin = 0
|
||||
em.botMargin = em.size.Y - 1
|
||||
|
|
|
|||
13
vendor/github.com/gdamore/tcell/v3/vt/key.go
generated
vendored
13
vendor/github.com/gdamore/tcell/v3/vt/key.go
generated
vendored
|
|
@ -15,7 +15,7 @@
|
|||
package vt
|
||||
|
||||
// BaseKey is the Kitty protocol base key. These are kitty's representation of a scan code.
|
||||
// As the Kitty protocol is likely the extended keyboard protocol we care about, we use
|
||||
// As the Kitty protocol is likely the extended keyboard protocol we care most about, we use
|
||||
// this as the primary reporting mechanism. (It also helps that this may provide an easier
|
||||
// fallback for implementations that don't have raw scan codes and are willing to assume an
|
||||
// ANSI layout.)
|
||||
|
|
@ -198,7 +198,7 @@ func (k Key) ScanCode() ScanCode {
|
|||
}
|
||||
|
||||
// WinVK represents a windows virtual key code.
|
||||
// These are similar to base keys, but a multiple scanned key codes
|
||||
// These are similar to base keys, but multiple scanned key codes
|
||||
// may result in the same virtual key. This can also be sensitive to
|
||||
// the keyboard layout.
|
||||
type WinVK rune
|
||||
|
|
@ -346,7 +346,7 @@ const (
|
|||
|
||||
var baseKeys map[Key]BaseKey
|
||||
|
||||
// KittyBase returns the corresponding Kitty "base" key for the given USB cod.
|
||||
// KittyBase returns the corresponding Kitty "base" key for the given USB code.
|
||||
// If no corresponding value can be found, then zero is returned. Note that
|
||||
// some keys (such as F1) are valid, and recognized by Kitty, but do not use the
|
||||
// base key encoding because they use another reporting format.
|
||||
|
|
@ -462,15 +462,16 @@ func init() {
|
|||
KeyRAlt: 57449,
|
||||
KeyRMeta: 57450,
|
||||
|
||||
// KeyHiragana: 0, // TBD
|
||||
// KeyConvert: 0, // TBD
|
||||
// KeyNonConvert: 0, // TD
|
||||
// KeyHiragana: 0, // Later
|
||||
// KeyConvert: 0, // Later
|
||||
// KeyNonConvert: 0, // Later
|
||||
|
||||
// Windows uses a bunch of HID usages from
|
||||
// the consumer page (0x0c) for media playback, and
|
||||
// other applications. We just ignore them.
|
||||
}
|
||||
|
||||
// Scan codes used by Windows.
|
||||
scanCodes = map[Key]ScanCode{
|
||||
KeyA: 0x1e,
|
||||
KeyB: 0x30,
|
||||
|
|
|
|||
11
vendor/github.com/gdamore/tcell/v3/vt/mock.go
generated
vendored
11
vendor/github.com/gdamore/tcell/v3/vt/mock.go
generated
vendored
|
|
@ -247,13 +247,16 @@ func NewMockTerm(opts ...MockOpt) MockTerm {
|
|||
mt := &mockTerm{}
|
||||
mt.mb = NewMockBackend(opts...)
|
||||
var be MockBackend = mt.mb
|
||||
emOpts := []EmulatorOpt{}
|
||||
for _, o := range opts {
|
||||
switch o.(type) {
|
||||
case MockOptNoBlit:
|
||||
be = &noMockBlit{be, struct{}{}}
|
||||
case MockOpt8BitControls:
|
||||
emOpts = append(emOpts, EmulatorOpt8BitControls{})
|
||||
}
|
||||
}
|
||||
mt.em = NewEmulator(be)
|
||||
mt.em = NewEmulator(be, emOpts...)
|
||||
mt.em.SetId("TCellMock", "1.0")
|
||||
mt.ks = &KeyboardState{}
|
||||
return mt
|
||||
|
|
@ -644,6 +647,12 @@ type MockOptNoBlit struct{}
|
|||
|
||||
func (MockOptNoBlit) SetMockOpt(mb *mockBackend) {}
|
||||
|
||||
// MockOpt8BitControls enables raw 8-bit and UTF-8 encoded C1 controls in the
|
||||
// emulator. The default is to accept only 7-bit ESC-prefixed controls.
|
||||
type MockOpt8BitControls struct{}
|
||||
|
||||
func (MockOpt8BitControls) SetMockOpt(mb *mockBackend) {}
|
||||
|
||||
// NewMockBackend returns a MockBackend modified by the given options.
|
||||
// The default is a fully featured 256-color backend with initial size 80x24.
|
||||
func NewMockBackend(options ...MockOpt) MockBackend {
|
||||
|
|
|
|||
729
vendor/github.com/gdamore/tcell/v3/wscreen.go
generated
vendored
729
vendor/github.com/gdamore/tcell/v3/wscreen.go
generated
vendored
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2025 The TCell Authors
|
||||
// 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.
|
||||
|
|
@ -19,611 +19,206 @@ package tcell
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"syscall/js"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gdamore/tcell/v3/tty"
|
||||
)
|
||||
|
||||
// NewTerminfoScreen gets a screen. The options are ignored for this platform.
|
||||
func NewTerminfoScreen(_ ...TerminfoScreenOption) (Screen, error) {
|
||||
t := &wScreen{}
|
||||
t.fallback = make(map[rune]string)
|
||||
|
||||
return &baseScreen{screenImpl: t}, nil
|
||||
}
|
||||
|
||||
func NewTerminfoScreenFromTty(_ tty.Tty, _ ...TerminfoScreenOption) (Screen, error) {
|
||||
// TODO: When we want to support testing webasm, we'll have to change this
|
||||
// to use a MockTerm. That will be appropriate when we switch to xterm.js.
|
||||
// The options are ignored for this platform.
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
type TerminfoScreenOption interface{ apply(*wScreen) }
|
||||
type OptColors int
|
||||
type OptTerm string
|
||||
type OptAltScreen bool
|
||||
|
||||
func (OptColors) apply(*wScreen) {}
|
||||
func (OptTerm) apply(*wScreen) {}
|
||||
func (OptAltScreen) apply(*wScreen) {}
|
||||
|
||||
type wScreen struct {
|
||||
w, h int
|
||||
style Style
|
||||
cells CellBuffer
|
||||
|
||||
running bool
|
||||
clear bool
|
||||
flagsPresent bool
|
||||
pasteEnabled bool
|
||||
mouseFlags MouseFlags
|
||||
|
||||
cursorStyle CursorStyle
|
||||
|
||||
quit chan struct{}
|
||||
evch chan Event
|
||||
fallback map[rune]string
|
||||
finiOnce sync.Once
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (t *wScreen) Init() error {
|
||||
t.w, t.h = 80, 24 // default for html as of now
|
||||
t.evch = make(chan Event, 10)
|
||||
t.quit = make(chan struct{})
|
||||
|
||||
t.Lock()
|
||||
t.running = true
|
||||
t.style = StyleDefault
|
||||
t.cells.Resize(t.w, t.h)
|
||||
t.Unlock()
|
||||
|
||||
js.Global().Set("onKeyEvent", js.FuncOf(t.onKeyEvent))
|
||||
js.Global().Set("onMouseClick", js.FuncOf(t.unset))
|
||||
js.Global().Set("onMouseMove", js.FuncOf(t.unset))
|
||||
js.Global().Set("onFocus", js.FuncOf(t.unset))
|
||||
|
||||
// initialize installs the browser-backed TTY used by tScreen on js/wasm.
|
||||
func (t *tScreen) initialize() error {
|
||||
if t.tty == nil {
|
||||
t.tty = newBrowserTty()
|
||||
}
|
||||
if t.term == "" {
|
||||
t.term = "ghostty-truecolor"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *wScreen) Fini() {
|
||||
t.finiOnce.Do(func() {
|
||||
close(t.quit)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *wScreen) SetStyle(style Style) {
|
||||
t.Lock()
|
||||
t.style = style
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
// paletteColor gives a more natural palette color actually matching
|
||||
// typical XTerm. We might in the future want to permit styling these
|
||||
// via CSS.
|
||||
|
||||
var palette = map[Color]int32{
|
||||
ColorBlack: 0x000000,
|
||||
ColorMaroon: 0xcd0000,
|
||||
ColorGreen: 0x00cd00,
|
||||
ColorOlive: 0xcdcd00,
|
||||
ColorNavy: 0x0000ee,
|
||||
ColorPurple: 0xcd00cd,
|
||||
ColorTeal: 0x00cdcd,
|
||||
ColorSilver: 0xe5e5e5,
|
||||
ColorGray: 0x7f7f7f,
|
||||
ColorRed: 0xff0000,
|
||||
ColorLime: 0x00ff00,
|
||||
ColorYellow: 0xffff00,
|
||||
ColorBlue: 0x5c5cff,
|
||||
ColorFuchsia: 0xff00ff,
|
||||
ColorAqua: 0x00ffff,
|
||||
ColorWhite: 0xffffff,
|
||||
}
|
||||
|
||||
func paletteColor(c Color) int32 {
|
||||
if c.IsRGB() {
|
||||
return int32(c & 0xffffff)
|
||||
}
|
||||
if c >= ColorBlack && c <= ColorWhite {
|
||||
return palette[c]
|
||||
}
|
||||
return c.Hex()
|
||||
}
|
||||
|
||||
func (t *wScreen) drawCell(x, y int) int {
|
||||
str, style, width := t.cells.Get(x, y)
|
||||
|
||||
if !t.cells.Dirty(x, y) {
|
||||
return width
|
||||
}
|
||||
|
||||
if style == StyleDefault {
|
||||
style = t.style
|
||||
}
|
||||
|
||||
fg, bg := paletteColor(style.fg), paletteColor(style.bg)
|
||||
if fg == -1 {
|
||||
fg = 0xe5e5e5
|
||||
}
|
||||
if bg == -1 {
|
||||
bg = 0x000000
|
||||
}
|
||||
us, uc := style.ulStyle, paletteColor(style.ulColor)
|
||||
if uc == -1 {
|
||||
uc = 0x000000
|
||||
}
|
||||
|
||||
t.cells.SetDirty(x, y, false)
|
||||
js.Global().Call("drawCell", x, y, str, fg, bg, int(style.attrs), int(us), int(uc))
|
||||
|
||||
return width
|
||||
}
|
||||
|
||||
func (t *wScreen) ShowCursor(x, y int) {
|
||||
t.Lock()
|
||||
js.Global().Call("showCursor", x, y)
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *wScreen) SetCursor(cs CursorStyle, cc Color) {
|
||||
if !cc.Valid() {
|
||||
cc = ColorLightGray
|
||||
}
|
||||
t.Lock()
|
||||
js.Global().Call("setCursorStyle", curStyleClasses[cs], fmt.Sprintf("#%06x", cc.Hex()))
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *wScreen) HideCursor() {
|
||||
t.ShowCursor(-1, -1)
|
||||
}
|
||||
|
||||
func (t *wScreen) Show() {
|
||||
t.Lock()
|
||||
t.resize()
|
||||
t.draw()
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *wScreen) clearScreen() {
|
||||
js.Global().Call("clearScreen", t.style.fg.Hex(), t.style.bg.Hex())
|
||||
t.clear = false
|
||||
}
|
||||
|
||||
func (t *wScreen) draw() {
|
||||
if t.clear {
|
||||
t.clearScreen()
|
||||
}
|
||||
|
||||
for y := 0; y < t.h; y++ {
|
||||
for x := 0; x < t.w; x++ {
|
||||
width := t.drawCell(x, y)
|
||||
x += width - 1
|
||||
}
|
||||
}
|
||||
|
||||
js.Global().Call("show")
|
||||
}
|
||||
|
||||
func (t *wScreen) EnableMouse(flags ...MouseFlags) {
|
||||
var f MouseFlags
|
||||
flagsPresent := false
|
||||
for _, flag := range flags {
|
||||
f |= flag
|
||||
flagsPresent = true
|
||||
}
|
||||
if !flagsPresent {
|
||||
f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents
|
||||
}
|
||||
|
||||
t.Lock()
|
||||
t.mouseFlags = f
|
||||
t.enableMouse(f)
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *wScreen) enableMouse(f MouseFlags) {
|
||||
if f&MouseButtonEvents != 0 {
|
||||
js.Global().Set("onMouseClick", js.FuncOf(t.onMouseEvent))
|
||||
} else {
|
||||
js.Global().Set("onMouseClick", js.FuncOf(t.unset))
|
||||
}
|
||||
|
||||
if f&MouseDragEvents != 0 || f&MouseMotionEvents != 0 {
|
||||
js.Global().Set("onMouseMove", js.FuncOf(t.onMouseEvent))
|
||||
} else {
|
||||
js.Global().Set("onMouseMove", js.FuncOf(t.unset))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *wScreen) DisableMouse() {
|
||||
t.Lock()
|
||||
t.mouseFlags = 0
|
||||
t.enableMouse(0)
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *wScreen) EnablePaste() {
|
||||
t.Lock()
|
||||
t.pasteEnabled = true
|
||||
t.enablePasting(true)
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *wScreen) DisablePaste() {
|
||||
t.Lock()
|
||||
t.pasteEnabled = false
|
||||
t.enablePasting(false)
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *wScreen) enablePasting(on bool) {
|
||||
if on {
|
||||
js.Global().Set("onPaste", js.FuncOf(t.onPaste))
|
||||
} else {
|
||||
js.Global().Set("onPaste", js.FuncOf(t.unset))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *wScreen) EnableFocus() {
|
||||
t.Lock()
|
||||
js.Global().Set("onFocus", js.FuncOf(t.onFocus))
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *wScreen) DisableFocus() {
|
||||
t.Lock()
|
||||
js.Global().Set("onFocus", js.FuncOf(t.unset))
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (s *wScreen) GetClipboard() {
|
||||
}
|
||||
|
||||
func (s *wScreen) SetClipboard(_ []byte) {
|
||||
}
|
||||
|
||||
func (s *wScreen) HasClipboard() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *wScreen) Size() (int, int) {
|
||||
t.Lock()
|
||||
w, h := t.w, t.h
|
||||
t.Unlock()
|
||||
return w, h
|
||||
}
|
||||
|
||||
// resize does nothing, as asking the web window to resize
|
||||
// without a specified width or height will cause no change.
|
||||
func (t *wScreen) resize() {}
|
||||
|
||||
func (t *wScreen) Colors() int {
|
||||
return 16777216 // 256 ^ 3
|
||||
}
|
||||
|
||||
func (t *wScreen) clip(x, y int) (int, int) {
|
||||
w, h := t.cells.Size()
|
||||
if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
if y < 0 {
|
||||
y = 0
|
||||
}
|
||||
if x > w-1 {
|
||||
x = w - 1
|
||||
}
|
||||
if y > h-1 {
|
||||
y = h - 1
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (t *wScreen) postEvent(ev Event) {
|
||||
select {
|
||||
case t.evch <- ev:
|
||||
case <-t.quit:
|
||||
}
|
||||
}
|
||||
|
||||
func (t *wScreen) onMouseEvent(this js.Value, args []js.Value) any {
|
||||
mod := ModNone
|
||||
button := ButtonNone
|
||||
|
||||
switch args[2].Int() {
|
||||
case 0:
|
||||
if t.mouseFlags&MouseMotionEvents == 0 {
|
||||
// don't want this event! is a mouse motion event, but user has asked not.
|
||||
return nil
|
||||
}
|
||||
button = ButtonNone
|
||||
case 1:
|
||||
button = Button1
|
||||
case 2:
|
||||
button = Button3 // Note we prefer to treat right as button 2
|
||||
case 3:
|
||||
button = Button2 // And the middle button as button 3
|
||||
}
|
||||
|
||||
if args[3].Bool() { // mod shift
|
||||
mod |= ModShift
|
||||
}
|
||||
|
||||
if args[4].Bool() { // mod alt
|
||||
mod |= ModAlt
|
||||
}
|
||||
|
||||
if args[5].Bool() { // mod ctrl
|
||||
mod |= ModCtrl
|
||||
}
|
||||
|
||||
t.postEvent(NewEventMouse(args[0].Int(), args[1].Int(), button, mod))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *wScreen) onKeyEvent(this js.Value, args []js.Value) any {
|
||||
key := args[0].String()
|
||||
|
||||
// don't accept any modifier keys as their own
|
||||
if key == "Control" || key == "Alt" || key == "Meta" || key == "Shift" {
|
||||
return nil
|
||||
}
|
||||
|
||||
mod := ModNone
|
||||
if args[1].Bool() { // mod shift
|
||||
mod |= ModShift
|
||||
}
|
||||
|
||||
if args[2].Bool() { // mod alt
|
||||
mod |= ModAlt
|
||||
}
|
||||
|
||||
if args[3].Bool() { // mod ctrl
|
||||
mod |= ModCtrl
|
||||
}
|
||||
|
||||
if args[4].Bool() { // mod meta
|
||||
mod |= ModMeta
|
||||
}
|
||||
|
||||
// next try function keys
|
||||
if k, ok := WebKeyNames[key]; ok {
|
||||
t.postEvent(NewEventKey(k, "", mod))
|
||||
return nil
|
||||
}
|
||||
|
||||
// finally try normal, printable chars
|
||||
r, _ := utf8.DecodeRuneInString(key)
|
||||
t.postEvent(NewEventKey(KeyRune, string(r), mod))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *wScreen) onPaste(this js.Value, args []js.Value) any {
|
||||
t.postEvent(NewEventPaste(args[0].Bool()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *wScreen) onFocus(this js.Value, args []js.Value) any {
|
||||
t.postEvent(NewEventFocus(args[0].Bool()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// unset is a dummy function for js when we want nothing to
|
||||
// happen when javascript calls a function (for example, when
|
||||
// mouse input is disabled, when onMouseEvent() is called from
|
||||
// js, it redirects here and does nothing).
|
||||
func (t *wScreen) unset(this js.Value, args []js.Value) any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *wScreen) Sync() {
|
||||
t.Lock()
|
||||
t.resize()
|
||||
t.clear = true
|
||||
t.cells.Invalidate()
|
||||
t.draw()
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *wScreen) CharacterSet() string {
|
||||
func getCharset() string {
|
||||
return "UTF-8"
|
||||
}
|
||||
|
||||
func (t *wScreen) RegisterRuneFallback(orig rune, fallback string) {
|
||||
t.Lock()
|
||||
t.fallback[orig] = fallback
|
||||
t.Unlock()
|
||||
type browserTty struct {
|
||||
mu sync.Mutex
|
||||
cond *sync.Cond
|
||||
started bool
|
||||
drained bool
|
||||
closed bool
|
||||
input []byte
|
||||
resizeQ chan<- bool
|
||||
|
||||
writeFunc js.Value
|
||||
sizeFunc js.Value
|
||||
closeFuncs []js.Func
|
||||
}
|
||||
|
||||
func (t *wScreen) UnregisterRuneFallback(orig rune) {
|
||||
t.Lock()
|
||||
delete(t.fallback, orig)
|
||||
t.Unlock()
|
||||
func newBrowserTty() *browserTty {
|
||||
t := &browserTty{}
|
||||
t.cond = sync.NewCond(&t.mu)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *wScreen) SetSize(w, h int) {
|
||||
if w == t.w && h == t.h {
|
||||
return
|
||||
}
|
||||
func (t *browserTty) Start() error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
t.cells.Invalidate()
|
||||
t.cells.Resize(w, h)
|
||||
js.Global().Call("resize", w, h)
|
||||
t.w, t.h = w, h
|
||||
t.postEvent(NewEventResize(w, h))
|
||||
}
|
||||
|
||||
func (t *wScreen) Resize(int, int, int, int) {}
|
||||
|
||||
// Suspend simply pauses all input and output, and clears the screen.
|
||||
// There isn't a "default terminal" to go back to.
|
||||
func (t *wScreen) Suspend() error {
|
||||
t.Lock()
|
||||
if !t.running {
|
||||
t.Unlock()
|
||||
if t.started {
|
||||
return nil
|
||||
}
|
||||
t.running = false
|
||||
t.clearScreen()
|
||||
t.enableMouse(0)
|
||||
t.enablePasting(false)
|
||||
js.Global().Set("onKeyEvent", js.FuncOf(t.unset)) // stop key presses
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *wScreen) Resume() error {
|
||||
t.Lock()
|
||||
|
||||
if t.running {
|
||||
return errors.New("already engaged")
|
||||
global := js.Global()
|
||||
t.writeFunc = global.Get("tcellWrite")
|
||||
t.sizeFunc = global.Get("tcellWindowSize")
|
||||
if t.writeFunc.Type() != js.TypeFunction || t.sizeFunc.Type() != js.TypeFunction {
|
||||
return errors.New("tcell wasm terminal host is not installed")
|
||||
}
|
||||
t.running = true
|
||||
|
||||
t.enableMouse(t.mouseFlags)
|
||||
t.enablePasting(t.pasteEnabled)
|
||||
onData := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
if args[0].InstanceOf(global.Get("Uint8Array")) {
|
||||
data := make([]byte, args[0].Get("byteLength").Int())
|
||||
js.CopyBytesToGo(data, args[0])
|
||||
t.enqueue(data)
|
||||
} else {
|
||||
t.enqueue([]byte(args[0].String()))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
onResize := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
t.mu.Lock()
|
||||
resizeQ := t.resizeQ
|
||||
t.mu.Unlock()
|
||||
if resizeQ != nil {
|
||||
select {
|
||||
case resizeQ <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
t.closeFuncs = []js.Func{onData, onResize}
|
||||
global.Set("tcellRead", onData)
|
||||
global.Set("tcellResize", onResize)
|
||||
|
||||
js.Global().Set("onKeyEvent", js.FuncOf(t.onKeyEvent))
|
||||
|
||||
t.Unlock()
|
||||
t.started = true
|
||||
t.drained = false
|
||||
t.closed = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *wScreen) Beep() error {
|
||||
js.Global().Call("beep")
|
||||
func (t *browserTty) Stop() error {
|
||||
t.mu.Lock()
|
||||
t.started = false
|
||||
t.drained = false
|
||||
funcs := t.closeFuncs
|
||||
t.closeFuncs = nil
|
||||
t.cond.Broadcast()
|
||||
t.mu.Unlock()
|
||||
|
||||
js.Global().Set("tcellRead", js.Undefined())
|
||||
js.Global().Set("tcellResize", js.Undefined())
|
||||
for _, fn := range funcs {
|
||||
fn.Release()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *wScreen) Tty() (Tty, bool) {
|
||||
return nil, false
|
||||
func (t *browserTty) Drain() error {
|
||||
t.mu.Lock()
|
||||
t.input = nil
|
||||
t.drained = true
|
||||
t.cond.Broadcast()
|
||||
t.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *wScreen) GetCells() *CellBuffer {
|
||||
return &t.cells
|
||||
func (t *browserTty) NotifyResize(resizeQ chan<- bool) {
|
||||
t.mu.Lock()
|
||||
t.resizeQ = resizeQ
|
||||
t.mu.Unlock()
|
||||
}
|
||||
|
||||
func (t *wScreen) EventQ() chan Event {
|
||||
return t.evch
|
||||
func (t *browserTty) WindowSize() (tty.WindowSize, error) {
|
||||
var ws tty.WindowSize
|
||||
t.mu.Lock()
|
||||
sizeFunc := t.sizeFunc
|
||||
t.mu.Unlock()
|
||||
if sizeFunc.Type() != js.TypeFunction {
|
||||
ws.Width = 80
|
||||
ws.Height = 24
|
||||
return ws, nil
|
||||
}
|
||||
size := sizeFunc.Invoke()
|
||||
ws.Width = size.Get("cols").Int()
|
||||
ws.Height = size.Get("rows").Int()
|
||||
ws.PixelWidth = size.Get("pixelWidth").Int()
|
||||
ws.PixelHeight = size.Get("pixelHeight").Int()
|
||||
if ws.Width == 0 {
|
||||
ws.Width = 80
|
||||
}
|
||||
if ws.Height == 0 {
|
||||
ws.Height = 24
|
||||
}
|
||||
return ws, nil
|
||||
}
|
||||
|
||||
func (t *wScreen) StopQ() <-chan struct{} {
|
||||
return t.quit
|
||||
func (t *browserTty) Read(b []byte) (int, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
for len(t.input) == 0 && t.started && !t.drained && !t.closed {
|
||||
t.cond.Wait()
|
||||
}
|
||||
if t.closed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if (!t.started || t.drained) && len(t.input) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := copy(b, t.input)
|
||||
t.input = t.input[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (t *wScreen) SetTitle(title string) {
|
||||
js.Global().Call("setTitle", title)
|
||||
func (t *browserTty) Write(b []byte) (int, error) {
|
||||
t.mu.Lock()
|
||||
writeFunc := t.writeFunc
|
||||
started := t.started
|
||||
t.mu.Unlock()
|
||||
if !started || writeFunc.Type() != js.TypeFunction {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
data := js.Global().Get("Uint8Array").New(len(b))
|
||||
js.CopyBytesToJS(data, b)
|
||||
writeFunc.Invoke(data)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (*wScreen) ShowNotification(title string, body string) {}
|
||||
func (t *browserTty) Close() error {
|
||||
t.mu.Lock()
|
||||
if t.closed {
|
||||
t.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
t.closed = true
|
||||
t.started = false
|
||||
t.drained = false
|
||||
t.cond.Broadcast()
|
||||
t.mu.Unlock()
|
||||
|
||||
// WebKeyNames maps string names reported from HTML
|
||||
// (KeyboardEvent.key) to tcell accepted keys.
|
||||
var WebKeyNames = map[string]Key{
|
||||
"Enter": KeyEnter,
|
||||
"Backspace": KeyBackspace,
|
||||
"Tab": KeyTab,
|
||||
"Backtab": KeyBacktab,
|
||||
"Escape": KeyEsc,
|
||||
"Backspace2": KeyBackspace2,
|
||||
"Delete": KeyDelete,
|
||||
"Insert": KeyInsert,
|
||||
"ArrowUp": KeyUp,
|
||||
"ArrowDown": KeyDown,
|
||||
"ArrowLeft": KeyLeft,
|
||||
"ArrowRight": KeyRight,
|
||||
"Home": KeyHome,
|
||||
"End": KeyEnd,
|
||||
"UpLeft": KeyUpLeft, // not supported by HTML
|
||||
"UpRight": KeyUpRight, // not supported by HTML
|
||||
"DownLeft": KeyDownLeft, // not supported by HTML
|
||||
"DownRight": KeyDownRight, // not supported by HTML
|
||||
"Center": KeyCenter,
|
||||
"PgDn": KeyPgDn,
|
||||
"PgUp": KeyPgUp,
|
||||
"Clear": KeyClear,
|
||||
"Exit": KeyExit,
|
||||
"Cancel": KeyCancel,
|
||||
"Pause": KeyPause,
|
||||
"Print": KeyPrint,
|
||||
"F1": KeyF1,
|
||||
"F2": KeyF2,
|
||||
"F3": KeyF3,
|
||||
"F4": KeyF4,
|
||||
"F5": KeyF5,
|
||||
"F6": KeyF6,
|
||||
"F7": KeyF7,
|
||||
"F8": KeyF8,
|
||||
"F9": KeyF9,
|
||||
"F10": KeyF10,
|
||||
"F11": KeyF11,
|
||||
"F12": KeyF12,
|
||||
"F13": KeyF13,
|
||||
"F14": KeyF14,
|
||||
"F15": KeyF15,
|
||||
"F16": KeyF16,
|
||||
"F17": KeyF17,
|
||||
"F18": KeyF18,
|
||||
"F19": KeyF19,
|
||||
"F20": KeyF20,
|
||||
"F21": KeyF21,
|
||||
"F22": KeyF22,
|
||||
"F23": KeyF23,
|
||||
"F24": KeyF24,
|
||||
"F25": KeyF25,
|
||||
"F26": KeyF26,
|
||||
"F27": KeyF27,
|
||||
"F28": KeyF28,
|
||||
"F29": KeyF29,
|
||||
"F30": KeyF30,
|
||||
"F31": KeyF31,
|
||||
"F32": KeyF32,
|
||||
"F33": KeyF33,
|
||||
"F34": KeyF34,
|
||||
"F35": KeyF35,
|
||||
"F36": KeyF36,
|
||||
"F37": KeyF37,
|
||||
"F38": KeyF38,
|
||||
"F39": KeyF39,
|
||||
"F40": KeyF40,
|
||||
"F41": KeyF41,
|
||||
"F42": KeyF42,
|
||||
"F43": KeyF43,
|
||||
"F44": KeyF44,
|
||||
"F45": KeyF45,
|
||||
"F46": KeyF46,
|
||||
"F47": KeyF47,
|
||||
"F48": KeyF48,
|
||||
"F49": KeyF49,
|
||||
"F50": KeyF50,
|
||||
"F51": KeyF51,
|
||||
"F52": KeyF52,
|
||||
"F53": KeyF53,
|
||||
"F54": KeyF54,
|
||||
"F55": KeyF55,
|
||||
"F56": KeyF56,
|
||||
"F57": KeyF57,
|
||||
"F58": KeyF58,
|
||||
"F59": KeyF59,
|
||||
"F60": KeyF60,
|
||||
"F61": KeyF61,
|
||||
"F62": KeyF62,
|
||||
"F63": KeyF63,
|
||||
"F64": KeyF64,
|
||||
return t.Stop()
|
||||
}
|
||||
|
||||
var curStyleClasses = map[CursorStyle]string{
|
||||
CursorStyleDefault: "cursor-blinking-block",
|
||||
CursorStyleBlinkingBlock: "cursor-blinking-block",
|
||||
CursorStyleSteadyBlock: "cursor-steady-block",
|
||||
CursorStyleBlinkingUnderline: "cursor-blinking-underline",
|
||||
CursorStyleSteadyUnderline: "cursor-steady-underline",
|
||||
CursorStyleBlinkingBar: "cursor-blinking-bar",
|
||||
CursorStyleSteadyBar: "cursor-steady-bar",
|
||||
func (t *browserTty) enqueue(data []byte) {
|
||||
t.mu.Lock()
|
||||
if t.started && !t.closed {
|
||||
t.input = append(t.input, data...)
|
||||
t.cond.Broadcast()
|
||||
}
|
||||
t.mu.Unlock()
|
||||
}
|
||||
|
||||
func (*wScreen) Terminal() (string, string) { return "Tcell-WebASM", "" }
|
||||
|
|
|
|||
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
|
|
@ -48,7 +48,7 @@ github.com/fatih/color
|
|||
# github.com/gdamore/encoding v1.0.1
|
||||
## explicit; go 1.9
|
||||
github.com/gdamore/encoding
|
||||
# github.com/gdamore/tcell/v3 v3.3.0
|
||||
# github.com/gdamore/tcell/v3 v3.4.0
|
||||
## explicit; go 1.25.0
|
||||
github.com/gdamore/tcell/v3
|
||||
github.com/gdamore/tcell/v3/color
|
||||
|
|
@ -184,10 +184,10 @@ golang.org/x/sync/errgroup
|
|||
golang.org/x/sys/plan9
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
# golang.org/x/term v0.42.0
|
||||
# golang.org/x/term v0.43.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/term
|
||||
# golang.org/x/text v0.36.0
|
||||
# golang.org/x/text v0.37.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/text/cases
|
||||
golang.org/x/text/encoding
|
||||
|
|
|
|||
Loading…
Reference in a new issue