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