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.
Files changed (61) hide show
  1. package/README.md +2 -2
  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/TextBuffer.d.ts +5 -31
  14. package/dist/components/MultilineInput/TextBuffer.js +91 -161
  15. package/dist/components/MultilineInput/TextRenderer.d.ts +6 -7
  16. package/dist/components/MultilineInput/TextRenderer.js +7 -7
  17. package/dist/components/MultilineInput/__tests__/BlockMarker.test.js +130 -0
  18. package/dist/components/MultilineInput/__tests__/BlockRegistry.test.js +225 -0
  19. package/dist/components/MultilineInput/__tests__/Placeholder.integration.test.js +44 -65
  20. package/dist/components/MultilineInput/__tests__/TextBuffer_images.test.js +10 -31
  21. package/dist/components/MultilineInput/__tests__/TextRenderer_images.test.js +27 -13
  22. package/dist/components/MultilineInput/__tests__/integration_images.test.js +2 -4
  23. package/dist/components/MultilineInput/__tests__/useTextInput_images.test.js +30 -29
  24. package/dist/components/MultilineInput/index.d.ts +6 -6
  25. package/dist/components/MultilineInput/index.js +56 -13
  26. package/dist/components/MultilineInput/types.d.ts +0 -20
  27. package/dist/components/MultilineInput/useTextInput.d.ts +4 -11
  28. package/dist/components/MultilineInput/useTextInput.js +79 -76
  29. package/package.json +1 -1
  30. package/dist/components/MultilineInput/Placeholder.d.ts +0 -30
  31. package/dist/components/MultilineInput/Placeholder.js +0 -200
  32. package/dist/components/MultilineInput/__tests__/Placeholder.test.js +0 -235
  33. package/dist/examples/examples/basic.js +0 -9
  34. package/dist/examples/src/components/MultilineInput/KeyHandler.d.ts +0 -15
  35. package/dist/examples/src/components/MultilineInput/KeyHandler.js +0 -97
  36. package/dist/examples/src/components/MultilineInput/TextBuffer.d.ts +0 -34
  37. package/dist/examples/src/components/MultilineInput/TextBuffer.js +0 -127
  38. package/dist/examples/src/components/MultilineInput/TextRenderer.d.ts +0 -24
  39. package/dist/examples/src/components/MultilineInput/TextRenderer.js +0 -72
  40. package/dist/examples/src/components/MultilineInput/__tests__/KeyHandler.test.js +0 -115
  41. package/dist/examples/src/components/MultilineInput/__tests__/TextBuffer.test.d.ts +0 -1
  42. package/dist/examples/src/components/MultilineInput/__tests__/TextBuffer.test.js +0 -254
  43. package/dist/examples/src/components/MultilineInput/__tests__/TextRenderer.test.d.ts +0 -1
  44. package/dist/examples/src/components/MultilineInput/__tests__/TextRenderer.test.js +0 -176
  45. package/dist/examples/src/components/MultilineInput/__tests__/integration.test.d.ts +0 -1
  46. package/dist/examples/src/components/MultilineInput/__tests__/integration.test.js +0 -71
  47. package/dist/examples/src/components/MultilineInput/__tests__/useTextInput.test.d.ts +0 -1
  48. package/dist/examples/src/components/MultilineInput/__tests__/useTextInput.test.js +0 -65
  49. package/dist/examples/src/components/MultilineInput/index.d.ts +0 -39
  50. package/dist/examples/src/components/MultilineInput/index.js +0 -82
  51. package/dist/examples/src/components/MultilineInput/types.d.ts +0 -55
  52. package/dist/examples/src/components/MultilineInput/types.js +0 -1
  53. package/dist/examples/src/components/MultilineInput/useTextInput.d.ts +0 -16
  54. package/dist/examples/src/components/MultilineInput/useTextInput.js +0 -82
  55. package/dist/examples/src/hello.test.d.ts +0 -1
  56. package/dist/examples/src/hello.test.js +0 -13
  57. package/dist/examples/src/index.d.ts +0 -2
  58. package/dist/examples/src/index.js +0 -2
  59. /package/dist/components/MultilineInput/{__tests__/Placeholder.test.d.ts → BlockTypes.js} +0 -0
  60. /package/dist/{examples/examples/basic.d.ts → components/MultilineInput/__tests__/BlockMarker.test.d.ts} +0 -0
  61. /package/dist/{examples/src/components/MultilineInput/__tests__/KeyHandler.test.d.ts → components/MultilineInput/__tests__/BlockRegistry.test.d.ts} +0 -0
@@ -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;
@@ -1,97 +0,0 @@
1
- /**
2
- * Handles keyboard input and maps it to text input actions.
3
- *
4
- * @param key - The Ink key object
5
- * @param input - The input string (if any)
6
- * @param buffer - The current text buffer
7
- * @param actions - The actions available to modify the state
8
- * @param cursor - The current cursor position (optional, but required for some logic like backslash check)
9
- */
10
- export function handleKey(key, input, buffer, actions, cursor) {
11
- // Navigation
12
- if (key.upArrow) {
13
- actions.moveCursor('up');
14
- return;
15
- }
16
- if (key.downArrow) {
17
- actions.moveCursor('down');
18
- return;
19
- }
20
- if (key.leftArrow) {
21
- actions.moveCursor('left');
22
- return;
23
- }
24
- if (key.rightArrow) {
25
- actions.moveCursor('right');
26
- return;
27
- }
28
- // Home/End (Ink might not provide these directly in all environments, but if it does)
29
- // We check for 'home' and 'end' properties if they exist on the key object,
30
- // or specific sequences if we were parsing raw input, but here we assume Ink's Key object.
31
- // Note: Ink's Key interface might not have home/end in all versions, but we'll assume it does or we extend it.
32
- // If not, we might need to check specific input sequences, but for now let's trust the test/types.
33
- if (key.home) {
34
- actions.moveCursor('lineStart');
35
- return;
36
- }
37
- if (key.end) {
38
- actions.moveCursor('lineEnd');
39
- return;
40
- }
41
- // History
42
- if (key.ctrl) {
43
- if (input === 'z') {
44
- actions.undo();
45
- return;
46
- }
47
- if (input === 'y') {
48
- actions.redo();
49
- return;
50
- }
51
- if (input === 'j') {
52
- actions.newLine();
53
- return;
54
- }
55
- }
56
- // Editing
57
- if (key.backspace) {
58
- actions.delete();
59
- return;
60
- }
61
- if (key.delete) {
62
- // Currently mapped to delete (backspace behavior) as per requirements/tests,
63
- // but usually delete is forward.
64
- // The plan said "Delete (delete at cursor)", which usually means forward.
65
- // But our useTextInput only has `delete` (which is backspace).
66
- // For now, we map it to `delete` as per the test "handles Delete".
67
- actions.delete();
68
- return;
69
- }
70
- // Submission / New Line
71
- if (key.return) {
72
- if (cursor) {
73
- const currentLine = buffer.lines[cursor.line];
74
- // Check if line ends with backslash AND cursor is at the end (or we just check the line content?)
75
- // Requirement: "Line ending with \ + Enter continues to next line"
76
- // Usually this implies the user typed '\' then Enter.
77
- // We should probably check if the character *before* the cursor is '\' if we want to be precise,
78
- // or just if the line ends with '\'.
79
- // Let's assume "line ends with \" means the last char of the line is '\'.
80
- if (currentLine.endsWith('\\')) {
81
- actions.delete(); // Remove the backslash
82
- actions.newLine(); // Insert newline
83
- return;
84
- }
85
- }
86
- actions.submit();
87
- return;
88
- }
89
- // Text Insertion
90
- // Ignore control keys if they don't have a specific handler above
91
- if (key.ctrl || key.meta) {
92
- return;
93
- }
94
- if (input) {
95
- actions.insert(input);
96
- }
97
- }
@@ -1,34 +0,0 @@
1
- import type { Buffer, Cursor, Direction } from './types';
2
- /**
3
- * Create a new buffer from optional initial text
4
- */
5
- export declare function createBuffer(text?: string): Buffer;
6
- /**
7
- * Insert a character at the cursor position
8
- */
9
- export declare function insertChar(buffer: Buffer, cursor: Cursor, char: string): {
10
- buffer: Buffer;
11
- cursor: Cursor;
12
- };
13
- /**
14
- * Delete character before cursor (backspace)
15
- */
16
- export declare function deleteChar(buffer: Buffer, cursor: Cursor): {
17
- buffer: Buffer;
18
- cursor: Cursor;
19
- };
20
- /**
21
- * Insert a new line at cursor position (splits current line)
22
- */
23
- export declare function insertNewLine(buffer: Buffer, cursor: Cursor): {
24
- buffer: Buffer;
25
- cursor: Cursor;
26
- };
27
- /**
28
- * Move cursor in specified direction with bounds checking
29
- */
30
- export declare function moveCursor(buffer: Buffer, cursor: Cursor, direction: Direction): Cursor;
31
- /**
32
- * Get the full text content from buffer (lines joined with newlines)
33
- */
34
- export declare function getTextContent(buffer: Buffer): string;
@@ -1,127 +0,0 @@
1
- /**
2
- * Create a new buffer from optional initial text
3
- */
4
- export function createBuffer(text) {
5
- if (!text) {
6
- return { lines: [''] };
7
- }
8
- return { lines: text.split('\n') };
9
- }
10
- /**
11
- * Insert a character at the cursor position
12
- */
13
- export function insertChar(buffer, cursor, char) {
14
- const { line, column } = cursor;
15
- const currentLine = buffer.lines[line];
16
- const newLine = currentLine.slice(0, column) + char + currentLine.slice(column);
17
- const newLines = [...buffer.lines];
18
- newLines[line] = newLine;
19
- return {
20
- buffer: { lines: newLines },
21
- cursor: { line, column: column + 1 },
22
- };
23
- }
24
- /**
25
- * Delete character before cursor (backspace)
26
- */
27
- export function deleteChar(buffer, cursor) {
28
- const { line, column } = cursor;
29
- // At the very start of the buffer - nothing to delete
30
- if (line === 0 && column === 0) {
31
- return { buffer, cursor };
32
- }
33
- // At the start of a line - merge with previous line
34
- if (column === 0) {
35
- const previousLine = buffer.lines[line - 1];
36
- const currentLine = buffer.lines[line];
37
- const mergedLine = previousLine + currentLine;
38
- const newLines = [...buffer.lines];
39
- newLines[line - 1] = mergedLine;
40
- newLines.splice(line, 1);
41
- return {
42
- buffer: { lines: newLines },
43
- cursor: { line: line - 1, column: previousLine.length },
44
- };
45
- }
46
- // Delete character within the line
47
- const currentLine = buffer.lines[line];
48
- const newLine = currentLine.slice(0, column - 1) + currentLine.slice(column);
49
- const newLines = [...buffer.lines];
50
- newLines[line] = newLine;
51
- return {
52
- buffer: { lines: newLines },
53
- cursor: { line, column: column - 1 },
54
- };
55
- }
56
- /**
57
- * Insert a new line at cursor position (splits current line)
58
- */
59
- export function insertNewLine(buffer, cursor) {
60
- const { line, column } = cursor;
61
- const currentLine = buffer.lines[line];
62
- const beforeCursor = currentLine.slice(0, column);
63
- const afterCursor = currentLine.slice(column);
64
- const newLines = [...buffer.lines];
65
- newLines[line] = beforeCursor;
66
- newLines.splice(line + 1, 0, afterCursor);
67
- return {
68
- buffer: { lines: newLines },
69
- cursor: { line: line + 1, column: 0 },
70
- };
71
- }
72
- /**
73
- * Move cursor in specified direction with bounds checking
74
- */
75
- export function moveCursor(buffer, cursor, direction) {
76
- const { line, column } = cursor;
77
- const currentLine = buffer.lines[line];
78
- const lineCount = buffer.lines.length;
79
- switch (direction) {
80
- case 'left':
81
- if (column > 0) {
82
- return { line, column: column - 1 };
83
- }
84
- // Wrap to end of previous line
85
- if (line > 0) {
86
- return { line: line - 1, column: buffer.lines[line - 1].length };
87
- }
88
- return cursor;
89
- case 'right':
90
- if (column < currentLine.length) {
91
- return { line, column: column + 1 };
92
- }
93
- // Wrap to start of next line
94
- if (line < lineCount - 1) {
95
- return { line: line + 1, column: 0 };
96
- }
97
- return cursor;
98
- case 'up':
99
- if (line > 0) {
100
- const targetLine = buffer.lines[line - 1];
101
- return { line: line - 1, column: Math.min(column, targetLine.length) };
102
- }
103
- return cursor;
104
- case 'down':
105
- if (line < lineCount - 1) {
106
- const targetLine = buffer.lines[line + 1];
107
- return { line: line + 1, column: Math.min(column, targetLine.length) };
108
- }
109
- return cursor;
110
- case 'lineStart':
111
- return { line, column: 0 };
112
- case 'lineEnd':
113
- return { line, column: currentLine.length };
114
- default:
115
- return cursor;
116
- }
117
- }
118
- /**
119
- * Get the full text content from buffer (lines joined with newlines)
120
- */
121
- export function getTextContent(buffer) {
122
- // Single empty line is considered empty buffer
123
- if (buffer.lines.length === 1 && buffer.lines[0] === '') {
124
- return '';
125
- }
126
- return buffer.lines.join('\n');
127
- }
@@ -1,24 +0,0 @@
1
- import React from 'react';
2
- import type { Buffer, Cursor, WrapResult } from './types';
3
- /**
4
- * Props for the TextRenderer component
5
- */
6
- export interface TextRendererProps {
7
- /** Text buffer to render */
8
- buffer: Buffer;
9
- /** Current cursor position */
10
- cursor: Cursor;
11
- /** Terminal width for word wrapping (defaults to 80) */
12
- width?: number;
13
- /** Whether to show the cursor (defaults to true) */
14
- showCursor?: boolean;
15
- }
16
- /**
17
- * Wrap buffer lines to fit within a given width.
18
- * Returns visual lines and maps cursor position to visual coordinates.
19
- */
20
- export declare function wrapLines(buffer: Buffer, cursor: Cursor, width: number): WrapResult;
21
- /**
22
- * TextRenderer component for displaying buffer content with cursor
23
- */
24
- export declare function TextRenderer({ buffer, cursor, width, showCursor, }: TextRendererProps): React.ReactElement;
@@ -1,72 +0,0 @@
1
- import React from 'react';
2
- /**
3
- * Wrap buffer lines to fit within a given width.
4
- * Returns visual lines and maps cursor position to visual coordinates.
5
- */
6
- export function wrapLines(buffer, cursor, width) {
7
- const visualLines = [];
8
- let cursorVisualRow = 0;
9
- let cursorVisualCol = 0;
10
- let visualRowIndex = 0;
11
- for (let lineIndex = 0; lineIndex < buffer.lines.length; lineIndex++) {
12
- const line = buffer.lines[lineIndex];
13
- const isCursorLine = lineIndex === cursor.line;
14
- if (line.length <= width) {
15
- // Line fits, no wrapping needed
16
- visualLines.push(line);
17
- if (isCursorLine) {
18
- cursorVisualRow = visualRowIndex;
19
- cursorVisualCol = cursor.column;
20
- }
21
- visualRowIndex++;
22
- }
23
- else {
24
- // Line needs to be wrapped
25
- let offset = 0;
26
- while (offset < line.length) {
27
- const chunk = line.slice(offset, offset + width);
28
- visualLines.push(chunk);
29
- if (isCursorLine) {
30
- // Check if cursor falls within this chunk
31
- if (cursor.column >= offset && cursor.column < offset + width) {
32
- cursorVisualRow = visualRowIndex;
33
- cursorVisualCol = cursor.column - offset;
34
- }
35
- else if (cursor.column === line.length && offset + chunk.length === line.length) {
36
- // Cursor at end of line
37
- cursorVisualRow = visualRowIndex;
38
- cursorVisualCol = chunk.length;
39
- }
40
- }
41
- offset += width;
42
- visualRowIndex++;
43
- }
44
- }
45
- }
46
- return { visualLines, cursorVisualRow, cursorVisualCol };
47
- }
48
- /**
49
- * Render a line with cursor inserted at the specified position
50
- */
51
- function renderLineWithCursor(line, cursorCol, showCursor) {
52
- if (!showCursor) {
53
- return line;
54
- }
55
- const cursorChar = '█';
56
- const before = line.slice(0, cursorCol);
57
- const after = line.slice(cursorCol);
58
- return before + cursorChar + after;
59
- }
60
- /**
61
- * TextRenderer component for displaying buffer content with cursor
62
- */
63
- export function TextRenderer({ buffer, cursor, width = 80, showCursor = true, }) {
64
- const { visualLines, cursorVisualRow, cursorVisualCol } = wrapLines(buffer, cursor, width);
65
- return (React.createElement("div", null, visualLines.map((line, index) => {
66
- const isCursorRow = index === cursorVisualRow;
67
- const displayLine = isCursorRow
68
- ? renderLineWithCursor(line, cursorVisualCol, showCursor)
69
- : line;
70
- return (React.createElement("div", { key: index }, displayLine));
71
- })));
72
- }