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.read_with(cx, |panel, cx| {
70 panel
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.read_with(cx, |panel, cx| {
81 panel
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.read_with(cx, |panel, cx| {
92 panel
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.read_with(cx, |panel, cx| {
103 panel
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.read_with(cx, |panel, cx| {
114 panel
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, _: &StepBack, _, 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_back(cx))
130 }
131 }
132 })
133 .register_action(|workspace, _: &Stop, _, cx| {
134 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
135 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
136 panel
137 .active_session()
138 .map(|session| session.read(cx).running_state().clone())
139 }) {
140 cx.defer(move |cx| {
141 active_item.update(cx, |item, cx| item.stop_thread(cx))
142 })
143 }
144 }
145 })
146 .register_action(|workspace, _: &ToggleIgnoreBreakpoints, _, cx| {
147 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
148 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
149 panel
150 .active_session()
151 .map(|session| session.read(cx).running_state().clone())
152 }) {
153 active_item.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
154 }
155 }
156 })
157 .register_action(
158 |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
159 workspace.project().update(cx, |project, cx| {
160 project.dap_store().update(cx, |store, cx| {
161 store.shutdown_sessions(cx).detach();
162 })
163 })
164 },
165 )
166 .register_action(
167 |workspace: &mut Workspace, _: &ShowStackTrace, window, cx| {
168 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
169 return;
170 };
171
172 if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx) {
173 let is_active = workspace
174 .active_item(cx)
175 .is_some_and(|item| item.item_id() == existing.item_id());
176 workspace.activate_item(&existing, true, !is_active, window, cx);
177 } else {
178 let Some(active_session) = debug_panel.read(cx).active_session() else {
179 return;
180 };
181
182 let project = workspace.project();
183
184 let stack_trace_view = active_session.update(cx, |session, cx| {
185 session.stack_trace_view(project, window, cx).clone()
186 });
187
188 workspace.add_item_to_active_pane(
189 Box::new(stack_trace_view),
190 None,
191 true,
192 window,
193 cx,
194 );
195 }
196 },
197 )
198 .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
199 NewSessionModal::show(workspace, window, cx);
200 });
201 })
202 })
203 .detach();
204
205 cx.observe_new({
206 move |editor: &mut Editor, _, cx| {
207 editor
208 .register_action(cx.listener(
209 move |editor, _: &editor::actions::DebuggerRunToCursor, _, cx| {
210 maybe!({
211 let debug_panel =
212 editor.workspace()?.read(cx).panel::<DebugPanel>(cx)?;
213 let cursor_point: language::Point = editor.selections.newest(cx).head();
214 let active_session = debug_panel.read(cx).active_session()?;
215
216 let (buffer, position, _) = editor
217 .buffer()
218 .read(cx)
219 .point_to_buffer_point(cursor_point, cx)?;
220
221 let path =
222 debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
223 &buffer, cx,
224 )?;
225
226 let source_breakpoint = SourceBreakpoint {
227 row: position.row,
228 path,
229 message: None,
230 condition: None,
231 hit_condition: None,
232 state: debugger::breakpoint_store::BreakpointState::Enabled,
233 };
234
235 active_session.update(cx, |session, cx| {
236 session.running_state().update(cx, |state, cx| {
237 if let Some(thread_id) = state.selected_thread_id() {
238 state.session().update(cx, |session, cx| {
239 session.run_to_position(
240 source_breakpoint,
241 thread_id,
242 cx,
243 );
244 })
245 }
246 });
247 });
248
249 Some(())
250 });
251 },
252 ))
253 .detach();
254
255 editor
256 .register_action(cx.listener(
257 move |editor, _: &editor::actions::DebuggerEvaluateSelectedText, window, cx| {
258 maybe!({
259 let debug_panel =
260 editor.workspace()?.read(cx).panel::<DebugPanel>(cx)?;
261 let active_session = debug_panel.read(cx).active_session()?;
262
263 let text = editor.text_for_range(
264 editor.selections.newest(cx).range(),
265 &mut None,
266 window,
267 cx,
268 )?;
269
270 active_session.update(cx, |session, cx| {
271 session.running_state().update(cx, |state, cx| {
272 let stack_id = state.selected_stack_frame_id(cx);
273
274 state.session().update(cx, |session, cx| {
275 session.evaluate(text, None, stack_id, None, cx).detach();
276 });
277 });
278 });
279
280 Some(())
281 });
282 },
283 ))
284 .detach();
285 }
286 })
287 .detach();
288}