fix: make explorer filtering explicitly modal

This commit is contained in:
Valdemar Lorenzen
2026-10-08 16:18:16 +02:00
parent 6d7be0c169
commit 5b8e5fbfbf
5 changed files with 72 additions and 78 deletions
+48 -53
View File
@@ -496,10 +496,10 @@ fn draw_file_list(frame: &mut Frame, picker: &FilePicker, panel: Rect, navigatio
let title = match (picker.presentation, picker.view, browsing) {
(FilePresentation::Floating, FileView::List, _) => " Files · List ",
(FilePresentation::Docked, FileView::List, _) => " Explorer · List ",
(FilePresentation::Floating, FileView::Tree, true) => " Files · Tree browse ",
(FilePresentation::Docked, FileView::Tree, true) => " Explorer · Tree browse ",
(FilePresentation::Floating, FileView::Tree, false) => " Files · Tree search ",
(FilePresentation::Docked, FileView::Tree, false) => " Explorer · Tree search ",
(FilePresentation::Floating, FileView::Tree, true) => " Files · Tree NORMAL ",
(FilePresentation::Docked, FileView::Tree, true) => " Explorer · Tree NORMAL ",
(FilePresentation::Floating, FileView::Tree, false) => " Files · Tree INSERT ",
(FilePresentation::Docked, FileView::Tree, false) => " Explorer · Tree INSERT ",
};
let block = Block::default()
.borders(if bordered {
@@ -525,7 +525,7 @@ fn draw_file_list(frame: &mut Frame, picker: &FilePicker, panel: Rect, navigatio
])
.areas(content);
let query = if browsing {
"browse · / search · ^T view".into()
"NORMAL · i filter · ^T view".into()
} else {
format!("> {}", display_text(&picker.query))
};
@@ -584,31 +584,30 @@ fn draw_file_list(frame: &mut Frame, picker: &FilePicker, panel: Rect, navigatio
.take(count)
.map(|(index, entry)| {
let selected = index == selected_index;
let style = if selected {
let mut style = if entry.is_dir {
Style::default().fg(Color::Cyan)
} else {
Style::default()
};
if selected {
style = style
.bg(if picker.focused {
Color::Blue
} else {
Color::DarkGray
})
.add_modifier(Modifier::BOLD)
} else {
Style::default()
};
.add_modifier(Modifier::BOLD);
}
let label = match picker.view {
FileView::List => {
format!("{} {}", if selected { ">" } else { " " }, entry.label)
}
FileView::Tree => format!(
"{} {:indent$}{} {}",
"{} {:indent$}{}{}",
if selected { ">" } else { " " },
"",
if entry.is_dir {
if entry.expanded { "▾" } else { "▸" }
} else {
" "
},
entry.label,
if entry.is_dir { "/" } else { "" },
indent = entry.depth * 2,
),
};
@@ -624,16 +623,14 @@ fn draw_file_list(frame: &mut Frame, picker: &FilePicker, panel: Rect, navigatio
}
}
let help = if picker.focused {
let controls = match picker.presentation {
FilePresentation::Floating => "Enter · Esc/^W close · ^R scan",
FilePresentation::Docked => "Enter · Esc/Tab edit · ^W · ^R",
let controls = match (picker.presentation, browsing) {
(FilePresentation::Floating, true) => "Enter · i filter · Esc/^W close · ^R · ^T",
(FilePresentation::Docked, true) => "Enter · i filter · Esc/Tab edit · ^W · ^R · ^T",
(_, false) if picker.view == FileView::Tree => "Enter · Esc NORMAL · ^W · ^R · ^T",
(FilePresentation::Floating, false) => "Enter · Esc/^W close · ^R · ^T",
(FilePresentation::Docked, false) => "Enter · Esc/Tab edit · ^W · ^R · ^T",
};
let view_controls = if picker.view == FileView::Tree {
"/ search · ^T view"
} else {
"^T view"
};
format!("{navigation_hint} · {view_controls} · {controls}")
format!("{navigation_hint} · {controls}")
} else {
"Editor focus · invoke explorer to browse".into()
};
@@ -1011,7 +1008,6 @@ mod tests {
.draw(|frame| draw_file_list(frame, &picker, area, editor.file_navigation_hint()))
.unwrap();
let text = screen_text(&terminal);
assert!(text.contains("Tree browse"), "{text}");
assert!(text.contains("No project files"), "{text}");
assert_eq!(
terminal.get_cursor_position().unwrap(),
@@ -1155,20 +1151,32 @@ mod tests {
.unwrap();
let text = screen_text(&terminal);
for label in [
" ▾ chain/a/b",
" leaf.txt",
" ▸ empty",
" ▾ mixed",
" ▾ nested",
" inner.txt",
" sibling.txt",
"> ▾ single",
" only.txt",
" root.txt",
"/ search · ^T view",
" chain/a/b/",
" leaf.txt",
" empty/",
" mixed/",
" nested/",
" inner.txt",
" sibling.txt",
"> single/",
" only.txt",
" root.txt",
] {
assert!(text.contains(label), "{label}: {text}");
}
assert!(!text.contains('▸') && !text.contains('▾'), "{text}");
let buffer = terminal.backend().buffer();
for (index, entry) in picker.entries().enumerate() {
let y = area.y + 3 + index as u16;
assert_eq!(
buffer[(area.x + 1, y)].fg,
if entry.is_dir {
Color::Cyan
} else {
Color::Reset
},
);
}
assert!(!text.contains("single/only.txt"), "{text}");
assert!(!text.contains("mixed/nested/inner.txt"), "{text}");
let cursor = terminal.get_cursor_position().unwrap();
@@ -1185,7 +1193,6 @@ mod tests {
.draw(|frame| draw_file_list(frame, &picker, area, editor.file_navigation_hint()))
.unwrap();
let text = screen_text(&terminal);
assert!(text.contains("List"), "{text}");
assert!(text.contains("chain/a/b/leaf.txt"), "{text}");
assert!(text.contains("single/only.txt"), "{text}");
assert!(!text.contains("▸"), "{text}");
@@ -1236,7 +1243,7 @@ mod tests {
}
if width == 100 {
let text = screen_text(&terminal);
assert!(text.contains("> file.txt"), "{text}");
assert!(text.contains("> file.txt"), "{text}");
assert!(!text.contains("dir-00"), "{text}");
}
picker.start_search();
@@ -1295,7 +1302,7 @@ mod tests {
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
.unwrap();
let browse_cursor = terminal.get_cursor_position().unwrap();
editor.handle(Input::Char('/'));
editor.handle(Input::Char('i'));
assert!(editor.file_picker().unwrap().searching);
assert_eq!(editor.file_picker().unwrap().query, "");
assert!(
@@ -1325,7 +1332,6 @@ mod tests {
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
.unwrap();
let text = screen_text(&terminal);
assert!(text.contains("Tree search"), "{text}");
assert!(text.contains("needle.txt"), "{text}");
assert_eq!(terminal.get_cursor_position().unwrap().y, 1);
editor.handle(Input::Escape);
@@ -1353,7 +1359,7 @@ mod tests {
assert_eq!(editor.lines(), ["unchanged"]);
editor.handle(Input::Char(' '));
editor.handle(Input::Char('e'));
editor.handle(Input::Char('/'));
editor.handle(Input::Char('i'));
editor.handle(Input::Char('n'));
editor.handle(Input::Char('\t'));
assert!(!editor.file_picker().unwrap().focused);
@@ -1376,7 +1382,6 @@ mod tests {
terminal
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
.unwrap();
assert!(screen_text(&terminal).contains("Tree search"));
assert_eq!(terminal.get_cursor_position().unwrap().y, 1);
editor.handle(Input::Escape);
assert!(editor.file_picker().unwrap().focused);
@@ -1423,22 +1428,12 @@ mod tests {
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
.unwrap();
let text = screen_text(&terminal);
assert!(
text.contains(if view == FileView::Tree {
"Tree browse"
} else {
"List"
}),
"{text}"
);
assert!(text.contains("^T view"), "{text}");
assert!(text.contains(editor.file_navigation_hint()), "{text}");
if view == FileView::Tree {
assert_ne!(
editor.file_navigation_hint(),
editor.selection_navigation_hint()
);
assert!(text.contains("/ search"), "{text}");
let cursor = terminal.get_cursor_position().unwrap();
assert_eq!(
terminal.backend().buffer()[(cursor.x, cursor.y)].symbol(),