@flowtty/core 1.0.0-alpha.6 → 1.0.0-alpha.7
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/{backend-ode0Vc7y.d.ts → backend-BYzQoXfC.d.ts} +14 -2
- package/dist/chunk-AQ33T2P5.js +34 -0
- package/dist/host/index.d.ts +2 -2
- package/dist/host/index.js +32 -6
- package/dist/{host-zpuR6C1c.d.ts → host-6NVC0_E3.d.ts} +23 -1
- package/dist/index.d.ts +29 -5
- package/dist/index.js +65 -10
- package/dist/testing/index.d.ts +6 -2
- package/dist/testing/index.js +27 -1
- package/package.json +1 -1
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { B as Buffer } from './cells-CaXEx4lH.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Every multi-character key name a backend produces. A printable key's name is
|
|
5
|
+
* the character itself (`' '`, `':'`, `'a'`) — there is no `'space'`, `'enter'`
|
|
6
|
+
* or `'colon'`. Sequences the key parser does not recognize surface under a
|
|
7
|
+
* `csi-…` name, which is deliberately not listed here.
|
|
8
|
+
*/
|
|
9
|
+
declare const NAMED_KEYS: readonly ["return", "escape", "tab", "backspace", "delete", "insert", "up", "down", "left", "right", "home", "end", "pageup", "pagedown", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "paste", "wheelup", "wheeldown"];
|
|
10
|
+
type NamedKey = typeof NAMED_KEYS[number];
|
|
11
|
+
/** A key name: a named key, or the character itself for a printable key. The
|
|
12
|
+
* `string & {}` arm keeps any character valid while editors still complete the
|
|
13
|
+
* named ones — it cannot reject a typo like `'space'`; `TestBackend.press` does. */
|
|
14
|
+
type KeyName = NamedKey | (string & {});
|
|
3
15
|
interface Key {
|
|
4
16
|
/**
|
|
5
17
|
* Canonical name of the key. For printable ASCII characters this is the
|
|
@@ -9,7 +21,7 @@ interface Key {
|
|
|
9
21
|
* whose content is in `text`. Mouse wheel steps are 'wheelup' / 'wheeldown',
|
|
10
22
|
* positioned by `x` / `y`.
|
|
11
23
|
*/
|
|
12
|
-
name:
|
|
24
|
+
name: KeyName;
|
|
13
25
|
/**
|
|
14
26
|
* Payload of a 'paste' key: the pasted text, line endings normalized to `\n`.
|
|
15
27
|
* A paste is delivered whole — its newlines and letters are text, never
|
|
@@ -87,4 +99,4 @@ interface Backend {
|
|
|
87
99
|
hyperlinks?: boolean;
|
|
88
100
|
}
|
|
89
101
|
|
|
90
|
-
export type
|
|
102
|
+
export { type Backend as B, type Key as K, NAMED_KEYS as N, type KeyName as a, type NamedKey as b };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/keys.ts
|
|
2
|
+
var NAMED_KEYS = [
|
|
3
|
+
"return",
|
|
4
|
+
"escape",
|
|
5
|
+
"tab",
|
|
6
|
+
"backspace",
|
|
7
|
+
"delete",
|
|
8
|
+
"insert",
|
|
9
|
+
"up",
|
|
10
|
+
"down",
|
|
11
|
+
"left",
|
|
12
|
+
"right",
|
|
13
|
+
"home",
|
|
14
|
+
"end",
|
|
15
|
+
"pageup",
|
|
16
|
+
"pagedown",
|
|
17
|
+
"f1",
|
|
18
|
+
"f2",
|
|
19
|
+
"f3",
|
|
20
|
+
"f4",
|
|
21
|
+
"f5",
|
|
22
|
+
"f6",
|
|
23
|
+
"f7",
|
|
24
|
+
"f8",
|
|
25
|
+
"f9",
|
|
26
|
+
"f10",
|
|
27
|
+
"f11",
|
|
28
|
+
"f12",
|
|
29
|
+
"paste",
|
|
30
|
+
"wheelup",
|
|
31
|
+
"wheeldown"
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export { NAMED_KEYS };
|
package/dist/host/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Container } from '../host-
|
|
2
|
-
export { B as BORDER_CHARS, H as HostType, I as Instance, R as Rect, T as TextInstance, Y as Yoga, a as YogaNode, b as appendChild, c as applyProps, d as computeLayout, e as createInstance, f as createTextInstance, g as getYoga, i as insertBefore, l as layoutOf, m as measureText, o as ownText, r as refreshMeasure, h as removeChild } from '../host-
|
|
1
|
+
import { C as Container } from '../host-6NVC0_E3.js';
|
|
2
|
+
export { B as BORDER_CHARS, H as HostType, I as Instance, R as Rect, T as TextInstance, Y as Yoga, a as YogaNode, b as appendChild, c as applyProps, d as computeLayout, e as createInstance, f as createTextInstance, g as getYoga, i as insertBefore, l as layoutOf, m as measureText, o as ownText, r as refreshMeasure, h as removeChild } from '../host-6NVC0_E3.js';
|
|
3
3
|
import { B as Buffer } from '../cells-CaXEx4lH.js';
|
|
4
4
|
import 'yoga-layout/load';
|
|
5
5
|
|
package/dist/host/index.js
CHANGED
|
@@ -315,13 +315,22 @@ function contentRectOf(inst, box) {
|
|
|
315
315
|
height: Math.max(0, box.height - padT - padB)
|
|
316
316
|
};
|
|
317
317
|
}
|
|
318
|
+
function paddingRectOf(inst, box) {
|
|
319
|
+
const n = inst.yogaNode;
|
|
320
|
+
const t = n.getComputedBorder(Edge.Top);
|
|
321
|
+
const r = n.getComputedBorder(Edge.Right);
|
|
322
|
+
const b = n.getComputedBorder(Edge.Bottom);
|
|
323
|
+
const l = n.getComputedBorder(Edge.Left);
|
|
324
|
+
return { left: box.left + l, top: box.top + t, width: Math.max(0, box.width - l - r), height: Math.max(0, box.height - t - b) };
|
|
325
|
+
}
|
|
318
326
|
function paintInstance(inst, buffer, offsetX, offsetY, inheritedBg = void 0, clip = null) {
|
|
319
327
|
if (inst.props.display === "none") return;
|
|
320
328
|
const box = layoutOf(inst, offsetX, offsetY);
|
|
321
329
|
inst.props.onLayout?.(box);
|
|
322
330
|
const ownBg = inst.props.backgroundColor;
|
|
323
331
|
const effectiveBg = ownBg ?? inheritedBg;
|
|
324
|
-
|
|
332
|
+
const offscreen = clip !== null && (box.left >= clip.left + clip.width || box.left + box.width <= clip.left || box.top >= clip.top + clip.height || box.top + box.height <= clip.top);
|
|
333
|
+
if (!offscreen && ownBg !== void 0) {
|
|
325
334
|
const fillStyle = ownBg === "default" ? {} : { bg: ownBg };
|
|
326
335
|
for (let y = box.top; y < box.top + box.height; y++) {
|
|
327
336
|
for (let x = box.left; x < box.left + box.width; x++) {
|
|
@@ -329,8 +338,8 @@ function paintInstance(inst, buffer, offsetX, offsetY, inheritedBg = void 0, cli
|
|
|
329
338
|
}
|
|
330
339
|
}
|
|
331
340
|
}
|
|
332
|
-
paintBorder(inst, buffer, box, clip, effectiveBg);
|
|
333
|
-
const text = ownText(inst);
|
|
341
|
+
if (!offscreen) paintBorder(inst, buffer, box, clip, effectiveBg);
|
|
342
|
+
const text = offscreen ? "" : ownText(inst);
|
|
334
343
|
if (text) {
|
|
335
344
|
const content = contentRectOf(inst, box);
|
|
336
345
|
const mode = inst.props.wrap ?? "none";
|
|
@@ -350,7 +359,23 @@ function paintInstance(inst, buffer, offsetX, offsetY, inheritedBg = void 0, cli
|
|
|
350
359
|
}
|
|
351
360
|
}
|
|
352
361
|
}
|
|
353
|
-
const
|
|
362
|
+
const scrolls = inst.props.scrollTop !== void 0 || inst.props.scrollBottom !== void 0;
|
|
363
|
+
let scrollTop = 0;
|
|
364
|
+
if (scrolls || inst.props.onScrollMetrics) {
|
|
365
|
+
const viewport = contentRectOf(inst, box);
|
|
366
|
+
let contentBottom = 0;
|
|
367
|
+
for (const child of inst.children) {
|
|
368
|
+
if (child.type !== "box" || child.props.position === "absolute" || child.props.display === "none") continue;
|
|
369
|
+
const n = child.yogaNode;
|
|
370
|
+
contentBottom = Math.max(contentBottom, n.getComputedTop() + n.getComputedHeight() + n.getComputedMargin(Edge.Bottom));
|
|
371
|
+
}
|
|
372
|
+
const contentHeight = Math.max(0, contentBottom - (viewport.top - box.top));
|
|
373
|
+
const maxScrollTop = Math.max(0, contentHeight - viewport.height);
|
|
374
|
+
const wanted = inst.props.scrollBottom !== void 0 ? maxScrollTop - inst.props.scrollBottom : inst.props.scrollTop ?? 0;
|
|
375
|
+
scrollTop = Math.max(0, Math.min(maxScrollTop, Math.round(wanted)));
|
|
376
|
+
inst.props.onScrollMetrics?.({ contentHeight, viewportHeight: viewport.height, scrollTop, maxScrollTop });
|
|
377
|
+
}
|
|
378
|
+
const childClip = inst.props.overflow === "hidden" || scrolls ? intersectRects(clip, contentRectOf(inst, box)) : clip;
|
|
354
379
|
const stackFlow = [];
|
|
355
380
|
const absolutes = [];
|
|
356
381
|
for (const child of inst.children) {
|
|
@@ -360,8 +385,9 @@ function paintInstance(inst, buffer, offsetX, offsetY, inheritedBg = void 0, cli
|
|
|
360
385
|
const byZ = (a, b) => (a.props.zIndex ?? 0) - (b.props.zIndex ?? 0);
|
|
361
386
|
stackFlow.sort(byZ);
|
|
362
387
|
absolutes.sort(byZ);
|
|
363
|
-
for (const child of stackFlow) paintInstance(child, buffer, box.left, box.top, effectiveBg, childClip);
|
|
364
|
-
|
|
388
|
+
for (const child of stackFlow) paintInstance(child, buffer, box.left, box.top - scrollTop, effectiveBg, childClip);
|
|
389
|
+
const overlayClip = scrolls ? intersectRects(clip, paddingRectOf(inst, box)) : childClip;
|
|
390
|
+
for (const child of absolutes) paintInstance(child, buffer, box.left, box.top, effectiveBg, overlayClip);
|
|
365
391
|
}
|
|
366
392
|
|
|
367
393
|
export { appendChild, applyProps, computeLayout, createInstance, createTextInstance, getYoga, insertBefore, layoutOf, measureText, ownText, paint, refreshMeasure, removeChild };
|
|
@@ -114,6 +114,28 @@ interface BoxProps {
|
|
|
114
114
|
* 'hidden' clips ALL descendant writes including their backgrounds and borders.
|
|
115
115
|
* Does NOT clip this box's own background or border (those are this box's own area). */
|
|
116
116
|
overflow?: 'visible' | 'hidden';
|
|
117
|
+
/** Scroll the content up by this many rows (0 = top). Setting either scroll
|
|
118
|
+
* prop makes the box a scroll viewport: it clips like `overflow: 'hidden'`
|
|
119
|
+
* and paints its flow children shifted. The value is clamped to the scrollable
|
|
120
|
+
* range at paint time, against the content's CURRENT height — so it never
|
|
121
|
+
* needs a second frame to catch up. `position: 'absolute'` children are
|
|
122
|
+
* overlays: they neither scroll nor count as content (a scrollbar, a
|
|
123
|
+
* "new messages" badge). */
|
|
124
|
+
scrollTop?: number;
|
|
125
|
+
/** Like `scrollTop`, but counted from the END of the content: 0 pins the last
|
|
126
|
+
* rows into view and keeps them there as content grows. Wins over `scrollTop`
|
|
127
|
+
* when both are set. */
|
|
128
|
+
scrollBottom?: number;
|
|
129
|
+
/** Fires every paint (diff before setState, like onLayout) with the numbers a
|
|
130
|
+
* scrolling component needs: content and viewport heights, the effective
|
|
131
|
+
* (clamped) `scrollTop`, and its maximum. */
|
|
132
|
+
onScrollMetrics?: (metrics: ScrollMetrics) => void;
|
|
133
|
+
}
|
|
134
|
+
interface ScrollMetrics {
|
|
135
|
+
contentHeight: number;
|
|
136
|
+
viewportHeight: number;
|
|
137
|
+
scrollTop: number;
|
|
138
|
+
maxScrollTop: number;
|
|
117
139
|
}
|
|
118
140
|
interface Instance {
|
|
119
141
|
type: 'box';
|
|
@@ -149,4 +171,4 @@ declare function appendChild(parent: Instance, child: Instance | TextInstance, Y
|
|
|
149
171
|
declare function removeChild(parent: Instance, child: Instance | TextInstance, Yoga: Yoga): void;
|
|
150
172
|
declare function insertBefore(parent: Instance, child: Instance | TextInstance, before: Instance | TextInstance, Yoga: Yoga): void;
|
|
151
173
|
|
|
152
|
-
export { BORDER_CHARS as B, type Container as C, DEFAULT_BORDER_STYLE as D, GRID_CHARS as G, type HostType as H, type Instance as I, type Rect as R, type TextInstance as T, type Yoga as Y, type YogaNode as a, appendChild as b, applyProps as c, computeLayout as d, createInstance as e, createTextInstance as f, getYoga as g, removeChild as h, insertBefore as i, type BorderChars as j, type BorderStyle as k, layoutOf as l, measureText as m, type BoxProps as n, ownText as o, type GridChars as p, refreshMeasure as r };
|
|
174
|
+
export { BORDER_CHARS as B, type Container as C, DEFAULT_BORDER_STYLE as D, GRID_CHARS as G, type HostType as H, type Instance as I, type Rect as R, type ScrollMetrics as S, type TextInstance as T, type Yoga as Y, type YogaNode as a, appendChild as b, applyProps as c, computeLayout as d, createInstance as e, createTextInstance as f, getYoga as g, removeChild as h, insertBefore as i, type BorderChars as j, type BorderStyle as k, layoutOf as l, measureText as m, type BoxProps as n, ownText as o, type GridChars as p, refreshMeasure as r };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { B as Buffer, C as Cell, S as Style } from './cells-CaXEx4lH.js';
|
|
2
|
-
import { K as Key } from './backend-
|
|
3
|
-
export { B as Backend } from './backend-
|
|
4
|
-
export { j as BorderChars, k as BorderStyle, n as BoxProps, D as DEFAULT_BORDER_STYLE, G as GRID_CHARS, p as GridChars } from './host-
|
|
2
|
+
import { K as Key } from './backend-BYzQoXfC.js';
|
|
3
|
+
export { B as Backend, a as KeyName, N as NAMED_KEYS, b as NamedKey } from './backend-BYzQoXfC.js';
|
|
4
|
+
export { j as BorderChars, k as BorderStyle, n as BoxProps, D as DEFAULT_BORDER_STYLE, G as GRID_CHARS, p as GridChars, S as ScrollMetrics } from './host-6NVC0_E3.js';
|
|
5
5
|
import 'yoga-layout/load';
|
|
6
6
|
|
|
7
7
|
type WrapMode = 'wrap' | 'truncate' | 'none';
|
|
@@ -94,7 +94,31 @@ type EditorAction = {
|
|
|
94
94
|
} | {
|
|
95
95
|
kind: 'noop';
|
|
96
96
|
};
|
|
97
|
-
|
|
97
|
+
interface EditorOptions {
|
|
98
|
+
/** Multi-line editing: line breaks are part of the value. Shift+Enter,
|
|
99
|
+
* Alt+Enter and backslash-then-Enter insert one (plain Enter still submits),
|
|
100
|
+
* up/down move between visual rows, Home/End and the kill bindings work
|
|
101
|
+
* within the current line, and a paste keeps its line breaks. */
|
|
102
|
+
multiline?: boolean;
|
|
103
|
+
/** Wrap width in cells — what up/down use to walk soft-wrapped rows. Only
|
|
104
|
+
* read when `multiline`; without it each source line counts as one row. */
|
|
105
|
+
width?: number;
|
|
106
|
+
}
|
|
107
|
+
declare function reduce$2(state: EditorState, key: Key, opts?: EditorOptions): EditorAction;
|
|
108
|
+
|
|
109
|
+
interface InputRow {
|
|
110
|
+
/** Text of this visual row (no line break). */
|
|
111
|
+
text: string;
|
|
112
|
+
/** Index into the value where this row starts. */
|
|
113
|
+
start: number;
|
|
114
|
+
/** True for a soft-wrap continuation of the previous row's source line. */
|
|
115
|
+
continuation: boolean;
|
|
116
|
+
}
|
|
117
|
+
declare function inputRows(value: string, width: number, cursor?: number): InputRow[];
|
|
118
|
+
declare function caretPosition(value: string, cursor: number, width: number): {
|
|
119
|
+
row: number;
|
|
120
|
+
col: number;
|
|
121
|
+
};
|
|
98
122
|
|
|
99
123
|
interface SelectItem<T> {
|
|
100
124
|
label: string;
|
|
@@ -140,4 +164,4 @@ type MultiSelectAction = {
|
|
|
140
164
|
};
|
|
141
165
|
declare function reduce<T>(items: SelectItem<T>[], state: MultiSelectState, key: Key): MultiSelectAction;
|
|
142
166
|
|
|
143
|
-
export { type EditorAction, type EditorState, Key, type MultiSelectAction, type MultiSelectState, type SelectAction, type SelectItem, type SelectState, type VisualLine, type WrapMode, charWidth, reduce$2 as editorReducer, reduce as multiSelectReducer, reduce$1 as selectReducer, splitVisualLines, stringWidth, visibleIndices, windowAround, wrapText };
|
|
167
|
+
export { type EditorAction, type EditorOptions, type EditorState, type InputRow, Key, type MultiSelectAction, type MultiSelectState, type SelectAction, type SelectItem, type SelectState, type VisualLine, type WrapMode, caretPosition, charWidth, reduce$2 as editorReducer, inputRows, reduce as multiSelectReducer, reduce$1 as selectReducer, splitVisualLines, stringWidth, visibleIndices, windowAround, wrapText };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Buffer, DEFAULT_BORDER_STYLE, GRID_CHARS, wrapText } from './chunk-D6HW2BNM.js';
|
|
2
|
+
export { NAMED_KEYS } from './chunk-AQ33T2P5.js';
|
|
2
3
|
|
|
3
4
|
// src/visualLines.ts
|
|
4
5
|
function splitVisualLines(text, mode, width) {
|
|
@@ -280,6 +281,36 @@ function stringWidth(str) {
|
|
|
280
281
|
return w;
|
|
281
282
|
}
|
|
282
283
|
|
|
284
|
+
// src/inputRows.ts
|
|
285
|
+
function inputRows(value, width, cursor) {
|
|
286
|
+
const w = Math.max(1, Math.floor(width));
|
|
287
|
+
const rows = [];
|
|
288
|
+
let lineStart = 0;
|
|
289
|
+
for (const line of value.split("\n")) {
|
|
290
|
+
if (line.length === 0) {
|
|
291
|
+
rows.push({ text: "", start: lineStart, continuation: false });
|
|
292
|
+
} else {
|
|
293
|
+
for (let at = 0; at < line.length; at += w) {
|
|
294
|
+
rows.push({ text: line.slice(at, at + w), start: lineStart + at, continuation: at > 0 });
|
|
295
|
+
}
|
|
296
|
+
if (cursor === lineStart + line.length && line.length % w === 0) {
|
|
297
|
+
rows.push({ text: "", start: cursor, continuation: true });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
lineStart += line.length + 1;
|
|
301
|
+
}
|
|
302
|
+
return rows;
|
|
303
|
+
}
|
|
304
|
+
function caretPosition(value, cursor, width) {
|
|
305
|
+
const c = Math.max(0, Math.min(value.length, cursor));
|
|
306
|
+
const rows = inputRows(value, width, c);
|
|
307
|
+
for (let r = rows.length - 1; r >= 0; r--) {
|
|
308
|
+
const row = rows[r];
|
|
309
|
+
if (row.start <= c && c <= row.start + row.text.length) return { row: r, col: c - row.start };
|
|
310
|
+
}
|
|
311
|
+
return { row: 0, col: 0 };
|
|
312
|
+
}
|
|
313
|
+
|
|
283
314
|
// src/editor.ts
|
|
284
315
|
var OPT_MAP = {
|
|
285
316
|
" ": ["\xA0"],
|
|
@@ -304,13 +335,37 @@ function wordRight(value, cursor) {
|
|
|
304
335
|
while (c < value.length && isWord(value[c])) c++;
|
|
305
336
|
return c;
|
|
306
337
|
}
|
|
307
|
-
function reduce(state, key) {
|
|
338
|
+
function reduce(state, key, opts = {}) {
|
|
308
339
|
const { value, cursor } = state;
|
|
309
340
|
const clamp = (n) => Math.max(0, Math.min(value.length, n));
|
|
341
|
+
const multiline = opts.multiline === true;
|
|
342
|
+
const lineStart = multiline ? value.lastIndexOf("\n", cursor - 1) + 1 : 0;
|
|
343
|
+
const nextBreak = multiline ? value.indexOf("\n", cursor) : -1;
|
|
344
|
+
const lineEnd = nextBreak < 0 ? value.length : nextBreak;
|
|
345
|
+
const insert = (text) => ({
|
|
346
|
+
kind: "edit",
|
|
347
|
+
state: { value: value.slice(0, cursor) + text + value.slice(cursor), cursor: cursor + text.length }
|
|
348
|
+
});
|
|
310
349
|
if (key.name === "paste") {
|
|
311
|
-
const text = (key.text ?? "").replace(/\n/g, " ");
|
|
350
|
+
const text = multiline ? key.text ?? "" : (key.text ?? "").replace(/\n/g, " ");
|
|
312
351
|
if (text === "") return { kind: "noop" };
|
|
313
|
-
return
|
|
352
|
+
return insert(text);
|
|
353
|
+
}
|
|
354
|
+
if (multiline) {
|
|
355
|
+
if (key.name === "return" && (key.shift || key.meta)) return insert("\n");
|
|
356
|
+
if (key.name === "return" && cursor > 0 && value[cursor - 1] === "\\") {
|
|
357
|
+
return { kind: "edit", state: { value: value.slice(0, cursor - 1) + "\n" + value.slice(cursor), cursor } };
|
|
358
|
+
}
|
|
359
|
+
if (key.name === "up" || key.name === "down") {
|
|
360
|
+
const width = opts.width ?? Number.MAX_SAFE_INTEGER;
|
|
361
|
+
const rows = inputRows(value, width, cursor);
|
|
362
|
+
const at = caretPosition(value, cursor, width);
|
|
363
|
+
const target = at.row + (key.name === "up" ? -1 : 1);
|
|
364
|
+
if (target < 0) return { kind: "edit", state: { value, cursor: 0 } };
|
|
365
|
+
if (target >= rows.length) return { kind: "edit", state: { value, cursor: value.length } };
|
|
366
|
+
const row = rows[target];
|
|
367
|
+
return { kind: "edit", state: { value, cursor: row.start + Math.min(at.col, row.text.length) } };
|
|
368
|
+
}
|
|
314
369
|
}
|
|
315
370
|
if (key.name === "left" && key.meta) return { kind: "edit", state: { value, cursor: wordLeft(value, cursor) } };
|
|
316
371
|
if (key.name === "b" && key.meta) return { kind: "edit", state: { value, cursor: wordLeft(value, cursor) } };
|
|
@@ -318,10 +373,10 @@ function reduce(state, key) {
|
|
|
318
373
|
if (key.name === "f" && key.meta) return { kind: "edit", state: { value, cursor: wordRight(value, cursor) } };
|
|
319
374
|
if (key.name === "left") return { kind: "edit", state: { value, cursor: clamp(cursor - 1) } };
|
|
320
375
|
if (key.name === "right") return { kind: "edit", state: { value, cursor: clamp(cursor + 1) } };
|
|
321
|
-
if (key.name === "home") return { kind: "edit", state: { value, cursor:
|
|
322
|
-
if (key.name === "end") return { kind: "edit", state: { value, cursor:
|
|
323
|
-
if (key.name === "a" && key.ctrl) return { kind: "edit", state: { value, cursor:
|
|
324
|
-
if (key.name === "e" && key.ctrl) return { kind: "edit", state: { value, cursor:
|
|
376
|
+
if (key.name === "home") return { kind: "edit", state: { value, cursor: lineStart } };
|
|
377
|
+
if (key.name === "end") return { kind: "edit", state: { value, cursor: lineEnd } };
|
|
378
|
+
if (key.name === "a" && key.ctrl) return { kind: "edit", state: { value, cursor: lineStart } };
|
|
379
|
+
if (key.name === "e" && key.ctrl) return { kind: "edit", state: { value, cursor: lineEnd } };
|
|
325
380
|
if (key.name === "backspace" && key.meta) {
|
|
326
381
|
const start = wordLeft(value, cursor);
|
|
327
382
|
return { kind: "edit", state: { value: value.slice(0, start) + value.slice(cursor), cursor: start } };
|
|
@@ -339,10 +394,10 @@ function reduce(state, key) {
|
|
|
339
394
|
return { kind: "edit", state: { value: value.slice(0, cursor) + value.slice(end), cursor } };
|
|
340
395
|
}
|
|
341
396
|
if (key.name === "k" && key.ctrl) {
|
|
342
|
-
return { kind: "edit", state: { value: value.slice(0, cursor), cursor } };
|
|
397
|
+
return { kind: "edit", state: { value: value.slice(0, cursor) + value.slice(lineEnd), cursor } };
|
|
343
398
|
}
|
|
344
399
|
if (key.name === "u" && key.ctrl) {
|
|
345
|
-
return { kind: "edit", state: { value: value.slice(cursor), cursor:
|
|
400
|
+
return { kind: "edit", state: { value: value.slice(0, lineStart) + value.slice(cursor), cursor: lineStart } };
|
|
346
401
|
}
|
|
347
402
|
if (key.meta && OPT_MAP[key.name]) {
|
|
348
403
|
const entry = OPT_MAP[key.name];
|
|
@@ -422,4 +477,4 @@ function reduce3(items, state, key) {
|
|
|
422
477
|
return { kind: "noop" };
|
|
423
478
|
}
|
|
424
479
|
|
|
425
|
-
export { charWidth, reduce as editorReducer, reduce3 as multiSelectReducer, reduce2 as selectReducer, splitVisualLines, stringWidth, visibleIndices, windowAround };
|
|
480
|
+
export { caretPosition, charWidth, reduce as editorReducer, inputRows, reduce3 as multiSelectReducer, reduce2 as selectReducer, splitVisualLines, stringWidth, visibleIndices, windowAround };
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { B as Buffer } from '../cells-CaXEx4lH.js';
|
|
2
|
-
import { B as Backend, K as Key } from '../backend-
|
|
2
|
+
import { B as Backend, K as Key } from '../backend-BYzQoXfC.js';
|
|
3
3
|
|
|
4
4
|
declare class TestBackend implements Backend {
|
|
5
5
|
private readonly cols;
|
|
@@ -16,7 +16,11 @@ declare class TestBackend implements Backend {
|
|
|
16
16
|
get lastFrame(): string;
|
|
17
17
|
get lastBuffer(): Buffer | null;
|
|
18
18
|
onKey(handler: (key: Key) => void): () => void;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Synchronously deliver one Key to every subscriber. Throws on a name no
|
|
21
|
+
* terminal can produce (`'space'`, `'enter'`): a test that presses such a key
|
|
22
|
+
* exercises a branch real input never reaches, and would pass anyway.
|
|
23
|
+
*/
|
|
20
24
|
press(key: Partial<Key> & {
|
|
21
25
|
name: string;
|
|
22
26
|
}): void;
|
package/dist/testing/index.js
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
+
import { NAMED_KEYS } from '../chunk-AQ33T2P5.js';
|
|
2
|
+
|
|
1
3
|
// src/testing/test-backend.ts
|
|
4
|
+
var KEY_NAME_HINTS = {
|
|
5
|
+
space: " ",
|
|
6
|
+
enter: "return",
|
|
7
|
+
esc: "escape",
|
|
8
|
+
del: "delete",
|
|
9
|
+
ins: "insert",
|
|
10
|
+
pgup: "pageup",
|
|
11
|
+
pgdn: "pagedown",
|
|
12
|
+
pgdown: "pagedown",
|
|
13
|
+
bs: "backspace"
|
|
14
|
+
};
|
|
15
|
+
function assertRealKeyName(name) {
|
|
16
|
+
if ([...name].length === 1 || name.startsWith("csi-")) return;
|
|
17
|
+
if (NAMED_KEYS.includes(name)) return;
|
|
18
|
+
const hint = KEY_NAME_HINTS[name.toLowerCase()];
|
|
19
|
+
throw new Error(
|
|
20
|
+
`TestBackend.press: '${name}' is not a key name any terminal produces` + (hint !== void 0 ? ` \u2014 use '${hint}'.` : `. A printable key is named by its character (':' not 'colon'); named keys: ${NAMED_KEYS.join(", ")}.`)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
2
23
|
var TestBackend = class {
|
|
3
24
|
constructor(cols = 40, rows = 10) {
|
|
4
25
|
this.cols = cols;
|
|
@@ -28,8 +49,13 @@ var TestBackend = class {
|
|
|
28
49
|
this.subscribers.delete(handler);
|
|
29
50
|
};
|
|
30
51
|
}
|
|
31
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Synchronously deliver one Key to every subscriber. Throws on a name no
|
|
54
|
+
* terminal can produce (`'space'`, `'enter'`): a test that presses such a key
|
|
55
|
+
* exercises a branch real input never reaches, and would pass anyway.
|
|
56
|
+
*/
|
|
32
57
|
press(key) {
|
|
58
|
+
assertRealKeyName(key.name);
|
|
33
59
|
const k = {
|
|
34
60
|
...key.text !== void 0 ? { text: key.text } : {},
|
|
35
61
|
...key.x !== void 0 ? { x: key.x } : {},
|