Add "Open in Terminal" context menu entries for project panel, editor and tab context menus (#10741)
Closes https://github.com/zed-industries/zed/issues/4566 Pane tabs (does not exist for multibuffer tabs): <img width="439" alt="Screenshot 2024-04-18 at 23 01 08" src="https://github.com/zed-industries/zed/assets/2690773/3af79ed8-07ea-4cf2-bcf9-735b1b3be8c4"> Editor context menu: <img width="404" alt="Screenshot 2024-04-18 at 23 01 14" src="https://github.com/zed-industries/zed/assets/2690773/38ea7afc-df2b-45ef-8331-eb6a4588af9f"> Project panel context menu (was not shown for file entries before this): <img width="408" alt="Screenshot 2024-04-18 at 23 01 18" src="https://github.com/zed-industries/zed/assets/2690773/e336fce1-7da0-4671-b8d2-8d3409c23eb6"> Release Notes: - (breaking change) Moved `project_panel::OpenInTerminal` into `workspace::OpenInTerminal` action and add it in editors, tab context menus and proper panel file entries ([4566](https://github.com/zed-industries/zed/issues/4566))
This commit is contained in:
parent
250b71fb44
commit
870a61dd4d
7 changed files with 101 additions and 45 deletions
|
@ -131,10 +131,10 @@ use ui::{
|
|||
use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
|
||||
use workspace::item::ItemHandle;
|
||||
use workspace::notifications::NotificationId;
|
||||
use workspace::Toast;
|
||||
use workspace::{
|
||||
searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
|
||||
};
|
||||
use workspace::{OpenInTerminal, OpenTerminal, Toast};
|
||||
|
||||
use crate::hover_links::find_url;
|
||||
|
||||
|
@ -4943,6 +4943,25 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
|
||||
if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
|
||||
let project_path = buffer.read(cx).project_path(cx)?;
|
||||
let project = self.project.as_ref()?.read(cx);
|
||||
let entry = project.entry_for_path(&project_path, cx)?;
|
||||
let abs_path = project.absolute_path(&project_path, cx)?;
|
||||
let parent = if entry.is_symlink {
|
||||
abs_path.canonicalize().ok()?
|
||||
} else {
|
||||
abs_path
|
||||
}
|
||||
.parent()?
|
||||
.to_path_buf();
|
||||
Some(parent)
|
||||
}) {
|
||||
cx.dispatch_action(OpenTerminal { working_directory }.boxed_clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn gather_revert_changes(
|
||||
&mut self,
|
||||
selections: &[Selection<Anchor>],
|
||||
|
|
|
@ -363,6 +363,7 @@ impl EditorElement {
|
|||
register_action(view, cx, Editor::unique_lines_case_sensitive);
|
||||
register_action(view, cx, Editor::accept_partial_inline_completion);
|
||||
register_action(view, cx, Editor::revert_selected_hunks);
|
||||
register_action(view, cx, Editor::open_active_item_in_terminal)
|
||||
}
|
||||
|
||||
fn register_key_listeners(&self, cx: &mut ElementContext, layout: &EditorLayout) {
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
GoToTypeDefinition, Rename, RevealInFinder, SelectMode, ToggleCodeActions,
|
||||
};
|
||||
use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
|
||||
use workspace::OpenInTerminal;
|
||||
|
||||
pub struct MouseContextMenu {
|
||||
pub(crate) position: Point<Pixels>,
|
||||
|
@ -83,6 +84,7 @@ pub fn deploy_context_menu(
|
|||
)
|
||||
.separator()
|
||||
.action("Reveal in Finder", Box::new(RevealInFinder))
|
||||
.action("Open in Terminal", Box::new(OpenInTerminal))
|
||||
})
|
||||
};
|
||||
let mouse_context_menu = MouseContextMenu::new(position, context_menu, cx);
|
||||
|
|
|
@ -37,7 +37,7 @@ use util::{maybe, NumericPrefixWithSuffix, ResultExt, TryFutureExt};
|
|||
use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
notifications::DetachAndPromptErr,
|
||||
Workspace,
|
||||
OpenInTerminal, Workspace,
|
||||
};
|
||||
|
||||
const PROJECT_PANEL_KEY: &str = "ProjectPanel";
|
||||
|
@ -127,7 +127,6 @@ actions!(
|
|||
CopyPath,
|
||||
CopyRelativePath,
|
||||
RevealInFinder,
|
||||
OpenInTerminal,
|
||||
Cut,
|
||||
Paste,
|
||||
Rename,
|
||||
|
@ -441,9 +440,7 @@ impl ProjectPanel {
|
|||
.action("New Folder", Box::new(NewDirectory))
|
||||
.separator()
|
||||
.action("Reveal in Finder", Box::new(RevealInFinder))
|
||||
.when(is_dir, |menu| {
|
||||
menu.action("Open in Terminal…", Box::new(OpenInTerminal))
|
||||
})
|
||||
.action("Open in Terminal", Box::new(OpenInTerminal))
|
||||
.when(is_dir, |menu| {
|
||||
menu.separator()
|
||||
.action("Find in Folder…", Box::new(NewSearchInDirectory))
|
||||
|
@ -1131,13 +1128,20 @@ impl ProjectPanel {
|
|||
|
||||
fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
|
||||
if let Some((worktree, entry)) = self.selected_entry(cx) {
|
||||
let path = worktree.abs_path().join(&entry.path);
|
||||
cx.dispatch_action(
|
||||
workspace::OpenTerminal {
|
||||
working_directory: path,
|
||||
let abs_path = worktree.abs_path().join(&entry.path);
|
||||
let working_directory = if entry.is_dir() {
|
||||
Some(abs_path)
|
||||
} else {
|
||||
if entry.is_symlink {
|
||||
abs_path.canonicalize().ok()
|
||||
} else {
|
||||
Some(abs_path)
|
||||
}
|
||||
.and_then(|path| Some(path.parent()?.to_path_buf()))
|
||||
};
|
||||
if let Some(working_directory) = working_directory {
|
||||
cx.dispatch_action(workspace::OpenTerminal { working_directory }.boxed_clone())
|
||||
}
|
||||
.boxed_clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -98,7 +98,6 @@ impl TerminalPanel {
|
|||
.on_click(cx.listener(|pane, _, cx| {
|
||||
pane.toggle_zoom(&workspace::ToggleZoom, cx);
|
||||
}))
|
||||
// TODO kb
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
if zoomed { "Zoom Out" } else { "Zoom In" },
|
||||
|
@ -292,13 +291,13 @@ impl TerminalPanel {
|
|||
action: &workspace::OpenTerminal,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let Some(this) = workspace.focus_panel::<Self>(cx) else {
|
||||
let Some(terminal_panel) = workspace.panel::<Self>(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.add_terminal(Some(action.working_directory.clone()), None, cx)
|
||||
})
|
||||
terminal_panel.update(cx, |panel, cx| {
|
||||
panel.add_terminal(Some(action.working_directory.clone()), None, cx)
|
||||
});
|
||||
workspace.focus_panel::<Self>(cx);
|
||||
}
|
||||
|
||||
fn spawn_task(&mut self, spawn_in_terminal: &SpawnInTerminal, cx: &mut ViewContext<Self>) {
|
||||
|
@ -433,20 +432,11 @@ impl TerminalPanel {
|
|||
_: &workspace::NewTerminal,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let has_no_terminals = workspace
|
||||
.panel::<Self>(cx)
|
||||
.map(|terminal_panel| terminal_panel.update(cx, |panel, cx| panel.has_no_terminals(cx)))
|
||||
.unwrap_or(true);
|
||||
let Some(this) = workspace.focus_panel::<Self>(cx) else {
|
||||
let Some(terminal_panel) = workspace.panel::<Self>(cx) else {
|
||||
return;
|
||||
};
|
||||
if has_no_terminals {
|
||||
// `set_active` on focus, will already add a new terminal
|
||||
// into an empty terminal pane, no need to add another one
|
||||
return;
|
||||
}
|
||||
|
||||
this.update(cx, |this, cx| this.add_terminal(None, None, cx))
|
||||
terminal_panel.update(cx, |this, cx| this.add_terminal(None, None, cx));
|
||||
workspace.focus_panel::<Self>(cx);
|
||||
}
|
||||
|
||||
fn terminals_for_task(
|
||||
|
|
|
@ -5,7 +5,8 @@ use crate::{
|
|||
},
|
||||
toolbar::Toolbar,
|
||||
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
|
||||
NewCenterTerminal, NewFile, NewSearch, OpenVisible, SplitDirection, ToggleZoom, Workspace,
|
||||
NewCenterTerminal, NewFile, NewSearch, OpenInTerminal, OpenTerminal, OpenVisible,
|
||||
SplitDirection, ToggleZoom, Workspace,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
|
@ -1597,8 +1598,32 @@ impl Pane {
|
|||
);
|
||||
|
||||
if let Some(entry) = single_entry_to_resolve {
|
||||
let parent_abs_path = pane
|
||||
.update(cx, |pane, cx| {
|
||||
pane.workspace.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().read(cx);
|
||||
project.worktree_for_entry(entry, cx).and_then(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
let entry = worktree.entry_for_id(entry)?;
|
||||
let abs_path = worktree.absolutize(&entry.path).ok()?;
|
||||
let parent = if entry.is_symlink {
|
||||
abs_path.canonicalize().ok()?
|
||||
} else {
|
||||
abs_path
|
||||
}
|
||||
.parent()?
|
||||
.to_path_buf();
|
||||
Some(parent)
|
||||
})
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let entry_id = entry.to_proto();
|
||||
menu = menu.separator().entry(
|
||||
menu = menu
|
||||
.separator()
|
||||
.entry(
|
||||
"Reveal In Project Panel",
|
||||
Some(Box::new(RevealInProjectPanel {
|
||||
entry_id: Some(entry_id),
|
||||
|
@ -1610,7 +1635,21 @@ impl Pane {
|
|||
))
|
||||
});
|
||||
}),
|
||||
)
|
||||
.when_some(parent_abs_path, |menu, abs_path| {
|
||||
menu.entry(
|
||||
"Open in Terminal",
|
||||
Some(Box::new(OpenInTerminal)),
|
||||
cx.handler_for(&pane, move |_, cx| {
|
||||
cx.dispatch_action(
|
||||
OpenTerminal {
|
||||
working_directory: abs_path.clone(),
|
||||
}
|
||||
.boxed_clone(),
|
||||
);
|
||||
}),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -112,6 +112,7 @@ actions!(
|
|||
workspace,
|
||||
[
|
||||
Open,
|
||||
OpenInTerminal,
|
||||
NewFile,
|
||||
NewWindow,
|
||||
CloseWindow,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue