ink-prompt 0.2.3 → 0.2.5

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.
Files changed (67) hide show
  1. package/README.md +3 -3
  2. package/dist/components/MultilineInput/AtomicBlocks.d.ts +10 -20
  3. package/dist/components/MultilineInput/AtomicBlocks.js +33 -42
  4. package/dist/components/MultilineInput/BlockMarker.d.ts +19 -0
  5. package/dist/components/MultilineInput/BlockMarker.js +83 -0
  6. package/dist/components/MultilineInput/BlockRegistry.d.ts +39 -0
  7. package/dist/components/MultilineInput/BlockRegistry.js +236 -0
  8. package/dist/components/MultilineInput/BlockTypes.d.ts +22 -0
  9. package/dist/components/MultilineInput/ImageTypes.d.ts +0 -2
  10. package/dist/components/MultilineInput/ImageTypes.js +1 -2
  11. package/dist/components/MultilineInput/ImageValidator.js +1 -1
  12. package/dist/components/MultilineInput/KeyHandler.d.ts +1 -1
  13. package/dist/components/MultilineInput/KeyHandler.js +18 -0
  14. package/dist/components/MultilineInput/TextBuffer.d.ts +5 -31
  15. package/dist/components/MultilineInput/TextBuffer.js +91 -161
  16. package/dist/components/MultilineInput/TextRenderer.d.ts +6 -7
  17. package/dist/components/MultilineInput/TextRenderer.js +7 -7
  18. package/dist/components/MultilineInput/__tests__/BlockMarker.test.js +130 -0
  19. package/dist/components/MultilineInput/__tests__/BlockRegistry.test.js +225 -0
  20. package/dist/components/MultilineInput/__tests__/KeyHandler.test.js +18 -0
  21. package/dist/components/MultilineInput/__tests__/Placeholder.integration.test.js +44 -65
  22. package/dist/components/MultilineInput/__tests__/TextBuffer_images.test.js +10 -31
  23. package/dist/components/MultilineInput/__tests__/TextRenderer_images.test.js +27 -13
  24. package/dist/components/MultilineInput/__tests__/integration_images.test.js +2 -4
  25. package/dist/components/MultilineInput/__tests__/useTextInput_images.test.js +30 -29
  26. package/dist/components/MultilineInput/index.d.ts +6 -6
  27. package/dist/components/MultilineInput/index.js +2 -11
  28. package/dist/components/MultilineInput/types.d.ts +0 -20
  29. package/dist/components/MultilineInput/useTextInput.d.ts +4 -11
  30. package/dist/components/MultilineInput/useTextInput.js +79 -76
  31. package/package.json +1 -1
  32. package/dist/components/MultilineInput/ImageSentinel.d.ts +0 -15
  33. package/dist/components/MultilineInput/ImageSentinel.js +0 -62
  34. package/dist/components/MultilineInput/Placeholder.d.ts +0 -30
  35. package/dist/components/MultilineInput/Placeholder.js +0 -200
  36. package/dist/components/MultilineInput/__tests__/ImageSentinel.test.js +0 -154
  37. package/dist/components/MultilineInput/__tests__/Placeholder.test.js +0 -235
  38. package/dist/examples/examples/basic.js +0 -9
  39. package/dist/examples/src/components/MultilineInput/KeyHandler.d.ts +0 -15
  40. package/dist/examples/src/components/MultilineInput/KeyHandler.js +0 -97
  41. package/dist/examples/src/components/MultilineInput/TextBuffer.d.ts +0 -34
  42. package/dist/examples/src/components/MultilineInput/TextBuffer.js +0 -127
  43. package/dist/examples/src/components/MultilineInput/TextRenderer.d.ts +0 -24
  44. package/dist/examples/src/components/MultilineInput/TextRenderer.js +0 -72
  45. package/dist/examples/src/components/MultilineInput/__tests__/KeyHandler.test.d.ts +0 -1
  46. package/dist/examples/src/components/MultilineInput/__tests__/KeyHandler.test.js +0 -115
  47. package/dist/examples/src/components/MultilineInput/__tests__/TextBuffer.test.d.ts +0 -1
  48. package/dist/examples/src/components/MultilineInput/__tests__/TextBuffer.test.js +0 -254
  49. package/dist/examples/src/components/MultilineInput/__tests__/TextRenderer.test.d.ts +0 -1
  50. package/dist/examples/src/components/MultilineInput/__tests__/TextRenderer.test.js +0 -176
  51. package/dist/examples/src/components/MultilineInput/__tests__/integration.test.d.ts +0 -1
  52. package/dist/examples/src/components/MultilineInput/__tests__/integration.test.js +0 -71
  53. package/dist/examples/src/components/MultilineInput/__tests__/useTextInput.test.d.ts +0 -1
  54. package/dist/examples/src/components/MultilineInput/__tests__/useTextInput.test.js +0 -65
  55. package/dist/examples/src/components/MultilineInput/index.d.ts +0 -39
  56. package/dist/examples/src/components/MultilineInput/index.js +0 -82
  57. package/dist/examples/src/components/MultilineInput/types.d.ts +0 -55
  58. package/dist/examples/src/components/MultilineInput/types.js +0 -1
  59. package/dist/examples/src/components/MultilineInput/useTextInput.d.ts +0 -16
  60. package/dist/examples/src/components/MultilineInput/useTextInput.js +0 -82
  61. package/dist/examples/src/hello.test.d.ts +0 -1
  62. package/dist/examples/src/hello.test.js +0 -13
  63. package/dist/examples/src/index.d.ts +0 -2
  64. package/dist/examples/src/index.js +0 -2
  65. /package/dist/components/MultilineInput/{__tests__/ImageSentinel.test.d.ts → BlockTypes.js} +0 -0
  66. /package/dist/components/MultilineInput/__tests__/{Placeholder.test.d.ts → BlockMarker.test.d.ts} +0 -0
  67. /package/dist/{examples/examples/basic.d.ts → components/MultilineInput/__tests__/BlockRegistry.test.d.ts} +0 -0
@@ -1,200 +0,0 @@
1
- export const MARKER_REGEX = /\x00P(\d+)\x00/g;
2
- export function createMarker(id) {
3
- return `\x00P${id}\x00`;
4
- }
5
- export function createPlaceholderState() {
6
- return { placeholders: new Map(), nextId: 0 };
7
- }
8
- export function addPlaceholder(state, originalText, displayText) {
9
- const id = state.nextId;
10
- const marker = createMarker(id);
11
- const newPlaceholders = new Map(state.placeholders);
12
- newPlaceholders.set(id, { id, originalText, displayText });
13
- return { id, marker, state: { placeholders: newPlaceholders, nextId: id + 1 } };
14
- }
15
- export function removePlaceholder(state, id) {
16
- const newPlaceholders = new Map(state.placeholders);
17
- newPlaceholders.delete(id);
18
- return { ...state, placeholders: newPlaceholders };
19
- }
20
- export function getDisplayLine(line, placeholders) {
21
- return line.replace(MARKER_REGEX, (_, idStr) => {
22
- const info = placeholders.get(Number(idStr));
23
- return info ? info.displayText : '';
24
- });
25
- }
26
- export function getValue(lines, placeholders) {
27
- const text = lines.join('\n');
28
- return text.replace(MARKER_REGEX, (_, idStr) => {
29
- const info = placeholders.get(Number(idStr));
30
- return info ? info.originalText : '';
31
- });
32
- }
33
- export function findPlaceholderAt(line, column) {
34
- MARKER_REGEX.lastIndex = 0;
35
- let match;
36
- while ((match = MARKER_REGEX.exec(line)) !== null) {
37
- const start = match.index;
38
- const end = start + match[0].length;
39
- if (column > start && column < end) {
40
- return { id: Number(match[1]), start, end };
41
- }
42
- }
43
- return null;
44
- }
45
- export function findPlaceholderAfter(line, column) {
46
- MARKER_REGEX.lastIndex = 0;
47
- let match;
48
- while ((match = MARKER_REGEX.exec(line)) !== null) {
49
- if (match.index === column) {
50
- return { id: Number(match[1]), start: match.index, end: match.index + match[0].length };
51
- }
52
- }
53
- return null;
54
- }
55
- export function findPlaceholderBefore(line, column) {
56
- MARKER_REGEX.lastIndex = 0;
57
- let match;
58
- while ((match = MARKER_REGEX.exec(line)) !== null) {
59
- const end = match.index + match[0].length;
60
- if (end === column) {
61
- return { id: Number(match[1]), start: match.index, end };
62
- }
63
- }
64
- return null;
65
- }
66
- export function bufferColToDisplayCol(line, column, placeholders) {
67
- if (!placeholders || placeholders.size === 0)
68
- return column;
69
- let displayCol = 0;
70
- let lastEnd = 0;
71
- MARKER_REGEX.lastIndex = 0;
72
- let match;
73
- while ((match = MARKER_REGEX.exec(line)) !== null) {
74
- const markerStart = match.index;
75
- const markerEnd = markerStart + match[0].length;
76
- if (column <= markerStart) {
77
- return displayCol + (column - lastEnd);
78
- }
79
- displayCol += markerStart - lastEnd;
80
- const info = placeholders.get(Number(match[1]));
81
- const displayLen = info ? info.displayText.length : 0;
82
- if (column <= markerEnd) {
83
- return displayCol + displayLen;
84
- }
85
- displayCol += displayLen;
86
- lastEnd = markerEnd;
87
- }
88
- return displayCol + (column - lastEnd);
89
- }
90
- export function displayColToBufferCol(line, displayColumn, placeholders) {
91
- if (!placeholders || placeholders.size === 0)
92
- return displayColumn;
93
- let bufPos = 0;
94
- let dispPos = 0;
95
- MARKER_REGEX.lastIndex = 0;
96
- let match;
97
- while ((match = MARKER_REGEX.exec(line)) !== null) {
98
- const markerStart = match.index;
99
- const markerEnd = markerStart + match[0].length;
100
- const textLen = markerStart - bufPos;
101
- if (displayColumn <= dispPos + textLen) {
102
- return bufPos + (displayColumn - dispPos);
103
- }
104
- dispPos += textLen;
105
- bufPos = markerStart;
106
- const info = placeholders.get(Number(match[1]));
107
- const displayLen = info ? info.displayText.length : 0;
108
- if (displayColumn <= dispPos + displayLen) {
109
- return markerEnd;
110
- }
111
- dispPos += displayLen;
112
- bufPos = markerEnd;
113
- }
114
- return bufPos + (displayColumn - dispPos);
115
- }
116
- export function getValueCursorOffset(lines, cursor, placeholders) {
117
- let offset = 0;
118
- for (let i = 0; i < cursor.line; i++) {
119
- offset += getExpandedLineLength(lines[i], placeholders) + 1;
120
- }
121
- const line = lines[cursor.line];
122
- let bufPos = 0;
123
- MARKER_REGEX.lastIndex = 0;
124
- let match;
125
- while ((match = MARKER_REGEX.exec(line)) !== null) {
126
- const markerStart = match.index;
127
- const markerEnd = markerStart + match[0].length;
128
- if (cursor.column <= markerStart) {
129
- offset += cursor.column - bufPos;
130
- return offset;
131
- }
132
- offset += markerStart - bufPos;
133
- const info = placeholders.get(Number(match[1]));
134
- offset += info ? info.originalText.length : 0;
135
- bufPos = markerEnd;
136
- if (cursor.column <= markerEnd) {
137
- return offset;
138
- }
139
- }
140
- offset += cursor.column - bufPos;
141
- return offset;
142
- }
143
- export function getCursorFromValueOffset(lines, offset, placeholders) {
144
- let currentOffset = 0;
145
- const lineCount = lines.length;
146
- for (let i = 0; i < lineCount; i++) {
147
- const lineLen = getExpandedLineLength(lines[i], placeholders);
148
- if (i === lineCount - 1) {
149
- if (offset <= currentOffset + lineLen) {
150
- const colInExpanded = offset - currentOffset;
151
- return { line: i, column: valueOffsetToBufferColumn(lines[i], colInExpanded, placeholders) };
152
- }
153
- return { line: i, column: lines[i].length };
154
- }
155
- if (offset <= currentOffset + lineLen) {
156
- const colInExpanded = offset - currentOffset;
157
- return { line: i, column: valueOffsetToBufferColumn(lines[i], colInExpanded, placeholders) };
158
- }
159
- currentOffset += lineLen + 1;
160
- }
161
- const lastIdx = lines.length - 1;
162
- return { line: lastIdx, column: lines[lastIdx].length };
163
- }
164
- function getExpandedLineLength(line, placeholders) {
165
- let len = 0;
166
- let lastEnd = 0;
167
- MARKER_REGEX.lastIndex = 0;
168
- let match;
169
- while ((match = MARKER_REGEX.exec(line)) !== null) {
170
- len += match.index - lastEnd;
171
- const info = placeholders.get(Number(match[1]));
172
- len += info ? info.originalText.length : 0;
173
- lastEnd = match.index + match[0].length;
174
- }
175
- len += line.length - lastEnd;
176
- return len;
177
- }
178
- function valueOffsetToBufferColumn(line, offsetInExpanded, placeholders) {
179
- let bufPos = 0;
180
- let expandedPos = 0;
181
- MARKER_REGEX.lastIndex = 0;
182
- let match;
183
- while ((match = MARKER_REGEX.exec(line)) !== null) {
184
- const markerStart = match.index;
185
- const textLen = markerStart - bufPos;
186
- if (offsetInExpanded <= expandedPos + textLen) {
187
- return bufPos + (offsetInExpanded - expandedPos);
188
- }
189
- expandedPos += textLen;
190
- bufPos = markerStart;
191
- const info = placeholders.get(Number(match[1]));
192
- const originalLen = info ? info.originalText.length : 0;
193
- if (offsetInExpanded <= expandedPos + originalLen) {
194
- return markerStart + match[0].length;
195
- }
196
- expandedPos += originalLen;
197
- bufPos = markerStart + match[0].length;
198
- }
199
- return bufPos + (offsetInExpanded - expandedPos);
200
- }
@@ -1,154 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { generateImageId, createSentinel, parseSentinels, findSentinelAt, isInsideSentinel, removeSentinel, getPlaceholderText, getPlaceholderVisualWidth, } from '../ImageSentinel.js';
3
- import { SENTINEL_OPEN, SENTINEL_CLOSE } from '../ImageTypes.js';
4
- describe('ImageSentinel', () => {
5
- describe('generateImageId', () => {
6
- it('generates a string id', () => {
7
- const id = generateImageId();
8
- expect(typeof id).toBe('string');
9
- expect(id.length).toBeGreaterThan(0);
10
- });
11
- it('generates unique ids', () => {
12
- const ids = new Set(Array.from({ length: 100 }, () => generateImageId()));
13
- expect(ids.size).toBe(100);
14
- });
15
- });
16
- describe('createSentinel', () => {
17
- it('creates a sentinel block with the given id and display number', () => {
18
- const result = createSentinel('abc123', 1);
19
- expect(result).toBe(`${SENTINEL_OPEN}abc123:1${SENTINEL_CLOSE}`);
20
- });
21
- it('creates a sentinel with higher display number', () => {
22
- const result = createSentinel('def456', 42);
23
- expect(result).toBe(`${SENTINEL_OPEN}def456:42${SENTINEL_CLOSE}`);
24
- });
25
- });
26
- describe('parseSentinels', () => {
27
- it('returns empty array for text without sentinels', () => {
28
- expect(parseSentinels('hello world')).toEqual([]);
29
- });
30
- it('finds a single sentinel', () => {
31
- const text = `hello ${SENTINEL_OPEN}abc123:1${SENTINEL_CLOSE} world`;
32
- const result = parseSentinels(text);
33
- expect(result).toEqual([
34
- { id: 'abc123', displayNumber: 1, start: 6, end: 16 },
35
- ]);
36
- });
37
- it('finds multiple sentinels', () => {
38
- const text = `${SENTINEL_OPEN}id1:1${SENTINEL_CLOSE}hello${SENTINEL_OPEN}id2:2${SENTINEL_CLOSE}`;
39
- const result = parseSentinels(text);
40
- expect(result).toEqual([
41
- { id: 'id1', displayNumber: 1, start: 0, end: 7 },
42
- { id: 'id2', displayNumber: 2, start: 12, end: 19 },
43
- ]);
44
- });
45
- it('returns empty for unmatched opener', () => {
46
- const text = `hello ${SENTINEL_OPEN}abc:1`;
47
- const result = parseSentinels(text);
48
- expect(result).toEqual([]);
49
- });
50
- it('returns empty for unmatched closer', () => {
51
- const text = `hello abc:1${SENTINEL_CLOSE} world`;
52
- const result = parseSentinels(text);
53
- expect(result).toEqual([]);
54
- });
55
- });
56
- describe('findSentinelAt', () => {
57
- it('returns null when offset is not near a sentinel', () => {
58
- const text = `hello ${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE} world`;
59
- expect(findSentinelAt(text, 0)).toBeNull();
60
- });
61
- it('finds sentinel when offset is at the opener', () => {
62
- const text = `hello ${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE} world`;
63
- const result = findSentinelAt(text, 6);
64
- expect(result).toMatchObject({ id: 'abc', displayNumber: 1, start: 6 });
65
- });
66
- it('finds sentinel when offset is inside the id', () => {
67
- const text = `hello ${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE} world`;
68
- const result = findSentinelAt(text, 8);
69
- expect(result).toMatchObject({ id: 'abc', displayNumber: 1 });
70
- });
71
- it('finds sentinel when offset is at the closer', () => {
72
- const text = `hello ${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE} world`;
73
- const result = findSentinelAt(text, 12);
74
- expect(result).toMatchObject({ id: 'abc', displayNumber: 1 });
75
- });
76
- it('finds sentinel when offset is right after the closer', () => {
77
- const text = `${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE}`;
78
- const result = findSentinelAt(text, 7);
79
- expect(result).toMatchObject({ id: 'abc', displayNumber: 1, start: 0, end: 7 });
80
- });
81
- });
82
- describe('isInsideSentinel', () => {
83
- it('returns false when offset is before any sentinel', () => {
84
- const text = `hi ${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE}`;
85
- expect(isInsideSentinel(text, 0)).toBe(false);
86
- });
87
- it('returns false when offset is after sentinel', () => {
88
- const text = `${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE} world`;
89
- expect(isInsideSentinel(text, 10)).toBe(false);
90
- });
91
- it('returns true when offset is at the opener', () => {
92
- const text = `${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE}`;
93
- expect(isInsideSentinel(text, 0)).toBe(true);
94
- });
95
- it('returns true when offset is inside the id', () => {
96
- const text = `${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE}`;
97
- expect(isInsideSentinel(text, 2)).toBe(true);
98
- });
99
- it('returns true when offset is at the closer', () => {
100
- const text = `${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE}`;
101
- expect(isInsideSentinel(text, 6)).toBe(true);
102
- });
103
- it('returns false when offset is after the closer', () => {
104
- const text = `${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE}`;
105
- expect(isInsideSentinel(text, 7)).toBe(false);
106
- });
107
- });
108
- describe('removeSentinel', () => {
109
- it('removes sentinel block at cursor position', () => {
110
- const text = `hello ${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE} world`;
111
- const result = removeSentinel(text, 10);
112
- expect(result).toBe('hello world');
113
- });
114
- it('removes sentinel when cursor is at the opener', () => {
115
- const text = `${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE}hello`;
116
- const result = removeSentinel(text, 0);
117
- expect(result).toBe('hello');
118
- });
119
- it('removes sentinel when cursor is at the closer', () => {
120
- const text = `hello${SENTINEL_OPEN}abc:1${SENTINEL_CLOSE}`;
121
- const result = removeSentinel(text, 11);
122
- expect(result).toBe('hello');
123
- });
124
- it('removes first sentinel when cursor is between two', () => {
125
- const text = `${SENTINEL_OPEN}a:1${SENTINEL_CLOSE}${SENTINEL_OPEN}b:2${SENTINEL_CLOSE}`;
126
- const result = removeSentinel(text, 1);
127
- expect(result).toBe(`${SENTINEL_OPEN}b:2${SENTINEL_CLOSE}`);
128
- });
129
- it('returns text unchanged if no sentinel at offset', () => {
130
- const text = 'hello world';
131
- const result = removeSentinel(text, 3);
132
- expect(result).toBe('hello world');
133
- });
134
- });
135
- describe('getPlaceholderText', () => {
136
- it('returns correct placeholder for display number 1', () => {
137
- expect(getPlaceholderText(1)).toBe('[Pasted Image #1]');
138
- });
139
- it('returns correct placeholder for display number 42', () => {
140
- expect(getPlaceholderText(42)).toBe('[Pasted Image #42]');
141
- });
142
- });
143
- describe('getPlaceholderVisualWidth', () => {
144
- it('returns correct width for display number 1', () => {
145
- expect(getPlaceholderVisualWidth(1)).toBe(17);
146
- });
147
- it('returns correct width for display number 100', () => {
148
- expect(getPlaceholderVisualWidth(100)).toBe(19);
149
- });
150
- it('returns correct width for display number 0', () => {
151
- expect(getPlaceholderVisualWidth(0)).toBe(17);
152
- });
153
- });
154
- });
@@ -1,235 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { createMarker, createPlaceholderState, addPlaceholder, removePlaceholder, getDisplayLine, getValue, findPlaceholderAt, findPlaceholderAfter, findPlaceholderBefore, bufferColToDisplayCol, displayColToBufferCol, getValueCursorOffset, getCursorFromValueOffset, } from '../Placeholder.js';
3
- const displayText = (id) => `[Paste text #${id}]`;
4
- describe('Placeholder', () => {
5
- describe('createMarker', () => {
6
- it('creates marker for given id', () => {
7
- const marker = createMarker(1);
8
- expect(marker).toBe('\x00P1\x00');
9
- expect(marker.length).toBe(4); // Each \x00 is one char: \x00 + P + 1 + \x00
10
- });
11
- it('creates marker for id 42', () => {
12
- expect(createMarker(42)).toBe('\x00P42\x00');
13
- expect(createMarker(42).length).toBe(5);
14
- });
15
- });
16
- describe('createPlaceholderState', () => {
17
- it('creates empty state', () => {
18
- const state = createPlaceholderState();
19
- expect(state.placeholders.size).toBe(0);
20
- expect(state.nextId).toBe(0);
21
- });
22
- });
23
- describe('addPlaceholder', () => {
24
- it('adds a placeholder and increments nextId', () => {
25
- const state = createPlaceholderState();
26
- const result = addPlaceholder(state, 'original text', displayText(0));
27
- expect(result.id).toBe(0);
28
- expect(result.marker).toBe('\x00P0\x00');
29
- expect(result.state.nextId).toBe(1);
30
- expect(result.state.placeholders.get(0)?.originalText).toBe('original text');
31
- expect(result.state.placeholders.get(0)?.displayText).toBe('[Paste text #0]');
32
- });
33
- it('increments id for sequential additions', () => {
34
- let state = createPlaceholderState();
35
- const r1 = addPlaceholder(state, 'text1', displayText(0));
36
- expect(r1.id).toBe(0);
37
- state = r1.state;
38
- const r2 = addPlaceholder(state, 'text2', displayText(1));
39
- expect(r2.id).toBe(1);
40
- state = r2.state;
41
- expect(state.placeholders.size).toBe(2);
42
- expect(state.nextId).toBe(2);
43
- });
44
- it('does not mutate original state', () => {
45
- const state = createPlaceholderState();
46
- addPlaceholder(state, 'text', displayText(0));
47
- expect(state.placeholders.size).toBe(0);
48
- expect(state.nextId).toBe(0);
49
- });
50
- });
51
- describe('removePlaceholder', () => {
52
- it('removes a placeholder from state', () => {
53
- let state = createPlaceholderState();
54
- const r = addPlaceholder(state, 'text', displayText(0));
55
- state = r.state;
56
- state = removePlaceholder(state, 0);
57
- expect(state.placeholders.size).toBe(0);
58
- expect(state.nextId).toBe(1); // nextId is not decremented
59
- });
60
- });
61
- describe('getDisplayLine', () => {
62
- it('replaces markers with display text', () => {
63
- const state = createPlaceholderState();
64
- const r = addPlaceholder(state, 'original long text', '[Paste text #1]');
65
- const placeholders = r.state.placeholders;
66
- const line = `Hello ${createMarker(0)} world`;
67
- const display = getDisplayLine(line, placeholders);
68
- expect(display).toBe('Hello [Paste text #1] world');
69
- });
70
- it('handles line without markers', () => {
71
- const display = getDisplayLine('Hello world', new Map());
72
- expect(display).toBe('Hello world');
73
- });
74
- });
75
- describe('getValue', () => {
76
- it('replaces markers with original text', () => {
77
- const state = createPlaceholderState();
78
- const r = addPlaceholder(state, 'hello\nworld\nfoo', displayText(0));
79
- const placeholders = r.state.placeholders;
80
- const value = getValue([`Hello ${createMarker(0)} bar`], placeholders);
81
- expect(value).toBe('Hello hello\nworld\nfoo bar');
82
- });
83
- it('handles lines without markers', () => {
84
- const value = getValue(['Hello', 'world'], new Map());
85
- expect(value).toBe('Hello\nworld');
86
- });
87
- });
88
- describe('findPlaceholderAt', () => {
89
- it('finds marker containing a position', () => {
90
- const line = `ab\x00P1\x00cd`;
91
- // marker is at columns 2-5 (0-indexed), length 4: \x00, P, 1, \x00
92
- // So column 2 is \x00 (start), 3 is P, 4 is 1, 5 is \x00 (end)
93
- // Column 2,3,4,5 are the marker (start inclusive, end exclusive would be columns 2-6)
94
- // Actually, findPlaceholderAt checks column > start && column < end
95
- // start = 2, end = 2 + 4 = 6
96
- // So columns 3,4,5 are "inside" the marker
97
- const result = findPlaceholderAt(line, 3);
98
- expect(result).not.toBeNull();
99
- expect(result.id).toBe(1);
100
- expect(result.start).toBe(2);
101
- expect(result.end).toBe(6);
102
- // Not inside marker
103
- expect(findPlaceholderAt(line, 1)).toBeNull();
104
- expect(findPlaceholderAt(line, 7)).toBeNull();
105
- });
106
- it('returns null when no marker at position', () => {
107
- expect(findPlaceholderAt('hello', 0)).toBeNull();
108
- });
109
- });
110
- describe('findPlaceholderAfter', () => {
111
- it('finds marker starting at given column', () => {
112
- const line = `ab\x00P1\x00cd`;
113
- const result = findPlaceholderAfter(line, 2);
114
- expect(result).not.toBeNull();
115
- expect(result.id).toBe(1);
116
- expect(result.start).toBe(2);
117
- expect(result.end).toBe(6);
118
- });
119
- it('returns null when no marker starts at column', () => {
120
- expect(findPlaceholderAfter('hello', 0)).toBeNull();
121
- });
122
- });
123
- describe('findPlaceholderBefore', () => {
124
- it('finds marker ending at given column', () => {
125
- const line = `ab\x00P1\x00cd`;
126
- const result = findPlaceholderBefore(line, 6);
127
- expect(result).not.toBeNull();
128
- expect(result.id).toBe(1);
129
- expect(result.start).toBe(2);
130
- expect(result.end).toBe(6);
131
- });
132
- it('returns null when no marker ends at column', () => {
133
- expect(findPlaceholderBefore('hello', 0)).toBeNull();
134
- });
135
- });
136
- describe('bufferColToDisplayCol', () => {
137
- it('converts column before marker', () => {
138
- const state = createPlaceholderState();
139
- const r = addPlaceholder(state, 'original', '[Paste text #1]');
140
- const line = `ab\x00P0\x00cd`;
141
- expect(bufferColToDisplayCol(line, 0, r.state.placeholders)).toBe(0);
142
- expect(bufferColToDisplayCol(line, 1, r.state.placeholders)).toBe(1);
143
- });
144
- it('converts column after marker', () => {
145
- const state = createPlaceholderState();
146
- const r = addPlaceholder(state, 'original', '[Paste text #1]');
147
- const line = `ab\x00P0\x00cd`;
148
- // buffer marker is 4 chars, display is 15 chars
149
- // buffer: a(0) b(1) \x00(2) P(3) 0(4) \x00(5) c(6) d(7)
150
- // But wait, \x00 is one char. Let me count:
151
- // ab = 2 chars, \x00P0\x00 = 4 chars, cd = 2 chars, total = 8 chars
152
- // Marker at 2-5 (4 chars)
153
- // display: ab[Paste text #1]cd
154
- // ab = 2, [Paste text #1] = 15, cd = 2, total = 19
155
- // Buffer col 6 (c) → display col 2 + 15 = 17
156
- expect(bufferColToDisplayCol(line, 6, r.state.placeholders)).toBe(17);
157
- });
158
- it('converts column at marker start', () => {
159
- const state = createPlaceholderState();
160
- const r = addPlaceholder(state, 'original', '[Paste text #1]');
161
- const line = `ab\x00P0\x00cd`;
162
- // Buffer col 2 (start of marker) → display col 2 (start of display text)
163
- expect(bufferColToDisplayCol(line, 2, r.state.placeholders)).toBe(2);
164
- });
165
- it('converts column at marker end', () => {
166
- const state = createPlaceholderState();
167
- const r = addPlaceholder(state, 'original', '[Paste text #1]');
168
- const line = `ab\x00P0\x00cd`;
169
- // Buffer col 6 (end of marker = start of 'c') → display col 2 + 15 = 17
170
- expect(bufferColToDisplayCol(line, 6, r.state.placeholders)).toBe(17);
171
- });
172
- });
173
- describe('displayColToBufferCol', () => {
174
- it('converts column before marker', () => {
175
- const state = createPlaceholderState();
176
- const r = addPlaceholder(state, 'original', '[Paste text #1]');
177
- const line = `ab\x00P0\x00cd`;
178
- expect(displayColToBufferCol(line, 0, r.state.placeholders)).toBe(0);
179
- expect(displayColToBufferCol(line, 1, r.state.placeholders)).toBe(1);
180
- });
181
- it('converts column after marker display text', () => {
182
- const state = createPlaceholderState();
183
- const r = addPlaceholder(state, 'original', '[Paste text #1]');
184
- const line = `ab\x00P0\x00cd`;
185
- // Display col 17 = right after display text → buffer col 6 (right after marker)
186
- expect(displayColToBufferCol(line, 17, r.state.placeholders)).toBe(6);
187
- });
188
- it('converts column within marker display text snaps to end', () => {
189
- const state = createPlaceholderState();
190
- const r = addPlaceholder(state, 'original', '[Paste text #1]');
191
- const line = `ab\x00P0\x00cd`;
192
- // Display col 10 (within [Paste text #1]) → should snap to marker end = 6
193
- expect(displayColToBufferCol(line, 10, r.state.placeholders)).toBe(6);
194
- });
195
- });
196
- describe('getValueCursorOffset', () => {
197
- it('computes offset in expanded value', () => {
198
- const state = createPlaceholderState();
199
- const r = addPlaceholder(state, 'hello\nworld', displayText(0));
200
- const cursor = { line: 0, column: 2 + 4 + 1 }; // After "ab" + marker + " " = 7
201
- // Wait, let me construct the line: "ab\x00P0\x00 cd"
202
- // Actually let me just use "ab" + marker + " cd"
203
- const lines = [`ab${createMarker(0)} cd`];
204
- // buffer: a b \x00 P 0 \x00 space c d (8 chars)
205
- // marker at cols 2-5 (length 4)
206
- // cursor at col 6 (after marker, before space)
207
- // expanded: "ab" + "hello\nworld" + " cd"
208
- // cursor offset = 2 + 11 (length of "hello\nworld") = 13
209
- expect(getValueCursorOffset(lines, { line: 0, column: 6 }, r.state.placeholders)).toBe(13);
210
- });
211
- it('computes offset before marker', () => {
212
- const state = createPlaceholderState();
213
- const r = addPlaceholder(state, 'hello', displayText(0));
214
- const lines = [`ab${createMarker(0)} cd`];
215
- expect(getValueCursorOffset(lines, { line: 0, column: 1 }, r.state.placeholders)).toBe(1);
216
- });
217
- });
218
- describe('getCursorFromValueOffset', () => {
219
- it('converts value offset to buffer cursor before marker', () => {
220
- const state = createPlaceholderState();
221
- const r = addPlaceholder(state, 'hello', displayText(0));
222
- const lines = [`ab${createMarker(0)} cd`];
223
- const cursor = getCursorFromValueOffset(lines, 1, r.state.placeholders);
224
- expect(cursor).toEqual({ line: 0, column: 1 });
225
- });
226
- it('converts value offset to buffer cursor after marker', () => {
227
- const state = createPlaceholderState();
228
- const r = addPlaceholder(state, 'hello', displayText(0));
229
- const lines = [`ab${createMarker(0)} cd`];
230
- // Value offset past 'abhello' = 7 → buffer col 6 (after marker)
231
- const cursor = getCursorFromValueOffset(lines, 7, r.state.placeholders);
232
- expect(cursor).toEqual({ line: 0, column: 6 });
233
- });
234
- });
235
- });
@@ -1,9 +0,0 @@
1
- import React from 'react';
2
- import { render } from 'ink';
3
- import { MultilineInput } from '../src/index.js';
4
- const App = () => {
5
- return (React.createElement(MultilineInput
6
- // Add props here
7
- , null));
8
- };
9
- render(React.createElement(App, null));
@@ -1,15 +0,0 @@
1
- import { type Key, type Buffer, type Cursor } from './types';
2
- import { type UseTextInputResult } from './useTextInput';
3
- export interface KeyHandlerActions extends Omit<UseTextInputResult, 'value' | 'cursor'> {
4
- submit: () => void;
5
- }
6
- /**
7
- * Handles keyboard input and maps it to text input actions.
8
- *
9
- * @param key - The Ink key object
10
- * @param input - The input string (if any)
11
- * @param buffer - The current text buffer
12
- * @param actions - The actions available to modify the state
13
- * @param cursor - The current cursor position (optional, but required for some logic like backslash check)
14
- */
15
- export declare function handleKey(key: Partial<Key>, input: string, buffer: Buffer, actions: KeyHandlerActions, cursor?: Cursor): void;