ink-prompt 0.2.3 → 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 +2 -11
  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,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
- }
@@ -1,115 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { handleKey } from '../KeyHandler';
3
- describe('KeyHandler', () => {
4
- let actions;
5
- let buffer;
6
- beforeEach(() => {
7
- actions = {
8
- insert: vi.fn(),
9
- delete: vi.fn(),
10
- newLine: vi.fn(),
11
- moveCursor: vi.fn(),
12
- undo: vi.fn(),
13
- redo: vi.fn(),
14
- setText: vi.fn(),
15
- submit: vi.fn(),
16
- };
17
- buffer = { lines: [''] };
18
- });
19
- describe('Navigation', () => {
20
- it('handles ArrowUp', () => {
21
- handleKey({ upArrow: true }, 'up', buffer, actions);
22
- expect(actions.moveCursor).toHaveBeenCalledWith('up');
23
- });
24
- it('handles ArrowDown', () => {
25
- handleKey({ downArrow: true }, 'down', buffer, actions);
26
- expect(actions.moveCursor).toHaveBeenCalledWith('down');
27
- });
28
- it('handles ArrowLeft', () => {
29
- handleKey({ leftArrow: true }, 'left', buffer, actions);
30
- expect(actions.moveCursor).toHaveBeenCalledWith('left');
31
- });
32
- it('handles ArrowRight', () => {
33
- handleKey({ rightArrow: true }, 'right', buffer, actions);
34
- expect(actions.moveCursor).toHaveBeenCalledWith('right');
35
- });
36
- it('handles Home', () => {
37
- // Ink doesn't have a specific 'home' property usually, but we check for special keys
38
- // Often represented as key.home if using a specific library or just checking input
39
- // For Ink's useInput, we might receive specific sequences.
40
- // Assuming we handle it via checking `key` object properties if available, or input string if it maps.
41
- // However, standard Ink `Key` interface has `home` and `end`?
42
- // Let's assume standard Ink Key interface.
43
- handleKey({ home: true }, '', buffer, actions);
44
- expect(actions.moveCursor).toHaveBeenCalledWith('lineStart');
45
- });
46
- it('handles End', () => {
47
- handleKey({ end: true }, '', buffer, actions);
48
- expect(actions.moveCursor).toHaveBeenCalledWith('lineEnd');
49
- });
50
- });
51
- describe('Editing', () => {
52
- it('handles Backspace', () => {
53
- handleKey({ backspace: true }, 'backspace', buffer, actions);
54
- expect(actions.delete).toHaveBeenCalled();
55
- });
56
- it('handles Delete', () => {
57
- handleKey({ delete: true }, 'delete', buffer, actions);
58
- expect(actions.delete).toHaveBeenCalled(); // Our current simple delete handles backspace, we might need forward delete later
59
- });
60
- it('handles Ctrl+J (NewLine)', () => {
61
- handleKey({ ctrl: true }, 'j', buffer, actions);
62
- expect(actions.newLine).toHaveBeenCalled();
63
- });
64
- it('handles regular text insertion', () => {
65
- handleKey({}, 'a', buffer, actions);
66
- expect(actions.insert).toHaveBeenCalledWith('a');
67
- });
68
- it('ignores control keys without text', () => {
69
- handleKey({ ctrl: true }, '', buffer, actions);
70
- expect(actions.insert).not.toHaveBeenCalled();
71
- });
72
- });
73
- describe('History', () => {
74
- it('handles Ctrl+Z (Undo)', () => {
75
- handleKey({ ctrl: true }, 'z', buffer, actions);
76
- expect(actions.undo).toHaveBeenCalled();
77
- });
78
- it('handles Ctrl+Y (Redo)', () => {
79
- handleKey({ ctrl: true }, 'y', buffer, actions);
80
- expect(actions.redo).toHaveBeenCalled();
81
- });
82
- });
83
- describe('Submission', () => {
84
- it('handles Enter as submit by default', () => {
85
- buffer = { lines: ['hello'] };
86
- handleKey({ return: true }, 'return', buffer, actions);
87
- expect(actions.submit).toHaveBeenCalled();
88
- expect(actions.newLine).not.toHaveBeenCalled();
89
- });
90
- it('handles Enter as newline if line ends with backslash', () => {
91
- buffer = { lines: ['hello\\'] };
92
- const cursor = { line: 0, column: 6 };
93
- handleKey({ return: true }, 'return', buffer, actions, cursor);
94
- // It should probably remove the backslash first.
95
- // Since we are mocking actions, we can't easily verify the state change between calls unless we implement a fake.
96
- // But we can check call order.
97
- expect(actions.delete).toHaveBeenCalled();
98
- expect(actions.newLine).toHaveBeenCalled();
99
- expect(actions.submit).not.toHaveBeenCalled();
100
- });
101
- it('handles Enter as newline if line ends with backslash (multiple lines)', () => {
102
- // Cursor is implicitly at the end for this logic usually, or we need to pass cursor to handleKey?
103
- // The plan says `handleKey` takes `buffer`. It probably needs `cursor` too to know which line we are on?
104
- // Ah, `TextBuffer` logic usually needs cursor.
105
- // If `handleKey` decides based on "current line", it needs to know the current line.
106
- // So `handleKey` signature should probably include `cursor`.
107
- const cursor = { line: 1, column: 6 };
108
- buffer = { lines: ['first', 'second\\'] };
109
- handleKey({ return: true }, 'return', buffer, actions, cursor);
110
- expect(actions.delete).toHaveBeenCalled();
111
- expect(actions.newLine).toHaveBeenCalled();
112
- expect(actions.submit).not.toHaveBeenCalled();
113
- });
114
- });
115
- });
@@ -1,254 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { createBuffer, insertChar, deleteChar, insertNewLine, moveCursor, getTextContent, } from '../TextBuffer';
3
- describe('TextBuffer', () => {
4
- describe('createBuffer', () => {
5
- it('creates empty buffer with single empty line', () => {
6
- const buffer = createBuffer();
7
- expect(buffer).toEqual({ lines: [''] });
8
- });
9
- it('creates buffer from single line text', () => {
10
- const buffer = createBuffer('hello');
11
- expect(buffer).toEqual({ lines: ['hello'] });
12
- });
13
- it('creates buffer from multi-line text', () => {
14
- const buffer = createBuffer('line1\nline2\nline3');
15
- expect(buffer).toEqual({ lines: ['line1', 'line2', 'line3'] });
16
- });
17
- it('handles text ending with newline', () => {
18
- const buffer = createBuffer('hello\n');
19
- expect(buffer).toEqual({ lines: ['hello', ''] });
20
- });
21
- });
22
- describe('insertChar', () => {
23
- it('inserts character into empty buffer', () => {
24
- const buffer = createBuffer();
25
- const cursor = { line: 0, column: 0 };
26
- const result = insertChar(buffer, cursor, 'a');
27
- expect(result.buffer).toEqual({ lines: ['a'] });
28
- expect(result.cursor).toEqual({ line: 0, column: 1 });
29
- });
30
- it('inserts character at end of line', () => {
31
- const buffer = createBuffer('hello');
32
- const cursor = { line: 0, column: 5 };
33
- const result = insertChar(buffer, cursor, '!');
34
- expect(result.buffer).toEqual({ lines: ['hello!'] });
35
- expect(result.cursor).toEqual({ line: 0, column: 6 });
36
- });
37
- it('inserts character in middle of line', () => {
38
- const buffer = createBuffer('hllo');
39
- const cursor = { line: 0, column: 1 };
40
- const result = insertChar(buffer, cursor, 'e');
41
- expect(result.buffer).toEqual({ lines: ['hello'] });
42
- expect(result.cursor).toEqual({ line: 0, column: 2 });
43
- });
44
- it('inserts character at line start', () => {
45
- const buffer = createBuffer('ello');
46
- const cursor = { line: 0, column: 0 };
47
- const result = insertChar(buffer, cursor, 'h');
48
- expect(result.buffer).toEqual({ lines: ['hello'] });
49
- expect(result.cursor).toEqual({ line: 0, column: 1 });
50
- });
51
- it('inserts into specific line of multi-line buffer', () => {
52
- const buffer = createBuffer('line1\nline2\nline3');
53
- const cursor = { line: 1, column: 4 };
54
- const result = insertChar(buffer, cursor, 'X');
55
- expect(result.buffer).toEqual({ lines: ['line1', 'lineX2', 'line3'] });
56
- expect(result.cursor).toEqual({ line: 1, column: 5 });
57
- });
58
- });
59
- describe('deleteChar (backspace)', () => {
60
- it('does nothing when cursor at buffer start', () => {
61
- const buffer = createBuffer('hello');
62
- const cursor = { line: 0, column: 0 };
63
- const result = deleteChar(buffer, cursor);
64
- expect(result.buffer).toEqual({ lines: ['hello'] });
65
- expect(result.cursor).toEqual({ line: 0, column: 0 });
66
- });
67
- it('deletes character before cursor', () => {
68
- const buffer = createBuffer('hello');
69
- const cursor = { line: 0, column: 5 };
70
- const result = deleteChar(buffer, cursor);
71
- expect(result.buffer).toEqual({ lines: ['hell'] });
72
- expect(result.cursor).toEqual({ line: 0, column: 4 });
73
- });
74
- it('deletes character in middle of line', () => {
75
- const buffer = createBuffer('heello');
76
- const cursor = { line: 0, column: 3 };
77
- const result = deleteChar(buffer, cursor);
78
- expect(result.buffer).toEqual({ lines: ['hello'] });
79
- expect(result.cursor).toEqual({ line: 0, column: 2 });
80
- });
81
- it('merges with previous line when at line start', () => {
82
- const buffer = createBuffer('hello\nworld');
83
- const cursor = { line: 1, column: 0 };
84
- const result = deleteChar(buffer, cursor);
85
- expect(result.buffer).toEqual({ lines: ['helloworld'] });
86
- expect(result.cursor).toEqual({ line: 0, column: 5 });
87
- });
88
- it('merges empty line with previous', () => {
89
- const buffer = createBuffer('hello\n');
90
- const cursor = { line: 1, column: 0 };
91
- const result = deleteChar(buffer, cursor);
92
- expect(result.buffer).toEqual({ lines: ['hello'] });
93
- expect(result.cursor).toEqual({ line: 0, column: 5 });
94
- });
95
- });
96
- describe('insertNewLine', () => {
97
- it('splits line at cursor position', () => {
98
- const buffer = createBuffer('helloworld');
99
- const cursor = { line: 0, column: 5 };
100
- const result = insertNewLine(buffer, cursor);
101
- expect(result.buffer).toEqual({ lines: ['hello', 'world'] });
102
- expect(result.cursor).toEqual({ line: 1, column: 0 });
103
- });
104
- it('creates new line at line start', () => {
105
- const buffer = createBuffer('hello');
106
- const cursor = { line: 0, column: 0 };
107
- const result = insertNewLine(buffer, cursor);
108
- expect(result.buffer).toEqual({ lines: ['', 'hello'] });
109
- expect(result.cursor).toEqual({ line: 1, column: 0 });
110
- });
111
- it('creates new line at line end', () => {
112
- const buffer = createBuffer('hello');
113
- const cursor = { line: 0, column: 5 };
114
- const result = insertNewLine(buffer, cursor);
115
- expect(result.buffer).toEqual({ lines: ['hello', ''] });
116
- expect(result.cursor).toEqual({ line: 1, column: 0 });
117
- });
118
- it('inserts new line in middle of multi-line buffer', () => {
119
- const buffer = createBuffer('line1\nline2\nline3');
120
- const cursor = { line: 1, column: 2 };
121
- const result = insertNewLine(buffer, cursor);
122
- expect(result.buffer).toEqual({ lines: ['line1', 'li', 'ne2', 'line3'] });
123
- expect(result.cursor).toEqual({ line: 2, column: 0 });
124
- });
125
- });
126
- describe('moveCursor', () => {
127
- describe('left', () => {
128
- it('moves left within line', () => {
129
- const buffer = createBuffer('hello');
130
- const cursor = { line: 0, column: 3 };
131
- const result = moveCursor(buffer, cursor, 'left');
132
- expect(result).toEqual({ line: 0, column: 2 });
133
- });
134
- it('wraps to end of previous line', () => {
135
- const buffer = createBuffer('hello\nworld');
136
- const cursor = { line: 1, column: 0 };
137
- const result = moveCursor(buffer, cursor, 'left');
138
- expect(result).toEqual({ line: 0, column: 5 });
139
- });
140
- it('stays at buffer start', () => {
141
- const buffer = createBuffer('hello');
142
- const cursor = { line: 0, column: 0 };
143
- const result = moveCursor(buffer, cursor, 'left');
144
- expect(result).toEqual({ line: 0, column: 0 });
145
- });
146
- });
147
- describe('right', () => {
148
- it('moves right within line', () => {
149
- const buffer = createBuffer('hello');
150
- const cursor = { line: 0, column: 2 };
151
- const result = moveCursor(buffer, cursor, 'right');
152
- expect(result).toEqual({ line: 0, column: 3 });
153
- });
154
- it('wraps to start of next line', () => {
155
- const buffer = createBuffer('hello\nworld');
156
- const cursor = { line: 0, column: 5 };
157
- const result = moveCursor(buffer, cursor, 'right');
158
- expect(result).toEqual({ line: 1, column: 0 });
159
- });
160
- it('stays at buffer end', () => {
161
- const buffer = createBuffer('hello');
162
- const cursor = { line: 0, column: 5 };
163
- const result = moveCursor(buffer, cursor, 'right');
164
- expect(result).toEqual({ line: 0, column: 5 });
165
- });
166
- });
167
- describe('up', () => {
168
- it('moves up maintaining column', () => {
169
- const buffer = createBuffer('hello\nworld');
170
- const cursor = { line: 1, column: 3 };
171
- const result = moveCursor(buffer, cursor, 'up');
172
- expect(result).toEqual({ line: 0, column: 3 });
173
- });
174
- it('clamps column to shorter line', () => {
175
- const buffer = createBuffer('hi\nhello');
176
- const cursor = { line: 1, column: 4 };
177
- const result = moveCursor(buffer, cursor, 'up');
178
- expect(result).toEqual({ line: 0, column: 2 });
179
- });
180
- it('stays on first line', () => {
181
- const buffer = createBuffer('hello');
182
- const cursor = { line: 0, column: 2 };
183
- const result = moveCursor(buffer, cursor, 'up');
184
- expect(result).toEqual({ line: 0, column: 2 });
185
- });
186
- });
187
- describe('down', () => {
188
- it('moves down maintaining column', () => {
189
- const buffer = createBuffer('hello\nworld');
190
- const cursor = { line: 0, column: 3 };
191
- const result = moveCursor(buffer, cursor, 'down');
192
- expect(result).toEqual({ line: 1, column: 3 });
193
- });
194
- it('clamps column to shorter line', () => {
195
- const buffer = createBuffer('hello\nhi');
196
- const cursor = { line: 0, column: 4 };
197
- const result = moveCursor(buffer, cursor, 'down');
198
- expect(result).toEqual({ line: 1, column: 2 });
199
- });
200
- it('stays on last line', () => {
201
- const buffer = createBuffer('hello');
202
- const cursor = { line: 0, column: 2 };
203
- const result = moveCursor(buffer, cursor, 'down');
204
- expect(result).toEqual({ line: 0, column: 2 });
205
- });
206
- });
207
- describe('lineStart', () => {
208
- it('moves to start of line', () => {
209
- const buffer = createBuffer('hello');
210
- const cursor = { line: 0, column: 3 };
211
- const result = moveCursor(buffer, cursor, 'lineStart');
212
- expect(result).toEqual({ line: 0, column: 0 });
213
- });
214
- it('stays at start if already there', () => {
215
- const buffer = createBuffer('hello');
216
- const cursor = { line: 0, column: 0 };
217
- const result = moveCursor(buffer, cursor, 'lineStart');
218
- expect(result).toEqual({ line: 0, column: 0 });
219
- });
220
- });
221
- describe('lineEnd', () => {
222
- it('moves to end of line', () => {
223
- const buffer = createBuffer('hello');
224
- const cursor = { line: 0, column: 2 };
225
- const result = moveCursor(buffer, cursor, 'lineEnd');
226
- expect(result).toEqual({ line: 0, column: 5 });
227
- });
228
- it('stays at end if already there', () => {
229
- const buffer = createBuffer('hello');
230
- const cursor = { line: 0, column: 5 };
231
- const result = moveCursor(buffer, cursor, 'lineEnd');
232
- expect(result).toEqual({ line: 0, column: 5 });
233
- });
234
- });
235
- });
236
- describe('getTextContent', () => {
237
- it('returns empty string for empty buffer', () => {
238
- const buffer = createBuffer();
239
- expect(getTextContent(buffer)).toBe('');
240
- });
241
- it('returns single line content', () => {
242
- const buffer = createBuffer('hello');
243
- expect(getTextContent(buffer)).toBe('hello');
244
- });
245
- it('joins multiple lines with newlines', () => {
246
- const buffer = createBuffer('line1\nline2\nline3');
247
- expect(getTextContent(buffer)).toBe('line1\nline2\nline3');
248
- });
249
- it('preserves empty lines', () => {
250
- const buffer = createBuffer('hello\n\nworld');
251
- expect(getTextContent(buffer)).toBe('hello\n\nworld');
252
- });
253
- });
254
- });