@kirosnn/mosaic 0.71.0 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -5
- package/package.json +4 -2
- package/src/agent/Agent.ts +353 -131
- package/src/agent/context.ts +4 -4
- package/src/agent/prompts/systemPrompt.ts +15 -6
- package/src/agent/prompts/toolsPrompt.ts +75 -10
- package/src/agent/provider/anthropic.ts +100 -100
- package/src/agent/provider/google.ts +102 -102
- package/src/agent/provider/mistral.ts +95 -95
- package/src/agent/provider/ollama.ts +77 -60
- package/src/agent/provider/openai.ts +42 -38
- package/src/agent/provider/rateLimit.ts +178 -0
- package/src/agent/provider/xai.ts +99 -99
- package/src/agent/tools/definitions.ts +19 -9
- package/src/agent/tools/executor.ts +95 -85
- package/src/agent/tools/exploreExecutor.ts +8 -10
- package/src/agent/tools/grep.ts +30 -29
- package/src/agent/tools/question.ts +7 -1
- package/src/agent/types.ts +9 -8
- package/src/components/App.tsx +45 -45
- package/src/components/CustomInput.tsx +214 -36
- package/src/components/Main.tsx +1146 -954
- package/src/components/Setup.tsx +1 -1
- package/src/components/Welcome.tsx +1 -1
- package/src/components/main/ApprovalPanel.tsx +4 -3
- package/src/components/main/ChatPage.tsx +858 -675
- package/src/components/main/HomePage.tsx +53 -38
- package/src/components/main/QuestionPanel.tsx +52 -7
- package/src/components/main/ThinkingIndicator.tsx +2 -1
- package/src/index.tsx +50 -20
- package/src/mcp/approvalPolicy.ts +148 -0
- package/src/mcp/cli/add.ts +185 -0
- package/src/mcp/cli/doctor.ts +77 -0
- package/src/mcp/cli/index.ts +85 -0
- package/src/mcp/cli/list.ts +50 -0
- package/src/mcp/cli/logs.ts +24 -0
- package/src/mcp/cli/manage.ts +99 -0
- package/src/mcp/cli/show.ts +53 -0
- package/src/mcp/cli/tools.ts +77 -0
- package/src/mcp/config.ts +223 -0
- package/src/mcp/index.ts +80 -0
- package/src/mcp/processManager.ts +299 -0
- package/src/mcp/rateLimiter.ts +50 -0
- package/src/mcp/registry.ts +151 -0
- package/src/mcp/schemaConverter.ts +100 -0
- package/src/mcp/servers/navigation.ts +854 -0
- package/src/mcp/toolCatalog.ts +169 -0
- package/src/mcp/types.ts +95 -0
- package/src/utils/approvalBridge.ts +17 -5
- package/src/utils/commands/compact.ts +30 -0
- package/src/utils/commands/echo.ts +1 -1
- package/src/utils/commands/index.ts +4 -6
- package/src/utils/commands/new.ts +15 -0
- package/src/utils/commands/types.ts +3 -0
- package/src/utils/config.ts +3 -1
- package/src/utils/diffRendering.tsx +1 -3
- package/src/utils/exploreBridge.ts +10 -0
- package/src/utils/markdown.tsx +163 -99
- package/src/utils/models.ts +31 -9
- package/src/utils/questionBridge.ts +36 -1
- package/src/utils/tokenEstimator.ts +32 -0
- package/src/utils/toolFormatting.ts +268 -7
- package/src/web/app.tsx +72 -72
- package/src/web/components/HomePage.tsx +7 -7
- package/src/web/components/MessageItem.tsx +22 -22
- package/src/web/components/QuestionPanel.tsx +72 -12
- package/src/web/components/Sidebar.tsx +0 -2
- package/src/web/components/ThinkingIndicator.tsx +1 -0
- package/src/web/server.tsx +767 -683
- package/src/utils/commands/redo.ts +0 -74
- package/src/utils/commands/sessions.ts +0 -129
- package/src/utils/commands/undo.ts +0 -75
- package/src/utils/undoRedo.ts +0 -429
- package/src/utils/undoRedoBridge.ts +0 -45
- package/src/utils/undoRedoDb.ts +0 -338
|
@@ -23,12 +23,15 @@ interface CustomInputProps {
|
|
|
23
23
|
pasteRequestId?: number
|
|
24
24
|
disableHistory?: boolean
|
|
25
25
|
submitDisabled?: boolean
|
|
26
|
+
maxWidth?: number
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export function CustomInput({ onSubmit, placeholder = '', password = false, focused = true, pasteRequestId = 0, disableHistory = false, submitDisabled = false }: CustomInputProps) {
|
|
29
|
+
export function CustomInput({ onSubmit, placeholder = '', password = false, focused = true, pasteRequestId = 0, disableHistory = false, submitDisabled = false, maxWidth }: CustomInputProps) {
|
|
29
30
|
const [value, setValue] = useState('')
|
|
30
31
|
const [cursorPosition, setCursorPosition] = useState(0)
|
|
31
32
|
const [terminalWidth, setTerminalWidth] = useState(process.stdout.columns || 80)
|
|
33
|
+
const [selectionStart, setSelectionStart] = useState<number | null>(null)
|
|
34
|
+
const [selectionEnd, setSelectionEnd] = useState<number | null>(null)
|
|
32
35
|
const [pasteBuffer, setPasteBuffer] = useState('')
|
|
33
36
|
const [inPasteMode, setInPasteMode] = useState(false)
|
|
34
37
|
const [historyIndex, setHistoryIndex] = useState(-1)
|
|
@@ -45,6 +48,8 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
45
48
|
const lastPasteUndoRef = useRef<{ prevValue: string; prevCursor: number; nextValue: string; nextCursor: number } | null>(null)
|
|
46
49
|
const valueRef = useRef(value)
|
|
47
50
|
const cursorPositionRef = useRef(cursorPosition)
|
|
51
|
+
const selectionStartRef = useRef<number | null>(selectionStart)
|
|
52
|
+
const selectionEndRef = useRef<number | null>(selectionEnd)
|
|
48
53
|
|
|
49
54
|
const renderer = useRenderer()
|
|
50
55
|
|
|
@@ -135,9 +140,13 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
135
140
|
const normalized = normalizePastedText(pastedText)
|
|
136
141
|
if (!normalized) return
|
|
137
142
|
|
|
138
|
-
const insertAt = cursorPositionRef.current
|
|
139
143
|
const prevValue = valueRef.current
|
|
140
|
-
const
|
|
144
|
+
const selStart = selectionStartRef.current
|
|
145
|
+
const selEnd = selectionEndRef.current
|
|
146
|
+
const hasSelection = typeof selStart === 'number' && typeof selEnd === 'number' && selEnd > selStart
|
|
147
|
+
const insertAt = hasSelection ? selStart! : cursorPositionRef.current
|
|
148
|
+
const deleteUntil = hasSelection ? selEnd! : insertAt
|
|
149
|
+
const nextValue = prevValue.slice(0, insertAt) + normalized + prevValue.slice(deleteUntil)
|
|
141
150
|
lastPasteUndoRef.current = {
|
|
142
151
|
prevValue,
|
|
143
152
|
prevCursor: insertAt,
|
|
@@ -146,6 +155,10 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
146
155
|
}
|
|
147
156
|
setValue(nextValue)
|
|
148
157
|
setCursorPosition(insertAt + normalized.length)
|
|
158
|
+
if (hasSelection) {
|
|
159
|
+
setSelectionStart(null)
|
|
160
|
+
setSelectionEnd(null)
|
|
161
|
+
}
|
|
149
162
|
pasteFlagRef.current = true
|
|
150
163
|
pastedContentRef.current = normalized
|
|
151
164
|
}
|
|
@@ -169,6 +182,8 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
169
182
|
setCursorPosition(normalized.length)
|
|
170
183
|
setHistoryIndex(-1)
|
|
171
184
|
desiredCursorColRef.current = null
|
|
185
|
+
setSelectionStart(null)
|
|
186
|
+
setSelectionEnd(null)
|
|
172
187
|
} catch (error) {
|
|
173
188
|
} finally {
|
|
174
189
|
try {
|
|
@@ -190,6 +205,14 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
190
205
|
cursorPositionRef.current = cursorPosition
|
|
191
206
|
}, [cursorPosition])
|
|
192
207
|
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
selectionStartRef.current = selectionStart
|
|
210
|
+
}, [selectionStart])
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
selectionEndRef.current = selectionEnd
|
|
214
|
+
}, [selectionEnd])
|
|
215
|
+
|
|
193
216
|
useEffect(() => {
|
|
194
217
|
const handleResize = () => {
|
|
195
218
|
setTerminalWidth(process.stdout.columns || 80)
|
|
@@ -267,15 +290,23 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
267
290
|
}
|
|
268
291
|
}, [focused, renderer.keyInput])
|
|
269
292
|
|
|
270
|
-
const wrapTextWithCursor = (text: string, cursorPos: number, maxWidth: number): {
|
|
293
|
+
const wrapTextWithCursor = (text: string, cursorPos: number, maxWidth: number): { lineStarts: number[], lineLengths: number[], cursorLine: number, cursorCol: number } => {
|
|
271
294
|
const safeCursorPos = Math.max(0, Math.min(text.length, cursorPos))
|
|
272
|
-
const
|
|
295
|
+
const lineStarts: number[] = [0]
|
|
296
|
+
const lineLengths: number[] = [0]
|
|
273
297
|
let lineIndex = 0
|
|
274
298
|
let col = 0
|
|
275
299
|
let cursorLine = 0
|
|
276
300
|
let cursorCol = 0
|
|
277
301
|
|
|
278
302
|
for (let i = 0; i <= text.length; i += 1) {
|
|
303
|
+
if (col >= maxWidth) {
|
|
304
|
+
lineStarts.push(i)
|
|
305
|
+
lineLengths.push(0)
|
|
306
|
+
lineIndex += 1
|
|
307
|
+
col = 0
|
|
308
|
+
}
|
|
309
|
+
|
|
279
310
|
if (i === safeCursorPos) {
|
|
280
311
|
cursorLine = lineIndex
|
|
281
312
|
cursorCol = col
|
|
@@ -285,23 +316,28 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
285
316
|
|
|
286
317
|
const ch = text[i]!
|
|
287
318
|
if (ch === '\n') {
|
|
288
|
-
|
|
319
|
+
lineStarts.push(i + 1)
|
|
320
|
+
lineLengths.push(0)
|
|
289
321
|
lineIndex += 1
|
|
290
322
|
col = 0
|
|
291
323
|
continue
|
|
292
324
|
}
|
|
293
325
|
|
|
294
|
-
|
|
295
|
-
lines.push('')
|
|
296
|
-
lineIndex += 1
|
|
297
|
-
col = 0
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
lines[lineIndex] = (lines[lineIndex] || '') + ch
|
|
326
|
+
lineLengths[lineIndex] = (lineLengths[lineIndex] || 0) + 1
|
|
301
327
|
col += 1
|
|
302
328
|
}
|
|
303
329
|
|
|
304
|
-
return {
|
|
330
|
+
return { lineStarts, lineLengths, cursorLine, cursorCol }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const buildDisplayLines = (displayText: string, lineStarts: number[], lineLengths: number[]) => {
|
|
334
|
+
const lines: string[] = []
|
|
335
|
+
for (let i = 0; i < lineStarts.length; i += 1) {
|
|
336
|
+
const start = lineStarts[i] ?? 0
|
|
337
|
+
const len = lineLengths[i] ?? 0
|
|
338
|
+
lines.push(displayText.slice(start, start + len))
|
|
339
|
+
}
|
|
340
|
+
return lines
|
|
305
341
|
}
|
|
306
342
|
|
|
307
343
|
useKeyboard((key) => {
|
|
@@ -342,6 +378,27 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
342
378
|
lastPasteUndoRef.current = null
|
|
343
379
|
pasteFlagRef.current = false
|
|
344
380
|
pastedContentRef.current = ''
|
|
381
|
+
setSelectionStart(null)
|
|
382
|
+
setSelectionEnd(null)
|
|
383
|
+
}
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (key.name === 'a' && (key.ctrl || key.meta)) {
|
|
388
|
+
if (value.length > 0) {
|
|
389
|
+
const selStart = selectionStartRef.current
|
|
390
|
+
const selEnd = selectionEndRef.current
|
|
391
|
+
const hasSelection = typeof selStart === 'number' && typeof selEnd === 'number' && selEnd > selStart
|
|
392
|
+
const fullSelection = hasSelection && selStart === 0 && selEnd === value.length
|
|
393
|
+
if (fullSelection) {
|
|
394
|
+
setSelectionStart(null)
|
|
395
|
+
setSelectionEnd(null)
|
|
396
|
+
setCursorPosition(value.length)
|
|
397
|
+
} else {
|
|
398
|
+
setSelectionStart(0)
|
|
399
|
+
setSelectionEnd(value.length)
|
|
400
|
+
setCursorPosition(value.length)
|
|
401
|
+
}
|
|
345
402
|
}
|
|
346
403
|
return
|
|
347
404
|
}
|
|
@@ -361,6 +418,8 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
361
418
|
pasteFlagRef.current = false
|
|
362
419
|
pastedContentRef.current = ''
|
|
363
420
|
desiredCursorColRef.current = null
|
|
421
|
+
setSelectionStart(null)
|
|
422
|
+
setSelectionEnd(null)
|
|
364
423
|
return
|
|
365
424
|
}
|
|
366
425
|
|
|
@@ -383,34 +442,63 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
383
442
|
pasteFlagRef.current = false
|
|
384
443
|
pastedContentRef.current = ''
|
|
385
444
|
desiredCursorColRef.current = null
|
|
445
|
+
setSelectionStart(null)
|
|
446
|
+
setSelectionEnd(null)
|
|
386
447
|
} else if (key.name === 'backspace') {
|
|
387
448
|
desiredCursorColRef.current = null
|
|
449
|
+
const selStart = selectionStartRef.current
|
|
450
|
+
const selEnd = selectionEndRef.current
|
|
451
|
+
if (typeof selStart === 'number' && typeof selEnd === 'number' && selEnd > selStart) {
|
|
452
|
+
const prev = valueRef.current
|
|
453
|
+
const nextValue = prev.slice(0, selStart) + prev.slice(selEnd)
|
|
454
|
+
setValue(nextValue)
|
|
455
|
+
setCursorPosition(selStart)
|
|
456
|
+
setSelectionStart(null)
|
|
457
|
+
setSelectionEnd(null)
|
|
458
|
+
return
|
|
459
|
+
}
|
|
388
460
|
if (cursorPosition > 0) {
|
|
389
461
|
setValue(prev => prev.slice(0, cursorPosition - 1) + prev.slice(cursorPosition))
|
|
390
462
|
setCursorPosition(prev => prev - 1)
|
|
391
463
|
}
|
|
392
464
|
} else if (key.name === 'delete') {
|
|
393
465
|
desiredCursorColRef.current = null
|
|
466
|
+
const selStart = selectionStartRef.current
|
|
467
|
+
const selEnd = selectionEndRef.current
|
|
468
|
+
if (typeof selStart === 'number' && typeof selEnd === 'number' && selEnd > selStart) {
|
|
469
|
+
const prev = valueRef.current
|
|
470
|
+
const nextValue = prev.slice(0, selStart) + prev.slice(selEnd)
|
|
471
|
+
setValue(nextValue)
|
|
472
|
+
setCursorPosition(selStart)
|
|
473
|
+
setSelectionStart(null)
|
|
474
|
+
setSelectionEnd(null)
|
|
475
|
+
return
|
|
476
|
+
}
|
|
394
477
|
if (key.ctrl || key.meta) {
|
|
395
478
|
setValue('')
|
|
396
479
|
setCursorPosition(0)
|
|
397
480
|
pasteFlagRef.current = false
|
|
398
481
|
pastedContentRef.current = ''
|
|
482
|
+
setSelectionStart(null)
|
|
483
|
+
setSelectionEnd(null)
|
|
399
484
|
} else if (cursorPosition < value.length) {
|
|
400
485
|
setValue(prev => prev.slice(0, cursorPosition) + prev.slice(cursorPosition + 1))
|
|
401
486
|
}
|
|
402
487
|
} else if (key.name === 'up') {
|
|
403
488
|
const lineWidth = Math.max(10, terminalWidth - 4)
|
|
404
|
-
const {
|
|
489
|
+
const { lineLengths, cursorLine: currentCursorLine, cursorCol: currentCursorCol, lineStarts } = wrapTextWithCursor(value, cursorPosition, lineWidth)
|
|
405
490
|
if (currentCursorLine > 0) {
|
|
406
491
|
if (desiredCursorColRef.current === null) {
|
|
407
492
|
desiredCursorColRef.current = currentCursorCol
|
|
408
493
|
}
|
|
409
494
|
const targetLine = currentCursorLine - 1
|
|
410
495
|
const targetCol = desiredCursorColRef.current
|
|
411
|
-
const targetLineLen =
|
|
412
|
-
const
|
|
413
|
-
|
|
496
|
+
const targetLineLen = lineLengths[targetLine] ?? 0
|
|
497
|
+
const lineStart = lineStarts[targetLine] ?? 0
|
|
498
|
+
const newCursorPos = lineStart + Math.min(targetCol, targetLineLen)
|
|
499
|
+
setCursorPosition(Math.min(value.length, newCursorPos))
|
|
500
|
+
setSelectionStart(null)
|
|
501
|
+
setSelectionEnd(null)
|
|
414
502
|
return
|
|
415
503
|
}
|
|
416
504
|
|
|
@@ -425,24 +513,31 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
425
513
|
setHistoryIndex(newIndex)
|
|
426
514
|
setValue(inputHistory[newIndex]!)
|
|
427
515
|
setCursorPosition(inputHistory[newIndex]!.length)
|
|
516
|
+
setSelectionStart(null)
|
|
517
|
+
setSelectionEnd(null)
|
|
428
518
|
} else if (historyIndex > 0) {
|
|
429
519
|
const newIndex = historyIndex - 1
|
|
430
520
|
setHistoryIndex(newIndex)
|
|
431
521
|
setValue(inputHistory[newIndex]!)
|
|
432
522
|
setCursorPosition(inputHistory[newIndex]!.length)
|
|
523
|
+
setSelectionStart(null)
|
|
524
|
+
setSelectionEnd(null)
|
|
433
525
|
}
|
|
434
526
|
} else if (key.name === 'down') {
|
|
435
527
|
const lineWidth = Math.max(10, terminalWidth - 4)
|
|
436
|
-
const {
|
|
437
|
-
if (currentCursorLine <
|
|
528
|
+
const { lineLengths, cursorLine: currentCursorLine, cursorCol: currentCursorCol, lineStarts } = wrapTextWithCursor(value, cursorPosition, lineWidth)
|
|
529
|
+
if (currentCursorLine < lineStarts.length - 1) {
|
|
438
530
|
if (desiredCursorColRef.current === null) {
|
|
439
531
|
desiredCursorColRef.current = currentCursorCol
|
|
440
532
|
}
|
|
441
533
|
const targetLine = currentCursorLine + 1
|
|
442
534
|
const targetCol = desiredCursorColRef.current
|
|
443
|
-
const targetLineLen =
|
|
444
|
-
const
|
|
445
|
-
|
|
535
|
+
const targetLineLen = lineLengths[targetLine] ?? 0
|
|
536
|
+
const lineStart = lineStarts[targetLine] ?? value.length
|
|
537
|
+
const newCursorPos = lineStart + Math.min(targetCol, targetLineLen)
|
|
538
|
+
setCursorPosition(Math.min(value.length, newCursorPos))
|
|
539
|
+
setSelectionStart(null)
|
|
540
|
+
setSelectionEnd(null)
|
|
446
541
|
return
|
|
447
542
|
}
|
|
448
543
|
|
|
@@ -456,31 +551,56 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
456
551
|
setHistoryIndex(newIndex)
|
|
457
552
|
setValue(inputHistory[newIndex]!)
|
|
458
553
|
setCursorPosition(inputHistory[newIndex]!.length)
|
|
554
|
+
setSelectionStart(null)
|
|
555
|
+
setSelectionEnd(null)
|
|
459
556
|
} else {
|
|
460
557
|
setHistoryIndex(-1)
|
|
461
558
|
setValue(currentInput)
|
|
462
559
|
setCursorPosition(currentInput.length)
|
|
560
|
+
setSelectionStart(null)
|
|
561
|
+
setSelectionEnd(null)
|
|
463
562
|
}
|
|
464
563
|
} else if (key.name === 'left') {
|
|
465
564
|
desiredCursorColRef.current = null
|
|
466
565
|
setCursorPosition(prev => Math.max(0, prev - 1))
|
|
566
|
+
setSelectionStart(null)
|
|
567
|
+
setSelectionEnd(null)
|
|
467
568
|
} else if (key.name === 'right') {
|
|
468
569
|
desiredCursorColRef.current = null
|
|
469
570
|
setCursorPosition(prev => Math.min(value.length, prev + 1))
|
|
571
|
+
setSelectionStart(null)
|
|
572
|
+
setSelectionEnd(null)
|
|
470
573
|
} else if (key.name === 'home') {
|
|
471
574
|
desiredCursorColRef.current = null
|
|
472
575
|
setCursorPosition(0)
|
|
576
|
+
setSelectionStart(null)
|
|
577
|
+
setSelectionEnd(null)
|
|
473
578
|
} else if (key.name === 'end') {
|
|
474
579
|
desiredCursorColRef.current = null
|
|
475
580
|
setCursorPosition(value.length)
|
|
581
|
+
setSelectionStart(null)
|
|
582
|
+
setSelectionEnd(null)
|
|
476
583
|
} else if (key.sequence && key.sequence.length > 1 && !key.ctrl && !key.meta && !key.name) {
|
|
477
584
|
addPastedBlock(key.sequence)
|
|
478
585
|
setHistoryIndex(-1)
|
|
479
586
|
desiredCursorColRef.current = null
|
|
587
|
+
setSelectionStart(null)
|
|
588
|
+
setSelectionEnd(null)
|
|
480
589
|
} else if (key.sequence && key.sequence.length === 1 && !key.ctrl && !key.meta) {
|
|
481
590
|
const char = key.sequence
|
|
482
|
-
|
|
483
|
-
|
|
591
|
+
const selStart = selectionStartRef.current
|
|
592
|
+
const selEnd = selectionEndRef.current
|
|
593
|
+
if (typeof selStart === 'number' && typeof selEnd === 'number' && selEnd > selStart) {
|
|
594
|
+
const prev = valueRef.current
|
|
595
|
+
const nextValue = prev.slice(0, selStart) + char + prev.slice(selEnd)
|
|
596
|
+
setValue(nextValue)
|
|
597
|
+
setCursorPosition(selStart + char.length)
|
|
598
|
+
setSelectionStart(null)
|
|
599
|
+
setSelectionEnd(null)
|
|
600
|
+
} else {
|
|
601
|
+
setValue(prev => prev.slice(0, cursorPosition) + char + prev.slice(cursorPosition))
|
|
602
|
+
setCursorPosition(prev => prev + char.length)
|
|
603
|
+
}
|
|
484
604
|
setHistoryIndex(-1)
|
|
485
605
|
desiredCursorColRef.current = null
|
|
486
606
|
}
|
|
@@ -489,7 +609,7 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
489
609
|
const displayValue = password && value ? Array.from(value, (char) => (char === '\n' ? '\n' : '•')).join('') : value
|
|
490
610
|
const isEmpty = value.length === 0
|
|
491
611
|
|
|
492
|
-
const lineWidth = Math.max(10, terminalWidth - 4)
|
|
612
|
+
const lineWidth = Math.max(10, maxWidth ?? (terminalWidth - 4))
|
|
493
613
|
|
|
494
614
|
if (isEmpty) {
|
|
495
615
|
if (!placeholder) {
|
|
@@ -512,21 +632,79 @@ export function CustomInput({ onSubmit, placeholder = '', password = false, focu
|
|
|
512
632
|
)
|
|
513
633
|
}
|
|
514
634
|
|
|
515
|
-
const {
|
|
635
|
+
const { lineStarts, lineLengths, cursorLine, cursorCol } = wrapTextWithCursor(value, cursorPosition, lineWidth)
|
|
636
|
+
const lines = buildDisplayLines(displayValue, lineStarts, lineLengths)
|
|
637
|
+
const selectionRange = (() => {
|
|
638
|
+
if (typeof selectionStart === 'number' && typeof selectionEnd === 'number') {
|
|
639
|
+
const start = Math.max(0, Math.min(selectionStart, selectionEnd))
|
|
640
|
+
const end = Math.max(0, Math.max(selectionStart, selectionEnd))
|
|
641
|
+
if (end > start) return { start, end }
|
|
642
|
+
}
|
|
643
|
+
return null
|
|
644
|
+
})()
|
|
645
|
+
|
|
646
|
+
const buildSelectionSegments = (text: string, absStart: number) => {
|
|
647
|
+
if (!selectionRange || text.length === 0) {
|
|
648
|
+
return [{ text, selected: false }]
|
|
649
|
+
}
|
|
650
|
+
const segStart = absStart
|
|
651
|
+
const segEnd = absStart + text.length
|
|
652
|
+
if (selectionRange.end <= segStart || selectionRange.start >= segEnd) {
|
|
653
|
+
return [{ text, selected: false }]
|
|
654
|
+
}
|
|
655
|
+
const parts: Array<{ text: string; selected: boolean }> = []
|
|
656
|
+
if (selectionRange.start > segStart) {
|
|
657
|
+
parts.push({ text: text.slice(0, selectionRange.start - segStart), selected: false })
|
|
658
|
+
}
|
|
659
|
+
const selFrom = Math.max(selectionRange.start, segStart) - segStart
|
|
660
|
+
const selTo = Math.min(selectionRange.end, segEnd) - segStart
|
|
661
|
+
parts.push({ text: text.slice(selFrom, selTo), selected: true })
|
|
662
|
+
if (selectionRange.end < segEnd) {
|
|
663
|
+
parts.push({ text: text.slice(selTo), selected: false })
|
|
664
|
+
}
|
|
665
|
+
return parts.filter(p => p.text.length > 0)
|
|
666
|
+
}
|
|
516
667
|
|
|
517
668
|
return (
|
|
518
669
|
<box flexDirection="column" flexGrow={1} width="100%">
|
|
519
670
|
{lines.map((line, lineIndex) => (
|
|
520
671
|
<box key={lineIndex} flexDirection="row">
|
|
521
|
-
{
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
672
|
+
{(() => {
|
|
673
|
+
const lineStart = lineStarts[lineIndex] ?? 0
|
|
674
|
+
if (lineIndex === cursorLine) {
|
|
675
|
+
const safeCursorCol = Math.max(0, Math.min(line.length, cursorCol))
|
|
676
|
+
const before = line.slice(0, safeCursorCol)
|
|
677
|
+
const cursorChar = line[safeCursorCol] || ' '
|
|
678
|
+
const after = line.slice(safeCursorCol + 1)
|
|
679
|
+
const beforeSegs = buildSelectionSegments(before, lineStart)
|
|
680
|
+
const afterSegs = buildSelectionSegments(after, lineStart + safeCursorCol + 1)
|
|
681
|
+
return (
|
|
682
|
+
<>
|
|
683
|
+
{beforeSegs.map((seg, i) => (
|
|
684
|
+
<text key={`b-${i}`} fg="white" bg={seg.selected ? "#3a3a3a" : undefined}>
|
|
685
|
+
{seg.text}
|
|
686
|
+
</text>
|
|
687
|
+
))}
|
|
688
|
+
<text fg="black" bg="white">{cursorChar}</text>
|
|
689
|
+
{afterSegs.map((seg, i) => (
|
|
690
|
+
<text key={`a-${i}`} fg="white" bg={seg.selected ? "#3a3a3a" : undefined}>
|
|
691
|
+
{seg.text}
|
|
692
|
+
</text>
|
|
693
|
+
))}
|
|
694
|
+
</>
|
|
695
|
+
)
|
|
696
|
+
}
|
|
697
|
+
const segs = buildSelectionSegments(line || ' ', lineStart)
|
|
698
|
+
return (
|
|
699
|
+
<>
|
|
700
|
+
{segs.map((seg, i) => (
|
|
701
|
+
<text key={`l-${i}`} fg="white" bg={seg.selected ? "#3a3a3a" : undefined}>
|
|
702
|
+
{seg.text}
|
|
703
|
+
</text>
|
|
704
|
+
))}
|
|
705
|
+
</>
|
|
706
|
+
)
|
|
707
|
+
})()}
|
|
530
708
|
</box>
|
|
531
709
|
))}
|
|
532
710
|
</box>
|