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