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