esp32tool 1.6.5 → 1.6.7

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.
@@ -1,3 +1,56 @@
1
+ const ANSI_256 = (() => {
2
+ const t = [];
3
+ // Standard colors 0-7
4
+ t[0] = "rgb(0,0,0)";
5
+ t[1] = "rgb(128,0,0)";
6
+ t[2] = "rgb(0,128,0)";
7
+ t[3] = "rgb(128,128,0)";
8
+ t[4] = "rgb(0,0,128)";
9
+ t[5] = "rgb(128,0,128)";
10
+ t[6] = "rgb(0,128,128)";
11
+ t[7] = "rgb(192,192,192)";
12
+ // Bright colors 8-15
13
+ t[8] = "rgb(128,128,128)";
14
+ t[9] = "rgb(255,0,0)";
15
+ t[10] = "rgb(0,255,0)";
16
+ t[11] = "rgb(255,255,0)";
17
+ t[12] = "rgb(99,153,255)";
18
+ t[13] = "rgb(255,0,255)";
19
+ t[14] = "rgb(0,255,255)";
20
+ t[15] = "rgb(255,255,255)";
21
+ // 6x6x6 color cube 16-231
22
+ for (let i = 0; i < 216; i++) {
23
+ const r = Math.floor(i / 36);
24
+ const g = Math.floor((i % 36) / 6);
25
+ const b = i % 6;
26
+ t[16 + i] =
27
+ "rgb(" +
28
+ (r ? r * 40 + 55 : 0) +
29
+ "," +
30
+ (g ? g * 40 + 55 : 0) +
31
+ "," +
32
+ (b ? b * 40 + 55 : 0) +
33
+ ")";
34
+ }
35
+ // Grayscale ramp 232-255
36
+ for (let i = 0; i < 24; i++) {
37
+ const v = i * 10 + 8;
38
+ t[232 + i] = "rgb(" + v + "," + v + "," + v + ")";
39
+ }
40
+ return t;
41
+ })();
42
+ // Maps 256-color indices 0–7 to the named CSS class tokens so that
43
+ // \x1b[38;5;1m renders the same red as \x1b[31m.
44
+ const ANSI_NAMED = [
45
+ "black",
46
+ "red",
47
+ "green",
48
+ "yellow",
49
+ "blue",
50
+ "magenta",
51
+ "cyan",
52
+ "white",
53
+ ];
1
54
  export class ColoredConsole {
2
55
  constructor(targetElement) {
3
56
  this.targetElement = targetElement;
@@ -8,6 +61,10 @@ export class ColoredConsole {
8
61
  strikethrough: false,
9
62
  foregroundColor: null,
10
63
  backgroundColor: null,
64
+ fgRgb: null,
65
+ bgRgb: null,
66
+ dim: false,
67
+ reverse: false,
11
68
  carriageReturn: false,
12
69
  lines: [],
13
70
  secret: false,
@@ -24,16 +81,16 @@ export class ColoredConsole {
24
81
  processLine(line) {
25
82
  // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences
26
83
  // eslint-disable-next-line no-control-regex
27
- const re = /(?:\x1B|\\x1B)(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1B\\))/g;
84
+ const re = /(?:\x1B|\\x1B)(?:\[(.*?)([@-~])|\].*?(?:\x07|\x1B\\))/g;
28
85
  let i = 0;
29
86
  const lineSpan = document.createElement("span");
30
87
  lineSpan.classList.add("line");
31
88
  const addSpan = (content) => {
32
- if (content === "")
33
- return;
34
89
  const span = document.createElement("span");
35
90
  if (this.state.bold)
36
91
  span.classList.add("log-bold");
92
+ if (this.state.dim)
93
+ span.classList.add("log-dim");
37
94
  if (this.state.italic)
38
95
  span.classList.add("log-italic");
39
96
  if (this.state.underline)
@@ -46,10 +103,41 @@ export class ColoredConsole {
46
103
  span.classList.add("log-blink");
47
104
  if (this.state.rapidBlink)
48
105
  span.classList.add("log-rapid-blink");
49
- if (this.state.foregroundColor !== null)
50
- span.classList.add(`log-fg-${this.state.foregroundColor}`);
51
- if (this.state.backgroundColor !== null)
52
- span.classList.add(`log-bg-${this.state.backgroundColor}`);
106
+ // Resolve colors with reverse-video support
107
+ let fgRgb = this.state.fgRgb;
108
+ let bgRgb = this.state.bgRgb;
109
+ let fg = this.state.foregroundColor;
110
+ let bg = this.state.backgroundColor;
111
+ if (this.state.reverse) {
112
+ fgRgb = this.state.bgRgb;
113
+ bgRgb = this.state.fgRgb;
114
+ fg = this.state.backgroundColor;
115
+ bg = this.state.foregroundColor;
116
+ // When one side is unset, fill in the terminal defaults so the
117
+ // swap is always visible (fg default=#ddd, bg default=#1c1c1c).
118
+ if (!fgRgb && !fg && !bgRgb && !bg) {
119
+ span.classList.add("log-reverse");
120
+ }
121
+ else {
122
+ if (!fgRgb && !fg)
123
+ fgRgb = "rgb(28,28,28)";
124
+ if (!bgRgb && !bg)
125
+ bgRgb = "rgb(221,221,221)";
126
+ }
127
+ }
128
+ // Inline rgb() style takes priority over CSS class
129
+ if (fgRgb) {
130
+ span.style.color = fgRgb;
131
+ }
132
+ else if (fg !== null) {
133
+ span.classList.add(`log-fg-${fg}`);
134
+ }
135
+ if (bgRgb) {
136
+ span.style.backgroundColor = bgRgb;
137
+ }
138
+ else if (bg !== null) {
139
+ span.classList.add(`log-bg-${bg}`);
140
+ }
53
141
  span.appendChild(document.createTextNode(content));
54
142
  lineSpan.appendChild(span);
55
143
  if (this.state.secret) {
@@ -66,18 +154,40 @@ export class ColoredConsole {
66
154
  const j = match.index;
67
155
  addSpan(line.substring(i, j));
68
156
  i = j + match[0].length;
69
- if (match[1] === undefined)
157
+ // Only process SGR sequences (final byte 'm'); skip cursor, erase, etc.
158
+ if (match[1] === undefined || match[2] !== "m")
159
+ continue;
160
+ const rawCodes = match[1] === "" ? [""] : match[1].split(";");
161
+ const codes = [];
162
+ let invalidSgr = false;
163
+ for (const rawCode of rawCodes) {
164
+ if (rawCode === "") {
165
+ codes.push(0);
166
+ continue;
167
+ }
168
+ if (!/^\d+$/.test(rawCode)) {
169
+ invalidSgr = true;
170
+ break;
171
+ }
172
+ codes.push(Number(rawCode));
173
+ }
174
+ if (invalidSgr)
70
175
  continue;
71
- for (const colorCode of match[1].split(";")) {
72
- switch (parseInt(colorCode)) {
176
+ let ci = 0;
177
+ while (ci < codes.length) {
178
+ const code = codes[ci];
179
+ switch (code) {
73
180
  case 0:
74
- // reset
75
181
  this.state.bold = false;
182
+ this.state.dim = false;
76
183
  this.state.italic = false;
77
184
  this.state.underline = false;
78
185
  this.state.strikethrough = false;
79
186
  this.state.foregroundColor = null;
80
187
  this.state.backgroundColor = null;
188
+ this.state.fgRgb = null;
189
+ this.state.bgRgb = null;
190
+ this.state.reverse = false;
81
191
  this.state.secret = false;
82
192
  this.state.blink = false;
83
193
  this.state.rapidBlink = false;
@@ -85,6 +195,9 @@ export class ColoredConsole {
85
195
  case 1:
86
196
  this.state.bold = true;
87
197
  break;
198
+ case 2:
199
+ this.state.dim = true;
200
+ break;
88
201
  case 3:
89
202
  this.state.italic = true;
90
203
  break;
@@ -99,6 +212,9 @@ export class ColoredConsole {
99
212
  this.state.rapidBlink = true;
100
213
  this.state.blink = false;
101
214
  break;
215
+ case 7:
216
+ this.state.reverse = true;
217
+ break;
102
218
  case 8:
103
219
  this.state.secret = true;
104
220
  break;
@@ -107,6 +223,7 @@ export class ColoredConsole {
107
223
  break;
108
224
  case 22:
109
225
  this.state.bold = false;
226
+ this.state.dim = false;
110
227
  break;
111
228
  case 23:
112
229
  this.state.italic = false;
@@ -118,6 +235,9 @@ export class ColoredConsole {
118
235
  this.state.blink = false;
119
236
  this.state.rapidBlink = false;
120
237
  break;
238
+ case 27:
239
+ this.state.reverse = false;
240
+ break;
121
241
  case 28:
122
242
  this.state.secret = false;
123
243
  break;
@@ -126,59 +246,214 @@ export class ColoredConsole {
126
246
  break;
127
247
  case 30:
128
248
  this.state.foregroundColor = "black";
249
+ this.state.fgRgb = null;
129
250
  break;
130
251
  case 31:
131
252
  this.state.foregroundColor = "red";
253
+ this.state.fgRgb = null;
132
254
  break;
133
255
  case 32:
134
256
  this.state.foregroundColor = "green";
257
+ this.state.fgRgb = null;
135
258
  break;
136
259
  case 33:
137
260
  this.state.foregroundColor = "yellow";
261
+ this.state.fgRgb = null;
138
262
  break;
139
263
  case 34:
140
264
  this.state.foregroundColor = "blue";
265
+ this.state.fgRgb = null;
141
266
  break;
142
267
  case 35:
143
268
  this.state.foregroundColor = "magenta";
269
+ this.state.fgRgb = null;
144
270
  break;
145
271
  case 36:
146
272
  this.state.foregroundColor = "cyan";
273
+ this.state.fgRgb = null;
147
274
  break;
148
275
  case 37:
149
276
  this.state.foregroundColor = "white";
277
+ this.state.fgRgb = null;
278
+ break;
279
+ case 38:
280
+ // Extended foreground: 38;5;n (256-color) or 38;2;r;g;b (true-color)
281
+ if (ci + 1 < codes.length) {
282
+ if (codes[ci + 1] === 5) {
283
+ if (ci + 2 < codes.length) {
284
+ const idx = codes[ci + 2];
285
+ if (idx >= 0 && idx <= 7 && ANSI_NAMED[idx]) {
286
+ this.state.foregroundColor = ANSI_NAMED[idx];
287
+ this.state.fgRgb = null;
288
+ }
289
+ else if (idx >= 0 && idx <= 255 && ANSI_256[idx]) {
290
+ this.state.foregroundColor = null;
291
+ this.state.fgRgb = ANSI_256[idx];
292
+ }
293
+ ci += 2;
294
+ }
295
+ else {
296
+ ci += 1;
297
+ }
298
+ }
299
+ else if (codes[ci + 1] === 2) {
300
+ if (ci + 4 < codes.length) {
301
+ this.state.foregroundColor = null;
302
+ const r = Math.max(0, Math.min(255, codes[ci + 2]));
303
+ const g = Math.max(0, Math.min(255, codes[ci + 3]));
304
+ const b = Math.max(0, Math.min(255, codes[ci + 4]));
305
+ this.state.fgRgb = "rgb(" + r + "," + g + "," + b + ")";
306
+ ci += 4;
307
+ }
308
+ else {
309
+ ci = codes.length - 1;
310
+ }
311
+ }
312
+ }
150
313
  break;
151
314
  case 39:
152
315
  this.state.foregroundColor = null;
316
+ this.state.fgRgb = null;
153
317
  break;
154
318
  case 40:
155
319
  this.state.backgroundColor = "black";
320
+ this.state.bgRgb = null;
156
321
  break;
157
322
  case 41:
158
323
  this.state.backgroundColor = "red";
324
+ this.state.bgRgb = null;
159
325
  break;
160
326
  case 42:
161
327
  this.state.backgroundColor = "green";
328
+ this.state.bgRgb = null;
162
329
  break;
163
330
  case 43:
164
331
  this.state.backgroundColor = "yellow";
332
+ this.state.bgRgb = null;
165
333
  break;
166
334
  case 44:
167
335
  this.state.backgroundColor = "blue";
336
+ this.state.bgRgb = null;
168
337
  break;
169
338
  case 45:
170
339
  this.state.backgroundColor = "magenta";
340
+ this.state.bgRgb = null;
171
341
  break;
172
342
  case 46:
173
343
  this.state.backgroundColor = "cyan";
344
+ this.state.bgRgb = null;
174
345
  break;
175
346
  case 47:
176
347
  this.state.backgroundColor = "white";
348
+ this.state.bgRgb = null;
349
+ break;
350
+ case 48:
351
+ // Extended background: 48;5;n (256-color) or 48;2;r;g;b (true-color)
352
+ if (ci + 1 < codes.length) {
353
+ if (codes[ci + 1] === 5) {
354
+ if (ci + 2 < codes.length) {
355
+ const idx = codes[ci + 2];
356
+ if (idx >= 0 && idx <= 7 && ANSI_NAMED[idx]) {
357
+ this.state.backgroundColor = ANSI_NAMED[idx];
358
+ this.state.bgRgb = null;
359
+ }
360
+ else if (idx >= 0 && idx <= 255 && ANSI_256[idx]) {
361
+ this.state.backgroundColor = null;
362
+ this.state.bgRgb = ANSI_256[idx];
363
+ }
364
+ ci += 2;
365
+ }
366
+ else {
367
+ ci += 1;
368
+ }
369
+ }
370
+ else if (codes[ci + 1] === 2) {
371
+ if (ci + 4 < codes.length) {
372
+ this.state.backgroundColor = null;
373
+ const r = Math.max(0, Math.min(255, codes[ci + 2]));
374
+ const g = Math.max(0, Math.min(255, codes[ci + 3]));
375
+ const b = Math.max(0, Math.min(255, codes[ci + 4]));
376
+ this.state.bgRgb = "rgb(" + r + "," + g + "," + b + ")";
377
+ ci += 4;
378
+ }
379
+ else {
380
+ ci = codes.length - 1;
381
+ }
382
+ }
383
+ }
177
384
  break;
178
385
  case 49:
179
386
  this.state.backgroundColor = null;
387
+ this.state.bgRgb = null;
388
+ break;
389
+ // Bright foreground colors
390
+ case 90:
391
+ this.state.foregroundColor = null;
392
+ this.state.fgRgb = ANSI_256[8];
393
+ break;
394
+ case 91:
395
+ this.state.foregroundColor = null;
396
+ this.state.fgRgb = ANSI_256[9];
397
+ break;
398
+ case 92:
399
+ this.state.foregroundColor = null;
400
+ this.state.fgRgb = ANSI_256[10];
401
+ break;
402
+ case 93:
403
+ this.state.foregroundColor = null;
404
+ this.state.fgRgb = ANSI_256[11];
405
+ break;
406
+ case 94:
407
+ this.state.foregroundColor = null;
408
+ this.state.fgRgb = ANSI_256[12];
409
+ break;
410
+ case 95:
411
+ this.state.foregroundColor = null;
412
+ this.state.fgRgb = ANSI_256[13];
413
+ break;
414
+ case 96:
415
+ this.state.foregroundColor = null;
416
+ this.state.fgRgb = ANSI_256[14];
417
+ break;
418
+ case 97:
419
+ this.state.foregroundColor = null;
420
+ this.state.fgRgb = ANSI_256[15];
421
+ break;
422
+ // Bright background colors
423
+ case 100:
424
+ this.state.backgroundColor = null;
425
+ this.state.bgRgb = ANSI_256[8];
426
+ break;
427
+ case 101:
428
+ this.state.backgroundColor = null;
429
+ this.state.bgRgb = ANSI_256[9];
430
+ break;
431
+ case 102:
432
+ this.state.backgroundColor = null;
433
+ this.state.bgRgb = ANSI_256[10];
434
+ break;
435
+ case 103:
436
+ this.state.backgroundColor = null;
437
+ this.state.bgRgb = ANSI_256[11];
438
+ break;
439
+ case 104:
440
+ this.state.backgroundColor = null;
441
+ this.state.bgRgb = ANSI_256[12];
442
+ break;
443
+ case 105:
444
+ this.state.backgroundColor = null;
445
+ this.state.bgRgb = ANSI_256[13];
446
+ break;
447
+ case 106:
448
+ this.state.backgroundColor = null;
449
+ this.state.bgRgb = ANSI_256[14];
450
+ break;
451
+ case 107:
452
+ this.state.backgroundColor = null;
453
+ this.state.bgRgb = ANSI_256[15];
180
454
  break;
181
455
  }
456
+ ci++;
182
457
  }
183
458
  }
184
459
  addSpan(line.substring(i));
@@ -193,8 +468,6 @@ export class ColoredConsole {
193
468
  return;
194
469
  }
195
470
  for (const line of this.state.lines) {
196
- // A lone \r is a pure carriage-return signal — update state but don't
197
- // create a DOM node for it (it has no renderable content).
198
471
  if (line === "\r") {
199
472
  this.state.carriageReturn = true;
200
473
  continue;
@@ -217,13 +490,11 @@ export class ColoredConsole {
217
490
  this.targetElement.appendChild(fragment);
218
491
  }
219
492
  this.state.lines = [];
220
- // Keep scroll at bottom
221
493
  if (atBottom) {
222
494
  this.targetElement.scrollTop = this.targetElement.scrollHeight;
223
495
  }
224
496
  }
225
497
  addLine(line) {
226
- // Processing of lines is deferred for performance reasons
227
498
  if (this.state.lines.length === 0) {
228
499
  setTimeout(() => this.processLines(), 0);
229
500
  }
@@ -249,6 +520,9 @@ export const coloredConsoleStyles = `
249
520
  .log-bold {
250
521
  font-weight: bold;
251
522
  }
523
+ .log-dim {
524
+ opacity: 0.5;
525
+ }
252
526
  .log-italic {
253
527
  font-style: italic;
254
528
  }
@@ -283,6 +557,10 @@ export const coloredConsoleStyles = `
283
557
  width: 1px;
284
558
  font-size: 1px;
285
559
  }
560
+ .log-reverse {
561
+ background: #ddd;
562
+ color: #1c1c1c;
563
+ }
286
564
  .log-fg-black {
287
565
  color: rgb(128, 128, 128);
288
566
  }
@@ -1,14 +1,16 @@
1
- // Matches lines that already carry a wall-clock or tick timestamp so we don't
2
- // add a redundant one. Intentionally does NOT match bare log-level prefixes
3
- // like ESPHome's [I][tag:line]: those have no time information.
4
- //
1
+ // Matches lines that already carry a wall-clock timestamp so we don't add a
2
+ // redundant one. Only real wall-clock formats are matched — tick-based
3
+ // formats like FreeRTOS "(12345)" or ESP-IDF "I (15) boot:" are NOT matched
4
+ // because they don't carry time-of-day information
5
5
  // Covered formats:
6
- // (123456) FreeRTOS ms-tick e.g. "(12345) "
7
6
  // [HH:MM:SS] wall-clock bracket
8
7
  // [HH:MM:SS.mmm] wall-clock bracket with millis
9
- // I (1234) tag: ESP-IDF log level + tick e.g. "I (1234) wifi: ..."
10
8
  // HH:MM:SS.mmm plain wall-clock
11
- const DEVICE_TIMESTAMP_RE = /^\s*(?:\(\d+\)\s|\[\d{2}:\d{2}:\d{2}(?:\.\d+)?\]|[DIWEACV] \(\d+\) \w|(?:\d{2}:){2}\d{2}\.\d)/;
9
+ const DEVICE_TIMESTAMP_RE = /^\s*(?:\[\d{2}:\d{2}:\d{2}(?:\.\d+)?\]|(?:\d{2}:){2}\d{2}\.\d)/;
10
+ // Matches leading ANSI SGR (color/style) codes at the start of a string
11
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences
12
+ // eslint-disable-next-line no-control-regex
13
+ const LEADING_ANSI_RE = /^(\x1b\[(?:\d+;)*\d*m)+/;
12
14
  export class TimestampTransformer {
13
15
  constructor() {
14
16
  this.deviceHasTimestamps = false;
@@ -27,11 +29,30 @@ export class TimestampTransformer {
27
29
  controller.enqueue(chunk);
28
30
  return;
29
31
  }
32
+ // Extract leading ANSI codes to preserve them across line splits
33
+ const ansiMatch = chunk.match(LEADING_ANSI_RE);
34
+ const leadingAnsi = ansiMatch ? ansiMatch[0] : "";
35
+ const contentWithoutAnsi = leadingAnsi
36
+ ? chunk.slice(leadingAnsi.length)
37
+ : chunk;
30
38
  const date = new Date();
31
39
  const h = date.getHours().toString().padStart(2, "0");
32
40
  const m = date.getMinutes().toString().padStart(2, "0");
33
41
  const s = date.getSeconds().toString().padStart(2, "0");
34
- controller.enqueue(`[${h}:${m}:${s}] ${chunk}`);
42
+ const timestamp = `[${h}:${m}:${s}]`;
43
+ // For multi-line chunks, we need to preserve ANSI codes on each line
44
+ // Split on newlines, but keep the newline characters
45
+ const lines = contentWithoutAnsi.split(/(\r?\n)/);
46
+ let result = "";
47
+ for (const part of lines) {
48
+ if (part === "\n" || part === "\r\n") {
49
+ result += part;
50
+ }
51
+ else if (part !== "") {
52
+ result += leadingAnsi + timestamp + " " + part;
53
+ }
54
+ }
55
+ controller.enqueue(result);
35
56
  }
36
57
  reset() {
37
58
  this.deviceHasTimestamps = false;