Rework the way project panel auto reveals entries

* gitignored entries are never auto revealed
* `project_panel::auto_reveal_entries = true` settings entry was added,
setting it to `false` will disable the auto reveal
* `pane::RevealInProjectPanel` action was added that activates the project panel and reveals the entry it got triggered on (including the gitignored ones)
This commit is contained in:
Kirill Bulatov 2023-12-11 23:37:45 +02:00
parent a9f817fc14
commit 27d6432c84
8 changed files with 136 additions and 76 deletions

View file

@ -143,7 +143,11 @@
// Whether to show the git status in the project panel. // Whether to show the git status in the project panel.
"git_status": true, "git_status": true,
// Amount of indentation for nested items. // 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": { "collaboration_panel": {
// Whether to show the collaboration panel button in the status bar. // Whether to show the collaboration panel button in the status bar.

View file

@ -1189,7 +1189,7 @@ impl CursorPosition {
impl Render for CursorPosition { impl Render for CursorPosition {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
div().when_some(self.position, |el, position| { div().when_some(self.position, |el, position| {
let mut text = format!( let mut text = format!(
"{}{FILE_ROW_COLUMN_DELIMITER}{}", "{}{FILE_ROW_COLUMN_DELIMITER}{}",

View file

@ -301,6 +301,7 @@ pub enum Event {
CollaboratorJoined(proto::PeerId), CollaboratorJoined(proto::PeerId),
CollaboratorLeft(proto::PeerId), CollaboratorLeft(proto::PeerId),
RefreshInlayHints, RefreshInlayHints,
RevealInProjectPanel(ProjectEntryId),
} }
pub enum LanguageServerState { pub enum LanguageServerState {

View file

@ -199,14 +199,14 @@ impl ProjectPanel {
.detach(); .detach();
cx.subscribe(&project, |this, project, event, cx| match event { cx.subscribe(&project, |this, project, event, cx| match event {
project::Event::ActiveEntryChanged(Some(entry_id)) => { project::Event::ActiveEntryChanged(Some(entry_id)) => {
if let Some(worktree_id) = project.read(cx).worktree_id_for_entry(*entry_id, cx) if settings::get::<ProjectPanelSettings>(cx).auto_reveal_entries {
{ this.reveal_entry(project, *entry_id, true, cx);
this.expand_entry(worktree_id, *entry_id, cx);
this.update_visible_entries(Some((worktree_id, *entry_id)), cx);
this.autoscroll(cx);
cx.notify();
} }
} }
project::Event::RevealInProjectPanel(entry_id) => {
this.reveal_entry(project, *entry_id, false, cx);
cx.emit(Event::ActivatePanel);
}
project::Event::ActivateProjectPanel => { project::Event::ActivateProjectPanel => {
cx.emit(Event::ActivatePanel); cx.emit(Event::ActivatePanel);
} }
@ -1531,6 +1531,32 @@ impl ProjectPanel {
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.into_any_named("project panel entry") .into_any_named("project panel entry")
} }
// TODO kb tests
fn reveal_entry(
&mut self,
project: ModelHandle<Project>,
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 { impl View for ProjectPanel {

View file

@ -18,6 +18,7 @@ pub struct ProjectPanelSettings {
pub folder_icons: bool, pub folder_icons: bool,
pub git_status: bool, pub git_status: bool,
pub indent_size: f32, pub indent_size: f32,
pub auto_reveal_entries: bool,
} }
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
@ -28,6 +29,7 @@ pub struct ProjectPanelSettingsContent {
pub folder_icons: Option<bool>, pub folder_icons: Option<bool>,
pub git_status: Option<bool>, pub git_status: Option<bool>,
pub indent_size: Option<f32>, pub indent_size: Option<f32>,
pub auto_reveal_entries: Option<bool>,
} }
impl Setting for ProjectPanelSettings { impl Setting for ProjectPanelSettings {

View file

@ -96,6 +96,12 @@ pub struct CloseAllItems {
pub save_intent: Option<SaveIntent>, pub save_intent: Option<SaveIntent>,
} }
#[derive(Clone, PartialEq, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RevealInProjectPanel {
pub entry_id: u64,
}
actions!( actions!(
pane, 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; 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, _: &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, _: &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, _: &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)] #[derive(Debug)]
@ -1232,80 +1253,87 @@ impl Pane {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let active_item_id = self.items[self.active_item_index].id(); 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 is_active_item = target_item_id == active_item_id;
let target_pane = cx.weak_handle(); 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 // 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| { self.tab_context_menu.update(cx, |menu, cx| {
menu.show( let mut menu_items = if is_active_item {
position, vec![
AnchorCorner::TopLeft, ContextMenuItem::action(
if is_active_item { "Close Active Item",
vec![ CloseActiveItem { save_intent: None },
ContextMenuItem::action( ),
"Close Active Item", ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
CloseActiveItem { save_intent: None }, ContextMenuItem::action("Close Clean Items", CloseCleanItems),
), ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
ContextMenuItem::action("Close Clean Items", CloseCleanItems), ContextMenuItem::action("Close All Items", CloseAllItems { save_intent: None }),
ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), ]
ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), } else {
ContextMenuItem::action( // 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.
"Close All Items", vec![
CloseAllItems { save_intent: None }, ContextMenuItem::handler("Close Inactive Item", {
), let pane = target_pane.clone();
] move |cx| {
} else { if let Some(pane) = pane.upgrade(cx) {
// 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. pane.update(cx, |pane, cx| {
vec![ pane.close_item_by_id(target_item_id, SaveIntent::Close, cx)
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); .detach_and_log_err(cx);
}) })
}
} }
}), }
ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), }),
ContextMenuItem::action("Close Clean Items", CloseCleanItems), ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
ContextMenuItem::handler("Close Items To The Left", { ContextMenuItem::action("Close Clean Items", CloseCleanItems),
let pane = target_pane.clone(); ContextMenuItem::handler("Close Items To The Left", {
move |cx| { let pane = target_pane.clone();
if let Some(pane) = pane.upgrade(cx) { move |cx| {
pane.update(cx, |pane, cx| { if let Some(pane) = pane.upgrade(cx) {
pane.close_items_to_the_left_by_id(target_item_id, cx) pane.update(cx, |pane, cx| {
.detach_and_log_err(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(); ContextMenuItem::handler("Close Items To The Right", {
move |cx| { let pane = target_pane.clone();
if let Some(pane) = pane.upgrade(cx) { move |cx| {
pane.update(cx, |pane, cx| { if let Some(pane) = pane.upgrade(cx) {
pane.close_items_to_the_right_by_id(target_item_id, cx) pane.update(cx, |pane, cx| {
.detach_and_log_err(cx); pane.close_items_to_the_right_by_id(target_item_id, cx)
}) .detach_and_log_err(cx);
} })
} }
}), }
ContextMenuItem::action( }),
"Close All Items", ContextMenuItem::action("Close All Items", CloseAllItems { save_intent: None }),
CloseAllItems { save_intent: None }, ]
), };
]
}, if let Some(entry) = single_entry_to_resolve {
cx, 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);
}); });
} }

View file

@ -1,4 +1,4 @@
use crate::{ se crate::{
item::{ClosePosition, Item, ItemHandle, ItemSettings, WeakItemHandle}, item::{ClosePosition, Item, ItemHandle, ItemSettings, WeakItemHandle},
toolbar::Toolbar, toolbar::Toolbar,
workspace_settings::{AutosaveSetting, WorkspaceSettings}, workspace_settings::{AutosaveSetting, WorkspaceSettings},

View file

@ -130,7 +130,6 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
let vim_mode_indicator = cx.build_view(|cx| vim::ModeIndicator::new(cx)); let vim_mode_indicator = cx.build_view(|cx| vim::ModeIndicator::new(cx));
let feedback_button = cx let feedback_button = cx
.build_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)); .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()); let cursor_position = cx.build_view(|_| editor::items::CursorPosition::new());
workspace.status_bar().update(cx, |status_bar, cx| { workspace.status_bar().update(cx, |status_bar, cx| {
status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(diagnostic_summary, cx);