mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Fix pane::RevealInProjectPanel to focus/open project panel for non-project buffers (#51246)
Update how `workspace::pane::Pane` handles the `RevealInProjectPanel` action so as to display a notification when the user attempts to reveal an unsaved buffer or a file that does not belong to any of the open projects. Closes #23967 Release Notes: - Update `pane: reveal in project panel` to display a notification when the user attempts to use it with an unsaved buffer or a file that is not part of the open projects --------- Signed-off-by: Pratik Karki <pratik@prertik.com> Co-authored-by: dino <dinojoaocosta@gmail.com>
This commit is contained in:
parent
93438829c7
commit
1dc3bb90e9
2 changed files with 203 additions and 9 deletions
|
|
@ -11,7 +11,7 @@ use std::path::{Path, PathBuf};
|
|||
use util::{path, paths::PathStyle, rel_path::rel_path};
|
||||
use workspace::{
|
||||
AppState, ItemHandle, MultiWorkspace, Pane, Workspace,
|
||||
item::{Item, ProjectItem},
|
||||
item::{Item, ProjectItem, test::TestItem},
|
||||
register_project_item,
|
||||
};
|
||||
|
||||
|
|
@ -6015,6 +6015,150 @@ async fn test_explicit_reveal(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_reveal_in_project_panel_notifications(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/workspace",
|
||||
json!({
|
||||
"README.md": ""
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/workspace".as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let panel = workspace.update_in(cx, ProjectPanel::new);
|
||||
cx.run_until_parked();
|
||||
|
||||
// Ensure that, attempting to run `pane: reveal in project panel` without
|
||||
// any active item does nothing, i.e., does not focus the project panel but
|
||||
// it also does not show a notification.
|
||||
cx.dispatch_action(workspace::RevealInProjectPanel::default());
|
||||
cx.run_until_parked();
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
assert!(
|
||||
!panel.focus_handle(cx).is_focused(window),
|
||||
"Project panel should not be focused after attempting to reveal an invisible worktree entry"
|
||||
);
|
||||
|
||||
panel.workspace.update(cx, |workspace, cx| {
|
||||
assert!(
|
||||
workspace.active_item(cx).is_none(),
|
||||
"Workspace should not have an active item"
|
||||
);
|
||||
assert_eq!(
|
||||
workspace.notification_ids(),
|
||||
vec![],
|
||||
"No notification should be shown when there's no active item"
|
||||
);
|
||||
}).unwrap();
|
||||
});
|
||||
|
||||
// Create a file in a different folder than the one in the project so we can
|
||||
// later open it and ensure that, attempting to reveal it in the project
|
||||
// panel shows a notification and does not focus the project panel.
|
||||
fs.insert_tree(
|
||||
"/external",
|
||||
json!({
|
||||
"file.txt": "External File",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let (worktree, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree("/external/file.txt", false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
let path = rel_path("").into();
|
||||
let project_path = ProjectPath { worktree_id, path };
|
||||
|
||||
workspace.open_path(project_path, None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.dispatch_action(workspace::RevealInProjectPanel::default());
|
||||
cx.run_until_parked();
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
assert!(
|
||||
!panel.focus_handle(cx).is_focused(window),
|
||||
"Project panel should not be focused after attempting to reveal an invisible worktree entry"
|
||||
);
|
||||
|
||||
panel.workspace.update(cx, |workspace, cx| {
|
||||
assert!(
|
||||
workspace.active_item(cx).is_some(),
|
||||
"Workspace should have an active item"
|
||||
);
|
||||
|
||||
let notification_ids = workspace.notification_ids();
|
||||
assert_eq!(
|
||||
notification_ids.len(),
|
||||
1,
|
||||
"A notification should be shown when trying to reveal an invisible worktree entry"
|
||||
);
|
||||
|
||||
workspace.dismiss_notification(¬ification_ids[0], cx);
|
||||
assert_eq!(
|
||||
workspace.notification_ids().len(),
|
||||
0,
|
||||
"No notifications should be left after dismissing"
|
||||
);
|
||||
}).unwrap();
|
||||
});
|
||||
|
||||
// Create an empty buffer so we can ensure that, attempting to reveal it in
|
||||
// the project panel shows a notification and does not focus the project
|
||||
// panel.
|
||||
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
|
||||
pane.update_in(cx, |pane, window, cx| {
|
||||
let item = cx.new(|cx| TestItem::new(cx).with_label("Unsaved buffer"));
|
||||
pane.add_item(Box::new(item), false, false, None, window, cx);
|
||||
});
|
||||
|
||||
cx.dispatch_action(workspace::RevealInProjectPanel::default());
|
||||
cx.run_until_parked();
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
assert!(
|
||||
!panel.focus_handle(cx).is_focused(window),
|
||||
"Project panel should not be focused after attempting to reveal an unsaved buffer"
|
||||
);
|
||||
|
||||
panel
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
assert!(
|
||||
workspace.active_item(cx).is_some(),
|
||||
"Workspace should have an active item"
|
||||
);
|
||||
|
||||
let notification_ids = workspace.notification_ids();
|
||||
assert_eq!(
|
||||
notification_ids.len(),
|
||||
1,
|
||||
"A notification should be shown when trying to reveal an unsaved buffer"
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_creating_excluded_entries(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ use crate::{
|
|||
TabContentParams, TabTooltipContent, WeakItemHandle,
|
||||
},
|
||||
move_item,
|
||||
notifications::NotifyResultExt,
|
||||
notifications::{
|
||||
NotificationId, NotifyResultExt, show_app_notification,
|
||||
simple_message_notification::MessageNotification,
|
||||
},
|
||||
toolbar::Toolbar,
|
||||
workspace_settings::{AutosaveSetting, FocusFollowsMouse, TabBarSettings, WorkspaceSettings},
|
||||
};
|
||||
|
|
@ -4400,17 +4403,64 @@ impl Render for Pane {
|
|||
))
|
||||
.on_action(
|
||||
cx.listener(|pane: &mut Self, action: &RevealInProjectPanel, _, cx| {
|
||||
let Some(active_item) = pane.active_item() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let entry_id = action
|
||||
.entry_id
|
||||
.map(ProjectEntryId::from_proto)
|
||||
.or_else(|| pane.active_item()?.project_entry_ids(cx).first().copied());
|
||||
if let Some(entry_id) = entry_id {
|
||||
pane.project
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(project::Event::RevealInProjectPanel(entry_id))
|
||||
})
|
||||
.ok();
|
||||
.or_else(|| active_item.project_entry_ids(cx).first().copied());
|
||||
|
||||
let show_reveal_error_toast = |display_name: &str, cx: &mut App| {
|
||||
let notification_id = NotificationId::unique::<RevealInProjectPanel>();
|
||||
let message = SharedString::from(format!(
|
||||
"\"{display_name}\" is not part of any open projects."
|
||||
));
|
||||
|
||||
show_app_notification(notification_id, cx, move |cx| {
|
||||
let message = message.clone();
|
||||
cx.new(|cx| MessageNotification::new(message, cx))
|
||||
});
|
||||
};
|
||||
|
||||
let Some(entry_id) = entry_id else {
|
||||
// When working with an unsaved buffer, display a toast
|
||||
// informing the user that the buffer is not present in
|
||||
// any of the open projects and stop execution, as we
|
||||
// don't want to open the project panel.
|
||||
let display_name = active_item
|
||||
.tab_tooltip_text(cx)
|
||||
.unwrap_or_else(|| active_item.tab_content_text(0, cx));
|
||||
|
||||
return show_reveal_error_toast(&display_name, cx);
|
||||
};
|
||||
|
||||
// We'll now check whether the entry belongs to a visible
|
||||
// worktree and, if that's not the case, it means the user
|
||||
// is interacting with a file that does not belong to any of
|
||||
// the open projects, so we'll show a toast informing them
|
||||
// of this and stop execution.
|
||||
let display_name = pane
|
||||
.project
|
||||
.read_with(cx, |project, cx| {
|
||||
project
|
||||
.worktree_for_entry(entry_id, cx)
|
||||
.filter(|worktree| !worktree.read(cx).is_visible())
|
||||
.map(|worktree| worktree.read(cx).root_name_str().to_string())
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
if let Some(display_name) = display_name {
|
||||
return show_reveal_error_toast(&display_name, cx);
|
||||
}
|
||||
|
||||
pane.project
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(project::Event::RevealInProjectPanel(entry_id))
|
||||
})
|
||||
.log_err();
|
||||
}),
|
||||
)
|
||||
.on_action(cx.listener(|_, _: &menu::Cancel, window, cx| {
|
||||
|
|
|
|||
Loading…
Reference in a new issue