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