Detailed changes
@@ -1019,5 +1019,12 @@
"bindings": {
"enter": "menu::Confirm"
}
+ },
+ {
+ "context": "RunModal",
+ "bindings": {
+ "ctrl-tab": "pane::ActivateNextItem",
+ "ctrl-shift-tab": "pane::ActivatePreviousItem"
+ }
}
]
@@ -1109,5 +1109,13 @@
"bindings": {
"enter": "menu::Confirm"
}
+ },
+ {
+ "context": "RunModal",
+ "use_key_equivalents": true,
+ "bindings": {
+ "ctrl-tab": "pane::ActivateNextItem",
+ "ctrl-shift-tab": "pane::ActivatePreviousItem"
+ }
}
]
@@ -5,7 +5,7 @@ use crate::{
ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart,
ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
- ToggleSessionPicker, ToggleThreadPicker, persistence,
+ ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
};
use anyhow::{Context as _, Result, anyhow};
use command_palette_hooks::CommandPaletteFilter;
@@ -65,6 +65,7 @@ pub struct DebugPanel {
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
+ debug_scenario_scheduled_last: bool,
pub(crate) thread_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
pub(crate) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
fs: Arc<dyn Fs>,
@@ -103,6 +104,7 @@ impl DebugPanel {
thread_picker_menu_handle,
session_picker_menu_handle,
_subscriptions: [focus_subscription],
+ debug_scenario_scheduled_last: true,
}
})
}
@@ -264,6 +266,7 @@ impl DebugPanel {
cx,
)
});
+ self.debug_scenario_scheduled_last = true;
if let Some(inventory) = self
.project
.read(cx)
@@ -1381,4 +1384,30 @@ impl workspace::DebuggerProvider for DebuggerProvider {
})
})
}
+
+ fn spawn_task_or_modal(
+ &self,
+ workspace: &mut Workspace,
+ action: &tasks_ui::Spawn,
+ window: &mut Window,
+ cx: &mut Context<Workspace>,
+ ) {
+ spawn_task_or_modal(workspace, action, window, cx);
+ }
+
+ fn debug_scenario_scheduled(&self, cx: &mut App) {
+ self.0.update(cx, |this, _| {
+ this.debug_scenario_scheduled_last = true;
+ });
+ }
+
+ fn task_scheduled(&self, cx: &mut App) {
+ self.0.update(cx, |this, _| {
+ this.debug_scenario_scheduled_last = false;
+ })
+ }
+
+ fn debug_scenario_scheduled_last(&self, cx: &App) -> bool {
+ self.0.read(cx).debug_scenario_scheduled_last
+ }
}
@@ -3,11 +3,12 @@ use debugger_panel::{DebugPanel, ToggleFocus};
use editor::Editor;
use feature_flags::{DebuggerFeatureFlag, FeatureFlagViewExt};
use gpui::{App, EntityInputHandler, actions};
-use new_session_modal::NewSessionModal;
+use new_session_modal::{NewSessionModal, NewSessionMode};
use project::debugger::{self, breakpoint_store::SourceBreakpoint};
use session::DebugSession;
use settings::Settings;
use stack_trace_view::StackTraceView;
+use tasks_ui::{Spawn, TaskOverrides};
use util::maybe;
use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
@@ -62,6 +63,7 @@ pub fn init(cx: &mut App) {
cx.when_flag_enabled::<DebuggerFeatureFlag>(window, |workspace, _, _| {
workspace
+ .register_action(spawn_task_or_modal)
.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<DebugPanel>(window, cx);
})
@@ -208,7 +210,7 @@ pub fn init(cx: &mut App) {
},
)
.register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
- NewSessionModal::show(workspace, window, cx);
+ NewSessionModal::show(workspace, window, NewSessionMode::Launch, None, cx);
})
.register_action(
|workspace: &mut Workspace, _: &RerunLastSession, window, cx| {
@@ -309,3 +311,48 @@ pub fn init(cx: &mut App) {
})
.detach();
}
+
+fn spawn_task_or_modal(
+ workspace: &mut Workspace,
+ action: &Spawn,
+ window: &mut ui::Window,
+ cx: &mut ui::Context<Workspace>,
+) {
+ match action {
+ Spawn::ByName {
+ task_name,
+ reveal_target,
+ } => {
+ let overrides = reveal_target.map(|reveal_target| TaskOverrides {
+ reveal_target: Some(reveal_target),
+ });
+ let name = task_name.clone();
+ tasks_ui::spawn_tasks_filtered(
+ move |(_, task)| task.label.eq(&name),
+ overrides,
+ window,
+ cx,
+ )
+ .detach_and_log_err(cx)
+ }
+ Spawn::ByTag {
+ task_tag,
+ reveal_target,
+ } => {
+ let overrides = reveal_target.map(|reveal_target| TaskOverrides {
+ reveal_target: Some(reveal_target),
+ });
+ let tag = task_tag.clone();
+ tasks_ui::spawn_tasks_filtered(
+ move |(_, task)| task.tags.contains(&tag),
+ overrides,
+ window,
+ cx,
+ )
+ .detach_and_log_err(cx)
+ }
+ Spawn::ViaModal { reveal_target } => {
+ NewSessionModal::show(workspace, window, NewSessionMode::Task, *reveal_target, cx);
+ }
+ }
+}
@@ -8,6 +8,7 @@ use std::{
time::Duration,
usize,
};
+use tasks_ui::{TaskOverrides, TasksModal};
use dap::{
DapRegistry, DebugRequest, TelemetrySpawnLocation, adapters::DebugAdapterName, send_telemetry,
@@ -16,12 +17,12 @@ use editor::{Anchor, Editor, EditorElement, EditorStyle, scroll::Autoscroll};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
Animation, AnimationExt as _, App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle,
- Focusable, Render, Subscription, TextStyle, Transformation, WeakEntity, percentage,
+ Focusable, KeyContext, Render, Subscription, TextStyle, Transformation, WeakEntity, percentage,
};
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
use project::{ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore};
use settings::Settings;
-use task::{DebugScenario, LaunchRequest, ZedDebugConfig};
+use task::{DebugScenario, LaunchRequest, RevealTarget, ZedDebugConfig};
use theme::ThemeSettings;
use ui::{
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
@@ -47,10 +48,11 @@ pub(super) struct NewSessionModal {
mode: NewSessionMode,
launch_picker: Entity<Picker<DebugScenarioDelegate>>,
attach_mode: Entity<AttachMode>,
- custom_mode: Entity<CustomMode>,
+ configure_mode: Entity<ConfigureMode>,
+ task_mode: TaskMode,
debugger: Option<DebugAdapterName>,
save_scenario_state: Option<SaveScenarioState>,
- _subscriptions: [Subscription; 2],
+ _subscriptions: [Subscription; 3],
}
fn suggested_label(request: &DebugRequest, debugger: &str) -> SharedString {
@@ -75,6 +77,8 @@ impl NewSessionModal {
pub(super) fn show(
workspace: &mut Workspace,
window: &mut Window,
+ mode: NewSessionMode,
+ reveal_target: Option<RevealTarget>,
cx: &mut Context<Workspace>,
) {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
@@ -84,20 +88,50 @@ impl NewSessionModal {
let languages = workspace.app_state().languages.clone();
cx.spawn_in(window, async move |workspace, cx| {
+ let task_contexts = workspace
+ .update_in(cx, |workspace, window, cx| {
+ tasks_ui::task_contexts(workspace, window, cx)
+ })?
+ .await;
+ let task_contexts = Arc::new(task_contexts);
workspace.update_in(cx, |workspace, window, cx| {
let workspace_handle = workspace.weak_handle();
workspace.toggle_modal(window, cx, |window, cx| {
let attach_mode = AttachMode::new(None, workspace_handle.clone(), window, cx);
let launch_picker = cx.new(|cx| {
- Picker::uniform_list(
- DebugScenarioDelegate::new(debug_panel.downgrade(), task_store),
- window,
- cx,
- )
- .modal(false)
+ let mut delegate =
+ DebugScenarioDelegate::new(debug_panel.downgrade(), task_store.clone());
+ delegate.task_contexts_loaded(task_contexts.clone(), languages, window, cx);
+ Picker::uniform_list(delegate, window, cx).modal(false)
});
+ let configure_mode = ConfigureMode::new(None, window, cx);
+ if let Some(active_cwd) = task_contexts
+ .active_context()
+ .and_then(|context| context.cwd.clone())
+ {
+ configure_mode.update(cx, |configure_mode, cx| {
+ configure_mode.load(active_cwd, window, cx);
+ });
+ }
+
+ let task_overrides = Some(TaskOverrides { reveal_target });
+
+ let task_mode = TaskMode {
+ task_modal: cx.new(|cx| {
+ TasksModal::new(
+ task_store.clone(),
+ task_contexts,
+ task_overrides,
+ false,
+ workspace_handle.clone(),
+ window,
+ cx,
+ )
+ }),
+ };
+
let _subscriptions = [
cx.subscribe(&launch_picker, |_, _, _, cx| {
cx.emit(DismissEvent);
@@ -108,52 +142,18 @@ impl NewSessionModal {
cx.emit(DismissEvent);
},
),
+ cx.subscribe(&task_mode.task_modal, |_, _, _: &DismissEvent, cx| {
+ cx.emit(DismissEvent)
+ }),
];
- let custom_mode = CustomMode::new(None, window, cx);
-
- cx.spawn_in(window, {
- let workspace_handle = workspace_handle.clone();
- async move |this, cx| {
- let task_contexts = workspace_handle
- .update_in(cx, |workspace, window, cx| {
- tasks_ui::task_contexts(workspace, window, cx)
- })?
- .await;
-
- this.update_in(cx, |this, window, cx| {
- if let Some(active_cwd) = task_contexts
- .active_context()
- .and_then(|context| context.cwd.clone())
- {
- this.custom_mode.update(cx, |custom, cx| {
- custom.load(active_cwd, window, cx);
- });
-
- this.debugger = None;
- }
-
- this.launch_picker.update(cx, |picker, cx| {
- picker.delegate.task_contexts_loaded(
- task_contexts,
- languages,
- window,
- cx,
- );
- picker.refresh(window, cx);
- cx.notify();
- });
- })
- }
- })
- .detach();
-
Self {
launch_picker,
attach_mode,
- custom_mode,
+ configure_mode,
+ task_mode,
debugger: None,
- mode: NewSessionMode::Launch,
+ mode,
debug_panel: debug_panel.downgrade(),
workspace: workspace_handle,
save_scenario_state: None,
@@ -170,10 +170,17 @@ impl NewSessionModal {
fn render_mode(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
let dap_menu = self.adapter_drop_down_menu(window, cx);
match self.mode {
+ NewSessionMode::Task => self
+ .task_mode
+ .task_modal
+ .read(cx)
+ .picker
+ .clone()
+ .into_any_element(),
NewSessionMode::Attach => self.attach_mode.update(cx, |this, cx| {
this.clone().render(window, cx).into_any_element()
}),
- NewSessionMode::Custom => self.custom_mode.update(cx, |this, cx| {
+ NewSessionMode::Configure => self.configure_mode.update(cx, |this, cx| {
this.clone().render(dap_menu, window, cx).into_any_element()
}),
NewSessionMode::Launch => v_flex()
@@ -185,16 +192,17 @@ impl NewSessionModal {
fn mode_focus_handle(&self, cx: &App) -> FocusHandle {
match self.mode {
+ NewSessionMode::Task => self.task_mode.task_modal.focus_handle(cx),
NewSessionMode::Attach => self.attach_mode.read(cx).attach_picker.focus_handle(cx),
- NewSessionMode::Custom => self.custom_mode.read(cx).program.focus_handle(cx),
+ NewSessionMode::Configure => self.configure_mode.read(cx).program.focus_handle(cx),
NewSessionMode::Launch => self.launch_picker.focus_handle(cx),
}
}
fn debug_scenario(&self, debugger: &str, cx: &App) -> Option<DebugScenario> {
let request = match self.mode {
- NewSessionMode::Custom => Some(DebugRequest::Launch(
- self.custom_mode.read(cx).debug_request(cx),
+ NewSessionMode::Configure => Some(DebugRequest::Launch(
+ self.configure_mode.read(cx).debug_request(cx),
)),
NewSessionMode::Attach => Some(DebugRequest::Attach(
self.attach_mode.read(cx).debug_request(),
@@ -203,8 +211,8 @@ impl NewSessionModal {
}?;
let label = suggested_label(&request, debugger);
- let stop_on_entry = if let NewSessionMode::Custom = &self.mode {
- Some(self.custom_mode.read(cx).stop_on_entry.selected())
+ let stop_on_entry = if let NewSessionMode::Configure = &self.mode {
+ Some(self.configure_mode.read(cx).stop_on_entry.selected())
} else {
None
};
@@ -527,7 +535,8 @@ static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select De
#[derive(Clone)]
pub(crate) enum NewSessionMode {
- Custom,
+ Task,
+ Configure,
Attach,
Launch,
}
@@ -535,9 +544,10 @@ pub(crate) enum NewSessionMode {
impl std::fmt::Display for NewSessionMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mode = match self {
- NewSessionMode::Launch => "Launch".to_owned(),
- NewSessionMode::Attach => "Attach".to_owned(),
- NewSessionMode::Custom => "Custom".to_owned(),
+ NewSessionMode::Task => "Run",
+ NewSessionMode::Launch => "Debug",
+ NewSessionMode::Attach => "Attach",
+ NewSessionMode::Configure => "Configure Debugger",
};
write!(f, "{}", mode)
@@ -597,36 +607,39 @@ impl Render for NewSessionModal {
v_flex()
.size_full()
.w(rems(34.))
- .key_context("Pane")
+ .key_context({
+ let mut key_context = KeyContext::new_with_defaults();
+ key_context.add("Pane");
+ key_context.add("RunModal");
+ key_context
+ })
.elevation_3(cx)
.bg(cx.theme().colors().elevated_surface_background)
.on_action(cx.listener(|_, _: &menu::Cancel, _, cx| {
cx.emit(DismissEvent);
}))
+ .on_action(cx.listener(|this, _: &pane::ActivateNextItem, window, cx| {
+ this.mode = match this.mode {
+ NewSessionMode::Task => NewSessionMode::Launch,
+ NewSessionMode::Launch => NewSessionMode::Attach,
+ NewSessionMode::Attach => NewSessionMode::Configure,
+ NewSessionMode::Configure => NewSessionMode::Task,
+ };
+
+ this.mode_focus_handle(cx).focus(window);
+ }))
.on_action(
cx.listener(|this, _: &pane::ActivatePreviousItem, window, cx| {
this.mode = match this.mode {
+ NewSessionMode::Task => NewSessionMode::Configure,
+ NewSessionMode::Launch => NewSessionMode::Task,
NewSessionMode::Attach => NewSessionMode::Launch,
- NewSessionMode::Launch => NewSessionMode::Attach,
- _ => {
- return;
- }
+ NewSessionMode::Configure => NewSessionMode::Attach,
};
this.mode_focus_handle(cx).focus(window);
}),
)
- .on_action(cx.listener(|this, _: &pane::ActivateNextItem, window, cx| {
- this.mode = match this.mode {
- NewSessionMode::Attach => NewSessionMode::Launch,
- NewSessionMode::Launch => NewSessionMode::Attach,
- _ => {
- return;
- }
- };
-
- this.mode_focus_handle(cx).focus(window);
- }))
.child(
h_flex()
.w_full()
@@ -637,37 +650,73 @@ impl Render for NewSessionModal {
.justify_start()
.w_full()
.child(
- ToggleButton::new("debugger-session-ui-picker-button", "Launch")
- .size(ButtonSize::Default)
- .style(ui::ButtonStyle::Subtle)
- .toggle_state(matches!(self.mode, NewSessionMode::Launch))
- .on_click(cx.listener(|this, _, window, cx| {
- this.mode = NewSessionMode::Launch;
- this.mode_focus_handle(cx).focus(window);
- cx.notify();
- }))
- .first(),
+ ToggleButton::new(
+ "debugger-session-ui-tasks-button",
+ NewSessionMode::Task.to_string(),
+ )
+ .size(ButtonSize::Default)
+ .toggle_state(matches!(self.mode, NewSessionMode::Task))
+ .style(ui::ButtonStyle::Subtle)
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.mode = NewSessionMode::Task;
+ this.mode_focus_handle(cx).focus(window);
+ cx.notify();
+ }))
+ .first(),
)
.child(
- ToggleButton::new("debugger-session-ui-attach-button", "Attach")
- .size(ButtonSize::Default)
- .toggle_state(matches!(self.mode, NewSessionMode::Attach))
- .style(ui::ButtonStyle::Subtle)
- .on_click(cx.listener(|this, _, window, cx| {
- this.mode = NewSessionMode::Attach;
-
- if let Some(debugger) = this.debugger.as_ref() {
- Self::update_attach_picker(
- &this.attach_mode,
- &debugger,
- window,
- cx,
- );
- }
- this.mode_focus_handle(cx).focus(window);
- cx.notify();
- }))
- .last(),
+ ToggleButton::new(
+ "debugger-session-ui-launch-button",
+ NewSessionMode::Launch.to_string(),
+ )
+ .size(ButtonSize::Default)
+ .style(ui::ButtonStyle::Subtle)
+ .toggle_state(matches!(self.mode, NewSessionMode::Launch))
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.mode = NewSessionMode::Launch;
+ this.mode_focus_handle(cx).focus(window);
+ cx.notify();
+ }))
+ .middle(),
+ )
+ .child(
+ ToggleButton::new(
+ "debugger-session-ui-attach-button",
+ NewSessionMode::Attach.to_string(),
+ )
+ .size(ButtonSize::Default)
+ .toggle_state(matches!(self.mode, NewSessionMode::Attach))
+ .style(ui::ButtonStyle::Subtle)
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.mode = NewSessionMode::Attach;
+
+ if let Some(debugger) = this.debugger.as_ref() {
+ Self::update_attach_picker(
+ &this.attach_mode,
+ &debugger,
+ window,
+ cx,
+ );
+ }
+ this.mode_focus_handle(cx).focus(window);
+ cx.notify();
+ }))
+ .middle(),
+ )
+ .child(
+ ToggleButton::new(
+ "debugger-session-ui-custom-button",
+ NewSessionMode::Configure.to_string(),
+ )
+ .size(ButtonSize::Default)
+ .toggle_state(matches!(self.mode, NewSessionMode::Configure))
+ .style(ui::ButtonStyle::Subtle)
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.mode = NewSessionMode::Configure;
+ this.mode_focus_handle(cx).focus(window);
+ cx.notify();
+ }))
+ .last(),
),
)
.justify_between()
@@ -675,83 +724,83 @@ impl Render for NewSessionModal {
.border_b_1(),
)
.child(v_flex().child(self.render_mode(window, cx)))
- .child(
- h_flex()
+ .map(|el| {
+ let container = h_flex()
.justify_between()
.gap_2()
.p_2()
.border_color(cx.theme().colors().border_variant)
.border_t_1()
- .w_full()
- .child(match self.mode {
- NewSessionMode::Attach => {
- div().child(self.adapter_drop_down_menu(window, cx))
- }
- NewSessionMode::Launch => div().child(
- Button::new("new-session-modal-custom", "Custom").on_click({
- let this = cx.weak_entity();
- move |_, window, cx| {
- this.update(cx, |this, cx| {
- this.mode = NewSessionMode::Custom;
- this.mode_focus_handle(cx).focus(window);
- })
- .ok();
- }
- }),
- ),
- NewSessionMode::Custom => h_flex()
+ .w_full();
+ match self.mode {
+ NewSessionMode::Configure => el.child(
+ container
+ .child(
+ h_flex()
+ .child(
+ Button::new(
+ "new-session-modal-back",
+ "Save to .zed/debug.json...",
+ )
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.save_debug_scenario(window, cx);
+ }))
+ .disabled(
+ self.debugger.is_none()
+ || self
+ .configure_mode
+ .read(cx)
+ .program
+ .read(cx)
+ .is_empty(cx)
+ || self.save_scenario_state.is_some(),
+ ),
+ )
+ .child(self.render_save_state(cx)),
+ )
.child(
- Button::new("new-session-modal-back", "Save to .zed/debug.json...")
+ Button::new("debugger-spawn", "Start")
.on_click(cx.listener(|this, _, window, cx| {
- this.save_debug_scenario(window, cx);
+ this.start_new_session(window, cx)
}))
.disabled(
self.debugger.is_none()
|| self
- .custom_mode
+ .configure_mode
.read(cx)
.program
.read(cx)
- .is_empty(cx)
- || self.save_scenario_state.is_some(),
+ .is_empty(cx),
),
- )
- .child(self.render_save_state(cx)),
- })
- .child(
- Button::new("debugger-spawn", "Start")
- .on_click(cx.listener(|this, _, window, cx| match &this.mode {
- NewSessionMode::Launch => {
- this.launch_picker.update(cx, |picker, cx| {
- picker.delegate.confirm(true, window, cx)
- })
- }
- _ => this.start_new_session(window, cx),
- }))
- .disabled(match self.mode {
- NewSessionMode::Launch => {
- !self.launch_picker.read(cx).delegate.matches.is_empty()
- }
- NewSessionMode::Attach => {
- self.debugger.is_none()
- || self
- .attach_mode
- .read(cx)
- .attach_picker
- .read(cx)
- .picker
- .read(cx)
- .delegate
- .match_count()
- == 0
- }
- NewSessionMode::Custom => {
- self.debugger.is_none()
- || self.custom_mode.read(cx).program.read(cx).is_empty(cx)
- }
- }),
+ ),
),
- )
+ NewSessionMode::Attach => el.child(
+ container
+ .child(div().child(self.adapter_drop_down_menu(window, cx)))
+ .child(
+ Button::new("debugger-spawn", "Start")
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.start_new_session(window, cx)
+ }))
+ .disabled(
+ self.debugger.is_none()
+ || self
+ .attach_mode
+ .read(cx)
+ .attach_picker
+ .read(cx)
+ .picker
+ .read(cx)
+ .delegate
+ .match_count()
+ == 0,
+ ),
+ ),
+ ),
+ NewSessionMode::Launch => el,
+ NewSessionMode::Task => el,
+ }
+ })
}
}
@@ -774,13 +823,13 @@ impl RenderOnce for AttachMode {
}
#[derive(Clone)]
-pub(super) struct CustomMode {
+pub(super) struct ConfigureMode {
program: Entity<Editor>,
cwd: Entity<Editor>,
stop_on_entry: ToggleState,
}
-impl CustomMode {
+impl ConfigureMode {
pub(super) fn new(
past_launch_config: Option<LaunchRequest>,
window: &mut Window,
@@ -940,6 +989,11 @@ impl AttachMode {
}
}
+#[derive(Clone)]
+pub(super) struct TaskMode {
+ pub(super) task_modal: Entity<TasksModal>,
+}
+
pub(super) struct DebugScenarioDelegate {
task_store: Entity<TaskStore>,
candidates: Vec<(Option<TaskSourceKind>, DebugScenario)>,
@@ -995,12 +1049,12 @@ impl DebugScenarioDelegate {
pub fn task_contexts_loaded(
&mut self,
- task_contexts: TaskContexts,
+ task_contexts: Arc<TaskContexts>,
languages: Arc<LanguageRegistry>,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) {
- self.task_contexts = Some(Arc::new(task_contexts));
+ self.task_contexts = Some(task_contexts);
let (recent, scenarios) = self
.task_store
@@ -1206,7 +1260,7 @@ pub(crate) fn resolve_path(path: &mut String) {
#[cfg(test)]
impl NewSessionModal {
- pub(crate) fn set_custom(
+ pub(crate) fn set_configure(
&mut self,
program: impl AsRef<str>,
cwd: impl AsRef<str>,
@@ -1214,21 +1268,21 @@ impl NewSessionModal {
window: &mut Window,
cx: &mut Context<Self>,
) {
- self.mode = NewSessionMode::Custom;
+ self.mode = NewSessionMode::Configure;
self.debugger = Some(dap::adapters::DebugAdapterName("fake-adapter".into()));
- self.custom_mode.update(cx, |custom, cx| {
- custom.program.update(cx, |editor, cx| {
+ self.configure_mode.update(cx, |configure, cx| {
+ configure.program.update(cx, |editor, cx| {
editor.clear(window, cx);
editor.set_text(program.as_ref(), window, cx);
});
- custom.cwd.update(cx, |editor, cx| {
+ configure.cwd.update(cx, |editor, cx| {
editor.clear(window, cx);
editor.set_text(cwd.as_ref(), window, cx);
});
- custom.stop_on_entry = match stop_on_entry {
+ configure.stop_on_entry = match stop_on_entry {
true => ToggleState::Selected,
_ => ToggleState::Unselected,
}
@@ -1239,28 +1293,3 @@ impl NewSessionModal {
self.save_debug_scenario(window, cx);
}
}
-
-#[cfg(test)]
-mod tests {
- use paths::home_dir;
-
- #[test]
- fn test_normalize_paths() {
- let sep = std::path::MAIN_SEPARATOR;
- let home = home_dir().to_string_lossy().to_string();
- let resolve_path = |path: &str| -> String {
- let mut path = path.to_string();
- super::resolve_path(&mut path);
- path
- };
-
- assert_eq!(resolve_path("bin"), format!("bin"));
- assert_eq!(resolve_path(&format!("{sep}foo")), format!("{sep}foo"));
- assert_eq!(resolve_path(""), format!(""));
- assert_eq!(
- resolve_path(&format!("~{sep}blah")),
- format!("{home}{sep}blah")
- );
- assert_eq!(resolve_path("~"), home);
- }
-}
@@ -7,6 +7,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use task::{DebugRequest, DebugScenario, LaunchRequest, TaskContext, VariableName, ZedDebugConfig};
use util::path;
+use crate::new_session_modal::NewSessionMode;
use crate::tests::{init_test, init_test_workspace};
#[gpui::test]
@@ -170,7 +171,13 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut
workspace
.update(cx, |workspace, window, cx| {
- crate::new_session_modal::NewSessionModal::show(workspace, window, cx);
+ crate::new_session_modal::NewSessionModal::show(
+ workspace,
+ window,
+ NewSessionMode::Launch,
+ None,
+ cx,
+ );
})
.unwrap();
@@ -184,7 +191,7 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut
.expect("Modal should be active");
modal.update_in(cx, |modal, window, cx| {
- modal.set_custom("/project/main", "/project", false, window, cx);
+ modal.set_configure("/project/main", "/project", false, window, cx);
modal.save_scenario(window, cx);
});
@@ -213,7 +220,7 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut
pretty_assertions::assert_eq!(expected_content, actual_lines);
modal.update_in(cx, |modal, window, cx| {
- modal.set_custom("/project/other", "/project", true, window, cx);
+ modal.set_configure("/project/other", "/project", true, window, cx);
modal.save_scenario(window, cx);
});
@@ -27,7 +27,7 @@
///
/// The keybindings themselves are managed independently by calling cx.bind_keys().
/// (Though mostly when developing Zed itself, you just need to add a new line to
-/// assets/keymaps/default.json).
+/// assets/keymaps/default-{platform}.json).
///
/// ```rust
/// cx.bind_keys([
@@ -23,7 +23,7 @@ use workspace::{ModalView, Workspace};
pub use zed_actions::{Rerun, Spawn};
/// A modal used to spawn new tasks.
-pub(crate) struct TasksModalDelegate {
+pub struct TasksModalDelegate {
task_store: Entity<TaskStore>,
candidates: Option<Vec<(TaskSourceKind, ResolvedTask)>>,
task_overrides: Option<TaskOverrides>,
@@ -33,21 +33,21 @@ pub(crate) struct TasksModalDelegate {
selected_index: usize,
workspace: WeakEntity<Workspace>,
prompt: String,
- task_contexts: TaskContexts,
+ task_contexts: Arc<TaskContexts>,
placeholder_text: Arc<str>,
}
/// Task template amendments to do before resolving the context.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub(crate) struct TaskOverrides {
+pub struct TaskOverrides {
/// See [`RevealTarget`].
- pub(crate) reveal_target: Option<RevealTarget>,
+ pub reveal_target: Option<RevealTarget>,
}
impl TasksModalDelegate {
fn new(
task_store: Entity<TaskStore>,
- task_contexts: TaskContexts,
+ task_contexts: Arc<TaskContexts>,
task_overrides: Option<TaskOverrides>,
workspace: WeakEntity<Workspace>,
) -> Self {
@@ -123,15 +123,16 @@ impl TasksModalDelegate {
}
pub struct TasksModal {
- picker: Entity<Picker<TasksModalDelegate>>,
+ pub picker: Entity<Picker<TasksModalDelegate>>,
_subscription: [Subscription; 2],
}
impl TasksModal {
- pub(crate) fn new(
+ pub fn new(
task_store: Entity<TaskStore>,
- task_contexts: TaskContexts,
+ task_contexts: Arc<TaskContexts>,
task_overrides: Option<TaskOverrides>,
+ is_modal: bool,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -142,6 +143,7 @@ impl TasksModal {
window,
cx,
)
+ .modal(is_modal)
});
let _subscription = [
cx.subscribe(&picker, |_, _, _: &DismissEvent, cx| {
@@ -158,6 +160,20 @@ impl TasksModal {
_subscription,
}
}
+
+ pub fn task_contexts_loaded(
+ &mut self,
+ task_contexts: Arc<TaskContexts>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ self.picker.update(cx, |picker, cx| {
+ picker.delegate.task_contexts = task_contexts;
+ picker.delegate.candidates = None;
+ picker.refresh(window, cx);
+ cx.notify();
+ })
+ }
}
impl Render for TasksModal {
@@ -568,6 +584,7 @@ impl PickerDelegate for TasksModalDelegate {
Vec::new()
}
}
+
fn render_footer(
&self,
window: &mut Window,
@@ -1,16 +1,15 @@
-use std::path::Path;
+use std::{path::Path, sync::Arc};
use collections::HashMap;
use editor::Editor;
use gpui::{App, AppContext as _, Context, Entity, Task, Window};
-use modal::TaskOverrides;
use project::{Location, TaskContexts, TaskSourceKind, Worktree};
use task::{RevealTarget, TaskContext, TaskId, TaskTemplate, TaskVariables, VariableName};
use workspace::Workspace;
mod modal;
-pub use modal::{Rerun, ShowAttachModal, Spawn, TasksModal};
+pub use modal::{Rerun, ShowAttachModal, Spawn, TaskOverrides, TasksModal};
pub fn init(cx: &mut App) {
cx.observe_new(
@@ -95,6 +94,11 @@ fn spawn_task_or_modal(
window: &mut Window,
cx: &mut Context<Workspace>,
) {
+ if let Some(provider) = workspace.debugger_provider() {
+ provider.spawn_task_or_modal(workspace, action, window, cx);
+ return;
+ }
+
match action {
Spawn::ByName {
task_name,
@@ -143,7 +147,7 @@ pub fn toggle_modal(
if can_open_modal {
let task_contexts = task_contexts(workspace, window, cx);
cx.spawn_in(window, async move |workspace, cx| {
- let task_contexts = task_contexts.await;
+ let task_contexts = Arc::new(task_contexts.await);
workspace
.update_in(cx, |workspace, window, cx| {
workspace.toggle_modal(window, cx, |window, cx| {
@@ -153,6 +157,7 @@ pub fn toggle_modal(
reveal_target.map(|target| TaskOverrides {
reveal_target: Some(target),
}),
+ true,
workspace_handle,
window,
cx,
@@ -166,7 +171,7 @@ pub fn toggle_modal(
}
}
-fn spawn_tasks_filtered<F>(
+pub fn spawn_tasks_filtered<F>(
mut predicate: F,
overrides: Option<TaskOverrides>,
window: &mut Window,
@@ -56,6 +56,10 @@ impl Workspace {
) {
let spawn_in_terminal = resolved_task.resolved.clone();
if !omit_history {
+ if let Some(debugger_provider) = self.debugger_provider.as_ref() {
+ debugger_provider.task_scheduled(cx);
+ }
+
self.project().update(cx, |project, cx| {
if let Some(task_inventory) =
project.task_store().read(cx).task_inventory().cloned()
@@ -100,13 +100,13 @@ use task::{DebugScenario, SpawnInTerminal, TaskContext};
use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub use ui;
-use ui::prelude::*;
+use ui::{Window, prelude::*};
use util::{ResultExt, TryFutureExt, paths::SanitizedPath, serde::default_true};
use uuid::Uuid;
pub use workspace_settings::{
AutosaveSetting, BottomDockLayout, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings,
};
-use zed_actions::feedback::FileBugReport;
+use zed_actions::{Spawn, feedback::FileBugReport};
use crate::notifications::NotificationId;
use crate::persistence::{
@@ -149,6 +149,18 @@ pub trait DebuggerProvider {
window: &mut Window,
cx: &mut App,
);
+
+ fn spawn_task_or_modal(
+ &self,
+ workspace: &mut Workspace,
+ action: &Spawn,
+ window: &mut Window,
+ cx: &mut Context<Workspace>,
+ );
+
+ fn task_scheduled(&self, cx: &mut App);
+ fn debug_scenario_scheduled(&self, cx: &mut App);
+ fn debug_scenario_scheduled_last(&self, cx: &App) -> bool;
}
actions!(
@@ -947,7 +959,7 @@ pub struct Workspace {
on_prompt_for_new_path: Option<PromptForNewPath>,
on_prompt_for_open_path: Option<PromptForOpenPath>,
terminal_provider: Option<Box<dyn TerminalProvider>>,
- debugger_provider: Option<Box<dyn DebuggerProvider>>,
+ debugger_provider: Option<Arc<dyn DebuggerProvider>>,
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
serialized_ssh_project: Option<SerializedSshProject>,
_items_serializer: Task<Result<()>>,
@@ -1828,7 +1840,11 @@ impl Workspace {
}
pub fn set_debugger_provider(&mut self, provider: impl DebuggerProvider + 'static) {
- self.debugger_provider = Some(Box::new(provider));
+ self.debugger_provider = Some(Arc::new(provider));
+ }
+
+ pub fn debugger_provider(&self) -> Option<Arc<dyn DebuggerProvider>> {
+ self.debugger_provider.clone()
}
pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
@@ -133,6 +133,46 @@ 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::Debug, // TODO: use debug + play icon
+ 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::Play,
+ false,
+ action.boxed_clone(),
+ focus_handle.clone(),
+ "Spawn Task",
+ move |_, window, cx| {
+ window.dispatch_action(action.boxed_clone(), cx);
+ },
+ )
+ };
+
let assistant_button = QuickActionBarButton::new(
"toggle inline assistant",
IconName::ZedAssistant,
@@ -561,6 +601,7 @@ impl Render for QuickActionBar {
AgentSettings::get_global(cx).enabled && AgentSettings::get_global(cx).button,
|bar| bar.child(assistant_button),
)
+ .child(run_button)
.children(code_actions_dropdown)
.children(editor_selections_dropdown)
.child(editor_settings_dropdown)