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