@teammates/consolonia 0.7.0 → 0.7.2

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/dist/ansi/esc.js CHANGED
@@ -74,12 +74,94 @@ export const eraseLine = `${ESC}2K`;
74
74
  export const bracketedPasteOn = `${ESC}?2004h`;
75
75
  /** Disable bracketed paste mode. */
76
76
  export const bracketedPasteOff = `${ESC}?2004l`;
77
- /** Enable SGR mouse tracking (any-event tracking + SGR extended coordinates). */
78
- export const mouseTrackingOn = `${ESC}?1003h${ESC}?1006h`;
79
- /** Disable SGR mouse tracking. */
80
- export const mouseTrackingOff = `${ESC}?1006l${ESC}?1003l`;
77
+ /**
78
+ * Enable mouse tracking with all supported reporting modes.
79
+ *
80
+ * Modes enabled (in order):
81
+ * ?1000h — Normal/VT200 click tracking (press + release)
82
+ * ?1003h — Any-event tracking (all mouse movement)
83
+ * ?1005h — UTF-8 coordinate encoding (extends X10 beyond col/row 223)
84
+ * ?1006h — SGR extended coordinates (";"-delimited decimals, M/m terminator)
85
+ * ?1015h — URXVT extended coordinates (decimal params, no "<" prefix)
86
+ * ?1016h — SGR-Pixels (same wire format as SGR, pixel coordinates)
87
+ *
88
+ * Terminals pick the highest mode they support. SGR is preferred by most
89
+ * modern terminals; URXVT and UTF-8 provide fallback for older ones.
90
+ */
91
+ export const mouseTrackingOn = `${ESC}?1000h${ESC}?1003h${ESC}?1005h${ESC}?1006h${ESC}?1015h${ESC}?1016h`;
92
+ /**
93
+ * Disable all mouse tracking modes (reverse order of enable).
94
+ */
95
+ export const mouseTrackingOff = `${ESC}?1016l${ESC}?1015l${ESC}?1006l${ESC}?1005l${ESC}?1003l${ESC}?1000l`;
81
96
  // ── Window ──────────────────────────────────────────────────────────
82
97
  /** Set the terminal window title. */
83
98
  export function setTitle(title) {
84
99
  return `${OSC}0;${title}\x07`;
85
100
  }
101
+ // ── Environment-aware init/restore ─────────────────────────────────
102
+ /**
103
+ * Mouse tracking sequence tailored to detected capabilities.
104
+ *
105
+ * When the terminal supports SGR, request all six modes so the terminal
106
+ * picks the highest one it handles. When SGR is not available (e.g. GNU
107
+ * screen), fall back to normal + any-event tracking only — UTF-8 and
108
+ * URXVT modes could confuse terminals that don't understand them.
109
+ */
110
+ export function mouseOn(caps) {
111
+ if (!caps.mouse)
112
+ return "";
113
+ if (caps.sgrMouse)
114
+ return mouseTrackingOn;
115
+ // Minimal: normal click + any-event only
116
+ return `${ESC}?1000h${ESC}?1003h`;
117
+ }
118
+ /** Matching disable sequence for mouseOn(). */
119
+ export function mouseOff(caps) {
120
+ if (!caps.mouse)
121
+ return "";
122
+ if (caps.sgrMouse)
123
+ return mouseTrackingOff;
124
+ return `${ESC}?1003l${ESC}?1000l`;
125
+ }
126
+ /**
127
+ * Build the full terminal preparation sequence for the given environment.
128
+ *
129
+ * @param caps - Detected terminal capabilities
130
+ * @param opts - Which optional features the app requested
131
+ */
132
+ export function initSequence(caps, opts) {
133
+ if (!caps.isTTY)
134
+ return "";
135
+ let seq = "";
136
+ if (opts.alternateScreen && caps.alternateScreen) {
137
+ seq += alternateScreenOn;
138
+ }
139
+ seq += hideCursor;
140
+ if (caps.bracketedPaste) {
141
+ seq += bracketedPasteOn;
142
+ }
143
+ if (opts.mouse) {
144
+ seq += mouseOn(caps);
145
+ }
146
+ seq += clearScreen;
147
+ return seq;
148
+ }
149
+ /**
150
+ * Build the full terminal restore sequence (mirror of initSequence).
151
+ */
152
+ export function restoreSequence(caps, opts) {
153
+ if (!caps.isTTY)
154
+ return "";
155
+ let seq = reset;
156
+ if (opts.mouse) {
157
+ seq += mouseOff(caps);
158
+ }
159
+ if (caps.bracketedPaste) {
160
+ seq += bracketedPasteOff;
161
+ }
162
+ seq += showCursor;
163
+ if (opts.alternateScreen && caps.alternateScreen) {
164
+ seq += alternateScreenOff;
165
+ }
166
+ return seq;
167
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Terminal environment detection.
3
+ *
4
+ * Probes environment variables and process state to determine which
5
+ * terminal capabilities are available. Used by App to send only the
6
+ * escape sequences the host terminal actually supports.
7
+ */
8
+ export interface TerminalCaps {
9
+ /** Terminal is a TTY (not piped). */
10
+ isTTY: boolean;
11
+ /** Supports alternate screen buffer (?1049h). */
12
+ alternateScreen: boolean;
13
+ /** Supports bracketed paste mode (?2004h). */
14
+ bracketedPaste: boolean;
15
+ /** Supports escape-based mouse tracking (?1000h and above). */
16
+ mouse: boolean;
17
+ /** Supports SGR extended mouse encoding (?1006h). */
18
+ sgrMouse: boolean;
19
+ /** Supports truecolor (24-bit) SGR sequences. */
20
+ truecolor: boolean;
21
+ /** Supports 256-color SGR sequences. */
22
+ color256: boolean;
23
+ /** Detected terminal name (for diagnostics). */
24
+ name: string;
25
+ }
26
+ /**
27
+ * Detect terminal capabilities from the current environment.
28
+ *
29
+ * On Windows the main differentiator is whether we're running under
30
+ * Windows Terminal / ConPTY (full VT support) or legacy conhost
31
+ * (very limited escape handling). On Unix the TERM variable and
32
+ * TERM_PROGRAM give us enough signal.
33
+ */
34
+ export declare function detectTerminal(): TerminalCaps;
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Terminal environment detection.
3
+ *
4
+ * Probes environment variables and process state to determine which
5
+ * terminal capabilities are available. Used by App to send only the
6
+ * escape sequences the host terminal actually supports.
7
+ */
8
+ // ── Detection ───────────────────────────────────────────────────────
9
+ /**
10
+ * Detect terminal capabilities from the current environment.
11
+ *
12
+ * On Windows the main differentiator is whether we're running under
13
+ * Windows Terminal / ConPTY (full VT support) or legacy conhost
14
+ * (very limited escape handling). On Unix the TERM variable and
15
+ * TERM_PROGRAM give us enough signal.
16
+ */
17
+ export function detectTerminal() {
18
+ const env = process.env;
19
+ const isTTY = !!process.stdout.isTTY;
20
+ if (!isTTY) {
21
+ return {
22
+ isTTY: false,
23
+ alternateScreen: false,
24
+ bracketedPaste: false,
25
+ mouse: false,
26
+ sgrMouse: false,
27
+ truecolor: false,
28
+ color256: false,
29
+ name: "pipe",
30
+ };
31
+ }
32
+ // ── Windows ─────────────────────────────────────────────────────
33
+ if (process.platform === "win32") {
34
+ // Windows Terminal sets WT_SESSION
35
+ if (env.WT_SESSION) {
36
+ return {
37
+ isTTY: true,
38
+ alternateScreen: true,
39
+ bracketedPaste: true,
40
+ mouse: true,
41
+ sgrMouse: true,
42
+ truecolor: true,
43
+ color256: true,
44
+ name: "windows-terminal",
45
+ };
46
+ }
47
+ // VS Code's integrated terminal (xterm.js)
48
+ if (env.TERM_PROGRAM === "vscode") {
49
+ return {
50
+ isTTY: true,
51
+ alternateScreen: true,
52
+ bracketedPaste: true,
53
+ mouse: true,
54
+ sgrMouse: true,
55
+ truecolor: true,
56
+ color256: true,
57
+ name: "vscode",
58
+ };
59
+ }
60
+ // ConEmu / Cmder
61
+ if (env.ConEmuPID) {
62
+ return {
63
+ isTTY: true,
64
+ alternateScreen: true,
65
+ bracketedPaste: true,
66
+ mouse: true,
67
+ sgrMouse: true,
68
+ truecolor: true,
69
+ color256: true,
70
+ name: "conemu",
71
+ };
72
+ }
73
+ // Mintty (Git Bash) — sets TERM=xterm*
74
+ if (env.TERM?.startsWith("xterm") && env.MSYSTEM) {
75
+ return {
76
+ isTTY: true,
77
+ alternateScreen: true,
78
+ bracketedPaste: true,
79
+ mouse: true,
80
+ sgrMouse: true,
81
+ truecolor: true,
82
+ color256: true,
83
+ name: "mintty",
84
+ };
85
+ }
86
+ // Fallback: modern Windows 10+ conhost with ConPTY has decent VT
87
+ // support, but mouse tracking can be unreliable. Enable everything
88
+ // and let the terminal silently ignore what it doesn't support.
89
+ return {
90
+ isTTY: true,
91
+ alternateScreen: true,
92
+ bracketedPaste: true,
93
+ mouse: true,
94
+ sgrMouse: true,
95
+ truecolor: true,
96
+ color256: true,
97
+ name: "conhost",
98
+ };
99
+ }
100
+ // ── Unix / macOS ────────────────────────────────────────────────
101
+ const term = env.TERM ?? "";
102
+ const termProgram = env.TERM_PROGRAM ?? "";
103
+ // tmux — full VT support, passes through SGR mouse
104
+ if (env.TMUX || term.startsWith("tmux") || term === "screen-256color") {
105
+ return {
106
+ isTTY: true,
107
+ alternateScreen: true,
108
+ bracketedPaste: true,
109
+ mouse: true,
110
+ sgrMouse: true,
111
+ truecolor: !!env.COLORTERM || term.includes("256color"),
112
+ color256: true,
113
+ name: "tmux",
114
+ };
115
+ }
116
+ // GNU screen — limited mouse support, no SGR
117
+ if (term === "screen" && !env.TMUX) {
118
+ return {
119
+ isTTY: true,
120
+ alternateScreen: true,
121
+ bracketedPaste: false,
122
+ mouse: true,
123
+ sgrMouse: false,
124
+ truecolor: false,
125
+ color256: false,
126
+ name: "screen",
127
+ };
128
+ }
129
+ // iTerm2
130
+ if (termProgram === "iTerm.app" || env.ITERM_SESSION_ID) {
131
+ return {
132
+ isTTY: true,
133
+ alternateScreen: true,
134
+ bracketedPaste: true,
135
+ mouse: true,
136
+ sgrMouse: true,
137
+ truecolor: true,
138
+ color256: true,
139
+ name: "iterm2",
140
+ };
141
+ }
142
+ // VS Code integrated terminal (macOS/Linux)
143
+ if (termProgram === "vscode") {
144
+ return {
145
+ isTTY: true,
146
+ alternateScreen: true,
147
+ bracketedPaste: true,
148
+ mouse: true,
149
+ sgrMouse: true,
150
+ truecolor: true,
151
+ color256: true,
152
+ name: "vscode",
153
+ };
154
+ }
155
+ // Alacritty
156
+ if (termProgram === "Alacritty" || term === "alacritty") {
157
+ return {
158
+ isTTY: true,
159
+ alternateScreen: true,
160
+ bracketedPaste: true,
161
+ mouse: true,
162
+ sgrMouse: true,
163
+ truecolor: true,
164
+ color256: true,
165
+ name: "alacritty",
166
+ };
167
+ }
168
+ // xterm-compatible (most Linux/macOS terminals)
169
+ if (term.startsWith("xterm") || term.includes("256color")) {
170
+ const hasTruecolor = env.COLORTERM === "truecolor" || env.COLORTERM === "24bit";
171
+ return {
172
+ isTTY: true,
173
+ alternateScreen: true,
174
+ bracketedPaste: true,
175
+ mouse: true,
176
+ sgrMouse: true,
177
+ truecolor: hasTruecolor,
178
+ color256: true,
179
+ name: term,
180
+ };
181
+ }
182
+ // Dumb terminal — absolute minimum
183
+ if (term === "dumb" || !term) {
184
+ return {
185
+ isTTY: true,
186
+ alternateScreen: false,
187
+ bracketedPaste: false,
188
+ mouse: false,
189
+ sgrMouse: false,
190
+ truecolor: false,
191
+ color256: false,
192
+ name: term || "unknown",
193
+ };
194
+ }
195
+ // Unknown but it is a TTY — enable common capabilities
196
+ return {
197
+ isTTY: true,
198
+ alternateScreen: true,
199
+ bracketedPaste: true,
200
+ mouse: true,
201
+ sgrMouse: false,
202
+ truecolor: false,
203
+ color256: true,
204
+ name: term,
205
+ };
206
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Win32 Console Mode — enables mouse input on Windows terminals.
3
+ *
4
+ * Node.js `setRawMode(true)` enables ENABLE_VIRTUAL_TERMINAL_INPUT but
5
+ * does NOT disable ENABLE_QUICK_EDIT_MODE (which intercepts mouse clicks
6
+ * for text selection) or enable ENABLE_MOUSE_INPUT. This module uses
7
+ * koffi to call the Win32 API directly and set the correct flags.
8
+ *
9
+ * Only loaded on win32 — no-ops on other platforms.
10
+ */
11
+ /**
12
+ * Configure the Windows console for mouse input.
13
+ *
14
+ * Disables Quick Edit Mode (which swallows mouse clicks) and enables
15
+ * ENABLE_MOUSE_INPUT + ENABLE_EXTENDED_FLAGS + ENABLE_WINDOW_INPUT.
16
+ * Saves the original mode so it can be restored later.
17
+ *
18
+ * No-op on non-Windows platforms or if koffi is not available.
19
+ * Returns true if the mode was successfully changed.
20
+ */
21
+ export declare function enableWin32Mouse(): boolean;
22
+ /**
23
+ * Restore the original Windows console mode saved by enableWin32Mouse().
24
+ *
25
+ * No-op if enableWin32Mouse() was never called or failed.
26
+ * Returns true if the mode was successfully restored.
27
+ */
28
+ export declare function restoreWin32Console(): boolean;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Win32 Console Mode — enables mouse input on Windows terminals.
3
+ *
4
+ * Node.js `setRawMode(true)` enables ENABLE_VIRTUAL_TERMINAL_INPUT but
5
+ * does NOT disable ENABLE_QUICK_EDIT_MODE (which intercepts mouse clicks
6
+ * for text selection) or enable ENABLE_MOUSE_INPUT. This module uses
7
+ * koffi to call the Win32 API directly and set the correct flags.
8
+ *
9
+ * Only loaded on win32 — no-ops on other platforms.
10
+ */
11
+ import { createRequire } from "node:module";
12
+ // ── Console mode flag constants ─────────────────────────────────────
13
+ const ENABLE_PROCESSED_INPUT = 0x0001;
14
+ const ENABLE_LINE_INPUT = 0x0002;
15
+ const ENABLE_ECHO_INPUT = 0x0004;
16
+ const ENABLE_WINDOW_INPUT = 0x0008;
17
+ const ENABLE_MOUSE_INPUT = 0x0010;
18
+ const ENABLE_QUICK_EDIT_MODE = 0x0040;
19
+ const ENABLE_EXTENDED_FLAGS = 0x0080;
20
+ const ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;
21
+ const STD_INPUT_HANDLE = -10;
22
+ // ── State ───────────────────────────────────────────────────────────
23
+ let originalMode = null;
24
+ let _kernel32;
25
+ function getKernel32() {
26
+ if (_kernel32 !== undefined)
27
+ return _kernel32;
28
+ try {
29
+ // koffi is an optional native dependency — dynamic require so the
30
+ // module loads cleanly even when koffi is absent.
31
+ const require = createRequire(import.meta.url);
32
+ const koffi = require("koffi");
33
+ const lib = koffi.load("kernel32.dll");
34
+ _kernel32 = {
35
+ GetStdHandle: lib.func("void* __stdcall GetStdHandle(int nStdHandle)"),
36
+ GetConsoleMode: lib.func("bool __stdcall GetConsoleMode(void* hConsoleHandle, _Out_ uint32_t* lpMode)"),
37
+ SetConsoleMode: lib.func("bool __stdcall SetConsoleMode(void* hConsoleHandle, uint32_t dwMode)"),
38
+ };
39
+ }
40
+ catch {
41
+ _kernel32 = null;
42
+ }
43
+ return _kernel32;
44
+ }
45
+ // ── Public API ──────────────────────────────────────────────────────
46
+ /**
47
+ * Configure the Windows console for mouse input.
48
+ *
49
+ * Disables Quick Edit Mode (which swallows mouse clicks) and enables
50
+ * ENABLE_MOUSE_INPUT + ENABLE_EXTENDED_FLAGS + ENABLE_WINDOW_INPUT.
51
+ * Saves the original mode so it can be restored later.
52
+ *
53
+ * No-op on non-Windows platforms or if koffi is not available.
54
+ * Returns true if the mode was successfully changed.
55
+ */
56
+ export function enableWin32Mouse() {
57
+ if (process.platform !== "win32")
58
+ return false;
59
+ const k32 = getKernel32();
60
+ if (!k32)
61
+ return false;
62
+ try {
63
+ const handle = k32.GetStdHandle(STD_INPUT_HANDLE);
64
+ if (!handle)
65
+ return false;
66
+ // Read current mode
67
+ const modeBuffer = Buffer.alloc(4);
68
+ if (!k32.GetConsoleMode(handle, modeBuffer))
69
+ return false;
70
+ originalMode = modeBuffer.readUInt32LE(0);
71
+ // Build new mode:
72
+ // - Keep ENABLE_VIRTUAL_TERMINAL_INPUT (set by Node raw mode)
73
+ // - Add ENABLE_MOUSE_INPUT + ENABLE_WINDOW_INPUT + ENABLE_EXTENDED_FLAGS
74
+ // - Remove ENABLE_QUICK_EDIT_MODE
75
+ // - Remove line/echo/processed (already cleared by raw mode)
76
+ let newMode = originalMode;
77
+ newMode |= ENABLE_MOUSE_INPUT;
78
+ newMode |= ENABLE_WINDOW_INPUT;
79
+ newMode |= ENABLE_EXTENDED_FLAGS;
80
+ newMode &= ~ENABLE_QUICK_EDIT_MODE;
81
+ newMode &= ~ENABLE_LINE_INPUT;
82
+ newMode &= ~ENABLE_ECHO_INPUT;
83
+ newMode &= ~ENABLE_PROCESSED_INPUT;
84
+ // Preserve VT input if it was set
85
+ if (originalMode & ENABLE_VIRTUAL_TERMINAL_INPUT) {
86
+ newMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
87
+ }
88
+ return k32.SetConsoleMode(handle, newMode);
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }
94
+ /**
95
+ * Restore the original Windows console mode saved by enableWin32Mouse().
96
+ *
97
+ * No-op if enableWin32Mouse() was never called or failed.
98
+ * Returns true if the mode was successfully restored.
99
+ */
100
+ export function restoreWin32Console() {
101
+ if (process.platform !== "win32" || originalMode === null)
102
+ return false;
103
+ const k32 = getKernel32();
104
+ if (!k32) {
105
+ originalMode = null;
106
+ return false;
107
+ }
108
+ try {
109
+ const handle = k32.GetStdHandle(STD_INPUT_HANDLE);
110
+ if (!handle) {
111
+ originalMode = null;
112
+ return false;
113
+ }
114
+ const result = k32.SetConsoleMode(handle, originalMode);
115
+ originalMode = null;
116
+ return result;
117
+ }
118
+ catch {
119
+ originalMode = null;
120
+ return false;
121
+ }
122
+ }
package/dist/app.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * RenderTarget, DrawingContext, InputProcessor) and drives the
6
6
  * measure → arrange → render loop in response to input and resize events.
7
7
  */
8
+ import { type TerminalCaps } from "./ansi/terminal-env.js";
8
9
  import type { Control } from "./layout/control.js";
9
10
  export interface AppOptions {
10
11
  /** Root control to render. */
@@ -21,6 +22,7 @@ export declare class App {
21
22
  private readonly _alternateScreen;
22
23
  private readonly _mouse;
23
24
  private readonly _title;
25
+ private readonly _caps;
24
26
  private _output;
25
27
  private _buffer;
26
28
  private _dirtyRegions;
@@ -34,6 +36,8 @@ export declare class App {
34
36
  private _resizeListener;
35
37
  private _sigintListener;
36
38
  private _renderScheduled;
39
+ /** Detected terminal capabilities (read-only, for diagnostics). */
40
+ get caps(): Readonly<TerminalCaps>;
37
41
  constructor(options: AppOptions);
38
42
  /**
39
43
  * Start the app — enters raw mode, sets up terminal, runs the event
package/dist/app.js CHANGED
@@ -7,6 +7,8 @@
7
7
  */
8
8
  import * as esc from "./ansi/esc.js";
9
9
  import { AnsiOutput } from "./ansi/output.js";
10
+ import { detectTerminal } from "./ansi/terminal-env.js";
11
+ import { enableWin32Mouse, restoreWin32Console } from "./ansi/win32-console.js";
10
12
  import { DrawingContext } from "./drawing/context.js";
11
13
  import { createInputProcessor } from "./input/processor.js";
12
14
  import { disableRawMode, enableRawMode } from "./input/raw-mode.js";
@@ -19,6 +21,7 @@ export class App {
19
21
  _alternateScreen;
20
22
  _mouse;
21
23
  _title;
24
+ _caps;
22
25
  // Subsystems — created during run()
23
26
  _output;
24
27
  _buffer;
@@ -34,11 +37,16 @@ export class App {
34
37
  _resizeListener = null;
35
38
  _sigintListener = null;
36
39
  _renderScheduled = false;
40
+ /** Detected terminal capabilities (read-only, for diagnostics). */
41
+ get caps() {
42
+ return this._caps;
43
+ }
37
44
  constructor(options) {
38
45
  this.root = options.root;
39
46
  this._alternateScreen = options.alternateScreen ?? true;
40
47
  this._mouse = options.mouse ?? false;
41
48
  this._title = options.title;
49
+ this._caps = detectTerminal();
42
50
  }
43
51
  // ── Public API ───────────────────────────────────────────────────
44
52
  /**
@@ -90,54 +98,50 @@ export class App {
90
98
  const stdout = process.stdout;
91
99
  // 1. Enable raw mode
92
100
  enableRawMode();
93
- // 2. Create ANSI output
101
+ // 2. On Windows, configure console mode for mouse input
102
+ // (must happen after raw mode so we modify the right base flags)
103
+ if (this._mouse) {
104
+ enableWin32Mouse();
105
+ }
106
+ // 3. Create ANSI output
94
107
  this._output = new AnsiOutput(stdout);
95
- // 3. Prepare terminal (custom sequence instead of prepareTerminal()
108
+ // 4. Prepare terminal (custom sequence instead of prepareTerminal()
96
109
  // so we can conditionally enable mouse tracking)
97
110
  this._prepareTerminal();
98
- // 4. Set terminal title
111
+ // 5. Set terminal title
99
112
  if (this._title) {
100
113
  stdout.write(esc.setTitle(this._title));
101
114
  }
102
- // 5. Create pixel buffer at terminal dimensions
115
+ // 6. Create pixel buffer at terminal dimensions
103
116
  const cols = stdout.columns || 80;
104
117
  const rows = stdout.rows || 24;
105
118
  this._createRenderPipeline(cols, rows);
106
- // 6. Wire up input
119
+ // 7. Wire up input
107
120
  this._setupInput();
108
- // 7. Wire up resize
121
+ // 8. Wire up resize
109
122
  this._resizeListener = () => this._handleResize();
110
123
  stdout.on("resize", this._resizeListener);
111
- // 8. SIGINT fallback
124
+ // 9. SIGINT fallback
112
125
  this._sigintListener = () => this.stop();
113
126
  process.on("SIGINT", this._sigintListener);
114
127
  }
115
128
  _prepareTerminal() {
116
129
  const stream = process.stdout;
117
- let seq = "";
118
- if (this._alternateScreen) {
119
- seq += esc.alternateScreenOn;
120
- }
121
- seq += esc.hideCursor;
122
- seq += esc.bracketedPasteOn;
123
- if (this._mouse) {
124
- seq += esc.mouseTrackingOn;
125
- }
126
- seq += esc.clearScreen;
127
- stream.write(seq);
130
+ const seq = esc.initSequence(this._caps, {
131
+ alternateScreen: this._alternateScreen,
132
+ mouse: this._mouse,
133
+ });
134
+ if (seq)
135
+ stream.write(seq);
128
136
  }
129
137
  _restoreTerminal() {
130
138
  const stream = process.stdout;
131
- let seq = esc.reset;
132
- if (this._mouse) {
133
- seq += esc.mouseTrackingOff;
134
- }
135
- seq += esc.bracketedPasteOff;
136
- seq += esc.showCursor;
137
- if (this._alternateScreen) {
138
- seq += esc.alternateScreenOff;
139
- }
140
- stream.write(seq);
139
+ const seq = esc.restoreSequence(this._caps, {
140
+ alternateScreen: this._alternateScreen,
141
+ mouse: this._mouse,
142
+ });
143
+ if (seq)
144
+ stream.write(seq);
141
145
  }
142
146
  _createRenderPipeline(cols, rows) {
143
147
  this._buffer = new PixelBuffer(cols, rows);
@@ -296,6 +300,8 @@ export class App {
296
300
  }
297
301
  // Restore terminal
298
302
  this._restoreTerminal();
303
+ // Restore Win32 console mode (before disabling raw mode)
304
+ restoreWin32Console();
299
305
  // Disable raw mode
300
306
  disableRawMode();
301
307
  // Resolve the run() promise
package/dist/index.d.ts CHANGED
@@ -16,6 +16,8 @@ export type { Constraint, Point, Rect, Size, } from "./layout/types.js";
16
16
  export * as esc from "./ansi/esc.js";
17
17
  export { AnsiOutput } from "./ansi/output.js";
18
18
  export { stripAnsi, truncateAnsi, visibleLength, } from "./ansi/strip.js";
19
+ export { detectTerminal, type TerminalCaps, } from "./ansi/terminal-env.js";
20
+ export { enableWin32Mouse, restoreWin32Console, } from "./ansi/win32-console.js";
19
21
  export { DirtyRegions, DirtySnapshot } from "./render/regions.js";
20
22
  export { RenderTarget } from "./render/render-target.js";
21
23
  export { EscapeMatcher } from "./input/escape-matcher.js";
package/dist/index.js CHANGED
@@ -23,6 +23,8 @@ export { charWidth, EMPTY_SYMBOL, isZeroWidth, stringDisplayWidth, sym, } from "
23
23
  export * as esc from "./ansi/esc.js";
24
24
  export { AnsiOutput } from "./ansi/output.js";
25
25
  export { stripAnsi, truncateAnsi, visibleLength, } from "./ansi/strip.js";
26
+ export { detectTerminal, } from "./ansi/terminal-env.js";
27
+ export { enableWin32Mouse, restoreWin32Console, } from "./ansi/win32-console.js";
26
28
  // ── Render pipeline ─────────────────────────────────────────────────
27
29
  export { DirtyRegions, DirtySnapshot } from "./render/regions.js";
28
30
  export { RenderTarget } from "./render/render-target.js";