deckide 3.5.25 → 3.5.26

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.
@@ -54,54 +54,106 @@ export function alignToUtf8End(buf, offset) {
54
54
  return pos;
55
55
  }
56
56
  /**
57
- * Skip past a partial ANSI CSI escape sequence at the start of a buffer.
57
+ * Skip past a partial ANSI escape sequence at the start of a buffer.
58
58
  *
59
59
  * When a terminal buffer is trimmed from the front, the cut point may land
60
- * inside a CSI sequence (e.g. "\x1b[38;2;100;50m"). The ESC and '[' are
61
- * discarded, leaving orphaned parameter bytes like "100;50m" that xterm.js
60
+ * inside an escape sequence. The leading ESC byte (and possibly the type
61
+ * indicator) is discarded, leaving orphaned payload bytes that xterm.js
62
62
  * would render as literal text, shifting all subsequent cursor positions.
63
63
  *
64
- * This function detects two patterns:
65
- * 1. Buffer starts with '[' followed by CSI parameter / intermediate /
66
- * final bytes → partial CSI whose ESC was trimmed.
67
- * 2. Buffer starts with CSI parameter bytes (digits, ';') containing at
68
- * least one ';', ending with a final byte → partial CSI whose
69
- * "ESC [" was trimmed.
64
+ * Handled sequence types:
65
+ *
66
+ * CSI (ESC [) — e.g. "\x1b[38;2;100;50m"
67
+ * Detected as: '[' + params/intermediates + final byte
68
+ * or: bare params with ';' + final byte (ESC and '[' both trimmed)
69
+ *
70
+ * OSC (ESC ]) — e.g. "\x1b]0;title\x07"
71
+ * Detected as: ']' + text + BEL/ST terminator
72
+ *
73
+ * DCS (ESC P) — e.g. "\x1bP1$r...\x1b\\"
74
+ * Detected as: 'P' + text + ST terminator
75
+ *
76
+ * APC (ESC _) — e.g. "\x1b_...\x1b\\"
77
+ * Detected as: '_' + text + ST terminator
70
78
  *
71
79
  * Returns the number of bytes to skip (0 if no partial sequence detected).
72
80
  */
73
81
  export function skipPartialEscapeSequence(buf, offset) {
74
82
  if (offset >= buf.length)
75
83
  return 0;
84
+ const b = buf[offset];
85
+ // ── CSI: starts with '[' (ESC was trimmed) ──
86
+ if (b === 0x5B /* '[' */) {
87
+ return skipPartialCSI(buf, offset);
88
+ }
89
+ // ── OSC: starts with ']' (ESC was trimmed) ──
90
+ if (b === 0x5D /* ']' */) {
91
+ return skipStringSequence(buf, offset);
92
+ }
93
+ // ── DCS: starts with 'P' (ESC was trimmed) ──
94
+ // Only treat as DCS if followed by a parameter byte, '$', or printable
95
+ // control sequence byte — avoids false positive on words like "Path".
96
+ if (b === 0x50 /* 'P' */ && offset + 1 < buf.length) {
97
+ const next = buf[offset + 1];
98
+ if ((next >= 0x30 && next <= 0x3F) || next === 0x24 /* '$' */) {
99
+ return skipStringSequence(buf, offset);
100
+ }
101
+ }
102
+ // ── APC: starts with '_' (ESC was trimmed) ──
103
+ if (b === 0x5F /* '_' */) {
104
+ return skipStringSequence(buf, offset);
105
+ }
106
+ // ── Bare CSI params (both ESC and '[' trimmed) ──
107
+ if (b >= 0x30 && b <= 0x3F) {
108
+ return skipBareCSIParams(buf, offset);
109
+ }
110
+ return 0;
111
+ }
112
+ /**
113
+ * Skip a partial CSI sequence starting with '['.
114
+ * Pattern: '[' params intermediates final
115
+ */
116
+ function skipPartialCSI(buf, offset) {
76
117
  const limit = Math.min(buf.length, offset + 128);
77
- let pos = offset;
78
- // Pattern 1: starts with '[' (CSI intro without preceding ESC)
79
- if (buf[pos] === 0x5B /* '[' */ && pos + 1 < limit) {
80
- const next = buf[pos + 1];
81
- // Only treat as CSI if next byte is a parameter (0x30-0x3F) or
82
- // intermediate (0x20-0x2F) byte avoids false positives like "[user@host"
83
- if ((next >= 0x30 && next <= 0x3F) || (next >= 0x20 && next <= 0x2F)) {
84
- pos++; // skip '['
85
- while (pos < limit) {
86
- const c = buf[pos];
87
- if ((c >= 0x30 && c <= 0x3F) || (c >= 0x20 && c <= 0x2F)) {
88
- pos++;
89
- continue;
90
- }
91
- if (c >= 0x40 && c <= 0x7E) {
92
- // CSI final byte — skip it and we're done
93
- return (pos + 1) - offset;
94
- }
95
- break;
96
- }
97
- return 0;
118
+ if (offset + 1 >= limit)
119
+ return 0;
120
+ const next = buf[offset + 1];
121
+ // Only treat as CSI if the next byte is a parameter (0x30-0x3F) or
122
+ // intermediate (0x20-0x2F) avoids false positives like "[user@host"
123
+ if (next < 0x20 || (next > 0x3F && next < 0x40))
124
+ return 0;
125
+ // If next is already a final byte (letter), it could be "[H" (cursor home)
126
+ // or "[hello" skip only the 2-byte "[H" style if the byte after final
127
+ // is a control char or ESC (strong signal it was a real sequence).
128
+ if (next >= 0x40 && next <= 0x7E) {
129
+ if (offset + 2 < buf.length) {
130
+ const after = buf[offset + 2];
131
+ if (after === 0x1B || after < 0x20)
132
+ return 2;
98
133
  }
99
134
  return 0;
100
135
  }
101
- // Pattern 2: starts with CSI parameter bytes (digits, ';', etc.)
102
- const b = buf[pos];
103
- if (b < 0x30 || b > 0x3F)
104
- return 0;
136
+ let pos = offset + 1;
137
+ while (pos < limit) {
138
+ const c = buf[pos];
139
+ if ((c >= 0x30 && c <= 0x3F) || (c >= 0x20 && c <= 0x2F)) {
140
+ pos++;
141
+ continue;
142
+ }
143
+ if (c >= 0x40 && c <= 0x7E) {
144
+ return (pos + 1) - offset;
145
+ }
146
+ break;
147
+ }
148
+ return 0;
149
+ }
150
+ /**
151
+ * Skip bare CSI parameter bytes (both ESC and '[' were trimmed).
152
+ * Pattern: digits/';' (with at least one ';') + final byte
153
+ */
154
+ function skipBareCSIParams(buf, offset) {
155
+ const limit = Math.min(buf.length, offset + 128);
156
+ let pos = offset;
105
157
  let hasSemicolon = false;
106
158
  while (pos < limit) {
107
159
  const c = buf[pos];
@@ -114,8 +166,7 @@ export function skipPartialEscapeSequence(buf, offset) {
114
166
  pos++;
115
167
  }
116
168
  else if (c >= 0x40 && c <= 0x7E) {
117
- // CSI final byte — only skip if we saw at least one semicolon
118
- // (avoids false positives like "5m" at the start of normal text)
169
+ // Only skip if we saw ';' avoids false positives like "5m"
119
170
  return hasSemicolon ? (pos + 1) - offset : 0;
120
171
  }
121
172
  else {
@@ -124,3 +175,35 @@ export function skipPartialEscapeSequence(buf, offset) {
124
175
  }
125
176
  return 0;
126
177
  }
178
+ /**
179
+ * Skip a string-type escape sequence (OSC / DCS / APC) that starts with
180
+ * the type indicator byte (']', 'P', or '_') — the leading ESC was trimmed.
181
+ *
182
+ * These sequences are terminated by:
183
+ * - BEL (0x07) — common for OSC
184
+ * - ST (ESC \ = 0x1B 0x5C) — standard for all
185
+ *
186
+ * We scan up to 4 KB for the terminator; if not found we skip nothing
187
+ * (the partial sequence might span far beyond what we want to discard).
188
+ */
189
+ function skipStringSequence(buf, offset) {
190
+ const limit = Math.min(buf.length, offset + 4096);
191
+ let pos = offset + 1; // skip the type indicator byte
192
+ while (pos < limit) {
193
+ const c = buf[pos];
194
+ if (c === 0x07) {
195
+ // BEL terminator
196
+ return (pos + 1) - offset;
197
+ }
198
+ if (c === 0x1B && pos + 1 < buf.length && buf[pos + 1] === 0x5C) {
199
+ // ST terminator (ESC \)
200
+ return (pos + 2) - offset;
201
+ }
202
+ // If we hit another ESC that's NOT followed by '\', it's a new sequence
203
+ if (c === 0x1B) {
204
+ return pos - offset;
205
+ }
206
+ pos++;
207
+ }
208
+ return 0;
209
+ }
package/dist/websocket.js CHANGED
@@ -149,11 +149,11 @@ function readBufferedRange(session, startOffset, endOffset) {
149
149
  if (relativeEnd <= relativeStart || session.bufferChunks.length === 0) {
150
150
  return Buffer.alloc(0);
151
151
  }
152
- // Materialize the raw range first, then align to UTF-8 boundaries.
153
- // This avoids splitting multi-byte characters that span chunk boundaries.
152
+ // Materialize the raw range into a contiguous copy so the caller
153
+ // is not affected if buffer chunks are later trimmed or reassigned.
154
154
  let raw;
155
155
  if (session.bufferChunks.length === 1) {
156
- raw = session.bufferChunks[0].subarray(relativeStart, relativeEnd);
156
+ raw = Buffer.from(session.bufferChunks[0].subarray(relativeStart, relativeEnd));
157
157
  }
158
158
  else {
159
159
  const slices = [];
@@ -172,7 +172,7 @@ function readBufferedRange(session, startOffset, endOffset) {
172
172
  const endInChunk = Math.min(chunk.length, relativeEnd - chunkStart);
173
173
  slices.push(chunk.subarray(startInChunk, endInChunk));
174
174
  }
175
- raw = slices.length === 1 ? slices[0] : Buffer.concat(slices);
175
+ raw = Buffer.concat(slices); // concat always creates a new Buffer
176
176
  }
177
177
  // Align start: skip orphaned continuation bytes
178
178
  const alignedStart = alignToUtf8Start(raw, 0);
@@ -181,7 +181,7 @@ function readBufferedRange(session, startOffset, endOffset) {
181
181
  if (alignedStart === 0 && alignedEnd === raw.length) {
182
182
  return raw;
183
183
  }
184
- return raw.subarray(alignedStart, alignedEnd);
184
+ return Buffer.from(raw.subarray(alignedStart, alignedEnd));
185
185
  }
186
186
  export function setupWebSocketServer(server, terminals) {
187
187
  const wss = new WebSocketServer({ server, maxPayload: MAX_MESSAGE_SIZE });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deckide",
3
- "version": "3.5.25",
3
+ "version": "3.5.26",
4
4
  "description": "Deck IDE - Browser-based IDE with terminal, file explorer, and git integration",
5
5
  "type": "module",
6
6
  "bin": {