1use std::any::TypeId;
2
3use dap::debugger_settings::DebuggerSettings;
4use debugger_panel::{DebugPanel, ToggleFocus};
5use editor::Editor;
6use feature_flags::{DebuggerFeatureFlag, FeatureFlagViewExt};
7use gpui::{App, DispatchPhase, EntityInputHandler, actions};
8use new_process_modal::{NewProcessModal, NewProcessMode};
9use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};
10use session::DebugSession;
11use settings::Settings;
12use stack_trace_view::StackTraceView;
13use tasks_ui::{Spawn, TaskOverrides};
14use ui::{FluentBuilder, InteractiveElement};
15use util::maybe;
16use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
17
18pub mod attach_modal;
19pub mod debugger_panel;
20mod dropdown_menus;
21mod new_process_modal;
22mod persistence;
23pub(crate) mod session;
24mod stack_trace_view;
25
26#[cfg(any(test, feature = "test-support"))]
27pub mod tests;
28
29actions!(
30 debugger,
31 [
32 Start,
33 Continue,
34 Detach,
35 Pause,
36 Restart,
37 StepInto,
38 StepOver,
39 StepOut,
40 StepBack,
41 Stop,
42 ToggleIgnoreBreakpoints,
43 ClearAllBreakpoints,
44 FocusConsole,
45 FocusVariables,
46 FocusBreakpointList,
47 FocusFrames,
48 FocusModules,
49 FocusLoadedSources,
50 FocusTerminal,
51 ShowStackTrace,
52 ToggleThreadPicker,
53 ToggleSessionPicker,
54 RerunLastSession,
55 ToggleExpandItem,
56 ]
57);
58
59actions!(dev, [CopyDebugAdapterArguments]);
60
61pub fn init(cx: &mut App) {
62 DebuggerSettings::register(cx);
63 workspace::FollowableViewRegistry::register::<DebugSession>(cx);
64
65 cx.observe_new(|_: &mut Workspace, window, cx| {
66 let Some(window) = window else {
67 return;
68 };
69
70 cx.when_flag_enabled::<DebuggerFeatureFlag>(window, |workspace, _, _| {
71 workspace
72 .register_action(spawn_task_or_modal)
73 .register_action(|workspace, _: &ToggleFocus, window, cx| {
74 workspace.toggle_panel_focus::<DebugPanel>(window, cx);
75 })
76 .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
77 NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
78 })
79 .register_action(
80 |workspace: &mut Workspace, _: &RerunLastSession, window, cx| {
81 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
82 return;
83 };
84
85 debug_panel.update(cx, |debug_panel, cx| {
86 debug_panel.rerun_last_session(workspace, window, cx);
87 })
88 },
89 )
90 .register_action(
91 |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
92 workspace.project().update(cx, |project, cx| {
93 project.dap_store().update(cx, |store, cx| {
94 store.shutdown_sessions(cx).detach();
95 })
96 })
97 },
98 )
99 .register_action_renderer(|div, workspace, _, cx| {
100 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
101 return div;
102 };
103 let Some(active_item) = debug_panel
104 .read(cx)
105 .active_session()
106 .map(|session| session.read(cx).running_state().clone())
107 else {
108 return div;
109 };
110 let running_state = active_item.read(cx);
111 if running_state.session().read(cx).is_terminated() {
112 return div;
113 }
114
115 let caps = running_state.capabilities(cx);
116 let supports_step_back = caps.supports_step_back.unwrap_or_default();
117 let supports_detach = running_state.session().read(cx).is_attached();
118 let status = running_state.thread_status(cx);
119
120 let active_item = active_item.downgrade();
121 div.when(status == Some(ThreadStatus::Running), |div| {
122 let active_item = active_item.clone();
123 div.on_action(move |_: &Pause, _, cx| {
124 active_item
125 .update(cx, |item, cx| item.pause_thread(cx))
126 .ok();
127 })
128 })
129 .when(status == Some(ThreadStatus::Stopped), |div| {
130 div.on_action({
131 let active_item = active_item.clone();
132 move |_: &StepInto, _, cx| {
133 active_item.update(cx, |item, cx| item.step_in(cx)).ok();
134 }
135 })
136 .on_action({
137 let active_item = active_item.clone();
138 move |_: &StepOver, _, cx| {
139 active_item.update(cx, |item, cx| item.step_over(cx)).ok();
140 }
141 })
142 .on_action({
143 let active_item = active_item.clone();
144 move |_: &StepOut, _, cx| {
145 active_item.update(cx, |item, cx| item.step_out(cx)).ok();
146 }
147 })
148 .when(supports_step_back, |div| {
149 let active_item = active_item.clone();
150 div.on_action(move |_: &StepBack, _, cx| {
151 active_item.update(cx, |item, cx| item.step_back(cx)).ok();
152 })
153 })
154 .on_action({
155 let active_item = active_item.clone();
156 move |_: &Continue, _, cx| {
157 active_item
158 .update(cx, |item, cx| item.continue_thread(cx))
159 .ok();
160 }
161 })
162 .on_action(cx.listener(
163 |workspace, _: &ShowStackTrace, window, cx| {
164 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
165 return;
166 };
167
168 if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx)
169 {
170 let is_active = workspace
171 .active_item(cx)
172 .is_some_and(|item| item.item_id() == existing.item_id());
173 workspace
174 .activate_item(&existing, true, !is_active, window, cx);
175 } else {
176 let Some(active_session) =
177 debug_panel.read(cx).active_session()
178 else {
179 return;
180 };
181
182 let project = workspace.project();
183
184 let stack_trace_view =
185 active_session.update(cx, |session, cx| {
186 session.stack_trace_view(project, window, cx).clone()
187 });
188
189 workspace.add_item_to_active_pane(
190 Box::new(stack_trace_view),
191 None,
192 true,
193 window,
194 cx,
195 );
196 }
197 },
198 ))
199 })
200 .when(supports_detach, |div| {
201 let active_item = active_item.clone();
202 div.on_action(move |_: &Detach, _, cx| {
203 active_item
204 .update(cx, |item, cx| item.detach_client(cx))
205 .ok();
206 })
207 })
208 .on_action({
209 let active_item = active_item.clone();
210 move |_: &Restart, _, cx| {
211 active_item
212 .update(cx, |item, cx| item.restart_session(cx))
213 .ok();
214 }
215 })
216 .on_action({
217 let active_item = active_item.clone();
218 move |_: &Stop, _, cx| {
219 active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();
220 }
221 })
222 .on_action({
223 let active_item = active_item.clone();
224 move |_: &ToggleIgnoreBreakpoints, _, cx| {
225 active_item
226 .update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
227 .ok();
228 }
229 })
230 });
231 })
232 })
233 .detach();
234
235 cx.observe_new({
236 move |editor: &mut Editor, _, _| {
237 editor
238 .register_action_renderer(move |editor, window, cx| {
239 let Some(workspace) = editor.workspace() else {
240 return;
241 };
242 let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {
243 return;
244 };
245 let Some(active_session) = debug_panel
246 .clone()
247 .update(cx, |panel, _| panel.active_session())
248 else {
249 return;
250 };
251 let editor = cx.entity().downgrade();
252 window.on_action(TypeId::of::<editor::actions::RunToCursor>(), {
253 let editor = editor.clone();
254 let active_session = active_session.clone();
255 move |_, phase, _, cx| {
256 if phase != DispatchPhase::Bubble {
257 return;
258 }
259 maybe!({
260 let (buffer, position, _) = editor
261 .update(cx, |editor, cx| {
262 let cursor_point: language::Point =
263 editor.selections.newest(cx).head();
264
265 editor
266 .buffer()
267 .read(cx)
268 .point_to_buffer_point(cursor_point, cx)
269 })
270 .ok()??;
271
272 let path =
273 debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
274 &buffer, cx,
275 )?;
276
277 let source_breakpoint = SourceBreakpoint {
278 row: position.row,
279 path,
280 message: None,
281 condition: None,
282 hit_condition: None,
283 state: debugger::breakpoint_store::BreakpointState::Enabled,
284 };
285
286 active_session.update(cx, |session, cx| {
287 session.running_state().update(cx, |state, cx| {
288 if let Some(thread_id) = state.selected_thread_id() {
289 state.session().update(cx, |session, cx| {
290 session.run_to_position(
291 source_breakpoint,
292 thread_id,
293 cx,
294 );
295 })
296 }
297 });
298 });
299
300 Some(())
301 });
302 }
303 });
304
305 window.on_action(
306 TypeId::of::<editor::actions::EvaluateSelectedText>(),
307 move |_, _, window, cx| {
308 maybe!({
309 let text = editor
310 .update(cx, |editor, cx| {
311 editor.text_for_range(
312 editor.selections.newest(cx).range(),
313 &mut None,
314 window,
315 cx,
316 )
317 })
318 .ok()??;
319
320 active_session.update(cx, |session, cx| {
321 session.running_state().update(cx, |state, cx| {
322 let stack_id = state.selected_stack_frame_id(cx);
323
324 state.session().update(cx, |session, cx| {
325 session
326 .evaluate(text, None, stack_id, None, cx)
327 .detach();
328 });
329 });
330 });
331
332 Some(())
333 });
334 },
335 );
336 })
337 .detach();
338 }
339 })
340 .detach();
341}
342
343fn spawn_task_or_modal(
344 workspace: &mut Workspace,
345 action: &Spawn,
346 window: &mut ui::Window,
347 cx: &mut ui::Context<Workspace>,
348) {
349 match action {
350 Spawn::ByName {
351 task_name,
352 reveal_target,
353 } => {
354 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
355 reveal_target: Some(reveal_target),
356 });
357 let name = task_name.clone();
358 tasks_ui::spawn_tasks_filtered(
359 move |(_, task)| task.label.eq(&name),
360 overrides,
361 window,
362 cx,
363 )
364 .detach_and_log_err(cx)
365 }
366 Spawn::ByTag {
367 task_tag,
368 reveal_target,
369 } => {
370 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
371 reveal_target: Some(reveal_target),
372 });
373 let tag = task_tag.clone();
374 tasks_ui::spawn_tasks_filtered(
375 move |(_, task)| task.tags.contains(&tag),
376 overrides,
377 window,
378 cx,
379 )
380 .detach_and_log_err(cx)
381 }
382 Spawn::ViaModal { reveal_target } => {
383 NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx);
384 }
385 }
386}