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