@getparallel/parx 0.4.1 → 0.4.3

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/tui.js CHANGED
@@ -1,4 +1,4 @@
1
- import { animacion, c, capacidades, sep, cabezaDePar, caraDePar, carril, fijarCapacidades, signo, ANCHO_DE_PAR, SIMBOLOS } from "./marca.js";
1
+ import { animacion, c, capacidades, colorDeModo, sep, cabezaDePar, caraDePar, carril, fijarCapacidades, signo, ANCHO_DE_PAR, SIMBOLOS } from "./marca.js";
2
2
  import { anchoDe, envolver, puntos, recortar, rellenar } from "./texto.js";
3
3
  import { ALIAS_COMANDO, COMANDOS, t as tt } from "./textos.js";
4
4
  import { MODELOS, ORDEN_DE_MODELOS } from "./modelos.js";
@@ -24,6 +24,33 @@ export function bloqueDeTexto(texto, sangria = '') {
24
24
  return out;
25
25
  };
26
26
  }
27
+ export function bloqueDeTabla(filas, o = {}) {
28
+ return (ancho) => {
29
+ const util = anchoUtil(ancho);
30
+ const sangria = ' '.repeat(o.sangria ?? 0);
31
+ const dentro = util - sangria.length;
32
+ const col = Math.min(Math.max(0, ...filas.map(([k]) => anchoDe(k))) + 3, Math.floor(dentro / 2));
33
+ const out = [];
34
+ if (o.titulo)
35
+ out.push(`${m}${c.negrita(o.titulo)}`);
36
+ for (const [k, d] of filas) {
37
+ const resto = dentro - col;
38
+ if (resto < 12) {
39
+ out.push(`${m}${sangria}${c.marca(recortar(k, dentro))}`);
40
+ for (const l of envolver(d, dentro - 2))
41
+ out.push(`${m}${sangria} ${l}`);
42
+ continue;
43
+ }
44
+ envolver(d, resto).forEach((l, i) => out.push(`${m}${sangria}${i === 0 ? c.marca(rellenar(recortar(k, col - 1), col)) : ' '.repeat(col)}${l}`));
45
+ }
46
+ if (o.pie) {
47
+ out.push('');
48
+ for (const l of envolver(o.pie, util))
49
+ out.push(`${m}${c.gris(l)}`);
50
+ }
51
+ return out;
52
+ };
53
+ }
27
54
  export function bloqueDeLinea(linea) {
28
55
  return (ancho) => [`${m}${recortar(linea, anchoUtil(ancho))}`];
29
56
  }
@@ -108,6 +135,26 @@ export function bloqueDeCierre(texto, marca) {
108
135
  return [`${m}${izq}${resto > 0 ? ` ${c.tenue(c.gris((capacidades().unicode ? '─' : '-').repeat(resto)))}` : ''}`];
109
136
  };
110
137
  }
138
+ export function bloqueDeBash(comando) {
139
+ return (ancho) => {
140
+ const util = anchoUtil(ancho);
141
+ return ['', ...envolver(comando, util - 2).map((l, i) => `${m}${i === 0 ? c.bash(c.negrita('!')) : ' '} ${l}`)];
142
+ };
143
+ }
144
+ export function bloqueDeSalidaDeBash(lineas) {
145
+ return (ancho) => {
146
+ const util = anchoUtil(ancho);
147
+ const raya = c.tenue(c.bash(capacidades().unicode ? '│' : '|'));
148
+ return lineas.map((l) => `${m}${raya} ${recortar(l.replace(/\t/g, ' '), util - 2)}`);
149
+ };
150
+ }
151
+ export function bloqueDeFinDeBash(bien, texto) {
152
+ return (ancho) => {
153
+ const u = capacidades().unicode;
154
+ const marca = bien ? c.ok(u ? '✓' : 'v') : c.error(u ? '✗' : 'x');
155
+ return [`${m}${c.tenue(c.bash(u ? '╰' : '`'))} ${marca} ${recortar(c.gris(texto), anchoUtil(ancho) - 4)}`];
156
+ };
157
+ }
111
158
  export function trocearTeclas(datos, estado) {
112
159
  const out = [];
113
160
  let i = 0;
@@ -125,6 +172,20 @@ export function trocearTeclas(datos, estado) {
125
172
  }
126
173
  const ch = datos[i];
127
174
  if (ch === '\x1b') {
175
+ const sgr = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])/.exec(datos.slice(i));
176
+ if (sgr) {
177
+ i += sgr[0].length;
178
+ out.push(teclaDeRaton(Number(sgr[1]), Number(sgr[2]), Number(sgr[3]), sgr[4] === 'm'));
179
+ continue;
180
+ }
181
+ if (datos.startsWith('\x1b[M', i) && datos.length >= i + 6) {
182
+ const b = datos.charCodeAt(i + 3) - 32;
183
+ const x = datos.charCodeAt(i + 4) - 32;
184
+ const y = datos.charCodeAt(i + 5) - 32;
185
+ i += 6;
186
+ out.push(teclaDeRaton(b, x, y, (b & 3) === 3 && (b & 64) === 0 && (b & 32) === 0, b & 64 ? 3 : b & 3));
187
+ continue;
188
+ }
128
189
  const r = /^\x1b(?:\[([0-9;?]*)([ -/]*[@-~])|O([@-~])|([\s\S]))?/.exec(datos.slice(i));
129
190
  const seq = r[0];
130
191
  i += Math.max(1, seq.length);
@@ -147,10 +208,16 @@ export function trocearTeclas(datos, estado) {
147
208
  out.push({ t: 'abajo' });
148
209
  else if (fin === 'H' || (fin === '~' && (params === '1' || params === '7')))
149
210
  out.push({ t: 'inicio' });
211
+ else if ((fin === 'F' && /;5$/.test(params)) || (fin === '~' && /^[48];5$/.test(params)))
212
+ out.push({ t: 'fondo' });
150
213
  else if (fin === 'F' || (fin === '~' && (params === '4' || params === '8')))
151
214
  out.push({ t: 'fin' });
152
215
  else if (fin === '~' && params === '3')
153
216
  out.push({ t: 'suprimir' });
217
+ else if (fin === '~' && params === '5')
218
+ out.push({ t: 'pagina_arriba' });
219
+ else if (fin === '~' && params === '6')
220
+ out.push({ t: 'pagina_abajo' });
154
221
  else if (fin === 'Z')
155
222
  out.push({ t: 'cambiar' });
156
223
  else if (fin === 'I' && params === '')
@@ -194,6 +261,8 @@ export function trocearTeclas(datos, estado) {
194
261
  out.push({ t: 'borrar_palabra' });
195
262
  else if (ch === '\x0c')
196
263
  out.push({ t: 'limpiar' });
264
+ else if (ch === '\x1a')
265
+ out.push({ t: 'suspender' });
197
266
  else if (ch === '\x10')
198
267
  out.push({ t: 'arriba' });
199
268
  else if (ch === '\x0e')
@@ -210,8 +279,17 @@ export function trocearTeclas(datos, estado) {
210
279
  }
211
280
  return out;
212
281
  }
213
- const CON_VALOR = new Set(['mode', 'model', 'tips', 'notify', 'teach']);
282
+ function teclaDeRaton(b, x, y, soltar, botonX10) {
283
+ const rueda = b & 64 ? ((b & 1) === 0 ? -1 : 1) : 0;
284
+ const mover = Boolean(b & 32);
285
+ const boton = botonX10 ?? (rueda ? 3 : b & 3);
286
+ return { t: 'raton', boton, x, y, soltar, mover, rueda: rueda };
287
+ }
288
+ export const RATON_SI = '\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h';
289
+ export const RATON_NO = '\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1006l';
290
+ const CON_VALOR = new Set(['mode', 'model', 'tips', 'notify', 'teach', 'mouse']);
214
291
  const CON_TEXTO = new Set(['folder']);
292
+ const SE_MANDAN_SOLOS = new Set(['teach', 'notify', 'tips', 'folder', 'mouse']);
215
293
  const MODOS_DEL_MENU = ['ask', 'auto', 'read', 'bypass'];
216
294
  export function opcionesDelMenu(valor) {
217
295
  if (!valor.startsWith('/') || valor.includes('\n'))
@@ -226,7 +304,8 @@ export function opcionesDelMenu(valor) {
226
304
  if (!casa)
227
305
  continue;
228
306
  const conAlgo = CON_VALOR.has(cmd) || CON_TEXTO.has(cmd);
229
- out.push({ nombre: `/${cmd}`, descripcion, completar: `/${cmd}${conAlgo ? ' ' : ''}`, enviar: !conAlgo });
307
+ const entero = q === cmd || ALIAS_COMANDO[q] === cmd;
308
+ out.push({ nombre: `/${cmd}`, descripcion, completar: `/${cmd}${conAlgo ? ' ' : ''}`, enviar: !conAlgo || (entero && SE_MANDAN_SOLOS.has(cmd)) });
230
309
  }
231
310
  return out;
232
311
  }
@@ -242,31 +321,52 @@ export function opcionesDelMenu(valor) {
242
321
  : cmd === 'model'
243
322
  ? ORDEN_DE_MODELOS.map((k) => [k, `${MODELOS[k].nombre} ${MODELOS[k].version}: ${MODELOS[k].descripcion}`])
244
323
  : [
245
- ['on', cmd === 'teach' ? 'Explain each step as it codes' : cmd === 'notify' ? 'Notify me when it finishes or needs me' : 'Show them when parx opens'],
246
- ['off', cmd === 'teach' ? 'Back to normal' : cmd === 'notify' ? 'No desktop notifications' : 'Don’t show them again'],
324
+ ['on', cmd === 'teach' ? 'Explain each step as it codes' : cmd === 'notify' ? 'Notify me when it finishes or needs me' : cmd === 'mouse' ? 'Click options, scroll with the wheel' : 'Show them when parx opens'],
325
+ ['off', cmd === 'teach' ? 'Back to normal' : cmd === 'notify' ? 'No desktop notifications' : cmd === 'mouse' ? 'The terminal selects and scrolls as usual' : 'Don’t show them again'],
247
326
  ];
248
327
  return valores
249
328
  .filter(([v]) => v.startsWith(q))
250
- .map(([v, d]) => ({ nombre: v, descripcion: d, completar: `${base}${v}`, enviar: true, ...(cmd === 'mode' && v === 'bypass' ? { peligro: true } : {}) }));
329
+ .map(([v, d]) => ({ nombre: v, descripcion: d, completar: `${base}${v}`, enviar: true, ...(cmd === 'mode' ? { modo: v, ...(v === 'bypass' ? { peligro: true } : {}) } : {}) }));
251
330
  }
252
331
  export function componerBarra(e, util) {
332
+ return componerBarraConZonas(e, util).texto;
333
+ }
334
+ export function componerBarraConZonas(e, util) {
335
+ const u = capacidades().unicode;
253
336
  const punto = c.gris(sep());
254
- const modo = e.peligro ? c.aviso(c.negrita(`${capacidades().unicode ? '▲' : '!'} ${e.modo}`)) : c.gris(e.modo);
337
+ const tinta = e.clave ? colorDeModo(e.clave) : e.peligro ? c.peligro : c.gris;
338
+ const modo = e.peligro ? tinta(c.negrita(`${u ? '▲' : '!'} ${e.modo}`)) : tinta(e.modo);
255
339
  const extra = e.extra ? `${punto}${c.marca(e.extra)}` : '';
340
+ const n = e.esperando ?? 0;
341
+ const espera = n ? c.aviso(`${u ? '●' : '*'} ${tt('bandeja.esperando', { n })}`) : '';
342
+ const esperaCorta = n ? c.aviso(`${u ? '●' : '*'} ${n}`) : '';
343
+ const conEspera = (base, e2) => (e2 ? [...base, { t: punto }, { t: e2, id: 'bandeja' }] : base);
344
+ const m = { t: modo, id: 'modo' };
256
345
  const opciones = [
257
- [`${modo}${punto}${c.gris(e.modelo)}${extra}`, e.saldo],
258
- [`${modo}${punto}${c.gris(e.modelo)}${extra}`, e.saldoCorto ?? e.saldo],
259
- [`${modo}${punto}${c.gris(e.modelo)}`, e.saldoCorto ?? e.saldo],
260
- [modo, e.saldoCorto ?? e.saldo],
346
+ [conEspera([m, { t: `${punto}${c.gris(e.modelo)}${extra}` }], espera), e.saldo],
347
+ [conEspera([m, { t: `${punto}${c.gris(e.modelo)}${extra}` }], espera), e.saldoCorto ?? e.saldo],
348
+ [conEspera([m, { t: `${punto}${c.gris(e.modelo)}` }], esperaCorta), e.saldoCorto ?? e.saldo],
349
+ [conEspera([m], esperaCorta), e.saldoCorto ?? e.saldo],
350
+ [[m], e.saldoCorto ?? e.saldo],
261
351
  ];
262
- for (const [izq, der] of opciones) {
352
+ for (const [trozos, der] of opciones) {
353
+ const izq = trozos.map((x) => x.t).join('');
263
354
  const hueco = util - anchoDe(izq) - anchoDe(der);
264
- if (hueco >= 2)
265
- return `${izq}${' '.repeat(hueco)}${c.gris(der)}`;
355
+ if (hueco < 2)
356
+ continue;
357
+ const zonas = [];
358
+ let col = 0;
359
+ for (const x of trozos) {
360
+ const w = anchoDe(x.t);
361
+ if (x.id)
362
+ zonas.push({ desde: col, hasta: col + w, id: x.id });
363
+ col += w;
364
+ }
365
+ return { texto: `${izq}${' '.repeat(hueco)}${c.gris(der)}`, zonas };
266
366
  }
267
367
  const der = e.saldoCorto ?? '';
268
368
  const izq = recortar(modo, Math.max(1, util - anchoDe(der) - 2));
269
- return rellenar(`${izq}${' '.repeat(Math.max(0, util - anchoDe(izq) - anchoDe(der)))}${c.gris(der)}`, util);
369
+ return { texto: rellenar(`${izq}${' '.repeat(Math.max(0, util - anchoDe(izq) - anchoDe(der)))}${c.gris(der)}`, util), zonas: [{ desde: 0, hasta: anchoDe(izq), id: 'modo' }] };
270
370
  }
271
371
  export function duracion(ms) {
272
372
  const s = Math.max(0, Math.floor(ms / 1000));
@@ -308,11 +408,16 @@ export function textoDeTrabajo(w, ahora) {
308
408
  return { texto: w.texto, anim: w.anim };
309
409
  return { texto: fraseDeEspera(ahora - w.esperaDesde, w.semilla), anim: 'ruta' };
310
410
  }
311
- export function filaDeTrabajo(w, ahora) {
411
+ export function filaDeTrabajo(w, ahora, ancho = Infinity) {
312
412
  const { texto, anim } = textoDeTrabajo(w, ahora);
313
413
  const tiempo = duracion(ahora - w.desde);
314
414
  const gasto = w.acciones ? `${sep()}${acciones(w.acciones)}` : '';
315
- return `${animacion(anim, w.paso)} ${texto}${c.gris(`${sep()}${tiempo}${gasto}`)}`;
415
+ for (const cola of [`${sep()}${tiempo}${gasto}`, `${sep()}${tiempo}${w.acciones ? `${sep()}${w.acciones.toLocaleString('en-US')}` : ''}`, `${sep()}${tiempo}`]) {
416
+ const fila = `${animacion(anim, w.paso)} ${texto}${c.gris(cola)}`;
417
+ if (anchoDe(fila) <= ancho)
418
+ return fila;
419
+ }
420
+ return recortar(`${animacion(anim, w.paso)} ${texto}`, ancho);
316
421
  }
317
422
  export function filaDeCarril(k, util, ahora) {
318
423
  const fin = k.hasta ?? ahora;
@@ -333,8 +438,11 @@ export function filaDeCarril(k, util, ahora) {
333
438
  }
334
439
  const anchoTarea = Math.max(8, Math.floor(util * 0.32));
335
440
  const tarea = recortar(k.tarea.replace(/\s+/g, ' '), anchoTarea);
336
- const izq = `${numero} ${señal} ${c.negrita(tarea)}${c.gris(sep())}${c.gris(texto)}`;
337
- return izquierdaYDerecha(recortar(izq, util - (anchoDe(derecha) + 2 <= util / 2 ? anchoDe(derecha) + 2 : 0)), derecha, util);
441
+ const sitio = util - (anchoDe(derecha) + 2 <= util / 2 ? anchoDe(derecha) + 2 : 0);
442
+ const base = `${numero} ${señal} ${c.negrita(tarea)}`;
443
+ const libre = sitio - anchoDe(base) - anchoDe(sep());
444
+ const izq = libre >= 8 ? `${base}${c.gris(sep())}${c.gris(recortar(texto, libre))}` : base;
445
+ return izquierdaYDerecha(recortar(izq, sitio), derecha, util);
338
446
  }
339
447
  function izquierdaYDerecha(izq, der, util) {
340
448
  if (anchoDe(izq) + anchoDe(der) + 2 <= util)
@@ -367,77 +475,163 @@ export function ventanaDeEntrada(unidades, cursor, ancho) {
367
475
  export function cabePar(ancho) {
368
476
  return ancho >= 56;
369
477
  }
478
+ export function esBash(unidades) {
479
+ return unidades[0] === '!';
480
+ }
481
+ export function boton(etiqueta, numero, o = {}) {
482
+ const u = capacidades().unicode;
483
+ const n = numero === null ? '' : `${numero} `;
484
+ if (o.elegido)
485
+ return c.boton(`[${u ? '▸' : '>'}${n}${etiqueta} ]`, o.peligro);
486
+ const pinta = o.peligro ? c.peligro : (x) => x;
487
+ if (o.sobre)
488
+ return `${c.claro('[')} ${c.marca(`${n}${etiqueta}`)} ${c.claro(']')}`;
489
+ return `${c.gris('[')} ${numero === null ? '' : c.claro(n)}${pinta(etiqueta)} ${c.gris(']')}`;
490
+ }
491
+ function filaDeBotones(etiquetas, p, sobre, ancho) {
492
+ const partes = etiquetas.map((e, i) => boton(e, i + 1, { elegido: i === p.elegida, sobre: sobre === `opcion:${i}`, peligro: p.peligro || p.notas?.[i]?.peligro }));
493
+ const total = partes.reduce((n, x) => n + anchoDe(x), 0) + 2 * (partes.length - 1);
494
+ if (total > ancho)
495
+ return null;
496
+ const zonas = [];
497
+ let col = 0;
498
+ partes.forEach((x, i) => {
499
+ zonas.push({ desde: col, hasta: col + anchoDe(x), id: `opcion:${i}` });
500
+ col += anchoDe(x) + 2;
501
+ });
502
+ return { texto: partes.join(' '), zonas };
503
+ }
370
504
  export function componerCarril(d, ancho, alto) {
371
505
  const util = anchoUtil(ancho);
372
506
  const conPar = cabePar(ancho);
373
507
  const huecoPar = conPar ? ANCHO_DE_PAR + 2 : 0;
374
- const signoW = 3;
508
+ const u = capacidades().unicode;
509
+ const bash = !d.pregunta && esBash(d.unidades);
510
+ const signoW = bash ? 2 : 3;
511
+ const holgado = alto >= 14;
512
+ const medio = alto >= 9;
513
+ const vacia = `${m}${' '.repeat(util)}`;
375
514
  let filaEscribir;
376
515
  let cursor = null;
377
516
  const anchoEscribir = util - (conPar ? huecoPar + 1 : 0);
378
- if (d.pregunta) {
379
- const p = d.pregunta;
380
- const pinta = (etiquetas) => etiquetas
381
- .map((e, i) => {
382
- const txt = `${i + 1} ${e}`;
383
- return i === p.elegida ? `${c.claro(capacidades().unicode ? '▸' : '>')} ${c.negrita(txt)}` : ` ${c.gris(txt)}`;
384
- })
385
- .join(' ');
386
- let opciones = pinta(p.opciones);
387
- if (anchoDe(opciones) > anchoEscribir - signoW)
388
- opciones = pinta(p.cortas);
389
- filaEscribir = rellenar(`${(p.peligro ? c.peligro : c.aviso)(signo())} ${opciones}`, anchoEscribir);
390
- }
391
- else {
517
+ {
392
518
  const libre = Math.max(4, anchoEscribir - signoW);
393
- const color = d.trabajo ? c.gris : c.claro;
394
- if (!d.unidades.length) {
395
- filaEscribir = rellenar(`${color(signo())} ${c.gris(recortar(d.ejemplo, libre))}`, anchoEscribir);
519
+ const color = d.trabajo || d.pregunta ? c.gris : d.estado.clave ? colorDeModo(d.estado.clave) : c.claro;
520
+ const marca = bash ? c.bash(c.negrita('!')) : color(signo());
521
+ const unidades = bash ? d.unidades.slice(1) : d.unidades;
522
+ const enCursor = bash ? Math.max(0, d.cursor - 1) : d.cursor;
523
+ if (!unidades.length) {
524
+ const ejemplo = bash ? tt('bash.ejemplo') : d.pregunta ? '' : d.ejemplo;
525
+ filaEscribir = rellenar(`${marca} ${c.gris(recortar(ejemplo, libre))}`, anchoEscribir);
396
526
  cursor = { fila: 0, col: MARGEN + signoW };
397
527
  }
398
528
  else {
399
- const v = ventanaDeEntrada(d.unidades, d.cursor, libre);
400
- filaEscribir = rellenar(`${color(signo())} ${v.texto}`, anchoEscribir);
529
+ const v = ventanaDeEntrada(unidades, enCursor, libre);
530
+ filaEscribir = rellenar(`${marca} ${d.pregunta ? c.gris(v.texto) : v.texto}`, anchoEscribir);
401
531
  cursor = { fila: 0, col: MARGEN + signoW + v.col };
402
532
  }
533
+ if (d.pregunta)
534
+ cursor = null;
403
535
  }
404
536
  if (conPar)
405
537
  filaEscribir += ` ${cabezaDePar()} `;
406
- const tinta = (t) => c.violeta(t);
538
+ const tinta = (t) => (bash ? c.bash(t) : c.violeta(t));
407
539
  const linea = conPar ? `${tinta(carril(util - ANCHO_DE_PAR - 2))}${caraDePar(d.gesto)}${tinta(carril(2))}` : tinta(carril(util));
408
540
  let barra;
541
+ let zonasBarra = [];
409
542
  const enCarriles = (d.carriles ?? []).filter((k) => k.estado === 'trabaja').length;
410
543
  if (d.trabajo && !d.pregunta && d.carriles?.length) {
411
544
  const gasto = d.carriles.reduce((n, k) => n + (k.trabajo.acciones ?? 0), 0);
412
- const izq = `${animacion('tren', d.trabajo.paso)} ${tt('carriles.en_marcha', { n: enCarriles, total: d.carriles.length })}${c.gris(`${sep()}${duracion(d.ahora - d.trabajo.desde)}${gasto ? `${sep()}${acciones(gasto)}` : ''}`)}`;
413
- barra = izquierdaYDerecha(recortar(izq, util), c.tenue(c.gris(d.aviso ?? tt('tui.parar'))), util);
545
+ const base = `${animacion('tren', d.trabajo.paso)} ${tt('carriles.en_marcha', { n: enCarriles, total: d.carriles.length })}`;
546
+ const tiempo = `${sep()}${duracion(d.ahora - d.trabajo.desde)}`;
547
+ const izq = [`${base}${c.gris(`${tiempo}${gasto ? `${sep()}${acciones(gasto)}` : ''}`)}`, `${base}${c.gris(tiempo)}`, base].find((x) => anchoDe(x) <= util) ?? recortar(base, util);
548
+ barra = izquierdaYDerecha(izq, c.tenue(c.gris(d.aviso ?? tt('tui.parar'))), util);
414
549
  }
415
550
  else if (d.trabajo && !d.pregunta) {
416
- barra = izquierdaYDerecha(recortar(filaDeTrabajo(d.trabajo, d.ahora), util), c.tenue(c.gris(d.aviso ?? tt('tui.parar'))), util);
551
+ barra = izquierdaYDerecha(filaDeTrabajo(d.trabajo, d.ahora, util), c.tenue(c.gris(d.aviso ?? tt('tui.parar'))), util);
417
552
  }
418
553
  else if (d.aviso) {
419
554
  barra = izquierdaYDerecha(c.aviso(recortar(d.aviso, util)), '', util);
420
555
  }
556
+ else if (bash) {
557
+ const etiqueta = c.bash(c.negrita(tt('bash.etiqueta')));
558
+ const largo = `${etiqueta}${c.gris(`${sep()}${tt('bash.intro')}`)}`;
559
+ barra = izquierdaYDerecha(anchoDe(largo) + anchoDe(d.estado.saldoCorto ?? '') + 2 <= util ? largo : etiqueta, c.gris(d.estado.saldoCorto ?? ''), util);
560
+ }
421
561
  else {
422
- barra = componerBarra(d.estado, util);
562
+ const b = componerBarraConZonas(d.estado, util);
563
+ barra = b.texto;
564
+ zonasBarra = b.zonas;
423
565
  }
424
- const base = [filaEscribir, linea, barra].map((f) => `${m}${f}`);
425
- let filas = alto >= 3 ? base : alto === 2 ? [base[0], base[2]] : [base[0]];
426
- const carriles = (d.carriles ?? []).slice(0, Math.max(0, alto - filas.length - 2));
566
+ const filas = [filaEscribir, linea, ...(holgado ? [''] : []), barra].map((f) => (f ? `${m}${f}` : vacia));
567
+ const zonas = [];
568
+ const filaBarra = filas.length - 1;
569
+ for (const z of zonasBarra)
570
+ zonas.push({ fila: filaBarra, desde: MARGEN + z.desde, hasta: MARGEN + z.hasta, id: z.id });
571
+ let abajo = alto >= 3 ? filas : alto === 2 ? [filas[0], filas[filas.length - 1]] : [filas[0]];
572
+ if (abajo !== filas)
573
+ zonas.length = 0;
574
+ const carriles = (d.carriles ?? []).slice(0, Math.max(0, alto - abajo.length - 2));
427
575
  const filasDeCarriles = carriles.map((k) => `${m}${rellenar(filaDeCarril(k, util, d.ahora), util)}`);
428
- filas = [...filasDeCarriles, ...filas];
429
- if (cursor)
430
- cursor = { fila: cursor.fila + filasDeCarriles.length, col: cursor.col };
431
- const hueco = Math.max(0, alto - filas.length);
432
- let encima = [];
433
- if (d.pregunta) {
576
+ let arriba = [...filasDeCarriles];
577
+ const zonasPregunta = [];
578
+ if (d.pregunta && alto >= 3) {
434
579
  const p = d.pregunta;
435
- const marca = p.peligro ? c.peligro(capacidades().unicode ? '▲' : '!') : c.aviso('?');
436
- encima = envolver(p.texto, util - 2).map((l, i) => `${m}${rellenar(`${i === 0 ? marca : ' '} ${c.negrita(p.peligro ? c.peligro(l) : l)}`, util)}`);
437
- if (encima.length > hueco)
438
- encima = [`${m}${rellenar(`${marca} ${c.negrita(p.texto)}`, util)}`];
580
+ const marca = p.peligro ? c.peligro(u ? '▲' : '!') : c.aviso('?');
581
+ const bloque = [];
582
+ const sitio = Math.max(1, alto - abajo.length - arriba.length - 2);
583
+ const titulo = envolver(p.texto, util - 2);
584
+ bloque.push(...titulo.map((l, i) => `${m}${rellenar(`${i === 0 ? marca : ' '} ${c.negrita(p.peligro ? c.peligro(l) : l)}`, util)}`));
585
+ if (p.detalle)
586
+ bloque.push(...envolver(p.detalle, util - 2).slice(0, 3).map((l) => `${m} ${rellenar(c.gris(l), util - 2)}`));
587
+ if (holgado)
588
+ bloque.push(vacia);
589
+ const enFila = !p.vertical ? (filaDeBotones(p.opciones, p, d.sobre, util - 2) ?? filaDeBotones(p.cortas, p, d.sobre, util - 2)) : null;
590
+ const filasBotones = [];
591
+ const zonasBotones = [];
592
+ if (enFila) {
593
+ filasBotones.push(`${m} ${rellenar(enFila.texto, util - 2)}`);
594
+ for (const z of enFila.zonas)
595
+ zonasBotones.push({ fila: 0, desde: MARGEN + 2 + z.desde, hasta: MARGEN + 2 + z.hasta, id: z.id });
596
+ }
597
+ else {
598
+ const lista = filasDeLista(p, d.sobre, util, Math.max(1, sitio - bloque.length - (holgado ? 1 : 0)));
599
+ filasBotones.push(...lista.filas);
600
+ zonasBotones.push(...lista.zonas);
601
+ }
602
+ if (holgado)
603
+ filasBotones.push(vacia);
604
+ let todo = [...bloque, ...filasBotones];
605
+ let inicioBotones = bloque.length;
606
+ if (todo.length > sitio) {
607
+ const una = `${m}${rellenar(`${marca} ${c.negrita(recortar(p.texto, util - 2))}`, util)}`;
608
+ todo = [una, ...filasBotones.filter((x) => x !== vacia)];
609
+ inicioBotones = 1;
610
+ }
611
+ for (const z of zonasBotones)
612
+ zonasPregunta.push({ ...z, fila: inicioBotones + z.fila });
613
+ arriba = [...todo, ...arriba];
439
614
  }
440
- else if (d.menu && d.menu.opciones.length) {
615
+ let separacion = medio || d.fondo ? vacia : null;
616
+ const zonaFondo = [];
617
+ if (separacion !== null && d.fondo) {
618
+ const texto = `${u ? '↓' : 'v'} ${tt('tui.al_fondo')}${d.fondo.nuevas ? `${sep()}${tt('tui.nuevas', { n: d.fondo.nuevas.toLocaleString('en-US') })}` : ''}`;
619
+ const b = d.sobre === 'fondo' ? c.boton(` ${texto} `) : boton(texto, null);
620
+ separacion = `${m}${rellenar(b, util)}`;
621
+ zonaFondo.push({ fila: 0, desde: MARGEN, hasta: MARGEN + anchoDe(b), id: 'fondo' });
622
+ }
623
+ const cabeza = separacion === null ? [] : [separacion];
624
+ const todas = [...cabeza, ...arriba, ...abajo];
625
+ const desplazar = cabeza.length + arriba.length;
626
+ for (const z of zonas)
627
+ z.fila += desplazar;
628
+ const zonasFinales = [...zonaFondo, ...zonasPregunta.map((z) => ({ ...z, fila: z.fila + cabeza.length })), ...zonas];
629
+ if (cursor)
630
+ cursor = { fila: cursor.fila + desplazar, col: cursor.col };
631
+ const hueco = Math.max(0, alto - todas.length);
632
+ let encima = [];
633
+ const zonasEncima = [];
634
+ if (!d.pregunta && d.menu && d.menu.opciones.length) {
441
635
  const ops = d.menu.opciones;
442
636
  const visibles = Math.min(6, ops.length, hueco);
443
637
  if (visibles > 0) {
@@ -446,20 +640,61 @@ export function componerCarril(d, ancho, alto) {
446
640
  encima = ops.slice(ini, ini + visibles).map((o, k) => {
447
641
  const i = ini + k;
448
642
  const sel = i === d.menu.elegida;
449
- const marca = sel ? (o.peligro ? c.peligro : c.claro)(capacidades().unicode ? '▌' : '>') : ' ';
643
+ const sobre = d.sobre === `menu:${i}`;
644
+ zonasEncima.push({ fila: k, desde: MARGEN, hasta: MARGEN + util, id: `menu:${i}` });
645
+ const tinta = o.modo ? colorDeModo(o.modo) : o.peligro ? c.peligro : c.claro;
646
+ const marca = sel ? tinta(u ? '▌' : '>') : ' ';
450
647
  const resto = Math.max(0, util - 2 - anchoNombre);
451
648
  const desc = recortar(o.descripcion, resto);
452
649
  if (o.peligro) {
453
- const nombre = rellenar(recortar(`${capacidades().unicode ? '▲' : '!'} ${o.nombre}`, anchoNombre - 1), anchoNombre);
454
- return `${m}${rellenar(`${marca} ${sel ? c.negrita(c.peligro(nombre)) : c.peligro(nombre)}${sel ? c.peligro(desc) : c.tenue(c.peligro(desc))}`, util)}`;
650
+ const nombre = rellenar(recortar(`${u ? '▲' : '!'} ${o.nombre}`, anchoNombre - 1), anchoNombre);
651
+ return `${m}${rellenar(`${marca} ${sel || sobre ? c.negrita(c.peligro(nombre)) : c.peligro(nombre)}${sel ? c.peligro(desc) : c.tenue(c.peligro(desc))}`, util)}`;
455
652
  }
456
653
  const nombre = rellenar(recortar(o.nombre, anchoNombre - 1), anchoNombre);
457
- return `${m}${rellenar(`${marca} ${sel ? c.negrita(c.marca(nombre)) : c.gris(nombre)}${sel ? desc : c.tenue(c.gris(desc))}`, util)}`;
654
+ if (o.modo)
655
+ return `${m}${rellenar(`${marca} ${sel || sobre ? c.negrita(tinta(nombre)) : tinta(nombre)}${sel || sobre ? desc : c.tenue(c.gris(desc))}`, util)}`;
656
+ return `${m}${rellenar(`${marca} ${sel ? c.negrita(c.marca(nombre)) : sobre ? c.marca(nombre) : c.gris(nombre)}${sel || sobre ? desc : c.tenue(c.gris(desc))}`, util)}`;
458
657
  });
459
658
  }
460
659
  }
461
- encima = encima.slice(Math.max(0, encima.length - hueco));
462
- return { encima, carril: filas, cursor: cursor && filas.length ? cursor : null };
660
+ const fuera = Math.max(0, encima.length - hueco);
661
+ encima = encima.slice(fuera);
662
+ return {
663
+ encima,
664
+ carril: todas,
665
+ cursor: cursor && todas.length ? cursor : null,
666
+ zonas: zonasFinales,
667
+ zonasEncima: zonasEncima.filter((z) => z.fila >= fuera).map((z) => ({ ...z, fila: z.fila - fuera })),
668
+ };
669
+ }
670
+ function filasDeLista(p, sobre, util, max) {
671
+ const u = capacidades().unicode;
672
+ const n = p.opciones.length;
673
+ const visibles = Math.max(1, Math.min(n, max, 8));
674
+ const ini = Math.max(0, Math.min(p.elegida - visibles + 1, n - visibles));
675
+ const filas = [];
676
+ const zonas = [];
677
+ const recom = anchoDe(tt('bandeja.recomendada')) + 2;
678
+ const anchoEtiqueta = Math.max(...p.opciones.map((o, i) => anchoDe(o) + (p.notas?.[i]?.recomendada ? recom : 0))) + 2;
679
+ const conNotas = anchoEtiqueta <= Math.floor(util * 0.65);
680
+ for (let k = 0; k < visibles; k++) {
681
+ const i = ini + k;
682
+ const sel = i === p.elegida;
683
+ const enc = sobre === `opcion:${i}`;
684
+ const nota = p.notas?.[i];
685
+ const peligro = p.peligro || nota?.peligro;
686
+ const marca = sel ? (peligro ? c.peligro : c.claro)(u ? '▌' : '>') : ' ';
687
+ const num = i < 9 ? `${i + 1}` : ' ';
688
+ const recomendada = nota?.recomendada ? ` ${c.marca(tt('bandeja.recomendada'))}` : '';
689
+ const etiqueta = sel ? c.negrita(peligro ? c.peligro(p.opciones[i]) : p.opciones[i]) : enc ? c.marca(p.opciones[i]) : peligro ? c.peligro(p.opciones[i]) : p.opciones[i];
690
+ const cabeza = `${marca} ${c.claro(num)} `;
691
+ const base = conNotas ? `${cabeza}${rellenar(`${etiqueta}${recomendada}`, anchoEtiqueta)}` : `${cabeza}${recortar(`${etiqueta}${recomendada}`, Math.max(4, util - anchoDe(cabeza)))}`;
692
+ const resto = util - anchoDe(base);
693
+ const extra = conNotas && nota?.nota && resto >= 6 ? c.gris(recortar(nota.nota, resto)) : '';
694
+ filas.push(`${m}${rellenar(`${base}${extra}`, util)}`);
695
+ zonas.push({ fila: k, desde: MARGEN, hasta: MARGEN + util, id: `opcion:${i}` });
696
+ }
697
+ return { filas, zonas };
463
698
  }
464
699
  export function opcionesDePregunta(tipo) {
465
700
  switch (tipo) {
@@ -495,20 +730,37 @@ export function rutaDeImagenPegada(texto) {
495
730
  v = decodeURIComponent(v.slice(7));
496
731
  return IMAGEN.test(v) && /^(\/|~|[A-Za-z]:\\|\.)/.test(v) ? v : null;
497
732
  }
733
+ function sinBlancoDoble(lineas, previaEnBlanco) {
734
+ let i = 0;
735
+ if (previaEnBlanco)
736
+ while (i < lineas.length && lineas[i].trim() === '')
737
+ i++;
738
+ return i ? lineas.slice(i) : lineas;
739
+ }
498
740
  export class Superficie {
499
741
  o;
500
742
  bloques = [];
501
743
  lineas = [];
744
+ volcadas = 0;
745
+ subidas = 0;
746
+ vistaSucia = false;
502
747
  anchoCompuesto = 0;
748
+ ultimoAlto = 0;
749
+ ultimoTrasBlanco = false;
503
750
  encimaPintadas = 0;
504
751
  carrilPintadas = 0;
505
752
  activa = false;
506
753
  oyente = null;
507
754
  alRedimensionar = null;
755
+ alReanudar = null;
508
756
  relojResize = null;
509
757
  relojAnim = null;
510
758
  relojAviso = null;
511
759
  pegando = { pegando: false };
760
+ raton;
761
+ zonas = [];
762
+ sobre = null;
763
+ pulsado = null;
512
764
  unidades = [];
513
765
  cursor = 0;
514
766
  pegados = [];
@@ -518,7 +770,7 @@ export class Superficie {
518
770
  borrador = [];
519
771
  menuCerradoPara = null;
520
772
  menuElegida = 0;
521
- cola = null;
773
+ cola = [];
522
774
  ctrlC = 0;
523
775
  ejemplo = null;
524
776
  resolverLectura = null;
@@ -535,6 +787,7 @@ export class Superficie {
535
787
  peligroPintado = false;
536
788
  constructor(o) {
537
789
  this.o = o;
790
+ this.raton = o.raton !== false;
538
791
  }
539
792
  get ancho() {
540
793
  return Math.max(20, this.o.salida.columns || 80);
@@ -545,6 +798,9 @@ export class Superficie {
545
798
  get enfocada() {
546
799
  return this.foco;
547
800
  }
801
+ get conRaton() {
802
+ return this.raton;
803
+ }
548
804
  escribir(s) {
549
805
  this.o.salida.write(s);
550
806
  }
@@ -553,6 +809,7 @@ export class Superficie {
553
809
  const ops = this.menuCerradoPara === valor ? null : opcionesDelMenu(valor);
554
810
  if (ops && this.menuElegida >= ops.length)
555
811
  this.menuElegida = Math.max(0, ops.length - 1);
812
+ const pendientes = this.lineas.length - this.volcadas;
556
813
  return {
557
814
  unidades: this.unidades.map((u) => this.vistaDeUnidad(u)),
558
815
  cursor: this.cursor,
@@ -565,6 +822,8 @@ export class Superficie {
565
822
  carriles: this.carriles,
566
823
  gesto: this.pregunta ? 'pregunta' : this.trabajo || this.carriles.some((k) => k.estado === 'trabaja') ? (((this.trabajo?.paso ?? this.carriles[0]?.trabajo.paso ?? 0) >> 1) % 4) : this.gesto,
567
824
  ahora: Date.now(),
825
+ sobre: this.raton ? this.sobre : null,
826
+ fondo: this.subidas > 0 || pendientes > 0 ? { nuevas: pendientes } : null,
568
827
  };
569
828
  }
570
829
  vistaDeUnidad(u) {
@@ -584,14 +843,31 @@ export class Superficie {
584
843
  return this.alto - n + 1;
585
844
  }
586
845
  desplazadas(altoCarril) {
587
- return Math.max(0, this.lineas.length - (this.alto - altoCarril));
846
+ return Math.max(0, this.volcadas - (this.alto - altoCarril));
588
847
  }
589
848
  filaTrasLaConversacion(altoCarril) {
590
- return Math.min(this.lineas.length, Math.max(0, this.alto - altoCarril)) + 1;
849
+ return Math.min(this.volcadas, Math.max(0, this.alto - altoCarril)) + 1;
591
850
  }
592
851
  lineaDeFila(fila, altoCarril) {
593
- const indice = this.desplazadas(altoCarril) + (fila - 1);
594
- return indice >= 0 ? (this.lineas[indice] ?? '') : '';
852
+ const indice = this.desplazadas(altoCarril) - this.subidas + (fila - 1);
853
+ return indice >= 0 && indice < this.volcadas ? (this.lineas[indice] ?? '') : '';
854
+ }
855
+ componerTodo() {
856
+ const out = [];
857
+ for (const b of this.bloques) {
858
+ this.ultimoTrasBlanco = out.length === 0 || out[out.length - 1].trim() === '';
859
+ const l = sinBlancoDoble(b(this.ancho), this.ultimoTrasBlanco);
860
+ this.ultimoAlto = l.length;
861
+ out.push(...l);
862
+ }
863
+ this.lineas = out;
864
+ }
865
+ recortarMemoria() {
866
+ const sobran = this.lineas.length - MAX_LINEAS;
867
+ if (sobran <= 0)
868
+ return;
869
+ this.lineas.splice(0, sobran);
870
+ this.volcadas = Math.max(0, this.volcadas - sobran);
595
871
  }
596
872
  pintar() {
597
873
  if (!this.activa)
@@ -607,21 +883,34 @@ export class Superficie {
607
883
  const k = componerCarril(this.datos(), this.ancho, this.alto);
608
884
  const n = k.carril.length;
609
885
  let s = '\x1b[?2026h\x1b[?25l';
610
- if (this.carrilPintadas && n > this.carrilPintadas) {
611
- s += this.destapar();
612
- const ocupadas = Math.min(this.lineas.length, Math.max(0, this.alto - this.carrilPintadas));
613
- const subir = Math.max(0, ocupadas - Math.max(0, this.alto - n));
614
- if (subir)
615
- s += `\x1b[${this.alto};1H${'\r\n'.repeat(subir)}`;
886
+ const subido = this.subidas > 0 || this.volcadas < this.lineas.length;
887
+ if (this.carrilPintadas && n !== this.carrilPintadas && subido) {
888
+ this.vistaSucia = true;
889
+ this.encimaPintadas = 0;
616
890
  }
617
- if (this.carrilPintadas && n < this.carrilPintadas) {
618
- s += this.destapar();
619
- const topNuevo = this.filaDelCarril(n);
620
- for (let fila = 1; fila < topNuevo; fila++)
621
- s += `\x1b[${fila};1H\x1b[2K${this.lineaDeFila(fila, n)}`;
891
+ else {
892
+ if (this.carrilPintadas && n > this.carrilPintadas) {
893
+ s += this.destapar();
894
+ const ocupadas = Math.min(this.volcadas, Math.max(0, this.alto - this.carrilPintadas));
895
+ const subir = Math.max(0, ocupadas - Math.max(0, this.alto - n));
896
+ if (subir)
897
+ s += `\x1b[${this.alto};1H${'\r\n'.repeat(subir)}`;
898
+ }
899
+ if (this.carrilPintadas && n < this.carrilPintadas) {
900
+ s += this.destapar();
901
+ const topNuevo = this.filaDelCarril(n);
902
+ for (let fila = 1; fila < topNuevo; fila++)
903
+ s += `\x1b[${fila};1H\x1b[2K${this.lineaDeFila(fila, n)}`;
904
+ }
622
905
  }
623
906
  this.carrilPintadas = n;
624
907
  const top = this.filaDelCarril(n);
908
+ if (this.vistaSucia) {
909
+ for (let fila = 1; fila < top; fila++)
910
+ s += `\x1b[${fila};1H\x1b[2K${this.lineaDeFila(fila, n)}`;
911
+ this.vistaSucia = false;
912
+ this.encimaPintadas = 0;
913
+ }
625
914
  for (let i = k.encima.length; i < this.encimaPintadas; i++) {
626
915
  const fila = top - 1 - i;
627
916
  if (fila >= 1)
@@ -636,6 +925,12 @@ export class Superficie {
636
925
  k.carril.forEach((l, i) => {
637
926
  s += `\x1b[${top + i};1H\x1b[2K${l}`;
638
927
  });
928
+ this.zonas = [
929
+ ...k.zonasEncima.map((z) => ({ ...z, fila: top - k.encima.length + z.fila })),
930
+ ...k.zonas.map((z) => ({ ...z, fila: top + z.fila })),
931
+ ];
932
+ if (this.sobre && !this.zonas.some((z) => z.id === this.sobre))
933
+ this.sobre = null;
639
934
  if (k.cursor)
640
935
  s += `\x1b[${top + k.cursor.fila};${k.cursor.col + 1}H\x1b[?25h`;
641
936
  s += '\x1b[?2026l';
@@ -659,9 +954,13 @@ export class Superficie {
659
954
  this.consejosVisibles = false;
660
955
  if (!this.activa)
661
956
  return;
662
- this.lineas = this.bloques.flatMap((b) => b(this.ancho));
663
- if (this.lineas.length > MAX_LINEAS)
664
- this.lineas.splice(0, this.lineas.length - MAX_LINEAS);
957
+ if (this.subidas > 0 || this.volcadas < this.lineas.length) {
958
+ this.recomponer();
959
+ return;
960
+ }
961
+ this.componerTodo();
962
+ this.volcadas = this.lineas.length;
963
+ this.recortarMemoria();
665
964
  const n = componerCarril(this.datos(), this.ancho, this.alto).carril.length;
666
965
  const top = this.filaDelCarril(n);
667
966
  let s = '\x1b[?2026h\x1b[?25l';
@@ -672,41 +971,68 @@ export class Superficie {
672
971
  this.escribir(s);
673
972
  this.pintar();
674
973
  }
974
+ terminaEnBlanco() {
975
+ const ultima = this.lineas[this.lineas.length - 1];
976
+ return ultima === undefined || ultima.trim() === '';
977
+ }
675
978
  anadir(b) {
676
979
  this.ocultarConsejos();
677
- const destapado = this.activa ? this.destapar() : '';
678
- const n = this.activa ? this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length : 0;
679
- const desde = this.activa ? this.filaTrasLaConversacion(n) : 0;
680
980
  this.bloques.push(b);
681
981
  if (this.bloques.length > MAX_BLOQUES)
682
982
  this.bloques.splice(0, this.bloques.length - MAX_BLOQUES);
683
- const nuevas = b(this.ancho);
983
+ this.ultimoTrasBlanco = this.terminaEnBlanco();
984
+ const nuevas = sinBlancoDoble(b(this.ancho), this.ultimoTrasBlanco);
985
+ this.ultimoAlto = nuevas.length;
986
+ if (this.activa && (this.subidas > 0 || this.volcadas < this.lineas.length)) {
987
+ this.lineas.push(...nuevas);
988
+ this.recortarMemoria();
989
+ this.pintar();
990
+ return;
991
+ }
992
+ const destapado = this.activa ? this.destapar() : '';
684
993
  this.lineas.push(...nuevas);
685
- if (this.lineas.length > MAX_LINEAS)
686
- this.lineas.splice(0, this.lineas.length - MAX_LINEAS);
687
- if (!this.activa)
994
+ if (!this.activa) {
995
+ this.volcadas = this.lineas.length;
996
+ this.recortarMemoria();
997
+ return;
998
+ }
999
+ this.escribir(destapado);
1000
+ this.volcar();
1001
+ this.pintar();
1002
+ }
1003
+ volcar() {
1004
+ const nuevas = this.lineas.slice(this.volcadas);
1005
+ if (!nuevas.length)
688
1006
  return;
689
- let s = `\x1b[?2026h\x1b[?25l${destapado}`;
690
- s += `\x1b[${desde};1H\x1b[J`;
1007
+ const n = this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length;
1008
+ const desde = this.filaTrasLaConversacion(n);
1009
+ let s = `\x1b[?2026h\x1b[?25l\x1b[${desde};1H\x1b[J`;
691
1010
  s += nuevas.map((l) => `${l}\x1b[K\r\n`).join('');
692
1011
  s += '\r\n'.repeat(Math.max(0, n - 1));
693
1012
  s += '\x1b[?2026l';
1013
+ this.volcadas = this.lineas.length;
1014
+ this.recortarMemoria();
1015
+ this.encimaPintadas = 0;
694
1016
  this.escribir(s);
695
- this.pintar();
696
1017
  }
697
1018
  ultimoBloque() {
698
1019
  return this.bloques[this.bloques.length - 1];
699
1020
  }
700
1021
  reemplazarUltimo(b) {
701
1022
  const viejo = this.ultimoBloque();
702
- const k = viejo ? viejo(this.ancho).length : -1;
703
- const nuevas = b(this.ancho);
1023
+ const k = viejo ? this.ultimoAlto : -1;
1024
+ const nuevas = sinBlancoDoble(b(this.ancho), this.ultimoTrasBlanco);
704
1025
  if (!viejo || !this.activa || nuevas.length !== k || this.encimaPintadas > 0 || this.lineas.length < k) {
705
1026
  this.anadir(b);
706
1027
  return;
707
1028
  }
708
1029
  this.bloques[this.bloques.length - 1] = b;
709
1030
  this.lineas.splice(this.lineas.length - k, k, ...nuevas);
1031
+ if (this.subidas > 0 || this.volcadas < this.lineas.length) {
1032
+ this.vistaSucia = true;
1033
+ this.pintar();
1034
+ return;
1035
+ }
710
1036
  const n = this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length;
711
1037
  const primera = this.lineas.length - k - this.desplazadas(n) + 1;
712
1038
  let s = '\x1b[?2026h\x1b[?25l';
@@ -724,9 +1050,10 @@ export class Superficie {
724
1050
  return;
725
1051
  fijarCapacidades({ ancho: this.ancho });
726
1052
  this.anchoCompuesto = this.ancho;
727
- this.lineas = this.bloques.flatMap((b) => b(this.ancho));
728
- if (this.lineas.length > MAX_LINEAS)
729
- this.lineas.splice(0, this.lineas.length - MAX_LINEAS);
1053
+ this.componerTodo();
1054
+ this.volcadas = this.lineas.length;
1055
+ this.subidas = 0;
1056
+ this.recortarMemoria();
730
1057
  const n = componerCarril(this.datos(), this.ancho, this.alto).carril.length;
731
1058
  let s = alEmpezar ? `\x1b[?2026h\x1b[?25l${'\r\n'.repeat(this.alto)}\x1b[H` : '\x1b[?2026h\x1b[?25l\x1b[H\x1b[2J\x1b[3J\x1b[H';
732
1059
  s += this.lineas.map((l) => `${l}\x1b[K\r\n`).join('');
@@ -737,6 +1064,113 @@ export class Superficie {
737
1064
  this.escribir(s);
738
1065
  this.pintar();
739
1066
  }
1067
+ filasDeVista() {
1068
+ return Math.max(1, this.filaDelCarril(this.carrilPintadas || 3) - 1);
1069
+ }
1070
+ desplazar(n) {
1071
+ if (!this.activa)
1072
+ return;
1073
+ const max = this.desplazadas(this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length);
1074
+ const nuevo = Math.max(0, Math.min(max, this.subidas + n));
1075
+ if (nuevo === 0) {
1076
+ if (this.subidas > 0 || this.volcadas < this.lineas.length)
1077
+ this.irAlFondo();
1078
+ return;
1079
+ }
1080
+ if (nuevo === this.subidas)
1081
+ return;
1082
+ this.subidas = nuevo;
1083
+ this.vistaSucia = true;
1084
+ this.pintar();
1085
+ }
1086
+ irAlFondo() {
1087
+ if (!this.activa || (this.subidas === 0 && this.volcadas === this.lineas.length))
1088
+ return;
1089
+ this.subidas = 0;
1090
+ const n = this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length;
1091
+ const top = this.filaDelCarril(n);
1092
+ let s = '\x1b[?2026h\x1b[?25l';
1093
+ for (let fila = 1; fila < top; fila++)
1094
+ s += `\x1b[${fila};1H\x1b[2K${this.lineaDeFila(fila, n)}`;
1095
+ s += '\x1b[?2026l';
1096
+ this.encimaPintadas = 0;
1097
+ this.escribir(s);
1098
+ this.volcar();
1099
+ this.pintar();
1100
+ }
1101
+ get _vista() {
1102
+ return { subidas: this.subidas, pendientes: this.lineas.length - this.volcadas };
1103
+ }
1104
+ fijarRaton(si) {
1105
+ this.raton = si;
1106
+ this.sobre = null;
1107
+ this.pulsado = null;
1108
+ if (this.activa)
1109
+ this.escribir(si ? RATON_SI : RATON_NO);
1110
+ if (!si)
1111
+ this.irAlFondo();
1112
+ this.pintar();
1113
+ }
1114
+ zonaEn(x, y) {
1115
+ const col = x - 1;
1116
+ for (const z of this.zonas)
1117
+ if (z.fila === y && col >= z.desde && col < z.hasta)
1118
+ return z.id;
1119
+ return null;
1120
+ }
1121
+ alRaton(k) {
1122
+ if (!this.raton)
1123
+ return;
1124
+ if (k.rueda) {
1125
+ this.desplazar(-k.rueda * 3);
1126
+ return;
1127
+ }
1128
+ const zona = this.zonaEn(k.x, k.y);
1129
+ if (k.mover && k.boton === 3) {
1130
+ if (zona !== this.sobre) {
1131
+ this.sobre = zona;
1132
+ this.pintar();
1133
+ }
1134
+ return;
1135
+ }
1136
+ if (k.boton !== 0 && !(k.soltar && k.boton === 3))
1137
+ return;
1138
+ if (!k.soltar) {
1139
+ if (!k.mover)
1140
+ this.pulsado = zona;
1141
+ return;
1142
+ }
1143
+ const pulsado = this.pulsado;
1144
+ this.pulsado = null;
1145
+ if (pulsado && zona === pulsado)
1146
+ this.activar(pulsado);
1147
+ }
1148
+ activar(id) {
1149
+ if (id === 'fondo')
1150
+ return this.irAlFondo();
1151
+ if (id === 'bandeja') {
1152
+ if (this.resolverLectura && !this.trabajo && !this.pregunta)
1153
+ this.o.alBandeja?.();
1154
+ return;
1155
+ }
1156
+ if (id === 'modo') {
1157
+ if (!this.pregunta)
1158
+ this.o.alCambiarModo?.();
1159
+ return;
1160
+ }
1161
+ const opcion = /^opcion:(\d+)$/.exec(id);
1162
+ if (opcion && this.pregunta)
1163
+ return this.responder(Number(opcion[1]));
1164
+ const menu = /^menu:(\d+)$/.exec(id);
1165
+ if (menu && !this.pregunta) {
1166
+ this.menuElegida = Number(menu[1]);
1167
+ this.tecla({ t: 'intro' });
1168
+ }
1169
+ }
1170
+ modosSi() {
1171
+ return `\x1b[?2004h\x1b[?1004h${this.raton ? RATON_SI : ''}`;
1172
+ }
1173
+ static MODOS_NO = `${RATON_NO}\x1b[?2004l\x1b[?1004l\x1b[?25h`;
740
1174
  iniciar(bienvenida, consejos = []) {
741
1175
  if (this.activa)
742
1176
  return;
@@ -744,7 +1178,7 @@ export class Superficie {
744
1178
  if (bienvenida)
745
1179
  this.bloques.push(bienvenida);
746
1180
  if (consejos.length) {
747
- const ocupado = (bienvenida ? bienvenida(this.ancho).length : 0) + 3;
1181
+ const ocupado = (bienvenida ? bienvenida(this.ancho).length : 0) + componerCarril(this.datos(), this.ancho, this.alto).carril.length;
748
1182
  let n = consejos.length;
749
1183
  while (n > 0 && ocupado + bloqueDeConsejos(() => consejos.slice(0, n))(this.ancho).length > this.alto)
750
1184
  n--;
@@ -753,7 +1187,7 @@ export class Superficie {
753
1187
  if (n > 0)
754
1188
  this.bloques.push(bloqueDeConsejos(() => (this.consejosVisibles ? this.consejos : [])));
755
1189
  }
756
- this.escribir('\x1b[?2004h\x1b[?1004h');
1190
+ this.escribir(this.modosSi());
757
1191
  try {
758
1192
  this.o.entrada.setRawMode?.(true);
759
1193
  }
@@ -799,12 +1233,13 @@ export class Superficie {
799
1233
  terminar(despedida) {
800
1234
  if (!this.activa)
801
1235
  return;
1236
+ this.irAlFondo();
802
1237
  const n = this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length;
803
1238
  let s = this.destapar();
804
1239
  s += `\x1b[${this.filaTrasLaConversacion(n)};1H\x1b[J`;
805
1240
  if (despedida)
806
- s += `${m}${despedida}\r\n`;
807
- s += '\x1b[?2004l\x1b[?1004l\x1b[?25h';
1241
+ s += `${this.terminaEnBlanco() ? '' : '\r\n'}${m}${despedida}\r\n`;
1242
+ s += Superficie.MODOS_NO;
808
1243
  this.escribir(s);
809
1244
  this.activa = false;
810
1245
  if (this.relojAnim)
@@ -817,6 +1252,8 @@ export class Superficie {
817
1252
  this.o.entrada.removeListener('data', this.oyente);
818
1253
  if (this.alRedimensionar)
819
1254
  this.o.salida.removeListener('resize', this.alRedimensionar);
1255
+ if (this.alReanudar)
1256
+ process.removeListener('SIGCONT', this.alReanudar);
820
1257
  try {
821
1258
  this.o.entrada.setRawMode?.(false);
822
1259
  }
@@ -827,21 +1264,65 @@ export class Superficie {
827
1264
  restaurarDeUrgencia() {
828
1265
  if (!this.activa)
829
1266
  return;
830
- this.o.salida.write('\x1b[?2004l\x1b[?1004l\x1b[?25h\x1b[?2026l');
1267
+ this.o.salida.write(`${Superficie.MODOS_NO}\x1b[?2026l`);
831
1268
  try {
832
1269
  this.o.entrada.setRawMode?.(false);
833
1270
  }
834
1271
  catch {
835
1272
  }
836
1273
  }
1274
+ suspender() {
1275
+ if (process.platform === 'win32' && !this.o.suspenderProceso)
1276
+ return;
1277
+ this.irAlFondo();
1278
+ const n = this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length;
1279
+ let s = this.destapar();
1280
+ s += `\x1b[${this.filaTrasLaConversacion(n)};1H\x1b[J`;
1281
+ s += Superficie.MODOS_NO;
1282
+ this.escribir(s);
1283
+ try {
1284
+ this.o.entrada.setRawMode?.(false);
1285
+ }
1286
+ catch {
1287
+ }
1288
+ if (!this.alReanudar) {
1289
+ this.alReanudar = () => this.reanudar();
1290
+ if (!this.o.suspenderProceso)
1291
+ process.on('SIGCONT', this.alReanudar);
1292
+ }
1293
+ if (this.o.suspenderProceso)
1294
+ this.o.suspenderProceso();
1295
+ else
1296
+ process.kill(process.pid, 'SIGTSTP');
1297
+ }
1298
+ reanudar() {
1299
+ if (!this.activa)
1300
+ return;
1301
+ try {
1302
+ this.o.entrada.setRawMode?.(true);
1303
+ }
1304
+ catch {
1305
+ }
1306
+ this.o.entrada.resume();
1307
+ const n = componerCarril(this.datos(), this.ancho, this.alto).carril.length;
1308
+ let s = `\x1b[?2026h\x1b[?25l${this.modosSi()}${'\r\n'.repeat(this.alto)}`;
1309
+ for (let fila = 1; fila < this.filaDelCarril(n); fila++)
1310
+ s += `\x1b[${fila};1H\x1b[2K${this.lineaDeFila(fila, n)}`;
1311
+ s += '\x1b[?2026l';
1312
+ this.carrilPintadas = n;
1313
+ this.encimaPintadas = 0;
1314
+ this.escribir(s);
1315
+ this.pintar();
1316
+ }
837
1317
  leer(ejemplo = null) {
838
1318
  this.ejemplo = ejemplo;
839
- if (this.cola !== null) {
840
- const r = this.cola;
841
- this.cola = null;
1319
+ const enCola = this.cola.shift();
1320
+ if (enCola !== undefined) {
1321
+ const r = enCola;
842
1322
  this.ejemplo = null;
843
- this.mostrarAviso(null);
844
- this.anadir(bloqueDeLaPersona(this.vistaDeEnvio(r)));
1323
+ this.mostrarAviso(this.cola.length ? tt('tui.en_cola_n', { n: this.cola.length }) : null);
1324
+ if (r.trim() && r.trim() !== '!')
1325
+ this.anadir(this.bloqueDeEnvio(r));
845
1326
  return Promise.resolve(this.expandir(r));
846
1327
  }
847
1328
  return new Promise((resolver) => {
@@ -852,6 +1333,15 @@ export class Superficie {
852
1333
  this.pintar();
853
1334
  });
854
1335
  }
1336
+ escribirYMandar(texto) {
1337
+ if (!this.libre)
1338
+ return;
1339
+ this.ponerValor(texto);
1340
+ this.enviar();
1341
+ }
1342
+ get libre() {
1343
+ return Boolean(this.resolverLectura) && !this.trabajo && !this.pregunta;
1344
+ }
855
1345
  trabajar(alParar) {
856
1346
  if (this.trabajo)
857
1347
  this.trabajo.alParar = alParar;
@@ -885,31 +1375,55 @@ export class Superficie {
885
1375
  this.pintar();
886
1376
  }
887
1377
  preguntar(texto, tipo = 'normal') {
1378
+ const o = opcionesDePregunta(tipo);
1379
+ const peligro = tipo === 'bypass';
888
1380
  return new Promise((resolver) => {
889
- const mostrar = () => {
890
- const o = opcionesDePregunta(tipo);
891
- this.pregunta = { texto, ...o, ...(tipo === 'bypass' ? { peligro: true } : {}), resolver };
892
- this.o.alPreguntar?.(texto);
893
- this.pintar();
894
- };
895
- if (this.pregunta)
896
- this.esperando.push(mostrar);
897
- else
898
- mostrar();
1381
+ this.mostrarPregunta({
1382
+ texto,
1383
+ opciones: o.opciones,
1384
+ cortas: o.cortas,
1385
+ elegida: o.elegida,
1386
+ ...(peligro ? { peligro: true } : {}),
1387
+ indiceNo: o.valores.indexOf('no'),
1388
+ siConY: o.valores[o.elegida] !== 'no' || peligro,
1389
+ }, (i) => resolver(i === null ? 'no' : (o.valores[i] ?? 'no')));
1390
+ });
1391
+ }
1392
+ elegir(texto, opciones, o = {}) {
1393
+ return new Promise((resolver) => {
1394
+ this.mostrarPregunta({
1395
+ texto,
1396
+ ...(o.detalle ? { detalle: o.detalle } : {}),
1397
+ opciones: opciones.map((x) => x.etiqueta),
1398
+ cortas: opciones.map((x) => x.corta ?? x.etiqueta),
1399
+ notas: opciones.map((x) => ({ nota: x.nota, recomendada: x.recomendada, peligro: x.peligro })),
1400
+ elegida: Math.max(0, Math.min(opciones.length - 1, o.elegida ?? 0)),
1401
+ ...(o.vertical ? { vertical: true } : {}),
1402
+ indiceNo: null,
1403
+ siConY: false,
1404
+ }, resolver);
899
1405
  });
900
1406
  }
1407
+ mostrarPregunta(p, resolver) {
1408
+ const mostrar = () => {
1409
+ this.pregunta = { ...p, resolver, sordaHasta: Date.now() + this.guardaDePregunta };
1410
+ this.o.alPreguntar?.(p.texto);
1411
+ this.pintar();
1412
+ };
1413
+ if (this.pregunta)
1414
+ this.esperando.push(mostrar);
1415
+ else
1416
+ mostrar();
1417
+ }
901
1418
  responder(i) {
902
1419
  const p = this.pregunta;
903
1420
  if (!p)
904
1421
  return;
905
- const r = p.valores[i] ?? 'no';
906
1422
  this.pregunta = null;
907
- const etiqueta = p.opciones[i] ?? tt('tui.no');
908
- if (r !== 'si' && !p.peligro)
909
- this.anadir(bloqueDeLinea(`${c.gris('?')} ${p.texto} ${c.gris('→')} ${r === 'no' ? c.gris(etiqueta) : c.marca(etiqueta)}`));
910
- else
911
- this.pintar();
912
- p.resolver(r);
1423
+ this.sobre = null;
1424
+ this.pulsado = null;
1425
+ this.pintar();
1426
+ p.resolver(i);
913
1427
  this.esperando.shift()?.();
914
1428
  }
915
1429
  mostrarAviso(texto, ms = 0) {
@@ -933,14 +1447,18 @@ export class Superficie {
933
1447
  }
934
1448
  expandir(v) {
935
1449
  return v
936
- .replace(/\uE000(\d+)/g, (_x, i) => this.pegados[Number(i)] ?? '')
937
- .replace(/\uE003(\d+)/g, (_x, i) => `\uE001${this.imagenes[Number(i)] ?? ''}\uE002`);
1450
+ .replace(/(\d+)/g, (_x, i) => this.pegados[Number(i)] ?? '')
1451
+ .replace(/(\d+)/g, (_x, i) => `${this.imagenes[Number(i)] ?? ''}`);
1452
+ }
1453
+ bloqueDeEnvio(crudo) {
1454
+ const v = crudo.trim();
1455
+ return v.startsWith('!') ? bloqueDeBash(this.vistaDeEnvio(v.slice(1).trim())) : bloqueDeLaPersona(this.vistaDeEnvio(v));
938
1456
  }
939
1457
  vistaDeEnvio(v) {
940
- return v.replace(/[\uE000\uE003]\d+/g, (x) => sinColorSimple(this.vistaDeUnidad(x)));
1458
+ return v.replace(/[]\d+/g, (x) => sinColorSimple(this.vistaDeUnidad(x)));
941
1459
  }
942
1460
  ponerValor(v) {
943
- this.unidades = v.match(/[\uE000\uE003]\d+|[\s\S]/gu) ?? [];
1461
+ this.unidades = v.match(/[]\d+|[\s\S]/gu) ?? [];
944
1462
  this.cursor = this.unidades.length;
945
1463
  }
946
1464
  insertar(texto) {
@@ -959,17 +1477,18 @@ export class Superficie {
959
1477
  if (v && this.historial[this.historial.length - 1] !== crudo)
960
1478
  this.historial.push(crudo);
961
1479
  this.posHistorial = this.historial.length;
1480
+ this.irAlFondo();
962
1481
  if (this.trabajo || !this.resolverLectura) {
963
1482
  if (v) {
964
- this.cola = crudo;
965
- this.mostrarAviso(tt('tui.en_cola'));
1483
+ this.cola.push(crudo);
1484
+ this.mostrarAviso(this.cola.length > 1 ? tt('tui.en_cola_n', { n: this.cola.length }) : tt('tui.en_cola'));
966
1485
  }
967
1486
  return;
968
1487
  }
969
1488
  const r = this.resolverLectura;
970
1489
  this.resolverLectura = null;
971
- if (v)
972
- this.anadir(bloqueDeLaPersona(this.vistaDeEnvio(crudo.trim())));
1490
+ if (v && v !== '!')
1491
+ this.anadir(this.bloqueDeEnvio(crudo));
973
1492
  else
974
1493
  this.pintar();
975
1494
  r(this.expandir(crudo));
@@ -986,6 +1505,14 @@ export class Superficie {
986
1505
  this.foco = k.t === 'foco_si';
987
1506
  return;
988
1507
  }
1508
+ if (k.t === 'raton')
1509
+ return this.alRaton(k);
1510
+ if (k.t === 'pagina_arriba' || k.t === 'pagina_abajo')
1511
+ return this.desplazar((k.t === 'pagina_arriba' ? 1 : -1) * Math.max(1, this.filasDeVista() - 2));
1512
+ if (k.t === 'fondo')
1513
+ return this.irAlFondo();
1514
+ if (k.t === 'suspender')
1515
+ return this.suspender();
989
1516
  if (this.pregunta)
990
1517
  return this.teclaDePregunta(k);
991
1518
  if (k.t !== 'ctrlc' && this.ctrlC && !this.trabajo) {
@@ -1007,13 +1534,13 @@ export class Superficie {
1007
1534
  const imagen = rutaDeImagenPegada(v);
1008
1535
  if (imagen) {
1009
1536
  this.imagenes.push(imagen);
1010
- this.unidades.splice(this.cursor, 0, `\uE003${this.imagenes.length - 1}`, ' ');
1537
+ this.unidades.splice(this.cursor, 0, `${this.imagenes.length - 1}`, ' ');
1011
1538
  this.cursor += 2;
1012
1539
  break;
1013
1540
  }
1014
1541
  if (v.includes('\n') || v.length > PEGADO_LARGO) {
1015
1542
  this.pegados.push(v);
1016
- this.unidades.splice(this.cursor, 0, `\uE000${this.pegados.length - 1}`);
1543
+ this.unidades.splice(this.cursor, 0, `${this.pegados.length - 1}`);
1017
1544
  this.cursor++;
1018
1545
  }
1019
1546
  else
@@ -1042,6 +1569,8 @@ export class Superficie {
1042
1569
  this.menuCerradoPara = valor;
1043
1570
  else if (this.trabajo)
1044
1571
  this.trabajo.alParar();
1572
+ else if (this.subidas > 0 || this.volcadas < this.lineas.length)
1573
+ this.irAlFondo();
1045
1574
  break;
1046
1575
  case 'borrar':
1047
1576
  if (this.cursor > 0) {
@@ -1083,6 +1612,8 @@ export class Superficie {
1083
1612
  this.cursor = 0;
1084
1613
  break;
1085
1614
  case 'fin':
1615
+ if (this.cursor >= this.unidades.length && (this.subidas > 0 || this.volcadas < this.lineas.length))
1616
+ this.irAlFondo();
1086
1617
  this.cursor = this.unidades.length;
1087
1618
  break;
1088
1619
  case 'borrar_linea':
@@ -1157,17 +1688,38 @@ export class Superficie {
1157
1688
  break;
1158
1689
  }
1159
1690
  }
1691
+ get guardaDePregunta() {
1692
+ return this.o.guardaDePreguntaMs ?? (this.o.quieta ? 0 : 600);
1693
+ }
1160
1694
  teclaDePregunta(k) {
1161
1695
  const p = this.pregunta;
1162
1696
  const n = p.opciones.length;
1697
+ const ahora = Date.now();
1698
+ if (k.t === 'esc' || k.t === 'ctrlc')
1699
+ return this.responder(p.indiceNo);
1700
+ const r0 = k.t === 'texto' ? k.v.trim().toLowerCase() : '';
1701
+ const esRespuesta = k.t === 'texto' && (/^[1-9]$/.test(r0) || ((r0 === 'y' || r0 === 'n') && p.indiceNo !== null));
1702
+ if (k.t === 'texto' && !esRespuesta) {
1703
+ this.insertar(k.v);
1704
+ p.escribiendo = true;
1705
+ p.sordaHasta = Math.max(p.sordaHasta, ahora + this.guardaDePregunta);
1706
+ return;
1707
+ }
1708
+ if (ahora < p.sordaHasta) {
1709
+ if (k.t === 'texto' && p.escribiendo) {
1710
+ this.insertar(k.v);
1711
+ p.sordaHasta = ahora + this.guardaDePregunta;
1712
+ }
1713
+ return;
1714
+ }
1163
1715
  if (k.t === 'texto') {
1164
1716
  const r = k.v.trim().toLowerCase();
1165
1717
  const num = Number(r);
1166
1718
  if (Number.isInteger(num) && num >= 1 && num <= n)
1167
1719
  return this.responder(num - 1);
1168
- if (r === 'n')
1169
- return this.responder(p.valores.indexOf('no'));
1170
- if (r === 'y' && (p.valores[p.elegida] !== 'no' || p.peligro))
1720
+ if (r === 'n' && p.indiceNo !== null)
1721
+ return this.responder(p.indiceNo);
1722
+ if (r === 'y' && p.siConY)
1171
1723
  return this.responder(0);
1172
1724
  return;
1173
1725
  }
@@ -1177,8 +1729,6 @@ export class Superficie {
1177
1729
  p.elegida = (p.elegida + 1) % n;
1178
1730
  else if (k.t === 'intro')
1179
1731
  this.responder(p.elegida);
1180
- else if (k.t === 'esc' || k.t === 'ctrlc')
1181
- this.responder(p.valores.indexOf('no'));
1182
1732
  }
1183
1733
  get _lineas() {
1184
1734
  return this.lineas;
@@ -1192,6 +1742,9 @@ export class Superficie {
1192
1742
  get _trabajo() {
1193
1743
  return this.trabajo;
1194
1744
  }
1745
+ get _zonas() {
1746
+ return this.zonas;
1747
+ }
1195
1748
  }
1196
1749
  function sinColorSimple(t) {
1197
1750
  return t.replace(/\x1b\[[0-9;]*m/g, '');