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,10 +1,9 @@
1
1
  import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
2
- import { createBuffer, insertText as bufferInsertText, deleteChar as bufferDeleteChar, deleteCharForward as bufferDeleteCharForward, insertNewLine as bufferInsertNewLine, moveCursor as bufferMoveCursor, getTextContent, } from './TextBuffer.js';
3
- import { createPlaceholderState, addPlaceholder, removePlaceholder, getValue, getValueCursorOffset, getCursorFromValueOffset, } from './Placeholder.js';
4
- import { createSentinel, parseSentinels } from './ImageSentinel.js';
2
+ import { createBuffer, insertText as bufferInsertText, deleteChar as bufferDeleteChar, deleteCharForward as bufferDeleteCharForward, insertNewLine as bufferInsertNewLine, moveCursor as bufferMoveCursor, } from './TextBuffer.js';
3
+ import { createBlockState, createPasteBlockEntry, createImageBlockEntry, removeBlock, getValue, getValueCursorOffset, getCursorFromValueOffset, } from './BlockRegistry.js';
5
4
  import { findAtomicBlockBefore, findAtomicBlockAfter } from './AtomicBlocks.js';
6
5
  import { log } from '../../utils/logger.js';
7
- const defaultFormatPlaceholder = (id) => `[Paste text #${id}]`;
6
+ const defaultFormatPlaceholder = (displayNumber) => `[Paste text #${displayNumber}]`;
8
7
  export function useTextInput({ initialValue = '', width, historyLimit = 100, undoDebounceMs = 200, pasteThreshold, formatPastePlaceholder = defaultFormatPlaceholder, } = {}) {
9
8
  const [buffer, setBuffer] = useState(() => createBuffer(initialValue));
10
9
  const [cursor, setCursor] = useState(() => {
@@ -14,9 +13,7 @@ export function useTextInput({ initialValue = '', width, historyLimit = 100, und
14
13
  column: lines[lines.length - 1].length,
15
14
  };
16
15
  });
17
- const [placeholderState, setPlaceholderState] = useState(() => createPlaceholderState());
18
- const [images, setImages] = useState({});
19
- const nextDisplayNumberRef = useRef(1);
16
+ const [blockState, setBlockState] = useState(() => createBlockState());
20
17
  const [undoStack, setUndoStack] = useState([]);
21
18
  const [redoStack, setRedoStack] = useState([]);
22
19
  const pendingInsertBatchRef = useRef({});
@@ -56,13 +53,12 @@ export function useTextInput({ initialValue = '', width, historyLimit = 100, und
56
53
  pendingInsertBatchRef.current.startState = {
57
54
  buffer: currentBuffer,
58
55
  cursor: currentCursor,
59
- placeholderState,
60
- images,
56
+ blockState,
61
57
  };
62
58
  setRedoStack([]);
63
59
  }
64
60
  schedulePendingInsertCommit();
65
- }, [schedulePendingInsertCommit, placeholderState, images]);
61
+ }, [schedulePendingInsertCommit, blockState]);
66
62
  const flushPendingInsertBatch = useCallback(() => {
67
63
  commitPendingInsertBatch();
68
64
  }, [commitPendingInsertBatch]);
@@ -70,11 +66,10 @@ export function useTextInput({ initialValue = '', width, historyLimit = 100, und
70
66
  appendUndoState({
71
67
  buffer: currentBuffer,
72
68
  cursor: currentCursor,
73
- placeholderState,
74
- images,
69
+ blockState,
75
70
  });
76
71
  setRedoStack([]);
77
- }, [appendUndoState, placeholderState, images]);
72
+ }, [appendUndoState, blockState]);
78
73
  useEffect(() => {
79
74
  return () => {
80
75
  clearPendingInsertTimer();
@@ -92,16 +87,16 @@ export function useTextInput({ initialValue = '', width, historyLimit = 100, und
92
87
  }
93
88
  }, [buffer, cursor, flushPendingInsertBatch, pushToHistory]);
94
89
  const insert = useCallback((char) => {
95
- log(`[INSERT] char="${char.replace(/[\x00-\x1F\x7F-￿]/g, c => `\\x${c.charCodeAt(0).toString(16)}`)}" len=${char.length} cursor={line:${cursor.line},col:${cursor.column}} linesBefore=${buffer.lines.length}`);
90
+ log(`[INSERT] char="${char.replace(/[\x00-\x1F\x7F-\uFFFF]/g, c => `\\x${c.charCodeAt(0).toString(16)}`)}" len=${char.length} cursor={line:${cursor.line},col:${cursor.column}} linesBefore=${buffer.lines.length}`);
96
91
  const normalized = char.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\x00/g, '');
97
92
  if (normalized.length === 0)
98
93
  return;
99
- // Check if this is a paste exceeding the threshold
100
94
  if (pasteThreshold !== undefined && pasteThreshold > 0 && normalized.length > pasteThreshold) {
101
95
  flushPendingInsertBatch();
102
96
  pushToHistory(buffer, cursor);
103
- const { id, marker, state: newPlaceholderState } = addPlaceholder(placeholderState, normalized, formatPastePlaceholder(placeholderState.nextId));
104
- setPlaceholderState(newPlaceholderState);
97
+ const displayText = formatPastePlaceholder(blockState.nextPasteNumber);
98
+ const { marker, state: newBlockState } = createPasteBlockEntry(blockState, normalized, displayText);
99
+ setBlockState(newBlockState);
105
100
  const result = bufferInsertText(buffer, cursor, marker);
106
101
  setBuffer(result.buffer);
107
102
  setCursor(result.cursor);
@@ -120,70 +115,61 @@ export function useTextInput({ initialValue = '', width, historyLimit = 100, und
120
115
  const result = bufferInsertText(buffer, cursor, normalized);
121
116
  setBuffer(result.buffer);
122
117
  setCursor(result.cursor);
123
- }, [beginOrRefreshInsertBatch, buffer, cursor, flushPendingInsertBatch, pushToHistory, undoDebounceMs, pasteThreshold, placeholderState, formatPastePlaceholder]);
118
+ }, [beginOrRefreshInsertBatch, buffer, cursor, flushPendingInsertBatch, pushToHistory, undoDebounceMs, pasteThreshold, blockState, formatPastePlaceholder]);
124
119
  const cleanupBlockRegistry = useCallback((block) => {
125
120
  if (!block)
126
121
  return;
127
- if (block.kind === 'placeholder') {
128
- setPlaceholderState((prev) => removePlaceholder(prev, block.id));
129
- }
130
- else if (block.kind === 'sentinel' && images[block.id]) {
131
- const next = { ...images };
132
- delete next[block.id];
133
- setImages(next);
134
- }
135
- }, [images]);
122
+ setBlockState((prev) => removeBlock(prev, block.id));
123
+ }, []);
136
124
  const deleteChar = useCallback(() => {
137
125
  applyEdit(() => {
138
126
  const line = buffer.lines[cursor.line];
139
- cleanupBlockRegistry(findAtomicBlockBefore(line, cursor.column, placeholderState.placeholders));
140
- return bufferDeleteChar(buffer, cursor, placeholderState.placeholders);
127
+ cleanupBlockRegistry(findAtomicBlockBefore(line, cursor.column, blockState.entries));
128
+ return bufferDeleteChar(buffer, cursor, blockState.entries);
141
129
  });
142
- }, [applyEdit, buffer, cursor, placeholderState, cleanupBlockRegistry]);
130
+ }, [applyEdit, buffer, cursor, blockState, cleanupBlockRegistry]);
143
131
  const deleteCharForward = useCallback(() => {
144
132
  applyEdit(() => {
145
133
  const line = buffer.lines[cursor.line];
146
- cleanupBlockRegistry(findAtomicBlockAfter(line, cursor.column, placeholderState.placeholders));
147
- return bufferDeleteCharForward(buffer, cursor, placeholderState.placeholders);
134
+ cleanupBlockRegistry(findAtomicBlockAfter(line, cursor.column, blockState.entries));
135
+ return bufferDeleteCharForward(buffer, cursor, blockState.entries);
148
136
  });
149
- }, [applyEdit, buffer, cursor, placeholderState, cleanupBlockRegistry]);
137
+ }, [applyEdit, buffer, cursor, blockState, cleanupBlockRegistry]);
150
138
  const newLine = useCallback(() => {
151
139
  applyEdit(() => bufferInsertNewLine(buffer, cursor));
152
140
  }, [applyEdit, buffer, cursor]);
153
141
  const deleteAndNewLine = useCallback(() => {
154
142
  applyEdit(() => {
155
- const afterDelete = bufferDeleteChar(buffer, cursor, placeholderState.placeholders);
143
+ const afterDelete = bufferDeleteChar(buffer, cursor, blockState.entries);
156
144
  return bufferInsertNewLine(afterDelete.buffer, afterDelete.cursor);
157
145
  });
158
- }, [applyEdit, buffer, cursor, placeholderState]);
146
+ }, [applyEdit, buffer, cursor, blockState]);
159
147
  const moveCursor = useCallback((direction) => {
160
148
  flushPendingInsertBatch();
161
- const newCursor = bufferMoveCursor(buffer, cursor, direction, width, placeholderState.placeholders);
149
+ const newCursor = bufferMoveCursor(buffer, cursor, direction, width, blockState.entries);
162
150
  setCursor(newCursor);
163
- }, [buffer, cursor, flushPendingInsertBatch, width, placeholderState]);
151
+ }, [buffer, cursor, flushPendingInsertBatch, width, blockState]);
164
152
  const undo = useCallback(() => {
165
153
  const pendingStartState = pendingInsertBatchRef.current.startState;
166
154
  if (pendingStartState) {
167
155
  clearPendingInsertTimer();
168
156
  pendingInsertBatchRef.current.startState = undefined;
169
- setRedoStack((prev) => [...prev, { buffer, cursor, placeholderState, images }]);
157
+ setRedoStack((prev) => [...prev, { buffer, cursor, blockState }]);
170
158
  setBuffer(pendingStartState.buffer);
171
159
  setCursor(pendingStartState.cursor);
172
- setPlaceholderState(pendingStartState.placeholderState);
173
- setImages(pendingStartState.images);
160
+ setBlockState(pendingStartState.blockState);
174
161
  return;
175
162
  }
176
163
  if (undoStack.length === 0)
177
164
  return;
178
165
  const previousState = undoStack[undoStack.length - 1];
179
166
  const newUndoStack = undoStack.slice(0, -1);
180
- setRedoStack((prev) => [...prev, { buffer, cursor, placeholderState, images }]);
167
+ setRedoStack((prev) => [...prev, { buffer, cursor, blockState }]);
181
168
  setBuffer(previousState.buffer);
182
169
  setCursor(previousState.cursor);
183
- setPlaceholderState(previousState.placeholderState);
184
- setImages(previousState.images);
170
+ setBlockState(previousState.blockState);
185
171
  setUndoStack(newUndoStack);
186
- }, [buffer, clearPendingInsertTimer, cursor, undoStack, placeholderState, images]);
172
+ }, [buffer, clearPendingInsertTimer, cursor, undoStack, blockState]);
187
173
  const redo = useCallback(() => {
188
174
  if (pendingInsertBatchRef.current.startState) {
189
175
  return;
@@ -192,53 +178,70 @@ export function useTextInput({ initialValue = '', width, historyLimit = 100, und
192
178
  return;
193
179
  const nextState = redoStack[redoStack.length - 1];
194
180
  const newRedoStack = redoStack.slice(0, -1);
195
- setUndoStack((prev) => [...prev, { buffer, cursor, placeholderState, images }]);
181
+ setUndoStack((prev) => [...prev, { buffer, cursor, blockState }]);
196
182
  setBuffer(nextState.buffer);
197
183
  setCursor(nextState.cursor);
198
- setPlaceholderState(nextState.placeholderState);
199
- setImages(nextState.images);
184
+ setBlockState(nextState.blockState);
200
185
  setRedoStack(newRedoStack);
201
- }, [buffer, cursor, redoStack, placeholderState, images]);
186
+ }, [buffer, cursor, redoStack, blockState]);
202
187
  const setText = useCallback((text) => {
203
188
  applyEdit(() => {
204
- setPlaceholderState(createPlaceholderState());
189
+ setBlockState(createBlockState());
205
190
  const newBuffer = createBuffer(text);
206
191
  const lines = text.split('\n');
207
192
  const newCursor = { line: lines.length - 1, column: lines[lines.length - 1].length };
208
- // Clean up orphaned images
209
- const usedIds = new Set(parseSentinels(getTextContent(newBuffer)).map((s) => s.id));
210
- setImages((prev) => {
211
- const next = {};
212
- for (const [id, ref] of Object.entries(prev)) {
213
- if (usedIds.has(id))
214
- next[id] = ref;
215
- }
216
- return next;
217
- });
218
193
  return { buffer: newBuffer, cursor: newCursor };
219
194
  });
220
195
  }, [applyEdit]);
221
196
  const insertImage = useCallback((imageRef) => {
222
197
  applyEdit(() => {
223
- setImages((prev) => ({ ...prev, [imageRef.id]: imageRef }));
224
- if (imageRef.displayNumber >= nextDisplayNumberRef.current) {
225
- nextDisplayNumberRef.current = imageRef.displayNumber + 1;
226
- }
227
- return bufferInsertText(buffer, cursor, createSentinel(imageRef.id, imageRef.displayNumber));
198
+ const { marker, state: newBlockState } = createImageBlockEntry(blockState, imageRef, imageRef.id);
199
+ setBlockState(newBlockState);
200
+ return bufferInsertText(buffer, cursor, marker);
228
201
  });
229
- }, [applyEdit, buffer, cursor]);
230
- const value = useMemo(() => getValue(buffer.lines, placeholderState.placeholders), [buffer.lines, placeholderState.placeholders]);
231
- const cursorOffset = useMemo(() => getValueCursorOffset(buffer.lines, cursor, placeholderState.placeholders), [buffer.lines, cursor, placeholderState.placeholders]);
232
- const imagesList = useMemo(() => Object.values(images), [images]);
202
+ }, [applyEdit, buffer, cursor, blockState]);
203
+ const value = useMemo(() => getValue(buffer.lines, blockState.entries), [buffer.lines, blockState.entries]);
204
+ const cursorOffset = useMemo(() => getValueCursorOffset(buffer.lines, cursor, blockState.entries), [buffer.lines, cursor, blockState.entries]);
205
+ const imagesList = useMemo(() => {
206
+ const result = [];
207
+ for (const entry of blockState.entries.values()) {
208
+ if (entry.kind === 'image') {
209
+ result.push({
210
+ id: entry.id,
211
+ data: entry.data,
212
+ mimeType: entry.mimeType,
213
+ byteSize: entry.byteSize,
214
+ displayNumber: entry.displayNumber,
215
+ });
216
+ }
217
+ }
218
+ return result;
219
+ }, [blockState.entries]);
233
220
  const getImagesCallback = useCallback(() => {
234
221
  return imagesList;
235
222
  }, [imagesList]);
236
223
  const setImagesCallback = useCallback((newImages) => {
237
- const map = {};
238
- for (const img of newImages) {
239
- map[img.id] = img;
240
- }
241
- setImages(map);
224
+ setBlockState((prev) => {
225
+ const newEntries = new Map(prev.entries);
226
+ for (const [id, entry] of prev.entries) {
227
+ if (entry.kind === 'image') {
228
+ newEntries.delete(id);
229
+ }
230
+ }
231
+ let nextImageNumber = prev.nextImageNumber;
232
+ for (const img of newImages) {
233
+ nextImageNumber = Math.max(nextImageNumber, img.displayNumber + 1);
234
+ newEntries.set(img.id, {
235
+ kind: 'image',
236
+ id: img.id,
237
+ displayNumber: img.displayNumber,
238
+ data: img.data,
239
+ mimeType: img.mimeType,
240
+ byteSize: img.byteSize,
241
+ });
242
+ }
243
+ return { ...prev, entries: newEntries, nextImageNumber };
244
+ });
242
245
  }, []);
243
246
  return {
244
247
  value,
@@ -256,9 +259,9 @@ export function useTextInput({ initialValue = '', width, historyLimit = 100, und
256
259
  cursorOffset,
257
260
  setCursorOffset: useCallback((offset) => {
258
261
  flushPendingInsertBatch();
259
- setCursor(getCursorFromValueOffset(buffer.lines, offset, placeholderState.placeholders));
260
- }, [buffer.lines, flushPendingInsertBatch, placeholderState.placeholders]),
261
- placeholderState,
262
+ setCursor(getCursorFromValueOffset(buffer.lines, offset, blockState.entries));
263
+ }, [buffer.lines, flushPendingInsertBatch, blockState.entries]),
264
+ blockState,
262
265
  insertImage,
263
266
  images: imagesList,
264
267
  getImages: getImagesCallback,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ink-prompt",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "A React Ink component for prompts",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,30 +0,0 @@
1
- import type { PlaceholderInfo, PlaceholderState } from './types.js';
2
- export declare const MARKER_REGEX: RegExp;
3
- export declare function createMarker(id: number): string;
4
- export declare function createPlaceholderState(): PlaceholderState;
5
- export declare function addPlaceholder(state: PlaceholderState, originalText: string, displayText: string): {
6
- id: number;
7
- marker: string;
8
- state: PlaceholderState;
9
- };
10
- export declare function removePlaceholder(state: PlaceholderState, id: number): PlaceholderState;
11
- export declare function getDisplayLine(line: string, placeholders: Map<number, PlaceholderInfo>): string;
12
- export declare function getValue(lines: string[], placeholders: Map<number, PlaceholderInfo>): string;
13
- export interface MarkerRange {
14
- id: number;
15
- start: number;
16
- end: number;
17
- }
18
- export declare function findPlaceholderAt(line: string, column: number): MarkerRange | null;
19
- export declare function findPlaceholderAfter(line: string, column: number): MarkerRange | null;
20
- export declare function findPlaceholderBefore(line: string, column: number): MarkerRange | null;
21
- export declare function bufferColToDisplayCol(line: string, column: number, placeholders: Map<number, PlaceholderInfo>): number;
22
- export declare function displayColToBufferCol(line: string, displayColumn: number, placeholders: Map<number, PlaceholderInfo>): number;
23
- export declare function getValueCursorOffset(lines: string[], cursor: {
24
- line: number;
25
- column: number;
26
- }, placeholders: Map<number, PlaceholderInfo>): number;
27
- export declare function getCursorFromValueOffset(lines: string[], offset: number, placeholders: Map<number, PlaceholderInfo>): {
28
- line: number;
29
- column: number;
30
- };
@@ -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
- }