Skip to content
65 changes: 65 additions & 0 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ mod persistence;
mod runnables;
mod rust_analyzer_ext;
pub mod scroll;
pub mod selection_controls;
mod selections_collection;
pub mod semantic_tokens;
mod split;
Expand Down Expand Up @@ -594,6 +595,50 @@ pub struct ContextMenuOptions {
pub placement: Option<ContextMenuPlacement>,
}

/// Controls which context-menu action groups are shown for an editor surface.
///
/// The default policy preserves normal Zed behavior. Embedded hosts, such as
/// Cherrypick, can install a restricted policy when the host owns those actions
/// or intentionally omits them from its file-editor surface.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EditorSurfacePolicy {
pub show_language_actions: bool,
pub show_agent_actions: bool,
pub show_preview_actions: bool,
pub show_terminal_actions: bool,
pub show_git_actions: bool,
}

impl EditorSurfacePolicy {
/// Shows every action group used by the standard Zed editor.
pub const fn full() -> Self {
Self {
show_language_actions: true,
show_agent_actions: true,
show_preview_actions: true,
show_terminal_actions: true,
show_git_actions: true,
}
}

/// Hides action groups that are handled by, or unavailable in, an embedded host.
pub const fn embedded() -> Self {
Self {
show_language_actions: false,
show_agent_actions: false,
show_preview_actions: false,
show_terminal_actions: false,
show_git_actions: false,
}
}
}

impl Default for EditorSurfacePolicy {
fn default() -> Self {
Self::full()
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ContextMenuPlacement {
Above,
Expand Down Expand Up @@ -1080,6 +1125,7 @@ pub struct Editor {
show_selection_menu: Option<bool>,
blame: Option<Entity<GitBlame>>,
blame_subscription: Option<Subscription>,
surface_policy: EditorSurfacePolicy,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
custom_context_menu: Option<
Box<
dyn 'static
Expand Down Expand Up @@ -1736,6 +1782,7 @@ impl Editor {
clone.needs_initial_data_update = self.enable_lsp_data;
clone.enable_runnables = self.enable_runnables;
clone.enable_code_lens = self.enable_code_lens;
clone.surface_policy = self.surface_policy;
clone
}

Expand Down Expand Up @@ -2305,6 +2352,7 @@ impl Editor {
}),
blame: None,
blame_subscription: None,
surface_policy: EditorSurfacePolicy::default(),

bookmark_store,
breakpoint_store,
Expand Down Expand Up @@ -2730,11 +2778,15 @@ impl Editor {
_: &mut Window,
cx: &mut Context<Self>,
) {
let was_enabled = self.selection_menu_enabled(cx);
self.show_selection_menu = self
.show_selection_menu
.map(|show_selections_menu| !show_selections_menu)
.or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));

if self.selection_menu_enabled(cx) != was_enabled {
cx.emit(EditorEvent::SelectionMenuChanged);
}
cx.notify();
}

Expand Down Expand Up @@ -2964,6 +3016,18 @@ impl Editor {
self.in_project_search = in_project_search;
}

/// Updates context-menu action visibility for this editor surface.
///
/// Standalone Zed leaves the default policy in place; embedded hosts call
/// this to hide actions that the host owns or does not expose.
pub fn set_surface_policy(&mut self, policy: EditorSurfacePolicy, cx: &mut Context<Self>) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: set_surface_policy is unused, so surface_policy stays default and non-default surface behavior is currently unreachable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/editor/src/editor.rs, line 3006:

<comment>`set_surface_policy` is unused, so `surface_policy` stays default and non-default surface behavior is currently unreachable.</comment>

<file context>
@@ -2964,6 +3003,14 @@ impl Editor {
         self.in_project_search = in_project_search;
     }
 
+    pub fn set_surface_policy(&mut self, policy: EditorSurfacePolicy, cx: &mut Context<Self>) {
+        if self.surface_policy == policy {
+            return;
</file context>

if self.surface_policy == policy {
return;
}
self.surface_policy = policy;
cx.notify();
}

pub fn set_custom_context_menu(
&mut self,
f: impl 'static
Expand Down Expand Up @@ -11628,6 +11692,7 @@ pub enum EditorEvent {
Saved,
TitleChanged,
FileHandleChanged,
SelectionMenuChanged,
SelectionsChanged {
local: bool,
},
Expand Down
19 changes: 19 additions & 0 deletions crates/editor/src/editor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,7 @@ fn test_clone(cx: &mut TestAppContext) {
});

_ = editor.update(cx, |editor, window, cx| {
editor.set_surface_policy(EditorSurfacePolicy::embedded(), cx);
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.select_ranges(
selection_ranges
Expand Down Expand Up @@ -915,6 +916,24 @@ fn test_clone(cx: &mut TestAppContext) {
.display_ranges(&e.display_snapshot(cx)))
.unwrap()
);
assert_eq!(
cloned_editor
.update(cx, |editor, _, _| editor.surface_policy)
.unwrap(),
EditorSurfacePolicy::embedded()
);
}

#[test]
fn test_embedded_surface_policy_hides_host_owned_actions() {
let policy = EditorSurfacePolicy::embedded();

assert!(!policy.show_language_actions);
assert!(!policy.show_agent_actions);
assert!(!policy.show_preview_actions);
assert!(!policy.show_terminal_actions);
assert!(!policy.show_git_actions);
assert_eq!(EditorSurfacePolicy::default(), EditorSurfacePolicy::full());
}

#[gpui::test]
Expand Down
5 changes: 4 additions & 1 deletion crates/editor/src/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ use workspace::{
use zed_actions::preview::{
markdown::OpenPreview as OpenMarkdownPreview, svg::OpenPreview as OpenSvgPreview,
};

pub const MAX_TAB_TITLE_LEN: usize = 24;

impl FollowableItem for Editor {
Expand Down Expand Up @@ -1173,6 +1172,10 @@ impl Item for Editor {
_window: &mut Window,
cx: &mut Context<Self>,
) -> Vec<(SharedString, Box<dyn gpui::Action>)> {
if !self.surface_policy.show_preview_actions {
return Vec::new();
}

let mut actions = Vec::new();

let is_markdown = self
Expand Down
128 changes: 72 additions & 56 deletions crates/editor/src/mouse_context_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,9 @@ pub fn deploy_context_menu(
.all::<PointUtf16>(&display_map)
.into_iter()
.any(|s| !s.is_empty());
let has_git_repo =
buffer
let surface_policy = editor.surface_policy;
let has_git_repo = surface_policy.show_git_actions
&& buffer
.anchor_to_buffer_anchor(anchor)
.is_some_and(|(buffer_anchor, _)| {
project
Expand All @@ -225,23 +226,25 @@ pub fn deploy_context_menu(
cx,
);

let is_markdown = editor
.buffer()
.read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).language())
.is_some_and(|language| language.name().as_ref() == "Markdown");
let is_markdown = surface_policy.show_preview_actions
&& editor
.buffer()
.read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).language())
.is_some_and(|language| language.name().as_ref() == "Markdown");

let is_svg = editor
.buffer()
.read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).file())
.is_some_and(|file| {
std::path::Path::new(file.file_name(cx))
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("svg"))
});
let is_svg = surface_policy.show_preview_actions
&& editor
.buffer()
.read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).file())
.is_some_and(|file| {
std::path::Path::new(file.file_name(cx))
.extension()
.is_some_and(|extension| extension.eq_ignore_ascii_case("svg"))
});

ui::ContextMenu::build(window, cx, |menu, _window, _cx| {
let builder = menu
Expand All @@ -256,31 +259,39 @@ pub fn deploy_context_menu(
run_to_cursor || (evaluate_selection && has_selections),
|builder| builder.separator(),
)
.action("Go to Definition", Box::new(GoToDefinition))
.action("Go to Declaration", Box::new(GoToDeclaration))
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
.action("Go to Implementation", Box::new(GoToImplementation))
.action(
"Find All References",
Box::new(FindAllReferences::default()),
)
.separator()
.action("Rename Symbol", Box::new(Rename))
.action("Format Buffer", Box::new(Format))
.when(format_selections, |cx| {
cx.action("Format Selections", Box::new(FormatSelections))
.when(surface_policy.show_language_actions, |builder| {
builder
.action("Go to Definition", Box::new(GoToDefinition))
.action("Go to Declaration", Box::new(GoToDeclaration))
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
.action("Go to Implementation", Box::new(GoToImplementation))
.action(
"Find All References",
Box::new(FindAllReferences::default()),
)
.separator()
.action("Rename Symbol", Box::new(Rename))
.action("Format Buffer", Box::new(Format))
.when(format_selections, |cx| {
cx.action("Format Selections", Box::new(FormatSelections))
})
.action(
"Show Code Actions",
Box::new(ToggleCodeActions {
deployed_from: None,
quick_launch: false,
}),
)
})
.action(
"Show Code Actions",
Box::new(ToggleCodeActions {
deployed_from: None,
quick_launch: false,
}),
.when(
surface_policy.show_agent_actions && !disable_ai && has_selections,
|this| this.action("Add to Agent Thread", Box::new(AddSelectionToThread)),
)
.when(
surface_policy.show_language_actions
|| (surface_policy.show_agent_actions && !disable_ai && has_selections),
|builder| builder.separator(),
)
.when(!disable_ai && has_selections, |this| {
this.action("Add to Agent Thread", Box::new(AddSelectionToThread))
})
.separator()
.action("Cut", Box::new(Cut))
.action("Copy", Box::new(Copy))
.action("Copy and Trim", Box::new(CopyAndTrim))
Expand All @@ -297,21 +308,26 @@ pub fn deploy_context_menu(
.when(is_svg, |builder| {
builder.action("Open SVG Preview", Box::new(OpenSvgPreview))
})
.action_disabled_when(
!has_reveal_target,
"Open in Terminal",
Box::new(OpenInTerminal),
)
.action_disabled_when(
!has_git_repo,
"Copy Permalink",
Box::new(CopyPermalinkToLine),
)
.action_disabled_when(
!has_git_repo,
"View File History",
Box::new(git::FileHistory),
);
.when(surface_policy.show_terminal_actions, |builder| {
builder.action_disabled_when(
!has_reveal_target,
"Open in Terminal",
Box::new(OpenInTerminal),
)
})
.when(surface_policy.show_git_actions, |builder| {
builder
.action_disabled_when(
!has_git_repo,
"Copy Permalink",
Box::new(CopyPermalinkToLine),
)
.action_disabled_when(
!has_git_repo,
"View File History",
Box::new(git::FileHistory),
)
});
match focus {
Some(focus) => builder.context(focus),
None => builder,
Expand Down
Loading
Loading