wispy-cli 2.7.24 → 2.7.25
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/lib/cjk-text-input.mjs +58 -120
- package/package.json +1 -1
package/lib/cjk-text-input.mjs
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* cjk-text-input.mjs — CJK-aware text input for Ink TUI
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Key insight: When stdin is in "raw" mode and encoding is set to "utf8",
|
|
9
|
-
* Node.js emits fully-composed UTF-8 strings (including Korean/CJK characters)
|
|
10
|
-
* rather than raw bytes. The terminal emulator handles IME composition before
|
|
11
|
-
* sending the final composed character to stdin.
|
|
4
|
+
* Uses Ink's native useInput hook (not raw stdin) so it works alongside
|
|
5
|
+
* other Ink components. Korean/CJK works because useInput already receives
|
|
6
|
+
* decoded UTF-8 characters — the real fix is handling them correctly.
|
|
12
7
|
*/
|
|
13
8
|
|
|
14
|
-
import React, { useState, useEffect
|
|
15
|
-
import { Box, Text,
|
|
9
|
+
import React, { useState, useEffect } from "react";
|
|
10
|
+
import { Box, Text, useInput } from "ink";
|
|
16
11
|
|
|
17
|
-
/**
|
|
18
|
-
* CJKTextInput — A text input component that properly handles CJK/Korean input.
|
|
19
|
-
*
|
|
20
|
-
* @param {object} props
|
|
21
|
-
* @param {string} props.value - Controlled value
|
|
22
|
-
* @param {function} props.onChange - Called with new value on change
|
|
23
|
-
* @param {function} props.onSubmit - Called with value on Enter
|
|
24
|
-
* @param {string} [props.placeholder] - Placeholder text when empty
|
|
25
|
-
* @param {boolean} [props.focus=true] - Whether this input is focused
|
|
26
|
-
* @param {boolean} [props.showCursor=true] - Whether to show cursor block
|
|
27
|
-
*/
|
|
28
12
|
function CJKTextInput({
|
|
29
13
|
value = "",
|
|
30
14
|
onChange,
|
|
@@ -33,118 +17,72 @@ function CJKTextInput({
|
|
|
33
17
|
focus = true,
|
|
34
18
|
showCursor = true,
|
|
35
19
|
}) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// before emitting the data event — this is the key fix for CJK input.
|
|
49
|
-
const prevEncoding = stdin.readableEncoding;
|
|
50
|
-
stdin.setEncoding("utf8");
|
|
51
|
-
|
|
52
|
-
const onData = (chunk) => {
|
|
53
|
-
// chunk is now a UTF-8 decoded string (not a Buffer)
|
|
54
|
-
const str = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
55
|
-
|
|
56
|
-
for (let i = 0; i < str.length; ) {
|
|
57
|
-
// Check for escape sequences (arrow keys, etc.)
|
|
58
|
-
if (str[i] === "\x1b") {
|
|
59
|
-
const seq = str.slice(i);
|
|
60
|
-
// Arrow keys
|
|
61
|
-
if (seq.startsWith("\x1b[D") || seq.startsWith("\x1bOD")) {
|
|
62
|
-
// Left arrow — not handled here, skip
|
|
63
|
-
i += seq.startsWith("\x1b[D") ? 3 : 3;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (seq.startsWith("\x1b[C") || seq.startsWith("\x1bOC")) {
|
|
67
|
-
// Right arrow — not handled here, skip
|
|
68
|
-
i += 3;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
// Other escape sequences — skip
|
|
72
|
-
i += 1;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const ch = str[i];
|
|
77
|
-
const code = ch.charCodeAt(0);
|
|
78
|
-
|
|
79
|
-
if (ch === "\r" || ch === "\n") {
|
|
80
|
-
onSubmit?.(valueRef.current);
|
|
81
|
-
i++;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (ch === "\x7f" || ch === "\b") {
|
|
86
|
-
// Backspace
|
|
87
|
-
const newVal = valueRef.current.slice(0, -1);
|
|
88
|
-
valueRef.current = newVal;
|
|
89
|
-
onChange?.(newVal);
|
|
90
|
-
i++;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (ch === "\x03") {
|
|
95
|
-
// Ctrl+C — let Ink handle it
|
|
96
|
-
i++;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (ch === "\t") {
|
|
101
|
-
// Tab — do NOT consume, let Ink's useInput handle tab for view switching
|
|
102
|
-
i++;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
20
|
+
useInput(
|
|
21
|
+
(input, key) => {
|
|
22
|
+
// Skip keys handled by parent (Tab, Ctrl+C, arrows for view switching)
|
|
23
|
+
if (
|
|
24
|
+
key.tab ||
|
|
25
|
+
(key.shift && key.tab) ||
|
|
26
|
+
key.upArrow ||
|
|
27
|
+
key.downArrow ||
|
|
28
|
+
(key.ctrl && input === "c")
|
|
29
|
+
) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
105
32
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
i++;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
33
|
+
if (key.return) {
|
|
34
|
+
onSubmit?.(value);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
113
37
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
38
|
+
if (key.backspace || key.delete) {
|
|
39
|
+
// For CJK: slice off one character (which may be multi-byte)
|
|
40
|
+
const newVal = [...value].slice(0, -1).join("");
|
|
41
|
+
onChange?.(newVal);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
119
44
|
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
valueRef.current = newVal;
|
|
125
|
-
onChange?.(newVal);
|
|
126
|
-
}
|
|
45
|
+
if (key.leftArrow || key.rightArrow) {
|
|
46
|
+
// Cursor movement — skip for now (append-only input)
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
127
49
|
|
|
128
|
-
|
|
50
|
+
// Ctrl+U — clear line
|
|
51
|
+
if (key.ctrl && input === "u") {
|
|
52
|
+
onChange?.("");
|
|
53
|
+
return;
|
|
129
54
|
}
|
|
130
|
-
};
|
|
131
55
|
|
|
132
|
-
|
|
56
|
+
// Ctrl+W — delete last word
|
|
57
|
+
if (key.ctrl && input === "w") {
|
|
58
|
+
const trimmed = value.trimEnd();
|
|
59
|
+
const lastSpace = trimmed.lastIndexOf(" ");
|
|
60
|
+
onChange?.(lastSpace >= 0 ? trimmed.slice(0, lastSpace + 1) : "");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
133
63
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
stdin.setEncoding(prevEncoding);
|
|
64
|
+
// Normal character input — this includes CJK characters
|
|
65
|
+
// Ink's useInput already decodes UTF-8, so 'input' is a proper string
|
|
66
|
+
if (input && input.length > 0) {
|
|
67
|
+
onChange?.(value + input);
|
|
139
68
|
}
|
|
140
|
-
}
|
|
141
|
-
|
|
69
|
+
},
|
|
70
|
+
{ isActive: focus }
|
|
71
|
+
);
|
|
142
72
|
|
|
143
73
|
// Render
|
|
144
74
|
const displayValue = value;
|
|
145
75
|
const showPlaceholder = !displayValue && placeholder;
|
|
146
76
|
|
|
147
77
|
if (showPlaceholder) {
|
|
78
|
+
if (showCursor && focus) {
|
|
79
|
+
return React.createElement(
|
|
80
|
+
Box,
|
|
81
|
+
{},
|
|
82
|
+
React.createElement(Text, { inverse: true }, placeholder[0] || " "),
|
|
83
|
+
React.createElement(Text, { dimColor: true }, placeholder.slice(1))
|
|
84
|
+
);
|
|
85
|
+
}
|
|
148
86
|
return React.createElement(Text, { dimColor: true }, placeholder);
|
|
149
87
|
}
|
|
150
88
|
|
|
@@ -153,7 +91,7 @@ function CJKTextInput({
|
|
|
153
91
|
Box,
|
|
154
92
|
{},
|
|
155
93
|
React.createElement(Text, {}, displayValue),
|
|
156
|
-
React.createElement(Text, { inverse: true }, " ")
|
|
94
|
+
React.createElement(Text, { inverse: true }, " ")
|
|
157
95
|
);
|
|
158
96
|
}
|
|
159
97
|
|
package/package.json
CHANGED