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