zames_pro 2.49.1 → 2.49.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/input.js +83 -18
- package/package.json +1 -1
package/dist/input.js
CHANGED
|
@@ -93,7 +93,12 @@ export function visRows(s, cols) {
|
|
|
93
93
|
const len = visLen(s);
|
|
94
94
|
return Math.max(1, Math.ceil(len / width));
|
|
95
95
|
}
|
|
96
|
-
// Visible length of a line without ANSI sequences.
|
|
96
|
+
// Visible length of a line without ANSI sequences. WIDE characters (emoji,
|
|
97
|
+
// CJK) count as 2 columns — a plain code-point count made the status-line
|
|
98
|
+
// arithmetic (right-align, wrapping, block erase) disagree with the real
|
|
99
|
+
// screen, so a status containing an emoji/CJK could wrap past the edge and
|
|
100
|
+
// make the block erase miss (status "stacking"). Combining marks and the
|
|
101
|
+
// zero-width joiner/space/variation selectors count as 0.
|
|
97
102
|
export function visLen(s) {
|
|
98
103
|
s = String(s);
|
|
99
104
|
let n = 0;
|
|
@@ -109,11 +114,48 @@ export function visLen(s) {
|
|
|
109
114
|
}
|
|
110
115
|
continue;
|
|
111
116
|
}
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
const cp = s.codePointAt(i);
|
|
118
|
+
n += charWidth(cp);
|
|
119
|
+
i += cp > 0xffff ? 2 : 1;
|
|
114
120
|
}
|
|
115
121
|
return n;
|
|
116
122
|
}
|
|
123
|
+
// Display width of one code point in columns (0, 1 or 2). Based on the
|
|
124
|
+
// conventional wcwidth ranges — good enough for terminal layout, and it makes
|
|
125
|
+
// the emoji used in the status line (🧠🌐) count as 2, while math symbols like
|
|
126
|
+
// ⧗ (U+29D7) stay 1, matching a real terminal.
|
|
127
|
+
export function charWidth(cp) {
|
|
128
|
+
// Zero width: combining marks, ZWJ/ZWNJ, variation selectors.
|
|
129
|
+
if (cp === 0x200b ||
|
|
130
|
+
cp === 0x200c ||
|
|
131
|
+
cp === 0x200d ||
|
|
132
|
+
(cp >= 0x0300 && cp <= 0x036f) ||
|
|
133
|
+
(cp >= 0x1ab0 && cp <= 0x1aff) ||
|
|
134
|
+
(cp >= 0x1dc0 && cp <= 0x1dff) ||
|
|
135
|
+
(cp >= 0x20d0 && cp <= 0x20ff) ||
|
|
136
|
+
(cp >= 0xfe00 && cp <= 0xfe0f) ||
|
|
137
|
+
(cp >= 0xfe20 && cp <= 0xfe2f)) {
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
// Wide: CJK, Hangul, fullwidth forms, emoji.
|
|
141
|
+
if ((cp >= 0x1100 && cp <= 0x115f) ||
|
|
142
|
+
(cp >= 0x2e80 && cp <= 0x303e) ||
|
|
143
|
+
(cp >= 0x3041 && cp <= 0x33ff) ||
|
|
144
|
+
(cp >= 0x3400 && cp <= 0x4dbf) ||
|
|
145
|
+
(cp >= 0x4e00 && cp <= 0x9fff) ||
|
|
146
|
+
(cp >= 0xa000 && cp <= 0xa4cf) ||
|
|
147
|
+
(cp >= 0xac00 && cp <= 0xd7a3) ||
|
|
148
|
+
(cp >= 0xf900 && cp <= 0xfaff) ||
|
|
149
|
+
(cp >= 0xfe30 && cp <= 0xfe4f) ||
|
|
150
|
+
(cp >= 0xff00 && cp <= 0xff60) ||
|
|
151
|
+
(cp >= 0xffe0 && cp <= 0xffe6) ||
|
|
152
|
+
(cp >= 0x1f000 && cp <= 0x1f02f) ||
|
|
153
|
+
(cp >= 0x1f300 && cp <= 0x1faff) ||
|
|
154
|
+
(cp >= 0x20000 && cp <= 0x3fffd)) {
|
|
155
|
+
return 2;
|
|
156
|
+
}
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
117
159
|
export function layoutInput(promptStr, buf, cursor, cols) {
|
|
118
160
|
const width = Math.max(20, Number(cols) || 80);
|
|
119
161
|
const promptW = visLen(promptStr);
|
|
@@ -128,21 +170,29 @@ export function layoutInput(promptStr, buf, cursor, cols) {
|
|
|
128
170
|
const avail = Math.max(1, width - promptW);
|
|
129
171
|
const start = i;
|
|
130
172
|
// Build the next visual line. First we take as many
|
|
131
|
-
// characters as fit (
|
|
173
|
+
// characters as fit (columns < avail). If we hit the width
|
|
132
174
|
// and the next character is not the end of the line, we roll back to the
|
|
133
175
|
// last space so we don't split a word in the middle (word-wrap).
|
|
176
|
+
// Wrapping and the cursor column are measured in COLUMNS (charWidth),
|
|
177
|
+
// not code points — otherwise a CJK/emoji input would overflow the row
|
|
178
|
+
// and put the cursor on the wrong cell.
|
|
134
179
|
let text = '';
|
|
135
|
-
let
|
|
136
|
-
let
|
|
137
|
-
|
|
180
|
+
let widthUsed = 0;
|
|
181
|
+
let charIdx = 0;
|
|
182
|
+
let lastSpace = -1; // char index of the space within text
|
|
183
|
+
while (i < chars.length && chars[i] !== NL) {
|
|
184
|
+
const w = charWidth(chars[i].codePointAt(0));
|
|
185
|
+
if (widthUsed + w > avail)
|
|
186
|
+
break;
|
|
138
187
|
if (chars[i] === ' ')
|
|
139
|
-
lastSpace =
|
|
188
|
+
lastSpace = charIdx;
|
|
140
189
|
text += chars[i];
|
|
141
190
|
i++;
|
|
142
|
-
|
|
191
|
+
charIdx++;
|
|
192
|
+
widthUsed += w;
|
|
143
193
|
}
|
|
144
194
|
const atLineEnd = i >= chars.length || chars[i] === NL;
|
|
145
|
-
if (!atLineEnd &&
|
|
195
|
+
if (!atLineEnd && widthUsed >= avail && lastSpace > 0) {
|
|
146
196
|
// Move the "tail" of the line to the next visual line.
|
|
147
197
|
const textChars = Array.from(text);
|
|
148
198
|
const tailLen = textChars.length - lastSpace;
|
|
@@ -162,7 +212,8 @@ export function layoutInput(promptStr, buf, cursor, cols) {
|
|
|
162
212
|
let cursorCol = promptW;
|
|
163
213
|
for (let r = 0; r < rows.length; r++) {
|
|
164
214
|
const row = rows[r];
|
|
165
|
-
const
|
|
215
|
+
const rowChars = Array.from(row.text);
|
|
216
|
+
const len = rowChars.length;
|
|
166
217
|
const from = row.start;
|
|
167
218
|
const to = row.start + len;
|
|
168
219
|
if (cur >= from && cur <= to) {
|
|
@@ -170,7 +221,12 @@ export function layoutInput(promptStr, buf, cursor, cols) {
|
|
|
170
221
|
if (cur === to && next && next.start === to)
|
|
171
222
|
continue;
|
|
172
223
|
cursorRow = r;
|
|
173
|
-
|
|
224
|
+
// Cursor column in COLUMNS (wide chars count as 2).
|
|
225
|
+
let w = 0;
|
|
226
|
+
for (let k = 0; k < cur - from; k++) {
|
|
227
|
+
w += charWidth(rowChars[k].codePointAt(0));
|
|
228
|
+
}
|
|
229
|
+
cursorCol = promptW + w;
|
|
174
230
|
break;
|
|
175
231
|
}
|
|
176
232
|
}
|
|
@@ -683,11 +739,19 @@ export class LineEditor {
|
|
|
683
739
|
// status stays on screen, only the input area is redrawn. This removes the
|
|
684
740
|
// full-screen flicker on terminals like Tabby, where erasing the whole block
|
|
685
741
|
// on every character made the display blink.
|
|
686
|
-
|
|
742
|
+
//
|
|
743
|
+
// `cursorRowInInput` is the cursor's row WITHIN the input area (0-based) as
|
|
744
|
+
// it is currently on screen: we move up exactly that many rows to the input's
|
|
745
|
+
// TOP and clear downward (ESC[J), which also wipes the hint lines below the
|
|
746
|
+
// input. Passing the input's full HEIGHT here (an earlier version did)
|
|
747
|
+
// overshot by `cursorRow` rows whenever the cursor was not on the last visual
|
|
748
|
+
// line, so every keystroke redrew the block one row higher — the input line
|
|
749
|
+
// visibly climbed from the bottom to the top of the screen.
|
|
750
|
+
_eraseInputOnly(cursorRowInInput) {
|
|
687
751
|
if (!this.rendered)
|
|
688
752
|
return;
|
|
689
753
|
const rows = process.stdout.rows || 24;
|
|
690
|
-
const up = Math.min(Math.max(0,
|
|
754
|
+
const up = Math.min(Math.max(0, cursorRowInInput), Math.max(0, rows - 1));
|
|
691
755
|
if (up > 0)
|
|
692
756
|
process.stdout.write(ESC + '[' + up + 'A');
|
|
693
757
|
process.stdout.write(CR + ESC + '[J');
|
|
@@ -777,13 +841,14 @@ export class LineEditor {
|
|
|
777
841
|
if (statusUnchanged) {
|
|
778
842
|
// Count the real input rows via layout (wrapping aware).
|
|
779
843
|
const layOnly = layoutInput(this.promptStr, this.buf, this.cursor, cols);
|
|
780
|
-
const inputLineRows = layOnly.rows.length;
|
|
781
844
|
const suggOnly = this._suggestions();
|
|
782
845
|
const shownOnly = suggOnly.slice(0, 8);
|
|
783
846
|
const linesBelowOnly = shownOnly.length
|
|
784
847
|
? shownOnly.length + (suggOnly.length > shownOnly.length ? 1 : 0)
|
|
785
848
|
: 0;
|
|
786
|
-
|
|
849
|
+
// Move up to the INPUT top using the OLD cursor row (the position that is
|
|
850
|
+
// on screen right now), never the input's height.
|
|
851
|
+
this._eraseInputOnly(this.cursorRowFromTop - this._statusTop);
|
|
787
852
|
let outOnly = '';
|
|
788
853
|
outOnly += layOnly.rows.map((r) => r.prefix + r.text).join(NL);
|
|
789
854
|
if (shownOnly.length) {
|
|
@@ -885,14 +950,14 @@ export class LineEditor {
|
|
|
885
950
|
}
|
|
886
951
|
const cols = process.stdout.columns || 80;
|
|
887
952
|
const lay = layoutInput(this.promptStr, this.buf, this.cursor, cols);
|
|
888
|
-
const inputRows = lay.rows.length;
|
|
889
953
|
const sugg = this._suggestions();
|
|
890
954
|
const shown = sugg.slice(0, 8);
|
|
891
955
|
this._suggestCount = shown.length;
|
|
892
956
|
const linesBelow = shown.length
|
|
893
957
|
? shown.length + (sugg.length > shown.length ? 1 : 0)
|
|
894
958
|
: 0;
|
|
895
|
-
|
|
959
|
+
// Move up to the INPUT top using the OLD cursor row (on screen right now).
|
|
960
|
+
this._eraseInputOnly(this.cursorRowFromTop - this._statusTop);
|
|
896
961
|
let out = lay.rows.map((r) => r.prefix + r.text).join(NL);
|
|
897
962
|
if (shown.length) {
|
|
898
963
|
const maxName = Math.max(...shown.map((c) => c.name.length));
|