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