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