Simplify debug launcher UI (#31928)

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
This commit is contained in:
Mikayla Maki 2025-06-02 14:24:08 -07:00 committed by GitHub
parent f1aab1120d
commit b7ec437b13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 497 additions and 490 deletions

View file

@ -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", "label": "Debug active PHP file",

View file

@ -0,0 +1,5 @@
// Project-local debug tasks
//
// For more documentation on how to configure debug tasks,
// see: https://zed.dev/docs/debugger
[]

View file

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

View file

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

View file

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

View file

@ -1,11 +1,10 @@
use collections::FxHashMap; use collections::FxHashMap;
use language::{LanguageRegistry, Point, Selection}; use language::LanguageRegistry;
use paths::local_debug_file_relative_path;
use std::{ use std::{
borrow::Cow, borrow::Cow,
ops::Not,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
time::Duration,
usize, usize,
}; };
use tasks_ui::{TaskOverrides, TasksModal}; use tasks_ui::{TaskOverrides, TasksModal};
@ -13,45 +12,47 @@ use tasks_ui::{TaskOverrides, TasksModal};
use dap::{ use dap::{
DapRegistry, DebugRequest, TelemetrySpawnLocation, adapters::DebugAdapterName, send_telemetry, 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 fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
Animation, AnimationExt as _, App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle,
Focusable, KeyContext, Render, Subscription, TextStyle, Transformation, WeakEntity, percentage, InteractiveText, KeyContext, PromptButton, PromptLevel, Render, StyledText, Subscription,
TextStyle, UnderlineStyle, WeakEntity,
}; };
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch}; use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
use project::{ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore}; use project::{ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore};
use settings::Settings; use settings::{Settings, initial_local_debug_tasks_content};
use task::{DebugScenario, LaunchRequest, RevealTarget, ZedDebugConfig}; use task::{DebugScenario, RevealTarget, ZedDebugConfig};
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{ use ui::{
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context, 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 _, IconWithIndicator, Indicator, InteractiveElement, IntoElement, Label, LabelCommon as _,
ListItem, ListItemSpacing, ParentElement, RenderOnce, SharedString, Styled, StyledExt, 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 util::ResultExt;
use workspace::{ModalView, Workspace, pane}; use workspace::{ModalView, Workspace, pane};
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel}; use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
enum SaveScenarioState { // enum SaveScenarioState {
Saving, // Saving,
Saved((ProjectPath, SharedString)), // Saved((ProjectPath, SharedString)),
Failed(SharedString), // Failed(SharedString),
} // }
pub(super) struct NewSessionModal { pub(super) struct NewProcessModal {
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
debug_panel: WeakEntity<DebugPanel>, debug_panel: WeakEntity<DebugPanel>,
mode: NewSessionMode, mode: NewProcessMode,
launch_picker: Entity<Picker<DebugScenarioDelegate>>, debug_picker: Entity<Picker<DebugDelegate>>,
attach_mode: Entity<AttachMode>, attach_mode: Entity<AttachMode>,
configure_mode: Entity<ConfigureMode>, launch_mode: Entity<LaunchMode>,
task_mode: TaskMode, task_mode: TaskMode,
debugger: Option<DebugAdapterName>, debugger: Option<DebugAdapterName>,
save_scenario_state: Option<SaveScenarioState>, // save_scenario_state: Option<SaveScenarioState>,
_subscriptions: [Subscription; 3], _subscriptions: [Subscription; 3],
} }
@ -73,11 +74,11 @@ fn suggested_label(request: &DebugRequest, debugger: &str) -> SharedString {
} }
} }
impl NewSessionModal { impl NewProcessModal {
pub(super) fn show( pub(super) fn show(
workspace: &mut Workspace, workspace: &mut Workspace,
window: &mut Window, window: &mut Window,
mode: NewSessionMode, mode: NewProcessMode,
reveal_target: Option<RevealTarget>, reveal_target: Option<RevealTarget>,
cx: &mut Context<Workspace>, cx: &mut Context<Workspace>,
) { ) {
@ -101,12 +102,12 @@ impl NewSessionModal {
let launch_picker = cx.new(|cx| { let launch_picker = cx.new(|cx| {
let mut delegate = 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); delegate.task_contexts_loaded(task_contexts.clone(), languages, window, cx);
Picker::uniform_list(delegate, window, cx).modal(false) 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 if let Some(active_cwd) = task_contexts
.active_context() .active_context()
.and_then(|context| context.cwd.clone()) .and_then(|context| context.cwd.clone())
@ -148,15 +149,15 @@ impl NewSessionModal {
]; ];
Self { Self {
launch_picker, debug_picker: launch_picker,
attach_mode, attach_mode,
configure_mode, launch_mode: configure_mode,
task_mode, task_mode,
debugger: None, debugger: None,
mode, mode,
debug_panel: debug_panel.downgrade(), debug_panel: debug_panel.downgrade(),
workspace: workspace_handle, workspace: workspace_handle,
save_scenario_state: None, // save_scenario_state: None,
_subscriptions, _subscriptions,
} }
}); });
@ -170,49 +171,49 @@ impl NewSessionModal {
fn render_mode(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement { 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); let dap_menu = self.adapter_drop_down_menu(window, cx);
match self.mode { match self.mode {
NewSessionMode::Task => self NewProcessMode::Task => self
.task_mode .task_mode
.task_modal .task_modal
.read(cx) .read(cx)
.picker .picker
.clone() .clone()
.into_any_element(), .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() 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() this.clone().render(dap_menu, window, cx).into_any_element()
}), }),
NewSessionMode::Launch => v_flex() NewProcessMode::Debug => v_flex()
.w(rems(34.)) .w(rems(34.))
.child(self.launch_picker.clone()) .child(self.debug_picker.clone())
.into_any_element(), .into_any_element(),
} }
} }
fn mode_focus_handle(&self, cx: &App) -> FocusHandle { fn mode_focus_handle(&self, cx: &App) -> FocusHandle {
match self.mode { match self.mode {
NewSessionMode::Task => self.task_mode.task_modal.focus_handle(cx), NewProcessMode::Task => self.task_mode.task_modal.focus_handle(cx),
NewSessionMode::Attach => self.attach_mode.read(cx).attach_picker.focus_handle(cx), NewProcessMode::Attach => self.attach_mode.read(cx).attach_picker.focus_handle(cx),
NewSessionMode::Configure => self.configure_mode.read(cx).program.focus_handle(cx), NewProcessMode::Launch => self.launch_mode.read(cx).program.focus_handle(cx),
NewSessionMode::Launch => self.launch_picker.focus_handle(cx), NewProcessMode::Debug => self.debug_picker.focus_handle(cx),
} }
} }
fn debug_scenario(&self, debugger: &str, cx: &App) -> Option<DebugScenario> { fn debug_scenario(&self, debugger: &str, cx: &App) -> Option<DebugScenario> {
let request = match self.mode { let request = match self.mode {
NewSessionMode::Configure => Some(DebugRequest::Launch( NewProcessMode::Launch => Some(DebugRequest::Launch(
self.configure_mode.read(cx).debug_request(cx), 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(), self.attach_mode.read(cx).debug_request(),
)), )),
_ => None, _ => None,
}?; }?;
let label = suggested_label(&request, debugger); let label = suggested_label(&request, debugger);
let stop_on_entry = if let NewSessionMode::Configure = &self.mode { let stop_on_entry = if let NewProcessMode::Launch = &self.mode {
Some(self.configure_mode.read(cx).stop_on_entry.selected()) Some(self.launch_mode.read(cx).stop_on_entry.selected())
} else { } else {
None None
}; };
@ -229,18 +230,29 @@ impl NewSessionModal {
.and_then(|adapter| adapter.config_from_zed_format(session_scenario).ok()) .and_then(|adapter| adapter.config_from_zed_format(session_scenario).ok())
} }
fn start_new_session(&self, window: &mut Window, cx: &mut Context<Self>) { fn start_new_session(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some(debugger) = self.debugger.as_ref() else { if self.debugger.as_ref().is_none() {
return; return;
}; }
if let NewSessionMode::Launch = &self.mode { if let NewProcessMode::Debug = &self.mode {
self.launch_picker.update(cx, |picker, cx| { self.debug_picker.update(cx, |picker, cx| {
picker.delegate.confirm(false, window, cx); picker.delegate.confirm(false, window, cx);
}); });
return; 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 { let Some(config) = self.debug_scenario(debugger, cx) else {
log::error!("debug config not found in mode: {}", self.mode); log::error!("debug config not found in mode: {}", self.mode);
return; return;
@ -289,179 +301,50 @@ impl NewSessionModal {
} }
fn task_contexts(&self, cx: &App) -> Option<Arc<TaskContexts>> { fn task_contexts(&self, cx: &App) -> Option<Arc<TaskContexts>> {
self.launch_picker.read(cx).delegate.task_contexts.clone() self.debug_picker.read(cx).delegate.task_contexts.clone()
} }
fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) { // fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some((save_scenario, scenario_label)) = self // let Some((save_scenario, scenario_label)) = self
.debugger // .debugger
.as_ref() // .as_ref()
.and_then(|debugger| self.debug_scenario(&debugger, cx)) // .and_then(|debugger| self.debug_scenario(&debugger, cx))
.zip(self.task_contexts(cx).and_then(|tcx| tcx.worktree())) // .zip(self.task_contexts(cx).and_then(|tcx| tcx.worktree()))
.and_then(|(scenario, worktree_id)| { // .and_then(|(scenario, worktree_id)| {
self.debug_panel // self.debug_panel
.update(cx, |panel, cx| { // .update(cx, |panel, cx| {
panel.save_scenario(&scenario, worktree_id, window, cx) // panel.save_scenario(&scenario, worktree_id, window, cx)
}) // })
.ok() // .ok()
.zip(Some(scenario.label.clone())) // .zip(Some(scenario.label.clone()))
}) // })
else { // else {
return; // return;
}; // };
self.save_scenario_state = Some(SaveScenarioState::Saving); // self.save_scenario_state = Some(SaveScenarioState::Saving);
cx.spawn(async move |this, cx| { // cx.spawn(async move |this, cx| {
let res = save_scenario.await; // let res = save_scenario.await;
this.update(cx, |this, _| match res { // this.update(cx, |this, _| match res {
Ok(saved_file) => { // Ok(saved_file) => {
this.save_scenario_state = // this.save_scenario_state =
Some(SaveScenarioState::Saved((saved_file, scenario_label))) // Some(SaveScenarioState::Saved((saved_file, scenario_label)))
} // }
Err(error) => { // Err(error) => {
this.save_scenario_state = // this.save_scenario_state =
Some(SaveScenarioState::Failed(error.to_string().into())) // Some(SaveScenarioState::Failed(error.to_string().into()))
} // }
}) // })
.ok(); // .ok();
cx.background_executor().timer(Duration::from_secs(3)).await; // cx.background_executor().timer(Duration::from_secs(3)).await;
this.update(cx, |this, _| this.save_scenario_state.take()) // this.update(cx, |this, _| this.save_scenario_state.take())
.ok(); // .ok();
}) // })
.detach(); // .detach();
} // }
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 adapter_drop_down_menu( fn adapter_drop_down_menu(
&mut self, &mut self,
@ -513,7 +396,7 @@ impl NewSessionModal {
weak.update(cx, |this, cx| { weak.update(cx, |this, cx| {
this.debugger = Some(name.clone()); this.debugger = Some(name.clone());
cx.notify(); cx.notify();
if let NewSessionMode::Attach = &this.mode { if let NewProcessMode::Attach = &this.mode {
Self::update_attach_picker(&this.attach_mode, &name, window, cx); 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"); static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select Debugger");
#[derive(Clone)] #[derive(Clone)]
pub(crate) enum NewSessionMode { pub(crate) enum NewProcessMode {
Task, Task,
Configure,
Attach,
Launch, 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mode = match self { let mode = match self {
NewSessionMode::Task => "Run", NewProcessMode::Task => "Run",
NewSessionMode::Launch => "Debug", NewProcessMode::Debug => "Debug",
NewSessionMode::Attach => "Attach", NewProcessMode::Attach => "Attach",
NewSessionMode::Configure => "Configure Debugger", NewProcessMode::Launch => "Launch",
}; };
write!(f, "{}", mode) write!(f, "{}", mode)
} }
} }
impl Focusable for NewSessionMode { impl Focusable for NewProcessMode {
fn focus_handle(&self, cx: &App) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
cx.focus_handle() cx.focus_handle()
} }
@ -598,7 +545,7 @@ fn render_editor(editor: &Entity<Editor>, window: &mut Window, cx: &App) -> impl
.bg(theme.colors().editor_background) .bg(theme.colors().editor_background)
} }
impl Render for NewSessionModal { impl Render for NewProcessModal {
fn render( fn render(
&mut self, &mut self,
window: &mut ui::Window, window: &mut ui::Window,
@ -620,10 +567,10 @@ impl Render for NewSessionModal {
})) }))
.on_action(cx.listener(|this, _: &pane::ActivateNextItem, window, cx| { .on_action(cx.listener(|this, _: &pane::ActivateNextItem, window, cx| {
this.mode = match this.mode { this.mode = match this.mode {
NewSessionMode::Task => NewSessionMode::Launch, NewProcessMode::Task => NewProcessMode::Debug,
NewSessionMode::Launch => NewSessionMode::Attach, NewProcessMode::Debug => NewProcessMode::Attach,
NewSessionMode::Attach => NewSessionMode::Configure, NewProcessMode::Attach => NewProcessMode::Launch,
NewSessionMode::Configure => NewSessionMode::Task, NewProcessMode::Launch => NewProcessMode::Task,
}; };
this.mode_focus_handle(cx).focus(window); this.mode_focus_handle(cx).focus(window);
@ -631,10 +578,10 @@ impl Render for NewSessionModal {
.on_action( .on_action(
cx.listener(|this, _: &pane::ActivatePreviousItem, window, cx| { cx.listener(|this, _: &pane::ActivatePreviousItem, window, cx| {
this.mode = match this.mode { this.mode = match this.mode {
NewSessionMode::Task => NewSessionMode::Configure, NewProcessMode::Task => NewProcessMode::Launch,
NewSessionMode::Launch => NewSessionMode::Task, NewProcessMode::Debug => NewProcessMode::Task,
NewSessionMode::Attach => NewSessionMode::Launch, NewProcessMode::Attach => NewProcessMode::Debug,
NewSessionMode::Configure => NewSessionMode::Attach, NewProcessMode::Launch => NewProcessMode::Attach,
}; };
this.mode_focus_handle(cx).focus(window); this.mode_focus_handle(cx).focus(window);
@ -652,13 +599,13 @@ impl Render for NewSessionModal {
.child( .child(
ToggleButton::new( ToggleButton::new(
"debugger-session-ui-tasks-button", "debugger-session-ui-tasks-button",
NewSessionMode::Task.to_string(), NewProcessMode::Task.to_string(),
) )
.size(ButtonSize::Default) .size(ButtonSize::Default)
.toggle_state(matches!(self.mode, NewSessionMode::Task)) .toggle_state(matches!(self.mode, NewProcessMode::Task))
.style(ui::ButtonStyle::Subtle) .style(ui::ButtonStyle::Subtle)
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.mode = NewSessionMode::Task; this.mode = NewProcessMode::Task;
this.mode_focus_handle(cx).focus(window); this.mode_focus_handle(cx).focus(window);
cx.notify(); cx.notify();
})) }))
@ -667,13 +614,13 @@ impl Render for NewSessionModal {
.child( .child(
ToggleButton::new( ToggleButton::new(
"debugger-session-ui-launch-button", "debugger-session-ui-launch-button",
NewSessionMode::Launch.to_string(), NewProcessMode::Debug.to_string(),
) )
.size(ButtonSize::Default) .size(ButtonSize::Default)
.style(ui::ButtonStyle::Subtle) .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| { .on_click(cx.listener(|this, _, window, cx| {
this.mode = NewSessionMode::Launch; this.mode = NewProcessMode::Debug;
this.mode_focus_handle(cx).focus(window); this.mode_focus_handle(cx).focus(window);
cx.notify(); cx.notify();
})) }))
@ -682,13 +629,13 @@ impl Render for NewSessionModal {
.child( .child(
ToggleButton::new( ToggleButton::new(
"debugger-session-ui-attach-button", "debugger-session-ui-attach-button",
NewSessionMode::Attach.to_string(), NewProcessMode::Attach.to_string(),
) )
.size(ButtonSize::Default) .size(ButtonSize::Default)
.toggle_state(matches!(self.mode, NewSessionMode::Attach)) .toggle_state(matches!(self.mode, NewProcessMode::Attach))
.style(ui::ButtonStyle::Subtle) .style(ui::ButtonStyle::Subtle)
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.mode = NewSessionMode::Attach; this.mode = NewProcessMode::Attach;
if let Some(debugger) = this.debugger.as_ref() { if let Some(debugger) = this.debugger.as_ref() {
Self::update_attach_picker( Self::update_attach_picker(
@ -706,13 +653,13 @@ impl Render for NewSessionModal {
.child( .child(
ToggleButton::new( ToggleButton::new(
"debugger-session-ui-custom-button", "debugger-session-ui-custom-button",
NewSessionMode::Configure.to_string(), NewProcessMode::Launch.to_string(),
) )
.size(ButtonSize::Default) .size(ButtonSize::Default)
.toggle_state(matches!(self.mode, NewSessionMode::Configure)) .toggle_state(matches!(self.mode, NewProcessMode::Launch))
.style(ui::ButtonStyle::Subtle) .style(ui::ButtonStyle::Subtle)
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.mode = NewSessionMode::Configure; this.mode = NewProcessMode::Launch;
this.mode_focus_handle(cx).focus(window); this.mode_focus_handle(cx).focus(window);
cx.notify(); cx.notify();
})) }))
@ -733,30 +680,42 @@ impl Render for NewSessionModal {
.border_t_1() .border_t_1()
.w_full(); .w_full();
match self.mode { match self.mode {
NewSessionMode::Configure => el.child( NewProcessMode::Launch => el.child(
container container
.child( .child(
h_flex() h_flex()
.text_ui_sm(cx)
.text_color(Color::Muted.color(cx))
.child( .child(
Button::new( InteractiveText::new(
"new-session-modal-back", "open-debug-json",
"Save to .zed/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| { .on_click(
this.save_debug_scenario(window, cx); vec![5..20],
})) {
.disabled( let this = cx.entity();
self.debugger.is_none() move |_, window, cx| {
|| self this.update(cx, |this, cx| {
.configure_mode this.open_debug_json(window, cx);
.read(cx) })
.program }
.read(cx) },
.is_empty(cx)
|| self.save_scenario_state.is_some(),
), ),
) ),
.child(self.render_save_state(cx)),
) )
.child( .child(
Button::new("debugger-spawn", "Start") Button::new("debugger-spawn", "Start")
@ -766,7 +725,7 @@ impl Render for NewSessionModal {
.disabled( .disabled(
self.debugger.is_none() self.debugger.is_none()
|| self || self
.configure_mode .launch_mode
.read(cx) .read(cx)
.program .program
.read(cx) .read(cx)
@ -774,7 +733,7 @@ impl Render for NewSessionModal {
), ),
), ),
), ),
NewSessionMode::Attach => el.child( NewProcessMode::Attach => el.child(
container container
.child(div().child(self.adapter_drop_down_menu(window, cx))) .child(div().child(self.adapter_drop_down_menu(window, cx)))
.child( .child(
@ -797,21 +756,21 @@ impl Render for NewSessionModal {
), ),
), ),
), ),
NewSessionMode::Launch => el, NewProcessMode::Debug => el,
NewSessionMode::Task => el, NewProcessMode::Task => el,
} }
}) })
} }
} }
impl EventEmitter<DismissEvent> for NewSessionModal {} impl EventEmitter<DismissEvent> for NewProcessModal {}
impl Focusable for NewSessionModal { impl Focusable for NewProcessModal {
fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle { fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
self.mode_focus_handle(cx) self.mode_focus_handle(cx)
} }
} }
impl ModalView for NewSessionModal {} impl ModalView for NewProcessModal {}
impl RenderOnce for AttachMode { impl RenderOnce for AttachMode {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
@ -823,44 +782,30 @@ impl RenderOnce for AttachMode {
} }
#[derive(Clone)] #[derive(Clone)]
pub(super) struct ConfigureMode { pub(super) struct LaunchMode {
program: Entity<Editor>, program: Entity<Editor>,
cwd: Entity<Editor>, cwd: Entity<Editor>,
stop_on_entry: ToggleState, stop_on_entry: ToggleState,
// save_to_debug_json: ToggleState,
} }
impl ConfigureMode { impl LaunchMode {
pub(super) fn new( pub(super) fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
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));
let program = cx.new(|cx| Editor::single_line(window, cx)); let program = cx.new(|cx| Editor::single_line(window, cx));
program.update(cx, |this, cx| { program.update(cx, |this, cx| {
this.set_placeholder_text( this.set_placeholder_text("ENV=Zed ~/bin/debugger --launch", cx);
"ALPHA=\"Windows\" BETA=\"Wen\" your_program --arg1 --arg2=arg3",
cx,
);
if let Some(past_program) = past_program {
this.set_text(past_program, window, cx);
};
}); });
let cwd = cx.new(|cx| Editor::single_line(window, cx)); let cwd = cx.new(|cx| Editor::single_line(window, cx));
cwd.update(cx, |this, cx| { cwd.update(cx, |this, cx| {
this.set_placeholder_text("Working Directory", cx); this.set_placeholder_text("Ex: $ZED_WORKTREE_ROOT", cx);
if let Some(past_cwd) = past_cwd {
this.set_text(past_cwd.to_string_lossy(), window, cx);
};
}); });
cx.new(|_| Self { cx.new(|_| Self {
program, program,
cwd, cwd,
stop_on_entry: ToggleState::Unselected, 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 { 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) { if cfg!(windows) {
return task::LaunchRequest { return task::LaunchRequest {
program: self.program.read(cx).text(cx), program: self.program.read(cx).text(cx),
cwd: path.is_empty().not().then(|| PathBuf::from(path)), cwd,
args: Default::default(), args: Default::default(),
env: Default::default(), env: Default::default(),
}; };
@ -902,7 +853,7 @@ impl ConfigureMode {
task::LaunchRequest { task::LaunchRequest {
program, program,
cwd: path.is_empty().not().then(|| PathBuf::from(path)), cwd,
args, args,
env, env,
} }
@ -929,7 +880,17 @@ impl ConfigureMode {
.gap(ui::DynamicSpacing::Base08.rems(cx)) .gap(ui::DynamicSpacing::Base08.rems(cx))
.child(adapter_menu), .child(adapter_menu),
) )
.child(
Label::new("Debugger Program")
.size(ui::LabelSize::Small)
.color(Color::Muted),
)
.child(render_editor(&self.program, window, cx)) .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(render_editor(&self.cwd, window, cx))
.child( .child(
CheckboxWithLabel::new( CheckboxWithLabel::new(
@ -950,6 +911,27 @@ impl ConfigureMode {
) )
.checkbox_position(ui::IconPosition::End), .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>, debugger: Option<DebugAdapterName>,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
window: &mut Window, window: &mut Window,
cx: &mut Context<NewSessionModal>, cx: &mut Context<NewProcessModal>,
) -> Entity<Self> { ) -> Entity<Self> {
let definition = ZedDebugConfig { let definition = ZedDebugConfig {
adapter: debugger.unwrap_or(DebugAdapterName("".into())).0, adapter: debugger.unwrap_or(DebugAdapterName("".into())).0,
@ -994,7 +976,7 @@ pub(super) struct TaskMode {
pub(super) task_modal: Entity<TasksModal>, pub(super) task_modal: Entity<TasksModal>,
} }
pub(super) struct DebugScenarioDelegate { pub(super) struct DebugDelegate {
task_store: Entity<TaskStore>, task_store: Entity<TaskStore>,
candidates: Vec<(Option<TaskSourceKind>, DebugScenario)>, candidates: Vec<(Option<TaskSourceKind>, DebugScenario)>,
selected_index: usize, selected_index: usize,
@ -1006,7 +988,7 @@ pub(super) struct DebugScenarioDelegate {
last_used_candidate_index: Option<usize>, last_used_candidate_index: Option<usize>,
} }
impl DebugScenarioDelegate { impl DebugDelegate {
pub(super) fn new(debug_panel: WeakEntity<DebugPanel>, task_store: Entity<TaskStore>) -> Self { pub(super) fn new(debug_panel: WeakEntity<DebugPanel>, task_store: Entity<TaskStore>) -> Self {
Self { Self {
task_store, task_store,
@ -1085,7 +1067,7 @@ impl DebugScenarioDelegate {
} }
} }
impl PickerDelegate for DebugScenarioDelegate { impl PickerDelegate for DebugDelegate {
type ListItem = ui::ListItem; type ListItem = ui::ListItem;
fn match_count(&self) -> usize { fn match_count(&self) -> usize {
@ -1270,37 +1252,38 @@ pub(crate) fn resolve_path(path: &mut String) {
} }
#[cfg(test)] #[cfg(test)]
impl NewSessionModal { impl NewProcessModal {
pub(crate) fn set_configure( // #[cfg(test)]
&mut self, // pub(crate) fn set_configure(
program: impl AsRef<str>, // &mut self,
cwd: impl AsRef<str>, // program: impl AsRef<str>,
stop_on_entry: bool, // cwd: impl AsRef<str>,
window: &mut Window, // stop_on_entry: bool,
cx: &mut Context<Self>, // window: &mut Window,
) { // cx: &mut Context<Self>,
self.mode = NewSessionMode::Configure; // ) {
self.debugger = Some(dap::adapters::DebugAdapterName("fake-adapter".into())); // self.mode = NewProcessMode::Launch;
// self.debugger = Some(dap::adapters::DebugAdapterName("fake-adapter".into()));
self.configure_mode.update(cx, |configure, cx| { // self.launch_mode.update(cx, |configure, cx| {
configure.program.update(cx, |editor, cx| { // configure.program.update(cx, |editor, cx| {
editor.clear(window, cx); // editor.clear(window, cx);
editor.set_text(program.as_ref(), window, cx); // editor.set_text(program.as_ref(), window, cx);
}); // });
configure.cwd.update(cx, |editor, cx| { // configure.cwd.update(cx, |editor, cx| {
editor.clear(window, cx); // editor.clear(window, cx);
editor.set_text(cwd.as_ref(), window, cx); // editor.set_text(cwd.as_ref(), window, cx);
}); // });
configure.stop_on_entry = match stop_on_entry { // configure.stop_on_entry = match stop_on_entry {
true => ToggleState::Selected, // true => ToggleState::Selected,
_ => ToggleState::Unselected, // _ => ToggleState::Unselected,
} // }
}) // })
} // }
pub(crate) fn save_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) { // pub(crate) fn save_scenario(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
self.save_debug_scenario(window, cx); // self.save_debug_scenario(window, cx);
} // }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -50,8 +50,8 @@ use rope::Rope;
use search::project_search::ProjectSearchBar; use search::project_search::ProjectSearchBar;
use settings::{ use settings::{
DEFAULT_KEYMAP_PATH, InvalidSettingsError, KeymapFile, KeymapFileLoadResult, Settings, DEFAULT_KEYMAP_PATH, InvalidSettingsError, KeymapFile, KeymapFileLoadResult, Settings,
SettingsStore, VIM_KEYMAP_PATH, initial_debug_tasks_content, initial_project_settings_content, SettingsStore, VIM_KEYMAP_PATH, initial_local_debug_tasks_content,
initial_tasks_content, update_settings_file, initial_project_settings_content, initial_tasks_content, update_settings_file,
}; };
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::{self, AtomicBool}; use std::sync::atomic::{self, AtomicBool};
@ -740,6 +740,14 @@ fn register_actions(
cx, 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_settings_file)
.register_action(open_project_tasks_file) .register_action(open_project_tasks_file)
.register_action(open_project_debug_tasks_file) .register_action(open_project_debug_tasks_file)
@ -1508,7 +1516,7 @@ fn open_project_debug_tasks_file(
open_local_file( open_local_file(
workspace, workspace,
local_debug_file_relative_path(), local_debug_file_relative_path(),
initial_debug_tasks_content(), initial_local_debug_tasks_content(),
window, window,
cx, cx,
) )