@tuidom/terminal-backend 0.2.1 → 0.3.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.
@@ -16,6 +16,9 @@ export declare class NodeTerminalBackend implements ITerminalBackend {
16
16
  private resizeCallbacks;
17
17
  private oscResponseCallbacks;
18
18
  private deviceReportCallbacks;
19
+ /** DA1-запросов отправлено / ответов получено — чтобы проба ждала именно свой DA1. */
20
+ private da1Sent;
21
+ private da1Received;
19
22
  private stdin;
20
23
  private stdout;
21
24
  private onDataHandler;
@@ -69,6 +72,14 @@ export declare class NodeTerminalBackend implements ITerminalBackend {
69
72
  onResize(callback: (size: Size) => void): void;
70
73
  onOscResponse(callback: (code: number, data: string) => void): void;
71
74
  probeKeyboardProtocol(onResult: (supported: boolean) => void): void;
75
+ probeTerminalVersion(onResult: (nameAndVersion: string | undefined) => void): void;
76
+ /**
77
+ * Send `query` followed by DA1 and collect device reports until *our* DA1 reply
78
+ * arrives (or the timeout fires), then call `finish` exactly once. Replies come back
79
+ * in request order, so the n-th DA1 reply closes the n-th probe — two probes in
80
+ * flight don't settle each other early.
81
+ */
82
+ private probe;
72
83
  /** Fan a parsed input batch out to the registered callbacks. */
73
84
  private dispatchInput;
74
85
  /** If a partial sequence is buffered, flush it after a short grace period (the rest never came). */
@@ -45,7 +45,13 @@ const BRACKETED_PASTE_DISABLE = "\x1b[?2004l";
45
45
  */
46
46
  const KITTY_FLAGS_QUERY = "\x1b[?u";
47
47
  const DA1_QUERY = "\x1b[c";
48
- const KEYBOARD_PROBE_TIMEOUT_MS = 200;
48
+ const PROBE_TIMEOUT_MS = 200;
49
+ /**
50
+ * Terminal name/version probe: XTVERSION (`CSI > 0 q`) → `DCS > | <name(version)> ST`,
51
+ * then DA1 as the same "all replies are in" sentinel — terminals without XTVERSION stay
52
+ * silent on the first query but always answer DA1.
53
+ */
54
+ const XTVERSION_QUERY = "\x1b[>0q";
49
55
  /**
50
56
  * How long to hold a partial escape sequence that arrived at the end of a stdin read,
51
57
  * waiting for the rest to follow (it does, on a split keypress over SSH/tmux). If nothing
@@ -82,6 +88,9 @@ export class NodeTerminalBackend {
82
88
  resizeCallbacks = [];
83
89
  oscResponseCallbacks = [];
84
90
  deviceReportCallbacks = [];
91
+ /** DA1-запросов отправлено / ответов получено — чтобы проба ждала именно свой DA1. */
92
+ da1Sent = 0;
93
+ da1Received = 0;
85
94
  stdin;
86
95
  stdout;
87
96
  onDataHandler = null;
@@ -155,28 +164,53 @@ export class NodeTerminalBackend {
155
164
  this.oscResponseCallbacks.push(callback);
156
165
  }
157
166
  probeKeyboardProtocol(onResult) {
158
- let settled = false;
159
167
  let supported = false;
168
+ // Direct (not passthrough): the reply must reflect tmux's own Kitty support, per-pane.
169
+ this.probe(KITTY_FLAGS_QUERY, (report) => {
170
+ if (report === "kitty-flags")
171
+ supported = true;
172
+ }, () => {
173
+ onResult(supported);
174
+ });
175
+ }
176
+ probeTerminalVersion(onResult) {
177
+ let nameAndVersion;
178
+ // Direct, как и проба клавиатуры: под tmux отвечает сам tmux — это его панель.
179
+ this.probe(XTVERSION_QUERY, (report, params) => {
180
+ if (report === "xtversion")
181
+ nameAndVersion = params;
182
+ }, () => {
183
+ onResult(nameAndVersion);
184
+ });
185
+ }
186
+ /**
187
+ * Send `query` followed by DA1 and collect device reports until *our* DA1 reply
188
+ * arrives (or the timeout fires), then call `finish` exactly once. Replies come back
189
+ * in request order, so the n-th DA1 reply closes the n-th probe — two probes in
190
+ * flight don't settle each other early.
191
+ */
192
+ probe(query, onReport, finish) {
193
+ const myDa1 = ++this.da1Sent;
194
+ let settled = false;
160
195
  let timer = null;
161
- const finish = () => {
196
+ const settle = () => {
162
197
  if (settled)
163
198
  return;
164
199
  settled = true;
165
200
  if (timer !== null)
166
201
  clearTimeout(timer);
167
- onResult(supported);
202
+ finish();
168
203
  };
169
- this.deviceReportCallbacks.push((report) => {
204
+ this.deviceReportCallbacks.push((report, params) => {
170
205
  if (settled)
171
206
  return;
172
- if (report === "kitty-flags")
173
- supported = true;
174
- else
175
- finish(); // DA1 reply = all replies are in
207
+ if (report !== "da1")
208
+ onReport(report, params);
209
+ else if (this.da1Received >= myDa1)
210
+ settle(); // our DA1 reply = all replies are in
176
211
  });
177
- // Direct (not passthrough): the reply must reflect tmux's own Kitty support, per-pane.
178
- this.writeDirect(KITTY_FLAGS_QUERY + DA1_QUERY);
179
- timer = setTimeout(finish, KEYBOARD_PROBE_TIMEOUT_MS);
212
+ this.writeDirect(query + DA1_QUERY);
213
+ timer = setTimeout(settle, PROBE_TIMEOUT_MS);
180
214
  }
181
215
  /** Fan a parsed input batch out to the registered callbacks. */
182
216
  dispatchInput(result) {
@@ -197,6 +231,8 @@ export class NodeTerminalBackend {
197
231
  cb(oscToken.code, oscToken.data);
198
232
  }
199
233
  for (const report of result.deviceReports) {
234
+ if (report.report === "da1")
235
+ this.da1Received++;
200
236
  for (const cb of this.deviceReportCallbacks)
201
237
  cb(report.report, report.params);
202
238
  }
@@ -271,11 +307,15 @@ export class NodeTerminalBackend {
271
307
  this.stdout.write("\x1b[?1049h");
272
308
  // Show cursor (will be positioned by the focused element)
273
309
  this.stdout.write("\x1b[?25h");
274
- // Enable Kitty Keyboard Protocol — direct, so tmux tracks it per-pane (no passthrough)
275
- this.writeDirect(KITTY_ENABLE);
276
- // Also enable xterm modifyOtherKeys — tmux ignores the Kitty push but honors this,
310
+ // Enable xterm modifyOtherKeys — tmux ignores the Kitty push but honors this,
277
311
  // delivering Ctrl+Tab / Ctrl+Shift+<key> as CSI-u. Direct, per-pane.
312
+ // Порядок важен: ДО kitty-push. iTerm2 трактует любой `CSI > 4 ; n m` как сброс
313
+ // стека kitty-флагов (VT100Terminal.m: `_keyReportingFlags = 0` + `removeAllObjects`),
314
+ // так что modifyOtherKeys после push откатывал его в legacy — и Cmd терялся.
278
315
  this.writeDirect(MODIFY_OTHER_KEYS_ENABLE);
316
+ // Enable Kitty Keyboard Protocol — direct, so tmux tracks it per-pane (no passthrough).
317
+ // Последним из клавиатурных режимов — см. выше.
318
+ this.writeDirect(KITTY_ENABLE);
279
319
  // Enable mouse tracking (all-motion mode for hover/enter/leave) — direct, per-pane
280
320
  this.writeDirect(MOUSE_TRACKING_ALL_ENABLE);
281
321
  // Enable bracketed paste so pastes arrive as one text block — direct, per-pane
@@ -326,6 +366,8 @@ export class NodeTerminalBackend {
326
366
  });
327
367
  }
328
368
  teardown() {
369
+ // Клавиатурные режимы снимаются в порядке, обратном setup (LIFO): сначала pop
370
+ // kitty-флагов, который setup положил последним, затем сброс modifyOtherKeys.
329
371
  // Disable Kitty Keyboard Protocol — direct (matches the per-pane enable in setup)
330
372
  this.writeDirect(KITTY_DISABLE);
331
373
  // Reset modifyOtherKeys — direct (matches the per-pane enable in setup)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuidom/terminal-backend",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Experimental terminal UI DOM — real terminal backend for Node (tty, ANSI diffing, Kitty protocol). API is unstable.",
6
6
  "license": "MIT",
@@ -28,6 +28,6 @@
28
28
  "./*": "./dist/*.js"
29
29
  },
30
30
  "dependencies": {
31
- "@tuidom/core": "0.2.1"
31
+ "@tuidom/core": "0.3.0"
32
32
  }
33
33
  }