project_panel: Disable undo and redo on read-only projects (#61104)

# Objective

Users in a read-only project should have no permission to create,
rename, move, trash or delete files, as such, they should also have no
way to undo or redo these actions. These changes ensure that the
listeners for both `project_panel::Undo` and `project_panel::Redo` are
only registered if the project is not read-only.

## Solution

The `ProjectPanel` listeners for both the `Undo` and `Redo` actions were
not gated behind the existing `!Project::is_read_only` call, on
`ProjectPanel::render`, so these changes move the listeners inside that
check.

## Testing

Currently, there isn't an easy way to manually test this, as these
actions were already disabled in collab and the easiest way to get into
a read-only project is connect as a collab guest. However, in order to
ensure this continues being asserted once we remove the collab
restriction, the
`project_panel::tests::undo::undo_redo_unavailable_for_read_only_collab_guest`
test has been added.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable

---

Release Notes:

- N/A
This commit is contained in:
Dino authored and GitHub committed 2026-07-16 11:45:03 +00:00
1 parent 7eaddbc5d9
commit ca2624960f
2 files changed
+46 -7

No files matched your search

+5 -7
View File
@@ -6880,6 +6880,7 @@ impl Render for ProjectPanel {
// version that understands these messages.
let is_collab = project.is_via_collab();
let is_local = project.is_local();
let supports_undo = cx.has_flag::<ProjectPanelUndoRedoFeatureFlag>();
if has_worktree {
let item_count = self
@@ -7011,13 +7012,6 @@ impl Render for ProjectPanel {
.on_action(cx.listener(Self::fold_directory))
.on_action(cx.listener(Self::remove_from_project))
.on_action(cx.listener(Self::compare_marked_files))
.when(
!is_collab && cx.has_flag::<ProjectPanelUndoRedoFeatureFlag>(),
|el| {
el.on_action(cx.listener(Self::undo))
.on_action(cx.listener(Self::redo))
},
)
.when(!project.is_read_only(cx), |el| {
el.on_action(cx.listener(Self::new_file))
.on_action(cx.listener(Self::new_directory))
@@ -7031,6 +7025,10 @@ impl Render for ProjectPanel {
.on_action(cx.listener(Self::add_to_gitignore))
.on_action(cx.listener(Self::add_to_git_info_exclude))
.when(!is_collab, |el| el.on_action(cx.listener(Self::trash)))
.when(!is_collab && supports_undo, |el| {
el.on_action(cx.listener(Self::undo))
.on_action(cx.listener(Self::redo))
})
})
.when(
project.is_local() || project.is_via_wsl_with_host_interop(cx),
+41
View File
@@ -1,5 +1,6 @@
#![cfg(test)]
use client::proto;
use collections::HashSet;
use fs::{FakeFs, Fs};
use gpui::{Entity, VisualTestContext};
@@ -240,6 +241,12 @@ impl TestContext {
.unwrap();
let mut cx = VisualTestContext::from_window(window.into(), cx);
let panel = workspace.update_in(&mut cx, ProjectPanel::new);
workspace.update_in(&mut cx, |workspace, window, cx| {
workspace.add_panel(panel.clone(), window, cx);
workspace.focus_panel::<ProjectPanel>(window, cx);
});
cx.run_until_parked();
TestContext { panel, fs, cx }
@@ -458,3 +465,37 @@ async fn record_via_collab(cx: &mut gpui::TestAppContext) {
cx.redo().await;
cx.assert_fs_state_is(&["b.txt", "renamed.txt"]);
}
#[gpui::test]
async fn undo_redo_unavailable_for_read_only_collab_guest(cx: &mut gpui::TestAppContext) {
let mut cx = TestContext::new(cx).await;
let focus_handle = cx
.panel
.read_with(&cx.cx, |panel, _| panel.focus_handle.clone());
cx.cx.update(|window, _cx| {
assert!(window.is_action_available_in(&crate::Undo, &focus_handle));
assert!(window.is_action_available_in(&crate::Redo, &focus_handle));
});
// In order to simulate a read-only project, we mark it both as a collab
// session as well as being a guest, which only has read access.
// This is currently a bit redundant, seeing as these actions are already
// disabled in collab either way. However, we'll want to enable undo/redo in
// collab in the future and this test will ensure that, at that point, we
// continue to not allow undo/redo in read-only projects.
cx.panel.update(&mut cx.cx, |panel, cx| {
panel.project.update(cx, |project, cx| {
project.mark_as_collab_for_testing();
project.set_role(proto::ChannelRole::Guest, cx);
});
assert!(panel.project.read(cx).is_read_only(cx));
cx.notify();
});
cx.cx.update(|window, _cx| {
assert!(!window.is_action_available_in(&crate::Undo, &focus_handle));
assert!(!window.is_action_available_in(&crate::Redo, &focus_handle));
});
}