@@ -15,8 +15,8 @@ use file_icons::FileIcons;
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{
actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle,
- FocusableView, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render, Styled, Task,
- View, ViewContext, VisualContext, WeakView,
+ FocusableView, KeyContext, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render,
+ Styled, Task, View, ViewContext, VisualContext, WeakView,
};
use new_path_prompt::NewPathPrompt;
use open_path_prompt::OpenPathPrompt;
@@ -32,16 +32,30 @@ use std::{
},
};
use text::Point;
-use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
+use ui::{
+ prelude::*, ButtonLike, ContextMenu, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing,
+ PopoverMenu, PopoverMenuHandle, TintColor,
+};
use util::{paths::PathWithPosition, post_inc, ResultExt};
-use workspace::{item::PreviewTabsSettings, notifications::NotifyResultExt, ModalView, Workspace};
+use workspace::{
+ item::PreviewTabsSettings, notifications::NotifyResultExt, pane, ModalView, SplitDirection,
+ Workspace,
+};
-actions!(file_finder, [SelectPrev]);
+actions!(file_finder, [SelectPrev, OpenMenu]);
-impl ModalView for FileFinder {}
+impl ModalView for FileFinder {
+ fn on_before_dismiss(&mut self, cx: &mut ViewContext<Self>) -> workspace::DismissDecision {
+ let submenu_focused = self.picker.update(cx, |picker, cx| {
+ picker.delegate.popover_menu_handle.is_focused(cx)
+ });
+ workspace::DismissDecision::Dismiss(!submenu_focused)
+ }
+}
pub struct FileFinder {
picker: View<Picker<FileFinderDelegate>>,
+ picker_focus_handle: FocusHandle,
init_modifiers: Option<Modifiers>,
}
@@ -142,8 +156,14 @@ impl FileFinder {
}
fn new(delegate: FileFinderDelegate, cx: &mut ViewContext<Self>) -> Self {
+ let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
+ let picker_focus_handle = picker.focus_handle(cx);
+ picker.update(cx, |picker, _| {
+ picker.delegate.focus_handle = picker_focus_handle.clone();
+ });
Self {
- picker: cx.new_view(|cx| Picker::uniform_list(delegate, cx)),
+ picker,
+ picker_focus_handle,
init_modifiers: cx.modifiers().modified().then_some(cx.modifiers()),
}
}
@@ -168,23 +188,85 @@ impl FileFinder {
self.init_modifiers = Some(cx.modifiers());
cx.dispatch_action(Box::new(menu::SelectPrev));
}
+
+ fn handle_open_menu(&mut self, _: &OpenMenu, cx: &mut ViewContext<Self>) {
+ self.picker.update(cx, |picker, cx| {
+ let menu_handle = &picker.delegate.popover_menu_handle;
+ if !menu_handle.is_deployed() {
+ menu_handle.show(cx);
+ }
+ });
+ }
+
+ fn go_to_file_split_left(&mut self, _: &pane::SplitLeft, cx: &mut ViewContext<Self>) {
+ self.go_to_file_split_inner(SplitDirection::Left, cx)
+ }
+
+ fn go_to_file_split_right(&mut self, _: &pane::SplitRight, cx: &mut ViewContext<Self>) {
+ self.go_to_file_split_inner(SplitDirection::Right, cx)
+ }
+
+ fn go_to_file_split_up(&mut self, _: &pane::SplitUp, cx: &mut ViewContext<Self>) {
+ self.go_to_file_split_inner(SplitDirection::Up, cx)
+ }
+
+ fn go_to_file_split_down(&mut self, _: &pane::SplitDown, cx: &mut ViewContext<Self>) {
+ self.go_to_file_split_inner(SplitDirection::Down, cx)
+ }
+
+ fn go_to_file_split_inner(
+ &mut self,
+ split_direction: SplitDirection,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.picker.update(cx, |picker, cx| {
+ let delegate = &mut picker.delegate;
+ if let Some(workspace) = delegate.workspace.upgrade() {
+ if let Some(m) = delegate.matches.get(delegate.selected_index()) {
+ let path = match &m {
+ Match::History { path, .. } => {
+ let worktree_id = path.project.worktree_id;
+ ProjectPath {
+ worktree_id,
+ path: Arc::clone(&path.project.path),
+ }
+ }
+ Match::Search(m) => ProjectPath {
+ worktree_id: WorktreeId::from_usize(m.0.worktree_id),
+ path: m.0.path.clone(),
+ },
+ };
+ let open_task = workspace.update(cx, move |workspace, cx| {
+ workspace.split_path_preview(path, false, Some(split_direction), cx)
+ });
+ open_task.detach_and_log_err(cx);
+ }
+ }
+ })
+ }
}
impl EventEmitter<DismissEvent> for FileFinder {}
impl FocusableView for FileFinder {
- fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
- self.picker.focus_handle(cx)
+ fn focus_handle(&self, _: &AppContext) -> FocusHandle {
+ self.picker_focus_handle.clone()
}
}
impl Render for FileFinder {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+ let key_context = self.picker.read(cx).delegate.key_context(cx);
v_flex()
- .key_context("FileFinder")
+ .key_context(key_context)
.w(rems(34.))
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
.on_action(cx.listener(Self::handle_select_prev))
+ .on_action(cx.listener(Self::handle_open_menu))
+ .on_action(cx.listener(Self::go_to_file_split_left))
+ .on_action(cx.listener(Self::go_to_file_split_right))
+ .on_action(cx.listener(Self::go_to_file_split_up))
+ .on_action(cx.listener(Self::go_to_file_split_down))
.child(self.picker.clone())
}
}
@@ -205,6 +287,8 @@ pub struct FileFinderDelegate {
history_items: Vec<FoundPath>,
separate_history: bool,
first_update: bool,
+ popover_menu_handle: PopoverMenuHandle<ContextMenu>,
+ focus_handle: FocusHandle,
}
/// Use a custom ordering for file finder: the regular one
@@ -533,6 +617,8 @@ impl FileFinderDelegate {
history_items,
separate_history,
first_update: true,
+ popover_menu_handle: PopoverMenuHandle::default(),
+ focus_handle: cx.focus_handle(),
}
}
@@ -845,6 +931,15 @@ impl FileFinderDelegate {
0
}
+
+ fn key_context(&self, cx: &WindowContext) -> KeyContext {
+ let mut key_context = KeyContext::new_with_defaults();
+ key_context.add("FileFinder");
+ if self.popover_menu_handle.is_focused(cx) {
+ key_context.add("menu_open");
+ }
+ key_context
+ }
}
impl PickerDelegate for FileFinderDelegate {
@@ -958,7 +1053,7 @@ impl PickerDelegate for FileFinderDelegate {
let allow_preview =
PreviewTabsSettings::get_global(cx).enable_preview_from_file_finder;
if secondary {
- workspace.split_path_preview(project_path, allow_preview, cx)
+ workspace.split_path_preview(project_path, allow_preview, None, cx)
} else {
workspace.open_path_preview(
project_path,
@@ -1125,6 +1220,60 @@ impl PickerDelegate for FileFinderDelegate {
),
)
}
+
+ fn render_footer(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
+ let menu_open = self.popover_menu_handle.is_focused(cx);
+ Some(
+ h_flex()
+ .w_full()
+ .border_t_1()
+ .py_2()
+ .pr_2()
+ .border_color(cx.theme().colors().border)
+ .justify_end()
+ .child(
+ ButtonLike::new("open-selection")
+ .when_some(KeyBinding::for_action(&menu::Confirm, cx), |button, key| {
+ button.child(key)
+ })
+ .child(Label::new("Open"))
+ .on_click(|_, cx| cx.dispatch_action(menu::Confirm.boxed_clone())),
+ )
+ .child(
+ div().pl_2().child(
+ PopoverMenu::new("menu-popover")
+ .with_handle(self.popover_menu_handle.clone())
+ .attach(gpui::AnchorCorner::TopRight)
+ .anchor(gpui::AnchorCorner::BottomRight)
+ .trigger(
+ ButtonLike::new("menu-trigger")
+ .selected(menu_open)
+ .selected_style(ButtonStyle::Tinted(TintColor::Accent))
+ .when_some(
+ KeyBinding::for_action_in(
+ &OpenMenu,
+ &self.focus_handle,
+ cx,
+ ),
+ |button, key| button.child(key),
+ )
+ .child(Label::new("More actions")),
+ )
+ .menu({
+ move |cx| {
+ Some(ContextMenu::build(cx, move |menu, _| {
+ menu.action("Split left", pane::SplitLeft.boxed_clone())
+ .action("Split right", pane::SplitRight.boxed_clone())
+ .action("Split up", pane::SplitUp.boxed_clone())
+ .action("Split down", pane::SplitDown.boxed_clone())
+ }))
+ }
+ }),
+ ),
+ )
+ .into_any(),
+ )
+ }
}
#[cfg(test)]
@@ -2631,13 +2631,14 @@ impl Workspace {
path: impl Into<ProjectPath>,
cx: &mut ViewContext<Self>,
) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
- self.split_path_preview(path, false, cx)
+ self.split_path_preview(path, false, None, cx)
}
pub fn split_path_preview(
&mut self,
path: impl Into<ProjectPath>,
allow_preview: bool,
+ split_direction: Option<SplitDirection>,
cx: &mut ViewContext<Self>,
) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
@@ -2658,7 +2659,8 @@ impl Workspace {
let (project_entry_id, build_item) = task.await?;
this.update(&mut cx, move |this, cx| -> Option<_> {
let pane = pane.upgrade()?;
- let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
+ let new_pane =
+ this.split_pane(pane, split_direction.unwrap_or(SplitDirection::Right), cx);
new_pane.update(cx, |new_pane, cx| {
Some(new_pane.open_item(project_entry_id, true, allow_preview, cx, build_item))
})