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_restart = caps.supports_restart_request.unwrap_or_default();
115 let supports_step_back = caps.supports_step_back.unwrap_or_default();
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_restart, |div| {
199 let active_item = active_item.clone();
200 div.on_action(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 })
223 .detach();
224
225 cx.observe_new({
226 move |editor: &mut Editor, _, _| {
227 editor
228 .register_action_renderer(move |editor, window, cx| {
229 let Some(workspace) = editor.workspace() else {
230 return;
231 };
232 let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {
233 return;
234 };
235 let Some(active_session) = debug_panel
236 .clone()
237 .update(cx, |panel, _| panel.active_session())
238 else {
239 return;
240 };
241 let editor = cx.entity().downgrade();
242 window.on_action(TypeId::of::<editor::actions::RunToCursor>(), {
243 let editor = editor.clone();
244 let active_session = active_session.clone();
245 move |_, phase, _, cx| {
246 if phase != DispatchPhase::Bubble {
247 return;
248 }
249 maybe!({
250 let (buffer, position, _) = editor
251 .update(cx, |editor, cx| {
252 let cursor_point: language::Point =
253 editor.selections.newest(cx).head();
254
255 editor
256 .buffer()
257 .read(cx)
258 .point_to_buffer_point(cursor_point, cx)
259 })
260 .ok()??;
261
262 let path =
263 debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
264 &buffer, cx,
265 )?;
266
267 let source_breakpoint = SourceBreakpoint {
268 row: position.row,
269 path,
270 message: None,
271 condition: None,
272 hit_condition: None,
273 state: debugger::breakpoint_store::BreakpointState::Enabled,
274 };
275
276 active_session.update(cx, |session, cx| {
277 session.running_state().update(cx, |state, cx| {
278 if let Some(thread_id) = state.selected_thread_id() {
279 state.session().update(cx, |session, cx| {
280 session.run_to_position(
281 source_breakpoint,
282 thread_id,
283 cx,
284 );
285 })
286 }
287 });
288 });
289
290 Some(())
291 });
292 }
293 });
294
295 window.on_action(
296 TypeId::of::<editor::actions::EvaluateSelectedText>(),
297 move |_, _, window, cx| {
298 maybe!({
299 let text = editor
300 .update(cx, |editor, cx| {
301 editor.text_for_range(
302 editor.selections.newest(cx).range(),
303 &mut None,
304 window,
305 cx,
306 )
307 })
308 .ok()??;
309
310 active_session.update(cx, |session, cx| {
311 session.running_state().update(cx, |state, cx| {
312 let stack_id = state.selected_stack_frame_id(cx);
313
314 state.session().update(cx, |session, cx| {
315 session
316 .evaluate(text, None, stack_id, None, cx)
317 .detach();
318 });
319 });
320 });
321
322 Some(())
323 });
324 },
325 );
326 })
327 .detach();
328 }
329 })
330 .detach();
331}
332
333fn spawn_task_or_modal(
334 workspace: &mut Workspace,
335 action: &Spawn,
336 window: &mut ui::Window,
337 cx: &mut ui::Context<Workspace>,
338) {
339 match action {
340 Spawn::ByName {
341 task_name,
342 reveal_target,
343 } => {
344 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
345 reveal_target: Some(reveal_target),
346 });
347 let name = task_name.clone();
348 tasks_ui::spawn_tasks_filtered(
349 move |(_, task)| task.label.eq(&name),
350 overrides,
351 window,
352 cx,
353 )
354 .detach_and_log_err(cx)
355 }
356 Spawn::ByTag {
357 task_tag,
358 reveal_target,
359 } => {
360 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
361 reveal_target: Some(reveal_target),
362 });
363 let tag = task_tag.clone();
364 tasks_ui::spawn_tasks_filtered(
365 move |(_, task)| task.tags.contains(&tag),
366 overrides,
367 window,
368 cx,
369 )
370 .detach_and_log_err(cx)
371 }
372 Spawn::ViaModal { reveal_target } => {
373 NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx);
374 }
375 }
376}