@teaui/core 1.10.8 → 1.11.9
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/Buffer.d.ts +5 -0
- package/.dist/Buffer.js +150 -7
- package/.dist/Buffer.js.map +1 -1
- package/.dist/HeadlessProgram.d.ts +24 -0
- package/.dist/HeadlessProgram.js +41 -0
- package/.dist/HeadlessProgram.js.map +1 -0
- package/.dist/{Theme.d.ts → Palette.d.ts} +16 -15
- package/.dist/{Theme.js → Palette.js} +43 -28
- package/.dist/Palette.js.map +1 -0
- package/.dist/Screen.d.ts +43 -24
- package/.dist/Screen.js +102 -90
- package/.dist/Screen.js.map +1 -1
- package/.dist/Style.js +11 -10
- package/.dist/Style.js.map +1 -1
- package/.dist/TestProgram.d.ts +28 -0
- package/.dist/TestProgram.js +52 -0
- package/.dist/TestProgram.js.map +1 -0
- package/.dist/{testing.d.ts → TestScreen.d.ts} +4 -6
- package/.dist/TestScreen.js +99 -0
- package/.dist/TestScreen.js.map +1 -0
- package/.dist/TestTerminal.js +5 -23
- package/.dist/TestTerminal.js.map +1 -1
- package/.dist/UI.d.ts +2 -2
- package/.dist/UI.js +20 -20
- package/.dist/UI.js.map +1 -1
- package/.dist/View.d.ts +12 -7
- package/.dist/View.js +17 -10
- package/.dist/View.js.map +1 -1
- package/.dist/Viewport.d.ts +26 -0
- package/.dist/Viewport.js +57 -7
- package/.dist/Viewport.js.map +1 -1
- package/.dist/components/AbstractLegend.d.ts +30 -0
- package/.dist/components/AbstractLegend.js +195 -0
- package/.dist/components/AbstractLegend.js.map +1 -0
- package/.dist/components/Accordion.js +1 -1
- package/.dist/components/Accordion.js.map +1 -1
- package/.dist/components/Alert.d.ts +1 -1
- package/.dist/components/Alert.js +1 -1
- package/.dist/components/At.d.ts +57 -0
- package/.dist/components/At.js +182 -0
- package/.dist/components/At.js.map +1 -0
- package/.dist/components/AutoLegend.d.ts +21 -0
- package/.dist/components/AutoLegend.js +68 -0
- package/.dist/components/AutoLegend.js.map +1 -0
- package/.dist/components/Badge.d.ts +16 -0
- package/.dist/components/Badge.js +54 -0
- package/.dist/components/Badge.js.map +1 -0
- package/.dist/components/Box.d.ts +1 -1
- package/.dist/components/Box.js +4 -5
- package/.dist/components/Box.js.map +1 -1
- package/.dist/components/Breadcrumb.js +1 -1
- package/.dist/components/Breadcrumb.js.map +1 -1
- package/.dist/components/Button.d.ts +3 -2
- package/.dist/components/Button.js +13 -7
- package/.dist/components/Button.js.map +1 -1
- package/.dist/components/Calendar.d.ts +3 -1
- package/.dist/components/Calendar.js +34 -24
- package/.dist/components/Calendar.js.map +1 -1
- package/.dist/components/Callout.js +16 -16
- package/.dist/components/Callout.js.map +1 -1
- package/.dist/components/Canvas.js +1 -1
- package/.dist/components/Canvas.js.map +1 -1
- package/.dist/components/Checkbox.d.ts +3 -1
- package/.dist/components/Checkbox.js +11 -5
- package/.dist/components/Checkbox.js.map +1 -1
- package/.dist/components/Collapsible.js +1 -1
- package/.dist/components/Collapsible.js.map +1 -1
- package/.dist/components/CollapsibleText.js +1 -1
- package/.dist/components/CollapsibleText.js.map +1 -1
- package/.dist/components/Digits.d.ts +1 -1
- package/.dist/components/Digits.js +1 -1
- package/.dist/components/Digits.js.map +1 -1
- package/.dist/components/Drawer.d.ts +5 -3
- package/.dist/components/Drawer.js +20 -14
- package/.dist/components/Drawer.js.map +1 -1
- package/.dist/components/Dropdown.d.ts +1 -1
- package/.dist/components/Dropdown.js +25 -15
- package/.dist/components/Dropdown.js.map +1 -1
- package/.dist/components/HotKey.d.ts +7 -4
- package/.dist/components/HotKey.js +12 -4
- package/.dist/components/HotKey.js.map +1 -1
- package/.dist/components/Input.d.ts +3 -1
- package/.dist/components/Input.js +369 -45
- package/.dist/components/Input.js.map +1 -1
- package/.dist/components/Legend.d.ts +7 -13
- package/.dist/components/Legend.js +15 -163
- package/.dist/components/Legend.js.map +1 -1
- package/.dist/components/Log.d.ts +1 -0
- package/.dist/components/Log.js +5 -2
- package/.dist/components/Log.js.map +1 -1
- package/.dist/components/Modal.d.ts +2 -1
- package/.dist/components/Modal.js +2 -2
- package/.dist/components/Modal.js.map +1 -1
- package/.dist/components/Notification.d.ts +1 -1
- package/.dist/components/Notification.js +3 -3
- package/.dist/components/Notification.js.map +1 -1
- package/.dist/components/Page.js +5 -14
- package/.dist/components/Page.js.map +1 -1
- package/.dist/components/Pane.js +13 -10
- package/.dist/components/Pane.js.map +1 -1
- package/.dist/components/Plot.js +1 -1
- package/.dist/components/Plot.js.map +1 -1
- package/.dist/components/Progress.js +2 -2
- package/.dist/components/Progress.js.map +1 -1
- package/.dist/components/Scrollable.js +4 -4
- package/.dist/components/Scrollable.js.map +1 -1
- package/.dist/components/ScrollableList.js +4 -4
- package/.dist/components/ScrollableList.js.map +1 -1
- package/.dist/components/Separator.js +1 -1
- package/.dist/components/Separator.js.map +1 -1
- package/.dist/components/Slider.d.ts +3 -2
- package/.dist/components/Slider.js +14 -4
- package/.dist/components/Slider.js.map +1 -1
- package/.dist/components/Space.d.ts +1 -1
- package/.dist/components/Space.js +1 -1
- package/.dist/components/Space.js.map +1 -1
- package/.dist/components/Spinner.d.ts +3 -1
- package/.dist/components/Spinner.js +11 -1
- package/.dist/components/Spinner.js.map +1 -1
- package/.dist/components/Table.d.ts +1 -2
- package/.dist/components/Table.js +5 -5
- package/.dist/components/Table.js.map +1 -1
- package/.dist/components/Tabs.d.ts +3 -2
- package/.dist/components/Tabs.js +42 -3
- package/.dist/components/Tabs.js.map +1 -1
- package/.dist/components/Text.d.ts +1 -1
- package/.dist/components/Text.js +8 -1
- package/.dist/components/Text.js.map +1 -1
- package/.dist/components/Toggle.d.ts +26 -0
- package/.dist/components/Toggle.js +151 -0
- package/.dist/components/Toggle.js.map +1 -0
- package/.dist/components/ToggleGroup.d.ts +1 -1
- package/.dist/components/ToggleGroup.js +22 -4
- package/.dist/components/ToggleGroup.js.map +1 -1
- package/.dist/components/ZStack.js +3 -1
- package/.dist/components/ZStack.js.map +1 -1
- package/.dist/components/charts/BarChart.js +2 -2
- package/.dist/components/charts/BarChart.js.map +1 -1
- package/.dist/components/index.d.ts +6 -2
- package/.dist/components/index.js +6 -2
- package/.dist/components/index.js.map +1 -1
- package/.dist/components/utility/TrackMouse.d.ts +3 -5
- package/.dist/components/utility/TrackMouse.js +1 -2
- package/.dist/components/utility/TrackMouse.js.map +1 -1
- package/.dist/events/key.d.ts +10 -6
- package/.dist/events/key.js +29 -6
- package/.dist/events/key.js.map +1 -1
- package/.dist/events/mouse.d.ts +1 -1
- package/.dist/events/translate.js +6 -6
- package/.dist/events/translate.js.map +1 -1
- package/.dist/iTerm2.d.ts +1 -1
- package/.dist/iTerm2.js +1 -1
- package/.dist/iTerm2.js.map +1 -1
- package/.dist/index.d.ts +6 -5
- package/.dist/index.js +6 -5
- package/.dist/index.js.map +1 -1
- package/.dist/log.d.ts +7 -2
- package/.dist/log.js +10 -3
- package/.dist/log.js.map +1 -1
- package/.dist/managers/FocusManager.d.ts +5 -2
- package/.dist/managers/FocusManager.js +58 -46
- package/.dist/managers/FocusManager.js.map +1 -1
- package/.dist/managers/MouseManager.js +1 -1
- package/.dist/managers/MouseManager.js.map +1 -1
- package/.dist/managers/TickManager.d.ts +1 -1
- package/.dist/managers/TickManager.js +4 -4
- package/.dist/managers/TickManager.js.map +1 -1
- package/.dist/renderToAnsi.d.ts +0 -7
- package/.dist/renderToAnsi.js +7 -36
- package/.dist/renderToAnsi.js.map +1 -1
- package/.dist/terminal.d.ts +1 -0
- package/.dist/types.d.ts +32 -0
- package/.dist/types.js.map +1 -1
- package/package.json +8 -4
- package/.dist/Theme.js.map +0 -1
- package/.dist/inspect.d.ts +0 -14
- package/.dist/inspect.js +0 -172
- package/.dist/inspect.js.map +0 -1
- package/.dist/testing.js +0 -190
- package/.dist/testing.js.map +0 -1
|
@@ -5,6 +5,7 @@ import { Style } from '../Style.js';
|
|
|
5
5
|
import { Point, Size } from '../geometry.js';
|
|
6
6
|
import { FONTS } from './fonts.js';
|
|
7
7
|
const NL_SIGIL = '⤦';
|
|
8
|
+
const TAB_SIGIL = '⭾ ';
|
|
8
9
|
/**
|
|
9
10
|
* Text input. Supports selection, word movement via alt+←→, single and multiline
|
|
10
11
|
* input, and wrapped lines.
|
|
@@ -28,12 +29,21 @@ export class Input extends View {
|
|
|
28
29
|
#wrap = false;
|
|
29
30
|
#multiline = false;
|
|
30
31
|
#font = 'default';
|
|
32
|
+
#format;
|
|
33
|
+
#formatStyles = [];
|
|
31
34
|
#onChange;
|
|
32
35
|
#onSubmit;
|
|
33
36
|
// Printable width
|
|
34
37
|
#maxLineWidth = 0;
|
|
35
38
|
#cursor = { start: 0, end: 0 };
|
|
36
39
|
#visibleWidth = 0;
|
|
40
|
+
// Undo/redo
|
|
41
|
+
#undoStack = [];
|
|
42
|
+
#redoStack = [];
|
|
43
|
+
#pendingUndoKind;
|
|
44
|
+
#preEditChars = [];
|
|
45
|
+
#preEditCursor = { start: 0, end: 0 };
|
|
46
|
+
#insertCoalesceEnabled = true;
|
|
37
47
|
constructor(props = {}) {
|
|
38
48
|
super(props);
|
|
39
49
|
this.#update(props);
|
|
@@ -43,11 +53,12 @@ export class Input extends View {
|
|
|
43
53
|
this.#update(props);
|
|
44
54
|
super.update(props);
|
|
45
55
|
}
|
|
46
|
-
#update({ value, wrap, multiline, font, placeholder, onChange, onSubmit, }) {
|
|
56
|
+
#update({ value, wrap, multiline, font, format, placeholder, onChange, onSubmit, }) {
|
|
47
57
|
this.#onChange = onChange;
|
|
48
58
|
this.#onSubmit = onSubmit;
|
|
49
59
|
this.#wrap = wrap ?? false;
|
|
50
60
|
this.#multiline = multiline ?? false;
|
|
61
|
+
this.#format = format;
|
|
51
62
|
this.#updatePlaceholderLines(placeholder ?? '');
|
|
52
63
|
this.#updateLines(unicode.printableChars(value ?? ''), font ?? 'default');
|
|
53
64
|
}
|
|
@@ -92,10 +103,11 @@ export class Input extends View {
|
|
|
92
103
|
return [lines, line];
|
|
93
104
|
}, [[], []]);
|
|
94
105
|
this.#printableLines = charLines.map((printableLine, index, all) => {
|
|
106
|
+
const displayLine = printableLine;
|
|
95
107
|
// every line needs a ' ' or NL_SIGIL at the end, for the EOL cursor
|
|
96
108
|
return [
|
|
97
|
-
|
|
98
|
-
|
|
109
|
+
displayLine.concat(index === all.length - 1 ? ' ' : NL_SIGIL),
|
|
110
|
+
displayLine.reduce((width, char) => width + unicode.charWidth(char), 0) + 1,
|
|
99
111
|
];
|
|
100
112
|
});
|
|
101
113
|
}
|
|
@@ -122,8 +134,48 @@ export class Input extends View {
|
|
|
122
134
|
// the _printable_ width, not the number of characters
|
|
123
135
|
return Math.max(maxWidth, width);
|
|
124
136
|
}, 0);
|
|
137
|
+
this.#updateFormatStyles();
|
|
125
138
|
this.invalidateSize();
|
|
126
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Run the format function on the current value and parse the ANSI output into
|
|
142
|
+
* a flat Style[] array with one entry per character in #chars.
|
|
143
|
+
*/
|
|
144
|
+
#updateFormatStyles() {
|
|
145
|
+
if (!this.#format || !this.#chars.length) {
|
|
146
|
+
this.#formatStyles = [];
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
let formatted;
|
|
150
|
+
try {
|
|
151
|
+
formatted = this.#format(this.#value);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
this.#formatStyles = [];
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Parse the ANSI output to extract a style per character.
|
|
158
|
+
// Walk through the formatted string, tracking ANSI state.
|
|
159
|
+
const styles = [];
|
|
160
|
+
let currentStyle = Style.NONE;
|
|
161
|
+
for (const token of unicode.printableChars(formatted)) {
|
|
162
|
+
const charWidth = unicode.charWidth(token);
|
|
163
|
+
if (charWidth === 0) {
|
|
164
|
+
// ANSI escape sequence — update current style.
|
|
165
|
+
// Full resets (ESC[0m) clear back to no styling.
|
|
166
|
+
if (RESET_RE.test(token)) {
|
|
167
|
+
currentStyle = Style.NONE;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
currentStyle = currentStyle.merge(Style.fromSGR(token, Style.NONE));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
styles.push(currentStyle);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
this.#formatStyles = styles;
|
|
178
|
+
}
|
|
127
179
|
get value() {
|
|
128
180
|
return this.#value;
|
|
129
181
|
}
|
|
@@ -164,6 +216,15 @@ export class Input extends View {
|
|
|
164
216
|
this.#updateLines(undefined, undefined);
|
|
165
217
|
}
|
|
166
218
|
}
|
|
219
|
+
legendItems() {
|
|
220
|
+
const items = [];
|
|
221
|
+
items.push({ key: ['C-a', 'home'], label: 'Line Start' }, { key: ['C-e', 'end'], label: 'Line End' }, { key: 'A-,', label: 'Start' }, { key: 'A-.', label: 'End' });
|
|
222
|
+
if (this.#multiline) {
|
|
223
|
+
items.push({ key: 'C-]', label: 'Indent' }, { key: 'C-[', label: 'Dedent' });
|
|
224
|
+
}
|
|
225
|
+
items.push({ key: 'C-z', label: 'Undo' }, { key: 'C-S-z', label: 'Redo' });
|
|
226
|
+
return items;
|
|
227
|
+
}
|
|
167
228
|
naturalSize(available) {
|
|
168
229
|
let lines = this.#printableLines;
|
|
169
230
|
if (!lines.length || !available.width) {
|
|
@@ -194,61 +255,111 @@ export class Input extends View {
|
|
|
194
255
|
const prevChars = this.#chars;
|
|
195
256
|
const prevText = this.#value;
|
|
196
257
|
let removeAccent = true;
|
|
197
|
-
if (event.
|
|
258
|
+
if (event.full === 'C-z' || event.full === 'C--') {
|
|
259
|
+
this.#undo();
|
|
260
|
+
}
|
|
261
|
+
else if (event.full === 'C-S-z' || event.full === 'C-S--') {
|
|
262
|
+
this.#redo();
|
|
263
|
+
}
|
|
264
|
+
else if (event.name === 'enter' || event.name === 'return') {
|
|
198
265
|
if (this.#multiline) {
|
|
199
|
-
this.#
|
|
266
|
+
this.#beginEdit('replace');
|
|
267
|
+
if (event.shift || event.alt) {
|
|
268
|
+
// Shift+Enter or Alt+Enter: plain newline without indentation
|
|
269
|
+
this.#receiveChar('\n', true);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
// Enter: newline + preserve current line's indentation
|
|
273
|
+
this.#receiveEnterWithIndent();
|
|
274
|
+
}
|
|
200
275
|
}
|
|
201
276
|
else {
|
|
202
277
|
this.#onSubmit?.(this.#value);
|
|
203
278
|
return;
|
|
204
279
|
}
|
|
205
280
|
}
|
|
206
|
-
else if (event.full === 'C-
|
|
207
|
-
this.#
|
|
281
|
+
else if (event.full === 'C-]') {
|
|
282
|
+
this.#beginEdit('replace');
|
|
283
|
+
this.#receiveIndent();
|
|
284
|
+
}
|
|
285
|
+
else if (event.full === 'C-[') {
|
|
286
|
+
this.#beginEdit('replace');
|
|
287
|
+
this.#receiveDedent();
|
|
208
288
|
}
|
|
209
|
-
else if (event.
|
|
210
|
-
this.#
|
|
289
|
+
else if (event.name === 'tab' && event.alt) {
|
|
290
|
+
this.#beginEdit('insert');
|
|
291
|
+
this.#receiveChar('\t', true);
|
|
292
|
+
}
|
|
293
|
+
else if (event.full === 'C-a' || event.name === 'home') {
|
|
294
|
+
this.#insertCoalesceEnabled = false;
|
|
295
|
+
this.#receiveHome(event);
|
|
296
|
+
}
|
|
297
|
+
else if (event.full === 'C-e' || event.name === 'end') {
|
|
298
|
+
this.#insertCoalesceEnabled = false;
|
|
299
|
+
this.#receiveEnd(event);
|
|
300
|
+
}
|
|
301
|
+
else if (event.full === 'A-,' ||
|
|
302
|
+
event.full === 'A-S-,' ||
|
|
303
|
+
event.full === 'A-S-<') {
|
|
304
|
+
this.#insertCoalesceEnabled = false;
|
|
305
|
+
this.#receiveGotoStart(event);
|
|
306
|
+
}
|
|
307
|
+
else if (event.full === 'A-.' ||
|
|
308
|
+
event.full === 'A-S-.' ||
|
|
309
|
+
event.full === 'A-S->') {
|
|
310
|
+
this.#insertCoalesceEnabled = false;
|
|
311
|
+
this.#receiveGotoEnd(event);
|
|
211
312
|
}
|
|
212
313
|
else if (event.name === 'up') {
|
|
314
|
+
this.#insertCoalesceEnabled = false;
|
|
213
315
|
this.#receiveKeyUpArrow(event);
|
|
214
316
|
}
|
|
215
317
|
else if (event.name === 'down') {
|
|
318
|
+
this.#insertCoalesceEnabled = false;
|
|
216
319
|
this.#receiveKeyDownArrow(event);
|
|
217
320
|
}
|
|
218
|
-
else if (event.name === 'home') {
|
|
219
|
-
this.#receiveHome(event);
|
|
220
|
-
}
|
|
221
|
-
else if (event.name === 'end') {
|
|
222
|
-
this.#receiveEnd(event);
|
|
223
|
-
}
|
|
224
321
|
else if (event.name === 'left') {
|
|
322
|
+
this.#insertCoalesceEnabled = false;
|
|
225
323
|
this.#receiveKeyLeftArrow(event);
|
|
226
324
|
}
|
|
227
325
|
else if (event.name === 'right') {
|
|
326
|
+
this.#insertCoalesceEnabled = false;
|
|
228
327
|
this.#receiveKeyRightArrow(event);
|
|
229
328
|
}
|
|
230
329
|
else if (event.full === 'backspace') {
|
|
330
|
+
this.#beginEdit('delete');
|
|
231
331
|
this.#receiveKeyBackspace();
|
|
232
332
|
}
|
|
233
333
|
else if (event.name === 'delete') {
|
|
334
|
+
this.#beginEdit('delete');
|
|
234
335
|
this.#receiveKeyDelete();
|
|
235
336
|
}
|
|
236
337
|
else if (event.full === 'A-backspace' || event.full === 'C-w') {
|
|
338
|
+
this.#beginEdit('delete');
|
|
237
339
|
this.#receiveKeyDeleteWord();
|
|
238
340
|
}
|
|
239
341
|
else if (isKeyAccent(event)) {
|
|
342
|
+
this.#beginEdit('insert');
|
|
240
343
|
this.#receiveKeyAccent(event);
|
|
241
344
|
removeAccent = false;
|
|
242
345
|
}
|
|
243
|
-
else if (
|
|
346
|
+
else if (!event.ctrl &&
|
|
347
|
+
!event.alt &&
|
|
348
|
+
!event.gui &&
|
|
349
|
+
isKeyPrintable(event)) {
|
|
350
|
+
this.#beginEdit('insert');
|
|
244
351
|
this.#receiveKeyPrintable(event);
|
|
245
352
|
}
|
|
246
353
|
if (removeAccent) {
|
|
247
354
|
this.#chars = this.#chars.filter(char => !isAccentChar(char));
|
|
248
355
|
}
|
|
249
356
|
if (prevChars !== this.#chars) {
|
|
357
|
+
this.#commitEdit();
|
|
250
358
|
this.#updateLines(this.#chars, undefined);
|
|
251
359
|
}
|
|
360
|
+
else {
|
|
361
|
+
this.#pendingUndoKind = undefined;
|
|
362
|
+
}
|
|
252
363
|
if (prevText !== this.#value) {
|
|
253
364
|
this.#onChange?.(this.#value);
|
|
254
365
|
}
|
|
@@ -257,6 +368,7 @@ export class Input extends View {
|
|
|
257
368
|
const pasteChars = unicode.printableChars(this.#multiline ? text : text.replaceAll('\n', ''));
|
|
258
369
|
if (pasteChars.length === 0)
|
|
259
370
|
return;
|
|
371
|
+
this.#beginEdit('replace');
|
|
260
372
|
const prevText = this.#value;
|
|
261
373
|
if (isEmptySelection(this.#cursor)) {
|
|
262
374
|
this.#chars = this.#chars
|
|
@@ -272,6 +384,7 @@ export class Input extends View {
|
|
|
272
384
|
this.#cursor.start = this.#cursor.end =
|
|
273
385
|
this.minSelected() + pasteChars.length;
|
|
274
386
|
}
|
|
387
|
+
this.#commitEdit();
|
|
275
388
|
this.#updateLines(this.#chars, undefined);
|
|
276
389
|
if (prevText !== this.#value) {
|
|
277
390
|
this.#onChange?.(this.#value);
|
|
@@ -337,23 +450,34 @@ export class Input extends View {
|
|
|
337
450
|
}
|
|
338
451
|
let isPlaceholder = !Boolean(this.#chars.length);
|
|
339
452
|
let currentStyle = Style.NONE;
|
|
340
|
-
const plainStyle = this.
|
|
453
|
+
const plainStyle = this.purpose.text({
|
|
341
454
|
isPlaceholder,
|
|
342
455
|
hasFocus,
|
|
343
456
|
});
|
|
344
|
-
const selectedStyle = this.
|
|
457
|
+
const selectedStyle = this.purpose.text({
|
|
345
458
|
isSelected: true,
|
|
346
459
|
hasFocus,
|
|
347
460
|
});
|
|
348
461
|
const cursorStyle = plainStyle.merge({ underline: true });
|
|
349
|
-
const nlStyle = this.
|
|
462
|
+
const nlStyle = this.purpose.text({ isPlaceholder: true });
|
|
350
463
|
const fontMap = this.#font && FONTS[this.#font];
|
|
464
|
+
const hasFormatStyles = this.#formatStyles.length > 0;
|
|
351
465
|
viewport.usingPen(pen => {
|
|
352
466
|
let style = plainStyle;
|
|
353
467
|
const visibleLines = lines.slice(cursorVisible.y);
|
|
354
468
|
if (visibleLines.length === 0) {
|
|
355
469
|
visibleLines.push([[' '], 0]);
|
|
356
470
|
}
|
|
471
|
+
// Compute the character offset into the format styles array at the start
|
|
472
|
+
// of visible lines. Each line's chars (excluding the trailing sigil) map
|
|
473
|
+
// 1:1 to format style entries. Newlines do NOT have entries in the styles
|
|
474
|
+
// array (charWidth('\n') === 0, so they're skipped during parsing).
|
|
475
|
+
let formatOffset = 0;
|
|
476
|
+
if (hasFormatStyles) {
|
|
477
|
+
for (let i = 0; i < cursorVisible.y && i < lines.length; i++) {
|
|
478
|
+
formatOffset += lines[i][0].length - 1;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
357
481
|
// is the viewport tall/wide enough to show ellipses …
|
|
358
482
|
const isTallEnough = viewport.contentSize.height > 4;
|
|
359
483
|
const isWideEnough = viewport.contentSize.width > 9;
|
|
@@ -372,41 +496,52 @@ export class Input extends View {
|
|
|
372
496
|
// set to true if any character is skipped
|
|
373
497
|
let drawInitialEllipses = false;
|
|
374
498
|
scanTextPosition.x = 0;
|
|
499
|
+
let charIndex = 0;
|
|
375
500
|
for (let char of line) {
|
|
376
501
|
char = fontMap?.get(char) ?? char;
|
|
377
502
|
const charWidth = unicode.charWidth(char);
|
|
503
|
+
const isSigil = charIndex === line.length - 1;
|
|
378
504
|
if (scanTextPosition.x >= cursorVisible.x) {
|
|
379
505
|
const inSelection = isInSelection(cursorMin, cursorMax, scanTextPosition);
|
|
380
506
|
const inCursor = scanTextPosition.x === cursorEnd.x &&
|
|
381
507
|
scanTextPosition.y === cursorEnd.y;
|
|
382
508
|
const inNewline = char === NL_SIGIL && scanTextPosition.x + charWidth === width;
|
|
509
|
+
const inTab = isTabSigil(char);
|
|
510
|
+
const isDimSigil = inNewline || inTab;
|
|
511
|
+
// Look up the format style for this character.
|
|
512
|
+
const formatStyle = hasFormatStyles && !isSigil && !inTab
|
|
513
|
+
? this.#formatStyles[formatOffset + charIndex]
|
|
514
|
+
: undefined;
|
|
515
|
+
const baseStyle = formatStyle
|
|
516
|
+
? plainStyle.merge(formatStyle)
|
|
517
|
+
: plainStyle;
|
|
383
518
|
if (isEmptySelection(this.#cursor)) {
|
|
384
519
|
if (isAccentChar(char)) {
|
|
385
|
-
style =
|
|
520
|
+
style = baseStyle.merge({ underline: true, inverse: true });
|
|
386
521
|
}
|
|
387
522
|
else if (hasFocus && inCursor) {
|
|
388
|
-
style =
|
|
523
|
+
style = isDimSigil
|
|
389
524
|
? nlStyle.merge({ underline: true })
|
|
390
|
-
:
|
|
525
|
+
: baseStyle.merge({ underline: true });
|
|
391
526
|
}
|
|
392
|
-
else if (
|
|
527
|
+
else if (isDimSigil) {
|
|
393
528
|
style = nlStyle;
|
|
394
529
|
}
|
|
395
530
|
else {
|
|
396
|
-
style =
|
|
531
|
+
style = baseStyle;
|
|
397
532
|
}
|
|
398
533
|
}
|
|
399
534
|
else {
|
|
400
535
|
if (inSelection) {
|
|
401
|
-
style =
|
|
402
|
-
?
|
|
536
|
+
style = isDimSigil
|
|
537
|
+
? selectedStyle.merge({ foreground: nlStyle.foreground })
|
|
403
538
|
: selectedStyle.merge({ underline: inCursor });
|
|
404
539
|
}
|
|
405
|
-
else if (
|
|
540
|
+
else if (isDimSigil) {
|
|
406
541
|
style = nlStyle;
|
|
407
542
|
}
|
|
408
543
|
else {
|
|
409
|
-
style =
|
|
544
|
+
style = baseStyle;
|
|
410
545
|
}
|
|
411
546
|
}
|
|
412
547
|
if (!currentStyle.isEqual(style)) {
|
|
@@ -430,18 +565,31 @@ export class Input extends View {
|
|
|
430
565
|
drawEllipses = true;
|
|
431
566
|
}
|
|
432
567
|
}
|
|
433
|
-
|
|
568
|
+
if (!drawEllipses && char === '\t') {
|
|
569
|
+
// Render tab as two-char sigil
|
|
570
|
+
const offset = scanTextPosition.offset(-cursorVisible.x, -cursorVisible.y);
|
|
571
|
+
viewport.write(TAB_SIGIL[0], offset);
|
|
572
|
+
viewport.write(TAB_SIGIL[1], offset.offset(1, 0));
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
viewport.write(drawEllipses ? '…' : char, scanTextPosition.offset(-cursorVisible.x, -cursorVisible.y));
|
|
576
|
+
}
|
|
434
577
|
drawInitialEllipses = false;
|
|
435
578
|
}
|
|
436
579
|
else {
|
|
437
580
|
drawInitialEllipses = true;
|
|
438
581
|
}
|
|
439
582
|
scanTextPosition.x += charWidth;
|
|
583
|
+
charIndex++;
|
|
440
584
|
if (scanTextPosition.x - cursorVisible.x >=
|
|
441
585
|
viewport.contentSize.width) {
|
|
442
586
|
break;
|
|
443
587
|
}
|
|
444
588
|
}
|
|
589
|
+
// Advance formatOffset past this line's chars (excluding the trailing sigil).
|
|
590
|
+
if (hasFormatStyles) {
|
|
591
|
+
formatOffset += line.length - 1;
|
|
592
|
+
}
|
|
445
593
|
scanTextPosition.y += 1;
|
|
446
594
|
if (scanTextPosition.y - cursorVisible.y >=
|
|
447
595
|
viewport.contentSize.height) {
|
|
@@ -680,11 +828,21 @@ export class Input extends View {
|
|
|
680
828
|
this.minSelected() + (advance ? 1 : 0);
|
|
681
829
|
}
|
|
682
830
|
}
|
|
683
|
-
#receiveGotoStart() {
|
|
684
|
-
|
|
831
|
+
#receiveGotoStart({ shift }) {
|
|
832
|
+
if (shift) {
|
|
833
|
+
this.#cursor.end = 0;
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
this.#cursor = { start: 0, end: 0 };
|
|
837
|
+
}
|
|
685
838
|
}
|
|
686
|
-
#receiveGotoEnd() {
|
|
687
|
-
|
|
839
|
+
#receiveGotoEnd({ shift }) {
|
|
840
|
+
if (shift) {
|
|
841
|
+
this.#cursor.end = this.#chars.length;
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
this.#cursor = { start: this.#chars.length, end: this.#chars.length };
|
|
845
|
+
}
|
|
688
846
|
}
|
|
689
847
|
#receiveHome({ shift }) {
|
|
690
848
|
let dest = 0;
|
|
@@ -698,8 +856,8 @@ export class Input extends View {
|
|
|
698
856
|
const [targetChars, targetWidth] = this.#wrappedLines[cursorPosition.y];
|
|
699
857
|
dest = this.#wrappedLines
|
|
700
858
|
.slice(0, cursorPosition.y)
|
|
701
|
-
.reduce((dest, [
|
|
702
|
-
return dest +
|
|
859
|
+
.reduce((dest, [chars]) => {
|
|
860
|
+
return dest + chars.length;
|
|
703
861
|
}, 0);
|
|
704
862
|
}
|
|
705
863
|
if (shift) {
|
|
@@ -722,8 +880,8 @@ export class Input extends View {
|
|
|
722
880
|
dest =
|
|
723
881
|
this.#wrappedLines
|
|
724
882
|
.slice(0, cursorPosition.y + 1)
|
|
725
|
-
.reduce((dest, [
|
|
726
|
-
return dest +
|
|
883
|
+
.reduce((dest, [chars]) => {
|
|
884
|
+
return dest + chars.length;
|
|
727
885
|
}, 0) - 1;
|
|
728
886
|
}
|
|
729
887
|
if (shift) {
|
|
@@ -745,19 +903,21 @@ export class Input extends View {
|
|
|
745
903
|
const [targetChars, targetWidth] = this.#wrappedLines[cursorPosition.y - 1];
|
|
746
904
|
dest = this.#wrappedLines
|
|
747
905
|
.slice(0, cursorPosition.y - 1)
|
|
748
|
-
.reduce((dest, [
|
|
749
|
-
return dest +
|
|
906
|
+
.reduce((dest, [chars]) => {
|
|
907
|
+
return dest + chars.length;
|
|
750
908
|
}, 0);
|
|
751
909
|
if (targetWidth <= cursorPosition.x) {
|
|
752
|
-
dest +=
|
|
910
|
+
dest += targetChars.length - 1;
|
|
753
911
|
}
|
|
754
912
|
else {
|
|
913
|
+
let displayX = 0;
|
|
755
914
|
let destOffset = 0;
|
|
756
915
|
for (const char of targetChars) {
|
|
757
916
|
const charWidth = unicode.charWidth(char);
|
|
758
|
-
if (
|
|
917
|
+
if (displayX + charWidth > cursorPosition.x) {
|
|
759
918
|
break;
|
|
760
919
|
}
|
|
920
|
+
displayX += charWidth;
|
|
761
921
|
destOffset += 1;
|
|
762
922
|
}
|
|
763
923
|
dest += destOffset;
|
|
@@ -783,19 +943,21 @@ export class Input extends View {
|
|
|
783
943
|
const [targetChars, targetWidth] = this.#wrappedLines[cursorPosition.y + 1];
|
|
784
944
|
dest = this.#wrappedLines
|
|
785
945
|
.slice(0, cursorPosition.y + 1)
|
|
786
|
-
.reduce((dest, [
|
|
787
|
-
return dest +
|
|
946
|
+
.reduce((dest, [chars]) => {
|
|
947
|
+
return dest + chars.length;
|
|
788
948
|
}, 0);
|
|
789
949
|
if (targetWidth <= cursorPosition.x) {
|
|
790
|
-
dest +=
|
|
950
|
+
dest += targetChars.length - 1;
|
|
791
951
|
}
|
|
792
952
|
else {
|
|
953
|
+
let displayX = 0;
|
|
793
954
|
let destOffset = 0;
|
|
794
955
|
for (const char of targetChars) {
|
|
795
956
|
const charWidth = unicode.charWidth(char);
|
|
796
|
-
if (
|
|
957
|
+
if (displayX + charWidth > cursorPosition.x) {
|
|
797
958
|
break;
|
|
798
959
|
}
|
|
960
|
+
displayX += charWidth;
|
|
799
961
|
destOffset += 1;
|
|
800
962
|
}
|
|
801
963
|
dest += destOffset;
|
|
@@ -942,6 +1104,162 @@ export class Input extends View {
|
|
|
942
1104
|
this.#cursor.start = this.#cursor.end = offset;
|
|
943
1105
|
this.#updateWidth();
|
|
944
1106
|
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Insert a newline followed by the current line's leading whitespace.
|
|
1109
|
+
*/
|
|
1110
|
+
#receiveEnterWithIndent() {
|
|
1111
|
+
const indent = this.#currentLineIndent();
|
|
1112
|
+
const chars = ['\n', ...indent];
|
|
1113
|
+
if (isEmptySelection(this.#cursor)) {
|
|
1114
|
+
this.#chars = this.#chars
|
|
1115
|
+
.slice(0, this.#cursor.start)
|
|
1116
|
+
.concat(chars, this.#chars.slice(this.#cursor.start));
|
|
1117
|
+
this.#cursor.start = this.#cursor.end = this.#cursor.start + chars.length;
|
|
1118
|
+
}
|
|
1119
|
+
else {
|
|
1120
|
+
this.#chars = this.#chars
|
|
1121
|
+
.slice(0, this.minSelected())
|
|
1122
|
+
.concat(chars, this.#chars.slice(this.maxSelected()));
|
|
1123
|
+
this.#cursor.start = this.#cursor.end = this.minSelected() + chars.length;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Indent the current line. Uses tabs if any line starts with a tab,
|
|
1128
|
+
* otherwise uses two spaces.
|
|
1129
|
+
*/
|
|
1130
|
+
#receiveIndent() {
|
|
1131
|
+
const indent = this.#useTabs() ? ['\t'] : [' ', ' '];
|
|
1132
|
+
const lineStart = this.#currentLineStart();
|
|
1133
|
+
this.#chars = this.#chars
|
|
1134
|
+
.slice(0, lineStart)
|
|
1135
|
+
.concat(indent, this.#chars.slice(lineStart));
|
|
1136
|
+
this.#cursor.start += indent.length;
|
|
1137
|
+
this.#cursor.end += indent.length;
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Remove one level of indentation from the current line.
|
|
1141
|
+
*/
|
|
1142
|
+
#receiveDedent() {
|
|
1143
|
+
const lineStart = this.#currentLineStart();
|
|
1144
|
+
if (this.#chars[lineStart] === '\t') {
|
|
1145
|
+
this.#chars = this.#chars
|
|
1146
|
+
.slice(0, lineStart)
|
|
1147
|
+
.concat(this.#chars.slice(lineStart + 1));
|
|
1148
|
+
this.#cursor.start = Math.max(lineStart, this.#cursor.start - 1);
|
|
1149
|
+
this.#cursor.end = Math.max(lineStart, this.#cursor.end - 1);
|
|
1150
|
+
}
|
|
1151
|
+
else if (this.#chars[lineStart] === ' ' &&
|
|
1152
|
+
this.#chars[lineStart + 1] === ' ') {
|
|
1153
|
+
this.#chars = this.#chars
|
|
1154
|
+
.slice(0, lineStart)
|
|
1155
|
+
.concat(this.#chars.slice(lineStart + 2));
|
|
1156
|
+
this.#cursor.start = Math.max(lineStart, this.#cursor.start - 2);
|
|
1157
|
+
this.#cursor.end = Math.max(lineStart, this.#cursor.end - 2);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Returns the leading whitespace characters of the current line.
|
|
1162
|
+
*/
|
|
1163
|
+
#currentLineIndent() {
|
|
1164
|
+
const lineStart = this.#currentLineStart();
|
|
1165
|
+
const indent = [];
|
|
1166
|
+
for (let i = lineStart; i < this.#chars.length; i++) {
|
|
1167
|
+
if (this.#chars[i] === ' ' || this.#chars[i] === '\t') {
|
|
1168
|
+
indent.push(this.#chars[i]);
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
break;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return indent;
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Returns the index in #chars where the current line starts.
|
|
1178
|
+
*/
|
|
1179
|
+
#currentLineStart() {
|
|
1180
|
+
const pos = Math.min(this.#cursor.end, this.#chars.length);
|
|
1181
|
+
for (let i = pos - 1; i >= 0; i--) {
|
|
1182
|
+
if (this.#chars[i] === '\n') {
|
|
1183
|
+
return i + 1;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return 0;
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Returns true if any line in the current text starts with a tab character.
|
|
1190
|
+
*/
|
|
1191
|
+
#useTabs() {
|
|
1192
|
+
for (let i = 0; i < this.#chars.length; i++) {
|
|
1193
|
+
if (this.#chars[i] === '\t' && (i === 0 || this.#chars[i - 1] === '\n')) {
|
|
1194
|
+
return true;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
return false;
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Snapshot the current state before an edit.
|
|
1201
|
+
*/
|
|
1202
|
+
#beginEdit(kind) {
|
|
1203
|
+
this.#pendingUndoKind = kind;
|
|
1204
|
+
this.#preEditChars = [...this.#chars];
|
|
1205
|
+
this.#preEditCursor = { ...this.#cursor };
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Push the completed edit onto the undo stack. For consecutive inserts at
|
|
1209
|
+
* adjacent positions, coalesce into a single undo entry.
|
|
1210
|
+
*/
|
|
1211
|
+
#commitEdit() {
|
|
1212
|
+
if (!this.#pendingUndoKind) {
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
const kind = this.#pendingUndoKind;
|
|
1216
|
+
this.#pendingUndoKind = undefined;
|
|
1217
|
+
const entry = {
|
|
1218
|
+
kind,
|
|
1219
|
+
beforeChars: this.#preEditChars,
|
|
1220
|
+
beforeCursor: this.#preEditCursor,
|
|
1221
|
+
afterChars: [...this.#chars],
|
|
1222
|
+
afterCursor: { ...this.#cursor },
|
|
1223
|
+
insertOffset: kind === 'insert' ? this.#preEditCursor.start : undefined,
|
|
1224
|
+
};
|
|
1225
|
+
// Coalesce consecutive inserts at adjacent positions
|
|
1226
|
+
if (kind === 'insert' &&
|
|
1227
|
+
this.#insertCoalesceEnabled &&
|
|
1228
|
+
this.#undoStack.length > 0) {
|
|
1229
|
+
const last = this.#undoStack[this.#undoStack.length - 1];
|
|
1230
|
+
if (last.kind === 'insert' &&
|
|
1231
|
+
last.afterCursor.start === entry.insertOffset) {
|
|
1232
|
+
// Extend the previous entry
|
|
1233
|
+
last.afterChars = entry.afterChars;
|
|
1234
|
+
last.afterCursor = entry.afterCursor;
|
|
1235
|
+
this.#redoStack = [];
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
this.#insertCoalesceEnabled = kind === 'insert';
|
|
1240
|
+
this.#undoStack.push(entry);
|
|
1241
|
+
this.#redoStack = [];
|
|
1242
|
+
}
|
|
1243
|
+
#undo() {
|
|
1244
|
+
const entry = this.#undoStack.pop();
|
|
1245
|
+
if (!entry) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
this.#redoStack.push(entry);
|
|
1249
|
+
this.#chars = [...entry.beforeChars];
|
|
1250
|
+
this.#cursor = { ...entry.beforeCursor };
|
|
1251
|
+
this.#updateLines(this.#chars, undefined);
|
|
1252
|
+
}
|
|
1253
|
+
#redo() {
|
|
1254
|
+
const entry = this.#redoStack.pop();
|
|
1255
|
+
if (!entry) {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
this.#undoStack.push(entry);
|
|
1259
|
+
this.#chars = [...entry.afterChars];
|
|
1260
|
+
this.#cursor = { ...entry.afterCursor };
|
|
1261
|
+
this.#updateLines(this.#chars, undefined);
|
|
1262
|
+
}
|
|
945
1263
|
}
|
|
946
1264
|
function isEmptySelection(cursor) {
|
|
947
1265
|
return cursor.start === cursor.end;
|
|
@@ -1113,9 +1431,11 @@ const ACCENTS = {
|
|
|
1113
1431
|
};
|
|
1114
1432
|
const ACCENT_KEYS = {
|
|
1115
1433
|
'A-a': '‵',
|
|
1434
|
+
'A-`': '‵',
|
|
1116
1435
|
'A-c': '¸',
|
|
1117
1436
|
'A-e': '´',
|
|
1118
1437
|
'A-i': 'ˆ',
|
|
1438
|
+
'A-6': 'ˆ',
|
|
1119
1439
|
'A-n': '˜',
|
|
1120
1440
|
'A-o': '¯',
|
|
1121
1441
|
'A-s': '¸',
|
|
@@ -1130,4 +1450,8 @@ function isKeyAccent(event) {
|
|
|
1130
1450
|
}
|
|
1131
1451
|
return ACCENT_KEYS[event.full] !== undefined;
|
|
1132
1452
|
}
|
|
1453
|
+
const RESET_RE = /^\x1b\[0?m$/;
|
|
1454
|
+
function isTabSigil(char) {
|
|
1455
|
+
return char === '\t';
|
|
1456
|
+
}
|
|
1133
1457
|
//# sourceMappingURL=Input.js.map
|