@textui/terminal 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -33
- package/dist/ansi.d.ts +2 -0
- package/dist/ansi.d.ts.map +1 -1
- package/dist/ansi.js +2 -0
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +19 -1
- package/dist/input.d.ts.map +1 -1
- package/dist/input.js +4 -0
- package/dist/node.d.ts +2 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +11 -1
- package/package.json +8 -8
- package/src/ansi.ts +3 -0
- package/src/capabilities.ts +19 -1
- package/src/input.ts +4 -0
- package/src/node.ts +11 -5
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @textui/terminal
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@textui/terminal) [](https://github.com/softov/textui/blob/main/LICENSE)
|
|
4
|
+
|
|
5
|
+
What [`@textui/core`](https://www.npmjs.com/package/@textui/core) needs to reach an actual terminal: adapters, capability detection, ANSI writing and input decoding.
|
|
5
6
|
|
|
6
7
|
```bash
|
|
7
8
|
npm install @textui/terminal
|
|
@@ -17,40 +18,27 @@ await app.start();
|
|
|
17
18
|
|
|
18
19
|
## Two adapters
|
|
19
20
|
|
|
20
|
-
`createNodeTerminal()` is the real one - stdin, stdout, resize, and the signal
|
|
21
|
-
handling that puts the screen back the way it found it when the process dies.
|
|
21
|
+
`createNodeTerminal()` is the real one - stdin, stdout, resize, and the signal handling that puts the screen back the way it found it when the process dies.
|
|
22
22
|
|
|
23
|
-
`createVirtualTerminal()` has no tty at all. It is what the test harness and
|
|
24
|
-
`--static` run on, and it is the reason a component that only works
|
|
25
|
-
interactively is a component nobody can test cheaply.
|
|
23
|
+
`createVirtualTerminal()` has no tty at all. It is what the test harness and `--static` run on, and it is the reason a component that only works interactively is a component nobody can test cheaply.
|
|
26
24
|
|
|
27
25
|
## Capabilities are detected, and can be lied to
|
|
28
26
|
|
|
29
|
-
Unicode level, colour depth, mouse, hyperlinks, the kitty keyboard protocol -
|
|
30
|
-
detection is right almost always, and the times it is not are the times nobody
|
|
31
|
-
is watching. So every capability can be forced, which is how you see the
|
|
32
|
-
terminal somebody else is sitting at:
|
|
27
|
+
Unicode level, colour depth, mouse, hyperlinks, the kitty keyboard protocol - detection is right almost always, and the times it is not are the times nobody is watching. So every capability can be forced, which is how you see the terminal somebody else is sitting at:
|
|
33
28
|
|
|
34
29
|
```ts
|
|
35
30
|
createNodeTerminal({ capabilities: { unicode: 'ascii', colors: 4 } });
|
|
36
31
|
```
|
|
37
32
|
|
|
38
|
-
Under the kitty protocol, `ctrl+shift+f` is a distinct stroke from `ctrl+f`.
|
|
39
|
-
Without it the terminal sends the same byte for both, and no amount of decoding
|
|
40
|
-
recovers the difference.
|
|
33
|
+
Under the kitty protocol, `ctrl+shift+f` is a distinct stroke from `ctrl+f`. Without it the terminal sends the same byte for both, and no amount of decoding recovers the difference.
|
|
41
34
|
|
|
42
35
|
## A frame you can keep
|
|
43
36
|
|
|
44
|
-
The screen is the output, so the next redraw destroys the frame that was wrong.
|
|
45
|
-
Two functions write one out instead - both from the buffer the runtime last
|
|
46
|
-
painted, neither needing a tty.
|
|
37
|
+
The screen is the output, so the next redraw destroys the frame that was wrong. Two functions write one out instead - both from the buffer the runtime last painted, neither needing a tty.
|
|
47
38
|
|
|
48
|
-
`captureBuffer` gives a frame a terminal can replay: every cell in order, no
|
|
49
|
-
cursor control, so it can go in a file or an issue. `colors: false` strips the
|
|
50
|
-
colour, which is the copy a diff can read.
|
|
39
|
+
`captureBuffer` gives a frame a terminal can replay: every cell in order, no cursor control, so it can go in a file or an issue. `colors: false` strips the colour, which is the copy a diff can read.
|
|
51
40
|
|
|
52
|
-
`bufferToSvg` gives one a repository page can show, because an `.ans` file is
|
|
53
|
-
only a screenshot on a terminal:
|
|
41
|
+
`bufferToSvg` gives one a repository page can show, because an `.ans` file is only a screenshot on a terminal:
|
|
54
42
|
|
|
55
43
|
```ts
|
|
56
44
|
const svg = bufferToSvg(app.buffer(), {
|
|
@@ -59,25 +47,26 @@ const svg = bufferToSvg(app.buffer(), {
|
|
|
59
47
|
});
|
|
60
48
|
```
|
|
61
49
|
|
|
62
|
-
Self-contained - no font, no stylesheet, no script, nothing fetched - which is
|
|
63
|
-
what survives GitHub's image proxy. And it is text, so a committed screenshot
|
|
64
|
-
diffs and CI can check it instead of somebody re-taking it.
|
|
50
|
+
Self-contained - no font, no stylesheet, no script, nothing fetched - which is what survives GitHub's image proxy. And it is text, so a committed screenshot diffs and CI can check it instead of somebody re-taking it.
|
|
65
51
|
|
|
66
52
|
## What it needs from a stream
|
|
67
53
|
|
|
68
|
-
`TerminalInput` and `TerminalOutput`, which say what the adapter uses rather
|
|
69
|
-
than which stream it is - `setRawMode`, `columns`, `write`, and half a dozen
|
|
70
|
-
more. `process.stdin` and `process.stdout` satisfy them, so nothing changes for
|
|
71
|
-
a caller, and so do the streams of runtimes that are not node.
|
|
54
|
+
`TerminalInput` and `TerminalOutput`, which say what the adapter uses rather than which stream it is - `setRawMode`, `columns`, `write`, and half a dozen more. `process.stdin` and `process.stdout` satisfy them, so nothing changes for a caller, and so do the streams of runtimes that are not node.
|
|
72
55
|
|
|
73
|
-
That is also why the published types name no Node interfaces: a package with no
|
|
74
|
-
dependencies should not need `@types/node` installed to compile.
|
|
56
|
+
That is also why the published types name no Node interfaces: a package with no dependencies should not need `@types/node` installed to compile.
|
|
75
57
|
|
|
76
58
|
## Runtime
|
|
77
59
|
|
|
78
|
-
No dependencies beyond `@textui/core
|
|
79
|
-
no other Node API. Node 22+ and Bun.
|
|
60
|
+
No dependencies beyond [`@textui/core`](https://www.npmjs.com/package/@textui/core). Uses `process` for stdio and signals, and no other Node API. Node 22+ and Bun.
|
|
80
61
|
|
|
81
62
|
## Documentation
|
|
82
63
|
|
|
83
64
|
<https://softov.github.io/textui/>
|
|
65
|
+
|
|
66
|
+
<!-- family -->
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
Part of **[TextUI](https://github.com/softov/textui)** - [documentation](https://softov.github.io/textui/) - [getting started](https://softov.github.io/textui/getting-started.html)
|
|
71
|
+
|
|
72
|
+
[`@textui/kit`](https://www.npmjs.com/package/@textui/kit) one install · [`@textui/core`](https://www.npmjs.com/package/@textui/core) the runtime · [`@textui/widgets`](https://www.npmjs.com/package/@textui/widgets) the catalog · **`@textui/terminal`** adapters and input · [`@textui/testing`](https://www.npmjs.com/package/@textui/testing) the harness · [`@textui/cli`](https://www.npmjs.com/package/@textui/cli) the CLI
|
package/dist/ansi.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export declare const cursorForward: (n?: number) => string;
|
|
|
23
23
|
export declare const cursorBack: (n?: number) => string;
|
|
24
24
|
export declare const cursorColumn: (x: number) => string;
|
|
25
25
|
/** Cursor shapes, for a text field that wants a bar rather than a block. */
|
|
26
|
+
/** DECSCUSR 0: back to whatever the terminal was configured for. */
|
|
27
|
+
export declare const cursorShapeReset = "\u001B[0 q";
|
|
26
28
|
export declare const cursorShape: (shape: "block" | "underline" | "bar", blinking?: boolean) => string;
|
|
27
29
|
export declare const eraseLine = "\u001B[2K";
|
|
28
30
|
export declare const eraseLineRight = "\u001B[0K";
|
package/dist/ansi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ansi.d.ts","sourceRoot":"","sources":["../src/ansi.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG,WAAS,CAAC;AAC1B,eAAO,MAAM,GAAG,YAAU,CAAC;AAC3B,eAAO,MAAM,GAAG,YAAU,CAAC;AAC3B,eAAO,MAAM,EAAE,aAAW,CAAC;AAC3B,eAAO,MAAM,GAAG,WAAS,CAAC;AAG1B,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAAoC,CAAC;AACrF,eAAO,MAAM,UAAU,aAAY,CAAC;AACpC,eAAO,MAAM,UAAU,gBAAe,CAAC;AACvC,eAAO,MAAM,UAAU,gBAAe,CAAC;AACvC,eAAO,MAAM,UAAU,YAAY,CAAC;AACpC,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,eAAO,MAAM,QAAQ,GAAI,UAAK,KAAG,MAAuB,CAAC;AACzD,eAAO,MAAM,UAAU,GAAI,UAAK,KAAG,MAAuB,CAAC;AAC3D,eAAO,MAAM,aAAa,GAAI,UAAK,KAAG,MAAuB,CAAC;AAC9D,eAAO,MAAM,UAAU,GAAI,UAAK,KAAG,MAAuB,CAAC;AAC3D,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,KAAG,MAA2B,CAAC;AAErE,4EAA4E;AAC5E,eAAO,MAAM,WAAW,GACtB,OAAO,OAAO,GAAG,WAAW,GAAG,KAAK,EACpC,kBAAe,KACd,MAGF,CAAC;AAGF,eAAO,MAAM,SAAS,cAAa,CAAC;AACpC,eAAO,MAAM,cAAc,cAAa,CAAC;AACzC,eAAO,MAAM,WAAW,cAAa,CAAC;AACtC,eAAO,MAAM,SAAS,cAAa,CAAC;AACpC,eAAO,MAAM,QAAQ,GAAI,UAAK,KAAG,MAAuB,CAAC;AAGzD,eAAO,MAAM,cAAc,kBAAiB,CAAC;AAC7C,eAAO,MAAM,cAAc,kBAAiB,CAAC;AAC7C,eAAO,MAAM,gBAAgB,kBAAiB,CAAC;AAC/C,eAAO,MAAM,iBAAiB,kBAAiB,CAAC;AAChD,eAAO,MAAM,aAAa,kBAAiB,CAAC;AAC5C,eAAO,MAAM,cAAc,kBAAiB,CAAC;AAE7C,yEAAyE;AACzE,eAAO,MAAM,OAAO,yDAAqD,CAAC;AAC1E,eAAO,MAAM,QAAQ,yDAAqD,CAAC;AAC3E,wEAAwE;AACxE,eAAO,MAAM,cAAc,4CAAyC,CAAC;AACrE,eAAO,MAAM,eAAe,4CAAyC,CAAC;AAEtE,+DAA+D;AAC/D,eAAO,MAAM,SAAS,kBAAiB,CAAC;AACxC,eAAO,MAAM,OAAO,kBAAiB,CAAC;AAEtC,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,eAAc,CAAC;AAC7C,eAAO,MAAM,gBAAgB,cAAa,CAAC;AAE3C,eAAO,MAAM,OAAO,eAAc,CAAC;AACnC,eAAO,MAAM,MAAM,eAAc,CAAC;AAGlC,eAAO,MAAM,qBAAqB,aAAY,CAAC;AAC/C,eAAO,MAAM,mBAAmB,cAAa,CAAC;AAC9C,uCAAuC;AACvC,eAAO,MAAM,cAAc,4BAA4B,CAAC;AACxD,eAAO,MAAM,SAAS,mBAAkB,CAAC;AAGzC,eAAO,MAAM,QAAQ,GAAI,OAAO,MAAM,KAAG,MAA+C,CAAC;AACzF,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,EAAE,MAAM,MAAM,KAAG,MACS,CAAC;AAC3D,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,KAAG,MAA6C,CAAC;AACrF,eAAO,MAAM,SAAS,uBAAmB,CAAC;AAE1C,gEAAgE;AAChE,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,MAK7C,CAAC;AASF,eAAO,MAAM,KAAK,cAAa,CAAC;AAEhC,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;;;;;CAmBN,CAAC"}
|
|
1
|
+
{"version":3,"file":"ansi.d.ts","sourceRoot":"","sources":["../src/ansi.ts"],"names":[],"mappings":"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"}
|
package/dist/ansi.js
CHANGED
|
@@ -24,6 +24,8 @@ export const cursorForward = (n = 1) => `${CSI}${n}C`;
|
|
|
24
24
|
export const cursorBack = (n = 1) => `${CSI}${n}D`;
|
|
25
25
|
export const cursorColumn = (x) => `${CSI}${x + 1}G`;
|
|
26
26
|
/** Cursor shapes, for a text field that wants a bar rather than a block. */
|
|
27
|
+
/** DECSCUSR 0: back to whatever the terminal was configured for. */
|
|
28
|
+
export const cursorShapeReset = `${CSI}0 q`;
|
|
27
29
|
export const cursorShape = (shape, blinking = true) => {
|
|
28
30
|
const base = shape === 'block' ? 1 : shape === 'underline' ? 3 : 5;
|
|
29
31
|
return `${CSI}${blinking ? base : base + 1} q`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGxG,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,UAAU,CA4BpG;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,YAAY,CAYnF;AAWD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGxG,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,UAAU,CA4BpG;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,YAAY,CAYnF;AAWD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,CAgE9E;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,oBAAoB,EAC1B,SAAS,EAAE,mBAAmB,GAAG,SAAS,GACzC,oBAAoB,CAEtB;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYjF"}
|
package/dist/capabilities.js
CHANGED
|
@@ -101,7 +101,25 @@ export function detectCapabilities(input) {
|
|
|
101
101
|
paste: true,
|
|
102
102
|
// tmux rewrites OSC 8, and Apple Terminal ignores it.
|
|
103
103
|
hyperlinks: modern && !mux && program !== 'Apple_Terminal',
|
|
104
|
-
|
|
104
|
+
/*
|
|
105
|
+
* OSC 52, and on for anything that is not known to mangle it.
|
|
106
|
+
*
|
|
107
|
+
* This used to require a recognised terminal, which switched it off in
|
|
108
|
+
* precisely the situation it exists for. Over ssh none of the variables
|
|
109
|
+
* that name a terminal survive the hop - `TERM_PROGRAM`, `KITTY_WINDOW_ID`
|
|
110
|
+
* and `WT_SESSION` are all set by the terminal on the machine you are
|
|
111
|
+
* sitting at, not the one the program is running on - so a remote session
|
|
112
|
+
* saw a bare `xterm-256color` and refused to copy. Reaching the clipboard
|
|
113
|
+
* of a machine the program is not running on is the entire reason OSC 52
|
|
114
|
+
* was specified.
|
|
115
|
+
*
|
|
116
|
+
* Assuming it works is safe in a way that assuming most sequences work is
|
|
117
|
+
* not: a terminal that does not implement OSC 52 ignores the whole string
|
|
118
|
+
* rather than printing part of it, because an OSC runs to its terminator.
|
|
119
|
+
* The one that does not is `screen`, which needs the payload wrapped in
|
|
120
|
+
* its own passthrough and shows it as text otherwise.
|
|
121
|
+
*/
|
|
122
|
+
clipboard: !(env.TERM ?? '').startsWith('screen'),
|
|
105
123
|
altScreen: true,
|
|
106
124
|
cursor: true,
|
|
107
125
|
// Synchronized output is safe when supported and harmless when not, but
|
package/dist/input.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../src/input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAwB,MAAM,cAAc,CAAC;AAiErE,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,YAAY;IAOrB,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,OAAO;IAPjB,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,WAAW,CAA8C;gBAGvD,IAAI,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,EACjC,OAAO,GAAE,cAAmB;IAGtC,iDAAiD;IACjD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAqBzB,OAAO,CAAC,gBAAgB;IAOxB,sEAAsE;IACtE,OAAO,CAAC,IAAI;IAyDZ,OAAO,CAAC,SAAS;IA8CjB,OAAO,CAAC,SAAS;IAkBjB,wEAAwE;IACxE,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,OAAO;IA8Ff,iEAAiE;IACjE,KAAK,IAAI,IAAI;CAMd;
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../src/input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAwB,MAAM,cAAc,CAAC;AAiErE,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,YAAY;IAOrB,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,OAAO;IAPjB,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,WAAW,CAA8C;gBAGvD,IAAI,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,EACjC,OAAO,GAAE,cAAmB;IAGtC,iDAAiD;IACjD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAqBzB,OAAO,CAAC,gBAAgB;IAOxB,sEAAsE;IACtE,OAAO,CAAC,IAAI;IAyDZ,OAAO,CAAC,SAAS;IA8CjB,OAAO,CAAC,SAAS;IAkBjB,wEAAwE;IACxE,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,OAAO;IA8Ff,iEAAiE;IACjE,KAAK,IAAI,IAAI;CAMd;AAgDD,wBAAgB,aAAa,CAC3B,IAAI,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,EACjC,OAAO,CAAC,EAAE,cAAc,GACvB,YAAY,CAEd"}
|
package/dist/input.js
CHANGED
|
@@ -333,6 +333,10 @@ function decodeSgrMouse(body, final) {
|
|
|
333
333
|
x: col - 1,
|
|
334
334
|
y: row - 1,
|
|
335
335
|
ctrl, alt, shift,
|
|
336
|
+
// Stamped on arrival, because nothing downstream can tell a double click
|
|
337
|
+
// from two clicks without knowing when each one landed. The wire says
|
|
338
|
+
// press and release and nothing else.
|
|
339
|
+
at: Date.now(),
|
|
336
340
|
handled: false,
|
|
337
341
|
};
|
|
338
342
|
if (wheel) {
|
package/dist/node.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AcquiredState, CapabilityOverrides, Disposable, InputEvent, Size, TerminalAdapter, TerminalCapabilities, TerminalSessionOptions } from '@textui/core';
|
|
1
|
+
import type { AcquiredState, CapabilityOverrides, CursorStyle, Disposable, InputEvent, Size, TerminalAdapter, TerminalCapabilities, TerminalSessionOptions } from '@textui/core';
|
|
2
2
|
/**
|
|
3
3
|
* The Node TTY adapter.
|
|
4
4
|
*
|
|
@@ -91,6 +91,7 @@ export declare class NodeTerminalAdapter implements TerminalAdapter {
|
|
|
91
91
|
onResize(fn: (size: Size) => void): Disposable;
|
|
92
92
|
writeClipboard(text: string): void;
|
|
93
93
|
setTitle(title: string): void;
|
|
94
|
+
setCursorShape(shape: CursorStyle): void;
|
|
94
95
|
private installExitHandlers;
|
|
95
96
|
private removeExitHandlers;
|
|
96
97
|
dispose(): void;
|
package/dist/node.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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"}
|
package/dist/node.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createDecoder } from './input.js';
|
|
|
5
5
|
const NOTHING_ACQUIRED = {
|
|
6
6
|
altScreen: false, mouse: false, wheel: false, focusEvents: false,
|
|
7
7
|
paste: false, cursorHidden: false, enhancedKeys: false, rawMode: false,
|
|
8
|
-
titleSet: false,
|
|
8
|
+
titleSet: false, cursorShaped: false,
|
|
9
9
|
};
|
|
10
10
|
export class NodeTerminalAdapter {
|
|
11
11
|
options;
|
|
@@ -147,6 +147,8 @@ export class NodeTerminalAdapter {
|
|
|
147
147
|
out.push(ansi.altScreenLeave);
|
|
148
148
|
if (state.cursorHidden)
|
|
149
149
|
out.push(ansi.cursorShow);
|
|
150
|
+
if (state.cursorShaped)
|
|
151
|
+
out.push(ansi.cursorShapeReset);
|
|
150
152
|
out.push(ansi.reset);
|
|
151
153
|
try {
|
|
152
154
|
this.stdout.write(out.join(''));
|
|
@@ -199,6 +201,14 @@ export class NodeTerminalAdapter {
|
|
|
199
201
|
return;
|
|
200
202
|
this.stdout.write(ansi.setTitle(title));
|
|
201
203
|
}
|
|
204
|
+
setCursorShape(shape) {
|
|
205
|
+
// No session means nothing to put back, and nothing that would put it
|
|
206
|
+
// back - so a shape set outside one is a shape left on the user's shell.
|
|
207
|
+
if (!this.caps.cursor || !this.state)
|
|
208
|
+
return;
|
|
209
|
+
this.stdout.write(ansi.cursorShape(shape));
|
|
210
|
+
this.state.cursorShaped = true;
|
|
211
|
+
}
|
|
202
212
|
installExitHandlers() {
|
|
203
213
|
this.exitHandler = () => this.release();
|
|
204
214
|
process.on('exit', this.exitHandler);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@textui/terminal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Terminal adapters, capability detection, ANSI writing and input decoding for TextUI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"terminal",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"import": "./dist/virtual.js"
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"@textui/core": "^0.1.0"
|
|
53
|
-
},
|
|
54
|
-
"publishConfig": {
|
|
55
|
-
"access": "public"
|
|
56
|
-
},
|
|
57
51
|
"scripts": {
|
|
58
52
|
"build": "tsc -p tsconfig.json",
|
|
59
53
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json",
|
|
60
54
|
"test": "vitest run"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@textui/core": "^0.2.0"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
61
|
}
|
|
62
|
-
}
|
|
62
|
+
}
|
package/src/ansi.ts
CHANGED
|
@@ -26,6 +26,9 @@ export const cursorBack = (n = 1): string => `${CSI}${n}D`;
|
|
|
26
26
|
export const cursorColumn = (x: number): string => `${CSI}${x + 1}G`;
|
|
27
27
|
|
|
28
28
|
/** Cursor shapes, for a text field that wants a bar rather than a block. */
|
|
29
|
+
/** DECSCUSR 0: back to whatever the terminal was configured for. */
|
|
30
|
+
export const cursorShapeReset = `${CSI}0 q`;
|
|
31
|
+
|
|
29
32
|
export const cursorShape = (
|
|
30
33
|
shape: 'block' | 'underline' | 'bar',
|
|
31
34
|
blinking = true,
|
package/src/capabilities.ts
CHANGED
|
@@ -106,7 +106,25 @@ export function detectCapabilities(input: DetectionInput): TerminalCapabilities
|
|
|
106
106
|
paste: true,
|
|
107
107
|
// tmux rewrites OSC 8, and Apple Terminal ignores it.
|
|
108
108
|
hyperlinks: modern && !mux && program !== 'Apple_Terminal',
|
|
109
|
-
|
|
109
|
+
/*
|
|
110
|
+
* OSC 52, and on for anything that is not known to mangle it.
|
|
111
|
+
*
|
|
112
|
+
* This used to require a recognised terminal, which switched it off in
|
|
113
|
+
* precisely the situation it exists for. Over ssh none of the variables
|
|
114
|
+
* that name a terminal survive the hop - `TERM_PROGRAM`, `KITTY_WINDOW_ID`
|
|
115
|
+
* and `WT_SESSION` are all set by the terminal on the machine you are
|
|
116
|
+
* sitting at, not the one the program is running on - so a remote session
|
|
117
|
+
* saw a bare `xterm-256color` and refused to copy. Reaching the clipboard
|
|
118
|
+
* of a machine the program is not running on is the entire reason OSC 52
|
|
119
|
+
* was specified.
|
|
120
|
+
*
|
|
121
|
+
* Assuming it works is safe in a way that assuming most sequences work is
|
|
122
|
+
* not: a terminal that does not implement OSC 52 ignores the whole string
|
|
123
|
+
* rather than printing part of it, because an OSC runs to its terminator.
|
|
124
|
+
* The one that does not is `screen`, which needs the payload wrapped in
|
|
125
|
+
* its own passthrough and shows it as text otherwise.
|
|
126
|
+
*/
|
|
127
|
+
clipboard: !(env.TERM ?? '').startsWith('screen'),
|
|
110
128
|
altScreen: true,
|
|
111
129
|
cursor: true,
|
|
112
130
|
// Synchronized output is safe when supported and harmless when not, but
|
package/src/input.ts
CHANGED
|
@@ -362,6 +362,10 @@ function decodeSgrMouse(body: string, final: string): MouseEvent | null {
|
|
|
362
362
|
x: col - 1,
|
|
363
363
|
y: row - 1,
|
|
364
364
|
ctrl, alt, shift,
|
|
365
|
+
// Stamped on arrival, because nothing downstream can tell a double click
|
|
366
|
+
// from two clicks without knowing when each one landed. The wire says
|
|
367
|
+
// press and release and nothing else.
|
|
368
|
+
at: Date.now(),
|
|
365
369
|
handled: false,
|
|
366
370
|
};
|
|
367
371
|
|
package/src/node.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AcquiredState, CapabilityOverrides, Disposable, InputEvent, Size,
|
|
3
|
-
TerminalAdapter, TerminalCapabilities, TerminalSessionOptions,
|
|
4
|
-
} from '@textui/core';
|
|
1
|
+
import type { AcquiredState, CapabilityOverrides, CursorStyle, Disposable, InputEvent, Size, TerminalAdapter, TerminalCapabilities, TerminalSessionOptions } from '@textui/core';
|
|
5
2
|
import { toDisposable } from '@textui/core';
|
|
6
3
|
import * as ansi from './ansi.js';
|
|
7
4
|
import { applyOverrides, detectCapabilities, describeEnvironment } from './capabilities.js';
|
|
@@ -71,7 +68,7 @@ export interface NodeAdapterOptions {
|
|
|
71
68
|
const NOTHING_ACQUIRED: AcquiredState = {
|
|
72
69
|
altScreen: false, mouse: false, wheel: false, focusEvents: false,
|
|
73
70
|
paste: false, cursorHidden: false, enhancedKeys: false, rawMode: false,
|
|
74
|
-
titleSet: false,
|
|
71
|
+
titleSet: false, cursorShaped: false,
|
|
75
72
|
};
|
|
76
73
|
|
|
77
74
|
export class NodeTerminalAdapter implements TerminalAdapter {
|
|
@@ -235,6 +232,7 @@ export class NodeTerminalAdapter implements TerminalAdapter {
|
|
|
235
232
|
if (state.mouse) out.push(state.wheel ? ansi.mouseOff : ansi.mouseButtonsOff);
|
|
236
233
|
if (state.altScreen) out.push(ansi.altScreenLeave);
|
|
237
234
|
if (state.cursorHidden) out.push(ansi.cursorShow);
|
|
235
|
+
if (state.cursorShaped) out.push(ansi.cursorShapeReset);
|
|
238
236
|
out.push(ansi.reset);
|
|
239
237
|
|
|
240
238
|
try {
|
|
@@ -291,6 +289,14 @@ export class NodeTerminalAdapter implements TerminalAdapter {
|
|
|
291
289
|
this.stdout.write(ansi.setTitle(title));
|
|
292
290
|
}
|
|
293
291
|
|
|
292
|
+
setCursorShape(shape: CursorStyle): void {
|
|
293
|
+
// No session means nothing to put back, and nothing that would put it
|
|
294
|
+
// back - so a shape set outside one is a shape left on the user's shell.
|
|
295
|
+
if (!this.caps.cursor || !this.state) return;
|
|
296
|
+
this.stdout.write(ansi.cursorShape(shape));
|
|
297
|
+
this.state.cursorShaped = true;
|
|
298
|
+
}
|
|
299
|
+
|
|
294
300
|
private installExitHandlers(): void {
|
|
295
301
|
this.exitHandler = () => this.release();
|
|
296
302
|
process.on('exit', this.exitHandler);
|