fix: route pane focus bindings past the file explorer

This commit is contained in:
2026-10-11 17:56:33 +02:00
parent ad9b214b8f
commit f595455479
2 changed files with 86 additions and 0 deletions
+2
View File
@@ -199,6 +199,8 @@ Closing a pane gives its space to its sibling:
There is no default close-pane binding. Option must send Alt/Meta in the terminal; on macOS, configure the terminal's Option-key behavior if it instead produces special characters. These bindings do not replace existing Ctrl+j or uppercase motion/edit bindings. There is no default close-pane binding. Option must send Alt/Meta in the terminal; on macOS, configure the terminal's Option-key behavior if it instead produces special characters. These bindings do not replace existing Ctrl+j or uppercase motion/edit bindings.
Pane-focus bindings also work while the ordinary file explorer or picker has focus. Moving to a neighboring pane returns keyboard focus to its document; a floating picker closes, while a docked explorer remains visible. Explicit `tree` or `selection` navigation overrides and disabled keys take precedence over pane-focus bindings.
Diff comparisons reuse the split geometry and directional-focus engine. Option/Alt navigation moves between Before, After, and Files first, then to a neighboring workspace pane when there is no internal target in that direction. Splitting a diff text view creates an ordinary sibling showing the same buffer; historical snapshots remain read-only, including after the comparison closes. Diff comparisons reuse the split geometry and directional-focus engine. Option/Alt navigation moves between Before, After, and Files first, then to a neighboring workspace pane when there is no internal target in that direction. Splitting a diff text view creates an ordinary sibling showing the same buffer; historical snapshots remain read-only, including after the comparison closes.
## Buffers ## Buffers
+84
View File
@@ -1662,6 +1662,9 @@ impl Editor {
.neighbor(self.pane_id, direction, self.pane_bounds) .neighbor(self.pane_id, direction, self.pane_bounds)
{ {
self.finish_pane_interaction(); self.finish_pane_interaction();
if self.diff.is_none() {
self.blur_file_picker();
}
self.select_pane(id); self.select_pane(id);
if self.active.view.mode == Mode::Insert { if self.active.view.mode == Mode::Insert {
self.begin_insert(); self.begin_insert();
@@ -4260,6 +4263,28 @@ impl Editor {
if (picker_focused || self.palette.is_some()) && self.handle_selection_binding(&input) { if (picker_focused || self.palette.is_some()) && self.handle_selection_binding(&input) {
return None; return None;
} }
if picker_focused
&& let Some(Binding::Action(action)) =
self.bindings.get(&mode).and_then(|keys| keys.get(&input))
&& matches!(
action,
Action::PaneFocusLeft
| Action::PaneFocusDown
| Action::PaneFocusUp
| Action::PaneFocusRight
)
{
let action = *action;
if self
.file_picker
.as_mut()
.expect("focused picker exists")
.handle_filter_input(&input)
{
return None;
}
return self.act(action);
}
if picker_focused { if picker_focused {
self.handle_file_picker(input); self.handle_file_picker(input);
return None; return None;
@@ -8040,6 +8065,65 @@ mod tests {
} }
} }
#[test]
fn pane_focus_remaps_do_not_steal_printable_picker_filter_input() {
let directory = Directory::new();
fs::write(directory.path("hello.txt"), "alpha").unwrap();
let mut editor = editor();
editor.project_root = directory.0.clone();
editor.handle(Input::Char('|'));
editor
.bind("normal", "h", Some("core.pane.focus_left".into()))
.unwrap();
editor.open_file_picker(FilePresentation::Floating);
editor.handle(Input::Char('h'));
assert_eq!(editor.pane_id(), 2);
assert_eq!(editor.file_picker().unwrap().query, "h");
}
#[test]
fn pane_focus_bindings_escape_focused_explorer_without_changing_documents() {
let directory = Directory::new();
fs::write(directory.path("a.txt"), "alpha").unwrap();
fs::write(directory.path("b.txt"), "beta").unwrap();
let mut editor = editor();
editor.project_root = directory.0.clone();
editor.handle(Input::Char('|'));
editor.handle(Input::Char('_'));
let original = editor.lines().to_vec();
for (key, pane) in [('k', 2), ('h', 1), ('l', 2), ('j', 3)] {
editor.open_file_picker(FilePresentation::Docked);
assert!(editor.file_picker().unwrap().focused);
editor.handle(Input::Alt(key));
assert_eq!(editor.pane_id(), pane);
assert!(!editor.file_picker().unwrap().focused);
assert_eq!(editor.lines(), original);
}
}
#[test]
fn explorer_navigation_overrides_take_precedence_over_pane_focus() {
for context in ["tree", "selection"] {
for target in [None, Some("core.down")] {
let directory = Directory::new();
fs::write(directory.path("a.txt"), "alpha").unwrap();
fs::write(directory.path("b.txt"), "beta").unwrap();
let mut editor = editor();
editor.project_root = directory.0.clone();
editor.handle(Input::Char('|'));
editor
.bind(context, "alt+h", target.map(str::to_owned))
.unwrap();
editor.open_file_picker(FilePresentation::Docked);
editor.handle(Input::Alt('h'));
assert_eq!(editor.pane_id(), 2);
let picker = editor.file_picker().unwrap();
assert!(picker.focused);
assert_eq!(picker.selected, usize::from(target.is_some()));
}
}
}
#[test] #[test]
fn nested_panes_inherit_views_and_focus_geometrically_without_touching_ctrl_j() { fn nested_panes_inherit_views_and_focus_geometrically_without_touching_ctrl_j() {
let mut editor = editor(); let mut editor = editor();