Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/btw-panel-compaction-priority.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Fix Esc and Ctrl+C cancelling compaction instead of closing an open /btw panel.
26 changes: 15 additions & 11 deletions apps/kimi-code/src/tui/controllers/editor-keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,21 +129,23 @@ export class EditorKeyboardController {
return;
}

if (host.state.appState.isCompacting) {
// The btw panel stacks above the transcript, so Ctrl+C cancels/closes it
// before touching an in-flight compaction or stream.
if (host.btwPanelController.cancelRunning()) {
this.clearPendingExit();

if (this.clearEditorTextIfPresent()) return;

this.cancelCurrentCompaction();
return;
}

if (host.btwPanelController.cancelRunning()) {
if (host.btwPanelController.closeOrCancel()) {
this.clearPendingExit();
return;
}
if (host.btwPanelController.closeOrCancel()) {

if (host.state.appState.isCompacting) {
this.clearPendingExit();

if (this.clearEditorTextIfPresent()) return;

this.cancelCurrentCompaction();
return;
}

Expand Down Expand Up @@ -184,12 +186,14 @@ export class EditorKeyboardController {
this.clearPendingUndoEsc();
return;
}
if (host.state.appState.isCompacting) {
this.cancelCurrentCompaction();
// The btw panel stacks above the transcript, so Esc dismisses it before
// touching an in-flight compaction or stream.
if (host.btwPanelController.closeOrCancel()) {
this.clearPendingUndoEsc();
return;
}
if (host.btwPanelController.closeOrCancel()) {
if (host.state.appState.isCompacting) {
this.cancelCurrentCompaction();
this.clearPendingUndoEsc();
return;
}
Expand Down
92 changes: 89 additions & 3 deletions apps/kimi-code/test/tui/controllers/editor-keyboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,24 @@ interface Harness {
readonly editor: Record<string, ((...args: never[]) => unknown) | undefined>;
readonly openUndoSelector: ReturnType<typeof vi.fn>;
readonly cancelRunningShellCommand: ReturnType<typeof vi.fn>;
readonly cancelCompaction: ReturnType<typeof vi.fn>;
readonly btwCancelRunning: ReturnType<typeof vi.fn>;
readonly btwCloseOrCancel: ReturnType<typeof vi.fn>;
}

function createHarness(options: { streamingPhase?: string; isCompacting?: boolean } = {}): Harness {
const editor: Record<string, ((...args: never[]) => unknown) | undefined> = {
setHistoryFilter: vi.fn() as unknown as (...args: never[]) => unknown,
setInputMode: vi.fn() as unknown as (...args: never[]) => unknown,
getText: vi.fn(() => '') as unknown as (...args: never[]) => unknown,
setText: vi.fn() as unknown as (...args: never[]) => unknown,
};
const openUndoSelector = vi.fn();
const cancelRunningShellCommand = vi.fn();
const session = { cancel: vi.fn(async () => {}) };
const cancelCompaction = vi.fn(async () => {});
const btwCancelRunning = vi.fn(() => false);
const btwCloseOrCancel = vi.fn(() => false);
const session = { cancel: vi.fn(async () => {}), cancelCompaction };

const host = {
state: {
Expand All @@ -35,7 +43,7 @@ function createHarness(options: { streamingPhase?: string; isCompacting?: boolea
ui: { requestRender: vi.fn() },
},
session,
btwPanelController: { closeOrCancel: vi.fn(() => false) },
btwPanelController: { cancelRunning: btwCancelRunning, closeOrCancel: btwCloseOrCancel },
openUndoSelector,
cancelRunningShellCommand,
} as unknown as EditorKeyboardHost;
Expand All @@ -46,7 +54,15 @@ function createHarness(options: { streamingPhase?: string; isCompacting?: boolea
);
controller.install();

return { host, editor, openUndoSelector, cancelRunningShellCommand };
return {
host,
editor,
openUndoSelector,
cancelRunningShellCommand,
cancelCompaction,
btwCancelRunning,
btwCloseOrCancel,
};
}

function pressEscape(editor: Harness['editor']): void {
Expand All @@ -55,6 +71,12 @@ function pressEscape(editor: Harness['editor']): void {
(handler as () => void)();
}

function pressCtrlC(editor: Harness['editor']): void {
const handler = editor['onCtrlC'];
if (handler === undefined) throw new Error('onCtrlC handler not installed');
(handler as () => void)();
}

function pressNonEscape(editor: Harness['editor']): void {
const handler = editor['onNonEscapeInput'];
if (handler === undefined) throw new Error('onNonEscapeInput handler not installed');
Expand Down Expand Up @@ -123,6 +145,70 @@ describe('EditorKeyboardController double-Esc undo', () => {
});
});

describe('EditorKeyboardController btw panel priority', () => {
it('Esc closes the btw panel first while compacting, without cancelling compaction', () => {
const { editor, btwCloseOrCancel, cancelCompaction } = createHarness({ isCompacting: true });
btwCloseOrCancel.mockReturnValue(true);

pressEscape(editor);

expect(btwCloseOrCancel).toHaveBeenCalledOnce();
expect(cancelCompaction).not.toHaveBeenCalled();
});

it('Esc cancels compaction on the next press once the btw panel is gone', () => {
const { editor, btwCloseOrCancel, cancelCompaction } = createHarness({ isCompacting: true });
btwCloseOrCancel.mockReturnValueOnce(true);

pressEscape(editor);
expect(cancelCompaction).not.toHaveBeenCalled();

pressEscape(editor);
expect(cancelCompaction).toHaveBeenCalledOnce();
});

it('Esc cancels compaction directly when no btw panel is open', () => {
const { editor, btwCloseOrCancel, cancelCompaction } = createHarness({ isCompacting: true });

pressEscape(editor);

expect(btwCloseOrCancel).toHaveBeenCalledOnce();
expect(cancelCompaction).toHaveBeenCalledOnce();
});

it('Ctrl+C cancels a running btw question first while compacting', () => {
const { editor, btwCancelRunning, cancelCompaction } = createHarness({ isCompacting: true });
btwCancelRunning.mockReturnValue(true);

pressCtrlC(editor);

expect(btwCancelRunning).toHaveBeenCalledOnce();
expect(cancelCompaction).not.toHaveBeenCalled();
});

it('Ctrl+C closes an idle btw panel while compacting, without cancelling compaction', () => {
const { editor, btwCloseOrCancel, cancelCompaction } = createHarness({ isCompacting: true });
btwCloseOrCancel.mockReturnValue(true);

pressCtrlC(editor);

expect(btwCloseOrCancel).toHaveBeenCalledOnce();
expect(cancelCompaction).not.toHaveBeenCalled();
});

it('Ctrl+C cancels compaction when no btw panel is open', () => {
const { editor, btwCancelRunning, btwCloseOrCancel, cancelCompaction } = createHarness({
isCompacting: true,
});

pressCtrlC(editor);

expect(btwCancelRunning).toHaveBeenCalledOnce();
expect(btwCloseOrCancel).toHaveBeenCalledOnce();
expect(cancelCompaction).toHaveBeenCalledOnce();
});
});

describe('EditorKeyboardController shell history recall', () => {
type Recall = (entry: string, direction: 1 | -1) => string | undefined;
type Mock = ReturnType<typeof vi.fn>;
Expand Down
Loading