Simplify debug launcher UI (#31928)

Mikayla Maki created

This PR updates the name of the `NewSessionModal` to `NewProcessModal`
(to reflect it's new purpose), changes the tabs in the modal to read
`Run | Debug | Attach | Launch` and changes the associated types in code
to match the tabs. In addition, this PR adds a few labels to the text
fields in the `Launch` tab, and adds a link to open the associated
settings file. In both debug.json files, added links to the zed.dev
debugger docs.

Release Notes:

- Debugger Beta: Improve the new process modal

Change summary

assets/settings/initial_debug_tasks.json          |   4 
assets/settings/initial_local_debug_tasks.json    |   5 
crates/debugger_ui/Cargo.toml                     |   1 
crates/debugger_ui/src/debugger_panel.rs          | 129 +-
crates/debugger_ui/src/debugger_ui.rs             |   8 
crates/debugger_ui/src/new_process_modal.rs       | 655 ++++++++--------
crates/debugger_ui/src/session/running.rs         |  10 
crates/debugger_ui/src/tests.rs                   |   2 
crates/debugger_ui/src/tests/new_process_modal.rs | 214 ++--
crates/paths/src/paths.rs                         |   1 
crates/settings/src/settings.rs                   |   4 
crates/zed/src/zed.rs                             |  14 
12 files changed, 527 insertions(+), 520 deletions(-)

Detailed changes

assets/settings/initial_debug_tasks.json 🔗

@@ -1,3 +1,7 @@
+// Some example tasks for common languages.
+//
+// For more documentation on how to configure debug tasks,
+// see: https://zed.dev/docs/debugger
 [
   {
     "label": "Debug active PHP file",

crates/debugger_ui/Cargo.toml 🔗

@@ -50,6 +50,7 @@ project.workspace = true
 rpc.workspace = true
 serde.workspace = true
 serde_json.workspace = true
+# serde_json_lenient.workspace = true
 settings.workspace = true
 shlex.workspace = true
 sysinfo.workspace = true

crates/debugger_ui/src/debugger_panel.rs 🔗

@@ -7,7 +7,7 @@ use crate::{
     ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
     ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
 };
-use anyhow::{Context as _, Result, anyhow};
+use anyhow::Result;
 use command_palette_hooks::CommandPaletteFilter;
 use dap::StartDebuggingRequestArguments;
 use dap::adapters::DebugAdapterName;
@@ -24,7 +24,7 @@ use gpui::{
 
 use language::Buffer;
 use project::debugger::session::{Session, SessionStateEvent};
-use project::{Fs, ProjectPath, WorktreeId};
+use project::{Fs, WorktreeId};
 use project::{Project, debugger::session::ThreadStatus};
 use rpc::proto::{self};
 use settings::Settings;
@@ -942,68 +942,69 @@ impl DebugPanel {
         cx.notify();
     }
 
-    pub(crate) fn save_scenario(
-        &self,
-        scenario: &DebugScenario,
-        worktree_id: WorktreeId,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> Task<Result<ProjectPath>> {
-        self.workspace
-            .update(cx, |workspace, cx| {
-                let Some(mut path) = workspace.absolute_path_of_worktree(worktree_id, cx) else {
-                    return Task::ready(Err(anyhow!("Couldn't get worktree path")));
-                };
-
-                let serialized_scenario = serde_json::to_value(scenario);
-
-                cx.spawn_in(window, async move |workspace, cx| {
-                    let serialized_scenario = serialized_scenario?;
-                    let fs =
-                        workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
-
-                    path.push(paths::local_settings_folder_relative_path());
-                    if !fs.is_dir(path.as_path()).await {
-                        fs.create_dir(path.as_path()).await?;
-                    }
-                    path.pop();
-
-                    path.push(paths::local_debug_file_relative_path());
-                    let path = path.as_path();
-
-                    if !fs.is_file(path).await {
-                        let content =
-                            serde_json::to_string_pretty(&serde_json::Value::Array(vec![
-                                serialized_scenario,
-                            ]))?;
-
-                        fs.create_file(path, Default::default()).await?;
-                        fs.save(path, &content.into(), Default::default()).await?;
-                    } else {
-                        let content = fs.load(path).await?;
-                        let mut values = serde_json::from_str::<Vec<serde_json::Value>>(&content)?;
-                        values.push(serialized_scenario);
-                        fs.save(
-                            path,
-                            &serde_json::to_string_pretty(&values).map(Into::into)?,
-                            Default::default(),
-                        )
-                        .await?;
-                    }
-
-                    workspace.update(cx, |workspace, cx| {
-                        workspace
-                            .project()
-                            .read(cx)
-                            .project_path_for_absolute_path(&path, cx)
-                            .context(
-                                "Couldn't get project path for .zed/debug.json in active worktree",
-                            )
-                    })?
-                })
-            })
-            .unwrap_or_else(|err| Task::ready(Err(err)))
-    }
+    // TODO: restore once we have proper comment preserving file edits
+    // pub(crate) fn save_scenario(
+    //     &self,
+    //     scenario: &DebugScenario,
+    //     worktree_id: WorktreeId,
+    //     window: &mut Window,
+    //     cx: &mut App,
+    // ) -> Task<Result<ProjectPath>> {
+    //     self.workspace
+    //         .update(cx, |workspace, cx| {
+    //             let Some(mut path) = workspace.absolute_path_of_worktree(worktree_id, cx) else {
+    //                 return Task::ready(Err(anyhow!("Couldn't get worktree path")));
+    //             };
+
+    //             let serialized_scenario = serde_json::to_value(scenario);
+
+    //             cx.spawn_in(window, async move |workspace, cx| {
+    //                 let serialized_scenario = serialized_scenario?;
+    //                 let fs =
+    //                     workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
+
+    //                 path.push(paths::local_settings_folder_relative_path());
+    //                 if !fs.is_dir(path.as_path()).await {
+    //                     fs.create_dir(path.as_path()).await?;
+    //                 }
+    //                 path.pop();
+
+    //                 path.push(paths::local_debug_file_relative_path());
+    //                 let path = path.as_path();
+
+    //                 if !fs.is_file(path).await {
+    //                     fs.create_file(path, Default::default()).await?;
+    //                     fs.write(
+    //                         path,
+    //                         initial_local_debug_tasks_content().to_string().as_bytes(),
+    //                     )
+    //                     .await?;
+    //                 }
+
+    //                 let content = fs.load(path).await?;
+    //                 let mut values =
+    //                     serde_json_lenient::from_str::<Vec<serde_json::Value>>(&content)?;
+    //                 values.push(serialized_scenario);
+    //                 fs.save(
+    //                     path,
+    //                     &serde_json_lenient::to_string_pretty(&values).map(Into::into)?,
+    //                     Default::default(),
+    //                 )
+    //                 .await?;
+
+    //                 workspace.update(cx, |workspace, cx| {
+    //                     workspace
+    //                         .project()
+    //                         .read(cx)
+    //                         .project_path_for_absolute_path(&path, cx)
+    //                         .context(
+    //                             "Couldn't get project path for .zed/debug.json in active worktree",
+    //                         )
+    //                 })?
+    //             })
+    //         })
+    //         .unwrap_or_else(|err| Task::ready(Err(err)))
+    // }
 
     pub(crate) fn toggle_thread_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
         self.thread_picker_menu_handle.toggle(window, cx);

crates/debugger_ui/src/debugger_ui.rs 🔗

@@ -3,7 +3,7 @@ use debugger_panel::{DebugPanel, ToggleFocus};
 use editor::Editor;
 use feature_flags::{DebuggerFeatureFlag, FeatureFlagViewExt};
 use gpui::{App, EntityInputHandler, actions};
-use new_session_modal::{NewSessionModal, NewSessionMode};
+use new_process_modal::{NewProcessModal, NewProcessMode};
 use project::debugger::{self, breakpoint_store::SourceBreakpoint};
 use session::DebugSession;
 use settings::Settings;
@@ -15,7 +15,7 @@ use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
 pub mod attach_modal;
 pub mod debugger_panel;
 mod dropdown_menus;
-mod new_session_modal;
+mod new_process_modal;
 mod persistence;
 pub(crate) mod session;
 mod stack_trace_view;
@@ -210,7 +210,7 @@ pub fn init(cx: &mut App) {
                     },
                 )
                 .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
-                    NewSessionModal::show(workspace, window, NewSessionMode::Launch, None, cx);
+                    NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
                 })
                 .register_action(
                     |workspace: &mut Workspace, _: &RerunLastSession, window, cx| {
@@ -352,7 +352,7 @@ fn spawn_task_or_modal(
             .detach_and_log_err(cx)
         }
         Spawn::ViaModal { reveal_target } => {
-            NewSessionModal::show(workspace, window, NewSessionMode::Task, *reveal_target, cx);
+            NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx);
         }
     }
 }

crates/debugger_ui/src/new_session_modal.rs → crates/debugger_ui/src/new_process_modal.rs 🔗

@@ -1,11 +1,10 @@
 use collections::FxHashMap;
-use language::{LanguageRegistry, Point, Selection};
+use language::LanguageRegistry;
+use paths::local_debug_file_relative_path;
 use std::{
     borrow::Cow,
-    ops::Not,
     path::{Path, PathBuf},
     sync::Arc,
-    time::Duration,
     usize,
 };
 use tasks_ui::{TaskOverrides, TasksModal};
@@ -13,45 +12,47 @@ use tasks_ui::{TaskOverrides, TasksModal};
 use dap::{
     DapRegistry, DebugRequest, TelemetrySpawnLocation, adapters::DebugAdapterName, send_telemetry,
 };
-use editor::{Anchor, Editor, EditorElement, EditorStyle, scroll::Autoscroll};
+use editor::{Editor, EditorElement, EditorStyle};
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    Animation, AnimationExt as _, App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle,
-    Focusable, KeyContext, Render, Subscription, TextStyle, Transformation, WeakEntity, percentage,
+    App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle,
+    InteractiveText, KeyContext, PromptButton, PromptLevel, Render, StyledText, Subscription,
+    TextStyle, UnderlineStyle, WeakEntity,
 };
 use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
 use project::{ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore};
-use settings::Settings;
-use task::{DebugScenario, LaunchRequest, RevealTarget, ZedDebugConfig};
+use settings::{Settings, initial_local_debug_tasks_content};
+use task::{DebugScenario, RevealTarget, ZedDebugConfig};
 use theme::ThemeSettings;
 use ui::{
     ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
-    ContextMenu, Disableable, DropdownMenu, FluentBuilder, Icon, IconButton, IconName, IconSize,
+    ContextMenu, Disableable, DropdownMenu, FluentBuilder, Icon, IconName, IconSize,
     IconWithIndicator, Indicator, InteractiveElement, IntoElement, Label, LabelCommon as _,
     ListItem, ListItemSpacing, ParentElement, RenderOnce, SharedString, Styled, StyledExt,
-    ToggleButton, ToggleState, Toggleable, Window, div, h_flex, relative, rems, v_flex,
+    StyledTypography, ToggleButton, ToggleState, Toggleable, Window, div, h_flex, px, relative,
+    rems, v_flex,
 };
 use util::ResultExt;
 use workspace::{ModalView, Workspace, pane};
 
 use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
 
-enum SaveScenarioState {
-    Saving,
-    Saved((ProjectPath, SharedString)),
-    Failed(SharedString),
-}
+// enum SaveScenarioState {
+//     Saving,
+//     Saved((ProjectPath, SharedString)),
+//     Failed(SharedString),
+// }
 
-pub(super) struct NewSessionModal {
+pub(super) struct NewProcessModal {
     workspace: WeakEntity<Workspace>,
     debug_panel: WeakEntity<DebugPanel>,
-    mode: NewSessionMode,
-    launch_picker: Entity<Picker<DebugScenarioDelegate>>,
+    mode: NewProcessMode,
+    debug_picker: Entity<Picker<DebugDelegate>>,
     attach_mode: Entity<AttachMode>,
-    configure_mode: Entity<ConfigureMode>,
+    launch_mode: Entity<LaunchMode>,
     task_mode: TaskMode,
     debugger: Option<DebugAdapterName>,
-    save_scenario_state: Option<SaveScenarioState>,
+    // save_scenario_state: Option<SaveScenarioState>,
     _subscriptions: [Subscription; 3],
 }
 
@@ -73,11 +74,11 @@ fn suggested_label(request: &DebugRequest, debugger: &str) -> SharedString {
     }
 }
 
-impl NewSessionModal {
+impl NewProcessModal {
     pub(super) fn show(
         workspace: &mut Workspace,
         window: &mut Window,
-        mode: NewSessionMode,
+        mode: NewProcessMode,
         reveal_target: Option<RevealTarget>,
         cx: &mut Context<Workspace>,
     ) {
@@ -101,12 +102,12 @@ impl NewSessionModal {
 
                     let launch_picker = cx.new(|cx| {
                         let mut delegate =
-                            DebugScenarioDelegate::new(debug_panel.downgrade(), task_store.clone());
+                            DebugDelegate::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);
+                    let configure_mode = LaunchMode::new(window, cx);
                     if let Some(active_cwd) = task_contexts
                         .active_context()
                         .and_then(|context| context.cwd.clone())
@@ -148,15 +149,15 @@ impl NewSessionModal {
                     ];
 
                     Self {
-                        launch_picker,
+                        debug_picker: launch_picker,
                         attach_mode,
-                        configure_mode,
+                        launch_mode: configure_mode,
                         task_mode,
                         debugger: None,
                         mode,
                         debug_panel: debug_panel.downgrade(),
                         workspace: workspace_handle,
-                        save_scenario_state: None,
+                        // save_scenario_state: None,
                         _subscriptions,
                     }
                 });
@@ -170,49 +171,49 @@ 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
+            NewProcessMode::Task => self
                 .task_mode
                 .task_modal
                 .read(cx)
                 .picker
                 .clone()
                 .into_any_element(),
-            NewSessionMode::Attach => self.attach_mode.update(cx, |this, cx| {
+            NewProcessMode::Attach => self.attach_mode.update(cx, |this, cx| {
                 this.clone().render(window, cx).into_any_element()
             }),
-            NewSessionMode::Configure => self.configure_mode.update(cx, |this, cx| {
+            NewProcessMode::Launch => self.launch_mode.update(cx, |this, cx| {
                 this.clone().render(dap_menu, window, cx).into_any_element()
             }),
-            NewSessionMode::Launch => v_flex()
+            NewProcessMode::Debug => v_flex()
                 .w(rems(34.))
-                .child(self.launch_picker.clone())
+                .child(self.debug_picker.clone())
                 .into_any_element(),
         }
     }
 
     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::Configure => self.configure_mode.read(cx).program.focus_handle(cx),
-            NewSessionMode::Launch => self.launch_picker.focus_handle(cx),
+            NewProcessMode::Task => self.task_mode.task_modal.focus_handle(cx),
+            NewProcessMode::Attach => self.attach_mode.read(cx).attach_picker.focus_handle(cx),
+            NewProcessMode::Launch => self.launch_mode.read(cx).program.focus_handle(cx),
+            NewProcessMode::Debug => self.debug_picker.focus_handle(cx),
         }
     }
 
     fn debug_scenario(&self, debugger: &str, cx: &App) -> Option<DebugScenario> {
         let request = match self.mode {
-            NewSessionMode::Configure => Some(DebugRequest::Launch(
-                self.configure_mode.read(cx).debug_request(cx),
+            NewProcessMode::Launch => Some(DebugRequest::Launch(
+                self.launch_mode.read(cx).debug_request(cx),
             )),
-            NewSessionMode::Attach => Some(DebugRequest::Attach(
+            NewProcessMode::Attach => Some(DebugRequest::Attach(
                 self.attach_mode.read(cx).debug_request(),
             )),
             _ => None,
         }?;
         let label = suggested_label(&request, debugger);
 
-        let stop_on_entry = if let NewSessionMode::Configure = &self.mode {
-            Some(self.configure_mode.read(cx).stop_on_entry.selected())
+        let stop_on_entry = if let NewProcessMode::Launch = &self.mode {
+            Some(self.launch_mode.read(cx).stop_on_entry.selected())
         } else {
             None
         };
@@ -229,18 +230,29 @@ impl NewSessionModal {
             .and_then(|adapter| adapter.config_from_zed_format(session_scenario).ok())
     }
 
-    fn start_new_session(&self, window: &mut Window, cx: &mut Context<Self>) {
-        let Some(debugger) = self.debugger.as_ref() else {
+    fn start_new_session(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        if self.debugger.as_ref().is_none() {
             return;
-        };
+        }
 
-        if let NewSessionMode::Launch = &self.mode {
-            self.launch_picker.update(cx, |picker, cx| {
+        if let NewProcessMode::Debug = &self.mode {
+            self.debug_picker.update(cx, |picker, cx| {
                 picker.delegate.confirm(false, window, cx);
             });
             return;
         }
 
+        // TODO: Restore once we have proper, comment preserving edits
+        // if let NewProcessMode::Launch = &self.mode {
+        //     if self.launch_mode.read(cx).save_to_debug_json.selected() {
+        //         self.save_debug_scenario(window, cx);
+        //     }
+        // }
+
+        let Some(debugger) = self.debugger.as_ref() else {
+            return;
+        };
+
         let Some(config) = self.debug_scenario(debugger, cx) else {
             log::error!("debug config not found in mode: {}", self.mode);
             return;
@@ -289,179 +301,50 @@ impl NewSessionModal {
     }
 
     fn task_contexts(&self, cx: &App) -> Option<Arc<TaskContexts>> {
-        self.launch_picker.read(cx).delegate.task_contexts.clone()
-    }
-
-    fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let Some((save_scenario, scenario_label)) = self
-            .debugger
-            .as_ref()
-            .and_then(|debugger| self.debug_scenario(&debugger, cx))
-            .zip(self.task_contexts(cx).and_then(|tcx| tcx.worktree()))
-            .and_then(|(scenario, worktree_id)| {
-                self.debug_panel
-                    .update(cx, |panel, cx| {
-                        panel.save_scenario(&scenario, worktree_id, window, cx)
-                    })
-                    .ok()
-                    .zip(Some(scenario.label.clone()))
-            })
-        else {
-            return;
-        };
-
-        self.save_scenario_state = Some(SaveScenarioState::Saving);
-
-        cx.spawn(async move |this, cx| {
-            let res = save_scenario.await;
-
-            this.update(cx, |this, _| match res {
-                Ok(saved_file) => {
-                    this.save_scenario_state =
-                        Some(SaveScenarioState::Saved((saved_file, scenario_label)))
-                }
-                Err(error) => {
-                    this.save_scenario_state =
-                        Some(SaveScenarioState::Failed(error.to_string().into()))
-                }
-            })
-            .ok();
-
-            cx.background_executor().timer(Duration::from_secs(3)).await;
-            this.update(cx, |this, _| this.save_scenario_state.take())
-                .ok();
-        })
-        .detach();
+        self.debug_picker.read(cx).delegate.task_contexts.clone()
     }
 
-    fn render_save_state(&self, cx: &mut Context<Self>) -> impl IntoElement {
-        let this_entity = cx.weak_entity().clone();
-
-        div().when_some(self.save_scenario_state.as_ref(), {
-            let this_entity = this_entity.clone();
-
-            move |this, save_state| match save_state {
-                SaveScenarioState::Saved((saved_path, scenario_label)) => this.child(
-                    IconButton::new("new-session-modal-go-to-file", IconName::ArrowUpRight)
-                        .icon_size(IconSize::Small)
-                        .icon_color(Color::Muted)
-                        .on_click({
-                            let this_entity = this_entity.clone();
-                            let saved_path = saved_path.clone();
-                            let scenario_label = scenario_label.clone();
-                            move |_, window, cx| {
-                                window
-                                    .spawn(cx, {
-                                        let this_entity = this_entity.clone();
-                                        let saved_path = saved_path.clone();
-                                        let scenario_label = scenario_label.clone();
-
-                                        async move |cx| {
-                                            let editor = this_entity
-                                                .update_in(cx, |this, window, cx| {
-                                                    this.workspace.update(cx, |workspace, cx| {
-                                                        workspace.open_path(
-                                                            saved_path.clone(),
-                                                            None,
-                                                            true,
-                                                            window,
-                                                            cx,
-                                                        )
-                                                    })
-                                                })??
-                                                .await?;
-
-                                            cx.update(|window, cx| {
-                                                if let Some(editor) = editor.act_as::<Editor>(cx) {
-                                                    editor.update(cx, |editor, cx| {
-                                                        let row = editor
-                                                            .text(cx)
-                                                            .lines()
-                                                            .enumerate()
-                                                            .find_map(|(row, text)| {
-                                                                if text.contains(
-                                                                    scenario_label.as_ref(),
-                                                                ) {
-                                                                    Some(row)
-                                                                } else {
-                                                                    None
-                                                                }
-                                                            })?;
-
-                                                        let buffer = editor.buffer().read(cx);
-                                                        let excerpt_id =
-                                                            *buffer.excerpt_ids().first()?;
-
-                                                        let snapshot = buffer
-                                                            .as_singleton()?
-                                                            .read(cx)
-                                                            .snapshot();
-
-                                                        let anchor = snapshot.anchor_before(
-                                                            Point::new(row as u32, 0),
-                                                        );
-
-                                                        let anchor = Anchor {
-                                                            buffer_id: anchor.buffer_id,
-                                                            excerpt_id,
-                                                            text_anchor: anchor,
-                                                            diff_base_anchor: None,
-                                                        };
-
-                                                        editor.change_selections(
-                                                            Some(Autoscroll::center()),
-                                                            window,
-                                                            cx,
-                                                            |selections| {
-                                                                let id =
-                                                                    selections.new_selection_id();
-                                                                selections.select_anchors(
-                                                                    vec![Selection {
-                                                                id,
-                                                                start: anchor,
-                                                                end: anchor,
-                                                                reversed: false,
-                                                                goal: language::SelectionGoal::None
-                                                            }],
-                                                                );
-                                                            },
-                                                        );
-
-                                                        Some(())
-                                                    });
-                                                }
-                                            })?;
-
-                                            this_entity
-                                                .update(cx, |_, cx| cx.emit(DismissEvent))
-                                                .ok();
-
-                                            anyhow::Ok(())
-                                        }
-                                    })
-                                    .detach();
-                            }
-                        }),
-                ),
-                SaveScenarioState::Saving => this.child(
-                    Icon::new(IconName::Spinner)
-                        .size(IconSize::Small)
-                        .color(Color::Muted)
-                        .with_animation(
-                            "Spinner",
-                            Animation::new(Duration::from_secs(3)).repeat(),
-                            |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
-                        ),
-                ),
-                SaveScenarioState::Failed(error_msg) => this.child(
-                    IconButton::new("Failed Scenario Saved", IconName::X)
-                        .icon_size(IconSize::Small)
-                        .icon_color(Color::Error)
-                        .tooltip(ui::Tooltip::text(error_msg.clone())),
-                ),
-            }
-        })
-    }
+    // fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+    //     let Some((save_scenario, scenario_label)) = self
+    //         .debugger
+    //         .as_ref()
+    //         .and_then(|debugger| self.debug_scenario(&debugger, cx))
+    //         .zip(self.task_contexts(cx).and_then(|tcx| tcx.worktree()))
+    //         .and_then(|(scenario, worktree_id)| {
+    //             self.debug_panel
+    //                 .update(cx, |panel, cx| {
+    //                     panel.save_scenario(&scenario, worktree_id, window, cx)
+    //                 })
+    //                 .ok()
+    //                 .zip(Some(scenario.label.clone()))
+    //         })
+    //     else {
+    //         return;
+    //     };
+
+    //     self.save_scenario_state = Some(SaveScenarioState::Saving);
+
+    //     cx.spawn(async move |this, cx| {
+    //         let res = save_scenario.await;
+
+    //         this.update(cx, |this, _| match res {
+    //             Ok(saved_file) => {
+    //                 this.save_scenario_state =
+    //                     Some(SaveScenarioState::Saved((saved_file, scenario_label)))
+    //             }
+    //             Err(error) => {
+    //                 this.save_scenario_state =
+    //                     Some(SaveScenarioState::Failed(error.to_string().into()))
+    //             }
+    //         })
+    //         .ok();
+
+    //         cx.background_executor().timer(Duration::from_secs(3)).await;
+    //         this.update(cx, |this, _| this.save_scenario_state.take())
+    //             .ok();
+    //     })
+    //     .detach();
+    // }
 
     fn adapter_drop_down_menu(
         &mut self,
@@ -513,7 +396,7 @@ impl NewSessionModal {
                         weak.update(cx, |this, cx| {
                             this.debugger = Some(name.clone());
                             cx.notify();
-                            if let NewSessionMode::Attach = &this.mode {
+                            if let NewProcessMode::Attach = &this.mode {
                                 Self::update_attach_picker(&this.attach_mode, &name, window, cx);
                             }
                         })
@@ -529,32 +412,96 @@ impl NewSessionModal {
             }),
         )
     }
+
+    fn open_debug_json(&self, window: &mut Window, cx: &mut Context<NewProcessModal>) {
+        let this = cx.entity();
+        window
+            .spawn(cx, async move |cx| {
+                let worktree_id = this.update(cx, |this, cx| {
+                    let tcx = this.task_contexts(cx);
+                    tcx?.worktree()
+                })?;
+
+                let Some(worktree_id) = worktree_id else {
+                    let _ = cx.prompt(
+                        PromptLevel::Critical,
+                        "Cannot open debug.json",
+                        Some("You must have at least one project open"),
+                        &[PromptButton::ok("Ok")],
+                    );
+                    return Ok(());
+                };
+
+                let editor = this
+                    .update_in(cx, |this, window, cx| {
+                        this.workspace.update(cx, |workspace, cx| {
+                            workspace.open_path(
+                                ProjectPath {
+                                    worktree_id,
+                                    path: local_debug_file_relative_path().into(),
+                                },
+                                None,
+                                true,
+                                window,
+                                cx,
+                            )
+                        })
+                    })??
+                    .await?;
+
+                cx.update(|_window, cx| {
+                    if let Some(editor) = editor.act_as::<Editor>(cx) {
+                        editor.update(cx, |editor, cx| {
+                            editor.buffer().update(cx, |buffer, cx| {
+                                if let Some(singleton) = buffer.as_singleton() {
+                                    singleton.update(cx, |buffer, cx| {
+                                        if buffer.is_empty() {
+                                            buffer.edit(
+                                                [(0..0, initial_local_debug_tasks_content())],
+                                                None,
+                                                cx,
+                                            );
+                                        }
+                                    })
+                                }
+                            })
+                        });
+                    }
+                })
+                .ok();
+
+                this.update(cx, |_, cx| cx.emit(DismissEvent)).ok();
+
+                anyhow::Ok(())
+            })
+            .detach();
+    }
 }
 
 static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select Debugger");
 
 #[derive(Clone)]
-pub(crate) enum NewSessionMode {
+pub(crate) enum NewProcessMode {
     Task,
-    Configure,
-    Attach,
     Launch,
+    Attach,
+    Debug,
 }
 
-impl std::fmt::Display for NewSessionMode {
+impl std::fmt::Display for NewProcessMode {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         let mode = match self {
-            NewSessionMode::Task => "Run",
-            NewSessionMode::Launch => "Debug",
-            NewSessionMode::Attach => "Attach",
-            NewSessionMode::Configure => "Configure Debugger",
+            NewProcessMode::Task => "Run",
+            NewProcessMode::Debug => "Debug",
+            NewProcessMode::Attach => "Attach",
+            NewProcessMode::Launch => "Launch",
         };
 
         write!(f, "{}", mode)
     }
 }
 
-impl Focusable for NewSessionMode {
+impl Focusable for NewProcessMode {
     fn focus_handle(&self, cx: &App) -> FocusHandle {
         cx.focus_handle()
     }
@@ -598,7 +545,7 @@ fn render_editor(editor: &Entity<Editor>, window: &mut Window, cx: &App) -> impl
         .bg(theme.colors().editor_background)
 }
 
-impl Render for NewSessionModal {
+impl Render for NewProcessModal {
     fn render(
         &mut self,
         window: &mut ui::Window,
@@ -620,10 +567,10 @@ impl Render for NewSessionModal {
             }))
             .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,
+                    NewProcessMode::Task => NewProcessMode::Debug,
+                    NewProcessMode::Debug => NewProcessMode::Attach,
+                    NewProcessMode::Attach => NewProcessMode::Launch,
+                    NewProcessMode::Launch => NewProcessMode::Task,
                 };
 
                 this.mode_focus_handle(cx).focus(window);
@@ -631,10 +578,10 @@ impl Render for NewSessionModal {
             .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::Configure => NewSessionMode::Attach,
+                        NewProcessMode::Task => NewProcessMode::Launch,
+                        NewProcessMode::Debug => NewProcessMode::Task,
+                        NewProcessMode::Attach => NewProcessMode::Debug,
+                        NewProcessMode::Launch => NewProcessMode::Attach,
                     };
 
                     this.mode_focus_handle(cx).focus(window);
@@ -652,13 +599,13 @@ impl Render for NewSessionModal {
                             .child(
                                 ToggleButton::new(
                                     "debugger-session-ui-tasks-button",
-                                    NewSessionMode::Task.to_string(),
+                                    NewProcessMode::Task.to_string(),
                                 )
                                 .size(ButtonSize::Default)
-                                .toggle_state(matches!(self.mode, NewSessionMode::Task))
+                                .toggle_state(matches!(self.mode, NewProcessMode::Task))
                                 .style(ui::ButtonStyle::Subtle)
                                 .on_click(cx.listener(|this, _, window, cx| {
-                                    this.mode = NewSessionMode::Task;
+                                    this.mode = NewProcessMode::Task;
                                     this.mode_focus_handle(cx).focus(window);
                                     cx.notify();
                                 }))
@@ -667,13 +614,13 @@ impl Render for NewSessionModal {
                             .child(
                                 ToggleButton::new(
                                     "debugger-session-ui-launch-button",
-                                    NewSessionMode::Launch.to_string(),
+                                    NewProcessMode::Debug.to_string(),
                                 )
                                 .size(ButtonSize::Default)
                                 .style(ui::ButtonStyle::Subtle)
-                                .toggle_state(matches!(self.mode, NewSessionMode::Launch))
+                                .toggle_state(matches!(self.mode, NewProcessMode::Debug))
                                 .on_click(cx.listener(|this, _, window, cx| {
-                                    this.mode = NewSessionMode::Launch;
+                                    this.mode = NewProcessMode::Debug;
                                     this.mode_focus_handle(cx).focus(window);
                                     cx.notify();
                                 }))
@@ -682,13 +629,13 @@ impl Render for NewSessionModal {
                             .child(
                                 ToggleButton::new(
                                     "debugger-session-ui-attach-button",
-                                    NewSessionMode::Attach.to_string(),
+                                    NewProcessMode::Attach.to_string(),
                                 )
                                 .size(ButtonSize::Default)
-                                .toggle_state(matches!(self.mode, NewSessionMode::Attach))
+                                .toggle_state(matches!(self.mode, NewProcessMode::Attach))
                                 .style(ui::ButtonStyle::Subtle)
                                 .on_click(cx.listener(|this, _, window, cx| {
-                                    this.mode = NewSessionMode::Attach;
+                                    this.mode = NewProcessMode::Attach;
 
                                     if let Some(debugger) = this.debugger.as_ref() {
                                         Self::update_attach_picker(
@@ -706,13 +653,13 @@ impl Render for NewSessionModal {
                             .child(
                                 ToggleButton::new(
                                     "debugger-session-ui-custom-button",
-                                    NewSessionMode::Configure.to_string(),
+                                    NewProcessMode::Launch.to_string(),
                                 )
                                 .size(ButtonSize::Default)
-                                .toggle_state(matches!(self.mode, NewSessionMode::Configure))
+                                .toggle_state(matches!(self.mode, NewProcessMode::Launch))
                                 .style(ui::ButtonStyle::Subtle)
                                 .on_click(cx.listener(|this, _, window, cx| {
-                                    this.mode = NewSessionMode::Configure;
+                                    this.mode = NewProcessMode::Launch;
                                     this.mode_focus_handle(cx).focus(window);
                                     cx.notify();
                                 }))
@@ -733,30 +680,42 @@ impl Render for NewSessionModal {
                     .border_t_1()
                     .w_full();
                 match self.mode {
-                    NewSessionMode::Configure => el.child(
+                    NewProcessMode::Launch => el.child(
                         container
                             .child(
                                 h_flex()
+                                    .text_ui_sm(cx)
+                                    .text_color(Color::Muted.color(cx))
                                     .child(
-                                        Button::new(
-                                            "new-session-modal-back",
-                                            "Save to .zed/debug.json...",
+                                        InteractiveText::new(
+                                            "open-debug-json",
+                                            StyledText::new(
+                                                "Open .zed/debug.json for advanced configuration",
+                                            )
+                                            .with_highlights([(
+                                                5..20,
+                                                HighlightStyle {
+                                                    underline: Some(UnderlineStyle {
+                                                        thickness: px(1.0),
+                                                        color: None,
+                                                        wavy: false,
+                                                    }),
+                                                    ..Default::default()
+                                                },
+                                            )]),
                                         )
-                                        .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(),
+                                        .on_click(
+                                            vec![5..20],
+                                            {
+                                                let this = cx.entity();
+                                                move |_, window, cx| {
+                                                    this.update(cx, |this, cx| {
+                                                        this.open_debug_json(window, cx);
+                                                    })
+                                                }
+                                            },
                                         ),
-                                    )
-                                    .child(self.render_save_state(cx)),
+                                    ),
                             )
                             .child(
                                 Button::new("debugger-spawn", "Start")
@@ -766,7 +725,7 @@ impl Render for NewSessionModal {
                                     .disabled(
                                         self.debugger.is_none()
                                             || self
-                                                .configure_mode
+                                                .launch_mode
                                                 .read(cx)
                                                 .program
                                                 .read(cx)
@@ -774,7 +733,7 @@ impl Render for NewSessionModal {
                                     ),
                             ),
                     ),
-                    NewSessionMode::Attach => el.child(
+                    NewProcessMode::Attach => el.child(
                         container
                             .child(div().child(self.adapter_drop_down_menu(window, cx)))
                             .child(
@@ -797,21 +756,21 @@ impl Render for NewSessionModal {
                                     ),
                             ),
                     ),
-                    NewSessionMode::Launch => el,
-                    NewSessionMode::Task => el,
+                    NewProcessMode::Debug => el,
+                    NewProcessMode::Task => el,
                 }
             })
     }
 }
 
-impl EventEmitter<DismissEvent> for NewSessionModal {}
-impl Focusable for NewSessionModal {
+impl EventEmitter<DismissEvent> for NewProcessModal {}
+impl Focusable for NewProcessModal {
     fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
         self.mode_focus_handle(cx)
     }
 }
 
-impl ModalView for NewSessionModal {}
+impl ModalView for NewProcessModal {}
 
 impl RenderOnce for AttachMode {
     fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
@@ -823,44 +782,30 @@ impl RenderOnce for AttachMode {
 }
 
 #[derive(Clone)]
-pub(super) struct ConfigureMode {
+pub(super) struct LaunchMode {
     program: Entity<Editor>,
     cwd: Entity<Editor>,
     stop_on_entry: ToggleState,
+    // save_to_debug_json: ToggleState,
 }
 
-impl ConfigureMode {
-    pub(super) fn new(
-        past_launch_config: Option<LaunchRequest>,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> Entity<Self> {
-        let (past_program, past_cwd) = past_launch_config
-            .map(|config| (Some(config.program), config.cwd))
-            .unwrap_or_else(|| (None, None));
-
+impl LaunchMode {
+    pub(super) fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
         let program = cx.new(|cx| Editor::single_line(window, cx));
         program.update(cx, |this, cx| {
-            this.set_placeholder_text(
-                "ALPHA=\"Windows\" BETA=\"Wen\" your_program --arg1 --arg2=arg3",
-                cx,
-            );
-
-            if let Some(past_program) = past_program {
-                this.set_text(past_program, window, cx);
-            };
+            this.set_placeholder_text("ENV=Zed ~/bin/debugger --launch", cx);
         });
+
         let cwd = cx.new(|cx| Editor::single_line(window, cx));
         cwd.update(cx, |this, cx| {
-            this.set_placeholder_text("Working Directory", cx);
-            if let Some(past_cwd) = past_cwd {
-                this.set_text(past_cwd.to_string_lossy(), window, cx);
-            };
+            this.set_placeholder_text("Ex: $ZED_WORKTREE_ROOT", cx);
         });
+
         cx.new(|_| Self {
             program,
             cwd,
             stop_on_entry: ToggleState::Unselected,
+            // save_to_debug_json: ToggleState::Unselected,
         })
     }
 
@@ -873,11 +818,17 @@ impl ConfigureMode {
     }
 
     pub(super) fn debug_request(&self, cx: &App) -> task::LaunchRequest {
-        let path = self.cwd.read(cx).text(cx);
+        let cwd_text = self.cwd.read(cx).text(cx);
+        let cwd = if cwd_text.is_empty() {
+            None
+        } else {
+            Some(PathBuf::from(cwd_text))
+        };
+
         if cfg!(windows) {
             return task::LaunchRequest {
                 program: self.program.read(cx).text(cx),
-                cwd: path.is_empty().not().then(|| PathBuf::from(path)),
+                cwd,
                 args: Default::default(),
                 env: Default::default(),
             };
@@ -902,7 +853,7 @@ impl ConfigureMode {
 
         task::LaunchRequest {
             program,
-            cwd: path.is_empty().not().then(|| PathBuf::from(path)),
+            cwd,
             args,
             env,
         }
@@ -929,7 +880,17 @@ impl ConfigureMode {
                     .gap(ui::DynamicSpacing::Base08.rems(cx))
                     .child(adapter_menu),
             )
+            .child(
+                Label::new("Debugger Program")
+                    .size(ui::LabelSize::Small)
+                    .color(Color::Muted),
+            )
             .child(render_editor(&self.program, window, cx))
+            .child(
+                Label::new("Working Directory")
+                    .size(ui::LabelSize::Small)
+                    .color(Color::Muted),
+            )
             .child(render_editor(&self.cwd, window, cx))
             .child(
                 CheckboxWithLabel::new(
@@ -950,6 +911,27 @@ impl ConfigureMode {
                 )
                 .checkbox_position(ui::IconPosition::End),
             )
+        // TODO: restore once we have proper, comment preserving
+        // file edits.
+        // .child(
+        //     CheckboxWithLabel::new(
+        //         "debugger-save-to-debug-json",
+        //         Label::new("Save to debug.json")
+        //             .size(ui::LabelSize::Small)
+        //             .color(Color::Muted),
+        //         self.save_to_debug_json,
+        //         {
+        //             let this = cx.weak_entity();
+        //             move |state, _, cx| {
+        //                 this.update(cx, |this, _| {
+        //                     this.save_to_debug_json = *state;
+        //                 })
+        //                 .ok();
+        //             }
+        //         },
+        //     )
+        //     .checkbox_position(ui::IconPosition::End),
+        // )
     }
 }
 
@@ -964,7 +946,7 @@ impl AttachMode {
         debugger: Option<DebugAdapterName>,
         workspace: WeakEntity<Workspace>,
         window: &mut Window,
-        cx: &mut Context<NewSessionModal>,
+        cx: &mut Context<NewProcessModal>,
     ) -> Entity<Self> {
         let definition = ZedDebugConfig {
             adapter: debugger.unwrap_or(DebugAdapterName("".into())).0,
@@ -994,7 +976,7 @@ pub(super) struct TaskMode {
     pub(super) task_modal: Entity<TasksModal>,
 }
 
-pub(super) struct DebugScenarioDelegate {
+pub(super) struct DebugDelegate {
     task_store: Entity<TaskStore>,
     candidates: Vec<(Option<TaskSourceKind>, DebugScenario)>,
     selected_index: usize,
@@ -1006,7 +988,7 @@ pub(super) struct DebugScenarioDelegate {
     last_used_candidate_index: Option<usize>,
 }
 
-impl DebugScenarioDelegate {
+impl DebugDelegate {
     pub(super) fn new(debug_panel: WeakEntity<DebugPanel>, task_store: Entity<TaskStore>) -> Self {
         Self {
             task_store,
@@ -1085,7 +1067,7 @@ impl DebugScenarioDelegate {
     }
 }
 
-impl PickerDelegate for DebugScenarioDelegate {
+impl PickerDelegate for DebugDelegate {
     type ListItem = ui::ListItem;
 
     fn match_count(&self) -> usize {
@@ -1270,37 +1252,38 @@ pub(crate) fn resolve_path(path: &mut String) {
 }
 
 #[cfg(test)]
-impl NewSessionModal {
-    pub(crate) fn set_configure(
-        &mut self,
-        program: impl AsRef<str>,
-        cwd: impl AsRef<str>,
-        stop_on_entry: bool,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.mode = NewSessionMode::Configure;
-        self.debugger = Some(dap::adapters::DebugAdapterName("fake-adapter".into()));
-
-        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);
-            });
-
-            configure.cwd.update(cx, |editor, cx| {
-                editor.clear(window, cx);
-                editor.set_text(cwd.as_ref(), window, cx);
-            });
-
-            configure.stop_on_entry = match stop_on_entry {
-                true => ToggleState::Selected,
-                _ => ToggleState::Unselected,
-            }
-        })
-    }
-
-    pub(crate) fn save_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        self.save_debug_scenario(window, cx);
-    }
+impl NewProcessModal {
+    // #[cfg(test)]
+    // pub(crate) fn set_configure(
+    //     &mut self,
+    //     program: impl AsRef<str>,
+    //     cwd: impl AsRef<str>,
+    //     stop_on_entry: bool,
+    //     window: &mut Window,
+    //     cx: &mut Context<Self>,
+    // ) {
+    //     self.mode = NewProcessMode::Launch;
+    //     self.debugger = Some(dap::adapters::DebugAdapterName("fake-adapter".into()));
+
+    //     self.launch_mode.update(cx, |configure, cx| {
+    //         configure.program.update(cx, |editor, cx| {
+    //             editor.clear(window, cx);
+    //             editor.set_text(program.as_ref(), window, cx);
+    //         });
+
+    //         configure.cwd.update(cx, |editor, cx| {
+    //             editor.clear(window, cx);
+    //             editor.set_text(cwd.as_ref(), window, cx);
+    //         });
+
+    //         configure.stop_on_entry = match stop_on_entry {
+    //             true => ToggleState::Selected,
+    //             _ => ToggleState::Unselected,
+    //         }
+    //     })
+    // }
+
+    // pub(crate) fn save_scenario(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
+    //     self.save_debug_scenario(window, cx);
+    // }
 }

crates/debugger_ui/src/session/running.rs 🔗

@@ -8,7 +8,7 @@ pub mod variable_list;
 use std::{any::Any, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration};
 
 use crate::{
-    new_session_modal::resolve_path,
+    new_process_modal::resolve_path,
     persistence::{self, DebuggerPaneItem, SerializedLayout},
 };
 
@@ -566,7 +566,7 @@ impl RunningState {
         }
     }
 
-    pub(crate) fn relativlize_paths(
+    pub(crate) fn relativize_paths(
         key: Option<&str>,
         config: &mut serde_json::Value,
         context: &TaskContext,
@@ -574,12 +574,12 @@ impl RunningState {
         match config {
             serde_json::Value::Object(obj) => {
                 obj.iter_mut()
-                    .for_each(|(key, value)| Self::relativlize_paths(Some(key), value, context));
+                    .for_each(|(key, value)| Self::relativize_paths(Some(key), value, context));
             }
             serde_json::Value::Array(array) => {
                 array
                     .iter_mut()
-                    .for_each(|value| Self::relativlize_paths(None, value, context));
+                    .for_each(|value| Self::relativize_paths(None, value, context));
             }
             serde_json::Value::String(s) if key == Some("program") || key == Some("cwd") => {
                 // Some built-in zed tasks wrap their arguments in quotes as they might contain spaces.
@@ -806,7 +806,7 @@ impl RunningState {
                 mut config,
                 tcp_connection,
             } = scenario;
-            Self::relativlize_paths(None, &mut config, &task_context);
+            Self::relativize_paths(None, &mut config, &task_context);
             Self::substitute_variables_in_config(&mut config, &task_context);
 
             let request_type = dap_registry

crates/debugger_ui/src/tests.rs 🔗

@@ -25,7 +25,7 @@ mod inline_values;
 #[cfg(test)]
 mod module_list;
 #[cfg(test)]
-mod new_session_modal;
+mod new_process_modal;
 #[cfg(test)]
 mod persistence;
 #[cfg(test)]

crates/debugger_ui/src/tests/new_session_modal.rs → crates/debugger_ui/src/tests/new_process_modal.rs 🔗

@@ -1,13 +1,13 @@
 use dap::DapRegistry;
 use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
-use project::{FakeFs, Fs, Project};
+use project::{FakeFs, Project};
 use serde_json::json;
 use std::sync::Arc;
 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::new_process_modal::NewProcessMode;
 use crate::tests::{init_test, init_test_workspace};
 
 #[gpui::test]
@@ -152,111 +152,111 @@ async fn test_debug_session_substitutes_variables_and_relativizes_paths(
     }
 }
 
-#[gpui::test]
-async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(executor.clone());
-    fs.insert_tree(
-        path!("/project"),
-        json!({
-            "main.rs": "fn main() {}"
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
-    let workspace = init_test_workspace(&project, cx).await;
-    let cx = &mut VisualTestContext::from_window(*workspace, cx);
-
-    workspace
-        .update(cx, |workspace, window, cx| {
-            crate::new_session_modal::NewSessionModal::show(
-                workspace,
-                window,
-                NewSessionMode::Launch,
-                None,
-                cx,
-            );
-        })
-        .unwrap();
-
-    cx.run_until_parked();
-
-    let modal = workspace
-        .update(cx, |workspace, _, cx| {
-            workspace.active_modal::<crate::new_session_modal::NewSessionModal>(cx)
-        })
-        .unwrap()
-        .expect("Modal should be active");
-
-    modal.update_in(cx, |modal, window, cx| {
-        modal.set_configure("/project/main", "/project", false, window, cx);
-        modal.save_scenario(window, cx);
-    });
-
-    cx.executor().run_until_parked();
-
-    let debug_json_content = fs
-        .load(path!("/project/.zed/debug.json").as_ref())
-        .await
-        .expect("debug.json should exist");
-
-    let expected_content = vec![
-        "[",
-        "  {",
-        r#"    "adapter": "fake-adapter","#,
-        r#"    "label": "main (fake-adapter)","#,
-        r#"    "request": "launch","#,
-        r#"    "program": "/project/main","#,
-        r#"    "cwd": "/project","#,
-        r#"    "args": [],"#,
-        r#"    "env": {}"#,
-        "  }",
-        "]",
-    ];
-
-    let actual_lines: Vec<&str> = debug_json_content.lines().collect();
-    pretty_assertions::assert_eq!(expected_content, actual_lines);
-
-    modal.update_in(cx, |modal, window, cx| {
-        modal.set_configure("/project/other", "/project", true, window, cx);
-        modal.save_scenario(window, cx);
-    });
-
-    cx.executor().run_until_parked();
-
-    let debug_json_content = fs
-        .load(path!("/project/.zed/debug.json").as_ref())
-        .await
-        .expect("debug.json should exist after second save");
-
-    let expected_content = vec![
-        "[",
-        "  {",
-        r#"    "adapter": "fake-adapter","#,
-        r#"    "label": "main (fake-adapter)","#,
-        r#"    "request": "launch","#,
-        r#"    "program": "/project/main","#,
-        r#"    "cwd": "/project","#,
-        r#"    "args": [],"#,
-        r#"    "env": {}"#,
-        "  },",
-        "  {",
-        r#"    "adapter": "fake-adapter","#,
-        r#"    "label": "other (fake-adapter)","#,
-        r#"    "request": "launch","#,
-        r#"    "program": "/project/other","#,
-        r#"    "cwd": "/project","#,
-        r#"    "args": [],"#,
-        r#"    "env": {}"#,
-        "  }",
-        "]",
-    ];
-
-    let actual_lines: Vec<&str> = debug_json_content.lines().collect();
-    pretty_assertions::assert_eq!(expected_content, actual_lines);
-}
+// #[gpui::test]
+// async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut TestAppContext) {
+//     init_test(cx);
+
+//     let fs = FakeFs::new(executor.clone());
+//     fs.insert_tree(
+//         path!("/project"),
+//         json!({
+//             "main.rs": "fn main() {}"
+//         }),
+//     )
+//     .await;
+
+//     let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
+//     let workspace = init_test_workspace(&project, cx).await;
+//     let cx = &mut VisualTestContext::from_window(*workspace, cx);
+
+//     workspace
+//         .update(cx, |workspace, window, cx| {
+//             crate::new_process_modal::NewProcessModal::show(
+//                 workspace,
+//                 window,
+//                 NewProcessMode::Debug,
+//                 None,
+//                 cx,
+//             );
+//         })
+//         .unwrap();
+
+//     cx.run_until_parked();
+
+//     let modal = workspace
+//         .update(cx, |workspace, _, cx| {
+//             workspace.active_modal::<crate::new_process_modal::NewProcessModal>(cx)
+//         })
+//         .unwrap()
+//         .expect("Modal should be active");
+
+//     modal.update_in(cx, |modal, window, cx| {
+//         modal.set_configure("/project/main", "/project", false, window, cx);
+//         modal.save_scenario(window, cx);
+//     });
+
+//     cx.executor().run_until_parked();
+
+//     let debug_json_content = fs
+//         .load(path!("/project/.zed/debug.json").as_ref())
+//         .await
+//         .expect("debug.json should exist");
+
+//     let expected_content = vec![
+//         "[",
+//         "  {",
+//         r#"    "adapter": "fake-adapter","#,
+//         r#"    "label": "main (fake-adapter)","#,
+//         r#"    "request": "launch","#,
+//         r#"    "program": "/project/main","#,
+//         r#"    "cwd": "/project","#,
+//         r#"    "args": [],"#,
+//         r#"    "env": {}"#,
+//         "  }",
+//         "]",
+//     ];
+
+//     let actual_lines: Vec<&str> = debug_json_content.lines().collect();
+//     pretty_assertions::assert_eq!(expected_content, actual_lines);
+
+//     modal.update_in(cx, |modal, window, cx| {
+//         modal.set_configure("/project/other", "/project", true, window, cx);
+//         modal.save_scenario(window, cx);
+//     });
+
+//     cx.executor().run_until_parked();
+
+//     let debug_json_content = fs
+//         .load(path!("/project/.zed/debug.json").as_ref())
+//         .await
+//         .expect("debug.json should exist after second save");
+
+//     let expected_content = vec![
+//         "[",
+//         "  {",
+//         r#"    "adapter": "fake-adapter","#,
+//         r#"    "label": "main (fake-adapter)","#,
+//         r#"    "request": "launch","#,
+//         r#"    "program": "/project/main","#,
+//         r#"    "cwd": "/project","#,
+//         r#"    "args": [],"#,
+//         r#"    "env": {}"#,
+//         "  },",
+//         "  {",
+//         r#"    "adapter": "fake-adapter","#,
+//         r#"    "label": "other (fake-adapter)","#,
+//         r#"    "request": "launch","#,
+//         r#"    "program": "/project/other","#,
+//         r#"    "cwd": "/project","#,
+//         r#"    "args": [],"#,
+//         r#"    "env": {}"#,
+//         "  }",
+//         "]",
+//     ];
+
+//     let actual_lines: Vec<&str> = debug_json_content.lines().collect();
+//     pretty_assertions::assert_eq!(expected_content, actual_lines);
+// }
 
 #[gpui::test]
 async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) {

crates/paths/src/paths.rs 🔗

@@ -408,6 +408,7 @@ pub fn task_file_name() -> &'static str {
 }
 
 /// Returns the relative path to a `debug.json` file within a project.
+/// .zed/debug.json
 pub fn local_debug_file_relative_path() -> &'static Path {
     Path::new(".zed/debug.json")
 }

crates/settings/src/settings.rs 🔗

@@ -115,3 +115,7 @@ pub fn initial_tasks_content() -> Cow<'static, str> {
 pub fn initial_debug_tasks_content() -> Cow<'static, str> {
     asset_str::<SettingsAssets>("settings/initial_debug_tasks.json")
 }
+
+pub fn initial_local_debug_tasks_content() -> Cow<'static, str> {
+    asset_str::<SettingsAssets>("settings/initial_local_debug_tasks.json")
+}

crates/zed/src/zed.rs 🔗

@@ -50,8 +50,8 @@ use rope::Rope;
 use search::project_search::ProjectSearchBar;
 use settings::{
     DEFAULT_KEYMAP_PATH, InvalidSettingsError, KeymapFile, KeymapFileLoadResult, Settings,
-    SettingsStore, VIM_KEYMAP_PATH, initial_debug_tasks_content, initial_project_settings_content,
-    initial_tasks_content, update_settings_file,
+    SettingsStore, VIM_KEYMAP_PATH, initial_local_debug_tasks_content,
+    initial_project_settings_content, initial_tasks_content, update_settings_file,
 };
 use std::path::PathBuf;
 use std::sync::atomic::{self, AtomicBool};
@@ -740,6 +740,14 @@ fn register_actions(
                 cx,
             );
         })
+        .register_action(move |_: &mut Workspace, _: &OpenDebugTasks, window, cx| {
+            open_settings_file(
+                paths::debug_scenarios_file(),
+                || settings::initial_debug_tasks_content().as_ref().into(),
+                window,
+                cx,
+            );
+        })
         .register_action(open_project_settings_file)
         .register_action(open_project_tasks_file)
         .register_action(open_project_debug_tasks_file)
@@ -1508,7 +1516,7 @@ fn open_project_debug_tasks_file(
     open_local_file(
         workspace,
         local_debug_file_relative_path(),
-        initial_debug_tasks_content(),
+        initial_local_debug_tasks_content(),
         window,
         cx,
     )