From cc3d4d58aed840fcdd970efb6c98db80119a7e36 Mon Sep 17 00:00:00 2001
From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Date: Fri, 19 Jun 2026 13:07:07 +0200
Subject: [PATCH] workspace: Allow user to pick arbitrary parent path as a
trustee (#59562)
This allows one to "bless" arbitrary parent path, without making
assumptions about the structure of the project storage on users machine.
## Testing
- Did you test these changes? If so, how?
- Are there any parts that need more testing?
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [x] 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
- [x] Performance impact has been considered and is acceptable
## Showcase
> This section is optional. If this PR does not include a visual change
or does not add a new user-facing feature, you can delete this section.
- Help others understand the result of this PR by showcasing your
awesome work!
- If this PR includes a visual change, consider adding a screenshot,
GIF, or video
- A before/after comparison is very useful for changes to existing
features!
While a showcase should aim to be brief and digestible, you can use a
toggleable section to save space on longer showcases:
Click to view showcase
My super cool demos here
---
Release Notes:
- When opening a project for the first time, you can now auto-trust
arbitrary parent path; previously Zed offered affordance for
auto-trusting a parent of a parent only.
---
Cargo.lock | 1 +
crates/editor/src/editor.rs | 7 +
crates/ui_input/src/ui_input.rs | 1 +
crates/workspace/Cargo.toml | 1 +
crates/workspace/src/security_modal.rs | 218 +++++++++++++++++++++++--
crates/workspace/src/workspace.rs | 4 +-
6 files changed, 213 insertions(+), 19 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index e8bfbb61cf..ee82f7d975 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -22451,6 +22451,7 @@ dependencies = [
"theme",
"theme_settings",
"ui",
+ "ui_input",
"url",
"util",
"uuid",
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index e98defa308..32487c5390 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -11824,6 +11824,13 @@ impl ui_input::ErasedEditor for ErasedEditorImpl {
editor.set_masked(masked, cx);
});
}
+
+ fn set_read_only(&self, read_only: bool, cx: &mut App) {
+ self.0.update(cx, |editor, cx| {
+ editor.set_read_only(read_only);
+ cx.notify();
+ });
+ }
}
impl Default for InvalidationStack {
fn default() -> Self {
diff --git a/crates/ui_input/src/ui_input.rs b/crates/ui_input/src/ui_input.rs
index ab3addc35c..595f16d849 100644
--- a/crates/ui_input/src/ui_input.rs
+++ b/crates/ui_input/src/ui_input.rs
@@ -20,6 +20,7 @@ pub trait ErasedEditor: 'static {
fn set_placeholder_text(&self, text: &str, window: &mut Window, _: &mut App);
fn move_selection_to_end(&self, window: &mut Window, _: &mut App);
fn set_masked(&self, masked: bool, window: &mut Window, cx: &mut App);
+ fn set_read_only(&self, read_only: bool, cx: &mut App);
fn focus_handle(&self, cx: &App) -> FocusHandle;
diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml
index ed91016bca..4fa5c14259 100644
--- a/crates/workspace/Cargo.toml
+++ b/crates/workspace/Cargo.toml
@@ -66,6 +66,7 @@ telemetry.workspace = true
theme.workspace = true
theme_settings.workspace = true
ui.workspace = true
+ui_input.workspace = true
url.workspace = true
util.workspace = true
uuid.workspace = true
diff --git a/crates/workspace/src/security_modal.rs b/crates/workspace/src/security_modal.rs
index 378968fd1c..3c8a831fb4 100644
--- a/crates/workspace/src/security_modal.rs
+++ b/crates/workspace/src/security_modal.rs
@@ -7,7 +7,7 @@ use std::{
};
use collections::{HashMap, HashSet};
-use gpui::{DismissEvent, EventEmitter, FocusHandle, Focusable, ScrollHandle, WeakEntity};
+use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, WeakEntity};
use project::{
WorktreeId,
@@ -20,6 +20,7 @@ use ui::{
AlertModal, Checkbox, FluentBuilder, KeyBinding, ListBulletItem, ToggleState, WithScrollbar,
prelude::*,
};
+use ui_input::InputField;
use crate::{DismissDecision, ModalView, ToggleWorktreeSecurity};
@@ -32,6 +33,9 @@ pub struct SecurityModal {
focus_handle: FocusHandle,
project_list_scroll_handle: ScrollHandle,
trusted: Option,
+ /// Editable trust scope shown inline with the checkbox when a single
+ /// project is being prompted for; read-only until the checkbox is ticked.
+ trust_path_input: Entity,
}
#[derive(Debug, PartialEq, Eq)]
@@ -86,6 +90,13 @@ impl Render for SecurityModal {
let trust_label = self.build_trust_label();
+ // The editable trust-scope field is shown only when a single project is
+ // being prompted for (Delta opens one worktree per thread).
+ let trust_input = self
+ .single_trustable_path()
+ .is_some()
+ .then(|| self.trust_path_input.clone());
+
AlertModal::new("security-modal")
.width(rems(40.))
.key_context("SecurityModal")
@@ -192,9 +203,55 @@ impl Render for SecurityModal {
.child(ListBulletItem::new("Language servers from running"))
.child(ListBulletItem::new("MCP Server integrations from installing")),
)
- .map(|this| match trust_label {
- Some(trust_label) => this.child(
- Checkbox::new("trust-parents", ToggleState::from(self.trust_parents))
+ .map(|this| {
+ let Some(trust_label) = trust_label else {
+ return this;
+ };
+ match trust_input {
+ // Single project: the editable scope field replaces
+ // the static folder name, inline with the checkbox.
+ Some(input) => this.child(
+ // Top-aligned so the field's validation error
+ // grows downward without shifting the checkbox;
+ // the checkbox sits in a fixed-height box matching
+ // the input row.
+ h_flex()
+ .items_start()
+ .gap_1p5()
+ .child(
+ h_flex().h_8().child(
+ Checkbox::new(
+ "trust-parents",
+ ToggleState::from(self.trust_parents),
+ )
+ .label("Trust all projects in")
+ .on_click(cx.listener(
+ |security_modal, state: &ToggleState, _, cx| {
+ let trust_parents = state.selected();
+ security_modal.trust_parents = trust_parents;
+ let input =
+ security_modal.trust_path_input.clone();
+ let editor = input.read(cx).editor().clone();
+ editor.set_read_only(!trust_parents, cx);
+ if !trust_parents {
+ input.update(cx, |input, cx| {
+ input.set_error(None::, cx)
+ });
+ }
+ cx.notify();
+ cx.stop_propagation();
+ },
+ )),
+ ),
+ )
+ .child(input),
+ ),
+ // Zero or several projects: keep the static label.
+ None => this.child(
+ Checkbox::new(
+ "trust-parents",
+ ToggleState::from(self.trust_parents),
+ )
.label(trust_label)
.on_click(cx.listener(
|security_modal, state: &ToggleState, _, cx| {
@@ -203,8 +260,8 @@ impl Render for SecurityModal {
cx.stop_propagation();
},
)),
- ),
- None => this,
+ ),
+ }
}),
)
.footer(
@@ -250,8 +307,10 @@ impl SecurityModal {
pub fn new(
worktree_store: WeakEntity,
remote_host: Option>,
+ window: &mut Window,
cx: &mut Context,
) -> Self {
+ let trust_path_input = cx.new(|cx| InputField::new(window, cx, "Folder to trust"));
let mut this = Self {
worktree_store,
remote_host: remote_host.map(|host| host.into()),
@@ -261,9 +320,21 @@ impl SecurityModal {
trust_parents: false,
home_dir: std::env::home_dir(),
trusted: None,
+ trust_path_input,
};
this.refresh_restricted_paths(cx);
+ // Pre-fill with the single project's parent folder (today's static
+ // scope), read-only until the checkbox is ticked.
+ if let Some(project) = this.single_trustable_path() {
+ let default_scope = project.parent().unwrap_or(&project).to_path_buf();
+ this.trust_path_input.update(cx, |field, cx| {
+ field.set_text(&default_scope.to_string_lossy(), window, cx);
+ });
+ }
+ let editor = this.trust_path_input.read(cx).editor().clone();
+ editor.set_read_only(!this.trust_parents, cx);
+
this
}
@@ -305,7 +376,31 @@ impl SecurityModal {
}
}
+ /// The user-edited trust scope, when an editable field is shown. `Ok(None)`
+ /// means fall back to the default per-parent behavior; `Err` is a validation
+ /// message to surface on the field.
+ fn edited_trust_scope(&self, cx: &App) -> Result