Add a run menu (#32505)

As part of this I refactored the logic that enabled/disabled actions in
the debugger to happen at action registration time instead of using
command palette filters. This allows the menu to grey out actions correctly.

Release Notes:

- Add a "Run" menu to contain tasks and debugger
This commit is contained in:
Conrad Irwin 2025-06-10 19:57:46 -06:00 committed by GitHub
parent 444f797827
commit 00a8101016
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 296 additions and 369 deletions

1
Cargo.lock generated
View file

@ -4695,7 +4695,6 @@ dependencies = [
"client", "client",
"clock", "clock",
"collections", "collections",
"command_palette_hooks",
"convert_case 0.8.0", "convert_case 0.8.0",
"ctor", "ctor",
"dap", "dap",

View file

@ -1,4 +1,4 @@
// Static tasks configuration. // Project tasks configuration. See https://zed.dev/docs/tasks for documentation.
// //
// Example: // Example:
[ [

View file

@ -4,12 +4,10 @@ use crate::session::running::RunningState;
use crate::{ use crate::{
ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames, ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, NewProcessModal, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, NewProcessModal,
NewProcessMode, Pause, Restart, ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop, NewProcessMode, Pause, Restart, StepInto, StepOut, StepOver, Stop, ToggleExpandItem,
ToggleExpandItem, ToggleIgnoreBreakpoints, ToggleSessionPicker, ToggleThreadPicker, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
persistence, spawn_task_or_modal,
}; };
use anyhow::Result; use anyhow::Result;
use command_palette_hooks::CommandPaletteFilter;
use dap::adapters::DebugAdapterName; use dap::adapters::DebugAdapterName;
use dap::debugger_settings::DebugPanelDockPosition; use dap::debugger_settings::DebugPanelDockPosition;
use dap::{ use dap::{
@ -29,7 +27,6 @@ use project::{Fs, WorktreeId};
use project::{Project, debugger::session::ThreadStatus}; use project::{Project, debugger::session::ThreadStatus};
use rpc::proto::{self}; use rpc::proto::{self};
use settings::Settings; use settings::Settings;
use std::any::TypeId;
use std::sync::Arc; use std::sync::Arc;
use task::{DebugScenario, TaskContext}; use task::{DebugScenario, TaskContext};
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*}; use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
@ -140,82 +137,6 @@ impl DebugPanel {
.map(|session| session.read(cx).running_state().clone()) .map(|session| session.read(cx).running_state().clone())
} }
pub(crate) fn filter_action_types(&self, cx: &mut App) {
let (has_active_session, supports_restart, support_step_back, status) = self
.active_session()
.map(|item| {
let running = item.read(cx).running_state().clone();
let caps = running.read(cx).capabilities(cx);
(
!running.read(cx).session().read(cx).is_terminated(),
caps.supports_restart_request.unwrap_or_default(),
caps.supports_step_back.unwrap_or_default(),
running.read(cx).thread_status(cx),
)
})
.unwrap_or((false, false, false, None));
let filter = CommandPaletteFilter::global_mut(cx);
let debugger_action_types = [
TypeId::of::<Detach>(),
TypeId::of::<Stop>(),
TypeId::of::<ToggleIgnoreBreakpoints>(),
];
let running_action_types = [TypeId::of::<Pause>()];
let stopped_action_type = [
TypeId::of::<Continue>(),
TypeId::of::<StepOver>(),
TypeId::of::<StepInto>(),
TypeId::of::<StepOut>(),
TypeId::of::<ShowStackTrace>(),
TypeId::of::<editor::actions::DebuggerRunToCursor>(),
TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),
];
let step_back_action_type = [TypeId::of::<StepBack>()];
let restart_action_type = [TypeId::of::<Restart>()];
if has_active_session {
filter.show_action_types(debugger_action_types.iter());
if supports_restart {
filter.show_action_types(restart_action_type.iter());
} else {
filter.hide_action_types(&restart_action_type);
}
if support_step_back {
filter.show_action_types(step_back_action_type.iter());
} else {
filter.hide_action_types(&step_back_action_type);
}
match status {
Some(ThreadStatus::Running) => {
filter.show_action_types(running_action_types.iter());
filter.hide_action_types(&stopped_action_type);
}
Some(ThreadStatus::Stopped) => {
filter.show_action_types(stopped_action_type.iter());
filter.hide_action_types(&running_action_types);
}
_ => {
filter.hide_action_types(&running_action_types);
filter.hide_action_types(&stopped_action_type);
}
}
} else {
// show only the `debug: start`
filter.hide_action_types(&debugger_action_types);
filter.hide_action_types(&step_back_action_type);
filter.hide_action_types(&restart_action_type);
filter.hide_action_types(&running_action_types);
filter.hide_action_types(&stopped_action_type);
}
}
pub fn load( pub fn load(
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
cx: &mut AsyncWindowContext, cx: &mut AsyncWindowContext,
@ -233,17 +154,6 @@ impl DebugPanel {
) )
}); });
cx.observe_new::<DebugPanel>(|debug_panel, _, cx| {
Self::filter_action_types(debug_panel, cx);
})
.detach();
cx.observe(&debug_panel, |_, debug_panel, cx| {
debug_panel.update(cx, |debug_panel, cx| {
Self::filter_action_types(debug_panel, cx);
});
})
.detach();
workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone())); workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
debug_panel debug_panel

View file

@ -1,14 +1,17 @@
use std::any::TypeId;
use dap::debugger_settings::DebuggerSettings; use dap::debugger_settings::DebuggerSettings;
use debugger_panel::{DebugPanel, ToggleFocus}; use debugger_panel::{DebugPanel, ToggleFocus};
use editor::Editor; use editor::Editor;
use feature_flags::{DebuggerFeatureFlag, FeatureFlagViewExt}; use feature_flags::{DebuggerFeatureFlag, FeatureFlagViewExt};
use gpui::{App, EntityInputHandler, actions}; use gpui::{App, DispatchPhase, EntityInputHandler, actions};
use new_process_modal::{NewProcessModal, NewProcessMode}; use new_process_modal::{NewProcessModal, NewProcessMode};
use project::debugger::{self, breakpoint_store::SourceBreakpoint}; use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};
use session::DebugSession; use session::DebugSession;
use settings::Settings; use settings::Settings;
use stack_trace_view::StackTraceView; use stack_trace_view::StackTraceView;
use tasks_ui::{Spawn, TaskOverrides}; use tasks_ui::{Spawn, TaskOverrides};
use ui::{FluentBuilder, InteractiveElement};
use util::maybe; use util::maybe;
use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace}; use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
@ -68,148 +71,6 @@ pub fn init(cx: &mut App) {
.register_action(|workspace, _: &ToggleFocus, window, cx| { .register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<DebugPanel>(window, cx); workspace.toggle_panel_focus::<DebugPanel>(window, cx);
}) })
.register_action(|workspace, _: &Pause, _, cx| {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
{
active_item.update(cx, |item, cx| item.pause_thread(cx))
}
}
})
.register_action(|workspace, _: &Restart, _, cx| {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
{
active_item.update(cx, |item, cx| item.restart_session(cx))
}
}
})
.register_action(|workspace, _: &Continue, _, cx| {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
{
active_item.update(cx, |item, cx| item.continue_thread(cx))
}
}
})
.register_action(|workspace, _: &StepInto, _, cx| {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
{
active_item.update(cx, |item, cx| item.step_in(cx))
}
}
})
.register_action(|workspace, _: &StepOver, _, cx| {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
{
active_item.update(cx, |item, cx| item.step_over(cx))
}
}
})
.register_action(|workspace, _: &StepOut, _, cx| {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
panel
.active_session()
.map(|session| session.read(cx).running_state().clone())
}) {
active_item.update(cx, |item, cx| item.step_out(cx))
}
}
})
.register_action(|workspace, _: &StepBack, _, cx| {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
{
active_item.update(cx, |item, cx| item.step_back(cx))
}
}
})
.register_action(|workspace, _: &Stop, _, cx| {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
{
cx.defer(move |cx| {
active_item.update(cx, |item, cx| item.stop_thread(cx))
})
}
}
})
.register_action(|workspace, _: &ToggleIgnoreBreakpoints, _, cx| {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
{
active_item.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
}
}
})
.register_action(
|workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
workspace.project().update(cx, |project, cx| {
project.dap_store().update(cx, |store, cx| {
store.shutdown_sessions(cx).detach();
})
})
},
)
.register_action(
|workspace: &mut Workspace, _: &ShowStackTrace, window, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return;
};
if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx) {
let is_active = workspace
.active_item(cx)
.is_some_and(|item| item.item_id() == existing.item_id());
workspace.activate_item(&existing, true, !is_active, window, cx);
} else {
let Some(active_session) = debug_panel.read(cx).active_session() else {
return;
};
let project = workspace.project();
let stack_trace_view = active_session.update(cx, |session, cx| {
session.stack_trace_view(project, window, cx).clone()
});
workspace.add_item_to_active_pane(
Box::new(stack_trace_view),
None,
true,
window,
cx,
);
}
},
)
.register_action(|workspace: &mut Workspace, _: &Start, window, cx| { .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx); NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
}) })
@ -223,26 +84,180 @@ pub fn init(cx: &mut App) {
debug_panel.rerun_last_session(workspace, window, cx); debug_panel.rerun_last_session(workspace, window, cx);
}) })
}, },
)
.register_action(
|workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
workspace.project().update(cx, |project, cx| {
project.dap_store().update(cx, |store, cx| {
store.shutdown_sessions(cx).detach();
})
})
},
)
.register_action_renderer(|div, workspace, _, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return div;
};
let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
else {
return div;
};
let running_state = active_item.read(cx);
if running_state.session().read(cx).is_terminated() {
return div;
}
let caps = running_state.capabilities(cx);
let supports_restart = caps.supports_restart_request.unwrap_or_default();
let supports_step_back = caps.supports_step_back.unwrap_or_default();
let status = running_state.thread_status(cx);
let active_item = active_item.downgrade();
div.when(status == Some(ThreadStatus::Running), |div| {
let active_item = active_item.clone();
div.on_action(move |_: &Pause, _, cx| {
active_item
.update(cx, |item, cx| item.pause_thread(cx))
.ok();
})
})
.when(status == Some(ThreadStatus::Stopped), |div| {
div.on_action({
let active_item = active_item.clone();
move |_: &StepInto, _, cx| {
active_item.update(cx, |item, cx| item.step_in(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &StepOver, _, cx| {
active_item.update(cx, |item, cx| item.step_over(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &StepOut, _, cx| {
active_item.update(cx, |item, cx| item.step_out(cx)).ok();
}
})
.when(supports_step_back, |div| {
let active_item = active_item.clone();
div.on_action(move |_: &StepBack, _, cx| {
active_item.update(cx, |item, cx| item.step_back(cx)).ok();
})
})
.on_action({
let active_item = active_item.clone();
move |_: &Continue, _, cx| {
active_item
.update(cx, |item, cx| item.continue_thread(cx))
.ok();
}
})
.on_action(cx.listener(
|workspace, _: &ShowStackTrace, window, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return;
};
if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx)
{
let is_active = workspace
.active_item(cx)
.is_some_and(|item| item.item_id() == existing.item_id());
workspace
.activate_item(&existing, true, !is_active, window, cx);
} else {
let Some(active_session) =
debug_panel.read(cx).active_session()
else {
return;
};
let project = workspace.project();
let stack_trace_view =
active_session.update(cx, |session, cx| {
session.stack_trace_view(project, window, cx).clone()
});
workspace.add_item_to_active_pane(
Box::new(stack_trace_view),
None,
true,
window,
cx,
); );
}
},
))
})
.when(supports_restart, |div| {
let active_item = active_item.clone();
div.on_action(move |_: &Restart, _, cx| {
active_item
.update(cx, |item, cx| item.restart_session(cx))
.ok();
})
})
.on_action({
let active_item = active_item.clone();
move |_: &Stop, _, cx| {
active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &ToggleIgnoreBreakpoints, _, cx| {
active_item
.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
.ok();
}
})
});
}) })
}) })
.detach(); .detach();
cx.observe_new({ cx.observe_new({
move |editor: &mut Editor, _, cx| { move |editor: &mut Editor, _, _| {
editor editor
.register_action(cx.listener( .register_action_renderer(move |editor, window, cx| {
move |editor, _: &editor::actions::DebuggerRunToCursor, _, cx| { let Some(workspace) = editor.workspace() else {
return;
};
let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {
return;
};
let Some(active_session) = debug_panel
.clone()
.update(cx, |panel, _| panel.active_session())
else {
return;
};
let editor = cx.entity().downgrade();
window.on_action(TypeId::of::<editor::actions::RunToCursor>(), {
let editor = editor.clone();
let active_session = active_session.clone();
move |_, phase, _, cx| {
if phase != DispatchPhase::Bubble {
return;
}
maybe!({ maybe!({
let debug_panel =
editor.workspace()?.read(cx).panel::<DebugPanel>(cx)?;
let cursor_point: language::Point = editor.selections.newest(cx).head();
let active_session = debug_panel.read(cx).active_session()?;
let (buffer, position, _) = editor let (buffer, position, _) = editor
.update(cx, |editor, cx| {
let cursor_point: language::Point =
editor.selections.newest(cx).head();
editor
.buffer() .buffer()
.read(cx) .read(cx)
.point_to_buffer_point(cursor_point, cx)?; .point_to_buffer_point(cursor_point, cx)
})
.ok()??;
let path = let path =
debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer( debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
@ -274,31 +289,32 @@ pub fn init(cx: &mut App) {
Some(()) Some(())
}); });
}, }
)) });
.detach();
editor window.on_action(
.register_action(cx.listener( TypeId::of::<editor::actions::EvaluateSelectedText>(),
move |editor, _: &editor::actions::DebuggerEvaluateSelectedText, window, cx| { move |_, _, window, cx| {
maybe!({ maybe!({
let debug_panel = let text = editor
editor.workspace()?.read(cx).panel::<DebugPanel>(cx)?; .update(cx, |editor, cx| {
let active_session = debug_panel.read(cx).active_session()?; editor.text_for_range(
let text = editor.text_for_range(
editor.selections.newest(cx).range(), editor.selections.newest(cx).range(),
&mut None, &mut None,
window, window,
cx, cx,
)?; )
})
.ok()??;
active_session.update(cx, |session, cx| { active_session.update(cx, |session, cx| {
session.running_state().update(cx, |state, cx| { session.running_state().update(cx, |state, cx| {
let stack_id = state.selected_stack_frame_id(cx); let stack_id = state.selected_stack_frame_id(cx);
state.session().update(cx, |session, cx| { state.session().update(cx, |session, cx| {
session.evaluate(text, None, stack_id, None, cx).detach(); session
.evaluate(text, None, stack_id, None, cx)
.detach();
}); });
}); });
}); });
@ -306,7 +322,8 @@ pub fn init(cx: &mut App) {
Some(()) Some(())
}); });
}, },
)) );
})
.detach(); .detach();
} }
}) })

View file

@ -35,7 +35,6 @@ assets.workspace = true
client.workspace = true client.workspace = true
clock.workspace = true clock.workspace = true
collections.workspace = true collections.workspace = true
command_palette_hooks.workspace = true
convert_case.workspace = true convert_case.workspace = true
dap.workspace = true dap.workspace = true
db.workspace = true db.workspace = true

View file

@ -243,6 +243,8 @@ impl_actions!(
] ]
); );
actions!(debugger, [RunToCursor, EvaluateSelectedText]);
actions!( actions!(
editor, editor,
[ [
@ -426,8 +428,6 @@ actions!(
DisableBreakpoint, DisableBreakpoint,
EnableBreakpoint, EnableBreakpoint,
EditLogBreakpoint, EditLogBreakpoint,
DebuggerRunToCursor,
DebuggerEvaluateSelectedText,
ToggleAutoSignatureHelp, ToggleAutoSignatureHelp,
ToggleGitBlameInline, ToggleGitBlameInline,
OpenGitBlameCommit, OpenGitBlameCommit,

View file

@ -1054,8 +1054,9 @@ pub struct Editor {
style: Option<EditorStyle>, style: Option<EditorStyle>,
text_style_refinement: Option<TextStyleRefinement>, text_style_refinement: Option<TextStyleRefinement>,
next_editor_action_id: EditorActionId, next_editor_action_id: EditorActionId,
editor_actions: editor_actions: Rc<
Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>, RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
>,
use_autoclose: bool, use_autoclose: bool,
use_auto_surround: bool, use_auto_surround: bool,
auto_replace_emoji_shortcode: bool, auto_replace_emoji_shortcode: bool,
@ -7541,8 +7542,7 @@ impl Editor {
"Set Breakpoint" "Set Breakpoint"
}; };
let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx) let run_to_cursor = window.is_action_available(&RunToCursor, cx);
.map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state { let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
BreakpointState::Enabled => Some("Disable"), BreakpointState::Enabled => Some("Disable"),
@ -7566,7 +7566,7 @@ impl Editor {
}) })
.ok(); .ok();
window.dispatch_action(Box::new(DebuggerRunToCursor), cx); window.dispatch_action(Box::new(RunToCursor), cx);
}) })
.separator() .separator()
}) })
@ -19819,6 +19819,21 @@ impl Editor {
} }
} }
pub fn register_action_renderer(
&mut self,
listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
) -> Subscription {
let id = self.next_editor_action_id.post_inc();
self.editor_actions
.borrow_mut()
.insert(id, Box::new(listener));
let editor_actions = self.editor_actions.clone();
Subscription::new(move || {
editor_actions.borrow_mut().remove(&id);
})
}
pub fn register_action<A: Action>( pub fn register_action<A: Action>(
&mut self, &mut self,
listener: impl Fn(&A, &mut Window, &mut App) + 'static, listener: impl Fn(&A, &mut Window, &mut App) + 'static,
@ -19827,7 +19842,7 @@ impl Editor {
let listener = Arc::new(listener); let listener = Arc::new(listener);
self.editor_actions.borrow_mut().insert( self.editor_actions.borrow_mut().insert(
id, id,
Box::new(move |window, _| { Box::new(move |_, window, _| {
let listener = listener.clone(); let listener = listener.clone();
window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| { window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
let action = action.downcast_ref().unwrap(); let action = action.downcast_ref().unwrap();

View file

@ -187,7 +187,7 @@ impl EditorElement {
let editor = &self.editor; let editor = &self.editor;
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
for action in editor.editor_actions.borrow().values() { for action in editor.editor_actions.borrow().values() {
(action)(window, cx) (action)(editor, window, cx)
} }
}); });

View file

@ -1,8 +1,8 @@
use crate::{ use crate::{
Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DebuggerEvaluateSelectedText, DisplayPoint, Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor,
DisplaySnapshot, Editor, FindAllReferences, GoToDeclaration, GoToDefinition, EvaluateSelectedText, FindAllReferences, GoToDeclaration, GoToDefinition, GoToImplementation,
GoToImplementation, GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionExt,
SelectionExt, ToDisplayPoint, ToggleCodeActions, ToDisplayPoint, ToggleCodeActions,
actions::{Format, FormatSelections}, actions::{Format, FormatSelections},
selections_collection::SelectionsCollection, selections_collection::SelectionsCollection,
}; };
@ -199,17 +199,14 @@ pub fn deploy_context_menu(
.is_some() .is_some()
}); });
let evaluate_selection = command_palette_hooks::CommandPaletteFilter::try_global(cx) let evaluate_selection = window.is_action_available(&EvaluateSelectedText, cx);
.map_or(false, |filter| {
!filter.is_hidden(&DebuggerEvaluateSelectedText)
});
ui::ContextMenu::build(window, cx, |menu, _window, _cx| { ui::ContextMenu::build(window, cx, |menu, _window, _cx| {
let builder = menu let builder = menu
.on_blur_subscription(Subscription::new(|| {})) .on_blur_subscription(Subscription::new(|| {}))
.when(evaluate_selection && has_selections, |builder| { .when(evaluate_selection && has_selections, |builder| {
builder builder
.action("Evaluate Selection", Box::new(DebuggerEvaluateSelectedText)) .action("Evaluate Selection", Box::new(EvaluateSelectedText))
.separator() .separator()
}) })
.action("Go to Definition", Box::new(GoToDefinition)) .action("Go to Definition", Box::new(GoToDefinition))

View file

@ -922,7 +922,7 @@ type PromptForOpenPath = Box<
/// that can be used to register a global action to be triggered from any place in the window. /// that can be used to register a global action to be triggered from any place in the window.
pub struct Workspace { pub struct Workspace {
weak_self: WeakEntity<Self>, weak_self: WeakEntity<Self>,
workspace_actions: Vec<Box<dyn Fn(Div, &mut Window, &mut Context<Self>) -> Div>>, workspace_actions: Vec<Box<dyn Fn(Div, &Workspace, &mut Window, &mut Context<Self>) -> Div>>,
zoomed: Option<AnyWeakView>, zoomed: Option<AnyWeakView>,
previous_dock_drag_coordinates: Option<Point<Pixels>>, previous_dock_drag_coordinates: Option<Point<Pixels>>,
zoomed_position: Option<DockPosition>, zoomed_position: Option<DockPosition>,
@ -5436,7 +5436,7 @@ impl Workspace {
) -> &mut Self { ) -> &mut Self {
let callback = Arc::new(callback); let callback = Arc::new(callback);
self.workspace_actions.push(Box::new(move |div, _, cx| { self.workspace_actions.push(Box::new(move |div, _, _, cx| {
let callback = callback.clone(); let callback = callback.clone();
div.on_action(cx.listener(move |workspace, event, window, cx| { div.on_action(cx.listener(move |workspace, event, window, cx| {
(callback)(workspace, event, window, cx) (callback)(workspace, event, window, cx)
@ -5444,6 +5444,13 @@ impl Workspace {
})); }));
self self
} }
pub fn register_action_renderer(
&mut self,
callback: impl Fn(Div, &Workspace, &mut Window, &mut Context<Self>) -> Div + 'static,
) -> &mut Self {
self.workspace_actions.push(Box::new(callback));
self
}
fn add_workspace_actions_listeners( fn add_workspace_actions_listeners(
&self, &self,
@ -5452,7 +5459,7 @@ impl Workspace {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Div { ) -> Div {
for action in self.workspace_actions.iter() { for action in self.workspace_actions.iter() {
div = (action)(div, window, cx) div = (action)(div, self, window, cx)
} }
div div
} }

View file

@ -203,6 +203,30 @@ pub fn app_menus() -> Vec<Menu> {
MenuItem::action("Previous Problem", editor::actions::GoToPreviousDiagnostic), MenuItem::action("Previous Problem", editor::actions::GoToPreviousDiagnostic),
], ],
}, },
Menu {
name: "Run".into(),
items: vec![
MenuItem::action(
"Spawn Task",
zed_actions::Spawn::ViaModal {
reveal_target: None,
},
),
MenuItem::action("Start Debugger", debugger_ui::Start),
MenuItem::separator(),
MenuItem::action("Edit tasks.json...", crate::zed::OpenProjectTasks),
MenuItem::action("Edit debug.json...", crate::zed::OpenProjectDebugTasks),
MenuItem::separator(),
MenuItem::action("Continue", debugger_ui::Continue),
MenuItem::action("Step Over", debugger_ui::StepOver),
MenuItem::action("Step Into", debugger_ui::StepInto),
MenuItem::action("Step Out", debugger_ui::StepOut),
MenuItem::separator(),
MenuItem::action("Toggle Breakpoint", editor::actions::ToggleBreakpoint),
MenuItem::action("Edit Breakpoint", editor::actions::EditLogBreakpoint),
MenuItem::action("Clear all Breakpoints", debugger_ui::ClearAllBreakpoints),
],
},
Menu { Menu {
name: "Window".into(), name: "Window".into(),
items: vec![ items: vec![

View file

@ -133,46 +133,6 @@ impl Render for QuickActionBar {
) )
}); });
let last_run_debug = self
.workspace
.read_with(cx, |workspace, cx| {
workspace
.debugger_provider()
.map(|provider| provider.debug_scenario_scheduled_last(cx))
.unwrap_or_default()
})
.ok()
.unwrap_or_default();
let run_button = if last_run_debug {
QuickActionBarButton::new(
"debug",
IconName::PlayBug,
false,
Box::new(debugger_ui::Start),
focus_handle.clone(),
"Debug",
move |_, window, cx| {
window.dispatch_action(Box::new(debugger_ui::Start), cx);
},
)
} else {
let action = Box::new(tasks_ui::Spawn::ViaModal {
reveal_target: None,
});
QuickActionBarButton::new(
"run",
IconName::PlayAlt,
false,
action.boxed_clone(),
focus_handle.clone(),
"Spawn Task",
move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx);
},
)
};
let assistant_button = QuickActionBarButton::new( let assistant_button = QuickActionBarButton::new(
"toggle inline assistant", "toggle inline assistant",
IconName::ZedAssistant, IconName::ZedAssistant,
@ -601,7 +561,6 @@ impl Render for QuickActionBar {
AgentSettings::get_global(cx).enabled && AgentSettings::get_global(cx).button, AgentSettings::get_global(cx).enabled && AgentSettings::get_global(cx).button,
|bar| bar.child(assistant_button), |bar| bar.child(assistant_button),
) )
.child(run_button)
.children(code_actions_dropdown) .children(code_actions_dropdown)
.children(editor_selections_dropdown) .children(editor_selections_dropdown)
.child(editor_settings_dropdown) .child(editor_settings_dropdown)