mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
feat: show full plan cards (#536)
* feat: show full plan cards * chore: patch plan card changeset
This commit is contained in:
parent
0fe13173f4
commit
b785e2698a
17 changed files with 21 additions and 208 deletions
5
.changeset/full-plan-card.md
Normal file
5
.changeset/full-plan-card.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Show full plan cards directly and remove the Plan card keyboard shortcut.
|
||||
|
|
@ -217,7 +217,6 @@ export class ApprovalPanelComponent extends Container implements Focusable {
|
|||
private request: PendingApproval;
|
||||
private readonly colors: ColorPalette;
|
||||
private readonly onToggleToolOutput: (() => void) | undefined;
|
||||
private readonly onTogglePlanExpand: (() => void) | undefined;
|
||||
private readonly onOpenPreview:
|
||||
| ((block: DiffDisplayBlock | FileContentDisplayBlock) => void)
|
||||
| undefined;
|
||||
|
|
@ -227,7 +226,6 @@ export class ApprovalPanelComponent extends Container implements Focusable {
|
|||
onResponse: (response: ApprovalPanelResponse) => void,
|
||||
colors: ColorPalette,
|
||||
onToggleToolOutput?: () => void,
|
||||
onTogglePlanExpand?: () => void,
|
||||
onOpenPreview?: (block: DiffDisplayBlock | FileContentDisplayBlock) => void,
|
||||
) {
|
||||
super();
|
||||
|
|
@ -235,7 +233,6 @@ export class ApprovalPanelComponent extends Container implements Focusable {
|
|||
this.onResponse = onResponse;
|
||||
this.colors = colors;
|
||||
this.onToggleToolOutput = onToggleToolOutput;
|
||||
this.onTogglePlanExpand = onTogglePlanExpand;
|
||||
this.onOpenPreview = onOpenPreview;
|
||||
this.feedbackInput.onSubmit = (value) => {
|
||||
this.submit(this.selectedIndex, value);
|
||||
|
|
@ -281,8 +278,6 @@ export class ApprovalPanelComponent extends Container implements Focusable {
|
|||
const previewable = this.findPreviewableBlock();
|
||||
if (previewable !== undefined && this.onOpenPreview !== undefined) {
|
||||
this.onOpenPreview(previewable);
|
||||
} else {
|
||||
this.onTogglePlanExpand?.();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ export class QuestionDialogComponent extends Container implements Focusable {
|
|||
private readonly answers: (string | undefined)[];
|
||||
|
||||
private readonly onToggleToolOutput: (() => void) | undefined;
|
||||
private readonly onTogglePlanExpand: (() => void) | undefined;
|
||||
|
||||
constructor(
|
||||
request: PendingQuestion,
|
||||
|
|
@ -107,7 +106,6 @@ export class QuestionDialogComponent extends Container implements Focusable {
|
|||
colors: ColorPalette,
|
||||
maxVisibleOptions = 6,
|
||||
onToggleToolOutput?: () => void,
|
||||
onTogglePlanExpand?: () => void,
|
||||
) {
|
||||
super();
|
||||
this.request = request;
|
||||
|
|
@ -115,7 +113,6 @@ export class QuestionDialogComponent extends Container implements Focusable {
|
|||
this.colors = colors;
|
||||
this.maxVisibleOptions = maxVisibleOptions;
|
||||
this.onToggleToolOutput = onToggleToolOutput;
|
||||
this.onTogglePlanExpand = onTogglePlanExpand;
|
||||
this.otherInput.onSubmit = (value) => {
|
||||
this.commitOtherInput(value, 'enter');
|
||||
};
|
||||
|
|
@ -147,11 +144,6 @@ export class QuestionDialogComponent extends Container implements Focusable {
|
|||
return;
|
||||
}
|
||||
|
||||
if (matchesKey(data, Key.ctrl('e'))) {
|
||||
this.onTogglePlanExpand?.();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isEditingOther()) {
|
||||
this.handleOtherInput(data);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -96,10 +96,6 @@ export class CustomEditor extends Editor {
|
|||
public onCtrlD?: () => void;
|
||||
public onCtrlC?: () => void;
|
||||
public onToggleToolExpand?: () => void;
|
||||
// Returns true when a plan card actually handled the toggle. When it
|
||||
// returns false (no plan in the transcript) the keystroke falls through
|
||||
// to pi-tui's default ctrl+e binding (move cursor to end of line).
|
||||
public onTogglePlanExpand?: () => boolean;
|
||||
public onOpenExternalEditor?: () => void;
|
||||
public onCtrlS?: () => void;
|
||||
public onUndo?: () => void;
|
||||
|
|
@ -292,11 +288,6 @@ export class CustomEditor extends Editor {
|
|||
return;
|
||||
}
|
||||
|
||||
if (matchesKey(normalized, Key.ctrl('e'))) {
|
||||
if (this.onTogglePlanExpand?.() === true) return;
|
||||
// No plan to toggle — fall through to pi-tui's end-of-line.
|
||||
}
|
||||
|
||||
if (matchesKey(normalized, Key.ctrl('s'))) {
|
||||
this.onCtrlS?.();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ const TITLE_PREFIX = ' plan: ';
|
|||
const TITLE_SUFFIX = ' ';
|
||||
|
||||
export interface PlanBoxOptions {
|
||||
maxContentLines?: number;
|
||||
expanded?: boolean;
|
||||
status?: {
|
||||
readonly label: string;
|
||||
readonly colorHex: string;
|
||||
|
|
@ -29,8 +27,6 @@ export interface PlanBoxOptions {
|
|||
|
||||
export class PlanBoxComponent implements Component {
|
||||
private readonly markdown: Markdown;
|
||||
private readonly maxContentLines: number | undefined;
|
||||
private readonly expanded: boolean;
|
||||
private readonly status: PlanBoxOptions['status'];
|
||||
private cachedWidth: number | undefined;
|
||||
private cachedLines: string[] | undefined;
|
||||
|
|
@ -47,8 +43,6 @@ export class PlanBoxComponent implements Component {
|
|||
// instance means repeated render() calls from the parent Container
|
||||
// hit the cache instead of re-parsing on every frame.
|
||||
this.markdown = new Markdown(plan.trim(), 0, 0, markdownTheme);
|
||||
this.maxContentLines = opts?.maxContentLines;
|
||||
this.expanded = opts?.expanded ?? false;
|
||||
this.status = opts?.status;
|
||||
}
|
||||
|
||||
|
|
@ -81,20 +75,12 @@ export class PlanBoxComponent implements Component {
|
|||
const bottom = indent + paint('└' + '─'.repeat(horzLen) + '┘');
|
||||
|
||||
const rawLines = this.markdown.render(contentWidth);
|
||||
const { shown, hiddenCount } = this.capContentLines(rawLines);
|
||||
|
||||
const lines: string[] = [top];
|
||||
for (const raw of shown) {
|
||||
for (const raw of rawLines) {
|
||||
const pad = Math.max(0, contentWidth - visibleWidth(raw));
|
||||
lines.push(indent + paint('│') + ' ' + raw + ' '.repeat(pad) + ' ' + paint('│'));
|
||||
}
|
||||
if (hiddenCount > 0) {
|
||||
const footer = chalk.dim(
|
||||
`... (${String(hiddenCount)} more line${hiddenCount === 1 ? '' : 's'}, ctrl+e to expand)`,
|
||||
);
|
||||
const pad = Math.max(0, contentWidth - visibleWidth(footer));
|
||||
lines.push(indent + paint('│') + ' ' + footer + ' '.repeat(pad) + ' ' + paint('│'));
|
||||
}
|
||||
lines.push(bottom);
|
||||
|
||||
this.cachedWidth = width;
|
||||
|
|
@ -102,15 +88,6 @@ export class PlanBoxComponent implements Component {
|
|||
return lines;
|
||||
}
|
||||
|
||||
private capContentLines(rawLines: string[]): { shown: string[]; hiddenCount: number } {
|
||||
const cap = this.maxContentLines;
|
||||
if (this.expanded || cap === undefined || rawLines.length <= cap) {
|
||||
return { shown: rawLines, hiddenCount: 0 };
|
||||
}
|
||||
const shownCount = Math.max(0, cap - 1);
|
||||
return { shown: rawLines.slice(0, shownCount), hiddenCount: rawLines.length - shownCount };
|
||||
}
|
||||
|
||||
private buildTitle(horzLen: number): string {
|
||||
const fallback = ' plan ';
|
||||
const statusSuffix = this.buildStatusSuffix();
|
||||
|
|
|
|||
|
|
@ -472,7 +472,6 @@ class PrefixedWrappedLine implements Component {
|
|||
|
||||
export class ToolCallComponent extends Container {
|
||||
private expanded = false;
|
||||
private planExpanded = false;
|
||||
private toolCall: ToolCallBlockData;
|
||||
private result: ToolResultBlockData | undefined;
|
||||
private colors: ColorPalette;
|
||||
|
|
@ -592,17 +591,6 @@ export class ToolCallComponent extends Container {
|
|||
this.rebuildBody();
|
||||
}
|
||||
|
||||
// Toggle the plan box's expanded state independently from tool-output
|
||||
// expansion. Returns true iff this card actually owns a plan preview
|
||||
// (ExitPlanMode), so the caller can decide whether to consume the keystroke.
|
||||
setPlanExpanded(expanded: boolean): boolean {
|
||||
if (this.toolCall.name !== 'ExitPlanMode') return false;
|
||||
if (this.planExpanded === expanded) return true;
|
||||
this.planExpanded = expanded;
|
||||
this.rebuildBody();
|
||||
return true;
|
||||
}
|
||||
|
||||
setResult(result: ToolResultBlockData): void {
|
||||
this.result = result;
|
||||
// Result supersedes any live progress chatter; the result body is the
|
||||
|
|
@ -1740,8 +1728,6 @@ export class ToolCallComponent extends Container {
|
|||
if (this.markdownTheme !== undefined) {
|
||||
this.addChild(
|
||||
new PlanBoxComponent(plan, this.markdownTheme, this.colors.success, path, {
|
||||
maxContentLines: this.computePlanBoxMaxContentLines(),
|
||||
expanded: this.planExpanded,
|
||||
status: this.resolvePlanBoxStatus(),
|
||||
}),
|
||||
);
|
||||
|
|
@ -1750,12 +1736,6 @@ export class ToolCallComponent extends Container {
|
|||
}
|
||||
}
|
||||
|
||||
private computePlanBoxMaxContentLines(): number | undefined {
|
||||
const rows = this.ui?.terminal.rows;
|
||||
if (rows === undefined || !Number.isFinite(rows) || rows <= 0) return undefined;
|
||||
return Math.max(8, Math.floor(rows * 0.6) - 4);
|
||||
}
|
||||
|
||||
private resolvePlanForPreview(): string {
|
||||
const inlinePlan = str(this.toolCall.args['plan']);
|
||||
if (inlinePlan.length > 0) return inlinePlan;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ export interface EditorKeyboardHost {
|
|||
updateEditorBorderHighlight(text?: string): void;
|
||||
updateQueueDisplay(): void;
|
||||
toggleToolOutputExpansion(): void;
|
||||
togglePlanExpansion(): boolean;
|
||||
hideSessionPicker(): void;
|
||||
stop(exitCode?: number): Promise<void>;
|
||||
handlePlanToggle(next: boolean): void;
|
||||
|
|
@ -150,8 +149,6 @@ export class EditorKeyboardController {
|
|||
host.toggleToolOutputExpansion();
|
||||
};
|
||||
|
||||
editor.onTogglePlanExpand = () => host.togglePlanExpansion();
|
||||
|
||||
editor.onCtrlS = () => {
|
||||
if (host.state.appState.streamingPhase === 'idle' || host.state.appState.isCompacting) return;
|
||||
const text = editor.getText().trim();
|
||||
|
|
|
|||
|
|
@ -617,7 +617,6 @@ export class StreamingUIController {
|
|||
state.appState.workDir,
|
||||
);
|
||||
if (state.toolOutputExpanded) tc.setExpanded(true);
|
||||
if (state.planExpanded) tc.setPlanExpanded(true);
|
||||
this._pendingToolComponents.set(toolCall.id, tc);
|
||||
|
||||
if (toolCall.name !== 'Agent') this._pendingAgentGroup = null;
|
||||
|
|
@ -664,7 +663,6 @@ export class StreamingUIController {
|
|||
state.appState.workDir,
|
||||
);
|
||||
if (state.toolOutputExpanded) completed.setExpanded(true);
|
||||
if (state.planExpanded) completed.setPlanExpanded(true);
|
||||
state.transcriptContainer.addChild(completed);
|
||||
state.ui.requestRender();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ import {
|
|||
type TUIStartupState,
|
||||
} from './types';
|
||||
import { createTUIState, type TUIState } from './tui-state';
|
||||
import { isExpandable, isPlanExpandable } from './utils/component-capabilities';
|
||||
import { isExpandable } from './utils/component-capabilities';
|
||||
import { isDeadTerminalError } from './utils/dead-terminal';
|
||||
import { formatErrorMessage } from './utils/event-payload';
|
||||
import { ImageAttachmentStore, type ImageAttachment } from './utils/image-attachment-store';
|
||||
|
|
@ -1313,7 +1313,6 @@ export class KimiTUI {
|
|||
this.state.appState.workDir,
|
||||
);
|
||||
if (this.state.toolOutputExpanded) tc.setExpanded(true);
|
||||
if (this.state.planExpanded) tc.setPlanExpanded(true);
|
||||
return tc;
|
||||
}
|
||||
if (entry.backgroundAgentStatus !== undefined) {
|
||||
|
|
@ -1589,21 +1588,6 @@ export class KimiTUI {
|
|||
this.state.ui.requestRender();
|
||||
}
|
||||
|
||||
// Returns true when at least one card toggled, so the caller can consume the keystroke.
|
||||
togglePlanExpansion(): boolean {
|
||||
const next = !this.state.planExpanded;
|
||||
let toggled = false;
|
||||
for (const child of this.state.transcriptContainer.children) {
|
||||
if (isPlanExpandable(child) && child.setPlanExpanded(next)) {
|
||||
toggled = true;
|
||||
}
|
||||
}
|
||||
if (!toggled) return false;
|
||||
this.state.planExpanded = next;
|
||||
this.state.ui.requestRender();
|
||||
return true;
|
||||
}
|
||||
|
||||
updateEditorBorderHighlight(text?: string): void {
|
||||
const trimmed = (text ?? this.state.editor.getText()).trimStart();
|
||||
const highlighted = this.state.appState.planMode || trimmed.startsWith('/');
|
||||
|
|
@ -1832,9 +1816,6 @@ export class KimiTUI {
|
|||
() => {
|
||||
this.toggleToolOutputExpansion();
|
||||
},
|
||||
() => {
|
||||
this.togglePlanExpansion();
|
||||
},
|
||||
(block) => {
|
||||
this.openApprovalPreview(panel, block);
|
||||
},
|
||||
|
|
@ -1905,9 +1886,6 @@ export class KimiTUI {
|
|||
() => {
|
||||
this.toggleToolOutputExpansion();
|
||||
},
|
||||
() => {
|
||||
this.togglePlanExpansion();
|
||||
},
|
||||
);
|
||||
this.mountEditorReplacement(dialog);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ export interface TUIState {
|
|||
terminalState: TerminalState;
|
||||
activitySpinner: { instance: MoonLoader; style: SpinnerStyle } | null;
|
||||
toolOutputExpanded: boolean;
|
||||
planExpanded: boolean;
|
||||
sessions: SessionRow[];
|
||||
loadingSessions: boolean;
|
||||
activeDialog: 'session-picker' | 'help' | null;
|
||||
|
|
@ -93,7 +92,6 @@ export function createTUIState(options: KimiTUIOptions): TUIState {
|
|||
terminalState: createTerminalState(),
|
||||
activitySpinner: null,
|
||||
toolOutputExpanded: false,
|
||||
planExpanded: false,
|
||||
sessions: [],
|
||||
loadingSessions: false,
|
||||
activeDialog: null,
|
||||
|
|
|
|||
|
|
@ -2,12 +2,6 @@ export interface Expandable {
|
|||
setExpanded(expanded: boolean): void;
|
||||
}
|
||||
|
||||
export interface PlanExpandable {
|
||||
// Returns true iff the component actually owns a plan preview and
|
||||
// applied the new state.
|
||||
setPlanExpanded(expanded: boolean): boolean;
|
||||
}
|
||||
|
||||
export interface Disposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
|
@ -21,15 +15,6 @@ export function isExpandable(obj: unknown): obj is Expandable {
|
|||
);
|
||||
}
|
||||
|
||||
export function isPlanExpandable(obj: unknown): obj is PlanExpandable {
|
||||
return (
|
||||
typeof obj === 'object' &&
|
||||
obj !== null &&
|
||||
'setPlanExpanded' in obj &&
|
||||
typeof (obj as PlanExpandable).setPlanExpanded === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
export function hasDispose(value: unknown): value is Disposable {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
|
|
|
|||
|
|
@ -260,14 +260,12 @@ describe('ApprovalPanelComponent', () => {
|
|||
},
|
||||
};
|
||||
let toolOutputToggles = 0;
|
||||
let planToggles = 0;
|
||||
const previewCalls: Array<DiffDisplayBlock | FileContentDisplayBlock> = [];
|
||||
const dialog = new ApprovalPanelComponent(
|
||||
pending,
|
||||
(r) => responses.push(r),
|
||||
COLORS,
|
||||
() => toolOutputToggles++,
|
||||
() => planToggles++,
|
||||
(block) => previewCalls.push(block),
|
||||
);
|
||||
|
||||
|
|
@ -284,8 +282,7 @@ describe('ApprovalPanelComponent', () => {
|
|||
expect(after).not.toContain('new30');
|
||||
expect(after).toContain('ctrl+e preview');
|
||||
expect(previewCalls).toEqual([diffBlock]);
|
||||
// The unrelated forward-only callbacks must not fire for ctrl+e.
|
||||
expect(planToggles).toBe(0);
|
||||
// The unrelated forward-only callback must not fire for ctrl+e.
|
||||
expect(toolOutputToggles).toBe(0);
|
||||
expect(responses).toEqual([]);
|
||||
});
|
||||
|
|
@ -320,10 +317,7 @@ describe('ApprovalPanelComponent', () => {
|
|||
expect(after).not.toContain('new30');
|
||||
});
|
||||
|
||||
// When there is no diff / file_content block to preview (e.g. plan_review
|
||||
// with an empty display), ctrl+e falls through to the legacy global plan
|
||||
// expand toggle so plan mode keeps working.
|
||||
it('falls through to onTogglePlanExpand when there is nothing to preview', () => {
|
||||
it('does nothing on ctrl+e when there is nothing to preview', () => {
|
||||
const pending: PendingApproval = {
|
||||
data: {
|
||||
id: 'approval_plan_only',
|
||||
|
|
@ -335,19 +329,16 @@ describe('ApprovalPanelComponent', () => {
|
|||
choices: [{ label: 'Approve', response: 'approved' }],
|
||||
},
|
||||
};
|
||||
let planToggles = 0;
|
||||
const previewCalls: Array<DiffDisplayBlock | FileContentDisplayBlock> = [];
|
||||
const dialog = new ApprovalPanelComponent(
|
||||
pending,
|
||||
() => {},
|
||||
COLORS,
|
||||
undefined,
|
||||
() => planToggles++,
|
||||
(block) => previewCalls.push(block),
|
||||
);
|
||||
|
||||
dialog.handleInput('\u0005'); // Ctrl+E
|
||||
expect(planToggles).toBe(1);
|
||||
expect(previewCalls).toEqual([]);
|
||||
});
|
||||
|
||||
|
|
@ -377,7 +368,6 @@ describe('ApprovalPanelComponent', () => {
|
|||
(r) => responses.push(r),
|
||||
COLORS,
|
||||
undefined,
|
||||
undefined,
|
||||
(block) => previewCalls.push(block),
|
||||
);
|
||||
|
||||
|
|
@ -420,7 +410,6 @@ describe('ApprovalPanelComponent', () => {
|
|||
() => {},
|
||||
COLORS,
|
||||
undefined,
|
||||
undefined,
|
||||
(block) => previewCalls.push(block),
|
||||
);
|
||||
const collapsed = strip(dialog.render(120).join('\n'));
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ function makePending(
|
|||
function makeDialog(
|
||||
pending: PendingQuestion,
|
||||
onToggleToolOutput?: () => void,
|
||||
onTogglePlanExpand?: () => void,
|
||||
): {
|
||||
dialog: QuestionDialogComponent;
|
||||
collected: string[][];
|
||||
|
|
@ -53,7 +52,6 @@ function makeDialog(
|
|||
darkColors,
|
||||
6,
|
||||
onToggleToolOutput,
|
||||
onTogglePlanExpand,
|
||||
);
|
||||
return { dialog, collected, methods };
|
||||
}
|
||||
|
|
@ -431,17 +429,6 @@ describe('QuestionDialogComponent', () => {
|
|||
expect(collected).toEqual([]);
|
||||
});
|
||||
|
||||
it('forwards ctrl+e to the global plan-expand toggle without answering', () => {
|
||||
let planToggles = 0;
|
||||
const pending = makePending([
|
||||
{ question: 'Q?', multi_select: false, options: [{ label: 'A' }] },
|
||||
]);
|
||||
const { dialog, collected } = makeDialog(pending, undefined, () => planToggles++);
|
||||
dialog.handleInput('\u0005'); // Ctrl+E
|
||||
expect(planToggles).toBe(1);
|
||||
expect(collected).toEqual([]);
|
||||
});
|
||||
|
||||
describe('long-content wrapping', () => {
|
||||
const longQuestion =
|
||||
'Please confirm whether this dangerous shell command should really be executed in the current workspace, including all of its side effects on the filesystem and the network.';
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ describe('ToolCallComponent', () => {
|
|||
expect(after).not.toContain('/tmp/refactor.md');
|
||||
});
|
||||
|
||||
it('caps the plan preview to the terminal height and expands on ctrl+e', () => {
|
||||
it('renders the full plan preview', () => {
|
||||
const longPlan = `# Refactor session\n\n${Array.from({ length: 40 }, (_, i) => `- step ${String(i + 1)}`).join('\n')}`;
|
||||
const component = new ToolCallComponent(
|
||||
{
|
||||
|
|
@ -269,39 +269,13 @@ describe('ToolCallComponent', () => {
|
|||
createMarkdownTheme(darkColors),
|
||||
);
|
||||
|
||||
const collapsed = strip(component.render(100).join('\n'));
|
||||
expect(collapsed).toContain('step 1');
|
||||
expect(collapsed).toMatch(/\.\.\. \(\d+ more lines, ctrl\+e to expand\)/);
|
||||
expect(collapsed).not.toContain('step 40');
|
||||
|
||||
expect(component.setPlanExpanded(true)).toBe(true);
|
||||
const expanded = strip(component.render(100).join('\n'));
|
||||
expect(expanded).toContain('step 40');
|
||||
expect(expanded).not.toContain('ctrl+e to expand');
|
||||
});
|
||||
|
||||
it('plan preview controls are no-ops for non-ExitPlanMode tool calls', () => {
|
||||
const component = new ToolCallComponent(
|
||||
{
|
||||
id: 'call_bash_plan',
|
||||
name: 'Bash',
|
||||
args: { command: 'echo hi' },
|
||||
},
|
||||
undefined,
|
||||
darkColors,
|
||||
undefined,
|
||||
createMarkdownTheme(darkColors),
|
||||
);
|
||||
|
||||
expect(component.setPlanExpanded(true)).toBe(false);
|
||||
component.setPlanInfo({ plan: 'should be ignored', path: '/etc/hosts' });
|
||||
|
||||
const out = strip(component.render(100).join('\n'));
|
||||
expect(out).not.toContain('should be ignored');
|
||||
expect(out).not.toContain('plan:');
|
||||
expect(out).toContain('step 1');
|
||||
expect(out).toContain('step 40');
|
||||
expect(out).not.toContain('more lines');
|
||||
});
|
||||
|
||||
it('ctrl+o does not affect the plan preview cap', () => {
|
||||
it('ctrl+o does not affect the full plan preview', () => {
|
||||
const longPlan = `# P\n\n${Array.from({ length: 40 }, (_, i) => `- step ${String(i + 1)}`).join('\n')}`;
|
||||
const component = new ToolCallComponent(
|
||||
{
|
||||
|
|
@ -316,8 +290,8 @@ describe('ToolCallComponent', () => {
|
|||
);
|
||||
component.setExpanded(true);
|
||||
const out = strip(component.render(100).join('\n'));
|
||||
expect(out).toContain('ctrl+e to expand');
|
||||
expect(out).not.toContain('step 40');
|
||||
expect(out).toContain('step 40');
|
||||
expect(out).not.toContain('more lines');
|
||||
});
|
||||
|
||||
it('header chips an Approved status when ExitPlanMode result indicates approval', () => {
|
||||
|
|
|
|||
|
|
@ -89,44 +89,13 @@ describe('PlanBoxComponent', () => {
|
|||
expect(top).not.toContain('plan:');
|
||||
});
|
||||
|
||||
it('renders all lines when content fits under maxContentLines', () => {
|
||||
const plan = Array.from({ length: 5 }, (_, i) => `- step ${String(i + 1)}`).join('\n');
|
||||
const box = new PlanBoxComponent(plan, theme, darkColors.success, undefined, {
|
||||
maxContentLines: 20,
|
||||
});
|
||||
it('renders all plan lines without a truncation footer', () => {
|
||||
const plan = Array.from({ length: 30 }, (_, i) => `- step ${String(i + 1)}`).join('\n');
|
||||
const box = new PlanBoxComponent(plan, theme, darkColors.success);
|
||||
const out = strip(box.render(80).join('\n'));
|
||||
expect(out).toContain('step 1');
|
||||
expect(out).toContain('step 5');
|
||||
expect(out).not.toContain('ctrl+e to expand');
|
||||
});
|
||||
|
||||
it('truncates content over maxContentLines with a footer inside the box', () => {
|
||||
const plan = Array.from({ length: 30 }, (_, i) => `- step ${String(i + 1)}`).join('\n');
|
||||
const box = new PlanBoxComponent(plan, theme, darkColors.success, undefined, {
|
||||
maxContentLines: 10,
|
||||
});
|
||||
const rendered = box.render(80);
|
||||
const out = strip(rendered.join('\n'));
|
||||
expect(out).toContain('step 1');
|
||||
expect(out).toContain('step 9');
|
||||
expect(out).not.toContain('step 10');
|
||||
expect(out).toMatch(/\.\.\. \(\d+ more lines, ctrl\+e to expand\)/);
|
||||
// Footer must live inside the bordered box, not after it.
|
||||
const footerIdx = rendered.findIndex((line) => strip(line).includes('ctrl+e to expand'));
|
||||
const bottomIdx = rendered.findIndex((line) => strip(line).includes('└'));
|
||||
expect(footerIdx).toBeGreaterThan(-1);
|
||||
expect(footerIdx).toBeLessThan(bottomIdx);
|
||||
expect(strip(rendered[footerIdx]!)).toMatch(/^\s+│.*│\s*$/);
|
||||
});
|
||||
|
||||
it('renders the full plan when expanded is true, ignoring maxContentLines', () => {
|
||||
const plan = Array.from({ length: 30 }, (_, i) => `- step ${String(i + 1)}`).join('\n');
|
||||
const box = new PlanBoxComponent(plan, theme, darkColors.success, undefined, {
|
||||
maxContentLines: 10,
|
||||
expanded: true,
|
||||
});
|
||||
const out = strip(box.render(80).join('\n'));
|
||||
expect(out).toContain('step 30');
|
||||
expect(out).not.toContain('ctrl+e to expand');
|
||||
expect(out).not.toContain('more lines');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ Press `Shift-Tab` to enable or disable Plan mode. When enabled, the Agent priori
|
|||
| `Ctrl-G` | Edit the current input in an external editor |
|
||||
| `Ctrl-V` | Paste an image or video from the clipboard (Unix / macOS) |
|
||||
| `Alt-V` | Paste an image or video from the clipboard (Windows) |
|
||||
| `Ctrl-E` | Expand or collapse the Plan card (moves the cursor to end-of-line when no Plan card is present) |
|
||||
| `Ctrl--` | Undo |
|
||||
|
||||
Pressing `Ctrl-G` opens an external editor, selected according to the following priority:
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ Kimi Code CLI 的 TUI 交互模式支持一套键盘快捷键。键位按使用
|
|||
| `Ctrl-G` | 在外部编辑器中编辑当前输入 |
|
||||
| `Ctrl-V` | 粘贴剪贴板中的图片或视频(Unix / macOS) |
|
||||
| `Alt-V` | 粘贴剪贴板中的图片或视频(Windows) |
|
||||
| `Ctrl-E` | 展开或折叠 Plan 卡片(无 Plan 卡片时将光标移到行尾) |
|
||||
| `Ctrl--` | 撤销(Undo) |
|
||||
|
||||
按 `Ctrl-G` 会打开外部编辑器,编辑器按以下优先级选择:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue