fix: ensure tool return text wrapping respects left column padding (#391)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { relative } from "node:path";
|
||||
import * as Diff from "diff";
|
||||
import { Box, Text } from "ink";
|
||||
import { useTerminalWidth } from "../hooks/useTerminalWidth";
|
||||
import { colors } from "./colors";
|
||||
|
||||
// Helper to format path as relative with ../
|
||||
@@ -28,6 +29,7 @@ interface DiffLineProps {
|
||||
type: "add" | "remove";
|
||||
content: string;
|
||||
compareContent?: string; // The other version to compare against for word diff
|
||||
columns: number;
|
||||
}
|
||||
|
||||
function DiffLine({
|
||||
@@ -35,6 +37,7 @@ function DiffLine({
|
||||
type,
|
||||
content,
|
||||
compareContent,
|
||||
columns,
|
||||
}: DiffLineProps) {
|
||||
const prefix = type === "add" ? "+" : "-";
|
||||
const lineBg =
|
||||
@@ -42,6 +45,9 @@ function DiffLine({
|
||||
const wordBg =
|
||||
type === "add" ? colors.diff.addedWordBg : colors.diff.removedWordBg;
|
||||
|
||||
const prefixWidth = 1; // Single space prefix
|
||||
const contentWidth = Math.max(0, columns - prefixWidth);
|
||||
|
||||
// If we have something to compare against, do word-level diff
|
||||
if (compareContent !== undefined && content.trim() && compareContent.trim()) {
|
||||
const wordDiffs =
|
||||
@@ -50,60 +56,74 @@ function DiffLine({
|
||||
: Diff.diffWords(content, compareContent);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Text> </Text>
|
||||
<Text backgroundColor={lineBg} color={colors.diff.textOnDark}>
|
||||
{`${lineNumber} ${prefix} `}
|
||||
</Text>
|
||||
{wordDiffs.map((part, i) => {
|
||||
if (part.added && type === "add") {
|
||||
// This part was added (show with brighter background, black text)
|
||||
return (
|
||||
<Text
|
||||
key={`word-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={wordBg}
|
||||
color={colors.diff.textOnHighlight}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
} else if (part.removed && type === "remove") {
|
||||
// This part was removed (show with brighter background, black text)
|
||||
return (
|
||||
<Text
|
||||
key={`word-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={wordBg}
|
||||
color={colors.diff.textOnHighlight}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
} else if (!part.added && !part.removed) {
|
||||
// Unchanged part (show with line background, white text)
|
||||
return (
|
||||
<Text
|
||||
key={`word-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={lineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
// Skip parts that don't belong in this line
|
||||
return null;
|
||||
})}
|
||||
<Box flexDirection="row">
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text> </Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text wrap="wrap">
|
||||
<Text backgroundColor={lineBg} color={colors.diff.textOnDark}>
|
||||
{`${lineNumber} ${prefix} `}
|
||||
</Text>
|
||||
{wordDiffs.map((part, i) => {
|
||||
if (part.added && type === "add") {
|
||||
// This part was added (show with brighter background, black text)
|
||||
return (
|
||||
<Text
|
||||
key={`word-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={wordBg}
|
||||
color={colors.diff.textOnHighlight}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
} else if (part.removed && type === "remove") {
|
||||
// This part was removed (show with brighter background, black text)
|
||||
return (
|
||||
<Text
|
||||
key={`word-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={wordBg}
|
||||
color={colors.diff.textOnHighlight}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
} else if (!part.added && !part.removed) {
|
||||
// Unchanged part (show with line background, white text)
|
||||
return (
|
||||
<Text
|
||||
key={`word-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={lineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
// Skip parts that don't belong in this line
|
||||
return null;
|
||||
})}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// No comparison, just show the whole line with one background
|
||||
return (
|
||||
<Box>
|
||||
<Text> </Text>
|
||||
<Text backgroundColor={lineBg} color={colors.diff.textOnDark}>
|
||||
{`${lineNumber} ${prefix} ${content}`}
|
||||
</Text>
|
||||
<Box flexDirection="row">
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text> </Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text
|
||||
backgroundColor={lineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
wrap="wrap"
|
||||
>
|
||||
{`${lineNumber} ${prefix} ${content}`}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -114,10 +134,14 @@ interface WriteRendererProps {
|
||||
}
|
||||
|
||||
export function WriteRenderer({ filePath, content }: WriteRendererProps) {
|
||||
const columns = useTerminalWidth();
|
||||
const relativePath = formatRelativePath(filePath);
|
||||
const lines = content.split("\n");
|
||||
const lineCount = lines.length;
|
||||
|
||||
const prefixWidth = 1; // Single space prefix
|
||||
const contentWidth = Math.max(0, columns - prefixWidth);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text>
|
||||
@@ -125,7 +149,14 @@ export function WriteRenderer({ filePath, content }: WriteRendererProps) {
|
||||
⎿ Wrote {lineCount} line{lineCount !== 1 ? "s" : ""} to {relativePath}
|
||||
</Text>
|
||||
{lines.map((line, i) => (
|
||||
<Text key={`line-${i}-${line.substring(0, 20)}`}> {line}</Text>
|
||||
<Box key={`line-${i}-${line.substring(0, 20)}`} flexDirection="row">
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text> </Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text wrap="wrap">{line}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
@@ -142,6 +173,7 @@ export function EditRenderer({
|
||||
oldString,
|
||||
newString,
|
||||
}: EditRendererProps) {
|
||||
const columns = useTerminalWidth();
|
||||
const relativePath = formatRelativePath(filePath);
|
||||
const oldLines = oldString.split("\n");
|
||||
const newLines = newString.split("\n");
|
||||
@@ -172,6 +204,7 @@ export function EditRenderer({
|
||||
type="remove"
|
||||
content={line}
|
||||
compareContent={singleLineEdit ? newLines[0] : undefined}
|
||||
columns={columns}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -183,6 +216,7 @@ export function EditRenderer({
|
||||
type="add"
|
||||
content={line}
|
||||
compareContent={singleLineEdit ? oldLines[0] : undefined}
|
||||
columns={columns}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
@@ -198,6 +232,7 @@ interface MultiEditRendererProps {
|
||||
}
|
||||
|
||||
export function MultiEditRenderer({ filePath, edits }: MultiEditRendererProps) {
|
||||
const columns = useTerminalWidth();
|
||||
const relativePath = formatRelativePath(filePath);
|
||||
|
||||
// Count total additions and removals
|
||||
@@ -238,6 +273,7 @@ export function MultiEditRenderer({ filePath, edits }: MultiEditRendererProps) {
|
||||
compareContent={
|
||||
singleLineEdit && i === 0 ? newLines[0] : undefined
|
||||
}
|
||||
columns={columns}
|
||||
/>
|
||||
))}
|
||||
{newLines.map((line, i) => (
|
||||
@@ -249,6 +285,7 @@ export function MultiEditRenderer({ filePath, edits }: MultiEditRendererProps) {
|
||||
compareContent={
|
||||
singleLineEdit && i === 0 ? oldLines[0] : undefined
|
||||
}
|
||||
columns={columns}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as Diff from "diff";
|
||||
import { Box, Text } from "ink";
|
||||
import { useTerminalWidth } from "../hooks/useTerminalWidth";
|
||||
import { colors } from "./colors";
|
||||
|
||||
interface MemoryDiffRendererProps {
|
||||
@@ -15,6 +16,8 @@ export function MemoryDiffRenderer({
|
||||
argsText,
|
||||
toolName,
|
||||
}: MemoryDiffRendererProps) {
|
||||
const columns = useTerminalWidth();
|
||||
|
||||
try {
|
||||
const args = JSON.parse(argsText);
|
||||
|
||||
@@ -22,7 +25,9 @@ export function MemoryDiffRenderer({
|
||||
if (toolName === "memory_apply_patch") {
|
||||
const label = args.label || "unknown";
|
||||
const patch = args.patch || "";
|
||||
return <PatchDiffRenderer label={label} patch={patch} />;
|
||||
return (
|
||||
<PatchDiffRenderer label={label} patch={patch} columns={columns} />
|
||||
);
|
||||
}
|
||||
|
||||
// Handle memory tool (command-based)
|
||||
@@ -41,6 +46,7 @@ export function MemoryDiffRenderer({
|
||||
blockName={blockName}
|
||||
oldStr={oldStr}
|
||||
newStr={newStr}
|
||||
columns={columns}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -48,6 +54,8 @@ export function MemoryDiffRenderer({
|
||||
case "insert": {
|
||||
const insertText = args.insert_text || "";
|
||||
const insertLine = args.insert_line;
|
||||
const prefixWidth = 4; // " " indent
|
||||
const contentWidth = Math.max(0, columns - prefixWidth);
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text>
|
||||
@@ -57,14 +65,22 @@ export function MemoryDiffRenderer({
|
||||
{insertLine !== undefined && ` at line ${insertLine}`}
|
||||
</Text>
|
||||
{insertText.split("\n").map((line: string, i: number) => (
|
||||
<Box key={`insert-${i}-${line.substring(0, 20)}`}>
|
||||
<Text> </Text>
|
||||
<Text
|
||||
backgroundColor={colors.diff.addedLineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
>
|
||||
{`+ ${line}`}
|
||||
</Text>
|
||||
<Box
|
||||
key={`insert-${i}-${line.substring(0, 20)}`}
|
||||
flexDirection="row"
|
||||
>
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text>{" "}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text
|
||||
backgroundColor={colors.diff.addedLineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
wrap="wrap"
|
||||
>
|
||||
{`+ ${line}`}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
@@ -74,6 +90,8 @@ export function MemoryDiffRenderer({
|
||||
case "create": {
|
||||
const description = args.description || "";
|
||||
const fileText = args.file_text || "";
|
||||
const prefixWidth = 4; // " " indent
|
||||
const contentWidth = Math.max(0, columns - prefixWidth);
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text>
|
||||
@@ -88,14 +106,22 @@ export function MemoryDiffRenderer({
|
||||
?.split("\n")
|
||||
.slice(0, 3)
|
||||
.map((line: string, i: number) => (
|
||||
<Box key={`create-${i}-${line.substring(0, 20)}`}>
|
||||
<Text> </Text>
|
||||
<Text
|
||||
backgroundColor={colors.diff.addedLineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
>
|
||||
{`+ ${truncate(line, 60)}`}
|
||||
</Text>
|
||||
<Box
|
||||
key={`create-${i}-${line.substring(0, 20)}`}
|
||||
flexDirection="row"
|
||||
>
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text>{" "}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text
|
||||
backgroundColor={colors.diff.addedLineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
wrap="wrap"
|
||||
>
|
||||
{`+ ${truncate(line, 60)}`}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
{fileText && fileText.split("\n").length > 3 && (
|
||||
@@ -162,10 +188,12 @@ function MemoryStrReplaceDiff({
|
||||
blockName,
|
||||
oldStr,
|
||||
newStr,
|
||||
columns,
|
||||
}: {
|
||||
blockName: string;
|
||||
oldStr: string;
|
||||
newStr: string;
|
||||
columns: number;
|
||||
}) {
|
||||
const oldLines = oldStr.split("\n");
|
||||
const newLines = newStr.split("\n");
|
||||
@@ -192,6 +220,7 @@ function MemoryStrReplaceDiff({
|
||||
type="remove"
|
||||
content={line}
|
||||
compareContent={singleLine ? newLines[0] : undefined}
|
||||
columns={columns}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -202,6 +231,7 @@ function MemoryStrReplaceDiff({
|
||||
type="add"
|
||||
content={line}
|
||||
compareContent={singleLine ? oldLines[0] : undefined}
|
||||
columns={columns}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -217,10 +247,12 @@ function DiffLine({
|
||||
type,
|
||||
content,
|
||||
compareContent,
|
||||
columns,
|
||||
}: {
|
||||
type: "add" | "remove";
|
||||
content: string;
|
||||
compareContent?: string;
|
||||
columns: number;
|
||||
}) {
|
||||
const prefix = type === "add" ? "+" : "-";
|
||||
const lineBg =
|
||||
@@ -228,6 +260,9 @@ function DiffLine({
|
||||
const wordBg =
|
||||
type === "add" ? colors.diff.addedWordBg : colors.diff.removedWordBg;
|
||||
|
||||
const prefixWidth = 4; // " " indent
|
||||
const contentWidth = Math.max(0, columns - prefixWidth);
|
||||
|
||||
// Word-level diff if we have something to compare
|
||||
if (compareContent !== undefined && content.trim() && compareContent.trim()) {
|
||||
const wordDiffs =
|
||||
@@ -236,56 +271,70 @@ function DiffLine({
|
||||
: Diff.diffWords(content, compareContent);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Text>{" "}</Text>
|
||||
<Text backgroundColor={lineBg} color={colors.diff.textOnDark}>
|
||||
{`${prefix} `}
|
||||
</Text>
|
||||
{wordDiffs.map((part, i) => {
|
||||
if (part.added && type === "add") {
|
||||
return (
|
||||
<Text
|
||||
key={`w-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={wordBg}
|
||||
color={colors.diff.textOnHighlight}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
} else if (part.removed && type === "remove") {
|
||||
return (
|
||||
<Text
|
||||
key={`w-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={wordBg}
|
||||
color={colors.diff.textOnHighlight}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
} else if (!part.added && !part.removed) {
|
||||
return (
|
||||
<Text
|
||||
key={`w-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={lineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
<Box flexDirection="row">
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text>{" "}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text wrap="wrap">
|
||||
<Text backgroundColor={lineBg} color={colors.diff.textOnDark}>
|
||||
{`${prefix} `}
|
||||
</Text>
|
||||
{wordDiffs.map((part, i) => {
|
||||
if (part.added && type === "add") {
|
||||
return (
|
||||
<Text
|
||||
key={`w-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={wordBg}
|
||||
color={colors.diff.textOnHighlight}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
} else if (part.removed && type === "remove") {
|
||||
return (
|
||||
<Text
|
||||
key={`w-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={wordBg}
|
||||
color={colors.diff.textOnHighlight}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
} else if (!part.added && !part.removed) {
|
||||
return (
|
||||
<Text
|
||||
key={`w-${i}-${part.value.substring(0, 10)}`}
|
||||
backgroundColor={lineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
>
|
||||
{part.value}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Simple line without word diff
|
||||
return (
|
||||
<Box>
|
||||
<Text>{" "}</Text>
|
||||
<Text backgroundColor={lineBg} color={colors.diff.textOnDark}>
|
||||
{`${prefix} ${content}`}
|
||||
</Text>
|
||||
<Box flexDirection="row">
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text>{" "}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text
|
||||
backgroundColor={lineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
wrap="wrap"
|
||||
>
|
||||
{`${prefix} ${content}`}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -298,12 +347,23 @@ function truncate(str: string, maxLen: number): string {
|
||||
/**
|
||||
* Renders a unified-diff patch from memory_apply_patch tool
|
||||
*/
|
||||
function PatchDiffRenderer({ label, patch }: { label: string; patch: string }) {
|
||||
function PatchDiffRenderer({
|
||||
label,
|
||||
patch,
|
||||
columns,
|
||||
}: {
|
||||
label: string;
|
||||
patch: string;
|
||||
columns: number;
|
||||
}) {
|
||||
const lines = patch.split("\n");
|
||||
const maxLines = 8;
|
||||
const displayLines = lines.slice(0, maxLines);
|
||||
const hasMore = lines.length > maxLines;
|
||||
|
||||
const prefixWidth = 4; // " " indent
|
||||
const contentWidth = Math.max(0, columns - prefixWidth);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text>
|
||||
@@ -322,45 +382,74 @@ function PatchDiffRenderer({ label, patch }: { label: string; patch: string }) {
|
||||
|
||||
if (firstChar === "+") {
|
||||
return (
|
||||
<Box key={`patch-${i}-${line.substring(0, 20)}`}>
|
||||
<Text>{" "}</Text>
|
||||
<Text
|
||||
backgroundColor={colors.diff.addedLineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
>
|
||||
{`+ ${content}`}
|
||||
</Text>
|
||||
<Box
|
||||
key={`patch-${i}-${line.substring(0, 20)}`}
|
||||
flexDirection="row"
|
||||
>
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text>{" "}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text
|
||||
backgroundColor={colors.diff.addedLineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
wrap="wrap"
|
||||
>
|
||||
{`+ ${content}`}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
} else if (firstChar === "-") {
|
||||
return (
|
||||
<Box key={`patch-${i}-${line.substring(0, 20)}`}>
|
||||
<Text>{" "}</Text>
|
||||
<Text
|
||||
backgroundColor={colors.diff.removedLineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
>
|
||||
{`- ${content}`}
|
||||
</Text>
|
||||
<Box
|
||||
key={`patch-${i}-${line.substring(0, 20)}`}
|
||||
flexDirection="row"
|
||||
>
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text>{" "}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text
|
||||
backgroundColor={colors.diff.removedLineBg}
|
||||
color={colors.diff.textOnDark}
|
||||
wrap="wrap"
|
||||
>
|
||||
{`- ${content}`}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
} else if (firstChar === " ") {
|
||||
// Context line - show dimmed
|
||||
return (
|
||||
<Box key={`patch-${i}-${line.substring(0, 20)}`}>
|
||||
<Text dimColor>
|
||||
{" "}
|
||||
{content}
|
||||
</Text>
|
||||
<Box
|
||||
key={`patch-${i}-${line.substring(0, 20)}`}
|
||||
flexDirection="row"
|
||||
>
|
||||
<Box width={prefixWidth + 2} flexShrink={0}>
|
||||
<Text dimColor>{" "}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={Math.max(0, columns - prefixWidth - 2)}>
|
||||
<Text dimColor wrap="wrap">
|
||||
{content}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
// Unknown format, show as-is
|
||||
return (
|
||||
<Text key={`patch-${i}-${line.substring(0, 20)}`} dimColor>
|
||||
{" "}
|
||||
{line}
|
||||
</Text>
|
||||
<Box key={`patch-${i}-${line.substring(0, 20)}`} flexDirection="row">
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text dimColor>{" "}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text dimColor wrap="wrap">
|
||||
{line}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
{hasMore && (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Box, Text } from "ink";
|
||||
import type React from "react";
|
||||
import { useTerminalWidth } from "../hooks/useTerminalWidth.js";
|
||||
import { colors } from "./colors.js";
|
||||
|
||||
interface PlanItem {
|
||||
@@ -16,14 +17,22 @@ export const PlanRenderer: React.FC<PlanRendererProps> = ({
|
||||
plan,
|
||||
explanation,
|
||||
}) => {
|
||||
const columns = useTerminalWidth();
|
||||
const prefixWidth = 5; // " ⎿ " or " "
|
||||
const contentWidth = Math.max(0, columns - prefixWidth);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{explanation && (
|
||||
<Box>
|
||||
<Text>{" ⎿ "}</Text>
|
||||
<Text italic dimColor>
|
||||
{explanation}
|
||||
</Text>
|
||||
<Box flexDirection="row">
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text>{" ⎿ "}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text italic dimColor wrap="wrap">
|
||||
{explanation}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{plan.map((item, index) => {
|
||||
@@ -34,21 +43,21 @@ export const PlanRenderer: React.FC<PlanRendererProps> = ({
|
||||
if (item.status === "completed") {
|
||||
// Green with strikethrough
|
||||
textElement = (
|
||||
<Text color={colors.todo.completed} strikethrough>
|
||||
<Text color={colors.todo.completed} strikethrough wrap="wrap">
|
||||
{checkbox} {item.step}
|
||||
</Text>
|
||||
);
|
||||
} else if (item.status === "in_progress") {
|
||||
// Blue bold
|
||||
textElement = (
|
||||
<Text color={colors.todo.inProgress} bold>
|
||||
<Text color={colors.todo.inProgress} bold wrap="wrap">
|
||||
{checkbox} {item.step}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
// Plain text for pending
|
||||
textElement = (
|
||||
<Text>
|
||||
<Text wrap="wrap">
|
||||
{checkbox} {item.step}
|
||||
</Text>
|
||||
);
|
||||
@@ -58,9 +67,13 @@ export const PlanRenderer: React.FC<PlanRendererProps> = ({
|
||||
const prefix = index === 0 && !explanation ? " ⎿ " : " ";
|
||||
|
||||
return (
|
||||
<Box key={`${index}-${item.step.slice(0, 20)}`}>
|
||||
<Text>{prefix}</Text>
|
||||
{textElement}
|
||||
<Box key={`${index}-${item.step.slice(0, 20)}`} flexDirection="row">
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text>{prefix}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
{textElement}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Box, Text } from "ink";
|
||||
import type React from "react";
|
||||
import { useTerminalWidth } from "../hooks/useTerminalWidth.js";
|
||||
import { colors } from "./colors.js";
|
||||
|
||||
interface TodoItem {
|
||||
@@ -14,6 +15,10 @@ interface TodoRendererProps {
|
||||
}
|
||||
|
||||
export const TodoRenderer: React.FC<TodoRendererProps> = ({ todos }) => {
|
||||
const columns = useTerminalWidth();
|
||||
const prefixWidth = 5; // " ⎿ " or " "
|
||||
const contentWidth = Math.max(0, columns - prefixWidth);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{todos.map((todo, index) => {
|
||||
@@ -24,21 +29,21 @@ export const TodoRenderer: React.FC<TodoRendererProps> = ({ todos }) => {
|
||||
if (todo.status === "completed") {
|
||||
// Green with strikethrough
|
||||
textElement = (
|
||||
<Text color={colors.todo.completed} strikethrough>
|
||||
<Text color={colors.todo.completed} strikethrough wrap="wrap">
|
||||
{checkbox} {todo.content}
|
||||
</Text>
|
||||
);
|
||||
} else if (todo.status === "in_progress") {
|
||||
// Blue bold (like code formatting)
|
||||
textElement = (
|
||||
<Text color={colors.todo.inProgress} bold>
|
||||
<Text color={colors.todo.inProgress} bold wrap="wrap">
|
||||
{checkbox} {todo.content}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
// Plain text for pending
|
||||
textElement = (
|
||||
<Text>
|
||||
<Text wrap="wrap">
|
||||
{checkbox} {todo.content}
|
||||
</Text>
|
||||
);
|
||||
@@ -48,9 +53,13 @@ export const TodoRenderer: React.FC<TodoRendererProps> = ({ todos }) => {
|
||||
const prefix = index === 0 ? " ⎿ " : " ";
|
||||
|
||||
return (
|
||||
<Box key={todo.id || index}>
|
||||
<Text>{prefix}</Text>
|
||||
{textElement}
|
||||
<Box key={todo.id || index} flexDirection="row">
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text>{prefix}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
{textElement}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user