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