@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 CHANGED
@@ -1,7 +1,8 @@
1
1
  # @textui/terminal
2
2
 
3
- What [`@textui/core`](https://github.com/softov/textui/tree/main/packages/core) needs to reach an actual terminal: adapters,
4
- capability detection, ANSI writing and input decoding.
3
+ [![npm](https://img.shields.io/npm/v/@textui/terminal?label=%40textui%2Fterminal)](https://www.npmjs.com/package/@textui/terminal) [![license](https://img.shields.io/npm/l/@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`. Uses `process` for stdio and signals, and
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";
@@ -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,CA8C9E;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"}
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"}
@@ -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
- clipboard: modern || mux,
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
@@ -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;AA4CD,wBAAgB,aAAa,CAC3B,IAAI,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,EACjC,OAAO,CAAC,EAAE,cAAc,GACvB,YAAY,CAEd"}
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;
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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"}
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.1.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,
@@ -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
- clipboard: modern || mux,
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);