Compare commits

...

8 Commits

Author SHA1 Message Date
Smit Barmase
8936755744 experiment: try to reuse context menu 2025-05-23 05:29:47 +05:30
Smit Barmase
fb0b11bea5 disable code action when none exists 2025-05-23 03:41:21 +05:30
Smit Barmase
600c078f9b code action accept works 2025-05-23 03:41:21 +05:30
Smit Barmase
132cfbbaef tie in confirm code action with quick popover 2025-05-23 03:41:12 +05:30
Smit Barmase
72e54e031e intro popover action menu type 2025-05-23 03:38:53 +05:30
Smit Barmase
78a8c6b6e8 clean up 2025-05-23 03:38:53 +05:30
Smit Barmase
780bac3b84 code action toolbar dropdown 2025-05-23 03:38:53 +05:30
Smit Barmase
8a13438b3d show code action in toolbar 2025-05-23 03:38:53 +05:30
12 changed files with 241 additions and 37 deletions

View File

@@ -322,7 +322,9 @@
// Whether to show the Selections menu in the editor toolbar.
"selections_menu": true,
// Whether to show agent review buttons in the editor toolbar.
"agent_review": true
"agent_review": true,
// Whether to show code action buttons in the editor toolbar.
"code_actions": true
},
// Titlebar related settings
"title_bar": {

View File

@@ -681,6 +681,7 @@ async fn test_collaborating_with_code_actions(
&ToggleCodeActions {
deployed_from_indicator: None,
quick_launch: false,
deployed_from_quick_action: false,
},
window,
cx,

View File

@@ -82,6 +82,10 @@ pub struct ToggleCodeActions {
#[serde(default)]
#[serde(skip)]
pub quick_launch: bool,
// If deployed from quick action bar.
#[serde(default)]
#[serde(skip)]
pub deployed_from_quick_action: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]

View File

@@ -168,6 +168,7 @@ impl CodeContextMenu {
pub enum ContextMenuOrigin {
Cursor,
GutterIndicator(DisplayRow),
QuickAction,
}
#[derive(Clone, Debug)]
@@ -876,7 +877,7 @@ impl CodeActionContents {
self.len() == 0
}
fn iter(&self) -> impl Iterator<Item = CodeActionsItem> + '_ {
pub fn iter(&self) -> impl Iterator<Item = CodeActionsItem> + '_ {
self.tasks
.iter()
.flat_map(|tasks| {
@@ -974,6 +975,7 @@ pub(crate) struct CodeActionsMenu {
pub selected_item: usize,
pub scroll_handle: UniformListScrollHandle,
pub deployed_from_indicator: Option<DisplayRow>,
pub deployed_from_quick_action: bool,
}
impl CodeActionsMenu {
@@ -1041,11 +1043,15 @@ impl CodeActionsMenu {
!self.actions.is_empty()
}
fn origin(&self) -> ContextMenuOrigin {
if let Some(row) = self.deployed_from_indicator {
ContextMenuOrigin::GutterIndicator(row)
pub fn origin(&self) -> ContextMenuOrigin {
if self.deployed_from_quick_action {
ContextMenuOrigin::QuickAction
} else {
ContextMenuOrigin::Cursor
if let Some(row) = self.deployed_from_indicator {
ContextMenuOrigin::GutterIndicator(row)
} else {
ContextMenuOrigin::Cursor
}
}
}

View File

@@ -200,7 +200,7 @@ use theme::{
};
use ui::{
ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
IconSize, Indicator, Key, ListItem, Tooltip, h_flex, prelude::*,
};
use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
use workspace::{
@@ -941,7 +941,7 @@ pub struct Editor {
scrollbar_marker_state: ScrollbarMarkerState,
active_indent_guides_state: ActiveIndentGuidesState,
nav_history: Option<ItemNavHistory>,
context_menu: RefCell<Option<CodeContextMenu>>,
pub context_menu: RefCell<Option<CodeContextMenu>>,
context_menu_options: Option<ContextMenuOptions>,
mouse_context_menu: Option<MouseContextMenu>,
completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
@@ -5372,6 +5372,7 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
println!("toggled");
let quick_launch = action.quick_launch;
let mut context_menu = self.context_menu.borrow_mut();
if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
@@ -5389,6 +5390,7 @@ impl Editor {
drop(context_menu);
let snapshot = self.snapshot(window, cx);
let deployed_from_indicator = action.deployed_from_indicator;
let deployed_from_quick_action = action.deployed_from_quick_action;
let mut task = self.code_actions_task.take();
let action = action.clone();
cx.spawn_in(window, async move |editor, cx| {
@@ -5396,9 +5398,9 @@ impl Editor {
prev_task.await.log_err();
task = editor.update(cx, |this, _| this.code_actions_task.take())?;
}
println!("task over");
let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
if editor.focus_handle.is_focused(window) {
if editor.focus_handle.is_focused(window) || deployed_from_quick_action {
let multibuffer_point = action
.deployed_from_indicator
.map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
@@ -5515,6 +5517,7 @@ impl Editor {
&& debug_scenarios.is_empty();
if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
crate::hover_popover::hide_hover(editor, cx);
dbg!(&code_actions.is_some());
*editor.context_menu.borrow_mut() =
Some(CodeContextMenu::CodeActions(CodeActionsMenu {
buffer,
@@ -5527,6 +5530,7 @@ impl Editor {
selected_item: Default::default(),
scroll_handle: UniformListScrollHandle::default(),
deployed_from_indicator,
deployed_from_quick_action,
}));
if spawn_straight_away {
if let Some(task) = editor.confirm_code_action(
@@ -5538,6 +5542,7 @@ impl Editor {
return task;
}
}
println!("cx notify on editor");
cx.notify();
Task::ready(Ok(()))
}) {
@@ -5567,17 +5572,16 @@ impl Editor {
) -> Option<Task<Result<()>>> {
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
let actions_menu =
if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
menu
let (action, buffer, context) =
if let Some(CodeContextMenu::CodeActions(menu)) = self.hide_context_menu(window, cx) {
let action_ix = action.item_ix.unwrap_or(menu.selected_item);
let action_item = menu.actions.get(action_ix)?;
(action_item, menu.buffer, Some(menu.actions.context))
} else {
return None;
};
let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
let action = actions_menu.actions.get(action_ix)?;
let title = action.label();
let buffer = actions_menu.buffer;
let workspace = self.workspace()?;
match action {
@@ -5615,13 +5619,15 @@ impl Editor {
}))
}
CodeActionsItem::DebugScenario(scenario) => {
let context = actions_menu.actions.context.clone();
workspace.update(cx, |workspace, cx| {
dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
});
Some(Task::ready(Ok(())))
if let Some(context) = context {
workspace.update(cx, |workspace, cx| {
dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
});
Some(Task::ready(Ok(())))
} else {
None
}
}
}
}
@@ -5746,6 +5752,16 @@ impl Editor {
self.refresh_code_actions(window, cx);
}
pub fn code_actions_enabled(&self, cx: &App) -> bool {
EditorSettings::get_global(cx).toolbar.code_actions
}
pub fn has_code_actions(&self) -> bool {
self.available_code_actions
.as_ref()
.is_some_and(|(_, actions)| !actions.is_empty())
}
fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
let newest_selection = self.selections.newest_anchor().clone();
let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
@@ -6396,6 +6412,70 @@ impl Editor {
self.update_visible_inline_completion(window, cx);
}
// In editor.rs, add this method:
pub fn render_code_actions_menu_for_popover(
&self,
menu_final: ContextMenu,
window: &mut Window,
cx: &mut App,
) -> ContextMenu {
let context_menu = self.context_menu.borrow();
match context_menu.as_ref() {
Some(CodeContextMenu::CodeActions(menu))
if matches!(menu.origin(), ContextMenuOrigin::QuickAction) =>
{
println!("rendering code context menu");
let actions = menu.actions.clone();
let selected_item = menu.selected_item;
let focus_handle = self.focus_handle.clone();
{
let mut menu_final = menu_final;
menu_final = menu_final.context(focus_handle);
for (index, action_item) in actions.iter().enumerate() {
let label = action_item.label();
let is_selected = index == selected_item;
menu_final = menu_final.custom_entry(
{
let label = label.clone();
move |_, _| {
ListItem::new(index)
.inset(true)
.toggle_state(is_selected)
.child(h_flex().overflow_hidden().child(label.clone()))
.into_any_element()
}
},
{
move |_, cx| {
// editor_entity.update(cx, |editor, cx| {
// if let Some(task) = editor.confirm_code_action(
// &ConfirmCodeAction {
// item_ix: Some(index),
// },
// window,
// cx,
// ) {
// task.detach_and_log_err(cx)
// }
// })
}
},
);
}
menu_final
}
}
_ => {
println!("rendering nothing");
menu_final
}
}
}
pub fn display_cursor_names(
&mut self,
_: &DisplayCursorNames,
@@ -7500,6 +7580,7 @@ impl Editor {
&ToggleCodeActions {
deployed_from_indicator: Some(row),
quick_launch,
deployed_from_quick_action: false,
},
window,
cx,
@@ -7519,7 +7600,7 @@ impl Editor {
.map_or(false, |menu| menu.visible())
}
fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
self.context_menu
.borrow()
.as_ref()

View File

@@ -109,6 +109,7 @@ pub struct Toolbar {
pub quick_actions: bool,
pub selections_menu: bool,
pub agent_review: bool,
pub code_actions: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@@ -503,6 +504,10 @@ pub struct ToolbarContent {
///
/// Default: true
pub agent_review: Option<bool>,
/// Whether to display code action buttons in the editor toolbar.
///
/// Default: true
pub code_actions: Option<bool>,
}
/// Scrollbar related settings

View File

@@ -14245,6 +14245,7 @@ async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
&ToggleCodeActions {
deployed_from_indicator: None,
quick_launch: false,
deployed_from_quick_action: false,
},
window,
cx,

View File

@@ -2386,14 +2386,19 @@ impl EditorElement {
let active_task_indicator_row =
if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
deployed_from_indicator,
deployed_from_quick_action,
actions,
..
})) = editor.context_menu.borrow().as_ref()
{
actions
.tasks()
.map(|tasks| tasks.position.to_display_point(snapshot).row())
.or(*deployed_from_indicator)
if *deployed_from_quick_action {
None
} else {
actions
.tasks()
.map(|tasks| tasks.position.to_display_point(snapshot).row())
.or(*deployed_from_indicator)
}
} else {
None
};
@@ -3583,7 +3588,7 @@ impl EditorElement {
header
}
fn layout_cursor_popovers(
fn layout_cursor_context_menu(
&self,
line_height: Pixels,
text_hitbox: &Hitbox,
@@ -8092,7 +8097,7 @@ impl Element for EditorElement {
let newest_selection_point =
newest_selection_head.to_point(&snapshot.display_snapshot);
if (start_row..end_row).contains(&newest_selection_head.row()) {
self.layout_cursor_popovers(
self.layout_cursor_context_menu(
line_height,
&text_hitbox,
content_origin,

View File

@@ -228,6 +228,7 @@ pub fn deploy_context_menu(
Box::new(ToggleCodeActions {
deployed_from_indicator: None,
quick_launch: false,
deployed_from_quick_action: false,
}),
)
.separator()

View File

@@ -71,6 +71,14 @@ impl<M: ManagedView> PopoverMenuHandle<M> {
}
}
pub fn menu(&self) -> Option<Entity<M>> {
self.0
.borrow()
.as_ref()
.map(|state| state.menu.borrow().clone())
.flatten()
}
pub fn hide(&self, cx: &mut App) {
if let Some(state) = self.0.borrow().as_ref() {
if let Some(menu) = state.menu.borrow().as_ref() {

View File

@@ -1,12 +1,13 @@
mod markdown_preview;
mod repl_menu;
use std::rc::Rc;
use assistant_settings::AssistantSettings;
use editor::actions::{
AddSelectionAbove, AddSelectionBelow, DuplicateLineDown, GoToDiagnostic, GoToHunk,
GoToPreviousDiagnostic, GoToPreviousHunk, MoveLineDown, MoveLineUp, SelectAll,
SelectLargerSyntaxNode, SelectNext, SelectSmallerSyntaxNode, ToggleDiagnostics, ToggleGoToLine,
ToggleInlineDiagnostics,
AddSelectionAbove, AddSelectionBelow, ConfirmCodeAction, DuplicateLineDown, GoToDiagnostic,
GoToHunk, GoToPreviousDiagnostic, GoToPreviousHunk, MoveLineDown, MoveLineUp, SelectAll,
SelectLargerSyntaxNode, SelectNext, SelectSmallerSyntaxNode, ToggleCodeActions,
ToggleDiagnostics, ToggleGoToLine, ToggleInlineDiagnostics,
};
use editor::{Editor, EditorSettings};
use gpui::{
@@ -18,7 +19,7 @@ use search::{BufferSearchBar, buffer_search};
use settings::{Settings, SettingsStore};
use ui::{
ButtonStyle, ContextMenu, ContextMenuEntry, DocumentationSide, IconButton, IconName, IconSize,
PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*,
ListItem, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*,
};
use vim_mode_setting::VimModeSetting;
use workspace::{
@@ -28,10 +29,12 @@ use zed_actions::{assistant::InlineAssist, outline::ToggleOutline};
pub struct QuickActionBar {
_inlay_hints_enabled_subscription: Option<Subscription>,
_context_menu_subscription: Option<Subscription>,
active_item: Option<Box<dyn ItemHandle>>,
buffer_search_bar: Entity<BufferSearchBar>,
show: bool,
toggle_selections_handle: PopoverMenuHandle<ContextMenu>,
toggle_code_action_handle: PopoverMenuHandle<ContextMenu>,
toggle_settings_handle: PopoverMenuHandle<ContextMenu>,
workspace: WeakEntity<Workspace>,
}
@@ -44,10 +47,12 @@ impl QuickActionBar {
) -> Self {
let mut this = Self {
_inlay_hints_enabled_subscription: None,
_context_menu_subscription: None,
active_item: None,
buffer_search_bar,
show: true,
toggle_selections_handle: Default::default(),
toggle_code_action_handle: Default::default(),
toggle_settings_handle: Default::default(),
workspace: workspace.weak_handle(),
};
@@ -107,6 +112,9 @@ impl Render for QuickActionBar {
editor_value.edit_predictions_enabled_at_cursor(cx);
let supports_minimap = editor_value.supports_minimap(cx);
let minimap_enabled = supports_minimap && editor_value.minimap().is_some();
let has_code_actions = editor_value.has_code_actions();
let code_action_enabled = editor_value.code_actions_enabled(cx);
let code_action_origin = editor_value.context_menu_origin();
let focus_handle = editor_value.focus_handle(cx);
@@ -141,6 +149,59 @@ impl Render for QuickActionBar {
},
);
let code_actions_dropdown = code_action_enabled.then(|| {
let focus = editor.focus_handle(cx);
let editor_weak = editor.downgrade();
PopoverMenu::new("editor-code-actions-dropdown")
.menu(move |window, cx| {
let editor = editor_weak.upgrade()?;
let menu =
ContextMenu::build_persistent(window, cx, move |menu, window, cx| {
let inner_menu = editor.update(cx, |editor, cx| {
// First, ensure code actions are loaded
// if !matches!(
// editor.context_menu_origin(),
// Some(ContextMenuOrigin::QuickAction)
// ) {
// }
// Now get the rendered menu
editor.render_code_actions_menu_for_popover(menu, window, cx)
});
inner_menu
});
Some(menu)
})
.on_open({
let editor_weak = editor.downgrade();
Rc::new(move |window, cx| {
println!("open called");
let Some(editor) = editor_weak.upgrade() else {
return;
};
editor.update(cx, |editor, cx| {
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: None,
quick_launch: false,
deployed_from_quick_action: true,
},
window,
cx,
);
})
})
})
.trigger_with_tooltip(
IconButton::new("toggle_code_actions_icon", IconName::Bolt)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.disabled(!has_code_actions)
.toggle_state(self.toggle_code_action_handle.is_deployed()),
Tooltip::text("Code Actions"),
)
.anchor(Corner::TopRight)
});
let editor_selections_dropdown = selection_menu_enabled.then(|| {
let focus = editor.focus_handle(cx);
@@ -487,6 +548,7 @@ impl Render for QuickActionBar {
&& AssistantSettings::get_global(cx).button,
|bar| bar.child(assistant_button),
)
.children(code_actions_dropdown)
.children(editor_selections_dropdown)
.child(editor_settings_dropdown)
}
@@ -547,12 +609,13 @@ impl ToolbarItemView for QuickActionBar {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
_: &mut Window,
window: &mut Window,
cx: &mut Context<Self>,
) -> ToolbarItemLocation {
self.active_item = active_pane_item.map(ItemHandle::boxed_clone);
if let Some(active_item) = active_pane_item {
self._inlay_hints_enabled_subscription.take();
self._context_menu_subscription.take();
if let Some(editor) = active_item.downcast::<Editor>() {
let (mut inlay_hints_enabled, mut supports_inlay_hints) =
@@ -579,6 +642,32 @@ impl ToolbarItemView for QuickActionBar {
cx.notify()
}
}));
self._context_menu_subscription =
Some(cx.observe_in(&editor, window, |this, editor, window, cx| {
// Check if the context menu origin is QuickAction
// let is_quick_action_menu = editor
// .read(cx)
// .context_menu_origin()
// .map_or(false, |origin| {
// matches!(origin, ContextMenuOrigin::QuickAction)
// });
this.toggle_code_action_handle
.menu()
.map(|menu| menu.update(cx, |menu, cx| menu.rebuild(window, cx)));
println!("notified quick action");
cx.notify();
// Update the popover handle state if needed
// if is_quick_action_menu && !this.toggle_code_actions_handle.is_deployed() {
// // The menu was opened but popover doesn't know about it
// } else if !is_quick_action_menu && this.toggle_code_actions_handle.is_deployed()
// {
// // The menu was closed but popover still thinks it's open
// this.toggle_code_actions_handle.hide(cx);
// cx.notify();
// }
}));
}
}
self.get_toolbar_item_location()

View File

@@ -1214,7 +1214,8 @@ or
"breadcrumbs": true,
"quick_actions": true,
"selections_menu": true,
"agent_review": true
"agent_review": true,
"code_actions": true
},
```