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