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/tidy-platform-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor
---

Add Vim-style j/k navigation to non-search TUI pickers.
13 changes: 11 additions & 2 deletions apps/kimi-code/src/tui/utils/searchable-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* The component owns presentation and the keys that carry component-specific
* meaning — Enter (submit), Esc (cancel), and ←/→ (paging in one picker, a
* thinking toggle in another). This unit owns the keys that behave identically
* everywhere: ↑/↓, PgUp/PgDn, and search editing.
* everywhere: ↑/↓, PgUp/PgDn, Vim-style j/k navigation for non-search
* pickers, and search editing.
*/

import { fuzzyFilter, Key, matchesKey } from '@earendil-works/pi-tui';
Expand Down Expand Up @@ -105,6 +106,7 @@ export class SearchableList<T> {
* Enter, Esc, and ←/→ are intentionally left to the component.
*/
handleKey(data: string): boolean {
const ch = printableChar(data);
if (matchesKey(data, Key.up)) {
this.moveUp();
return true;
Expand All @@ -121,6 +123,14 @@ export class SearchableList<T> {
this.pageDown();
return true;
}
if (!this.searchable && ch === 'k') {
this.moveUp();
return true;
}
if (!this.searchable && ch === 'j') {
this.moveDown();
return true;
}
if (!this.searchable) return false;
if (matchesKey(data, Key.backspace)) {
if (this.query.length > 0) {
Expand All @@ -129,7 +139,6 @@ export class SearchableList<T> {
}
return true;
}
const ch = printableChar(data);
if (isPrintableChar(ch)) {
this.query += ch;
this.cursor = 0;
Expand Down
13 changes: 13 additions & 0 deletions apps/kimi-code/test/tui/utils/searchable-list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,17 @@ describe('SearchableList', () => {
expect(search.handleKey(BACKSPACE)).toBe(true);
expect(search.view().query).toBe('');
});

it('supports j/k navigation for non-search pickers without stealing searchable input', () => {
const nav = make({ searchable: false, initialIndex: 1 });
expect(nav.handleKey('j')).toBe(true);
expect(nav.selected()).toBe('item02');
expect(nav.handleKey('k')).toBe(true);
expect(nav.selected()).toBe('item01');

const search = make({ searchable: true });
expect(search.handleKey('j')).toBe(true);
expect(search.view().query).toBe('j');
expect(search.selected()).toBe(search.view().items[0]);
});
});