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 ]
87);
88
89actions!(
90 dev,
91 [
92 /// Copies debug adapter launch arguments to clipboard.
93 CopyDebugAdapterArguments
94 ]
95);
96
97pub fn init(cx: &mut App) {
98 DebuggerSettings::register(cx);
99 workspace::FollowableViewRegistry::register::<DebugSession>(cx);
100
101 cx.observe_new(|workspace: &mut Workspace, _, _| {
102 workspace
103 .register_action(spawn_task_or_modal)
104 .register_action(|workspace, _: &ToggleFocus, window, cx| {
105 workspace.toggle_panel_focus::<DebugPanel>(window, cx);
106 })
107 .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
108 NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
109 })
110 .register_action(|workspace: &mut Workspace, _: &Rerun, window, cx| {
111 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
112 return;
113 };
114
115 debug_panel.update(cx, |debug_panel, cx| {
116 debug_panel.rerun_last_session(workspace, window, cx);
117 })
118 })
119 .register_action(
120 |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
121 workspace.project().update(cx, |project, cx| {
122 project.dap_store().update(cx, |store, cx| {
123 store.shutdown_sessions(cx).detach();
124 })
125 })
126 },
127 )
128 .register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
129 DebuggerOnboardingModal::toggle(workspace, window, cx)
130 })
131 .register_action_renderer(|div, workspace, _, cx| {
132 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
133 return div;
134 };
135 let Some(active_item) = debug_panel
136 .read(cx)
137 .active_session()
138 .map(|session| session.read(cx).running_state().clone())
139 else {
140 return div;
141 };
142 let running_state = active_item.read(cx);
143 if running_state.session().read(cx).is_terminated() {
144 return div;
145 }
146
147 let caps = running_state.capabilities(cx);
148 let supports_step_back = caps.supports_step_back.unwrap_or_default();
149 let supports_detach = running_state.session().read(cx).is_attached();
150 let status = running_state.thread_status(cx);
151
152 let active_item = active_item.downgrade();
153 div.when(status == Some(ThreadStatus::Running), |div| {
154 let active_item = active_item.clone();
155 div.on_action(move |_: &Pause, _, cx| {
156 active_item
157 .update(cx, |item, cx| item.pause_thread(cx))
158 .ok();
159 })
160 })
161 .when(status == Some(ThreadStatus::Stopped), |div| {
162 div.on_action({
163 let active_item = active_item.clone();
164 move |_: &StepInto, _, cx| {
165 active_item.update(cx, |item, cx| item.step_in(cx)).ok();
166 }
167 })
168 .on_action({
169 let active_item = active_item.clone();
170 move |_: &StepOver, _, cx| {
171 active_item.update(cx, |item, cx| item.step_over(cx)).ok();
172 }
173 })
174 .on_action({
175 let active_item = active_item.clone();
176 move |_: &StepOut, _, cx| {
177 active_item.update(cx, |item, cx| item.step_out(cx)).ok();
178 }
179 })
180 .when(supports_step_back, |div| {
181 let active_item = active_item.clone();
182 div.on_action(move |_: &StepBack, _, cx| {
183 active_item.update(cx, |item, cx| item.step_back(cx)).ok();
184 })
185 })
186 .on_action({
187 let active_item = active_item.clone();
188 move |_: &Continue, _, cx| {
189 active_item
190 .update(cx, |item, cx| item.continue_thread(cx))
191 .ok();
192 }
193 })
194 .on_action(cx.listener(
195 |workspace, _: &ShowStackTrace, window, cx| {
196 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
197 return;
198 };
199
200 if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx) {
201 let is_active = workspace
202 .active_item(cx)
203 .is_some_and(|item| item.item_id() == existing.item_id());
204 workspace.activate_item(&existing, true, !is_active, window, cx);
205 } else {
206 let Some(active_session) = debug_panel.read(cx).active_session()
207 else {
208 return;
209 };
210
211 let project = workspace.project();
212
213 let stack_trace_view = active_session.update(cx, |session, cx| {
214 session.stack_trace_view(project, window, cx).clone()
215 });
216
217 workspace.add_item_to_active_pane(
218 Box::new(stack_trace_view),
219 None,
220 true,
221 window,
222 cx,
223 );
224 }
225 },
226 ))
227 })
228 .when(supports_detach, |div| {
229 let active_item = active_item.clone();
230 div.on_action(move |_: &Detach, _, cx| {
231 active_item
232 .update(cx, |item, cx| item.detach_client(cx))
233 .ok();
234 })
235 })
236 .on_action({
237 let active_item = active_item.clone();
238 move |_: &Restart, _, cx| {
239 active_item
240 .update(cx, |item, cx| item.restart_session(cx))
241 .ok();
242 }
243 })
244 .on_action({
245 let active_item = active_item.clone();
246 move |_: &RerunSession, window, cx| {
247 active_item
248 .update(cx, |item, cx| item.rerun_session(window, cx))
249 .ok();
250 }
251 })
252 .on_action({
253 let active_item = active_item.clone();
254 move |_: &Stop, _, cx| {
255 active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();
256 }
257 })
258 .on_action({
259 let active_item = active_item.clone();
260 move |_: &ToggleIgnoreBreakpoints, _, cx| {
261 active_item
262 .update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
263 .ok();
264 }
265 })
266 });
267 })
268 .detach();
269
270 cx.observe_new({
271 move |editor: &mut Editor, _, _| {
272 editor
273 .register_action_renderer(move |editor, window, cx| {
274 let Some(workspace) = editor.workspace() else {
275 return;
276 };
277 let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {
278 return;
279 };
280 let Some(active_session) = debug_panel
281 .clone()
282 .update(cx, |panel, _| panel.active_session())
283 else {
284 return;
285 };
286 let editor = cx.entity().downgrade();
287 window.on_action(TypeId::of::<editor::actions::RunToCursor>(), {
288 let editor = editor.clone();
289 let active_session = active_session.clone();
290 move |_, phase, _, cx| {
291 if phase != DispatchPhase::Bubble {
292 return;
293 }
294 maybe!({
295 let (buffer, position, _) = editor
296 .update(cx, |editor, cx| {
297 let cursor_point: language::Point =
298 editor.selections.newest(cx).head();
299
300 editor
301 .buffer()
302 .read(cx)
303 .point_to_buffer_point(cursor_point, cx)
304 })
305 .ok()??;
306
307 let path =
308 debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
309 &buffer, cx,
310 )?;
311
312 let source_breakpoint = SourceBreakpoint {
313 row: position.row,
314 path,
315 message: None,
316 condition: None,
317 hit_condition: None,
318 state: debugger::breakpoint_store::BreakpointState::Enabled,
319 };
320
321 active_session.update(cx, |session, cx| {
322 session.running_state().update(cx, |state, cx| {
323 if let Some(thread_id) = state.selected_thread_id() {
324 state.session().update(cx, |session, cx| {
325 session.run_to_position(
326 source_breakpoint,
327 thread_id,
328 cx,
329 );
330 })
331 }
332 });
333 });
334
335 Some(())
336 });
337 }
338 });
339
340 window.on_action(
341 TypeId::of::<editor::actions::EvaluateSelectedText>(),
342 move |_, _, window, cx| {
343 maybe!({
344 let text = editor
345 .update(cx, |editor, cx| {
346 editor.text_for_range(
347 editor.selections.newest(cx).range(),
348 &mut None,
349 window,
350 cx,
351 )
352 })
353 .ok()??;
354
355 active_session.update(cx, |session, cx| {
356 session.running_state().update(cx, |state, cx| {
357 let stack_id = state.selected_stack_frame_id(cx);
358
359 state.session().update(cx, |session, cx| {
360 session
361 .evaluate(text, None, stack_id, None, cx)
362 .detach();
363 });
364 });
365 });
366
367 Some(())
368 });
369 },
370 );
371 })
372 .detach();
373 }
374 })
375 .detach();
376}
377
378fn spawn_task_or_modal(
379 workspace: &mut Workspace,
380 action: &Spawn,
381 window: &mut ui::Window,
382 cx: &mut ui::Context<Workspace>,
383) {
384 match action {
385 Spawn::ByName {
386 task_name,
387 reveal_target,
388 } => {
389 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
390 reveal_target: Some(reveal_target),
391 });
392 let name = task_name.clone();
393 tasks_ui::spawn_tasks_filtered(
394 move |(_, task)| task.label.eq(&name),
395 overrides,
396 window,
397 cx,
398 )
399 .detach_and_log_err(cx)
400 }
401 Spawn::ByTag {
402 task_tag,
403 reveal_target,
404 } => {
405 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
406 reveal_target: Some(reveal_target),
407 });
408 let tag = task_tag.clone();
409 tasks_ui::spawn_tasks_filtered(
410 move |(_, task)| task.tags.contains(&tag),
411 overrides,
412 window,
413 cx,
414 )
415 .detach_and_log_err(cx)
416 }
417 Spawn::ViaModal { reveal_target } => {
418 NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx);
419 }
420 }
421}