@flowtty/core 1.0.0-alpha.1 → 1.0.0-alpha.10

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/index.js CHANGED
@@ -1,382 +1,721 @@
1
- export { Buffer, DEFAULT_BORDER_STYLE, GRID_CHARS, wrapText } from './chunk-D6HW2BNM.js';
2
-
3
- // src/visualLines.ts
1
+ import { a as Buffer, i as GRID_CHARS, r as DEFAULT_BORDER_STYLE, t as wrapText } from "./wrap-D5f0P1iA.js";
2
+ import { t as NAMED_KEYS } from "./keys-Ck-RALk_.js";
3
+ //#region src/visualLines.ts
4
4
  function splitVisualLines(text, mode, width) {
5
- const sources = text.split("\n");
6
- if (mode === "nowrap" || width <= 0) {
7
- return sources.map((line, i) => ({ text: line, lineNum: i + 1 }));
8
- }
9
- const out = [];
10
- for (let i = 0; i < sources.length; i++) {
11
- const line = sources[i];
12
- const chars = [...line];
13
- if (chars.length <= width) {
14
- out.push({ text: line, lineNum: i + 1 });
15
- continue;
16
- }
17
- let first = true;
18
- for (let j = 0; j < chars.length; j += width) {
19
- out.push({
20
- text: chars.slice(j, j + width).join(""),
21
- lineNum: first ? i + 1 : null
22
- });
23
- first = false;
24
- }
25
- }
26
- return out;
5
+ const sources = text.split("\n");
6
+ if (mode === "nowrap" || width <= 0) return sources.map((line, i) => ({
7
+ text: line,
8
+ lineNum: i + 1
9
+ }));
10
+ const out = [];
11
+ for (let i = 0; i < sources.length; i++) {
12
+ const line = sources[i];
13
+ const chars = [...line];
14
+ if (chars.length <= width) {
15
+ out.push({
16
+ text: line,
17
+ lineNum: i + 1
18
+ });
19
+ continue;
20
+ }
21
+ let first = true;
22
+ for (let j = 0; j < chars.length; j += width) {
23
+ out.push({
24
+ text: chars.slice(j, j + width).join(""),
25
+ lineNum: first ? i + 1 : null
26
+ });
27
+ first = false;
28
+ }
29
+ }
30
+ return out;
27
31
  }
28
-
29
- // src/windowAround.ts
32
+ //#endregion
33
+ //#region src/windowAround.ts
34
+ /**
35
+ * Center a `visible`-sized window of items around `cursor`, clamped so the
36
+ * window never starts before 0 or extends past the end of the list. Returns
37
+ * the start index and the sliced items.
38
+ *
39
+ * Typical use — list views that scroll a cursor through long data:
40
+ * ```ts
41
+ * const { start, items } = windowAround(rows, cursor, termHeight - chrome);
42
+ * items.map((row, i) => {
43
+ * const sel = (start + i) === cursor;
44
+ * ...
45
+ * });
46
+ * ```
47
+ *
48
+ * The window prefers to center the cursor; near the edges it sticks to the
49
+ * top/bottom so the cursor remains visible without empty padding.
50
+ */
30
51
  function windowAround(items, cursor, visible) {
31
- const v = Math.max(1, visible);
32
- const start = Math.max(0, Math.min(items.length - v, cursor - Math.floor(v / 2)));
33
- return { start, items: items.slice(start, start + v) };
52
+ const v = Math.max(1, visible);
53
+ const start = Math.max(0, Math.min(items.length - v, cursor - Math.floor(v / 2)));
54
+ return {
55
+ start,
56
+ items: items.slice(start, start + v)
57
+ };
34
58
  }
35
-
36
- // src/displayWidth.ts
37
- var ZERO_WIDTH = [
38
- [768, 879],
39
- [1155, 1158],
40
- [1160, 1161],
41
- [1425, 1469],
42
- [1471, 1471],
43
- [1473, 1474],
44
- [1476, 1477],
45
- [1479, 1479],
46
- [1536, 1539],
47
- [1552, 1557],
48
- [1611, 1630],
49
- [1648, 1648],
50
- [1750, 1764],
51
- [1767, 1768],
52
- [1770, 1773],
53
- [1807, 1807],
54
- [1809, 1809],
55
- [1840, 1866],
56
- [1958, 1968],
57
- [2027, 2035],
58
- [2305, 2306],
59
- [2364, 2364],
60
- [2369, 2376],
61
- [2381, 2381],
62
- [2385, 2388],
63
- [2402, 2403],
64
- [2433, 2433],
65
- [2492, 2492],
66
- [2497, 2500],
67
- [2509, 2509],
68
- [2530, 2531],
69
- [2561, 2562],
70
- [2620, 2620],
71
- [2625, 2626],
72
- [2631, 2632],
73
- [2635, 2637],
74
- [2672, 2673],
75
- [2689, 2690],
76
- [2748, 2748],
77
- [2753, 2757],
78
- [2759, 2760],
79
- [2765, 2765],
80
- [2786, 2787],
81
- [2817, 2817],
82
- [2876, 2876],
83
- [2879, 2879],
84
- [2881, 2883],
85
- [2893, 2893],
86
- [2902, 2902],
87
- [2946, 2946],
88
- [3008, 3008],
89
- [3021, 3021],
90
- [3134, 3136],
91
- [3142, 3144],
92
- [3146, 3149],
93
- [3157, 3158],
94
- [3260, 3260],
95
- [3263, 3263],
96
- [3270, 3270],
97
- [3276, 3277],
98
- [3298, 3299],
99
- [3393, 3395],
100
- [3405, 3405],
101
- [3530, 3530],
102
- [3538, 3540],
103
- [3542, 3542],
104
- [3633, 3633],
105
- [3636, 3642],
106
- [3655, 3662],
107
- [3761, 3761],
108
- [3764, 3769],
109
- [3771, 3772],
110
- [3784, 3789],
111
- [3864, 3865],
112
- [3893, 3893],
113
- [3895, 3895],
114
- [3897, 3897],
115
- [3953, 3966],
116
- [3968, 3972],
117
- [3974, 3975],
118
- [3984, 3991],
119
- [3993, 4028],
120
- [4038, 4038],
121
- [4141, 4144],
122
- [4146, 4146],
123
- [4150, 4151],
124
- [4153, 4153],
125
- [4184, 4185],
126
- [4448, 4607],
127
- [4959, 4959],
128
- [5906, 5908],
129
- [5938, 5940],
130
- [5970, 5971],
131
- [6002, 6003],
132
- [6068, 6069],
133
- [6071, 6077],
134
- [6086, 6086],
135
- [6089, 6099],
136
- [6109, 6109],
137
- [6155, 6157],
138
- [6313, 6313],
139
- [6432, 6434],
140
- [6439, 6440],
141
- [6450, 6450],
142
- [6457, 6459],
143
- [6679, 6680],
144
- [6912, 6915],
145
- [6964, 6964],
146
- [6966, 6970],
147
- [6972, 6972],
148
- [6978, 6978],
149
- [7019, 7027],
150
- [7616, 7626],
151
- [7678, 7679],
152
- [8203, 8207],
153
- [8234, 8238],
154
- [8288, 8291],
155
- [8298, 8303],
156
- [8400, 8431],
157
- [12330, 12335],
158
- [12441, 12442],
159
- [43014, 43014],
160
- [43019, 43019],
161
- [43045, 43046],
162
- [64286, 64286],
163
- [65024, 65039],
164
- [65056, 65059],
165
- [65279, 65279],
166
- [65529, 65531],
167
- [68097, 68099],
168
- [68101, 68102],
169
- [68108, 68111],
170
- [68152, 68154],
171
- [68159, 68159],
172
- [119143, 119145],
173
- [119155, 119170],
174
- [119173, 119179],
175
- [119210, 119213],
176
- [119362, 119364],
177
- [917505, 917505],
178
- [917536, 917631],
179
- [917760, 917999]
59
+ //#endregion
60
+ //#region src/displayWidth.ts
61
+ const ZERO_WIDTH = [
62
+ [768, 879],
63
+ [1155, 1158],
64
+ [1160, 1161],
65
+ [1425, 1469],
66
+ [1471, 1471],
67
+ [1473, 1474],
68
+ [1476, 1477],
69
+ [1479, 1479],
70
+ [1536, 1539],
71
+ [1552, 1557],
72
+ [1611, 1630],
73
+ [1648, 1648],
74
+ [1750, 1764],
75
+ [1767, 1768],
76
+ [1770, 1773],
77
+ [1807, 1807],
78
+ [1809, 1809],
79
+ [1840, 1866],
80
+ [1958, 1968],
81
+ [2027, 2035],
82
+ [2305, 2306],
83
+ [2364, 2364],
84
+ [2369, 2376],
85
+ [2381, 2381],
86
+ [2385, 2388],
87
+ [2402, 2403],
88
+ [2433, 2433],
89
+ [2492, 2492],
90
+ [2497, 2500],
91
+ [2509, 2509],
92
+ [2530, 2531],
93
+ [2561, 2562],
94
+ [2620, 2620],
95
+ [2625, 2626],
96
+ [2631, 2632],
97
+ [2635, 2637],
98
+ [2672, 2673],
99
+ [2689, 2690],
100
+ [2748, 2748],
101
+ [2753, 2757],
102
+ [2759, 2760],
103
+ [2765, 2765],
104
+ [2786, 2787],
105
+ [2817, 2817],
106
+ [2876, 2876],
107
+ [2879, 2879],
108
+ [2881, 2883],
109
+ [2893, 2893],
110
+ [2902, 2902],
111
+ [2946, 2946],
112
+ [3008, 3008],
113
+ [3021, 3021],
114
+ [3134, 3136],
115
+ [3142, 3144],
116
+ [3146, 3149],
117
+ [3157, 3158],
118
+ [3260, 3260],
119
+ [3263, 3263],
120
+ [3270, 3270],
121
+ [3276, 3277],
122
+ [3298, 3299],
123
+ [3393, 3395],
124
+ [3405, 3405],
125
+ [3530, 3530],
126
+ [3538, 3540],
127
+ [3542, 3542],
128
+ [3633, 3633],
129
+ [3636, 3642],
130
+ [3655, 3662],
131
+ [3761, 3761],
132
+ [3764, 3769],
133
+ [3771, 3772],
134
+ [3784, 3789],
135
+ [3864, 3865],
136
+ [3893, 3893],
137
+ [3895, 3895],
138
+ [3897, 3897],
139
+ [3953, 3966],
140
+ [3968, 3972],
141
+ [3974, 3975],
142
+ [3984, 3991],
143
+ [3993, 4028],
144
+ [4038, 4038],
145
+ [4141, 4144],
146
+ [4146, 4146],
147
+ [4150, 4151],
148
+ [4153, 4153],
149
+ [4184, 4185],
150
+ [4448, 4607],
151
+ [4959, 4959],
152
+ [5906, 5908],
153
+ [5938, 5940],
154
+ [5970, 5971],
155
+ [6002, 6003],
156
+ [6068, 6069],
157
+ [6071, 6077],
158
+ [6086, 6086],
159
+ [6089, 6099],
160
+ [6109, 6109],
161
+ [6155, 6157],
162
+ [6313, 6313],
163
+ [6432, 6434],
164
+ [6439, 6440],
165
+ [6450, 6450],
166
+ [6457, 6459],
167
+ [6679, 6680],
168
+ [6912, 6915],
169
+ [6964, 6964],
170
+ [6966, 6970],
171
+ [6972, 6972],
172
+ [6978, 6978],
173
+ [7019, 7027],
174
+ [7616, 7626],
175
+ [7678, 7679],
176
+ [8203, 8207],
177
+ [8234, 8238],
178
+ [8288, 8291],
179
+ [8298, 8303],
180
+ [8400, 8431],
181
+ [12330, 12335],
182
+ [12441, 12442],
183
+ [43014, 43014],
184
+ [43019, 43019],
185
+ [43045, 43046],
186
+ [64286, 64286],
187
+ [65024, 65039],
188
+ [65056, 65059],
189
+ [65279, 65279],
190
+ [65529, 65531],
191
+ [68097, 68099],
192
+ [68101, 68102],
193
+ [68108, 68111],
194
+ [68152, 68154],
195
+ [68159, 68159],
196
+ [119143, 119145],
197
+ [119155, 119170],
198
+ [119173, 119179],
199
+ [119210, 119213],
200
+ [119362, 119364],
201
+ [917505, 917505],
202
+ [917536, 917631],
203
+ [917760, 917999]
180
204
  ];
181
- var WIDE = [
182
- [4352, 4447],
183
- // Hangul Jamo (leading consonants)
184
- [9001, 9002],
185
- // angle brackets
186
- [11904, 12350],
187
- // CJK Radicals … Kangxi … CJK Symbols (stops before U+303F)
188
- [12353, 13311],
189
- // Hiragana … Katakana … CJK compatibility
190
- [13312, 19903],
191
- // CJK Unified Ideographs Extension A
192
- [19968, 40959],
193
- // CJK Unified Ideographs
194
- [40960, 42191],
195
- // Yi Syllables / Radicals
196
- [43360, 43391],
197
- // Hangul Jamo Extended-A
198
- [44032, 55203],
199
- // Hangul Syllables
200
- [63744, 64255],
201
- // CJK Compatibility Ideographs
202
- [65040, 65049],
203
- // Vertical Forms
204
- [65072, 65135],
205
- // CJK Compatibility Forms / Small Form Variants
206
- [65280, 65376],
207
- // Fullwidth Forms (stops before halfwidth katakana U+FF61)
208
- [65504, 65510],
209
- // Fullwidth signs
210
- [110592, 110593],
211
- // Kana Supplement
212
- [127488, 127569],
213
- // Enclosed Ideographic Supplement
214
- [127744, 128591],
215
- // Misc Symbols & Pictographs + Emoticons
216
- [129280, 129535],
217
- // Supplemental Symbols and Pictographs
218
- [131072, 262141]
219
- // CJK Unified Ideographs Extension B and beyond
205
+ const WIDE = [
206
+ [4352, 4447],
207
+ [8986, 8987],
208
+ [9001, 9002],
209
+ [9193, 9196],
210
+ [9200, 9200],
211
+ [9203, 9203],
212
+ [9725, 9726],
213
+ [9748, 9749],
214
+ [9800, 9811],
215
+ [9855, 9855],
216
+ [9875, 9875],
217
+ [9889, 9889],
218
+ [9898, 9899],
219
+ [9917, 9918],
220
+ [9924, 9925],
221
+ [9934, 9934],
222
+ [9940, 9940],
223
+ [9962, 9962],
224
+ [9970, 9971],
225
+ [9973, 9973],
226
+ [9978, 9978],
227
+ [9981, 9981],
228
+ [9989, 9989],
229
+ [9994, 9995],
230
+ [10024, 10024],
231
+ [10060, 10060],
232
+ [10062, 10062],
233
+ [10067, 10069],
234
+ [10071, 10071],
235
+ [10133, 10135],
236
+ [10160, 10160],
237
+ [10175, 10175],
238
+ [11035, 11036],
239
+ [11088, 11088],
240
+ [11093, 11093],
241
+ [11904, 12350],
242
+ [12353, 13311],
243
+ [13312, 19903],
244
+ [19968, 40959],
245
+ [40960, 42191],
246
+ [43360, 43391],
247
+ [44032, 55203],
248
+ [63744, 64255],
249
+ [65040, 65049],
250
+ [65072, 65135],
251
+ [65280, 65376],
252
+ [65504, 65510],
253
+ [110592, 110593],
254
+ [127488, 127569],
255
+ [127744, 128591],
256
+ [128640, 128767],
257
+ [129280, 129535],
258
+ [129648, 129791],
259
+ [131072, 262141]
220
260
  ];
221
261
  function inRanges(cp, table) {
222
- let lo = 0;
223
- let hi = table.length - 1;
224
- while (lo <= hi) {
225
- const mid = lo + hi >> 1;
226
- const [start, end] = table[mid];
227
- if (cp < start) hi = mid - 1;
228
- else if (cp > end) lo = mid + 1;
229
- else return true;
230
- }
231
- return false;
262
+ let lo = 0;
263
+ let hi = table.length - 1;
264
+ while (lo <= hi) {
265
+ const mid = lo + hi >> 1;
266
+ const [start, end] = table[mid];
267
+ if (cp < start) hi = mid - 1;
268
+ else if (cp > end) lo = mid + 1;
269
+ else return true;
270
+ }
271
+ return false;
232
272
  }
273
+ /**
274
+ * Display width in terminal cells of a single Unicode code point: 0 for control
275
+ * / combining / zero-width, 2 for East Asian Wide & Fullwidth (and most emoji),
276
+ * 1 otherwise. Pass a code point (e.g. from `String#codePointAt`), not a string.
277
+ */
233
278
  function charWidth(cp) {
234
- if (cp < 32 || cp >= 127 && cp < 160) return 0;
235
- if (inRanges(cp, ZERO_WIDTH)) return 0;
236
- if (inRanges(cp, WIDE)) return 2;
237
- return 1;
279
+ if (cp < 32 || cp >= 127 && cp < 160) return 0;
280
+ if (inRanges(cp, ZERO_WIDTH)) return 0;
281
+ if (inRanges(cp, WIDE)) return 2;
282
+ return 1;
238
283
  }
284
+ /**
285
+ * Display width in terminal cells of a string, summing `charWidth` over its
286
+ * code points (surrogate pairs counted once). See the file header for the
287
+ * grapheme-cluster caveat. Expects plain text, not ANSI-styled.
288
+ */
239
289
  function stringWidth(str) {
240
- let w = 0;
241
- for (const ch of str) w += charWidth(ch.codePointAt(0));
242
- return w;
290
+ let w = 0;
291
+ for (const ch of str) w += charWidth(ch.codePointAt(0));
292
+ return w;
243
293
  }
244
-
245
- // src/editor.ts
246
- var OPT_MAP = {
247
- " ": ["\xA0"],
248
- // U+00A0 NBSP
249
- "-": ["\u2013", "\u2014"],
250
- // en-dash U+2013, em-dash U+2014
251
- "[": ["\u201C", "\u201D"],
252
- // left/right double quote
253
- "]": ["\u2018", "\u2019"]
254
- // left/right single quote
294
+ //#endregion
295
+ //#region src/colors.ts
296
+ const NAMED_COLORS = [
297
+ "black",
298
+ "red",
299
+ "green",
300
+ "yellow",
301
+ "blue",
302
+ "magenta",
303
+ "cyan",
304
+ "white",
305
+ "gray",
306
+ "grey",
307
+ "blackBright",
308
+ "redBright",
309
+ "greenBright",
310
+ "yellowBright",
311
+ "blueBright",
312
+ "magentaBright",
313
+ "cyanBright",
314
+ "whiteBright"
315
+ ];
316
+ //#endregion
317
+ //#region src/inputRows.ts
318
+ function inputRows(value, width, cursor) {
319
+ const w = Math.max(1, Math.floor(width));
320
+ const rows = [];
321
+ let lineStart = 0;
322
+ for (const line of value.split("\n")) {
323
+ const chars = [...line];
324
+ if (chars.length === 0) rows.push({
325
+ text: "",
326
+ start: lineStart,
327
+ continuation: false
328
+ });
329
+ else {
330
+ let start = lineStart;
331
+ for (let at = 0; at < chars.length; at += w) {
332
+ const text = chars.slice(at, at + w).join("");
333
+ rows.push({
334
+ text,
335
+ start,
336
+ continuation: at > 0
337
+ });
338
+ start += text.length;
339
+ }
340
+ if (cursor === lineStart + line.length && chars.length % w === 0) rows.push({
341
+ text: "",
342
+ start: cursor,
343
+ continuation: true
344
+ });
345
+ }
346
+ lineStart += line.length + 1;
347
+ }
348
+ return rows;
349
+ }
350
+ /** UTF-16 index of character column `col` within `row` (clamped to its end). */
351
+ function rowIndexAt(row, col) {
352
+ return row.start + [...row.text].slice(0, Math.max(0, col)).join("").length;
353
+ }
354
+ function caretPosition(value, cursor, width) {
355
+ const c = Math.max(0, Math.min(value.length, cursor));
356
+ const rows = inputRows(value, width, c);
357
+ for (let r = rows.length - 1; r >= 0; r--) {
358
+ const row = rows[r];
359
+ if (row.start <= c && c <= row.start + row.text.length) return {
360
+ row: r,
361
+ col: [...value.slice(row.start, c)].length
362
+ };
363
+ }
364
+ return {
365
+ row: 0,
366
+ col: 0
367
+ };
368
+ }
369
+ //#endregion
370
+ //#region src/editor.ts
371
+ const isHigh = (u) => u !== void 0 && u >= "\ud800" && u <= "\udbff";
372
+ const isLow = (u) => u !== void 0 && u >= "\udc00" && u <= "\udfff";
373
+ const prevIndex = (v, i) => i >= 2 && isLow(v[i - 1]) && isHigh(v[i - 2]) ? i - 2 : Math.max(0, i - 1);
374
+ const nextIndex = (v, i) => isHigh(v[i]) && isLow(v[i + 1]) ? i + 2 : Math.min(v.length, i + 1);
375
+ const OPT_MAP = {
376
+ " ": ["\xA0"],
377
+ "-": ["–", "—"],
378
+ "[": ["“", "”"],
379
+ "]": ["‘", "’"]
255
380
  };
256
- var isWord = (c) => /[\p{L}\p{N}_]/u.test(c);
381
+ const isWord = (c) => /[\p{L}\p{N}_]/u.test(c);
257
382
  function wordLeft(value, cursor) {
258
- let c = cursor;
259
- while (c > 0 && !isWord(value[c - 1])) c--;
260
- while (c > 0 && isWord(value[c - 1])) c--;
261
- return c;
383
+ let c = cursor;
384
+ while (c > 0 && !isWord(value[c - 1])) c--;
385
+ while (c > 0 && isWord(value[c - 1])) c--;
386
+ return c;
262
387
  }
263
388
  function wordRight(value, cursor) {
264
- let c = cursor;
265
- while (c < value.length && !isWord(value[c])) c++;
266
- while (c < value.length && isWord(value[c])) c++;
267
- return c;
389
+ let c = cursor;
390
+ while (c < value.length && !isWord(value[c])) c++;
391
+ while (c < value.length && isWord(value[c])) c++;
392
+ return c;
268
393
  }
269
- function reduce(state, key) {
270
- const { value, cursor } = state;
271
- const clamp = (n) => Math.max(0, Math.min(value.length, n));
272
- if (key.name === "left" && key.meta) return { kind: "edit", state: { value, cursor: wordLeft(value, cursor) } };
273
- if (key.name === "b" && key.meta) return { kind: "edit", state: { value, cursor: wordLeft(value, cursor) } };
274
- if (key.name === "right" && key.meta) return { kind: "edit", state: { value, cursor: wordRight(value, cursor) } };
275
- if (key.name === "f" && key.meta) return { kind: "edit", state: { value, cursor: wordRight(value, cursor) } };
276
- if (key.name === "left") return { kind: "edit", state: { value, cursor: clamp(cursor - 1) } };
277
- if (key.name === "right") return { kind: "edit", state: { value, cursor: clamp(cursor + 1) } };
278
- if (key.name === "home") return { kind: "edit", state: { value, cursor: 0 } };
279
- if (key.name === "end") return { kind: "edit", state: { value, cursor: value.length } };
280
- if (key.name === "a" && key.ctrl) return { kind: "edit", state: { value, cursor: 0 } };
281
- if (key.name === "e" && key.ctrl) return { kind: "edit", state: { value, cursor: value.length } };
282
- if (key.name === "backspace" && key.meta) {
283
- const start = wordLeft(value, cursor);
284
- return { kind: "edit", state: { value: value.slice(0, start) + value.slice(cursor), cursor: start } };
285
- }
286
- if (key.name === "w" && key.ctrl) {
287
- const start = wordLeft(value, cursor);
288
- return { kind: "edit", state: { value: value.slice(0, start) + value.slice(cursor), cursor: start } };
289
- }
290
- if (key.name === "d" && key.meta) {
291
- const end = wordRight(value, cursor);
292
- return { kind: "edit", state: { value: value.slice(0, cursor) + value.slice(end), cursor } };
293
- }
294
- if (key.name === "delete" && key.meta) {
295
- const end = wordRight(value, cursor);
296
- return { kind: "edit", state: { value: value.slice(0, cursor) + value.slice(end), cursor } };
297
- }
298
- if (key.name === "k" && key.ctrl) {
299
- return { kind: "edit", state: { value: value.slice(0, cursor), cursor } };
300
- }
301
- if (key.name === "u" && key.ctrl) {
302
- return { kind: "edit", state: { value: value.slice(cursor), cursor: 0 } };
303
- }
304
- if (key.meta && OPT_MAP[key.name]) {
305
- const entry = OPT_MAP[key.name];
306
- const ch = key.shift && entry[1] !== void 0 ? entry[1] : entry[0];
307
- return { kind: "edit", state: { value: value.slice(0, cursor) + ch + value.slice(cursor), cursor: cursor + 1 } };
308
- }
309
- if (key.name === "backspace") {
310
- if (cursor === 0) return { kind: "edit", state };
311
- return { kind: "edit", state: { value: value.slice(0, cursor - 1) + value.slice(cursor), cursor: cursor - 1 } };
312
- }
313
- if (key.name === "delete" || key.name === "d" && key.ctrl) {
314
- if (cursor === value.length) return { kind: "edit", state };
315
- return { kind: "edit", state: { value: value.slice(0, cursor) + value.slice(cursor + 1), cursor } };
316
- }
317
- if (key.name === "return") return { kind: "submit" };
318
- if (key.name === "escape") return { kind: "cancel" };
319
- if (!key.ctrl && !key.meta && key.name.length === 1) {
320
- const ch = key.name;
321
- return { kind: "edit", state: { value: value.slice(0, cursor) + ch + value.slice(cursor), cursor: cursor + 1 } };
322
- }
323
- return { kind: "noop" };
394
+ /**
395
+ * One key against an editor state. Contract hosts rely on: a key the editor has
396
+ * no meaning for (Tab, Ctrl+R, PgUp, the wheel, an unrecognized sequence …)
397
+ * returns `{ kind: 'noop' }` never an edit so a host can handle its own keys
398
+ * first and pass everything else through.
399
+ */
400
+ function reduce(state, key, opts = {}) {
401
+ const { value } = state;
402
+ let cursor = Math.max(0, Math.min(value.length, state.cursor));
403
+ if (isLow(value[cursor]) && isHigh(value[cursor - 1])) cursor--;
404
+ const multiline = opts.multiline === true;
405
+ const lineStart = multiline ? value.lastIndexOf("\n", cursor - 1) + 1 : 0;
406
+ const nextBreak = multiline ? value.indexOf("\n", cursor) : -1;
407
+ const lineEnd = nextBreak < 0 ? value.length : nextBreak;
408
+ const insert = (text) => ({
409
+ kind: "edit",
410
+ state: {
411
+ value: value.slice(0, cursor) + text + value.slice(cursor),
412
+ cursor: cursor + text.length
413
+ }
414
+ });
415
+ if (key.name === "paste") {
416
+ const pasted = (key.text ?? "").replace(/\r\n?/g, "\n");
417
+ const text = multiline ? pasted : pasted.replace(/\n/g, " ");
418
+ if (text === "") return { kind: "noop" };
419
+ return insert(text);
420
+ }
421
+ if (multiline) {
422
+ if (key.name === "return" && (key.shift || key.meta)) return insert("\n");
423
+ if (key.name === "return" && cursor > 0 && value[cursor - 1] === "\\") return {
424
+ kind: "edit",
425
+ state: {
426
+ value: value.slice(0, cursor - 1) + "\n" + value.slice(cursor),
427
+ cursor
428
+ }
429
+ };
430
+ if (key.name === "up" || key.name === "down") {
431
+ const width = opts.width ?? Number.MAX_SAFE_INTEGER;
432
+ const rows = inputRows(value, width, cursor);
433
+ const at = caretPosition(value, cursor, width);
434
+ const target = at.row + (key.name === "up" ? -1 : 1);
435
+ if (target < 0) return {
436
+ kind: "edit",
437
+ state: {
438
+ value,
439
+ cursor: 0
440
+ }
441
+ };
442
+ if (target >= rows.length) return {
443
+ kind: "edit",
444
+ state: {
445
+ value,
446
+ cursor: value.length
447
+ }
448
+ };
449
+ const row = rows[target];
450
+ return {
451
+ kind: "edit",
452
+ state: {
453
+ value,
454
+ cursor: rowIndexAt(row, at.col)
455
+ }
456
+ };
457
+ }
458
+ }
459
+ if (key.name === "left" && key.meta) return {
460
+ kind: "edit",
461
+ state: {
462
+ value,
463
+ cursor: wordLeft(value, cursor)
464
+ }
465
+ };
466
+ if (key.name === "b" && key.meta) return {
467
+ kind: "edit",
468
+ state: {
469
+ value,
470
+ cursor: wordLeft(value, cursor)
471
+ }
472
+ };
473
+ if (key.name === "right" && key.meta) return {
474
+ kind: "edit",
475
+ state: {
476
+ value,
477
+ cursor: wordRight(value, cursor)
478
+ }
479
+ };
480
+ if (key.name === "f" && key.meta) return {
481
+ kind: "edit",
482
+ state: {
483
+ value,
484
+ cursor: wordRight(value, cursor)
485
+ }
486
+ };
487
+ if (key.name === "left") return {
488
+ kind: "edit",
489
+ state: {
490
+ value,
491
+ cursor: prevIndex(value, cursor)
492
+ }
493
+ };
494
+ if (key.name === "right") return {
495
+ kind: "edit",
496
+ state: {
497
+ value,
498
+ cursor: nextIndex(value, cursor)
499
+ }
500
+ };
501
+ if (key.name === "home") return {
502
+ kind: "edit",
503
+ state: {
504
+ value,
505
+ cursor: lineStart
506
+ }
507
+ };
508
+ if (key.name === "end") return {
509
+ kind: "edit",
510
+ state: {
511
+ value,
512
+ cursor: lineEnd
513
+ }
514
+ };
515
+ if (key.name === "a" && key.ctrl) return {
516
+ kind: "edit",
517
+ state: {
518
+ value,
519
+ cursor: lineStart
520
+ }
521
+ };
522
+ if (key.name === "e" && key.ctrl) return {
523
+ kind: "edit",
524
+ state: {
525
+ value,
526
+ cursor: lineEnd
527
+ }
528
+ };
529
+ if (key.name === "backspace" && key.meta) {
530
+ const start = wordLeft(value, cursor);
531
+ return {
532
+ kind: "edit",
533
+ state: {
534
+ value: value.slice(0, start) + value.slice(cursor),
535
+ cursor: start
536
+ }
537
+ };
538
+ }
539
+ if (key.name === "w" && key.ctrl) {
540
+ const start = wordLeft(value, cursor);
541
+ return {
542
+ kind: "edit",
543
+ state: {
544
+ value: value.slice(0, start) + value.slice(cursor),
545
+ cursor: start
546
+ }
547
+ };
548
+ }
549
+ if (key.name === "d" && key.meta) {
550
+ const end = wordRight(value, cursor);
551
+ return {
552
+ kind: "edit",
553
+ state: {
554
+ value: value.slice(0, cursor) + value.slice(end),
555
+ cursor
556
+ }
557
+ };
558
+ }
559
+ if (key.name === "delete" && key.meta) {
560
+ const end = wordRight(value, cursor);
561
+ return {
562
+ kind: "edit",
563
+ state: {
564
+ value: value.slice(0, cursor) + value.slice(end),
565
+ cursor
566
+ }
567
+ };
568
+ }
569
+ if (key.name === "k" && key.ctrl) return {
570
+ kind: "edit",
571
+ state: {
572
+ value: value.slice(0, cursor) + value.slice(lineEnd),
573
+ cursor
574
+ }
575
+ };
576
+ if (key.name === "u" && key.ctrl) return {
577
+ kind: "edit",
578
+ state: {
579
+ value: value.slice(0, lineStart) + value.slice(cursor),
580
+ cursor: lineStart
581
+ }
582
+ };
583
+ if (key.meta && OPT_MAP[key.name]) {
584
+ const entry = OPT_MAP[key.name];
585
+ const ch = key.shift && entry[1] !== void 0 ? entry[1] : entry[0];
586
+ return {
587
+ kind: "edit",
588
+ state: {
589
+ value: value.slice(0, cursor) + ch + value.slice(cursor),
590
+ cursor: cursor + 1
591
+ }
592
+ };
593
+ }
594
+ if (key.name === "backspace") {
595
+ if (cursor === 0) return {
596
+ kind: "edit",
597
+ state: {
598
+ value,
599
+ cursor
600
+ }
601
+ };
602
+ const from = prevIndex(value, cursor);
603
+ return {
604
+ kind: "edit",
605
+ state: {
606
+ value: value.slice(0, from) + value.slice(cursor),
607
+ cursor: from
608
+ }
609
+ };
610
+ }
611
+ if (key.name === "delete" || key.name === "d" && key.ctrl) {
612
+ if (cursor === value.length) return {
613
+ kind: "edit",
614
+ state: {
615
+ value,
616
+ cursor
617
+ }
618
+ };
619
+ return {
620
+ kind: "edit",
621
+ state: {
622
+ value: value.slice(0, cursor) + value.slice(nextIndex(value, cursor)),
623
+ cursor
624
+ }
625
+ };
626
+ }
627
+ if (key.name === "return") return { kind: "submit" };
628
+ if (key.name === "escape") return { kind: "cancel" };
629
+ if (!key.ctrl && !key.meta && [...key.name].length === 1) return insert(key.name);
630
+ return { kind: "noop" };
324
631
  }
325
-
326
- // src/selectReducer.ts
632
+ //#endregion
633
+ //#region src/selectReducer.ts
634
+ /**
635
+ * Indices into `items` of items whose label contains `filter` (case-insensitive
636
+ * substring). Empty filter → all indices.
637
+ */
327
638
  function visibleIndices(items, filter) {
328
- if (filter === "") return items.map((_, i) => i);
329
- const q = filter.toLowerCase();
330
- const out = [];
331
- for (let i = 0; i < items.length; i++) {
332
- if (items[i].label.toLowerCase().includes(q)) out.push(i);
333
- }
334
- return out;
639
+ if (filter === "") return items.map((_, i) => i);
640
+ const q = filter.toLowerCase();
641
+ const out = [];
642
+ for (let i = 0; i < items.length; i++) if (items[i].label.toLowerCase().includes(q)) out.push(i);
643
+ return out;
335
644
  }
336
- function reduce2(items, state, key) {
337
- const visible = visibleIndices(items, state.filter);
338
- const n = visible.length;
339
- if (key.name === "escape") return { kind: "cancel" };
340
- if (key.name === "return") {
341
- if (n === 0) return { kind: "noop" };
342
- const cursor = Math.min(state.cursor, n - 1);
343
- return { kind: "submit", index: visible[cursor] };
344
- }
345
- if (key.name === "down" || key.name === "j" && !key.ctrl && !key.meta) {
346
- if (n === 0) return { kind: "noop" };
347
- return { kind: "state", state: { cursor: (state.cursor + 1) % n, filter: state.filter } };
348
- }
349
- if (key.name === "up" || key.name === "k" && !key.ctrl && !key.meta) {
350
- if (n === 0) return { kind: "noop" };
351
- return { kind: "state", state: { cursor: (state.cursor - 1 + n) % n, filter: state.filter } };
352
- }
353
- if (key.name === "backspace") {
354
- if (state.filter === "") return { kind: "noop" };
355
- return { kind: "state", state: { cursor: 0, filter: state.filter.slice(0, -1) } };
356
- }
357
- if (!key.ctrl && !key.meta && key.name.length === 1) {
358
- return { kind: "state", state: { cursor: 0, filter: state.filter + key.name } };
359
- }
360
- return { kind: "noop" };
645
+ function reduce$2(items, state, key) {
646
+ const visible = visibleIndices(items, state.filter);
647
+ const n = visible.length;
648
+ if (key.name === "escape") return { kind: "cancel" };
649
+ if (key.name === "return") {
650
+ if (n === 0) return { kind: "noop" };
651
+ return {
652
+ kind: "submit",
653
+ index: visible[Math.min(state.cursor, n - 1)]
654
+ };
655
+ }
656
+ if (key.name === "down" || key.name === "j" && !key.ctrl && !key.meta) {
657
+ if (n === 0) return { kind: "noop" };
658
+ return {
659
+ kind: "state",
660
+ state: {
661
+ cursor: (state.cursor + 1) % n,
662
+ filter: state.filter
663
+ }
664
+ };
665
+ }
666
+ if (key.name === "up" || key.name === "k" && !key.ctrl && !key.meta) {
667
+ if (n === 0) return { kind: "noop" };
668
+ return {
669
+ kind: "state",
670
+ state: {
671
+ cursor: (state.cursor - 1 + n) % n,
672
+ filter: state.filter
673
+ }
674
+ };
675
+ }
676
+ if (key.name === "backspace") {
677
+ if (state.filter === "") return { kind: "noop" };
678
+ return {
679
+ kind: "state",
680
+ state: {
681
+ cursor: 0,
682
+ filter: state.filter.slice(0, -1)
683
+ }
684
+ };
685
+ }
686
+ if (!key.ctrl && !key.meta && key.name.length === 1) return {
687
+ kind: "state",
688
+ state: {
689
+ cursor: 0,
690
+ filter: state.filter + key.name
691
+ }
692
+ };
693
+ return { kind: "noop" };
361
694
  }
362
-
363
- // src/multiSelectReducer.ts
364
- function reduce3(items, state, key) {
365
- const n = items.length;
366
- if (key.name === "escape") return { kind: "cancel" };
367
- if (key.name === "return") return { kind: "submit" };
368
- if (n === 0) return { kind: "noop" };
369
- if (key.name === "down" || key.name === "j" && !key.ctrl && !key.meta) {
370
- return { kind: "state", state: { cursor: (state.cursor + 1) % n } };
371
- }
372
- if (key.name === "up" || key.name === "k" && !key.ctrl && !key.meta) {
373
- return { kind: "state", state: { cursor: (state.cursor - 1 + n) % n } };
374
- }
375
- if (key.name === " ") {
376
- const index = Math.max(0, Math.min(state.cursor, n - 1));
377
- return { kind: "toggle", index };
378
- }
379
- return { kind: "noop" };
695
+ //#endregion
696
+ //#region src/multiSelectReducer.ts
697
+ function reduce$1(items, state, key, opts = {}) {
698
+ const n = items.length + Math.max(0, opts.extraRows ?? 0);
699
+ if (key.name === "escape") return { kind: "cancel" };
700
+ if (key.name === "return") return { kind: "submit" };
701
+ if (n === 0) return { kind: "noop" };
702
+ if (key.name === "down" || key.name === "j" && !key.ctrl && !key.meta) return {
703
+ kind: "state",
704
+ state: { cursor: (state.cursor + 1) % n }
705
+ };
706
+ if (key.name === "up" || key.name === "k" && !key.ctrl && !key.meta) return {
707
+ kind: "state",
708
+ state: { cursor: (state.cursor - 1 + n) % n }
709
+ };
710
+ if (key.name === " ") {
711
+ const index = Math.max(0, Math.min(state.cursor, n - 1));
712
+ if (index >= items.length) return { kind: "noop" };
713
+ return {
714
+ kind: "toggle",
715
+ index
716
+ };
717
+ }
718
+ return { kind: "noop" };
380
719
  }
381
-
382
- export { charWidth, reduce as editorReducer, reduce3 as multiSelectReducer, reduce2 as selectReducer, splitVisualLines, stringWidth, visibleIndices, windowAround };
720
+ //#endregion
721
+ export { Buffer, DEFAULT_BORDER_STYLE, GRID_CHARS, NAMED_COLORS, NAMED_KEYS, caretPosition, charWidth, reduce as editorReducer, inputRows, reduce$1 as multiSelectReducer, rowIndexAt, reduce$2 as selectReducer, splitVisualLines, stringWidth, visibleIndices, windowAround, wrapText };