Skip to content
Open
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/bash-mode-large-paste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Enter shell mode when pasting a large `!` command into an empty prompt, even if the paste is folded into a marker.
17 changes: 9 additions & 8 deletions apps/kimi-code/src/tui/components/editor/custom-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,14 +523,15 @@ export class CustomEditor extends Editor {
const emptyPromptBeforeInput = this.inputMode === 'prompt' && this.getText().length === 0;
super.handleInput(normalized);

// Enter bash mode when `!...` is pasted into an empty prompt. The typed path
// above handles the single `!` keystroke; this catches bracketed / Ctrl-V
// pastes whose content starts with `!`. Strip the leading `!` so the buffer
// holds only the command, exactly like the typed path.
if (emptyPromptBeforeInput && this.inputMode === 'prompt' && this.getText().startsWith('!')) {
this.inputMode = 'bash';
this.onInputModeChange?.('bash');
this.setText(this.getText().slice(1));
// Enter bash mode when `!...` is pasted into an empty prompt. Check
// getExpandedText() so folded large pastes still match on the leading `!`.
if (emptyPromptBeforeInput && this.inputMode === 'prompt') {
const expanded = this.getExpandedText();
if (expanded.startsWith('!')) {
this.inputMode = 'bash';
this.onInputModeChange?.('bash');
this.setText(expanded.slice(1));
}
}

this.reopenAutocompleteAfterInput();
Expand Down
69 changes: 69 additions & 0 deletions apps/kimi-code/test/tui/components/editor/custom-editor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,75 @@ describe('CustomEditor bash mode via paste', () => {
expect(editor.inputMode).toBe('bash');
expect(editor.getText()).toBe('');
});

it('enters bash mode when a folded large ! paste (>1000 chars) lands in an empty prompt', () => {
const editor = makeEditor();
const modes: Array<'prompt' | 'bash'> = [];
editor.onInputModeChange = (mode) => modes.push(mode);

// 1 + 1000 = 1001 chars → pi-tui folds into a [paste #N …] marker
const body = `printf ok #${'x'.repeat(1000 - 'printf ok #'.length)}`;
const pasted = `!${body}`;
expect(pasted.length).toBe(1001);

editor.handleInput(`${PASTE_START}${pasted}${PASTE_END}`);

expect(editor.inputMode).toBe('bash');
expect(modes).toEqual(['bash']);
expect(editor.getText()).toBe(body);
expect(editor.getText()).not.toContain('[paste #');
expect(editor.getText().startsWith('!')).toBe(false);
});

it('enters bash mode for a ! paste at the 1000-char fold boundary', () => {
const editor = makeEditor();
const body = `printf ok #${'x'.repeat(999 - 'printf ok #'.length)}`;
const pasted = `!${body}`;
expect(pasted.length).toBe(1000);

editor.handleInput(`${PASTE_START}${pasted}${PASTE_END}`);

expect(editor.inputMode).toBe('bash');
expect(editor.getText()).toBe(body);
});

it('enters bash mode when a folded multiline ! paste (>10 lines) lands in an empty prompt', () => {
const editor = makeEditor();
// 11 lines beginning with ! → folded by line count
const lines = ['!echo one', ...Array.from({ length: 10 }, (_, i) => `echo line${i}`)];
expect(lines.length).toBe(11);
const pasted = lines.join('\n');

editor.handleInput(`${PASTE_START}${pasted}${PASTE_END}`);

expect(editor.inputMode).toBe('bash');
expect(editor.getText()).toBe(['echo one', ...lines.slice(1)].join('\n'));
expect(editor.getText()).not.toContain('[paste #');
});

it('enters bash mode for a ! paste at the 10-line fold boundary', () => {
const editor = makeEditor();
const lines = ['!echo one', ...Array.from({ length: 9 }, (_, i) => `echo line${i}`)];
expect(lines.length).toBe(10);
const pasted = lines.join('\n');

editor.handleInput(`${PASTE_START}${pasted}${PASTE_END}`);

expect(editor.inputMode).toBe('bash');
expect(editor.getText()).toBe(['echo one', ...lines.slice(1)].join('\n'));
});

it('does not enter bash mode for a large paste without a leading !', () => {
const editor = makeEditor();
const pasted = `printf ok #${'x'.repeat(1001 - 'printf ok #'.length)}`;
expect(pasted.length).toBe(1001);
expect(pasted.startsWith('!')).toBe(false);

editor.handleInput(`${PASTE_START}${pasted}${PASTE_END}`);

expect(editor.inputMode).toBe('prompt');
expect(editor.getText()).toMatch(/\[paste #1 \d+ chars\]/);
});
});

describe('CustomEditor bash mode file completion', () => {
Expand Down