Skip to content

Commit 026219c

Browse files
authored
Merge pull request #2 from CodeCraftersLLC/feat/code-editor
Add embedded surface policies for Cherrypick file editor
2 parents 45e8438 + c1ebeef commit 026219c

11 files changed

Lines changed: 795 additions & 154 deletions

File tree

crates/editor/src/editor.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mod persistence;
4141
mod runnables;
4242
mod rust_analyzer_ext;
4343
pub mod scroll;
44+
pub mod selection_controls;
4445
mod selections_collection;
4546
pub mod semantic_tokens;
4647
mod split;
@@ -594,6 +595,50 @@ pub struct ContextMenuOptions {
594595
pub placement: Option<ContextMenuPlacement>,
595596
}
596597

598+
/// Controls which context-menu action groups are shown for an editor surface.
599+
///
600+
/// The default policy preserves normal Zed behavior. Embedded hosts, such as
601+
/// Cherrypick, can install a restricted policy when the host owns those actions
602+
/// or intentionally omits them from its file-editor surface.
603+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
604+
pub struct EditorSurfacePolicy {
605+
pub show_language_actions: bool,
606+
pub show_agent_actions: bool,
607+
pub show_preview_actions: bool,
608+
pub show_terminal_actions: bool,
609+
pub show_git_actions: bool,
610+
}
611+
612+
impl EditorSurfacePolicy {
613+
/// Shows every action group used by the standard Zed editor.
614+
pub const fn full() -> Self {
615+
Self {
616+
show_language_actions: true,
617+
show_agent_actions: true,
618+
show_preview_actions: true,
619+
show_terminal_actions: true,
620+
show_git_actions: true,
621+
}
622+
}
623+
624+
/// Hides action groups that are handled by, or unavailable in, an embedded host.
625+
pub const fn embedded() -> Self {
626+
Self {
627+
show_language_actions: false,
628+
show_agent_actions: false,
629+
show_preview_actions: false,
630+
show_terminal_actions: false,
631+
show_git_actions: false,
632+
}
633+
}
634+
}
635+
636+
impl Default for EditorSurfacePolicy {
637+
fn default() -> Self {
638+
Self::full()
639+
}
640+
}
641+
597642
#[derive(Debug, Clone, PartialEq, Eq)]
598643
pub enum ContextMenuPlacement {
599644
Above,
@@ -1080,6 +1125,7 @@ pub struct Editor {
10801125
show_selection_menu: Option<bool>,
10811126
blame: Option<Entity<GitBlame>>,
10821127
blame_subscription: Option<Subscription>,
1128+
surface_policy: EditorSurfacePolicy,
10831129
custom_context_menu: Option<
10841130
Box<
10851131
dyn 'static
@@ -1736,6 +1782,7 @@ impl Editor {
17361782
clone.needs_initial_data_update = self.enable_lsp_data;
17371783
clone.enable_runnables = self.enable_runnables;
17381784
clone.enable_code_lens = self.enable_code_lens;
1785+
clone.surface_policy = self.surface_policy;
17391786
clone
17401787
}
17411788

@@ -2305,6 +2352,7 @@ impl Editor {
23052352
}),
23062353
blame: None,
23072354
blame_subscription: None,
2355+
surface_policy: EditorSurfacePolicy::default(),
23082356

23092357
bookmark_store,
23102358
breakpoint_store,
@@ -2730,11 +2778,15 @@ impl Editor {
27302778
_: &mut Window,
27312779
cx: &mut Context<Self>,
27322780
) {
2781+
let was_enabled = self.selection_menu_enabled(cx);
27332782
self.show_selection_menu = self
27342783
.show_selection_menu
27352784
.map(|show_selections_menu| !show_selections_menu)
27362785
.or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
27372786

2787+
if self.selection_menu_enabled(cx) != was_enabled {
2788+
cx.emit(EditorEvent::SelectionMenuChanged);
2789+
}
27382790
cx.notify();
27392791
}
27402792

@@ -2964,6 +3016,18 @@ impl Editor {
29643016
self.in_project_search = in_project_search;
29653017
}
29663018

3019+
/// Updates context-menu action visibility for this editor surface.
3020+
///
3021+
/// Standalone Zed leaves the default policy in place; embedded hosts call
3022+
/// this to hide actions that the host owns or does not expose.
3023+
pub fn set_surface_policy(&mut self, policy: EditorSurfacePolicy, cx: &mut Context<Self>) {
3024+
if self.surface_policy == policy {
3025+
return;
3026+
}
3027+
self.surface_policy = policy;
3028+
cx.notify();
3029+
}
3030+
29673031
pub fn set_custom_context_menu(
29683032
&mut self,
29693033
f: impl 'static
@@ -11628,6 +11692,7 @@ pub enum EditorEvent {
1162811692
Saved,
1162911693
TitleChanged,
1163011694
FileHandleChanged,
11695+
SelectionMenuChanged,
1163111696
SelectionsChanged {
1163211697
local: bool,
1163311698
},

crates/editor/src/editor_tests.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,7 @@ fn test_clone(cx: &mut TestAppContext) {
843843
});
844844

845845
_ = editor.update(cx, |editor, window, cx| {
846+
editor.set_surface_policy(EditorSurfacePolicy::embedded(), cx);
846847
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
847848
s.select_ranges(
848849
selection_ranges
@@ -915,6 +916,24 @@ fn test_clone(cx: &mut TestAppContext) {
915916
.display_ranges(&e.display_snapshot(cx)))
916917
.unwrap()
917918
);
919+
assert_eq!(
920+
cloned_editor
921+
.update(cx, |editor, _, _| editor.surface_policy)
922+
.unwrap(),
923+
EditorSurfacePolicy::embedded()
924+
);
925+
}
926+
927+
#[test]
928+
fn test_embedded_surface_policy_hides_host_owned_actions() {
929+
let policy = EditorSurfacePolicy::embedded();
930+
931+
assert!(!policy.show_language_actions);
932+
assert!(!policy.show_agent_actions);
933+
assert!(!policy.show_preview_actions);
934+
assert!(!policy.show_terminal_actions);
935+
assert!(!policy.show_git_actions);
936+
assert_eq!(EditorSurfacePolicy::default(), EditorSurfacePolicy::full());
918937
}
919938

920939
#[gpui::test]

crates/editor/src/items.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ use workspace::{
6060
use zed_actions::preview::{
6161
markdown::OpenPreview as OpenMarkdownPreview, svg::OpenPreview as OpenSvgPreview,
6262
};
63-
6463
pub const MAX_TAB_TITLE_LEN: usize = 24;
6564

6665
impl FollowableItem for Editor {
@@ -1173,6 +1172,10 @@ impl Item for Editor {
11731172
_window: &mut Window,
11741173
cx: &mut Context<Self>,
11751174
) -> Vec<(SharedString, Box<dyn gpui::Action>)> {
1175+
if !self.surface_policy.show_preview_actions {
1176+
return Vec::new();
1177+
}
1178+
11761179
let mut actions = Vec::new();
11771180

11781181
let is_markdown = self

crates/editor/src/mouse_context_menu.rs

Lines changed: 72 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,9 @@ pub fn deploy_context_menu(
205205
.all::<PointUtf16>(&display_map)
206206
.into_iter()
207207
.any(|s| !s.is_empty());
208-
let has_git_repo =
209-
buffer
208+
let surface_policy = editor.surface_policy;
209+
let has_git_repo = surface_policy.show_git_actions
210+
&& buffer
210211
.anchor_to_buffer_anchor(anchor)
211212
.is_some_and(|(buffer_anchor, _)| {
212213
project
@@ -225,23 +226,25 @@ pub fn deploy_context_menu(
225226
cx,
226227
);
227228

228-
let is_markdown = editor
229-
.buffer()
230-
.read(cx)
231-
.as_singleton()
232-
.and_then(|buffer| buffer.read(cx).language())
233-
.is_some_and(|language| language.name().as_ref() == "Markdown");
229+
let is_markdown = surface_policy.show_preview_actions
230+
&& editor
231+
.buffer()
232+
.read(cx)
233+
.as_singleton()
234+
.and_then(|buffer| buffer.read(cx).language())
235+
.is_some_and(|language| language.name().as_ref() == "Markdown");
234236

235-
let is_svg = editor
236-
.buffer()
237-
.read(cx)
238-
.as_singleton()
239-
.and_then(|buffer| buffer.read(cx).file())
240-
.is_some_and(|file| {
241-
std::path::Path::new(file.file_name(cx))
242-
.extension()
243-
.is_some_and(|ext| ext.eq_ignore_ascii_case("svg"))
244-
});
237+
let is_svg = surface_policy.show_preview_actions
238+
&& editor
239+
.buffer()
240+
.read(cx)
241+
.as_singleton()
242+
.and_then(|buffer| buffer.read(cx).file())
243+
.is_some_and(|file| {
244+
std::path::Path::new(file.file_name(cx))
245+
.extension()
246+
.is_some_and(|extension| extension.eq_ignore_ascii_case("svg"))
247+
});
245248

246249
ui::ContextMenu::build(window, cx, |menu, _window, _cx| {
247250
let builder = menu
@@ -256,31 +259,39 @@ pub fn deploy_context_menu(
256259
run_to_cursor || (evaluate_selection && has_selections),
257260
|builder| builder.separator(),
258261
)
259-
.action("Go to Definition", Box::new(GoToDefinition))
260-
.action("Go to Declaration", Box::new(GoToDeclaration))
261-
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
262-
.action("Go to Implementation", Box::new(GoToImplementation))
263-
.action(
264-
"Find All References",
265-
Box::new(FindAllReferences::default()),
266-
)
267-
.separator()
268-
.action("Rename Symbol", Box::new(Rename))
269-
.action("Format Buffer", Box::new(Format))
270-
.when(format_selections, |cx| {
271-
cx.action("Format Selections", Box::new(FormatSelections))
262+
.when(surface_policy.show_language_actions, |builder| {
263+
builder
264+
.action("Go to Definition", Box::new(GoToDefinition))
265+
.action("Go to Declaration", Box::new(GoToDeclaration))
266+
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
267+
.action("Go to Implementation", Box::new(GoToImplementation))
268+
.action(
269+
"Find All References",
270+
Box::new(FindAllReferences::default()),
271+
)
272+
.separator()
273+
.action("Rename Symbol", Box::new(Rename))
274+
.action("Format Buffer", Box::new(Format))
275+
.when(format_selections, |cx| {
276+
cx.action("Format Selections", Box::new(FormatSelections))
277+
})
278+
.action(
279+
"Show Code Actions",
280+
Box::new(ToggleCodeActions {
281+
deployed_from: None,
282+
quick_launch: false,
283+
}),
284+
)
272285
})
273-
.action(
274-
"Show Code Actions",
275-
Box::new(ToggleCodeActions {
276-
deployed_from: None,
277-
quick_launch: false,
278-
}),
286+
.when(
287+
surface_policy.show_agent_actions && !disable_ai && has_selections,
288+
|this| this.action("Add to Agent Thread", Box::new(AddSelectionToThread)),
289+
)
290+
.when(
291+
surface_policy.show_language_actions
292+
|| (surface_policy.show_agent_actions && !disable_ai && has_selections),
293+
|builder| builder.separator(),
279294
)
280-
.when(!disable_ai && has_selections, |this| {
281-
this.action("Add to Agent Thread", Box::new(AddSelectionToThread))
282-
})
283-
.separator()
284295
.action("Cut", Box::new(Cut))
285296
.action("Copy", Box::new(Copy))
286297
.action("Copy and Trim", Box::new(CopyAndTrim))
@@ -297,21 +308,26 @@ pub fn deploy_context_menu(
297308
.when(is_svg, |builder| {
298309
builder.action("Open SVG Preview", Box::new(OpenSvgPreview))
299310
})
300-
.action_disabled_when(
301-
!has_reveal_target,
302-
"Open in Terminal",
303-
Box::new(OpenInTerminal),
304-
)
305-
.action_disabled_when(
306-
!has_git_repo,
307-
"Copy Permalink",
308-
Box::new(CopyPermalinkToLine),
309-
)
310-
.action_disabled_when(
311-
!has_git_repo,
312-
"View File History",
313-
Box::new(git::FileHistory),
314-
);
311+
.when(surface_policy.show_terminal_actions, |builder| {
312+
builder.action_disabled_when(
313+
!has_reveal_target,
314+
"Open in Terminal",
315+
Box::new(OpenInTerminal),
316+
)
317+
})
318+
.when(surface_policy.show_git_actions, |builder| {
319+
builder
320+
.action_disabled_when(
321+
!has_git_repo,
322+
"Copy Permalink",
323+
Box::new(CopyPermalinkToLine),
324+
)
325+
.action_disabled_when(
326+
!has_git_repo,
327+
"View File History",
328+
Box::new(git::FileHistory),
329+
)
330+
});
315331
match focus {
316332
Some(focus) => builder.context(focus),
317333
None => builder,

0 commit comments

Comments
 (0)