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 ]
89);
90
91/// Extends selection down by a specified number of lines.
92#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
93#[action(namespace = debugger)]
94#[serde(deny_unknown_fields)]
95/// Set a data breakpoint on the selected variable or memory region.
96pub struct ToggleDataBreakpoint {
97 /// The type of data breakpoint
98 /// Read & Write
99 /// Read
100 /// Write
101 #[serde(default)]
102 pub access_type: Option<dap::DataBreakpointAccessType>,
103}
104
105actions!(
106 dev,
107 [
108 /// Copies debug adapter launch arguments to clipboard.
109 CopyDebugAdapterArguments
110 ]
111);
112
113pub fn init(cx: &mut App) {
114 DebuggerSettings::register(cx);
115 workspace::FollowableViewRegistry::register::<DebugSession>(cx);
116
117 cx.observe_new(|workspace: &mut Workspace, _, _| {
118 workspace
119 .register_action(spawn_task_or_modal)
120 .register_action(|workspace, _: &ToggleFocus, window, cx| {
121 workspace.toggle_panel_focus::<DebugPanel>(window, cx);
122 })
123 .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
124 NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
125 })
126 .register_action(|workspace: &mut Workspace, _: &Rerun, window, cx| {
127 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
128 return;
129 };
130
131 debug_panel.update(cx, |debug_panel, cx| {
132 debug_panel.rerun_last_session(workspace, window, cx);
133 })
134 })
135 .register_action(
136 |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
137 workspace.project().update(cx, |project, cx| {
138 project.dap_store().update(cx, |store, cx| {
139 store.shutdown_sessions(cx).detach();
140 })
141 })
142 },
143 )
144 .register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
145 DebuggerOnboardingModal::toggle(workspace, window, cx)
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 });
283 })
284 .detach();
285
286 cx.observe_new({
287 move |editor: &mut Editor, _, _| {
288 editor
289 .register_action_renderer(move |editor, window, cx| {
290 let Some(workspace) = editor.workspace() else {
291 return;
292 };
293 let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {
294 return;
295 };
296 let Some(active_session) = debug_panel
297 .clone()
298 .update(cx, |panel, _| panel.active_session())
299 else {
300 return;
301 };
302
303 let session = active_session
304 .read(cx)
305 .running_state
306 .read(cx)
307 .session()
308 .read(cx);
309
310 if session.is_terminated() {
311 return;
312 }
313
314 let editor = cx.entity().downgrade();
315
316 window.on_action_when(
317 session.any_stopped_thread(),
318 TypeId::of::<editor::actions::RunToCursor>(),
319 {
320 let editor = editor.clone();
321 let active_session = active_session.clone();
322 move |_, phase, _, cx| {
323 if phase != DispatchPhase::Bubble {
324 return;
325 }
326 maybe!({
327 let (buffer, position, _) = editor
328 .update(cx, |editor, cx| {
329 let cursor_point: language::Point =
330 editor.selections.newest(cx).head();
331
332 editor
333 .buffer()
334 .read(cx)
335 .point_to_buffer_point(cursor_point, cx)
336 })
337 .ok()??;
338
339 let path =
340 debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
341 &buffer, cx,
342 )?;
343
344 let source_breakpoint = SourceBreakpoint {
345 row: position.row,
346 path,
347 message: None,
348 condition: None,
349 hit_condition: None,
350 state: debugger::breakpoint_store::BreakpointState::Enabled,
351 };
352
353 active_session.update(cx, |session, cx| {
354 session.running_state().update(cx, |state, cx| {
355 if let Some(thread_id) = state.selected_thread_id() {
356 state.session().update(cx, |session, cx| {
357 session.run_to_position(
358 source_breakpoint,
359 thread_id,
360 cx,
361 );
362 })
363 }
364 });
365 });
366
367 Some(())
368 });
369 }
370 },
371 );
372
373 window.on_action(
374 TypeId::of::<editor::actions::EvaluateSelectedText>(),
375 move |_, _, window, cx| {
376 maybe!({
377 let text = editor
378 .update(cx, |editor, cx| {
379 editor.text_for_range(
380 editor.selections.newest(cx).range(),
381 &mut None,
382 window,
383 cx,
384 )
385 })
386 .ok()??;
387
388 active_session.update(cx, |session, cx| {
389 session.running_state().update(cx, |state, cx| {
390 let stack_id = state.selected_stack_frame_id(cx);
391
392 state.session().update(cx, |session, cx| {
393 session
394 .evaluate(text, None, stack_id, None, cx)
395 .detach();
396 });
397 });
398 });
399
400 Some(())
401 });
402 },
403 );
404 })
405 .detach();
406 }
407 })
408 .detach();
409}
410
411fn spawn_task_or_modal(
412 workspace: &mut Workspace,
413 action: &Spawn,
414 window: &mut ui::Window,
415 cx: &mut ui::Context<Workspace>,
416) {
417 match action {
418 Spawn::ByName {
419 task_name,
420 reveal_target,
421 } => {
422 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
423 reveal_target: Some(reveal_target),
424 });
425 let name = task_name.clone();
426 tasks_ui::spawn_tasks_filtered(
427 move |(_, task)| task.label.eq(&name),
428 overrides,
429 window,
430 cx,
431 )
432 .detach_and_log_err(cx)
433 }
434 Spawn::ByTag {
435 task_tag,
436 reveal_target,
437 } => {
438 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
439 reveal_target: Some(reveal_target),
440 });
441 let tag = task_tag.clone();
442 tasks_ui::spawn_tasks_filtered(
443 move |(_, task)| task.tags.contains(&tag),
444 overrides,
445 window,
446 cx,
447 )
448 .detach_and_log_err(cx)
449 }
450 Spawn::ViaModal { reveal_target } => {
451 NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx);
452 }
453 }
454}