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