diff --git a/assets/settings/default.json b/assets/settings/default.json index 221862ca98..8217f1675a 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -143,7 +143,11 @@ // Whether to show the git status in the project panel. "git_status": true, // Amount of indentation for nested items. - "indent_size": 20 + "indent_size": 20, + // Whether to reveal it in the project panel automatically, + // when a corresponding project entry becomes active. + // Gitignored entries are never auto revealed. + "auto_reveal_entries": true }, "collaboration_panel": { // Whether to show the collaboration panel button in the status bar. diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs index eb30931aac..fc8f7dfbbf 100644 --- a/crates/editor2/src/items.rs +++ b/crates/editor2/src/items.rs @@ -1189,7 +1189,7 @@ impl CursorPosition { impl Render for CursorPosition { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _: &mut ViewContext) -> Self::Element { div().when_some(self.position, |el, position| { let mut text = format!( "{}{FILE_ROW_COLUMN_DELIMITER}{}", diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8b059948c8..ed18ff700b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -301,6 +301,7 @@ pub enum Event { CollaboratorJoined(proto::PeerId), CollaboratorLeft(proto::PeerId), RefreshInlayHints, + RevealInProjectPanel(ProjectEntryId), } pub enum LanguageServerState { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c37d388041..75e64e4926 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -199,14 +199,14 @@ impl ProjectPanel { .detach(); cx.subscribe(&project, |this, project, event, cx| match event { project::Event::ActiveEntryChanged(Some(entry_id)) => { - if let Some(worktree_id) = project.read(cx).worktree_id_for_entry(*entry_id, cx) - { - this.expand_entry(worktree_id, *entry_id, cx); - this.update_visible_entries(Some((worktree_id, *entry_id)), cx); - this.autoscroll(cx); - cx.notify(); + if settings::get::(cx).auto_reveal_entries { + this.reveal_entry(project, *entry_id, true, cx); } } + project::Event::RevealInProjectPanel(entry_id) => { + this.reveal_entry(project, *entry_id, false, cx); + cx.emit(Event::ActivatePanel); + } project::Event::ActivateProjectPanel => { cx.emit(Event::ActivatePanel); } @@ -1531,6 +1531,32 @@ impl ProjectPanel { .with_cursor_style(CursorStyle::PointingHand) .into_any_named("project panel entry") } + + // TODO kb tests + fn reveal_entry( + &mut self, + project: ModelHandle, + entry_id: ProjectEntryId, + skip_ignored: bool, + cx: &mut ViewContext<'_, '_, ProjectPanel>, + ) { + if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) { + let worktree = worktree.read(cx); + if skip_ignored + && worktree + .entry_for_id(entry_id) + .map_or(true, |entry| entry.is_ignored) + { + return; + } + + let worktree_id = worktree.id(); + self.expand_entry(worktree_id, entry_id, cx); + self.update_visible_entries(Some((worktree_id, entry_id)), cx); + self.autoscroll(cx); + cx.notify(); + } + } } impl View for ProjectPanel { diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index 126433e5a3..836fe1d558 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -18,6 +18,7 @@ pub struct ProjectPanelSettings { pub folder_icons: bool, pub git_status: bool, pub indent_size: f32, + pub auto_reveal_entries: bool, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] @@ -28,6 +29,7 @@ pub struct ProjectPanelSettingsContent { pub folder_icons: Option, pub git_status: Option, pub indent_size: Option, + pub auto_reveal_entries: Option, } impl Setting for ProjectPanelSettings { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a50a109c83..c32dca936e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -96,6 +96,12 @@ pub struct CloseAllItems { pub save_intent: Option, } +#[derive(Clone, PartialEq, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RevealInProjectPanel { + pub entry_id: u64, +} + actions!( pane, [ @@ -116,7 +122,15 @@ actions!( ] ); -impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]); +impl_actions!( + pane, + [ + ActivateItem, + CloseActiveItem, + CloseAllItems, + RevealInProjectPanel, + ] +); const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; @@ -146,6 +160,13 @@ pub fn init(cx: &mut AppContext) { cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)); cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)); cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)); + cx.add_action(|pane: &mut Pane, action: &RevealInProjectPanel, cx| { + pane.project.update(cx, |_, cx| { + cx.emit(project::Event::RevealInProjectPanel( + ProjectEntryId::from_proto(action.entry_id), + )) + }) + }); } #[derive(Debug)] @@ -1232,80 +1253,87 @@ impl Pane { cx: &mut ViewContext, ) { let active_item_id = self.items[self.active_item_index].id(); + let single_entry_to_resolve = + self.items() + .find(|i| i.id() == target_item_id) + .and_then(|i| { + let item_entries = i.project_entry_ids(cx); + if item_entries.len() == 1 { + Some(item_entries[0]) + } else { + None + } + }); let is_active_item = target_item_id == active_item_id; let target_pane = cx.weak_handle(); // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab - self.tab_context_menu.update(cx, |menu, cx| { - menu.show( - position, - AnchorCorner::TopLeft, - if is_active_item { - vec![ - ContextMenuItem::action( - "Close Active Item", - CloseActiveItem { save_intent: None }, - ), - ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), - ContextMenuItem::action("Close Clean Items", CloseCleanItems), - ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), - ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), - ContextMenuItem::action( - "Close All Items", - CloseAllItems { save_intent: None }, - ), - ] - } else { - // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. - vec![ - ContextMenuItem::handler("Close Inactive Item", { - let pane = target_pane.clone(); - move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_item_by_id( - target_item_id, - SaveIntent::Close, - cx, - ) + let mut menu_items = if is_active_item { + vec![ + ContextMenuItem::action( + "Close Active Item", + CloseActiveItem { save_intent: None }, + ), + ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), + ContextMenuItem::action("Close Clean Items", CloseCleanItems), + ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), + ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), + ContextMenuItem::action("Close All Items", CloseAllItems { save_intent: None }), + ] + } else { + // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. + vec![ + ContextMenuItem::handler("Close Inactive Item", { + let pane = target_pane.clone(); + move |cx| { + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_item_by_id(target_item_id, SaveIntent::Close, cx) .detach_and_log_err(cx); - }) - } + }) } - }), - ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), - ContextMenuItem::action("Close Clean Items", CloseCleanItems), - ContextMenuItem::handler("Close Items To The Left", { - let pane = target_pane.clone(); - move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_items_to_the_left_by_id(target_item_id, cx) - .detach_and_log_err(cx); - }) - } + } + }), + ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), + ContextMenuItem::action("Close Clean Items", CloseCleanItems), + ContextMenuItem::handler("Close Items To The Left", { + let pane = target_pane.clone(); + move |cx| { + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left_by_id(target_item_id, cx) + .detach_and_log_err(cx); + }) } - }), - ContextMenuItem::handler("Close Items To The Right", { - let pane = target_pane.clone(); - move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_items_to_the_right_by_id(target_item_id, cx) - .detach_and_log_err(cx); - }) - } + } + }), + ContextMenuItem::handler("Close Items To The Right", { + let pane = target_pane.clone(); + move |cx| { + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right_by_id(target_item_id, cx) + .detach_and_log_err(cx); + }) } - }), - ContextMenuItem::action( - "Close All Items", - CloseAllItems { save_intent: None }, - ), - ] - }, - cx, - ); + } + }), + ContextMenuItem::action("Close All Items", CloseAllItems { save_intent: None }), + ] + }; + + if let Some(entry) = single_entry_to_resolve { + menu_items.push(ContextMenuItem::Separator); + menu_items.push(ContextMenuItem::action( + "Reveal In Project Panel", + RevealInProjectPanel { + entry_id: entry.to_proto(), + }, + )); + } + + menu.show(position, AnchorCorner::TopLeft, menu_items, cx); }); } diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 5c5801960e..e559ecb8f5 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1,4 +1,4 @@ -use crate::{ +se crate::{ item::{ClosePosition, Item, ItemHandle, ItemSettings, WeakItemHandle}, toolbar::Toolbar, workspace_settings::{AutosaveSetting, WorkspaceSettings}, diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 7c8dd0c45e..958ea4cf04 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -130,7 +130,6 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { let vim_mode_indicator = cx.build_view(|cx| vim::ModeIndicator::new(cx)); let feedback_button = cx .build_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)); - // let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); let cursor_position = cx.build_view(|_| editor::items::CursorPosition::new()); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx);