@getparallel/parx 0.0.0-stage → 0.4.1

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 ADDED
@@ -0,0 +1,1198 @@
1
+ import { animacion, c, capacidades, sep, cabezaDePar, caraDePar, carril, fijarCapacidades, signo, ANCHO_DE_PAR, SIMBOLOS } from "./marca.js";
2
+ import { anchoDe, envolver, puntos, recortar, rellenar } from "./texto.js";
3
+ import { ALIAS_COMANDO, COMANDOS, t as tt } from "./textos.js";
4
+ import { MODELOS, ORDEN_DE_MODELOS } from "./modelos.js";
5
+ import { consejo, ordenDeConsejos } from "./consejos.js";
6
+ export { anchoDe } from "./texto.js";
7
+ export const MARGEN = 2;
8
+ export function anchoUtil(ancho) {
9
+ return Math.max(10, ancho - MARGEN * 2);
10
+ }
11
+ const m = ' '.repeat(MARGEN);
12
+ function barraDoble() {
13
+ return capacidades().unicode ? '║' : '|';
14
+ }
15
+ export function bloqueDeTexto(texto, sangria = '') {
16
+ return (ancho) => {
17
+ const util = anchoUtil(ancho);
18
+ const out = [];
19
+ for (const linea of texto.split('\n')) {
20
+ const propia = /^\s*/.exec(linea)?.[0] ?? '';
21
+ for (const l of envolver(linea, util, sangria || propia))
22
+ out.push(`${m}${l}`);
23
+ }
24
+ return out;
25
+ };
26
+ }
27
+ export function bloqueDeLinea(linea) {
28
+ return (ancho) => [`${m}${recortar(linea, anchoUtil(ancho))}`];
29
+ }
30
+ export function bloqueDeLaPersona(texto) {
31
+ return (ancho) => {
32
+ const util = anchoUtil(ancho);
33
+ const barra = `${c.violeta(barraDoble())} `;
34
+ const lineas = texto.split('\n').flatMap((l) => envolver(l, util - 2));
35
+ return ['', ...lineas.map((l) => `${m}${barra}${l}`)];
36
+ };
37
+ }
38
+ export function bloqueDeBienvenida(d) {
39
+ return (ancho) => {
40
+ const util = anchoUtil(ancho) - 2;
41
+ const barra = `${c.violeta(barraDoble())} `;
42
+ const filas = [
43
+ `${c.negrita('parx')} ${c.gris(d.version)}${c.gris(sep())}${d.quien ? tt('tui.hola', { nombre: d.quien }) : tt('tui.hola_sin_nombre')}`,
44
+ c.gris(`${d.carpeta}${d.plan ? `${sep()}${d.plan}` : ''}`),
45
+ ...(d.novedad ? [c.gris(d.novedad)] : []),
46
+ ];
47
+ const out = filas.map((f) => `${m}${barra}${recortar(f, util)}`);
48
+ if (d.peligro?.())
49
+ out.push(...avisoDeBypass(ancho));
50
+ return [...out, ''];
51
+ };
52
+ }
53
+ export function avisoDeBypass(ancho) {
54
+ const util = anchoUtil(ancho);
55
+ const senal = c.peligro(capacidades().unicode ? '▲' : '!');
56
+ const lineas = envolver(`${tt('bypass.arriba')}. ${tt('bypass.arriba_desc')}`, util - 2);
57
+ const titulo = `${tt('bypass.arriba')}.`;
58
+ return [
59
+ '',
60
+ ...lineas.map((l, i) => {
61
+ if (i === 0 && l.startsWith(titulo))
62
+ return `${m}${senal} ${c.negrita(c.peligro(titulo))}${c.gris(l.slice(titulo.length))}`;
63
+ return `${m} ${c.gris(l)}`;
64
+ }),
65
+ ];
66
+ }
67
+ export function bloqueDeConsejos(ids) {
68
+ return (ancho) => {
69
+ const lista = ids();
70
+ if (!lista.length)
71
+ return [];
72
+ const u = capacidades().unicode;
73
+ const util = anchoUtil(ancho);
74
+ const barra = c.violeta(u ? '│' : '|');
75
+ const interior = util - 2;
76
+ const consejos = lista.map((id) => consejo(id));
77
+ const out = [];
78
+ const titulo = c.negrita(recortar(tt('consejos.titulo'), interior));
79
+ const quitar = c.tenue(c.gris(tt('consejos.quitar', { orden: ordenDeConsejos(false) })));
80
+ out.push(`${m}${barra} ${izquierdaYDerecha(titulo, quitar, interior).trimEnd()}`);
81
+ const anchoTecla = Math.max(...consejos.map((k) => anchoDe(k.tecla))) + 3;
82
+ const dosColumnas = interior - anchoTecla >= 24;
83
+ for (const k of consejos) {
84
+ if (dosColumnas) {
85
+ const lineas = envolver(k.texto, interior - anchoTecla);
86
+ lineas.forEach((l, i) => {
87
+ const tecla = i === 0 ? c.marca(rellenar(k.tecla, anchoTecla)) : ' '.repeat(anchoTecla);
88
+ out.push(`${m}${barra} ${tecla}${c.gris(l)}`);
89
+ });
90
+ }
91
+ else {
92
+ out.push(`${m}${barra} ${c.marca(recortar(k.tecla, interior))}`);
93
+ for (const l of envolver(k.texto, interior - 2))
94
+ out.push(`${m}${barra} ${c.gris(l)}`);
95
+ }
96
+ }
97
+ const esquina = u ? '╰' : '`';
98
+ const raya = u ? '─' : '-';
99
+ out.push(`${m}${c.violeta(`${esquina}${raya.repeat(Math.max(0, util - 1))}`)}`);
100
+ return out;
101
+ };
102
+ }
103
+ export function bloqueDeCierre(texto, marca) {
104
+ return (ancho) => {
105
+ const util = anchoUtil(ancho);
106
+ const izq = recortar(`${marca} ${texto}`, util);
107
+ const resto = util - anchoDe(izq) - 1;
108
+ return [`${m}${izq}${resto > 0 ? ` ${c.tenue(c.gris((capacidades().unicode ? '─' : '-').repeat(resto)))}` : ''}`];
109
+ };
110
+ }
111
+ export function trocearTeclas(datos, estado) {
112
+ const out = [];
113
+ let i = 0;
114
+ while (i < datos.length) {
115
+ if (estado.pegando) {
116
+ const fin = datos.indexOf('\x1b[201~', i);
117
+ const trozo = fin === -1 ? datos.slice(i) : datos.slice(i, fin);
118
+ if (trozo)
119
+ out.push({ t: 'pegado', v: trozo });
120
+ if (fin === -1)
121
+ return out;
122
+ estado.pegando = false;
123
+ i = fin + 6;
124
+ continue;
125
+ }
126
+ const ch = datos[i];
127
+ if (ch === '\x1b') {
128
+ const r = /^\x1b(?:\[([0-9;?]*)([ -/]*[@-~])|O([@-~])|([\s\S]))?/.exec(datos.slice(i));
129
+ const seq = r[0];
130
+ i += Math.max(1, seq.length);
131
+ const params = r[1] ?? '';
132
+ const fin = r[2] ?? r[3] ?? '';
133
+ if (seq === '\x1b[200~') {
134
+ estado.pegando = true;
135
+ continue;
136
+ }
137
+ const conModificador = /;[35]$/.test(params);
138
+ if (seq === '\x1b')
139
+ out.push({ t: 'esc' });
140
+ else if (fin === 'D')
141
+ out.push({ t: conModificador ? 'palabra_izq' : 'izq' });
142
+ else if (fin === 'C')
143
+ out.push({ t: conModificador ? 'palabra_der' : 'der' });
144
+ else if (fin === 'A')
145
+ out.push({ t: 'arriba' });
146
+ else if (fin === 'B')
147
+ out.push({ t: 'abajo' });
148
+ else if (fin === 'H' || (fin === '~' && (params === '1' || params === '7')))
149
+ out.push({ t: 'inicio' });
150
+ else if (fin === 'F' || (fin === '~' && (params === '4' || params === '8')))
151
+ out.push({ t: 'fin' });
152
+ else if (fin === '~' && params === '3')
153
+ out.push({ t: 'suprimir' });
154
+ else if (fin === 'Z')
155
+ out.push({ t: 'cambiar' });
156
+ else if (fin === 'I' && params === '')
157
+ out.push({ t: 'foco_si' });
158
+ else if (fin === 'O' && params === '' && r[2])
159
+ out.push({ t: 'foco_no' });
160
+ else if (r[4] === '\x7f')
161
+ out.push({ t: 'borrar_palabra' });
162
+ else if (r[4] === 'b')
163
+ out.push({ t: 'palabra_izq' });
164
+ else if (r[4] === 'f')
165
+ out.push({ t: 'palabra_der' });
166
+ else if (r[4] === '\r')
167
+ out.push({ t: 'intro' });
168
+ else
169
+ out.push({ t: 'nada' });
170
+ continue;
171
+ }
172
+ i++;
173
+ if (ch === '\r' || ch === '\n')
174
+ out.push({ t: 'intro' });
175
+ else if (ch === '\x7f' || ch === '\b')
176
+ out.push({ t: 'borrar' });
177
+ else if (ch === '\x03')
178
+ out.push({ t: 'ctrlc' });
179
+ else if (ch === '\x04')
180
+ out.push({ t: 'ctrld' });
181
+ else if (ch === '\x01')
182
+ out.push({ t: 'inicio' });
183
+ else if (ch === '\x05')
184
+ out.push({ t: 'fin' });
185
+ else if (ch === '\x02')
186
+ out.push({ t: 'izq' });
187
+ else if (ch === '\x06')
188
+ out.push({ t: 'der' });
189
+ else if (ch === '\x0b')
190
+ out.push({ t: 'borrar_hasta_fin' });
191
+ else if (ch === '\x15')
192
+ out.push({ t: 'borrar_linea' });
193
+ else if (ch === '\x17')
194
+ out.push({ t: 'borrar_palabra' });
195
+ else if (ch === '\x0c')
196
+ out.push({ t: 'limpiar' });
197
+ else if (ch === '\x10')
198
+ out.push({ t: 'arriba' });
199
+ else if (ch === '\x0e')
200
+ out.push({ t: 'abajo' });
201
+ else if (ch === '\t')
202
+ out.push({ t: 'tab' });
203
+ else if (ch >= ' ') {
204
+ const ultimo = out[out.length - 1];
205
+ if (ultimo && ultimo.t === 'texto')
206
+ ultimo.v += ch;
207
+ else
208
+ out.push({ t: 'texto', v: ch });
209
+ }
210
+ }
211
+ return out;
212
+ }
213
+ const CON_VALOR = new Set(['mode', 'model', 'tips', 'notify', 'teach']);
214
+ const CON_TEXTO = new Set(['folder']);
215
+ const MODOS_DEL_MENU = ['ask', 'auto', 'read', 'bypass'];
216
+ export function opcionesDelMenu(valor) {
217
+ if (!valor.startsWith('/') || valor.includes('\n'))
218
+ return null;
219
+ const sinBarra = valor.slice(1);
220
+ const espacio = sinBarra.indexOf(' ');
221
+ if (espacio === -1) {
222
+ const q = sinBarra.toLocaleLowerCase();
223
+ const out = [];
224
+ for (const [cmd, descripcion] of COMANDOS) {
225
+ const casa = cmd.startsWith(q) || (q.length > 0 && Object.entries(ALIAS_COMANDO).some(([alias, destino]) => destino === cmd && alias.startsWith(q)));
226
+ if (!casa)
227
+ continue;
228
+ const conAlgo = CON_VALOR.has(cmd) || CON_TEXTO.has(cmd);
229
+ out.push({ nombre: `/${cmd}`, descripcion, completar: `/${cmd}${conAlgo ? ' ' : ''}`, enviar: !conAlgo });
230
+ }
231
+ return out;
232
+ }
233
+ const cmd = ALIAS_COMANDO[sinBarra.slice(0, espacio).toLocaleLowerCase()];
234
+ if (!cmd || !CON_VALOR.has(cmd))
235
+ return null;
236
+ const q = sinBarra.slice(espacio + 1).toLocaleLowerCase();
237
+ if (q.includes(' '))
238
+ return null;
239
+ const base = valor.slice(0, valor.length - q.length);
240
+ const valores = cmd === 'mode'
241
+ ? MODOS_DEL_MENU.map((k) => [k, tt(`modo.${k}.desc`)])
242
+ : cmd === 'model'
243
+ ? ORDEN_DE_MODELOS.map((k) => [k, `${MODELOS[k].nombre} ${MODELOS[k].version}: ${MODELOS[k].descripcion}`])
244
+ : [
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'],
247
+ ];
248
+ return valores
249
+ .filter(([v]) => v.startsWith(q))
250
+ .map(([v, d]) => ({ nombre: v, descripcion: d, completar: `${base}${v}`, enviar: true, ...(cmd === 'mode' && v === 'bypass' ? { peligro: true } : {}) }));
251
+ }
252
+ export function componerBarra(e, util) {
253
+ const punto = c.gris(sep());
254
+ const modo = e.peligro ? c.aviso(c.negrita(`${capacidades().unicode ? '▲' : '!'} ${e.modo}`)) : c.gris(e.modo);
255
+ const extra = e.extra ? `${punto}${c.marca(e.extra)}` : '';
256
+ 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],
261
+ ];
262
+ for (const [izq, der] of opciones) {
263
+ const hueco = util - anchoDe(izq) - anchoDe(der);
264
+ if (hueco >= 2)
265
+ return `${izq}${' '.repeat(hueco)}${c.gris(der)}`;
266
+ }
267
+ const der = e.saldoCorto ?? '';
268
+ 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);
270
+ }
271
+ export function duracion(ms) {
272
+ const s = Math.max(0, Math.floor(ms / 1000));
273
+ if (s < 60)
274
+ return `${s}s`;
275
+ const m = Math.floor(s / 60);
276
+ if (m < 60)
277
+ return `${m}m ${String(s % 60).padStart(2, '0')}s`;
278
+ return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, '0')}m`;
279
+ }
280
+ export function acciones(n) {
281
+ return n === 1 ? tt('estado.accion') : tt('estado.acciones', { n: n.toLocaleString('en-US') });
282
+ }
283
+ const FRASES = ['trabajo.ruta', 'trabajo.puntos', 'trabajo.carriles', 'trabajo.numeros', 'trabajo.mapa', 'trabajo.afinar', 'trabajo.agujas', 'trabajo.estacion'];
284
+ export const MS_POR_FRASE = 2800;
285
+ export function fraseDeEspera(ms, semilla = 0) {
286
+ const vuelta = Math.floor(Math.max(0, ms) / MS_POR_FRASE);
287
+ if (vuelta === 0)
288
+ return `${tt(FRASES[0])}…`;
289
+ return `${tt(FRASES[1 + ((semilla + vuelta - 1) % (FRASES.length - 1))])}…`;
290
+ }
291
+ export function ponerTextoDeTrabajo(w, texto, anim = 'ruta', vivo = false, ahora = Date.now()) {
292
+ if (texto) {
293
+ w.texto = texto;
294
+ w.anim = anim;
295
+ w.vivo = vivo;
296
+ w.literalHasta = ahora + 1800;
297
+ }
298
+ else {
299
+ w.vivo = false;
300
+ w.esperaDesde = Math.max(ahora, w.literalHasta);
301
+ }
302
+ }
303
+ export function nuevoTrabajo(ahora = Date.now()) {
304
+ return { texto: null, literalHasta: 0, vivo: false, anim: 'ruta', desde: ahora, esperaDesde: ahora, paso: 0, acciones: null, semilla: Math.floor(Math.random() * 7) };
305
+ }
306
+ export function textoDeTrabajo(w, ahora) {
307
+ if (w.texto && (w.vivo || ahora < w.literalHasta))
308
+ return { texto: w.texto, anim: w.anim };
309
+ return { texto: fraseDeEspera(ahora - w.esperaDesde, w.semilla), anim: 'ruta' };
310
+ }
311
+ export function filaDeTrabajo(w, ahora) {
312
+ const { texto, anim } = textoDeTrabajo(w, ahora);
313
+ const tiempo = duracion(ahora - w.desde);
314
+ const gasto = w.acciones ? `${sep()}${acciones(w.acciones)}` : '';
315
+ return `${animacion(anim, w.paso)} ${texto}${c.gris(`${sep()}${tiempo}${gasto}`)}`;
316
+ }
317
+ export function filaDeCarril(k, util, ahora) {
318
+ const fin = k.hasta ?? ahora;
319
+ const numero = c.claro(String(k.numero));
320
+ const tiempo = duracion(fin - k.trabajo.desde);
321
+ const derecha = c.gris(`${tiempo}${k.trabajo.acciones ? `${sep()}${acciones(k.trabajo.acciones)}` : ''}`);
322
+ let señal;
323
+ let texto;
324
+ if (k.estado === 'trabaja') {
325
+ const x = textoDeTrabajo(k.trabajo, ahora);
326
+ señal = animacion(x.anim, k.trabajo.paso + k.numero * 3);
327
+ texto = x.texto;
328
+ }
329
+ else {
330
+ const marca = k.estado === 'hecho' ? c.ok(SIMBOLOS.hecho) : k.estado === 'error' ? c.error(SIMBOLOS.falla) : c.gris('■');
331
+ señal = rellenar(marca, 8);
332
+ texto = k.estado === 'hecho' ? tt('estado.hecho') : k.estado === 'error' ? 'Stopped by an error' : tt('estado.interrumpido');
333
+ }
334
+ const anchoTarea = Math.max(8, Math.floor(util * 0.32));
335
+ 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);
338
+ }
339
+ function izquierdaYDerecha(izq, der, util) {
340
+ if (anchoDe(izq) + anchoDe(der) + 2 <= util)
341
+ return `${izq}${' '.repeat(util - anchoDe(izq) - anchoDe(der))}${der}`;
342
+ return rellenar(izq, util);
343
+ }
344
+ export function ventanaDeEntrada(unidades, cursor, ancho) {
345
+ const w = unidades.map(anchoDe);
346
+ const suma = (d, h) => w.slice(d, h).reduce((x, y) => x + y, 0);
347
+ if (suma(0, w.length) < ancho)
348
+ return { texto: unidades.join(''), col: suma(0, cursor) };
349
+ let desde = 0;
350
+ while (desde < cursor && (desde > 0 ? 1 : 0) + suma(desde, cursor) > ancho - 2)
351
+ desde++;
352
+ let texto = desde > 0 ? puntos() : '';
353
+ let usado = desde > 0 ? 1 : 0;
354
+ let i = desde;
355
+ for (; i < unidades.length; i++) {
356
+ const limite = i < unidades.length - 1 ? ancho - 1 : ancho;
357
+ if (usado + w[i] > limite)
358
+ break;
359
+ texto += unidades[i];
360
+ usado += w[i];
361
+ }
362
+ if (i < unidades.length)
363
+ texto += puntos();
364
+ const col = (desde > 0 ? 1 : 0) + suma(desde, cursor);
365
+ return { texto: recortar(texto, ancho), col: Math.max(0, Math.min(col, ancho - 1)) };
366
+ }
367
+ export function cabePar(ancho) {
368
+ return ancho >= 56;
369
+ }
370
+ export function componerCarril(d, ancho, alto) {
371
+ const util = anchoUtil(ancho);
372
+ const conPar = cabePar(ancho);
373
+ const huecoPar = conPar ? ANCHO_DE_PAR + 2 : 0;
374
+ const signoW = 3;
375
+ let filaEscribir;
376
+ let cursor = null;
377
+ 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 {
392
+ 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);
396
+ cursor = { fila: 0, col: MARGEN + signoW };
397
+ }
398
+ else {
399
+ const v = ventanaDeEntrada(d.unidades, d.cursor, libre);
400
+ filaEscribir = rellenar(`${color(signo())} ${v.texto}`, anchoEscribir);
401
+ cursor = { fila: 0, col: MARGEN + signoW + v.col };
402
+ }
403
+ }
404
+ if (conPar)
405
+ filaEscribir += ` ${cabezaDePar()} `;
406
+ const tinta = (t) => c.violeta(t);
407
+ const linea = conPar ? `${tinta(carril(util - ANCHO_DE_PAR - 2))}${caraDePar(d.gesto)}${tinta(carril(2))}` : tinta(carril(util));
408
+ let barra;
409
+ const enCarriles = (d.carriles ?? []).filter((k) => k.estado === 'trabaja').length;
410
+ if (d.trabajo && !d.pregunta && d.carriles?.length) {
411
+ 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);
414
+ }
415
+ 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);
417
+ }
418
+ else if (d.aviso) {
419
+ barra = izquierdaYDerecha(c.aviso(recortar(d.aviso, util)), '', util);
420
+ }
421
+ else {
422
+ barra = componerBarra(d.estado, util);
423
+ }
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));
427
+ 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) {
434
+ 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)}`];
439
+ }
440
+ else if (d.menu && d.menu.opciones.length) {
441
+ const ops = d.menu.opciones;
442
+ const visibles = Math.min(6, ops.length, hueco);
443
+ if (visibles > 0) {
444
+ const ini = Math.max(0, Math.min(d.menu.elegida - visibles + 1, ops.length - visibles));
445
+ const anchoNombre = Math.min(Math.max(...ops.map((o) => anchoDe(o.nombre) + (o.peligro ? 2 : 0))) + 2, Math.floor(util / 2));
446
+ encima = ops.slice(ini, ini + visibles).map((o, k) => {
447
+ const i = ini + k;
448
+ const sel = i === d.menu.elegida;
449
+ const marca = sel ? (o.peligro ? c.peligro : c.claro)(capacidades().unicode ? '▌' : '>') : ' ';
450
+ const resto = Math.max(0, util - 2 - anchoNombre);
451
+ const desc = recortar(o.descripcion, resto);
452
+ 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)}`;
455
+ }
456
+ 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)}`;
458
+ });
459
+ }
460
+ }
461
+ encima = encima.slice(Math.max(0, encima.length - hueco));
462
+ return { encima, carril: filas, cursor: cursor && filas.length ? cursor : null };
463
+ }
464
+ export function opcionesDePregunta(tipo) {
465
+ switch (tipo) {
466
+ case 'carpeta':
467
+ return { opciones: [tt('tui.si'), tt('tui.si_carpeta'), tt('tui.no')], cortas: [tt('tui.si'), tt('tui.si_carpeta_corto'), tt('tui.no')], valores: ['si', 'siempre', 'no'], elegida: 0 };
468
+ case 'ediciones':
469
+ return { opciones: [tt('tui.si'), tt('tui.si_ediciones'), tt('tui.no')], cortas: [tt('tui.si'), tt('tui.si_ediciones_corto'), tt('tui.no')], valores: ['si', 'siempre', 'no'], elegida: 0 };
470
+ case 'dos':
471
+ return { opciones: [tt('tui.si'), tt('tui.no')], cortas: [tt('tui.si'), tt('tui.no')], valores: ['si', 'no'], elegida: 0 };
472
+ case 'credencial':
473
+ return { opciones: [tt('tui.una_vez'), tt('tui.no')], cortas: [tt('tui.si'), tt('tui.no')], valores: ['si', 'no'], elegida: 1 };
474
+ case 'bypass':
475
+ return { opciones: [tt('bypass.aceptar'), tt('bypass.rechazar')], cortas: [tt('bypass.aceptar'), tt('bypass.rechazar')], valores: ['si', 'no'], elegida: 1 };
476
+ default:
477
+ return { opciones: [tt('tui.si'), tt('tui.siempre'), tt('tui.no')], cortas: [tt('tui.si'), tt('tui.siempre_corto'), tt('tui.no')], valores: ['si', 'siempre', 'no'], elegida: 0 };
478
+ }
479
+ }
480
+ const MAX_LINEAS = 4000;
481
+ const MAX_BLOQUES = 1500;
482
+ const PEGADO_LARGO = 280;
483
+ const FICHA = /^(\d+)$/;
484
+ const FICHA_IMAGEN = /^(\d+)$/;
485
+ const IMAGEN = /\.(png|jpe?g|gif|webp)$/i;
486
+ export function rutaDeImagenPegada(texto) {
487
+ let v = texto.trim();
488
+ if (!v || v.includes('\n'))
489
+ return null;
490
+ if ((v.startsWith("'") && v.endsWith("'")) || (v.startsWith('"') && v.endsWith('"')))
491
+ v = v.slice(1, -1);
492
+ else
493
+ v = v.replace(/\\(.)/g, '$1');
494
+ if (v.startsWith('file://'))
495
+ v = decodeURIComponent(v.slice(7));
496
+ return IMAGEN.test(v) && /^(\/|~|[A-Za-z]:\\|\.)/.test(v) ? v : null;
497
+ }
498
+ export class Superficie {
499
+ o;
500
+ bloques = [];
501
+ lineas = [];
502
+ anchoCompuesto = 0;
503
+ encimaPintadas = 0;
504
+ carrilPintadas = 0;
505
+ activa = false;
506
+ oyente = null;
507
+ alRedimensionar = null;
508
+ relojResize = null;
509
+ relojAnim = null;
510
+ relojAviso = null;
511
+ pegando = { pegando: false };
512
+ unidades = [];
513
+ cursor = 0;
514
+ pegados = [];
515
+ imagenes = [];
516
+ historial = [];
517
+ posHistorial = 0;
518
+ borrador = [];
519
+ menuCerradoPara = null;
520
+ menuElegida = 0;
521
+ cola = null;
522
+ ctrlC = 0;
523
+ ejemplo = null;
524
+ resolverLectura = null;
525
+ trabajo = null;
526
+ carriles = [];
527
+ pregunta = null;
528
+ esperando = [];
529
+ aviso = null;
530
+ gesto = 'quieto';
531
+ ultimoParpadeo = Date.now();
532
+ consejos = [];
533
+ consejosVisibles = false;
534
+ foco = null;
535
+ peligroPintado = false;
536
+ constructor(o) {
537
+ this.o = o;
538
+ }
539
+ get ancho() {
540
+ return Math.max(20, this.o.salida.columns || 80);
541
+ }
542
+ get alto() {
543
+ return Math.max(1, this.o.salida.rows || 24);
544
+ }
545
+ get enfocada() {
546
+ return this.foco;
547
+ }
548
+ escribir(s) {
549
+ this.o.salida.write(s);
550
+ }
551
+ datos() {
552
+ const valor = this.valorCrudo();
553
+ const ops = this.menuCerradoPara === valor ? null : opcionesDelMenu(valor);
554
+ if (ops && this.menuElegida >= ops.length)
555
+ this.menuElegida = Math.max(0, ops.length - 1);
556
+ return {
557
+ unidades: this.unidades.map((u) => this.vistaDeUnidad(u)),
558
+ cursor: this.cursor,
559
+ ejemplo: this.ejemplo ?? tt('tui.escribe'),
560
+ estado: this.o.estado(),
561
+ trabajo: this.trabajo,
562
+ aviso: this.aviso,
563
+ pregunta: this.pregunta,
564
+ menu: ops && ops.length ? { opciones: ops, elegida: this.menuElegida } : null,
565
+ carriles: this.carriles,
566
+ 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
+ ahora: Date.now(),
568
+ };
569
+ }
570
+ vistaDeUnidad(u) {
571
+ const img = FICHA_IMAGEN.exec(u);
572
+ if (img) {
573
+ const ruta = this.imagenes[Number(img[1])] ?? '';
574
+ return c.marca(`[${tt('tui.imagen', { nombre: ruta.split(/[\\/]/).pop() ?? ruta })}]`);
575
+ }
576
+ const f = FICHA.exec(u);
577
+ if (!f)
578
+ return u === '\n' ? c.gris(capacidades().unicode ? '⏎' : '|') : u;
579
+ const p = this.pegados[Number(f[1])] ?? '';
580
+ const n = p.split('\n').length;
581
+ return c.marca(`[${n > 1 ? tt('tui.pegado', { n }) : tt('tui.pegado_corto', { n: p.length.toLocaleString('en-US') })}]`);
582
+ }
583
+ filaDelCarril(n) {
584
+ return this.alto - n + 1;
585
+ }
586
+ desplazadas(altoCarril) {
587
+ return Math.max(0, this.lineas.length - (this.alto - altoCarril));
588
+ }
589
+ filaTrasLaConversacion(altoCarril) {
590
+ return Math.min(this.lineas.length, Math.max(0, this.alto - altoCarril)) + 1;
591
+ }
592
+ lineaDeFila(fila, altoCarril) {
593
+ const indice = this.desplazadas(altoCarril) + (fila - 1);
594
+ return indice >= 0 ? (this.lineas[indice] ?? '') : '';
595
+ }
596
+ pintar() {
597
+ if (!this.activa)
598
+ return;
599
+ const peligro = Boolean(this.o.estado().peligro);
600
+ if (peligro !== this.peligroPintado) {
601
+ this.peligroPintado = peligro;
602
+ this.recomponer();
603
+ if (peligro && this.desplazadas(this.carrilPintadas) > 0)
604
+ this.anadir((ancho) => avisoDeBypass(ancho));
605
+ return;
606
+ }
607
+ const k = componerCarril(this.datos(), this.ancho, this.alto);
608
+ const n = k.carril.length;
609
+ 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)}`;
616
+ }
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)}`;
622
+ }
623
+ this.carrilPintadas = n;
624
+ const top = this.filaDelCarril(n);
625
+ for (let i = k.encima.length; i < this.encimaPintadas; i++) {
626
+ const fila = top - 1 - i;
627
+ if (fila >= 1)
628
+ s += `\x1b[${fila};1H\x1b[2K${this.lineaDeFila(fila, n)}`;
629
+ }
630
+ k.encima.forEach((l, i) => {
631
+ const fila = top - k.encima.length + i;
632
+ if (fila >= 1)
633
+ s += `\x1b[${fila};1H\x1b[2K${l}`;
634
+ });
635
+ this.encimaPintadas = k.encima.length;
636
+ k.carril.forEach((l, i) => {
637
+ s += `\x1b[${top + i};1H\x1b[2K${l}`;
638
+ });
639
+ if (k.cursor)
640
+ s += `\x1b[${top + k.cursor.fila};${k.cursor.col + 1}H\x1b[?25h`;
641
+ s += '\x1b[?2026l';
642
+ this.escribir(s);
643
+ }
644
+ destapar() {
645
+ const n = this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length;
646
+ const top = this.filaDelCarril(n);
647
+ let s = '';
648
+ for (let i = 0; i < this.encimaPintadas; i++) {
649
+ const fila = top - 1 - i;
650
+ if (fila >= 1)
651
+ s += `\x1b[${fila};1H\x1b[2K${this.lineaDeFila(fila, n)}`;
652
+ }
653
+ this.encimaPintadas = 0;
654
+ return s;
655
+ }
656
+ ocultarConsejos() {
657
+ if (!this.consejosVisibles)
658
+ return;
659
+ this.consejosVisibles = false;
660
+ if (!this.activa)
661
+ 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);
665
+ const n = componerCarril(this.datos(), this.ancho, this.alto).carril.length;
666
+ const top = this.filaDelCarril(n);
667
+ let s = '\x1b[?2026h\x1b[?25l';
668
+ for (let fila = 1; fila < top; fila++)
669
+ s += `\x1b[${fila};1H\x1b[2K${this.lineaDeFila(fila, n)}`;
670
+ s += '\x1b[?2026l';
671
+ this.encimaPintadas = 0;
672
+ this.escribir(s);
673
+ this.pintar();
674
+ }
675
+ anadir(b) {
676
+ 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
+ this.bloques.push(b);
681
+ if (this.bloques.length > MAX_BLOQUES)
682
+ this.bloques.splice(0, this.bloques.length - MAX_BLOQUES);
683
+ const nuevas = b(this.ancho);
684
+ 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)
688
+ return;
689
+ let s = `\x1b[?2026h\x1b[?25l${destapado}`;
690
+ s += `\x1b[${desde};1H\x1b[J`;
691
+ s += nuevas.map((l) => `${l}\x1b[K\r\n`).join('');
692
+ s += '\r\n'.repeat(Math.max(0, n - 1));
693
+ s += '\x1b[?2026l';
694
+ this.escribir(s);
695
+ this.pintar();
696
+ }
697
+ ultimoBloque() {
698
+ return this.bloques[this.bloques.length - 1];
699
+ }
700
+ reemplazarUltimo(b) {
701
+ const viejo = this.ultimoBloque();
702
+ const k = viejo ? viejo(this.ancho).length : -1;
703
+ const nuevas = b(this.ancho);
704
+ if (!viejo || !this.activa || nuevas.length !== k || this.encimaPintadas > 0 || this.lineas.length < k) {
705
+ this.anadir(b);
706
+ return;
707
+ }
708
+ this.bloques[this.bloques.length - 1] = b;
709
+ this.lineas.splice(this.lineas.length - k, k, ...nuevas);
710
+ const n = this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length;
711
+ const primera = this.lineas.length - k - this.desplazadas(n) + 1;
712
+ let s = '\x1b[?2026h\x1b[?25l';
713
+ nuevas.forEach((l, i) => {
714
+ const fila = primera + i;
715
+ if (fila >= 1)
716
+ s += `\x1b[${fila};1H\x1b[2K${l}`;
717
+ });
718
+ s += '\x1b[?2026l';
719
+ this.escribir(s);
720
+ this.pintar();
721
+ }
722
+ recomponer(alEmpezar = false) {
723
+ if (!this.activa)
724
+ return;
725
+ fijarCapacidades({ ancho: this.ancho });
726
+ 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);
730
+ const n = componerCarril(this.datos(), this.ancho, this.alto).carril.length;
731
+ 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
+ s += this.lineas.map((l) => `${l}\x1b[K\r\n`).join('');
733
+ s += '\r\n'.repeat(Math.max(0, n - 1));
734
+ s += '\x1b[?2026l';
735
+ this.encimaPintadas = 0;
736
+ this.carrilPintadas = n;
737
+ this.escribir(s);
738
+ this.pintar();
739
+ }
740
+ iniciar(bienvenida, consejos = []) {
741
+ if (this.activa)
742
+ return;
743
+ this.activa = true;
744
+ if (bienvenida)
745
+ this.bloques.push(bienvenida);
746
+ if (consejos.length) {
747
+ const ocupado = (bienvenida ? bienvenida(this.ancho).length : 0) + 3;
748
+ let n = consejos.length;
749
+ while (n > 0 && ocupado + bloqueDeConsejos(() => consejos.slice(0, n))(this.ancho).length > this.alto)
750
+ n--;
751
+ this.consejos = consejos.slice(0, n);
752
+ this.consejosVisibles = n > 0;
753
+ if (n > 0)
754
+ this.bloques.push(bloqueDeConsejos(() => (this.consejosVisibles ? this.consejos : [])));
755
+ }
756
+ this.escribir('\x1b[?2004h\x1b[?1004h');
757
+ try {
758
+ this.o.entrada.setRawMode?.(true);
759
+ }
760
+ catch {
761
+ }
762
+ this.o.entrada.resume();
763
+ this.oyente = (d) => this.alTeclear(String(d));
764
+ this.o.entrada.on('data', this.oyente);
765
+ this.peligroPintado = Boolean(this.o.estado().peligro);
766
+ this.alRedimensionar = () => {
767
+ if (this.relojResize)
768
+ clearTimeout(this.relojResize);
769
+ this.relojResize = setTimeout(() => this.recomponer(), 40);
770
+ };
771
+ this.o.salida.on('resize', this.alRedimensionar);
772
+ this.recomponer(true);
773
+ if (!this.o.quieta) {
774
+ this.relojAnim = setInterval(() => this.latido(), 110);
775
+ this.relojAnim.unref?.();
776
+ }
777
+ }
778
+ latido() {
779
+ const enCarriles = this.carriles.filter((k) => k.estado === 'trabaja');
780
+ if (this.trabajo || enCarriles.length) {
781
+ if (this.trabajo)
782
+ this.trabajo.paso++;
783
+ for (const k of enCarriles)
784
+ k.trabajo.paso++;
785
+ this.pintar();
786
+ return;
787
+ }
788
+ const ahora = Date.now();
789
+ if (this.gesto === 'parpadeo' && ahora - this.ultimoParpadeo > 160) {
790
+ this.gesto = 'quieto';
791
+ this.pintar();
792
+ }
793
+ else if (this.gesto === 'quieto' && !this.pregunta && ahora - this.ultimoParpadeo > 5200) {
794
+ this.ultimoParpadeo = ahora;
795
+ this.gesto = 'parpadeo';
796
+ this.pintar();
797
+ }
798
+ }
799
+ terminar(despedida) {
800
+ if (!this.activa)
801
+ return;
802
+ const n = this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length;
803
+ let s = this.destapar();
804
+ s += `\x1b[${this.filaTrasLaConversacion(n)};1H\x1b[J`;
805
+ if (despedida)
806
+ s += `${m}${despedida}\r\n`;
807
+ s += '\x1b[?2004l\x1b[?1004l\x1b[?25h';
808
+ this.escribir(s);
809
+ this.activa = false;
810
+ if (this.relojAnim)
811
+ clearInterval(this.relojAnim);
812
+ if (this.relojResize)
813
+ clearTimeout(this.relojResize);
814
+ if (this.relojAviso)
815
+ clearTimeout(this.relojAviso);
816
+ if (this.oyente)
817
+ this.o.entrada.removeListener('data', this.oyente);
818
+ if (this.alRedimensionar)
819
+ this.o.salida.removeListener('resize', this.alRedimensionar);
820
+ try {
821
+ this.o.entrada.setRawMode?.(false);
822
+ }
823
+ catch {
824
+ }
825
+ this.o.entrada.pause();
826
+ }
827
+ restaurarDeUrgencia() {
828
+ if (!this.activa)
829
+ return;
830
+ this.o.salida.write('\x1b[?2004l\x1b[?1004l\x1b[?25h\x1b[?2026l');
831
+ try {
832
+ this.o.entrada.setRawMode?.(false);
833
+ }
834
+ catch {
835
+ }
836
+ }
837
+ leer(ejemplo = null) {
838
+ this.ejemplo = ejemplo;
839
+ if (this.cola !== null) {
840
+ const r = this.cola;
841
+ this.cola = null;
842
+ this.ejemplo = null;
843
+ this.mostrarAviso(null);
844
+ this.anadir(bloqueDeLaPersona(this.vistaDeEnvio(r)));
845
+ return Promise.resolve(this.expandir(r));
846
+ }
847
+ return new Promise((resolver) => {
848
+ this.resolverLectura = (r) => {
849
+ this.ejemplo = null;
850
+ resolver(r);
851
+ };
852
+ this.pintar();
853
+ });
854
+ }
855
+ trabajar(alParar) {
856
+ if (this.trabajo)
857
+ this.trabajo.alParar = alParar;
858
+ else
859
+ this.trabajo = { ...nuevoTrabajo(), alParar };
860
+ this.pintar();
861
+ }
862
+ textoDeTrabajo(texto, anim = 'ruta', vivo = false) {
863
+ if (!this.trabajo)
864
+ return;
865
+ ponerTextoDeTrabajo(this.trabajo, texto, anim, vivo);
866
+ this.pintar();
867
+ }
868
+ progreso(accionesDeLaTarea) {
869
+ if (!this.trabajo)
870
+ return;
871
+ this.trabajo.acciones = accionesDeLaTarea;
872
+ this.pintar();
873
+ }
874
+ descansar() {
875
+ this.trabajo = null;
876
+ if (this.aviso === tt('tui.parar'))
877
+ this.aviso = null;
878
+ this.pintar();
879
+ }
880
+ fijarCarriles(carriles) {
881
+ this.carriles = carriles;
882
+ this.pintar();
883
+ }
884
+ actualizarCarriles() {
885
+ this.pintar();
886
+ }
887
+ preguntar(texto, tipo = 'normal') {
888
+ 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();
899
+ });
900
+ }
901
+ responder(i) {
902
+ const p = this.pregunta;
903
+ if (!p)
904
+ return;
905
+ const r = p.valores[i] ?? 'no';
906
+ 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);
913
+ this.esperando.shift()?.();
914
+ }
915
+ mostrarAviso(texto, ms = 0) {
916
+ this.aviso = texto;
917
+ if (this.relojAviso)
918
+ clearTimeout(this.relojAviso);
919
+ if (texto && ms) {
920
+ this.relojAviso = setTimeout(() => {
921
+ if (this.aviso === texto) {
922
+ this.aviso = null;
923
+ this.ctrlC = 0;
924
+ this.pintar();
925
+ }
926
+ }, ms);
927
+ this.relojAviso.unref?.();
928
+ }
929
+ this.pintar();
930
+ }
931
+ valorCrudo() {
932
+ return this.unidades.join('');
933
+ }
934
+ expandir(v) {
935
+ 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`);
938
+ }
939
+ vistaDeEnvio(v) {
940
+ return v.replace(/[\uE000\uE003]\d+/g, (x) => sinColorSimple(this.vistaDeUnidad(x)));
941
+ }
942
+ ponerValor(v) {
943
+ this.unidades = v.match(/[\uE000\uE003]\d+|[\s\S]/gu) ?? [];
944
+ this.cursor = this.unidades.length;
945
+ }
946
+ insertar(texto) {
947
+ const nuevas = Array.from(texto);
948
+ this.unidades.splice(this.cursor, 0, ...nuevas);
949
+ this.cursor += nuevas.length;
950
+ }
951
+ enviar() {
952
+ const crudo = this.valorCrudo();
953
+ const v = crudo.trim();
954
+ this.unidades = [];
955
+ this.cursor = 0;
956
+ this.menuCerradoPara = null;
957
+ this.menuElegida = 0;
958
+ this.borrador = [];
959
+ if (v && this.historial[this.historial.length - 1] !== crudo)
960
+ this.historial.push(crudo);
961
+ this.posHistorial = this.historial.length;
962
+ if (this.trabajo || !this.resolverLectura) {
963
+ if (v) {
964
+ this.cola = crudo;
965
+ this.mostrarAviso(tt('tui.en_cola'));
966
+ }
967
+ return;
968
+ }
969
+ const r = this.resolverLectura;
970
+ this.resolverLectura = null;
971
+ if (v)
972
+ this.anadir(bloqueDeLaPersona(this.vistaDeEnvio(crudo.trim())));
973
+ else
974
+ this.pintar();
975
+ r(this.expandir(crudo));
976
+ }
977
+ alTeclear(datos) {
978
+ for (const k of trocearTeclas(datos, this.pegando))
979
+ this.tecla(k);
980
+ if (this.consejosVisibles && this.unidades.length)
981
+ this.ocultarConsejos();
982
+ this.pintar();
983
+ }
984
+ tecla(k) {
985
+ if (k.t === 'foco_si' || k.t === 'foco_no') {
986
+ this.foco = k.t === 'foco_si';
987
+ return;
988
+ }
989
+ if (this.pregunta)
990
+ return this.teclaDePregunta(k);
991
+ if (k.t !== 'ctrlc' && this.ctrlC && !this.trabajo) {
992
+ this.ctrlC = 0;
993
+ if (this.aviso === tt('tui.salir_otra'))
994
+ this.aviso = null;
995
+ }
996
+ const valor = this.valorCrudo();
997
+ const ops = this.menuCerradoPara === valor ? null : opcionesDelMenu(valor);
998
+ const menu = ops && ops.length ? ops : null;
999
+ switch (k.t) {
1000
+ case 'texto':
1001
+ this.insertar(k.v);
1002
+ this.menuElegida = 0;
1003
+ this.menuCerradoPara = null;
1004
+ break;
1005
+ case 'pegado': {
1006
+ const v = k.v.replace(/\r\n?/g, '\n');
1007
+ const imagen = rutaDeImagenPegada(v);
1008
+ if (imagen) {
1009
+ this.imagenes.push(imagen);
1010
+ this.unidades.splice(this.cursor, 0, `\uE003${this.imagenes.length - 1}`, ' ');
1011
+ this.cursor += 2;
1012
+ break;
1013
+ }
1014
+ if (v.includes('\n') || v.length > PEGADO_LARGO) {
1015
+ this.pegados.push(v);
1016
+ this.unidades.splice(this.cursor, 0, `\uE000${this.pegados.length - 1}`);
1017
+ this.cursor++;
1018
+ }
1019
+ else
1020
+ this.insertar(v);
1021
+ break;
1022
+ }
1023
+ case 'intro':
1024
+ if (menu) {
1025
+ const o = menu[this.menuElegida];
1026
+ this.ponerValor(o.completar);
1027
+ if (!o.enviar) {
1028
+ this.menuElegida = 0;
1029
+ break;
1030
+ }
1031
+ }
1032
+ this.enviar();
1033
+ break;
1034
+ case 'tab':
1035
+ if (menu) {
1036
+ this.ponerValor(menu[this.menuElegida].completar);
1037
+ this.menuElegida = 0;
1038
+ }
1039
+ break;
1040
+ case 'esc':
1041
+ if (menu)
1042
+ this.menuCerradoPara = valor;
1043
+ else if (this.trabajo)
1044
+ this.trabajo.alParar();
1045
+ break;
1046
+ case 'borrar':
1047
+ if (this.cursor > 0) {
1048
+ this.unidades.splice(this.cursor - 1, 1);
1049
+ this.cursor--;
1050
+ this.menuElegida = 0;
1051
+ this.menuCerradoPara = null;
1052
+ }
1053
+ break;
1054
+ case 'suprimir':
1055
+ if (this.cursor < this.unidades.length)
1056
+ this.unidades.splice(this.cursor, 1);
1057
+ break;
1058
+ case 'izq':
1059
+ this.cursor = Math.max(0, this.cursor - 1);
1060
+ break;
1061
+ case 'der':
1062
+ this.cursor = Math.min(this.unidades.length, this.cursor + 1);
1063
+ break;
1064
+ case 'palabra_izq': {
1065
+ let i = this.cursor;
1066
+ while (i > 0 && this.unidades[i - 1] === ' ')
1067
+ i--;
1068
+ while (i > 0 && this.unidades[i - 1] !== ' ')
1069
+ i--;
1070
+ this.cursor = i;
1071
+ break;
1072
+ }
1073
+ case 'palabra_der': {
1074
+ let i = this.cursor;
1075
+ while (i < this.unidades.length && this.unidades[i] === ' ')
1076
+ i++;
1077
+ while (i < this.unidades.length && this.unidades[i] !== ' ')
1078
+ i++;
1079
+ this.cursor = i;
1080
+ break;
1081
+ }
1082
+ case 'inicio':
1083
+ this.cursor = 0;
1084
+ break;
1085
+ case 'fin':
1086
+ this.cursor = this.unidades.length;
1087
+ break;
1088
+ case 'borrar_linea':
1089
+ this.unidades.splice(0, this.cursor);
1090
+ this.cursor = 0;
1091
+ break;
1092
+ case 'borrar_hasta_fin':
1093
+ this.unidades.splice(this.cursor);
1094
+ break;
1095
+ case 'borrar_palabra': {
1096
+ let i = this.cursor;
1097
+ while (i > 0 && this.unidades[i - 1] === ' ')
1098
+ i--;
1099
+ while (i > 0 && this.unidades[i - 1] !== ' ')
1100
+ i--;
1101
+ this.unidades.splice(i, this.cursor - i);
1102
+ this.cursor = i;
1103
+ break;
1104
+ }
1105
+ case 'arriba':
1106
+ case 'abajo': {
1107
+ if (menu) {
1108
+ const n = menu.length;
1109
+ this.menuElegida = (this.menuElegida + (k.t === 'arriba' ? -1 : 1) + n) % n;
1110
+ break;
1111
+ }
1112
+ if (!this.historial.length)
1113
+ break;
1114
+ if (this.posHistorial === this.historial.length)
1115
+ this.borrador = [...this.unidades];
1116
+ this.posHistorial = Math.max(0, Math.min(this.historial.length, this.posHistorial + (k.t === 'arriba' ? -1 : 1)));
1117
+ if (this.posHistorial === this.historial.length) {
1118
+ this.unidades = [...this.borrador];
1119
+ this.cursor = this.unidades.length;
1120
+ }
1121
+ else
1122
+ this.ponerValor(this.historial[this.posHistorial]);
1123
+ this.menuCerradoPara = this.valorCrudo();
1124
+ break;
1125
+ }
1126
+ case 'cambiar':
1127
+ this.o.alCambiarModo?.();
1128
+ break;
1129
+ case 'ctrlc':
1130
+ if (this.trabajo) {
1131
+ this.trabajo.alParar();
1132
+ break;
1133
+ }
1134
+ if (this.unidades.length) {
1135
+ this.unidades = [];
1136
+ this.cursor = 0;
1137
+ break;
1138
+ }
1139
+ this.ctrlC++;
1140
+ if (this.ctrlC >= 2) {
1141
+ this.o.alSalir?.();
1142
+ break;
1143
+ }
1144
+ this.mostrarAviso(tt('tui.salir_otra'), 2000);
1145
+ break;
1146
+ case 'ctrld':
1147
+ if (!this.unidades.length && this.resolverLectura && !this.trabajo) {
1148
+ const r = this.resolverLectura;
1149
+ this.resolverLectura = null;
1150
+ r(null);
1151
+ }
1152
+ break;
1153
+ case 'limpiar':
1154
+ this.recomponer();
1155
+ break;
1156
+ default:
1157
+ break;
1158
+ }
1159
+ }
1160
+ teclaDePregunta(k) {
1161
+ const p = this.pregunta;
1162
+ const n = p.opciones.length;
1163
+ if (k.t === 'texto') {
1164
+ const r = k.v.trim().toLowerCase();
1165
+ const num = Number(r);
1166
+ if (Number.isInteger(num) && num >= 1 && num <= n)
1167
+ 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))
1171
+ return this.responder(0);
1172
+ return;
1173
+ }
1174
+ if (k.t === 'izq' || k.t === 'arriba')
1175
+ p.elegida = (p.elegida + n - 1) % n;
1176
+ else if (k.t === 'der' || k.t === 'abajo' || k.t === 'tab')
1177
+ p.elegida = (p.elegida + 1) % n;
1178
+ else if (k.t === 'intro')
1179
+ this.responder(p.elegida);
1180
+ else if (k.t === 'esc' || k.t === 'ctrlc')
1181
+ this.responder(p.valores.indexOf('no'));
1182
+ }
1183
+ get _lineas() {
1184
+ return this.lineas;
1185
+ }
1186
+ get _consejosVisibles() {
1187
+ return this.consejosVisibles;
1188
+ }
1189
+ get _anchoCompuesto() {
1190
+ return this.anchoCompuesto;
1191
+ }
1192
+ get _trabajo() {
1193
+ return this.trabajo;
1194
+ }
1195
+ }
1196
+ function sinColorSimple(t) {
1197
+ return t.replace(/\x1b\[[0-9;]*m/g, '');
1198
+ }