xgen-dex-cli 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DexError,
|
|
3
|
+
dataDirectory,
|
|
3
4
|
publicError
|
|
4
5
|
} from "./chunk-ULMEXJOR.js";
|
|
5
6
|
|
|
6
7
|
// src/tui/index.tsx
|
|
7
8
|
import { render } from "ink";
|
|
8
|
-
import { stdout } from "node:process";
|
|
9
|
+
import { stdin, stdout } from "node:process";
|
|
9
10
|
|
|
10
11
|
// src/tui/app.tsx
|
|
11
12
|
import { useCallback, useEffect as useEffect8, useState as useState11 } from "react";
|
|
@@ -287,7 +288,7 @@ import { Box as Box2, Text as Text2 } from "ink";
|
|
|
287
288
|
|
|
288
289
|
// src/tui/ime-text-input.tsx
|
|
289
290
|
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
290
|
-
import { Box, Text, useCursor, useInput } from "ink";
|
|
291
|
+
import { Box, Text, useCursor, useInput, useStdin } from "ink";
|
|
291
292
|
import stringWidth2 from "string-width";
|
|
292
293
|
|
|
293
294
|
// src/tui/terminal-input.ts
|
|
@@ -304,6 +305,257 @@ function classifyInput(input, pasting = false) {
|
|
|
304
305
|
return text ? { kind: "text", text } : { kind: "ignore" };
|
|
305
306
|
}
|
|
306
307
|
|
|
308
|
+
// src/tui/hangul.ts
|
|
309
|
+
var CHO = "\u3131\u3132\u3134\u3137\u3138\u3139\u3141\u3142\u3143\u3145\u3146\u3147\u3148\u3149\u314A\u314B\u314C\u314D\u314E";
|
|
310
|
+
var JUNG = "\u314F\u3150\u3151\u3152\u3153\u3154\u3155\u3156\u3157\u3158\u3159\u315A\u315B\u315C\u315D\u315E\u315F\u3160\u3161\u3162\u3163";
|
|
311
|
+
var JONG = " \u3131\u3132\u3133\u3134\u3135\u3136\u3137\u3139\u313A\u313B\u313C\u313D\u313E\u313F\u3140\u3141\u3142\u3144\u3145\u3146\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
312
|
+
var SYLLABLE_BASE = 44032;
|
|
313
|
+
var KEYS = {
|
|
314
|
+
r: "\u3131",
|
|
315
|
+
R: "\u3132",
|
|
316
|
+
s: "\u3134",
|
|
317
|
+
e: "\u3137",
|
|
318
|
+
E: "\u3138",
|
|
319
|
+
f: "\u3139",
|
|
320
|
+
a: "\u3141",
|
|
321
|
+
q: "\u3142",
|
|
322
|
+
Q: "\u3143",
|
|
323
|
+
t: "\u3145",
|
|
324
|
+
T: "\u3146",
|
|
325
|
+
d: "\u3147",
|
|
326
|
+
w: "\u3148",
|
|
327
|
+
W: "\u3149",
|
|
328
|
+
c: "\u314A",
|
|
329
|
+
z: "\u314B",
|
|
330
|
+
x: "\u314C",
|
|
331
|
+
v: "\u314D",
|
|
332
|
+
g: "\u314E",
|
|
333
|
+
k: "\u314F",
|
|
334
|
+
o: "\u3150",
|
|
335
|
+
i: "\u3151",
|
|
336
|
+
O: "\u3152",
|
|
337
|
+
j: "\u3153",
|
|
338
|
+
p: "\u3154",
|
|
339
|
+
u: "\u3155",
|
|
340
|
+
P: "\u3156",
|
|
341
|
+
h: "\u3157",
|
|
342
|
+
y: "\u315B",
|
|
343
|
+
n: "\u315C",
|
|
344
|
+
b: "\u3160",
|
|
345
|
+
m: "\u3161",
|
|
346
|
+
l: "\u3163"
|
|
347
|
+
};
|
|
348
|
+
var VOWEL_PAIRS = {
|
|
349
|
+
"\u3157\u314F": "\u3158",
|
|
350
|
+
"\u3157\u3150": "\u3159",
|
|
351
|
+
"\u3157\u3163": "\u315A",
|
|
352
|
+
"\u315C\u3153": "\u315D",
|
|
353
|
+
"\u315C\u3154": "\u315E",
|
|
354
|
+
"\u315C\u3163": "\u315F",
|
|
355
|
+
"\u3161\u3163": "\u3162"
|
|
356
|
+
};
|
|
357
|
+
var FINAL_PAIRS = {
|
|
358
|
+
"\u3131\u3145": "\u3133",
|
|
359
|
+
"\u3134\u3148": "\u3135",
|
|
360
|
+
"\u3134\u314E": "\u3136",
|
|
361
|
+
"\u3139\u3131": "\u313A",
|
|
362
|
+
"\u3139\u3141": "\u313B",
|
|
363
|
+
"\u3139\u3142": "\u313C",
|
|
364
|
+
"\u3139\u3145": "\u313D",
|
|
365
|
+
"\u3139\u314C": "\u313E",
|
|
366
|
+
"\u3139\u314D": "\u313F",
|
|
367
|
+
"\u3139\u314E": "\u3140",
|
|
368
|
+
"\u3142\u3145": "\u3144"
|
|
369
|
+
};
|
|
370
|
+
var SPLIT = Object.fromEntries(
|
|
371
|
+
[...Object.entries(VOWEL_PAIRS), ...Object.entries(FINAL_PAIRS)].map(([pair, joined]) => [
|
|
372
|
+
joined,
|
|
373
|
+
[pair[0], pair[1]]
|
|
374
|
+
])
|
|
375
|
+
);
|
|
376
|
+
var EMPTY = {};
|
|
377
|
+
var SHIFTED_KEYS = /* @__PURE__ */ new Set(["r", "e", "q", "t", "w", "o", "p"]);
|
|
378
|
+
function jamoOf(key, capsLock = false) {
|
|
379
|
+
const effective = capsLock ? swapCase(key) : key;
|
|
380
|
+
if (KEYS[effective]) return KEYS[effective];
|
|
381
|
+
const lower = effective.toLowerCase();
|
|
382
|
+
return lower === effective ? void 0 : KEYS[lower];
|
|
383
|
+
}
|
|
384
|
+
function swapCase(key) {
|
|
385
|
+
const lower = key.toLowerCase();
|
|
386
|
+
return key === lower ? key.toUpperCase() : lower;
|
|
387
|
+
}
|
|
388
|
+
function detectCapsLock(current, key) {
|
|
389
|
+
const lower = key.toLowerCase();
|
|
390
|
+
const upper = key.toUpperCase();
|
|
391
|
+
if (lower === upper) return current;
|
|
392
|
+
if (!KEYS[lower]) return current;
|
|
393
|
+
if (SHIFTED_KEYS.has(lower)) return current;
|
|
394
|
+
return key === upper;
|
|
395
|
+
}
|
|
396
|
+
function isVowel(jamo) {
|
|
397
|
+
return JUNG.includes(jamo) || jamo === "\u3157" || jamo === "\u315C" || jamo === "\u3161";
|
|
398
|
+
}
|
|
399
|
+
function canBeFinal(jamo) {
|
|
400
|
+
return JONG.indexOf(jamo) > 0;
|
|
401
|
+
}
|
|
402
|
+
function display(state) {
|
|
403
|
+
const { cho, jung, jong } = state;
|
|
404
|
+
if (cho && jung) {
|
|
405
|
+
const l = CHO.indexOf(cho);
|
|
406
|
+
const v = JUNG.indexOf(jung);
|
|
407
|
+
const t = jong ? JONG.indexOf(jong) : 0;
|
|
408
|
+
if (l >= 0 && v >= 0 && t >= 0) {
|
|
409
|
+
return String.fromCharCode(SYLLABLE_BASE + (l * 21 + v) * 28 + t);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return cho ?? jung ?? "";
|
|
413
|
+
}
|
|
414
|
+
function feed(state, jamo) {
|
|
415
|
+
const { cho, jung, jong } = state;
|
|
416
|
+
if (isVowel(jamo)) {
|
|
417
|
+
if (cho && jung && jong) {
|
|
418
|
+
const parts = SPLIT[jong];
|
|
419
|
+
const moved = parts ? parts[1] : jong;
|
|
420
|
+
const kept = parts ? parts[0] : void 0;
|
|
421
|
+
return {
|
|
422
|
+
commit: display({ cho, jung, jong: kept }),
|
|
423
|
+
state: { cho: moved, jung: jamo }
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (cho && jung) {
|
|
427
|
+
const merged = VOWEL_PAIRS[jung + jamo];
|
|
428
|
+
if (merged) return { commit: "", state: { cho, jung: merged } };
|
|
429
|
+
return { commit: display(state), state: { jung: jamo } };
|
|
430
|
+
}
|
|
431
|
+
if (cho) return { commit: "", state: { cho, jung: jamo } };
|
|
432
|
+
if (jung) {
|
|
433
|
+
const merged = VOWEL_PAIRS[jung + jamo];
|
|
434
|
+
if (merged) return { commit: "", state: { jung: merged } };
|
|
435
|
+
return { commit: jung, state: { jung: jamo } };
|
|
436
|
+
}
|
|
437
|
+
return { commit: "", state: { jung: jamo } };
|
|
438
|
+
}
|
|
439
|
+
if (cho && jung) {
|
|
440
|
+
if (!jong) {
|
|
441
|
+
if (canBeFinal(jamo)) return { commit: "", state: { cho, jung, jong: jamo } };
|
|
442
|
+
return { commit: display(state), state: { cho: jamo } };
|
|
443
|
+
}
|
|
444
|
+
const merged = FINAL_PAIRS[jong + jamo];
|
|
445
|
+
if (merged) return { commit: "", state: { cho, jung, jong: merged } };
|
|
446
|
+
return { commit: display(state), state: { cho: jamo } };
|
|
447
|
+
}
|
|
448
|
+
if (cho) {
|
|
449
|
+
return { commit: cho, state: { cho: jamo } };
|
|
450
|
+
}
|
|
451
|
+
if (jung) return { commit: jung, state: { cho: jamo } };
|
|
452
|
+
return { commit: "", state: { cho: jamo } };
|
|
453
|
+
}
|
|
454
|
+
function back(state) {
|
|
455
|
+
const { cho, jung, jong } = state;
|
|
456
|
+
if (jong) {
|
|
457
|
+
const parts = SPLIT[jong];
|
|
458
|
+
return { state: { cho, jung, jong: parts ? parts[0] : void 0 }, handled: true };
|
|
459
|
+
}
|
|
460
|
+
if (jung) {
|
|
461
|
+
const parts = SPLIT[jung];
|
|
462
|
+
return { state: { cho, jung: parts ? parts[0] : void 0 }, handled: true };
|
|
463
|
+
}
|
|
464
|
+
if (cho) return { state: EMPTY, handled: true };
|
|
465
|
+
return { state: EMPTY, handled: false };
|
|
466
|
+
}
|
|
467
|
+
var IDLE = { state: EMPTY, keys: [], capsLock: false };
|
|
468
|
+
function typeKey(session, key) {
|
|
469
|
+
const caps = detectCapsLock(session.capsLock, key);
|
|
470
|
+
let { state, keys } = session;
|
|
471
|
+
let commit = "";
|
|
472
|
+
if (caps !== session.capsLock && keys.length > 0) {
|
|
473
|
+
let rebuilt = EMPTY;
|
|
474
|
+
for (const previous of keys) {
|
|
475
|
+
const jamo2 = jamoOf(previous, caps);
|
|
476
|
+
if (!jamo2) continue;
|
|
477
|
+
const step = feed(rebuilt, jamo2);
|
|
478
|
+
commit += step.commit;
|
|
479
|
+
rebuilt = step.state;
|
|
480
|
+
}
|
|
481
|
+
state = rebuilt;
|
|
482
|
+
}
|
|
483
|
+
const jamo = jamoOf(key, caps);
|
|
484
|
+
if (!jamo) {
|
|
485
|
+
commit += display(state) + key;
|
|
486
|
+
return { session: { state: EMPTY, keys: [], capsLock: caps }, commit, composing: "" };
|
|
487
|
+
}
|
|
488
|
+
const result = feed(state, jamo);
|
|
489
|
+
commit += result.commit;
|
|
490
|
+
keys = result.commit ? [key] : [...keys, key];
|
|
491
|
+
return {
|
|
492
|
+
session: { state: result.state, keys, capsLock: caps },
|
|
493
|
+
commit,
|
|
494
|
+
composing: display(result.state)
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function backspace(session) {
|
|
498
|
+
const stepped = back(session.state);
|
|
499
|
+
if (!stepped.handled) return { session, composing: "", handled: false };
|
|
500
|
+
return {
|
|
501
|
+
session: { ...session, state: stepped.state, keys: session.keys.slice(0, -1) },
|
|
502
|
+
composing: display(stepped.state),
|
|
503
|
+
handled: true
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/tui/kitty.ts
|
|
508
|
+
var ESC = "\x1B";
|
|
509
|
+
var QUERY = `${ESC}[?u`;
|
|
510
|
+
var KEY_CODES = {
|
|
511
|
+
57358: "capslock",
|
|
512
|
+
57449: "rightalt"
|
|
513
|
+
};
|
|
514
|
+
var CAPS_LOCK_BIT = 64;
|
|
515
|
+
function parseKeyEvents(chunk) {
|
|
516
|
+
const events = [];
|
|
517
|
+
const pattern = /\u001B\[(\d+)(?::\d+)*(?:;(\d+)(?::(\d+))?)?(?:;[\d:]*)?u/g;
|
|
518
|
+
for (const match of chunk.matchAll(pattern)) {
|
|
519
|
+
const code = Number(match[1]);
|
|
520
|
+
const modifiers = match[2] ? Number(match[2]) - 1 : 0;
|
|
521
|
+
events.push({
|
|
522
|
+
name: KEY_CODES[code],
|
|
523
|
+
// eslint-disable-next-line no-bitwise
|
|
524
|
+
capsLock: (modifiers & CAPS_LOCK_BIT) !== 0,
|
|
525
|
+
eventType: match[3] ? Number(match[3]) : 1
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return events;
|
|
529
|
+
}
|
|
530
|
+
async function supportsKittyKeyboard(streams, timeoutMs = 200) {
|
|
531
|
+
if (process.env.DEX_NO_KITTY === "1") return false;
|
|
532
|
+
if (!streams.stdout.isTTY || !streams.isTTY) return false;
|
|
533
|
+
const wasRaw = streams.stdin.isRaw === true;
|
|
534
|
+
const wasPaused = !wasRaw;
|
|
535
|
+
streams.stdin.setRawMode?.(true);
|
|
536
|
+
streams.stdin.resume?.();
|
|
537
|
+
return new Promise((resolve) => {
|
|
538
|
+
let settled = false;
|
|
539
|
+
let seen = "";
|
|
540
|
+
const finish = (supported) => {
|
|
541
|
+
if (settled) return;
|
|
542
|
+
settled = true;
|
|
543
|
+
clearTimeout(timer);
|
|
544
|
+
streams.stdin.removeListener("data", onData);
|
|
545
|
+
if (!wasRaw) streams.stdin.setRawMode?.(false);
|
|
546
|
+
if (wasPaused) streams.stdin.pause?.();
|
|
547
|
+
resolve(supported);
|
|
548
|
+
};
|
|
549
|
+
const onData = (data) => {
|
|
550
|
+
seen += typeof data === "string" ? data : data.toString("utf8");
|
|
551
|
+
if (/\u001B\[\?\d*u/.test(seen)) finish(true);
|
|
552
|
+
};
|
|
553
|
+
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
554
|
+
streams.stdin.on("data", onData);
|
|
555
|
+
streams.stdout.write(QUERY);
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
307
559
|
// src/tui/ime-text-input.tsx
|
|
308
560
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
309
561
|
var segmenter = new Intl.Segmenter("ko", { granularity: "grapheme" });
|
|
@@ -349,6 +601,7 @@ function ImeTextInput(props) {
|
|
|
349
601
|
const valueRef = useRef2(props.value);
|
|
350
602
|
const cursorRef = useRef2(initialSegments.length);
|
|
351
603
|
const pastingRef = useRef2(false);
|
|
604
|
+
const typingRef = useRef2(IDLE);
|
|
352
605
|
const moveCursor = (next, length) => {
|
|
353
606
|
const resolved = clamp(next, 0, length);
|
|
354
607
|
cursorRef.current = resolved;
|
|
@@ -360,6 +613,27 @@ function ImeTextInput(props) {
|
|
|
360
613
|
moveCursor(nextCursor, segments.length);
|
|
361
614
|
props.onChange(nextValue);
|
|
362
615
|
};
|
|
616
|
+
const { stdin: stdin2, isRawModeSupported } = useStdin();
|
|
617
|
+
const onModeKeyRef = useRef2(void 0);
|
|
618
|
+
onModeKeyRef.current = () => props.onHangulModeChange?.(!props.hangulMode);
|
|
619
|
+
useEffect2(() => {
|
|
620
|
+
if (!props.focus || !isRawModeSupported) return void 0;
|
|
621
|
+
const onData = (data) => {
|
|
622
|
+
const chunk = typeof data === "string" ? data : data.toString("utf8");
|
|
623
|
+
for (const event of parseKeyEvents(chunk)) {
|
|
624
|
+
typingRef.current = { ...typingRef.current, capsLock: event.capsLock };
|
|
625
|
+
if (event.eventType !== 1) continue;
|
|
626
|
+
if (event.name === "rightalt" || event.name === "capslock") onModeKeyRef.current?.();
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
stdin2?.on("data", onData);
|
|
630
|
+
return () => void stdin2?.off("data", onData);
|
|
631
|
+
}, [props.focus, isRawModeSupported, stdin2]);
|
|
632
|
+
useEffect2(() => {
|
|
633
|
+
if (!props.focus || !props.hangulMode) {
|
|
634
|
+
typingRef.current = { ...IDLE, capsLock: typingRef.current.capsLock };
|
|
635
|
+
}
|
|
636
|
+
}, [props.focus, props.hangulMode]);
|
|
363
637
|
useEffect2(() => {
|
|
364
638
|
if (props.value === valueRef.current) return;
|
|
365
639
|
const previousLength = graphemes(valueRef.current).length;
|
|
@@ -368,12 +642,23 @@ function ImeTextInput(props) {
|
|
|
368
642
|
valueRef.current = props.value;
|
|
369
643
|
moveCursor(wasAtEnd ? nextLength : cursorRef.current, nextLength);
|
|
370
644
|
}, [props.value]);
|
|
645
|
+
const applyComposition = (segments, cursor2, previous, commit, next) => {
|
|
646
|
+
const removed = previous ? 1 : 0;
|
|
647
|
+
const inserted = graphemes(commit + next);
|
|
648
|
+
segments.splice(cursor2 - removed, removed, ...inserted);
|
|
649
|
+
updateValue(segments, cursor2 - removed + inserted.length);
|
|
650
|
+
};
|
|
371
651
|
useInput(
|
|
372
652
|
(input, key) => {
|
|
373
653
|
const current = graphemes(valueRef.current);
|
|
374
654
|
const currentCursor = clamp(cursorRef.current, 0, current.length);
|
|
375
655
|
const event = classifyInput(input, pastingRef.current);
|
|
656
|
+
const shown = display(typingRef.current.state);
|
|
657
|
+
const settle = () => {
|
|
658
|
+
typingRef.current = { ...IDLE, capsLock: typingRef.current.capsLock };
|
|
659
|
+
};
|
|
376
660
|
if (event.kind === "paste-start") {
|
|
661
|
+
settle();
|
|
377
662
|
pastingRef.current = true;
|
|
378
663
|
return;
|
|
379
664
|
}
|
|
@@ -389,36 +674,70 @@ function ImeTextInput(props) {
|
|
|
389
674
|
}
|
|
390
675
|
return;
|
|
391
676
|
}
|
|
677
|
+
if (key.ctrl && (input === "`" || input === "l") || key.meta && input === " ") {
|
|
678
|
+
settle();
|
|
679
|
+
props.onHangulModeChange?.(!props.hangulMode);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
392
682
|
if (key.return) {
|
|
683
|
+
settle();
|
|
393
684
|
props.onSubmit?.(valueRef.current);
|
|
394
685
|
return;
|
|
395
686
|
}
|
|
396
687
|
if (key.leftArrow) {
|
|
688
|
+
settle();
|
|
397
689
|
moveCursor(currentCursor - 1, current.length);
|
|
398
690
|
return;
|
|
399
691
|
}
|
|
400
692
|
if (key.rightArrow) {
|
|
693
|
+
settle();
|
|
401
694
|
moveCursor(currentCursor + 1, current.length);
|
|
402
695
|
return;
|
|
403
696
|
}
|
|
404
697
|
if (key.home) {
|
|
698
|
+
settle();
|
|
405
699
|
moveCursor(0, current.length);
|
|
406
700
|
return;
|
|
407
701
|
}
|
|
408
702
|
if (key.end) {
|
|
703
|
+
settle();
|
|
409
704
|
moveCursor(current.length, current.length);
|
|
410
705
|
return;
|
|
411
706
|
}
|
|
412
707
|
if (key.backspace || key.delete) {
|
|
708
|
+
const stepped = backspace(typingRef.current);
|
|
709
|
+
if (stepped.handled) {
|
|
710
|
+
typingRef.current = stepped.session;
|
|
711
|
+
applyComposition(current, currentCursor, shown, "", stepped.composing);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
413
714
|
if (currentCursor === 0) return;
|
|
414
715
|
current.splice(currentCursor - 1, 1);
|
|
415
716
|
updateValue(current, currentCursor - 1);
|
|
416
717
|
return;
|
|
417
718
|
}
|
|
418
719
|
if (key.ctrl || key.meta || key.tab || key.escape || key.upArrow || key.downArrow || key.pageUp || key.pageDown) {
|
|
720
|
+
settle();
|
|
419
721
|
return;
|
|
420
722
|
}
|
|
421
723
|
if (event.kind !== "text") return;
|
|
724
|
+
if (props.hangulMode) {
|
|
725
|
+
let session = typingRef.current;
|
|
726
|
+
let previous = shown;
|
|
727
|
+
let segments = current;
|
|
728
|
+
let cursor2 = currentCursor;
|
|
729
|
+
for (const character of event.text) {
|
|
730
|
+
const result = typeKey(session, character);
|
|
731
|
+
session = result.session;
|
|
732
|
+
applyComposition(segments, cursor2, previous, result.commit, result.composing);
|
|
733
|
+
segments = graphemes(valueRef.current);
|
|
734
|
+
cursor2 = cursorRef.current;
|
|
735
|
+
previous = result.composing;
|
|
736
|
+
}
|
|
737
|
+
typingRef.current = session;
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
settle();
|
|
422
741
|
const inserted = graphemes(event.text);
|
|
423
742
|
current.splice(currentCursor, 0, ...inserted);
|
|
424
743
|
updateValue(current, currentCursor + inserted.length);
|
|
@@ -451,8 +770,16 @@ function Header(props) {
|
|
|
451
770
|
] }) : /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\uC124\uC815 \uD544\uC694" })
|
|
452
771
|
] });
|
|
453
772
|
}
|
|
454
|
-
function Footer({ text }) {
|
|
455
|
-
return /* @__PURE__ */
|
|
773
|
+
function Footer({ text, mode }) {
|
|
774
|
+
return /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, children: [
|
|
775
|
+
mode ? /* @__PURE__ */ jsxs2(Text2, { bold: true, color: mode === "\uD55C" ? "yellow" : "gray", children: [
|
|
776
|
+
"[",
|
|
777
|
+
mode,
|
|
778
|
+
"]",
|
|
779
|
+
" "
|
|
780
|
+
] }) : null,
|
|
781
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: text })
|
|
782
|
+
] });
|
|
456
783
|
}
|
|
457
784
|
function Loading({ label = "\uBD88\uB7EC\uC624\uB294 \uC911..." }) {
|
|
458
785
|
return /* @__PURE__ */ jsx2(Box2, { padding: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
|
|
@@ -736,7 +1063,8 @@ function ChatPane(props) {
|
|
|
736
1063
|
}
|
|
737
1064
|
function Composer(props) {
|
|
738
1065
|
return /* @__PURE__ */ jsxs6(Box6, { borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
|
|
739
|
-
/* @__PURE__ */ jsx6(Text6, { color: "
|
|
1066
|
+
/* @__PURE__ */ jsx6(Text6, { color: props.hangulMode ? "yellow" : void 0, dimColor: !props.hangulMode, children: props.hangulMode ? "\uD55C" : "EN" }),
|
|
1067
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: " \u203A " }),
|
|
740
1068
|
props.disabled ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uC751\uB2F5\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911..." }) : /* @__PURE__ */ jsx6(
|
|
741
1069
|
ImeTextInput,
|
|
742
1070
|
{
|
|
@@ -744,7 +1072,9 @@ function Composer(props) {
|
|
|
744
1072
|
onChange: props.onChange,
|
|
745
1073
|
onSubmit: props.onSubmit,
|
|
746
1074
|
focus: props.focused,
|
|
747
|
-
placeholder: "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD558\uC138\uC694"
|
|
1075
|
+
placeholder: "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD558\uC138\uC694",
|
|
1076
|
+
hangulMode: props.hangulMode,
|
|
1077
|
+
onHangulModeChange: props.onHangulModeChange
|
|
748
1078
|
}
|
|
749
1079
|
)
|
|
750
1080
|
] });
|
|
@@ -765,6 +1095,11 @@ function Dashboard(props) {
|
|
|
765
1095
|
const [history, setHistory] = useState7(false);
|
|
766
1096
|
const [start, setStart] = useState7();
|
|
767
1097
|
const [scrollUp, setScrollUp] = useState7(0);
|
|
1098
|
+
const [hangulMode, setHangulMode] = useState7(props.preferences?.hangulMode ?? false);
|
|
1099
|
+
const changeHangulMode = (enabled) => {
|
|
1100
|
+
setHangulMode(enabled);
|
|
1101
|
+
props.preferences?.onHangulModeChange?.(enabled);
|
|
1102
|
+
};
|
|
768
1103
|
const viewport = useRef3({ lineCount: 0, height: 0 });
|
|
769
1104
|
const [starting, setStarting] = useState7(false);
|
|
770
1105
|
const controller = useRef3(null);
|
|
@@ -966,7 +1301,9 @@ function Dashboard(props) {
|
|
|
966
1301
|
onChange: setInput,
|
|
967
1302
|
onSubmit: (value) => void send(value),
|
|
968
1303
|
focused: focus === "composer",
|
|
969
|
-
disabled: chat.running || !selected
|
|
1304
|
+
disabled: chat.running || !selected,
|
|
1305
|
+
hangulMode,
|
|
1306
|
+
onHangulModeChange: changeHangulMode
|
|
970
1307
|
}
|
|
971
1308
|
)
|
|
972
1309
|
] });
|
|
@@ -985,7 +1322,13 @@ function Dashboard(props) {
|
|
|
985
1322
|
}
|
|
986
1323
|
),
|
|
987
1324
|
body,
|
|
988
|
-
/* @__PURE__ */ jsx6(
|
|
1325
|
+
/* @__PURE__ */ jsx6(
|
|
1326
|
+
Footer,
|
|
1327
|
+
{
|
|
1328
|
+
mode: hangulMode ? "\uD55C" : "EN",
|
|
1329
|
+
text: "Ctrl+Space \uD55C/\uC601 \xB7 Tab \uD328\uB110 \xB7 PgUp/PgDn \uC2A4\uD06C\uB864 \xB7 Ctrl+K \uBA85\uB839 \xB7 Ctrl+H \uAE30\uB85D \xB7 Ctrl+P \uD504\uB85C\uD544 \xB7 Esc \uCDE8\uC18C \xB7 Ctrl+Q \uC885\uB8CC"
|
|
1330
|
+
}
|
|
1331
|
+
)
|
|
989
1332
|
] });
|
|
990
1333
|
}
|
|
991
1334
|
|
|
@@ -1193,7 +1536,10 @@ function ServerScreen(props) {
|
|
|
1193
1536
|
|
|
1194
1537
|
// src/tui/app.tsx
|
|
1195
1538
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1196
|
-
function App({
|
|
1539
|
+
function App({
|
|
1540
|
+
engine,
|
|
1541
|
+
preferences
|
|
1542
|
+
}) {
|
|
1197
1543
|
const { exit } = useApp2();
|
|
1198
1544
|
const [route, setRoute] = useState11("boot");
|
|
1199
1545
|
const [session, setSession] = useState11();
|
|
@@ -1385,6 +1731,7 @@ function App({ engine }) {
|
|
|
1385
1731
|
return /* @__PURE__ */ jsx10(
|
|
1386
1732
|
Dashboard,
|
|
1387
1733
|
{
|
|
1734
|
+
preferences,
|
|
1388
1735
|
engine,
|
|
1389
1736
|
session,
|
|
1390
1737
|
onProfiles: () => void openProfiles(),
|
|
@@ -1408,25 +1755,26 @@ function RetryInput({ onRetry }) {
|
|
|
1408
1755
|
}
|
|
1409
1756
|
|
|
1410
1757
|
// src/tui/screen.ts
|
|
1411
|
-
var
|
|
1412
|
-
var ENTER_ALT_SCREEN = `${
|
|
1413
|
-
var LEAVE_ALT_SCREEN = `${
|
|
1414
|
-
var SHOW_CURSOR = `${
|
|
1758
|
+
var ESC2 = "\x1B";
|
|
1759
|
+
var ENTER_ALT_SCREEN = `${ESC2}[?1049h`;
|
|
1760
|
+
var LEAVE_ALT_SCREEN = `${ESC2}[?1049l`;
|
|
1761
|
+
var SHOW_CURSOR = `${ESC2}[?25h`;
|
|
1415
1762
|
var DISABLE_REPORTS = [
|
|
1416
|
-
`${
|
|
1763
|
+
`${ESC2}[?1004l`,
|
|
1417
1764
|
// 포커스 들어옴/나감
|
|
1418
|
-
`${
|
|
1765
|
+
`${ESC2}[?1000l`,
|
|
1419
1766
|
// 마우스 클릭
|
|
1420
|
-
`${
|
|
1767
|
+
`${ESC2}[?1002l`,
|
|
1421
1768
|
// 마우스 드래그
|
|
1422
|
-
`${
|
|
1769
|
+
`${ESC2}[?1003l`,
|
|
1423
1770
|
// 마우스 이동 전부
|
|
1424
|
-
`${
|
|
1771
|
+
`${ESC2}[?1006l`
|
|
1425
1772
|
// SGR 확장 좌표
|
|
1426
1773
|
].join("");
|
|
1427
|
-
var ENABLE_BRACKETED_PASTE = `${
|
|
1428
|
-
var DISABLE_BRACKETED_PASTE = `${
|
|
1429
|
-
|
|
1774
|
+
var ENABLE_BRACKETED_PASTE = `${ESC2}[?2004h`;
|
|
1775
|
+
var DISABLE_BRACKETED_PASTE = `${ESC2}[?2004l`;
|
|
1776
|
+
var POP_KITTY_KEYBOARD = `${ESC2}[<u`;
|
|
1777
|
+
function createScreenGuard(stream, options = {}) {
|
|
1430
1778
|
const alt = Boolean(stream.isTTY);
|
|
1431
1779
|
let entered = false;
|
|
1432
1780
|
let restored = false;
|
|
@@ -1439,16 +1787,49 @@ function createScreenGuard(stream) {
|
|
|
1439
1787
|
restore() {
|
|
1440
1788
|
if (restored) return;
|
|
1441
1789
|
restored = true;
|
|
1442
|
-
if (entered)
|
|
1790
|
+
if (entered) {
|
|
1791
|
+
stream.write(
|
|
1792
|
+
(options.kittyKeyboard ? POP_KITTY_KEYBOARD : "") + DISABLE_BRACKETED_PASTE + LEAVE_ALT_SCREEN
|
|
1793
|
+
);
|
|
1794
|
+
}
|
|
1443
1795
|
stream.write(SHOW_CURSOR);
|
|
1444
1796
|
}
|
|
1445
1797
|
};
|
|
1446
1798
|
}
|
|
1447
1799
|
|
|
1800
|
+
// src/tui/preferences.ts
|
|
1801
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
1802
|
+
import { dirname, join } from "node:path";
|
|
1803
|
+
function preferencesPath(env = process.env) {
|
|
1804
|
+
return join(dataDirectory(env), "tui.json");
|
|
1805
|
+
}
|
|
1806
|
+
function localeDefaultHangul(env = process.env) {
|
|
1807
|
+
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
1808
|
+
return /^ko(_|-|\.|$)/i.test(locale.trim());
|
|
1809
|
+
}
|
|
1810
|
+
async function readPreferences(env = process.env) {
|
|
1811
|
+
const fallback = { hangulMode: localeDefaultHangul(env) };
|
|
1812
|
+
try {
|
|
1813
|
+
const raw = JSON.parse(await readFile(preferencesPath(env), "utf8"));
|
|
1814
|
+
return typeof raw.hangulMode === "boolean" ? { hangulMode: raw.hangulMode } : fallback;
|
|
1815
|
+
} catch {
|
|
1816
|
+
return fallback;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
async function writePreferences(preferences, env = process.env) {
|
|
1820
|
+
try {
|
|
1821
|
+
const path = preferencesPath(env);
|
|
1822
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1823
|
+
await writeFile(path, `${JSON.stringify(preferences, null, 2)}
|
|
1824
|
+
`, "utf8");
|
|
1825
|
+
} catch {
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1448
1829
|
// src/tui/index.tsx
|
|
1449
1830
|
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1450
1831
|
async function runTui(engine) {
|
|
1451
|
-
|
|
1832
|
+
let screen = createScreenGuard(stdout);
|
|
1452
1833
|
const restore = () => screen.restore();
|
|
1453
1834
|
const onSignal = () => {
|
|
1454
1835
|
restore();
|
|
@@ -1457,12 +1838,34 @@ async function runTui(engine) {
|
|
|
1457
1838
|
process.once("exit", restore);
|
|
1458
1839
|
process.once("SIGINT", onSignal);
|
|
1459
1840
|
process.once("SIGTERM", onSignal);
|
|
1841
|
+
const preferences = await readPreferences();
|
|
1842
|
+
const kitty = await supportsKittyKeyboard({ stdin, stdout, isTTY: stdin.isTTY });
|
|
1843
|
+
screen = createScreenGuard(stdout, { kittyKeyboard: kitty });
|
|
1460
1844
|
screen.enter();
|
|
1461
1845
|
try {
|
|
1462
|
-
const instance = render(
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1846
|
+
const instance = render(
|
|
1847
|
+
/* @__PURE__ */ jsx11(
|
|
1848
|
+
App,
|
|
1849
|
+
{
|
|
1850
|
+
engine,
|
|
1851
|
+
preferences: {
|
|
1852
|
+
hangulMode: preferences.hangulMode,
|
|
1853
|
+
onHangulModeChange: (enabled) => void writePreferences({ hangulMode: enabled })
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
),
|
|
1857
|
+
{
|
|
1858
|
+
exitOnCtrlC: true,
|
|
1859
|
+
patchConsole: true,
|
|
1860
|
+
// 모든 키를 이스케이프로 받아야 수식 키 자체가 사건으로 온다.
|
|
1861
|
+
...kitty ? {
|
|
1862
|
+
kittyKeyboard: {
|
|
1863
|
+
mode: "enabled",
|
|
1864
|
+
flags: ["disambiguateEscapeCodes", "reportAllKeysAsEscapeCodes"]
|
|
1865
|
+
}
|
|
1866
|
+
} : {}
|
|
1867
|
+
}
|
|
1868
|
+
);
|
|
1466
1869
|
await instance.waitUntilExit();
|
|
1467
1870
|
} finally {
|
|
1468
1871
|
restore();
|
|
@@ -1474,4 +1877,4 @@ async function runTui(engine) {
|
|
|
1474
1877
|
export {
|
|
1475
1878
|
runTui
|
|
1476
1879
|
};
|
|
1477
|
-
//# sourceMappingURL=tui-
|
|
1880
|
+
//# sourceMappingURL=tui-QF5OJEFW.js.map
|