Skip to content

Commit 4005e64

Browse files
Show terminal command in run_terminal_command preview
🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
1 parent 8572335 commit 4005e64

File tree

2 files changed

+60
-27
lines changed

2 files changed

+60
-27
lines changed

cli/src/components/message-block.tsx

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import type { ChatTheme } from '../utils/theme-system'
1616
const trimTrailingNewlines = (value: string): string =>
1717
value.replace(/[\r\n]+$/g, '')
1818

19+
const sanitizePreview = (value: string): string =>
20+
value.replace(/[#*_`~\[\]()]/g, '').trim()
21+
1922
interface MessageBlockProps {
2023
messageId: string
2124
blocks?: ContentBlock[]
@@ -123,13 +126,23 @@ export const MessageBlock = ({
123126
.split('\n')
124127
.filter((line) => line.trim())
125128
const firstLine = lines[0] || ''
129+
const lastLine = lines[lines.length - 1] || firstLine
130+
const commandPreview =
131+
block.toolName === 'run_terminal_command' &&
132+
block.input &&
133+
typeof (block.input as any).command === 'string'
134+
? `$ ${(block.input as any).command.trim()}`
135+
: null
136+
126137
const streamingPreview = isStreaming
127-
? firstLine.replace(/[#*_`~\[\]()]/g, '').trim() + '...'
138+
? commandPreview ?? `${sanitizePreview(firstLine)}...`
128139
: ''
129140

130141
let finishedPreview = ''
131142
if (!isStreaming && isCollapsed) {
132-
if (block.toolName === 'run_terminal_command' && block.output) {
143+
if (commandPreview) {
144+
finishedPreview = commandPreview
145+
} else if (block.toolName === 'run_terminal_command' && block.output) {
133146
const outputLines = block.output
134147
.split('\n')
135148
.filter((line) => line.trim())
@@ -139,9 +152,7 @@ export const MessageBlock = ({
139152
? '...\n' + lastThreeLines.join('\n')
140153
: lastThreeLines.join('\n')
141154
} else {
142-
finishedPreview = lastLine
143-
.replace(/[#*_`~\[\]()]/g, '')
144-
.trim()
155+
finishedPreview = sanitizePreview(lastLine)
145156
}
146157
}
147158

@@ -284,15 +295,22 @@ export const MessageBlock = ({
284295
const lastNestedLine =
285296
nestedLines[nestedLines.length - 1] || firstNestedLine
286297

298+
const nestedCommandPreview =
299+
nestedBlock.toolName === 'run_terminal_command' &&
300+
nestedBlock.input &&
301+
typeof (nestedBlock.input as any).command === 'string'
302+
? `$ ${(nestedBlock.input as any).command.trim()}`
303+
: null
304+
287305
const nestedStreamingPreview = isNestedStreaming
288-
? firstNestedLine
289-
.replace(/[#*_`~\[\]()]/g, '')
290-
.trim() + '...'
306+
? nestedCommandPreview ?? `${sanitizePreview(firstNestedLine)}...`
291307
: ''
292308

293309
let nestedFinishedPreview = ''
294310
if (!isNestedStreaming && isNestedCollapsed) {
295-
if (
311+
if (nestedCommandPreview) {
312+
nestedFinishedPreview = nestedCommandPreview
313+
} else if (
296314
nestedBlock.toolName === 'run_terminal_command' &&
297315
nestedBlock.output
298316
) {
@@ -305,9 +323,9 @@ export const MessageBlock = ({
305323
? '...\n' + lastThreeLines.join('\n')
306324
: lastThreeLines.join('\n')
307325
} else {
308-
nestedFinishedPreview = lastNestedLine
309-
.replace(/[#*_`~\[\]()]/g, '')
310-
.trim()
326+
nestedFinishedPreview = sanitizePreview(
327+
lastNestedLine,
328+
)
311329
}
312330
}
313331

cli/src/components/multiline-input.tsx

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useKeyboard, usePaste } from '@opentui/react'
22
import { useCallback, useState, useEffect, useMemo, useRef } from 'react'
33

4-
import type { ScrollBoxRenderable } from '@opentui/core'
4+
import { TextAttributes, type ScrollBoxRenderable } from '@opentui/core'
55

66

77
// Helper functions for text manipulation
@@ -53,6 +53,8 @@ function findNextWordBoundary(text: string, cursor: number): number {
5353
return pos
5454
}
5555

56+
const CURSOR_CHAR = '\u2009'
57+
5658
interface MultilineInputProps {
5759
value: string
5860
onChange: (value: string) => void
@@ -394,30 +396,29 @@ export function MultilineInput({
394396
// Calculate display with cursor
395397
const displayValue = value || placeholder
396398
const isPlaceholder = !value && placeholder
397-
const displayText =
398-
focused && !isPlaceholder
399-
? displayValue.slice(0, cursorPosition) +
400-
'│' +
401-
displayValue.slice(cursorPosition)
402-
: displayValue
399+
const showCursor = focused && !isPlaceholder
400+
const beforeCursor = showCursor ? displayValue.slice(0, cursorPosition) : ''
401+
const afterCursor = showCursor ? displayValue.slice(cursorPosition) : ''
402+
const displayText = showCursor
403+
? `${beforeCursor}${CURSOR_CHAR}${afterCursor}`
404+
: displayValue
403405

404406
// Memoize height calculation to avoid expensive computation on every render
405407
const height = useMemo(() => {
406408
const maxCharsPerLine = Math.max(1, width - 4)
407-
const lines = displayValue.split('\n')
409+
const contentForHeight = showCursor ? displayText : displayValue
410+
const lines = contentForHeight.split('\n')
408411
let totalLineCount = 0
409412
for (const line of lines) {
410-
if (line.length === 0) {
413+
const length = line.length
414+
if (length === 0) {
411415
totalLineCount += 1
412416
} else {
413-
// Account for cursor character which adds 1 to display length
414-
const displayLength =
415-
focused && !isPlaceholder ? line.length + 1 : line.length
416-
totalLineCount += Math.ceil(displayLength / maxCharsPerLine)
417+
totalLineCount += Math.ceil(length / maxCharsPerLine)
417418
}
418419
}
419420
return Math.max(1, Math.min(totalLineCount, maxHeight))
420-
}, [displayValue, width, focused, isPlaceholder, maxHeight])
421+
}, [displayValue, displayText, showCursor, width, maxHeight])
421422

422423
return (
423424
<scrollbox
@@ -456,7 +457,21 @@ export function MultilineInput({
456457
: theme.inputFg,
457458
}}
458459
>
459-
{displayText}
460+
{showCursor ? (
461+
<>
462+
{beforeCursor}
463+
<span
464+
fg={theme.cursor}
465+
bg={theme.cursor}
466+
attributes={TextAttributes.BOLD}
467+
>
468+
{CURSOR_CHAR}
469+
</span>
470+
{afterCursor}
471+
</>
472+
) : (
473+
displayText
474+
)}
460475
</text>
461476
</scrollbox>
462477
)

0 commit comments

Comments
 (0)