feat(workspace): allow alternative actions to open files and symbols in split

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
Alex Viscreanu 2023-07-14 21:41:23 +02:00
parent 369ccc725c
commit c7669317ec
No known key found for this signature in database
25 changed files with 277 additions and 80 deletions

View File

@ -9,6 +9,7 @@
"context": "Editor", "context": "Editor",
"bindings": { "bindings": {
"cmd-b": "editor::GoToDefinition", "cmd-b": "editor::GoToDefinition",
"alt-cmd-b": "editor::GoToDefinitionSplit",
"cmd-<": "editor::ScrollCursorCenter", "cmd-<": "editor::ScrollCursorCenter",
"cmd-g": [ "cmd-g": [
"editor::SelectNext", "editor::SelectNext",

View File

@ -13,6 +13,7 @@
"cmd-up": "menu::SelectFirst", "cmd-up": "menu::SelectFirst",
"cmd-down": "menu::SelectLast", "cmd-down": "menu::SelectLast",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel", "escape": "menu::Cancel",
"ctrl-c": "menu::Cancel", "ctrl-c": "menu::Cancel",
"cmd-{": "pane::ActivatePrevItem", "cmd-{": "pane::ActivatePrevItem",
@ -298,7 +299,9 @@
"shift-f8": "editor::GoToPrevDiagnostic", "shift-f8": "editor::GoToPrevDiagnostic",
"f2": "editor::Rename", "f2": "editor::Rename",
"f12": "editor::GoToDefinition", "f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit",
"cmd-f12": "editor::GoToTypeDefinition", "cmd-f12": "editor::GoToTypeDefinition",
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences", "alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket", "ctrl-m": "editor::MoveToEnclosingBracket",
"alt-cmd-[": "editor::Fold", "alt-cmd-[": "editor::Fold",

View File

@ -46,8 +46,9 @@
"alt-f7": "editor::FindAllReferences", "alt-f7": "editor::FindAllReferences",
"cmd-alt-f7": "editor::FindAllReferences", "cmd-alt-f7": "editor::FindAllReferences",
"cmd-b": "editor::GoToDefinition", "cmd-b": "editor::GoToDefinition",
"cmd-alt-b": "editor::GoToDefinition", "cmd-alt-b": "editor::GoToDefinitionSplit",
"cmd-shift-b": "editor::GoToTypeDefinition", "cmd-shift-b": "editor::GoToTypeDefinition",
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
"alt-enter": "editor::ToggleCodeActions", "alt-enter": "editor::ToggleCodeActions",
"f2": "editor::GoToDiagnostic", "f2": "editor::GoToDiagnostic",
"cmd-f2": "editor::GoToPrevDiagnostic", "cmd-f2": "editor::GoToPrevDiagnostic",

View File

@ -20,6 +20,7 @@
"cmd-shift-a": "editor::SelectLargerSyntaxNode", "cmd-shift-a": "editor::SelectLargerSyntaxNode",
"shift-f12": "editor::FindAllReferences", "shift-f12": "editor::FindAllReferences",
"alt-cmd-down": "editor::GoToDefinition", "alt-cmd-down": "editor::GoToDefinition",
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",
"alt-shift-cmd-down": "editor::FindAllReferences", "alt-shift-cmd-down": "editor::FindAllReferences",
"ctrl-.": "editor::GoToHunk", "ctrl-.": "editor::GoToHunk",
"ctrl-,": "editor::GoToPrevHunk", "ctrl-,": "editor::GoToPrevHunk",

View File

@ -12,6 +12,7 @@
"cmd-l": "go_to_line::Toggle", "cmd-l": "go_to_line::Toggle",
"ctrl-shift-d": "editor::DuplicateLine", "ctrl-shift-d": "editor::DuplicateLine",
"cmd-b": "editor::GoToDefinition", "cmd-b": "editor::GoToDefinition",
"alt-cmd-b": "editor::GoToDefinition",
"cmd-j": "editor::ScrollCursorCenter", "cmd-j": "editor::ScrollCursorCenter",
"cmd-shift-l": "editor::SelectLine", "cmd-shift-l": "editor::SelectLine",
"cmd-shift-t": "outline::Toggle", "cmd-shift-t": "outline::Toggle",

View File

@ -7217,7 +7217,7 @@ async fn test_peers_following_each_other(
// Clients A and B follow each other in split panes // Clients A and B follow each other in split panes
workspace_a.update(cx_a, |workspace, cx| { workspace_a.update(cx_a, |workspace, cx| {
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
}); });
workspace_a workspace_a
.update(cx_a, |workspace, cx| { .update(cx_a, |workspace, cx| {
@ -7228,7 +7228,7 @@ async fn test_peers_following_each_other(
.await .await
.unwrap(); .unwrap();
workspace_b.update(cx_b, |workspace, cx| { workspace_b.update(cx_b, |workspace, cx| {
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
}); });
workspace_b workspace_b
.update(cx_b, |workspace, cx| { .update(cx_b, |workspace, cx| {
@ -7455,7 +7455,7 @@ async fn test_auto_unfollowing(
// When client B activates a different pane, it continues following client A in the original pane. // When client B activates a different pane, it continues following client A in the original pane.
workspace_b.update(cx_b, |workspace, cx| { workspace_b.update(cx_b, |workspace, cx| {
workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx) workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
}); });
assert_eq!( assert_eq!(
workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)), workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),

View File

@ -67,7 +67,7 @@ impl PickerDelegate for ContactFinderDelegate {
}) })
} }
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) { fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(user) = self.potential_contacts.get(self.selected_index) { if let Some(user) = self.potential_contacts.get(self.selected_index) {
let user_store = self.user_store.read(cx); let user_store = self.user_store.read(cx);
match user_store.contact_request_status(user) { match user_store.contact_request_status(user) {

View File

@ -160,7 +160,7 @@ impl PickerDelegate for CommandPaletteDelegate {
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {} fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) { fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if !self.matches.is_empty() { if !self.matches.is_empty() {
let window_id = cx.window_id(); let window_id = cx.window_id();
let focused_view_id = self.focused_view_id; let focused_view_id = self.focused_view_id;

View File

@ -271,7 +271,9 @@ actions!(
SelectLargerSyntaxNode, SelectLargerSyntaxNode,
SelectSmallerSyntaxNode, SelectSmallerSyntaxNode,
GoToDefinition, GoToDefinition,
GoToDefinitionSplit,
GoToTypeDefinition, GoToTypeDefinition,
GoToTypeDefinitionSplit,
MoveToEnclosingBracket, MoveToEnclosingBracket,
UndoSelection, UndoSelection,
RedoSelection, RedoSelection,
@ -407,7 +409,9 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::go_to_hunk); cx.add_action(Editor::go_to_hunk);
cx.add_action(Editor::go_to_prev_hunk); cx.add_action(Editor::go_to_prev_hunk);
cx.add_action(Editor::go_to_definition); cx.add_action(Editor::go_to_definition);
cx.add_action(Editor::go_to_definition_split);
cx.add_action(Editor::go_to_type_definition); cx.add_action(Editor::go_to_type_definition);
cx.add_action(Editor::go_to_type_definition_split);
cx.add_action(Editor::fold); cx.add_action(Editor::fold);
cx.add_action(Editor::fold_at); cx.add_action(Editor::fold_at);
cx.add_action(Editor::unfold_lines); cx.add_action(Editor::unfold_lines);
@ -6185,14 +6189,31 @@ impl Editor {
} }
pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext<Self>) { pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext<Self>) {
self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, cx); self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx);
} }
pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext<Self>) { pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext<Self>) {
self.go_to_definition_of_kind(GotoDefinitionKind::Type, cx); self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx);
} }
fn go_to_definition_of_kind(&mut self, kind: GotoDefinitionKind, cx: &mut ViewContext<Self>) { pub fn go_to_definition_split(&mut self, _: &GoToDefinitionSplit, cx: &mut ViewContext<Self>) {
self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx);
}
pub fn go_to_type_definition_split(
&mut self,
_: &GoToTypeDefinitionSplit,
cx: &mut ViewContext<Self>,
) {
self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx);
}
fn go_to_definition_of_kind(
&mut self,
kind: GotoDefinitionKind,
split: bool,
cx: &mut ViewContext<Self>,
) {
let Some(workspace) = self.workspace(cx) else { return }; let Some(workspace) = self.workspace(cx) else { return };
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let head = self.selections.newest::<usize>(cx).head(); let head = self.selections.newest::<usize>(cx).head();
@ -6211,7 +6232,7 @@ impl Editor {
cx.spawn_labeled("Fetching Definition...", |editor, mut cx| async move { cx.spawn_labeled("Fetching Definition...", |editor, mut cx| async move {
let definitions = definitions.await?; let definitions = definitions.await?;
editor.update(&mut cx, |editor, cx| { editor.update(&mut cx, |editor, cx| {
editor.navigate_to_definitions(definitions, cx); editor.navigate_to_definitions(definitions, split, cx);
})?; })?;
Ok::<(), anyhow::Error>(()) Ok::<(), anyhow::Error>(())
}) })
@ -6221,6 +6242,7 @@ impl Editor {
pub fn navigate_to_definitions( pub fn navigate_to_definitions(
&mut self, &mut self,
mut definitions: Vec<LocationLink>, mut definitions: Vec<LocationLink>,
split: bool,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
let Some(workspace) = self.workspace(cx) else { return }; let Some(workspace) = self.workspace(cx) else { return };
@ -6240,7 +6262,11 @@ impl Editor {
} else { } else {
cx.window_context().defer(move |cx| { cx.window_context().defer(move |cx| {
let target_editor: ViewHandle<Self> = workspace.update(cx, |workspace, cx| { let target_editor: ViewHandle<Self> = workspace.update(cx, |workspace, cx| {
workspace.open_project_item(definition.target.buffer.clone(), cx) if split {
workspace.split_project_item(definition.target.buffer.clone(), cx)
} else {
workspace.open_project_item(definition.target.buffer.clone(), cx)
}
}); });
target_editor.update(cx, |target_editor, cx| { target_editor.update(cx, |target_editor, cx| {
// When selecting a definition in a different buffer, disable the nav history // When selecting a definition in a different buffer, disable the nav history
@ -6276,7 +6302,9 @@ impl Editor {
.map(|definition| definition.target) .map(|definition| definition.target)
.collect(); .collect();
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx) Self::open_locations_in_multibuffer(
workspace, locations, replica_id, title, split, cx,
)
}); });
}); });
} }
@ -6321,7 +6349,7 @@ impl Editor {
}) })
.unwrap(); .unwrap();
Self::open_locations_in_multibuffer( Self::open_locations_in_multibuffer(
workspace, locations, replica_id, title, cx, workspace, locations, replica_id, title, false, cx,
); );
})?; })?;
@ -6336,6 +6364,7 @@ impl Editor {
mut locations: Vec<Location>, mut locations: Vec<Location>,
replica_id: ReplicaId, replica_id: ReplicaId,
title: String, title: String,
split: bool,
cx: &mut ViewContext<Workspace>, cx: &mut ViewContext<Workspace>,
) { ) {
// If there are multiple definitions, open them in a multibuffer // If there are multiple definitions, open them in a multibuffer
@ -6382,7 +6411,11 @@ impl Editor {
cx, cx,
); );
}); });
workspace.add_item(Box::new(editor), cx); if split {
workspace.split_item(Box::new(editor), cx);
} else {
workspace.add_item(Box::new(editor), cx);
}
} }
pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> { pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {

View File

@ -156,6 +156,7 @@ impl EditorElement {
event.position, event.position,
event.cmd, event.cmd,
event.shift, event.shift,
event.alt,
position_map.as_ref(), position_map.as_ref(),
text_bounds, text_bounds,
cx, cx,
@ -308,6 +309,7 @@ impl EditorElement {
position: Vector2F, position: Vector2F,
cmd: bool, cmd: bool,
shift: bool, shift: bool,
alt: bool,
position_map: &PositionMap, position_map: &PositionMap,
text_bounds: RectF, text_bounds: RectF,
cx: &mut EventContext<Editor>, cx: &mut EventContext<Editor>,
@ -324,9 +326,9 @@ impl EditorElement {
if point == target_point { if point == target_point {
if shift { if shift {
go_to_fetched_type_definition(editor, point, cx); go_to_fetched_type_definition(editor, point, alt, cx);
} else { } else {
go_to_fetched_definition(editor, point, cx); go_to_fetched_definition(editor, point, alt, cx);
} }
return true; return true;

View File

@ -246,23 +246,26 @@ pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
pub fn go_to_fetched_definition( pub fn go_to_fetched_definition(
editor: &mut Editor, editor: &mut Editor,
point: DisplayPoint, point: DisplayPoint,
split: bool,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, cx); go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, split, cx);
} }
pub fn go_to_fetched_type_definition( pub fn go_to_fetched_type_definition(
editor: &mut Editor, editor: &mut Editor,
point: DisplayPoint, point: DisplayPoint,
split: bool,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, cx); go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, split, cx);
} }
fn go_to_fetched_definition_of_kind( fn go_to_fetched_definition_of_kind(
kind: LinkDefinitionKind, kind: LinkDefinitionKind,
editor: &mut Editor, editor: &mut Editor,
point: DisplayPoint, point: DisplayPoint,
split: bool,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
let cached_definitions = editor.link_go_to_definition_state.definitions.clone(); let cached_definitions = editor.link_go_to_definition_state.definitions.clone();
@ -275,7 +278,7 @@ fn go_to_fetched_definition_of_kind(
cx.focus_self(); cx.focus_self();
} }
editor.navigate_to_definitions(cached_definitions, cx); editor.navigate_to_definitions(cached_definitions, split, cx);
} else { } else {
editor.select( editor.select(
SelectPhase::Begin { SelectPhase::Begin {
@ -403,7 +406,7 @@ mod tests {
}); });
cx.update_editor(|editor, cx| { cx.update_editor(|editor, cx| {
go_to_fetched_type_definition(editor, hover_point, cx); go_to_fetched_type_definition(editor, hover_point, false, cx);
}); });
requests.next().await; requests.next().await;
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
@ -614,7 +617,7 @@ mod tests {
// Cmd click with existing definition doesn't re-request and dismisses highlight // Cmd click with existing definition doesn't re-request and dismisses highlight
cx.update_editor(|editor, cx| { cx.update_editor(|editor, cx| {
go_to_fetched_definition(editor, hover_point, cx); go_to_fetched_definition(editor, hover_point, false, cx);
}); });
// Assert selection moved to to definition // Assert selection moved to to definition
cx.lsp cx.lsp
@ -655,7 +658,7 @@ mod tests {
]))) ])))
}); });
cx.update_editor(|editor, cx| { cx.update_editor(|editor, cx| {
go_to_fetched_definition(editor, hover_point, cx); go_to_fetched_definition(editor, hover_point, false, cx);
}); });
requests.next().await; requests.next().await;
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();

View File

@ -442,53 +442,71 @@ impl PickerDelegate for FileFinderDelegate {
} }
} }
fn confirm(&mut self, cx: &mut ViewContext<FileFinder>) { fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<FileFinder>) {
if let Some(m) = self.matches.get(self.selected_index()) { if let Some(m) = self.matches.get(self.selected_index()) {
if let Some(workspace) = self.workspace.upgrade(cx) { if let Some(workspace) = self.workspace.upgrade(cx) {
let open_task = workspace.update(cx, |workspace, cx| match m { let open_task = workspace.update(cx, move |workspace, cx| {
Match::History(history_match) => { let split_or_open = |workspace: &mut Workspace, project_path, cx| {
let worktree_id = history_match.project.worktree_id; if secondary {
if workspace workspace.split_path(project_path, cx)
.project()
.read(cx)
.worktree_for_id(worktree_id, cx)
.is_some()
{
workspace.open_path(
ProjectPath {
worktree_id,
path: Arc::clone(&history_match.project.path),
},
None,
true,
cx,
)
} else { } else {
match history_match.absolute.as_ref() { workspace.open_path(project_path, None, true, cx)
Some(abs_path) => { }
workspace.open_abs_path(abs_path.to_path_buf(), false, cx) };
} match m {
None => workspace.open_path( Match::History(history_match) => {
let worktree_id = history_match.project.worktree_id;
if workspace
.project()
.read(cx)
.worktree_for_id(worktree_id, cx)
.is_some()
{
split_or_open(
workspace,
ProjectPath { ProjectPath {
worktree_id, worktree_id,
path: Arc::clone(&history_match.project.path), path: Arc::clone(&history_match.project.path),
}, },
None,
true,
cx, cx,
), )
} else {
match history_match.absolute.as_ref() {
Some(abs_path) => {
if secondary {
workspace.split_abs_path(
abs_path.to_path_buf(),
false,
cx,
)
} else {
workspace.open_abs_path(
abs_path.to_path_buf(),
false,
cx,
)
}
}
None => split_or_open(
workspace,
ProjectPath {
worktree_id,
path: Arc::clone(&history_match.project.path),
},
cx,
),
}
} }
} }
Match::Search(m) => split_or_open(
workspace,
ProjectPath {
worktree_id: WorktreeId::from_usize(m.worktree_id),
path: m.path.clone(),
},
cx,
),
} }
Match::Search(m) => workspace.open_path(
ProjectPath {
worktree_id: WorktreeId::from_usize(m.worktree_id),
path: m.path.clone(),
},
None,
true,
cx,
),
}); });
let row = self let row = self

View File

@ -93,7 +93,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
self.matches.len() self.matches.len()
} }
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) { fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(mat) = self.matches.get(self.selected_index) { if let Some(mat) = self.matches.get(self.selected_index) {
let language_name = &self.candidates[mat.candidate_id].string; let language_name = &self.candidates[mat.candidate_id].string;
let language = self.language_registry.language_for_name(language_name); let language = self.language_registry.language_for_name(language_name);

View File

@ -3,6 +3,7 @@ gpui::actions!(
[ [
Cancel, Cancel,
Confirm, Confirm,
SecondaryConfirm,
SelectPrev, SelectPrev,
SelectNext, SelectNext,
SelectFirst, SelectFirst,

View File

@ -177,7 +177,7 @@ impl PickerDelegate for OutlineViewDelegate {
Task::ready(()) Task::ready(())
} }
fn confirm(&mut self, cx: &mut ViewContext<OutlineView>) { fn confirm(&mut self, _: bool, cx: &mut ViewContext<OutlineView>) {
self.prev_scroll_position.take(); self.prev_scroll_position.take();
self.active_editor.update(cx, |active_editor, cx| { self.active_editor.update(cx, |active_editor, cx| {
if let Some(rows) = active_editor.highlighted_rows() { if let Some(rows) = active_editor.highlighted_rows() {

View File

@ -7,7 +7,7 @@ use gpui::{
AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext, AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext,
ViewHandle, ViewHandle,
}; };
use menu::{Cancel, Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; use menu::{Cancel, Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{cmp, sync::Arc}; use std::{cmp, sync::Arc};
use util::ResultExt; use util::ResultExt;
@ -34,7 +34,7 @@ pub trait PickerDelegate: Sized + 'static {
fn selected_index(&self) -> usize; fn selected_index(&self) -> usize;
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>); fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>; fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>); fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>); fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
fn render_match( fn render_match(
&self, &self,
@ -118,8 +118,8 @@ impl<D: PickerDelegate> View for Picker<D> {
// Capture mouse events // Capture mouse events
.on_down(MouseButton::Left, |_, _, _| {}) .on_down(MouseButton::Left, |_, _, _| {})
.on_up(MouseButton::Left, |_, _, _| {}) .on_up(MouseButton::Left, |_, _, _| {})
.on_click(MouseButton::Left, move |_, picker, cx| { .on_click(MouseButton::Left, move |click, picker, cx| {
picker.select_index(ix, cx); picker.select_index(ix, click.cmd, cx);
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.into_any() .into_any()
@ -175,6 +175,7 @@ impl<D: PickerDelegate> Picker<D> {
cx.add_action(Self::select_next); cx.add_action(Self::select_next);
cx.add_action(Self::select_prev); cx.add_action(Self::select_prev);
cx.add_action(Self::confirm); cx.add_action(Self::confirm);
cx.add_action(Self::secondary_confirm);
cx.add_action(Self::cancel); cx.add_action(Self::cancel);
} }
@ -288,11 +289,11 @@ impl<D: PickerDelegate> Picker<D> {
cx.notify(); cx.notify();
} }
pub fn select_index(&mut self, index: usize, cx: &mut ViewContext<Self>) { pub fn select_index(&mut self, index: usize, cmd: bool, cx: &mut ViewContext<Self>) {
if self.delegate.match_count() > 0 { if self.delegate.match_count() > 0 {
self.confirmed = true; self.confirmed = true;
self.delegate.set_selected_index(index, cx); self.delegate.set_selected_index(index, cx);
self.delegate.confirm(cx); self.delegate.confirm(cmd, cx);
} }
} }
@ -330,7 +331,12 @@ impl<D: PickerDelegate> Picker<D> {
pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) { pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
self.confirmed = true; self.confirmed = true;
self.delegate.confirm(cx); self.delegate.confirm(false, cx);
}
pub fn secondary_confirm(&mut self, _: &SecondaryConfirm, cx: &mut ViewContext<Self>) {
self.confirmed = true;
self.delegate.confirm(true, cx);
} }
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {

View File

@ -159,6 +159,9 @@ pub enum Event {
entry_id: ProjectEntryId, entry_id: ProjectEntryId,
focus_opened_item: bool, focus_opened_item: bool,
}, },
SplitEntry {
entry_id: ProjectEntryId,
},
DockPositionChanged, DockPositionChanged,
Focus, Focus,
} }
@ -290,6 +293,21 @@ impl ProjectPanel {
} }
} }
} }
&Event::SplitEntry { entry_id } => {
if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) {
workspace
.split_path(
ProjectPath {
worktree_id: worktree.read(cx).id(),
path: entry.path.clone(),
},
cx,
)
.detach_and_log_err(cx);
}
}
}
_ => {} _ => {}
} }
}) })
@ -620,6 +638,10 @@ impl ProjectPanel {
}); });
} }
fn split_entry(&mut self, entry_id: ProjectEntryId, cx: &mut ViewContext<Self>) {
cx.emit(Event::SplitEntry { entry_id });
}
fn new_file(&mut self, _: &NewFile, cx: &mut ViewContext<Self>) { fn new_file(&mut self, _: &NewFile, cx: &mut ViewContext<Self>) {
self.add_entry(false, cx) self.add_entry(false, cx)
} }
@ -1333,7 +1355,11 @@ impl ProjectPanel {
if kind.is_dir() { if kind.is_dir() {
this.toggle_expanded(entry_id, cx); this.toggle_expanded(entry_id, cx);
} else { } else {
this.open_entry(entry_id, event.click_count > 1, cx); if event.cmd && event.click_count > 1 {
this.split_entry(entry_id, cx);
} else if !event.cmd {
this.open_entry(entry_id, event.click_count > 1, cx);
}
} }
} }
}) })

View File

@ -104,7 +104,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
"Search project symbols...".into() "Search project symbols...".into()
} }
fn confirm(&mut self, cx: &mut ViewContext<ProjectSymbols>) { fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<ProjectSymbols>) {
if let Some(symbol) = self if let Some(symbol) = self
.matches .matches
.get(self.selected_match_index) .get(self.selected_match_index)
@ -122,7 +122,12 @@ impl PickerDelegate for ProjectSymbolsDelegate {
.read(cx) .read(cx)
.clip_point_utf16(symbol.range.start, Bias::Left); .clip_point_utf16(symbol.range.start, Bias::Left);
let editor = workspace.open_project_item::<Editor>(buffer, cx); let editor = if secondary {
workspace.split_project_item::<Editor>(buffer, cx)
} else {
workspace.open_project_item::<Editor>(buffer, cx)
};
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::center()), cx, |s| { editor.change_selections(Some(Autoscroll::center()), cx, |s| {
s.select_ranges([position..position]) s.select_ranges([position..position])

View File

@ -161,7 +161,7 @@ impl PickerDelegate for RecentProjectsDelegate {
Task::ready(()) Task::ready(())
} }
fn confirm(&mut self, cx: &mut ViewContext<RecentProjects>) { fn confirm(&mut self, _: bool, cx: &mut ViewContext<RecentProjects>) {
if let Some((selected_match, workspace)) = self if let Some((selected_match, workspace)) = self
.matches .matches
.get(self.selected_index()) .get(self.selected_index())

View File

@ -120,7 +120,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
self.matches.len() self.matches.len()
} }
fn confirm(&mut self, cx: &mut ViewContext<ThemeSelector>) { fn confirm(&mut self, _: bool, cx: &mut ViewContext<ThemeSelector>) {
self.selection_completed = true; self.selection_completed = true;
let theme_name = theme::current(cx).meta.name.clone(); let theme_name = theme::current(cx).meta.name.clone();

View File

@ -182,7 +182,7 @@ impl PickerDelegate for BranchListDelegate {
}) })
} }
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) { fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
let current_pick = self.selected_index(); let current_pick = self.selected_index();
let Some(current_pick) = self.matches.get(current_pick).map(|pick| pick.string.clone()) else { let Some(current_pick) = self.matches.get(current_pick).map(|pick| pick.string.clone()) else {
return; return;

View File

@ -51,7 +51,7 @@ impl PickerDelegate for SemanticSearchDelegate {
"Search repository in natural language...".into() "Search repository in natural language...".into()
} }
fn confirm(&mut self, cx: &mut ViewContext<SemanticSearch>) { fn confirm(&mut self, _: bool, cx: &mut ViewContext<SemanticSearch>) {
if let Some(search_result) = self.matches.get(self.selected_match_index) { if let Some(search_result) = self.matches.get(self.selected_match_index) {
// Open Buffer // Open Buffer
let search_result = search_result.clone(); let search_result = search_result.clone();

View File

@ -120,7 +120,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
}) })
} }
fn confirm(&mut self, cx: &mut ViewContext<BaseKeymapSelector>) { fn confirm(&mut self, _: bool, cx: &mut ViewContext<BaseKeymapSelector>) {
if let Some(selection) = self.matches.get(self.selected_index) { if let Some(selection) = self.matches.get(self.selected_index) {
let base_keymap = BaseKeymap::from_names(&selection.string); let base_keymap = BaseKeymap::from_names(&selection.string);
update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting| { update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting| {

View File

@ -1821,6 +1821,13 @@ impl Workspace {
.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
} }
pub fn split_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
let new_pane = self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
new_pane.update(cx, move |new_pane, cx| {
new_pane.add_item(item, true, true, None, cx)
})
}
pub fn open_abs_path( pub fn open_abs_path(
&mut self, &mut self,
abs_path: PathBuf, abs_path: PathBuf,
@ -1851,6 +1858,21 @@ impl Workspace {
}) })
} }
pub fn split_abs_path(
&mut self,
abs_path: PathBuf,
visible: bool,
cx: &mut ViewContext<Self>,
) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
let project_path_task =
Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
cx.spawn(|this, mut cx| async move {
let (_, path) = project_path_task.await?;
this.update(&mut cx, |this, cx| this.split_path(path, cx))?
.await
})
}
pub fn open_path( pub fn open_path(
&mut self, &mut self,
path: impl Into<ProjectPath>, path: impl Into<ProjectPath>,
@ -1876,6 +1898,38 @@ impl Workspace {
}) })
} }
pub fn split_path(
&mut self,
path: impl Into<ProjectPath>,
cx: &mut ViewContext<Self>,
) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
self.panes
.first()
.expect("There must be an active pane")
.downgrade()
});
if let Member::Pane(center_pane) = &self.center.root {
if center_pane.read(cx).items_len() == 0 {
return self.open_path(path, Some(pane), true, cx);
}
}
let task = self.load_path(path.into(), cx);
cx.spawn(|this, mut cx| async move {
let (project_entry_id, build_item) = task.await?;
this.update(&mut cx, move |this, cx| -> Option<_> {
let pane = pane.upgrade(cx)?;
let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
new_pane.update(cx, |new_pane, cx| {
Some(new_pane.open_item(project_entry_id, true, cx, build_item))
})
})
.map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
})
}
pub(crate) fn load_path( pub(crate) fn load_path(
&mut self, &mut self,
path: ProjectPath, path: ProjectPath,
@ -1926,6 +1980,30 @@ impl Workspace {
item item
} }
pub fn split_project_item<T>(
&mut self,
project_item: ModelHandle<T::Item>,
cx: &mut ViewContext<Self>,
) -> ViewHandle<T>
where
T: ProjectItem,
{
use project::Item as _;
let entry_id = project_item.read(cx).entry_id(cx);
if let Some(item) = entry_id
.and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
.and_then(|item| item.downcast())
{
self.activate_item(&item, cx);
return item;
}
let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
self.split_item(Box::new(item.clone()), cx);
item
}
pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) { pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
self.active_pane.update(cx, |pane, cx| { self.active_pane.update(cx, |pane, cx| {
@ -1953,7 +2031,7 @@ impl Workspace {
if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) { if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
cx.focus(&pane); cx.focus(&pane);
} else { } else {
self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx); self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
} }
} }
@ -2006,7 +2084,7 @@ impl Workspace {
match event { match event {
pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
pane::Event::Split(direction) => { pane::Event::Split(direction) => {
self.split_pane(pane, *direction, cx); self.split_and_clone(pane, *direction, cx);
} }
pane::Event::Remove => self.remove_pane(pane, cx), pane::Event::Remove => self.remove_pane(pane, cx),
pane::Event::ActivateItem { local } => { pane::Event::ActivateItem { local } => {
@ -2057,6 +2135,20 @@ impl Workspace {
} }
pub fn split_pane( pub fn split_pane(
&mut self,
pane_to_split: ViewHandle<Pane>,
split_direction: SplitDirection,
cx: &mut ViewContext<Self>,
) -> ViewHandle<Pane> {
let new_pane = self.add_pane(cx);
self.center
.split(&pane_to_split, &new_pane, split_direction)
.unwrap();
cx.notify();
new_pane
}
pub fn split_and_clone(
&mut self, &mut self,
pane: ViewHandle<Pane>, pane: ViewHandle<Pane>,
direction: SplitDirection, direction: SplitDirection,
@ -4246,7 +4338,7 @@ mod tests {
}); });
workspace workspace
.split_pane(left_pane.clone(), SplitDirection::Right, cx) .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
.unwrap(); .unwrap();
left_pane left_pane

View File

@ -1021,7 +1021,7 @@ mod tests {
// Split the pane with the first entry, then open the second entry again. // Split the pane with the first entry, then open the second entry again.
workspace workspace
.update(cx, |w, cx| { .update(cx, |w, cx| {
w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx); w.split_and_clone(w.active_pane().clone(), SplitDirection::Right, cx);
w.open_path(file2.clone(), None, true, cx) w.open_path(file2.clone(), None, true, cx)
}) })
.await .await
@ -1344,7 +1344,11 @@ mod tests {
cx.dispatch_action(window_id, NewFile); cx.dispatch_action(window_id, NewFile);
workspace workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); workspace.split_and_clone(
workspace.active_pane().clone(),
SplitDirection::Right,
cx,
);
workspace.open_path((worktree.read(cx).id(), "the-new-name.rs"), None, true, cx) workspace.open_path((worktree.read(cx).id(), "the-new-name.rs"), None, true, cx)
}) })
.await .await