init claude-code
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import chalk from 'chalk'
|
||||
import { ctrlOToExpand } from '../components/CtrlOToExpand.js'
|
||||
import { stringWidth } from '../ink/stringWidth.js'
|
||||
import sliceAnsi from './sliceAnsi.js'
|
||||
|
||||
// Text rendering utilities for terminal display
|
||||
const MAX_LINES_TO_SHOW = 3
|
||||
// Account for MessageResponse prefix (" ⎿ " = 5 chars) + parent width
|
||||
// reduction (columns - 5 in tool result rendering)
|
||||
const PADDING_TO_PREVENT_OVERFLOW = 10
|
||||
|
||||
/**
|
||||
* Inserts newlines in a string to wrap it at the specified width.
|
||||
* Uses ANSI-aware slicing to avoid splitting escape sequences.
|
||||
* @param text The text to wrap.
|
||||
* @param wrapWidth The width at which to wrap lines (in visible characters).
|
||||
* @returns The wrapped text.
|
||||
*/
|
||||
function wrapText(
|
||||
text: string,
|
||||
wrapWidth: number,
|
||||
): { aboveTheFold: string; remainingLines: number } {
|
||||
const lines = text.split('\n')
|
||||
const wrappedLines: string[] = []
|
||||
|
||||
for (const line of lines) {
|
||||
const visibleWidth = stringWidth(line)
|
||||
if (visibleWidth <= wrapWidth) {
|
||||
wrappedLines.push(line.trimEnd())
|
||||
} else {
|
||||
// Break long lines into chunks of wrapWidth visible characters
|
||||
// using ANSI-aware slicing to preserve escape sequences
|
||||
let position = 0
|
||||
while (position < visibleWidth) {
|
||||
const chunk = sliceAnsi(line, position, position + wrapWidth)
|
||||
wrappedLines.push(chunk.trimEnd())
|
||||
position += wrapWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const remainingLines = wrappedLines.length - MAX_LINES_TO_SHOW
|
||||
|
||||
// If there's only 1 line after the fold, show it directly
|
||||
// instead of showing "... +1 line (ctrl+o to expand)"
|
||||
if (remainingLines === 1) {
|
||||
return {
|
||||
aboveTheFold: wrappedLines
|
||||
.slice(0, MAX_LINES_TO_SHOW + 1)
|
||||
.join('\n')
|
||||
.trimEnd(),
|
||||
remainingLines: 0, // All lines are shown, nothing remaining
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise show the standard MAX_LINES_TO_SHOW
|
||||
return {
|
||||
aboveTheFold: wrappedLines.slice(0, MAX_LINES_TO_SHOW).join('\n').trimEnd(),
|
||||
remainingLines: Math.max(0, remainingLines),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content with line-based truncation for terminal display.
|
||||
* If the content exceeds the maximum number of lines, it truncates the content
|
||||
* and adds a message indicating the number of additional lines.
|
||||
* @param content The content to render.
|
||||
* @param terminalWidth Terminal width for wrapping lines.
|
||||
* @returns The rendered content with truncation if needed.
|
||||
*/
|
||||
export function renderTruncatedContent(
|
||||
content: string,
|
||||
terminalWidth: number,
|
||||
suppressExpandHint = false,
|
||||
): string {
|
||||
const trimmedContent = content.trimEnd()
|
||||
if (!trimmedContent) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const wrapWidth = Math.max(terminalWidth - PADDING_TO_PREVENT_OVERFLOW, 10)
|
||||
|
||||
// Only process enough content for the visible lines. Avoids O(n) wrapping
|
||||
// on huge outputs (e.g. 64MB binary dumps that cause 382K-row screens).
|
||||
const maxChars = MAX_LINES_TO_SHOW * wrapWidth * 4
|
||||
const preTruncated = trimmedContent.length > maxChars
|
||||
const contentForWrapping = preTruncated
|
||||
? trimmedContent.slice(0, maxChars)
|
||||
: trimmedContent
|
||||
|
||||
const { aboveTheFold, remainingLines } = wrapText(
|
||||
contentForWrapping,
|
||||
wrapWidth,
|
||||
)
|
||||
|
||||
const estimatedRemaining = preTruncated
|
||||
? Math.max(
|
||||
remainingLines,
|
||||
Math.ceil(trimmedContent.length / wrapWidth) - MAX_LINES_TO_SHOW,
|
||||
)
|
||||
: remainingLines
|
||||
|
||||
return [
|
||||
aboveTheFold,
|
||||
estimatedRemaining > 0
|
||||
? chalk.dim(
|
||||
`… +${estimatedRemaining} lines${suppressExpandHint ? '' : ` ${ctrlOToExpand()}`}`,
|
||||
)
|
||||
: '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
/** Fast check: would OutputLine truncate this content? Counts raw newlines
|
||||
* only (ignores terminal-width wrapping), so it may return false for a single
|
||||
* very long line that wraps past 3 visual rows — acceptable, since the common
|
||||
* case is multi-line output. */
|
||||
export function isOutputLineTruncated(content: string): boolean {
|
||||
let pos = 0
|
||||
// Need more than MAX_LINES_TO_SHOW newlines (content fills > 3 lines).
|
||||
// The +1 accounts for wrapText showing an extra line when remainingLines==1.
|
||||
for (let i = 0; i <= MAX_LINES_TO_SHOW; i++) {
|
||||
pos = content.indexOf('\n', pos)
|
||||
if (pos === -1) return false
|
||||
pos++
|
||||
}
|
||||
// A trailing newline is a terminator, not a new line — match
|
||||
// renderTruncatedContent's trimEnd() behavior.
|
||||
return pos < content.length
|
||||
}
|
||||
Reference in New Issue
Block a user