diff --git a/docs/editing.md b/docs/editing.md index 3334ffd..99a4042 100644 --- a/docs/editing.md +++ b/docs/editing.md @@ -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. +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. ## Buffers diff --git a/src/editor.rs b/src/editor.rs index 36c6388..109dfcf 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1662,6 +1662,9 @@ impl Editor { .neighbor(self.pane_id, direction, self.pane_bounds) { self.finish_pane_interaction(); + if self.diff.is_none() { + self.blur_file_picker(); + } self.select_pane(id); if self.active.view.mode == Mode::Insert { self.begin_insert(); @@ -4260,6 +4263,28 @@ impl Editor { if (picker_focused || self.palette.is_some()) && self.handle_selection_binding(&input) { 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 { self.handle_file_picker(input); 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] fn nested_panes_inherit_views_and_focus_geometrically_without_touching_ctrl_j() { let mut editor = editor();