Type-to-filter in file panels

Typing while the list is focused builds a filter query: a bar appears under
the column headers (search icon, query, live match count, Esc hint, clear
button) and the listing narrows to substring matches — '..' always stays.
Backspace edits the query, Escape clears it, cursor parks on the first match,
arrows/Enter keep working (the list never loses focus), and Space keeps its
select meaning until a query exists. Filter resets on directory change and
survives refresh; operations act only on visible (filtered) selection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jokin
2026-07-11 22:05:23 +02:00
co-authored by Claude Fable 5
parent 41f9e14cad
commit 9737cad6de
4 changed files with 74 additions and 1 deletions
+36
View File
@@ -100,6 +100,17 @@ export class FilePanel {
return this.panel().selected().has(entry.name);
}
clearFilter(): void {
this.panel().setFilter('');
this.listEl().nativeElement.focus();
}
filterMatches(): number {
return this.panel()
.sorted()
.filter((e) => e.name !== '..').length;
}
sortIndicator(key: SortKey): string {
if (this.panel().sortKey() !== key) return '';
return this.panel().sortAsc() ? '↑' : '↓';
@@ -151,6 +162,31 @@ export class FilePanel {
return;
}
// type-to-filter: printable characters build the query (Space only once a
// query exists — an empty-filter Space keeps its toggle-select meaning)
if (
key.length === 1 &&
!event.metaKey &&
!event.ctrlKey &&
!event.altKey &&
(key !== ' ' || panel.filter() !== '')
) {
event.preventDefault();
panel.setFilter(panel.filter() + key);
this.scrollCursorIntoView();
return;
}
if (key === 'Backspace' && panel.filter() !== '') {
event.preventDefault();
panel.setFilter(panel.filter().slice(0, -1));
return;
}
if (key === 'Escape' && panel.filter() !== '') {
event.preventDefault();
panel.setFilter('');
return;
}
switch (key) {
case 'ArrowDown':
panel.moveCursor(1);