1use std::any::TypeId;
2
3use dap::debugger_settings::DebuggerSettings;
4use debugger_panel::DebugPanel;
5use editor::Editor;
6use gpui::{App, DispatchPhase, EntityInputHandler, actions};
7use new_process_modal::{NewProcessModal, NewProcessMode};
8use onboarding_modal::DebuggerOnboardingModal;
9use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};
10use session::DebugSession;
11use settings::Settings;
12use stack_trace_view::StackTraceView;
13use tasks_ui::{Spawn, TaskOverrides};
14use ui::{FluentBuilder, InteractiveElement};
15use util::maybe;
16use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
17use zed_actions::ToggleFocus;
18use zed_actions::debugger::OpenOnboardingModal;
19
20pub mod attach_modal;
21pub mod debugger_panel;
22mod dropdown_menus;
23mod new_process_modal;
24mod onboarding_modal;
25mod persistence;
26pub(crate) mod session;
27mod stack_trace_view;
28
29#[cfg(any(test, feature = "test-support"))]
30pub mod tests;
31
32actions!(
33 debugger,
34 [
35 /// Starts a new debugging session.
36 Start,
37 /// Continues execution until the next breakpoint.
38 Continue,
39 /// Detaches the debugger from the running process.
40 Detach,
41 /// Pauses the currently running program.
42 Pause,
43 /// Restarts the current debugging session.
44 Restart,
45 /// Reruns the current debugging session with the same configuration.
46 RerunSession,
47 /// Steps into the next function call.
48 StepInto,
49 /// Steps over the current line.
50 StepOver,
51 /// Steps out of the current function.
52 StepOut,
53 /// Steps back to the previous statement.
54 StepBack,
55 /// Stops the debugging session.
56 Stop,
57 /// Toggles whether to ignore all breakpoints.
58 ToggleIgnoreBreakpoints,
59 /// Clears all breakpoints in the project.
60 ClearAllBreakpoints,
61 /// Focuses on the debugger console panel.
62 FocusConsole,
63 /// Focuses on the variables panel.
64 FocusVariables,
65 /// Focuses on the breakpoint list panel.
66 FocusBreakpointList,
67 /// Focuses on the call stack frames panel.
68 FocusFrames,
69 /// Focuses on the loaded modules panel.
70 FocusModules,
71 /// Focuses on the loaded sources panel.
72 FocusLoadedSources,
73 /// Focuses on the terminal panel.
74 FocusTerminal,
75 /// Shows the stack trace for the current thread.
76 ShowStackTrace,
77 /// Toggles the thread picker dropdown.
78 ToggleThreadPicker,
79 /// Toggles the session picker dropdown.
80 ToggleSessionPicker,
81 /// Reruns the last debugging session.
82 #[action(deprecated_aliases = ["debugger::RerunLastSession"])]
83 Rerun,
84 /// Toggles expansion of the selected item in the debugger UI.
85 ToggleExpandItem,
86 /// Set a data breakpoint on the selected variable or memory region.
87 ToggleDataBreakpoint,
88 ]
89);
90
91actions!(
92 dev,
93 [
94 /// Copies debug adapter launch arguments to clipboard.
95 CopyDebugAdapterArguments
96 ]
97);
98
99pub fn init(cx: &mut App) {
100 DebuggerSettings::register(cx);
101 workspace::FollowableViewRegistry::register::<DebugSession>(cx);
102
103 cx.observe_new(|workspace: &mut Workspace, _, _| {
104 workspace
105 .register_action(spawn_task_or_modal)
106 .register_action(|workspace, _: &ToggleFocus, window, cx| {
107 workspace.toggle_panel_focus::<DebugPanel>(window, cx);
108 })
109 .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
110 NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
111 })
112 .register_action(|workspace: &mut Workspace, _: &Rerun, window, cx| {
113 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
114 return;
115 };
116
117 debug_panel.update(cx, |debug_panel, cx| {
118 debug_panel.rerun_last_session(workspace, window, cx);
119 })
120 })
121 .register_action(
122 |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
123 workspace.project().update(cx, |project, cx| {
124 project.dap_store().update(cx, |store, cx| {
125 store.shutdown_sessions(cx).detach();
126 })
127 })
128 },
129 )
130 .register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
131 DebuggerOnboardingModal::toggle(workspace, window, cx)
132 })
133 .register_action_renderer(|div, workspace, _, cx| {
134 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
135 return div;
136 };
137 let Some(active_item) = debug_panel
138 .read(cx)
139 .active_session()
140 .map(|session| session.read(cx).running_state().clone())
141 else {
142 return div;
143 };
144 let running_state = active_item.read(cx);
145 if running_state.session().read(cx).is_terminated() {
146 return div;
147 }
148
149 let caps = running_state.capabilities(cx);
150 let supports_step_back = caps.supports_step_back.unwrap_or_default();
151 let supports_detach = running_state.session().read(cx).is_attached();
152 let status = running_state.thread_status(cx);
153
154 let active_item = active_item.downgrade();
155 div.when(status == Some(ThreadStatus::Running), |div| {
156 let active_item = active_item.clone();
157 div.on_action(move |_: &Pause, _, cx| {
158 active_item
159 .update(cx, |item, cx| item.pause_thread(cx))
160 .ok();
161 })
162 })
163 .when(status == Some(ThreadStatus::Stopped), |div| {
164 div.on_action({
165 let active_item = active_item.clone();
166 move |_: &StepInto, _, cx| {
167 active_item.update(cx, |item, cx| item.step_in(cx)).ok();
168 }
169 })
170 .on_action({
171 let active_item = active_item.clone();
172 move |_: &StepOver, _, cx| {
173 active_item.update(cx, |item, cx| item.step_over(cx)).ok();
174 }
175 })
176 .on_action({
177 let active_item = active_item.clone();
178 move |_: &StepOut, _, cx| {
179 active_item.update(cx, |item, cx| item.step_out(cx)).ok();
180 }
181 })
182 .when(supports_step_back, |div| {
183 let active_item = active_item.clone();
184 div.on_action(move |_: &StepBack, _, cx| {
185 active_item.update(cx, |item, cx| item.step_back(cx)).ok();
186 })
187 })
188 .on_action({
189 let active_item = active_item.clone();
190 move |_: &Continue, _, cx| {
191 active_item
192 .update(cx, |item, cx| item.continue_thread(cx))
193 .ok();
194 }
195 })
196 .on_action(cx.listener(
197 |workspace, _: &ShowStackTrace, window, cx| {
198 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
199 return;
200 };
201
202 if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx) {
203 let is_active = workspace
204 .active_item(cx)
205 .is_some_and(|item| item.item_id() == existing.item_id());
206 workspace.activate_item(&existing, true, !is_active, window, cx);
207 } else {
208 let Some(active_session) = debug_panel.read(cx).active_session()
209 else {
210 return;
211 };
212
213 let project = workspace.project();
214
215 let stack_trace_view = active_session.update(cx, |session, cx| {
216 session.stack_trace_view(project, window, cx).clone()
217 });
218
219 workspace.add_item_to_active_pane(
220 Box::new(stack_trace_view),
221 None,
222 true,
223 window,
224 cx,
225 );
226 }
227 },
228 ))
229 })
230 .when(supports_detach, |div| {
231 let active_item = active_item.clone();
232 div.on_action(move |_: &Detach, _, cx| {
233 active_item
234 .update(cx, |item, cx| item.detach_client(cx))
235 .ok();
236 })
237 })
238 .on_action({
239 let active_item = active_item.clone();
240 move |_: &Restart, _, cx| {
241 active_item
242 .update(cx, |item, cx| item.restart_session(cx))
243 .ok();
244 }
245 })
246 .on_action({
247 let active_item = active_item.clone();
248 move |_: &RerunSession, window, cx| {
249 active_item
250 .update(cx, |item, cx| item.rerun_session(window, cx))
251 .ok();
252 }
253 })
254 .on_action({
255 let active_item = active_item.clone();
256 move |_: &Stop, _, cx| {
257 active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();
258 }
259 })
260 .on_action({
261 let active_item = active_item.clone();
262 move |_: &ToggleIgnoreBreakpoints, _, cx| {
263 active_item
264 .update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
265 .ok();
266 }
267 })
268 });
269 })
270 .detach();
271
272 cx.observe_new({
273 move |editor: &mut Editor, _, _| {
274 editor
275 .register_action_renderer(move |editor, window, cx| {
276 let Some(workspace) = editor.workspace() else {
277 return;
278 };
279 let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {
280 return;
281 };
282 let Some(active_session) = debug_panel
283 .clone()
284 .update(cx, |panel, _| panel.active_session())
285 else {
286 return;
287 };
288 let editor = cx.entity().downgrade();
289 window.on_action(TypeId::of::<editor::actions::RunToCursor>(), {
290 let editor = editor.clone();
291 let active_session = active_session.clone();
292 move |_, phase, _, cx| {
293 if phase != DispatchPhase::Bubble {
294 return;
295 }
296 maybe!({
297 let (buffer, position, _) = editor
298 .update(cx, |editor, cx| {
299 let cursor_point: language::Point =
300 editor.selections.newest(cx).head();
301
302 editor
303 .buffer()
304 .read(cx)
305 .point_to_buffer_point(cursor_point, cx)
306 })
307 .ok()??;
308
309 let path =
310 debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
311 &buffer, cx,
312 )?;
313
314 let source_breakpoint = SourceBreakpoint {
315 row: position.row,
316 path,
317 message: None,
318 condition: None,
319 hit_condition: None,
320 state: debugger::breakpoint_store::BreakpointState::Enabled,
321 };
322
323 active_session.update(cx, |session, cx| {
324 session.running_state().update(cx, |state, cx| {
325 if let Some(thread_id) = state.selected_thread_id() {
326 state.session().update(cx, |session, cx| {
327 session.run_to_position(
328 source_breakpoint,
329 thread_id,
330 cx,
331 );
332 })
333 }
334 });
335 });
336
337 Some(())
338 });
339 }
340 });
341
342 window.on_action(
343 TypeId::of::<editor::actions::EvaluateSelectedText>(),
344 move |_, _, window, cx| {
345 maybe!({
346 let text = editor
347 .update(cx, |editor, cx| {
348 editor.text_for_range(
349 editor.selections.newest(cx).range(),
350 &mut None,
351 window,
352 cx,
353 )
354 })
355 .ok()??;
356
357 active_session.update(cx, |session, cx| {
358 session.running_state().update(cx, |state, cx| {
359 let stack_id = state.selected_stack_frame_id(cx);
360
361 state.session().update(cx, |session, cx| {
362 session
363 .evaluate(text, None, stack_id, None, cx)
364 .detach();
365 });
366 });
367 });
368
369 Some(())
370 });
371 },
372 );
373 })
374 .detach();
375 }
376 })
377 .detach();
378}
379
380fn spawn_task_or_modal(
381 workspace: &mut Workspace,
382 action: &Spawn,
383 window: &mut ui::Window,
384 cx: &mut ui::Context<Workspace>,
385) {
386 match action {
387 Spawn::ByName {
388 task_name,
389 reveal_target,
390 } => {
391 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
392 reveal_target: Some(reveal_target),
393 });
394 let name = task_name.clone();
395 tasks_ui::spawn_tasks_filtered(
396 move |(_, task)| task.label.eq(&name),
397 overrides,
398 window,
399 cx,
400 )
401 .detach_and_log_err(cx)
402 }
403 Spawn::ByTag {
404 task_tag,
405 reveal_target,
406 } => {
407 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
408 reveal_target: Some(reveal_target),
409 });
410 let tag = task_tag.clone();
411 tasks_ui::spawn_tasks_filtered(
412 move |(_, task)| task.tags.contains(&tag),
413 overrides,
414 window,
415 cx,
416 )
417 .detach_and_log_err(cx)
418 }
419 Spawn::ViaModal { reveal_target } => {
420 NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx);
421 }
422 }
423}