ink-prompt 0.2.2 → 0.2.4
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/README.md +2 -2
- package/dist/components/MultilineInput/AtomicBlocks.d.ts +10 -20
- package/dist/components/MultilineInput/AtomicBlocks.js +33 -42
- package/dist/components/MultilineInput/BlockMarker.d.ts +19 -0
- package/dist/components/MultilineInput/BlockMarker.js +83 -0
- package/dist/components/MultilineInput/BlockRegistry.d.ts +39 -0
- package/dist/components/MultilineInput/BlockRegistry.js +236 -0
- package/dist/components/MultilineInput/BlockTypes.d.ts +22 -0
- package/dist/components/MultilineInput/ImageTypes.d.ts +0 -2
- package/dist/components/MultilineInput/ImageTypes.js +1 -2
- package/dist/components/MultilineInput/ImageValidator.js +1 -1
- package/dist/components/MultilineInput/KeyHandler.d.ts +1 -1
- package/dist/components/MultilineInput/TextBuffer.d.ts +5 -31
- package/dist/components/MultilineInput/TextBuffer.js +91 -161
- package/dist/components/MultilineInput/TextRenderer.d.ts +6 -7
- package/dist/components/MultilineInput/TextRenderer.js +7 -7
- package/dist/components/MultilineInput/__tests__/BlockMarker.test.js +130 -0
- package/dist/components/MultilineInput/__tests__/BlockRegistry.test.js +225 -0
- package/dist/components/MultilineInput/__tests__/Placeholder.integration.test.js +44 -65
- package/dist/components/MultilineInput/__tests__/TextBuffer_images.test.js +10 -31
- package/dist/components/MultilineInput/__tests__/TextRenderer_images.test.js +27 -13
- package/dist/components/MultilineInput/__tests__/integration_images.test.js +2 -4
- package/dist/components/MultilineInput/__tests__/useTextInput_images.test.js +30 -29
- package/dist/components/MultilineInput/index.d.ts +6 -6
- package/dist/components/MultilineInput/index.js +56 -13
- package/dist/components/MultilineInput/types.d.ts +0 -20
- package/dist/components/MultilineInput/useTextInput.d.ts +4 -11
- package/dist/components/MultilineInput/useTextInput.js +79 -76
- package/package.json +1 -1
- package/dist/components/MultilineInput/Placeholder.d.ts +0 -30
- package/dist/components/MultilineInput/Placeholder.js +0 -200
- package/dist/components/MultilineInput/__tests__/Placeholder.test.js +0 -235
- package/dist/examples/examples/basic.js +0 -9
- package/dist/examples/src/components/MultilineInput/KeyHandler.d.ts +0 -15
- package/dist/examples/src/components/MultilineInput/KeyHandler.js +0 -97
- package/dist/examples/src/components/MultilineInput/TextBuffer.d.ts +0 -34
- package/dist/examples/src/components/MultilineInput/TextBuffer.js +0 -127
- package/dist/examples/src/components/MultilineInput/TextRenderer.d.ts +0 -24
- package/dist/examples/src/components/MultilineInput/TextRenderer.js +0 -72
- package/dist/examples/src/components/MultilineInput/__tests__/KeyHandler.test.js +0 -115
- package/dist/examples/src/components/MultilineInput/__tests__/TextBuffer.test.d.ts +0 -1
- package/dist/examples/src/components/MultilineInput/__tests__/TextBuffer.test.js +0 -254
- package/dist/examples/src/components/MultilineInput/__tests__/TextRenderer.test.d.ts +0 -1
- package/dist/examples/src/components/MultilineInput/__tests__/TextRenderer.test.js +0 -176
- package/dist/examples/src/components/MultilineInput/__tests__/integration.test.d.ts +0 -1
- package/dist/examples/src/components/MultilineInput/__tests__/integration.test.js +0 -71
- package/dist/examples/src/components/MultilineInput/__tests__/useTextInput.test.d.ts +0 -1
- package/dist/examples/src/components/MultilineInput/__tests__/useTextInput.test.js +0 -65
- package/dist/examples/src/components/MultilineInput/index.d.ts +0 -39
- package/dist/examples/src/components/MultilineInput/index.js +0 -82
- package/dist/examples/src/components/MultilineInput/types.d.ts +0 -55
- package/dist/examples/src/components/MultilineInput/types.js +0 -1
- package/dist/examples/src/components/MultilineInput/useTextInput.d.ts +0 -16
- package/dist/examples/src/components/MultilineInput/useTextInput.js +0 -82
- package/dist/examples/src/hello.test.d.ts +0 -1
- package/dist/examples/src/hello.test.js +0 -13
- package/dist/examples/src/index.d.ts +0 -2
- package/dist/examples/src/index.js +0 -2
- /package/dist/components/MultilineInput/{__tests__/Placeholder.test.d.ts → BlockTypes.js} +0 -0
- /package/dist/{examples/examples/basic.d.ts → components/MultilineInput/__tests__/BlockMarker.test.d.ts} +0 -0
- /package/dist/{examples/src/components/MultilineInput/__tests__/KeyHandler.test.d.ts → components/MultilineInput/__tests__/BlockRegistry.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ render(<App />);
|
|
|
46
46
|
| `onBoundaryArrow` | `(direction: 'up' \| 'down' \| 'left' \| 'right') => void` | | Called when arrow key reaches a boundary |
|
|
47
47
|
| `undoDebounceMs` | `number` | `200` | Milliseconds of inactivity to commit undo batch (`0` = disable) |
|
|
48
48
|
| `pasteThreshold` | `number` | | Max paste length before text is replaced by a placeholder |
|
|
49
|
-
| `formatPastePlaceholder` | `(
|
|
49
|
+
| `formatPastePlaceholder` | `(displayNumber: number) => string` | | Custom placeholder display format (1-based) |
|
|
50
50
|
| `enableImagePaste` | `boolean` | `false` | Enables image-aware Ctrl+V handling |
|
|
51
51
|
| `images` | `ImageRef[]` | | Controlled image state for pasted images |
|
|
52
52
|
| `onImagesChange` | `(images: ImageRef[]) => void` | | Called when images change |
|
|
@@ -77,7 +77,7 @@ replace the pasted content with a compact placeholder for cleaner display.
|
|
|
77
77
|
<MultilineInput
|
|
78
78
|
onSubmit={(value) => console.log(value)}
|
|
79
79
|
pasteThreshold={200} // Text >200 chars becomes a placeholder
|
|
80
|
-
formatPastePlaceholder={(
|
|
80
|
+
formatPastePlaceholder={(n) => `[Pasted block #${n}]`} // Optional formatter
|
|
81
81
|
/>
|
|
82
82
|
```
|
|
83
83
|
|
|
@@ -1,25 +1,15 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export
|
|
3
|
-
kind:
|
|
1
|
+
import type { BlockEntry, BlockKind } from './BlockTypes.js';
|
|
2
|
+
export interface AtomicBlock {
|
|
3
|
+
kind: BlockKind;
|
|
4
4
|
id: string;
|
|
5
|
-
displayNumber: number;
|
|
6
5
|
start: number;
|
|
7
6
|
end: number;
|
|
8
7
|
displayWidth: number;
|
|
9
8
|
displayText: string;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
export type Placeholders = Map<number, PlaceholderInfo> | undefined;
|
|
19
|
-
export declare function findAtomicBlocks(line: string, placeholders?: Placeholders): AtomicBlock[];
|
|
20
|
-
/** Block that strictly contains offset (offset > start && offset < end) — cursor is in the interior. */
|
|
21
|
-
export declare function findAtomicBlockSpanning(line: string, offset: number, placeholders?: Placeholders): AtomicBlock | null;
|
|
22
|
-
/** Block whose end === offset (the block immediately to the left of the cursor). */
|
|
23
|
-
export declare function findAtomicBlockBefore(line: string, offset: number, placeholders?: Placeholders): AtomicBlock | null;
|
|
24
|
-
/** Block whose start === offset (the block immediately to the right of the cursor). */
|
|
25
|
-
export declare function findAtomicBlockAfter(line: string, offset: number, placeholders?: Placeholders): AtomicBlock | null;
|
|
9
|
+
dim: boolean;
|
|
10
|
+
}
|
|
11
|
+
export type BlockEntries = Map<string, BlockEntry> | undefined;
|
|
12
|
+
export declare function findAtomicBlocks(line: string, entries?: BlockEntries): AtomicBlock[];
|
|
13
|
+
export declare function findAtomicBlockSpanning(line: string, offset: number, entries?: BlockEntries): AtomicBlock | null;
|
|
14
|
+
export declare function findAtomicBlockBefore(line: string, offset: number, entries?: BlockEntries): AtomicBlock | null;
|
|
15
|
+
export declare function findAtomicBlockAfter(line: string, offset: number, entries?: BlockEntries): AtomicBlock | null;
|
|
@@ -1,58 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
blocks.push({
|
|
8
|
-
kind: 'sentinel',
|
|
9
|
-
id: s.id,
|
|
10
|
-
displayNumber: s.displayNumber,
|
|
11
|
-
start: s.start,
|
|
12
|
-
end: s.end,
|
|
13
|
-
displayWidth: displayText.length,
|
|
14
|
-
displayText,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
if (placeholders && placeholders.size > 0) {
|
|
18
|
-
MARKER_REGEX.lastIndex = 0;
|
|
19
|
-
let m;
|
|
20
|
-
while ((m = MARKER_REGEX.exec(line)) !== null) {
|
|
21
|
-
const id = Number(m[1]);
|
|
22
|
-
const info = placeholders.get(id);
|
|
23
|
-
const displayText = info ? info.displayText : '';
|
|
24
|
-
blocks.push({
|
|
25
|
-
kind: 'placeholder',
|
|
26
|
-
id,
|
|
27
|
-
start: m.index,
|
|
28
|
-
end: m.index + m[0].length,
|
|
29
|
-
displayWidth: displayText.length,
|
|
30
|
-
displayText,
|
|
31
|
-
});
|
|
1
|
+
import { parseBlockMarkers } from './BlockMarker.js';
|
|
2
|
+
function getDisplayInfo(marker, entries) {
|
|
3
|
+
if (entries) {
|
|
4
|
+
const entry = entries.get(marker.id);
|
|
5
|
+
if (entry && entry.kind === 'paste') {
|
|
6
|
+
return { displayWidth: entry.displayText.length, displayText: entry.displayText };
|
|
32
7
|
}
|
|
33
8
|
}
|
|
34
|
-
|
|
35
|
-
|
|
9
|
+
if (marker.kind === 'p') {
|
|
10
|
+
const text = `[Paste text #${marker.displayNumber}]`;
|
|
11
|
+
return { displayWidth: text.length, displayText: text };
|
|
12
|
+
}
|
|
13
|
+
const text = `[Pasted Image #${marker.displayNumber}]`;
|
|
14
|
+
return { displayWidth: text.length, displayText: text };
|
|
15
|
+
}
|
|
16
|
+
export function findAtomicBlocks(line, entries) {
|
|
17
|
+
const markers = parseBlockMarkers(line);
|
|
18
|
+
return markers.map((m) => {
|
|
19
|
+
const { displayWidth, displayText } = getDisplayInfo(m, entries);
|
|
20
|
+
return {
|
|
21
|
+
kind: m.kind === 'p' ? 'paste' : 'image',
|
|
22
|
+
id: m.id,
|
|
23
|
+
start: m.start,
|
|
24
|
+
end: m.end,
|
|
25
|
+
displayWidth,
|
|
26
|
+
displayText,
|
|
27
|
+
dim: m.kind === 'i',
|
|
28
|
+
};
|
|
29
|
+
});
|
|
36
30
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
for (const b of findAtomicBlocks(line, placeholders)) {
|
|
31
|
+
export function findAtomicBlockSpanning(line, offset, entries) {
|
|
32
|
+
for (const b of findAtomicBlocks(line, entries)) {
|
|
40
33
|
if (offset > b.start && offset < b.end)
|
|
41
34
|
return b;
|
|
42
35
|
}
|
|
43
36
|
return null;
|
|
44
37
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
for (const b of findAtomicBlocks(line, placeholders)) {
|
|
38
|
+
export function findAtomicBlockBefore(line, offset, entries) {
|
|
39
|
+
for (const b of findAtomicBlocks(line, entries)) {
|
|
48
40
|
if (b.end === offset)
|
|
49
41
|
return b;
|
|
50
42
|
}
|
|
51
43
|
return null;
|
|
52
44
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
for (const b of findAtomicBlocks(line, placeholders)) {
|
|
45
|
+
export function findAtomicBlockAfter(line, offset, entries) {
|
|
46
|
+
for (const b of findAtomicBlocks(line, entries)) {
|
|
56
47
|
if (b.start === offset)
|
|
57
48
|
return b;
|
|
58
49
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const BLOCK_OPEN = "\uE000";
|
|
2
|
+
export declare const BLOCK_CLOSE = "\uE001";
|
|
3
|
+
export type BlockMarkerKind = 'p' | 'i';
|
|
4
|
+
export interface BlockMarkerInfo {
|
|
5
|
+
kind: BlockMarkerKind;
|
|
6
|
+
id: string;
|
|
7
|
+
displayNumber: number;
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function generateBlockId(): string;
|
|
12
|
+
export declare function createBlockMarker(kind: BlockMarkerKind, id: string, displayNumber: number): string;
|
|
13
|
+
export declare function parseBlockMarkers(text: string): BlockMarkerInfo[];
|
|
14
|
+
export declare function findBlockMarkerAt(text: string, offset: number): BlockMarkerInfo | null;
|
|
15
|
+
export declare function findBlockMarkerBefore(text: string, offset: number): BlockMarkerInfo | null;
|
|
16
|
+
export declare function findBlockMarkerAfter(text: string, offset: number): BlockMarkerInfo | null;
|
|
17
|
+
export declare function removeBlockMarker(text: string, offset: number): string;
|
|
18
|
+
export declare function blockMarkerVisualWidth(displayNumber: number): number;
|
|
19
|
+
export declare function getBlockPlaceholderText(kind: BlockMarkerKind, displayNumber: number): string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export const BLOCK_OPEN = '\uE000';
|
|
2
|
+
export const BLOCK_CLOSE = '\uE001';
|
|
3
|
+
export function generateBlockId() {
|
|
4
|
+
return Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 6);
|
|
5
|
+
}
|
|
6
|
+
export function createBlockMarker(kind, id, displayNumber) {
|
|
7
|
+
return `${BLOCK_OPEN}${kind}:${id}:${displayNumber}${BLOCK_CLOSE}`;
|
|
8
|
+
}
|
|
9
|
+
export function parseBlockMarkers(text) {
|
|
10
|
+
const result = [];
|
|
11
|
+
let i = 0;
|
|
12
|
+
while (i < text.length) {
|
|
13
|
+
const openIdx = text.indexOf(BLOCK_OPEN, i);
|
|
14
|
+
if (openIdx === -1)
|
|
15
|
+
break;
|
|
16
|
+
const closeIdx = text.indexOf(BLOCK_CLOSE, openIdx + 1);
|
|
17
|
+
if (closeIdx === -1)
|
|
18
|
+
break;
|
|
19
|
+
const raw = text.substring(openIdx + 1, closeIdx);
|
|
20
|
+
const kindChar = raw[0];
|
|
21
|
+
if (kindChar !== 'p' && kindChar !== 'i') {
|
|
22
|
+
i = closeIdx + 1;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const rest = raw.substring(2);
|
|
26
|
+
const colonIdx = rest.lastIndexOf(':');
|
|
27
|
+
if (colonIdx === -1) {
|
|
28
|
+
i = closeIdx + 1;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const id = rest.substring(0, colonIdx);
|
|
32
|
+
const displayNumber = parseInt(rest.substring(colonIdx + 1), 10);
|
|
33
|
+
if (isNaN(displayNumber)) {
|
|
34
|
+
i = closeIdx + 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
result.push({
|
|
38
|
+
kind: kindChar,
|
|
39
|
+
id,
|
|
40
|
+
displayNumber,
|
|
41
|
+
start: openIdx,
|
|
42
|
+
end: closeIdx + 1,
|
|
43
|
+
});
|
|
44
|
+
i = closeIdx + 1;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
export function findBlockMarkerAt(text, offset) {
|
|
49
|
+
for (const m of parseBlockMarkers(text)) {
|
|
50
|
+
if (offset > m.start && offset < m.end)
|
|
51
|
+
return m;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
export function findBlockMarkerBefore(text, offset) {
|
|
56
|
+
for (const m of parseBlockMarkers(text)) {
|
|
57
|
+
if (m.end === offset)
|
|
58
|
+
return m;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
export function findBlockMarkerAfter(text, offset) {
|
|
63
|
+
for (const m of parseBlockMarkers(text)) {
|
|
64
|
+
if (m.start === offset)
|
|
65
|
+
return m;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
export function removeBlockMarker(text, offset) {
|
|
70
|
+
const marker = findBlockMarkerAt(text, offset);
|
|
71
|
+
if (!marker)
|
|
72
|
+
return text;
|
|
73
|
+
return text.slice(0, marker.start) + text.slice(marker.end);
|
|
74
|
+
}
|
|
75
|
+
export function blockMarkerVisualWidth(displayNumber) {
|
|
76
|
+
return getBlockPlaceholderText('i', displayNumber).length;
|
|
77
|
+
}
|
|
78
|
+
export function getBlockPlaceholderText(kind, displayNumber) {
|
|
79
|
+
if (kind === 'i') {
|
|
80
|
+
return `[Pasted Image #${displayNumber}]`;
|
|
81
|
+
}
|
|
82
|
+
return `[Paste text #${displayNumber}]`;
|
|
83
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { BlockEntry, BlockState } from './BlockTypes.js';
|
|
2
|
+
import { type BlockMarkerKind } from './BlockMarker.js';
|
|
3
|
+
import type { ImageRef } from './ImageTypes.js';
|
|
4
|
+
export declare function createBlockState(): BlockState;
|
|
5
|
+
export declare function createPasteBlockEntry(state: BlockState, originalText: string, displayText: string): {
|
|
6
|
+
id: string;
|
|
7
|
+
marker: string;
|
|
8
|
+
state: BlockState;
|
|
9
|
+
};
|
|
10
|
+
export declare function createImageBlockEntry(state: BlockState, imageRef: ImageRef, id?: string): {
|
|
11
|
+
id: string;
|
|
12
|
+
marker: string;
|
|
13
|
+
state: BlockState;
|
|
14
|
+
};
|
|
15
|
+
export declare function removeBlock(state: BlockState, id: string): BlockState;
|
|
16
|
+
export declare function getBlock(state: BlockState, id: string): BlockEntry | undefined;
|
|
17
|
+
export declare function getDisplayLine(line: string, entries: Map<string, BlockEntry>): string;
|
|
18
|
+
export declare function getValue(lines: string[], entries: Map<string, BlockEntry>): string;
|
|
19
|
+
export declare function bufferColToDisplayCol(line: string, column: number, entries: Map<string, BlockEntry>): number;
|
|
20
|
+
export declare function displayColToBufferCol(line: string, displayColumn: number, entries: Map<string, BlockEntry>): number;
|
|
21
|
+
export declare function getExpandedLineLength(line: string, entries: Map<string, BlockEntry>): number;
|
|
22
|
+
export declare function getValueCursorOffset(lines: string[], cursor: {
|
|
23
|
+
line: number;
|
|
24
|
+
column: number;
|
|
25
|
+
}, entries: Map<string, BlockEntry>): number;
|
|
26
|
+
export declare function getCursorFromValueOffset(lines: string[], offset: number, entries: Map<string, BlockEntry>): {
|
|
27
|
+
line: number;
|
|
28
|
+
column: number;
|
|
29
|
+
};
|
|
30
|
+
export declare function getDisplayWidthForMarker(m: {
|
|
31
|
+
kind: BlockMarkerKind;
|
|
32
|
+
id: string;
|
|
33
|
+
displayNumber: number;
|
|
34
|
+
}, entries: Map<string, BlockEntry>): number;
|
|
35
|
+
export declare function getDisplayTextForMarker(m: {
|
|
36
|
+
kind: BlockMarkerKind;
|
|
37
|
+
id: string;
|
|
38
|
+
displayNumber: number;
|
|
39
|
+
}, entries: Map<string, BlockEntry>): string;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { createBlockMarker, generateBlockId, parseBlockMarkers, } from './BlockMarker.js';
|
|
2
|
+
export function createBlockState() {
|
|
3
|
+
return { entries: new Map(), nextPasteNumber: 1, nextImageNumber: 1 };
|
|
4
|
+
}
|
|
5
|
+
export function createPasteBlockEntry(state, originalText, displayText) {
|
|
6
|
+
const id = generateBlockId();
|
|
7
|
+
const displayNumber = state.nextPasteNumber;
|
|
8
|
+
const marker = createBlockMarker('p', id, displayNumber);
|
|
9
|
+
const newEntries = new Map(state.entries);
|
|
10
|
+
newEntries.set(id, { kind: 'paste', id, displayNumber, originalText, displayText });
|
|
11
|
+
return {
|
|
12
|
+
id,
|
|
13
|
+
marker,
|
|
14
|
+
state: { entries: newEntries, nextPasteNumber: displayNumber + 1, nextImageNumber: state.nextImageNumber },
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function createImageBlockEntry(state, imageRef, id) {
|
|
18
|
+
const blockId = id || imageRef.id;
|
|
19
|
+
const displayNumber = imageRef.displayNumber;
|
|
20
|
+
const marker = createBlockMarker('i', blockId, displayNumber);
|
|
21
|
+
const newEntries = new Map(state.entries);
|
|
22
|
+
newEntries.set(blockId, {
|
|
23
|
+
kind: 'image',
|
|
24
|
+
id: blockId,
|
|
25
|
+
displayNumber,
|
|
26
|
+
data: imageRef.data,
|
|
27
|
+
mimeType: imageRef.mimeType,
|
|
28
|
+
byteSize: imageRef.byteSize,
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
id: blockId,
|
|
32
|
+
marker,
|
|
33
|
+
state: {
|
|
34
|
+
entries: newEntries,
|
|
35
|
+
nextPasteNumber: state.nextPasteNumber,
|
|
36
|
+
nextImageNumber: Math.max(state.nextImageNumber, displayNumber + 1),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function removeBlock(state, id) {
|
|
41
|
+
const newEntries = new Map(state.entries);
|
|
42
|
+
newEntries.delete(id);
|
|
43
|
+
return { ...state, entries: newEntries };
|
|
44
|
+
}
|
|
45
|
+
export function getBlock(state, id) {
|
|
46
|
+
return state.entries.get(id);
|
|
47
|
+
}
|
|
48
|
+
export function getDisplayLine(line, entries) {
|
|
49
|
+
const markers = parseBlockMarkers(line);
|
|
50
|
+
if (markers.length === 0)
|
|
51
|
+
return line;
|
|
52
|
+
let result = '';
|
|
53
|
+
let lastEnd = 0;
|
|
54
|
+
for (const m of markers) {
|
|
55
|
+
result += line.slice(lastEnd, m.start);
|
|
56
|
+
const entry = entries.get(m.id);
|
|
57
|
+
if (entry && entry.kind === 'paste') {
|
|
58
|
+
result += entry.displayText;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
result += m.kind === 'i' ? `[Pasted Image #${m.displayNumber}]` : `[Paste text #${m.displayNumber}]`;
|
|
62
|
+
}
|
|
63
|
+
lastEnd = m.end;
|
|
64
|
+
}
|
|
65
|
+
result += line.slice(lastEnd);
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
export function getValue(lines, entries) {
|
|
69
|
+
return lines.map((line) => {
|
|
70
|
+
const markers = parseBlockMarkers(line);
|
|
71
|
+
if (markers.length === 0)
|
|
72
|
+
return line;
|
|
73
|
+
let result = '';
|
|
74
|
+
let lastEnd = 0;
|
|
75
|
+
for (const m of markers) {
|
|
76
|
+
result += line.slice(lastEnd, m.start);
|
|
77
|
+
const entry = entries.get(m.id);
|
|
78
|
+
if (entry && entry.kind === 'paste') {
|
|
79
|
+
result += entry.originalText;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
result += line.slice(m.start, m.end);
|
|
83
|
+
}
|
|
84
|
+
lastEnd = m.end;
|
|
85
|
+
}
|
|
86
|
+
result += line.slice(lastEnd);
|
|
87
|
+
return result;
|
|
88
|
+
}).join('\n');
|
|
89
|
+
}
|
|
90
|
+
export function bufferColToDisplayCol(line, column, entries) {
|
|
91
|
+
if (entries.size === 0)
|
|
92
|
+
return column;
|
|
93
|
+
let displayCol = 0;
|
|
94
|
+
let lastEnd = 0;
|
|
95
|
+
const markers = parseBlockMarkers(line);
|
|
96
|
+
for (const m of markers) {
|
|
97
|
+
if (column <= m.start) {
|
|
98
|
+
return displayCol + (column - lastEnd);
|
|
99
|
+
}
|
|
100
|
+
displayCol += m.start - lastEnd;
|
|
101
|
+
const entry = entries.get(m.id);
|
|
102
|
+
const displayLen = entry && entry.kind === 'paste' ? entry.displayText.length : `[Pasted Image #${m.displayNumber}]`.length;
|
|
103
|
+
if (column <= m.end) {
|
|
104
|
+
return displayCol + displayLen;
|
|
105
|
+
}
|
|
106
|
+
displayCol += displayLen;
|
|
107
|
+
lastEnd = m.end;
|
|
108
|
+
}
|
|
109
|
+
return displayCol + (column - lastEnd);
|
|
110
|
+
}
|
|
111
|
+
export function displayColToBufferCol(line, displayColumn, entries) {
|
|
112
|
+
if (entries.size === 0)
|
|
113
|
+
return displayColumn;
|
|
114
|
+
let bufPos = 0;
|
|
115
|
+
let dispPos = 0;
|
|
116
|
+
const markers = parseBlockMarkers(line);
|
|
117
|
+
for (const m of markers) {
|
|
118
|
+
const textLen = m.start - bufPos;
|
|
119
|
+
if (displayColumn <= dispPos + textLen) {
|
|
120
|
+
return bufPos + (displayColumn - dispPos);
|
|
121
|
+
}
|
|
122
|
+
dispPos += textLen;
|
|
123
|
+
bufPos = m.start;
|
|
124
|
+
const entry = entries.get(m.id);
|
|
125
|
+
const displayLen = entry && entry.kind === 'paste' ? entry.displayText.length : `[Pasted Image #${m.displayNumber}]`.length;
|
|
126
|
+
if (displayColumn <= dispPos + displayLen) {
|
|
127
|
+
return m.end;
|
|
128
|
+
}
|
|
129
|
+
dispPos += displayLen;
|
|
130
|
+
bufPos = m.end;
|
|
131
|
+
}
|
|
132
|
+
return bufPos + (displayColumn - dispPos);
|
|
133
|
+
}
|
|
134
|
+
export function getExpandedLineLength(line, entries) {
|
|
135
|
+
let len = 0;
|
|
136
|
+
let lastEnd = 0;
|
|
137
|
+
const markers = parseBlockMarkers(line);
|
|
138
|
+
for (const m of markers) {
|
|
139
|
+
len += m.start - lastEnd;
|
|
140
|
+
const entry = entries.get(m.id);
|
|
141
|
+
if (entry && entry.kind === 'paste') {
|
|
142
|
+
len += entry.originalText.length;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
len += m.end - m.start;
|
|
146
|
+
}
|
|
147
|
+
lastEnd = m.end;
|
|
148
|
+
}
|
|
149
|
+
len += line.length - lastEnd;
|
|
150
|
+
return len;
|
|
151
|
+
}
|
|
152
|
+
export function getValueCursorOffset(lines, cursor, entries) {
|
|
153
|
+
let offset = 0;
|
|
154
|
+
for (let i = 0; i < cursor.line; i++) {
|
|
155
|
+
offset += getExpandedLineLength(lines[i], entries) + 1;
|
|
156
|
+
}
|
|
157
|
+
const line = lines[cursor.line];
|
|
158
|
+
let bufPos = 0;
|
|
159
|
+
const markers = parseBlockMarkers(line);
|
|
160
|
+
for (const m of markers) {
|
|
161
|
+
if (cursor.column <= m.start) {
|
|
162
|
+
offset += cursor.column - bufPos;
|
|
163
|
+
return offset;
|
|
164
|
+
}
|
|
165
|
+
offset += m.start - bufPos;
|
|
166
|
+
const entry = entries.get(m.id);
|
|
167
|
+
if (entry && entry.kind === 'paste') {
|
|
168
|
+
offset += entry.originalText.length;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
offset += m.end - m.start;
|
|
172
|
+
}
|
|
173
|
+
bufPos = m.end;
|
|
174
|
+
if (cursor.column <= m.end) {
|
|
175
|
+
return offset;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
offset += cursor.column - bufPos;
|
|
179
|
+
return offset;
|
|
180
|
+
}
|
|
181
|
+
export function getCursorFromValueOffset(lines, offset, entries) {
|
|
182
|
+
let currentOffset = 0;
|
|
183
|
+
const lineCount = lines.length;
|
|
184
|
+
for (let i = 0; i < lineCount; i++) {
|
|
185
|
+
const lineLen = getExpandedLineLength(lines[i], entries);
|
|
186
|
+
if (i === lineCount - 1) {
|
|
187
|
+
if (offset <= currentOffset + lineLen) {
|
|
188
|
+
const colInExpanded = offset - currentOffset;
|
|
189
|
+
return { line: i, column: valueOffsetToBufferColumn(lines[i], colInExpanded, entries) };
|
|
190
|
+
}
|
|
191
|
+
return { line: i, column: lines[i].length };
|
|
192
|
+
}
|
|
193
|
+
if (offset <= currentOffset + lineLen) {
|
|
194
|
+
const colInExpanded = offset - currentOffset;
|
|
195
|
+
return { line: i, column: valueOffsetToBufferColumn(lines[i], colInExpanded, entries) };
|
|
196
|
+
}
|
|
197
|
+
currentOffset += lineLen + 1;
|
|
198
|
+
}
|
|
199
|
+
const lastIdx = lines.length - 1;
|
|
200
|
+
return { line: lastIdx, column: lines[lastIdx].length };
|
|
201
|
+
}
|
|
202
|
+
function valueOffsetToBufferColumn(line, offsetInExpanded, entries) {
|
|
203
|
+
let bufPos = 0;
|
|
204
|
+
let expandedPos = 0;
|
|
205
|
+
const markers = parseBlockMarkers(line);
|
|
206
|
+
for (const m of markers) {
|
|
207
|
+
const textLen = m.start - bufPos;
|
|
208
|
+
if (offsetInExpanded <= expandedPos + textLen) {
|
|
209
|
+
return bufPos + (offsetInExpanded - expandedPos);
|
|
210
|
+
}
|
|
211
|
+
expandedPos += textLen;
|
|
212
|
+
bufPos = m.start;
|
|
213
|
+
const entry = entries.get(m.id);
|
|
214
|
+
const originalLen = entry && entry.kind === 'paste' ? entry.originalText.length : (m.end - m.start);
|
|
215
|
+
if (offsetInExpanded <= expandedPos + originalLen) {
|
|
216
|
+
return m.end;
|
|
217
|
+
}
|
|
218
|
+
expandedPos += originalLen;
|
|
219
|
+
bufPos = m.end;
|
|
220
|
+
}
|
|
221
|
+
return bufPos + (offsetInExpanded - expandedPos);
|
|
222
|
+
}
|
|
223
|
+
export function getDisplayWidthForMarker(m, entries) {
|
|
224
|
+
const entry = entries.get(m.id);
|
|
225
|
+
if (entry && entry.kind === 'paste') {
|
|
226
|
+
return entry.displayText.length;
|
|
227
|
+
}
|
|
228
|
+
return `[Pasted Image #${m.displayNumber}]`.length;
|
|
229
|
+
}
|
|
230
|
+
export function getDisplayTextForMarker(m, entries) {
|
|
231
|
+
const entry = entries.get(m.id);
|
|
232
|
+
if (entry && entry.kind === 'paste') {
|
|
233
|
+
return entry.displayText;
|
|
234
|
+
}
|
|
235
|
+
return `[Pasted Image #${m.displayNumber}]`;
|
|
236
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type BlockKind = 'paste' | 'image';
|
|
2
|
+
export interface PasteBlockEntry {
|
|
3
|
+
kind: 'paste';
|
|
4
|
+
id: string;
|
|
5
|
+
displayNumber: number;
|
|
6
|
+
originalText: string;
|
|
7
|
+
displayText: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ImageBlockEntry {
|
|
10
|
+
kind: 'image';
|
|
11
|
+
id: string;
|
|
12
|
+
displayNumber: number;
|
|
13
|
+
data: string;
|
|
14
|
+
mimeType: string;
|
|
15
|
+
byteSize: number;
|
|
16
|
+
}
|
|
17
|
+
export type BlockEntry = PasteBlockEntry | ImageBlockEntry;
|
|
18
|
+
export interface BlockState {
|
|
19
|
+
entries: Map<string, BlockEntry>;
|
|
20
|
+
nextPasteNumber: number;
|
|
21
|
+
nextImageNumber: number;
|
|
22
|
+
}
|
|
@@ -6,5 +6,3 @@ export interface ImageRef {
|
|
|
6
6
|
displayNumber: number;
|
|
7
7
|
}
|
|
8
8
|
export type PasteErrorReason = 'clipboard-timeout' | 'clipboard-read-error' | 'clipboard-unsupported-type' | 'image-too-large' | 'too-many-images' | 'clipboard-empty';
|
|
9
|
-
export declare const SENTINEL_OPEN = "\uE000";
|
|
10
|
-
export declare const SENTINEL_CLOSE = "\uE001";
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export const SENTINEL_CLOSE = '\uE001';
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Key, type Buffer, type Cursor } from './types.js';
|
|
2
2
|
import { type UseTextInputResult } from './useTextInput.js';
|
|
3
|
-
export interface KeyHandlerActions extends Omit<UseTextInputResult, 'value' | 'cursor' | 'cursorOffset' | 'setCursorOffset' | 'buffer' | '
|
|
3
|
+
export interface KeyHandlerActions extends Omit<UseTextInputResult, 'value' | 'cursor' | 'cursorOffset' | 'setCursorOffset' | 'buffer' | 'blockState' | 'insertImage' | 'images' | 'getImages' | 'setImages'> {
|
|
4
4
|
submit: () => void;
|
|
5
5
|
onBoundaryArrow?: (direction: 'up' | 'down' | 'left' | 'right') => void;
|
|
6
6
|
paste?: () => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Buffer, Cursor, Direction } from './types.js';
|
|
2
|
-
import { type
|
|
2
|
+
import { type BlockEntries } from './AtomicBlocks.js';
|
|
3
3
|
/**
|
|
4
4
|
* Create a new buffer from optional initial text
|
|
5
5
|
*/
|
|
@@ -15,14 +15,14 @@ export declare function insertText(buffer: Buffer, cursor: Cursor, text: string)
|
|
|
15
15
|
/**
|
|
16
16
|
* Delete character before cursor (backspace)
|
|
17
17
|
*/
|
|
18
|
-
export declare function deleteChar(buffer: Buffer, cursor: Cursor,
|
|
18
|
+
export declare function deleteChar(buffer: Buffer, cursor: Cursor, entries?: BlockEntries): {
|
|
19
19
|
buffer: Buffer;
|
|
20
20
|
cursor: Cursor;
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
23
|
* Delete character after cursor (forward delete / Delete key)
|
|
24
24
|
*/
|
|
25
|
-
export declare function deleteCharForward(buffer: Buffer, cursor: Cursor,
|
|
25
|
+
export declare function deleteCharForward(buffer: Buffer, cursor: Cursor, entries?: BlockEntries): {
|
|
26
26
|
buffer: Buffer;
|
|
27
27
|
cursor: Cursor;
|
|
28
28
|
};
|
|
@@ -33,39 +33,13 @@ export declare function insertNewLine(buffer: Buffer, cursor: Cursor): {
|
|
|
33
33
|
buffer: Buffer;
|
|
34
34
|
cursor: Cursor;
|
|
35
35
|
};
|
|
36
|
-
/**
|
|
37
|
-
* Information about a visual row within a wrapped line.
|
|
38
|
-
*/
|
|
39
36
|
interface VisualRowInfo {
|
|
40
|
-
/** Starting offset in the buffer line */
|
|
41
37
|
start: number;
|
|
42
|
-
/** Length of this visual row */
|
|
43
38
|
length: number;
|
|
44
39
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* Words are kept intact when possible, breaking at spaces.
|
|
48
|
-
* Long words that exceed width are hard-wrapped.
|
|
49
|
-
* Sentinel blocks are atomic: never split, and occupy visual width
|
|
50
|
-
* equal to their placeholder text length.
|
|
51
|
-
*/
|
|
52
|
-
export declare function getVisualRows(line: string, width: number, placeholders?: Placeholders): VisualRowInfo[];
|
|
53
|
-
/**
|
|
54
|
-
* Move cursor in specified direction with bounds checking.
|
|
55
|
-
* When width is provided, up/down movement is based on visual lines (accounting for wrapping).
|
|
56
|
-
* When width is not provided, up/down movement is based on buffer lines.
|
|
57
|
-
*/
|
|
58
|
-
export declare function moveCursor(buffer: Buffer, cursor: Cursor, direction: Direction, width?: number, placeholders?: Placeholders): Cursor;
|
|
59
|
-
/**
|
|
60
|
-
* Get the full text content from buffer (lines joined with newlines)
|
|
61
|
-
*/
|
|
40
|
+
export declare function getVisualRows(line: string, width: number, entries?: BlockEntries): VisualRowInfo[];
|
|
41
|
+
export declare function moveCursor(buffer: Buffer, cursor: Cursor, direction: Direction, width?: number, entries?: BlockEntries): Cursor;
|
|
62
42
|
export declare function getTextContent(buffer: Buffer): string;
|
|
63
|
-
/**
|
|
64
|
-
* Get the flat offset (index) from a cursor position.
|
|
65
|
-
*/
|
|
66
43
|
export declare function getOffset(buffer: Buffer, cursor: Cursor): number;
|
|
67
|
-
/**
|
|
68
|
-
* Get the cursor position from a flat offset.
|
|
69
|
-
*/
|
|
70
44
|
export declare function getCursor(buffer: Buffer, offset: number): Cursor;
|
|
71
45
|
export {};
|