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