Picker: Support child and parent navigation ( menu::SelectChild and menu::SelectParent in PickerDelegate ) fixes (#61053) (#61314)

# Objective

- Provides directory navigation (forward and backward) inside the
built-in system file finder / path prompt using keyboard
  shortcuts. 
- Fixes #61053 

## Solution

1. Extended `PickerDelegate` trait with `select_child` and
`select_parent` methods following conventions from other methods such as
`confirm_completion` (defaulting to `None`)
2. Implemented the new delegate methods inside `OpenPathDelegate`:
- `select_child`: Finds the selected directory candidate, appends it to
the current path query, and appends the path separator.
- `select_parent`: Moves upwards by trimming the current directory and
getting the parent directory path.
3. Added listeners for `Self::select_child` and `Self::select_parent` in
the `/crates/picker/src/render.rs`

## Testing

- Did you test these changes? If so, how?
I actually went ahead and changed the keybindings in
`/assets/keymaps/default-macos.json` as shown :
```json
  {
    "context": "Picker > Editor",
    "use_key_equivalents": true,
    "bindings": {
      "escape": "menu::Cancel",
      "up": "menu::SelectPrevious",
      "down": "menu::SelectNext",
      // I binded to shift arrow because I wanted to keep the cursor movement still relevant
      "shift-left": "menu::SelectParent",// shift left
      "shift-right": "menu::SelectChild",// shift right
      "tab": "picker::ConfirmCompletion",
      "alt-enter": ["picker::ConfirmInput", { "secondary": false }],
      "cmd-alt-enter": ["picker::ConfirmInput", { "secondary": true }]
      // Picker bindings (TogglePreview, SetPreviewRight/Below/Hidden,
      // ToggleActionsMenu) live in keymaps/specific-overrides-macos.json, which
      // is loaded after the base keymap so they win over conflicting base-keymap
      // Editor bindings.
    }
  },
```
Then I did manual testing as shown in the Showcase Section.
I did not push this change as I was not sure if this was required as the
bug was about adding support for child and parent navigation.

- Are there any parts that need more testing?
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
This is my first Pull Request in a large Open Source Repository, so any
feedback on testing conventions or additional edge cases would be
especially helpful.

- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable

## Showcase
<img width="2404" height="1136" alt="Screen Recording 2026-07-20 at 12
52 06 AM"
src="https://github.com/user-attachments/assets/462e382d-5396-4a01-b75f-0199612cd062"
/>


---

Release Notes:

- Fixed child and parent directory navigation in the built-in folder
finder using the left and right arrow keys.

---------

Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
This commit is contained in:
vinod akshatandChristopher Biscardi authored and GitHub committed 2026-07-22 08:42:34 +00:00
1 parent b7e1f5026b
commit e78adfd9ab
3 files changed
+81

No files matched your search

@@ -577,7 +577,45 @@ impl PickerDelegate for OpenPathDelegate {
.ok();
})
}
fn select_child(
&mut self,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Option<String> {
let candidate = self.get_entry(self.selected_index)?;
if candidate.path.string.is_empty() || !candidate.is_dir {
return None;
}
let path_style = self.path_style;
match &self.directory_state {
DirectoryState::List { parent_path, .. }
| DirectoryState::Create { parent_path, .. } => Some(format!(
"{}{}{}",
parent_path,
candidate.path.string,
path_style.primary_separator()
)),
DirectoryState::None { .. } => None,
}
}
fn select_parent(
&mut self,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Option<String> {
let parent_path = match &self.directory_state {
DirectoryState::List { parent_path, .. }
| DirectoryState::Create { parent_path, .. } => parent_path,
DirectoryState::None { .. } => return None,
};
if parent_path == &self.prompt_root {
return None;
}
let trimmed = parent_path.trim_end_matches(['/', '\\']);
let (dir, _) = get_dir_and_suffix(trimmed.to_string(), self.path_style);
Some(dir)
}
fn confirm_completion(
&mut self,
query: String,
+41
View File
@@ -301,6 +301,27 @@ pub trait PickerDelegate: Sized + 'static {
) -> Option<String> {
None
}
/// Called when `SelectChild` fires (e.g. shift-right-arrow). Return `Some(query)`
/// to step into the currently selected item (e.g. a directory); the picker
/// will set the query and refresh matches.
fn select_child(
&mut self,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Option<String> {
None
}
/// Called when `SelectParent` fires (e.g. shift-left-arrow). Return `Some(query)`
/// to step back to the parent; the picker will set the query and refresh
/// matches.
fn select_parent(
&mut self,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Option<String> {
None
}
fn editor_position(&self) -> PickerEditorPosition {
PickerEditorPosition::default()
@@ -1002,6 +1023,26 @@ impl<D: PickerDelegate> Picker<D> {
cx.propagate()
}
}
fn select_child(&mut self, _: &menu::SelectChild, window: &mut Window, cx: &mut Context<Self>) {
if let Some(new_query) = self.delegate.select_child(window, cx) {
self.set_query(&new_query, window, cx);
} else {
cx.propagate()
}
}
fn select_parent(
&mut self,
_: &menu::SelectParent,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(new_query) = self.delegate.select_parent(window, cx) {
self.set_query(&new_query, window, cx);
} else {
cx.propagate()
}
}
fn set_preview_right(
&mut self,
+2
View File
@@ -192,6 +192,8 @@ impl<D: PickerDelegate> Picker<D> {
.on_action(cx.listener(Self::editor_move_up))
.on_action(cx.listener(Self::select_first))
.on_action(cx.listener(Self::select_last))
.on_action(cx.listener(Self::select_child))
.on_action(cx.listener(Self::select_parent))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::secondary_confirm))