1use std::any::TypeId;
2
3use debugger_panel::DebugPanel;
4use editor::{Editor, MultiBufferOffsetUtf16};
5use gpui::{Action, App, DispatchPhase, EntityInputHandler, actions};
6use new_process_modal::{NewProcessModal, NewProcessMode};
7use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};
8use schemars::JsonSchema;
9use serde::Deserialize;
10use session::DebugSession;
11
12use tasks_ui::{Spawn, TaskOverrides};
13use ui::{FluentBuilder, InteractiveElement};
14use util::maybe;
15use workspace::{ShutdownDebugAdapters, Workspace};
16use zed_actions::debug_panel::{Toggle, ToggleFocus};
17
18pub mod attach_modal;
19pub mod debugger_panel;
20mod dropdown_menus;
21mod new_process_modal;
22mod persistence;
23pub(crate) mod session;
24
25#[cfg(any(test, feature = "test-support"))]
26pub mod tests;
27
28// Let's see the diff-test in action.
29actions!(
30 debugger,
31 [
32 /// Starts a new debugging session.
33 Start,
34 /// Continues execution until the next breakpoint.
35 Continue,
36 /// Detaches the debugger from the running process.
37 Detach,
38 /// Pauses the currently running program.
39 Pause,
40 /// Restarts the current debugging session.
41 Restart,
42 /// Reruns the current debugging session with the same configuration.
43 RerunSession,
44 /// Steps into the next function call.
45 StepInto,
46 /// Steps over the current line.
47 StepOver,
48 /// Steps out of the current function.
49 StepOut,
50 /// Steps back to the previous statement.
51 StepBack,
52 /// Stops the debugging session.
53 Stop,
54 /// Toggles whether to ignore all breakpoints.
55 ToggleIgnoreBreakpoints,
56 /// Clears all breakpoints in the project.
57 ClearAllBreakpoints,
58 /// Focuses on the debugger console panel.
59 FocusConsole,
60 /// Focuses on the variables panel.
61 FocusVariables,
62 /// Focuses on the breakpoint list panel.
63 FocusBreakpointList,
64 /// Focuses on the call stack frames panel.
65 FocusFrames,
66 /// Focuses on the loaded modules panel.
67 FocusModules,
68 /// Focuses on the loaded sources panel.
69 FocusLoadedSources,
70 /// Focuses on the terminal panel.
71 FocusTerminal,
72 /// Toggles the thread picker dropdown.
73 ToggleThreadPicker,
74 /// Toggles the session picker dropdown.
75 ToggleSessionPicker,
76 /// Reruns the last debugging session.
77 #[action(deprecated_aliases = ["debugger::RerunLastSession"])]
78 Rerun,
79 /// Toggles expansion of the selected item in the debugger UI.
80 ToggleExpandItem,
81 /// Toggle the user frame filter in the stack frame list
82 /// When toggled on, only frames from the user's code are shown
83 /// When toggled off, all frames are shown
84 ToggleUserFrames,
85 ]
86);
87
88/// Set a data breakpoint on the selected variable or memory region.
89#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
90#[action(namespace = debugger)]
91#[serde(deny_unknown_fields)]
92pub struct ToggleDataBreakpoint {
93 /// The type of data breakpoint
94 /// Read & Write
95 /// Read
96 /// Write
97 #[serde(default)]
98 pub access_type: Option<dap::DataBreakpointAccessType>,
99}
100
101actions!(
102 dev,
103 [
104 /// Copies debug adapter launch arguments to clipboard.
105 CopyDebugAdapterArguments
106 ]
107);
108
109pub fn init(cx: &mut App) {
110 workspace::FollowableViewRegistry::register::<DebugSession>(cx);
111
112 cx.observe_new(|workspace: &mut Workspace, _, _| {
113 workspace
114 .register_action(spawn_task_or_modal)
115 .register_action(|workspace, _: &ToggleFocus, window, cx| {
116 workspace.toggle_panel_focus::<DebugPanel>(window, cx);
117 })
118 .register_action(|workspace, _: &Toggle, window, cx| {
119 if !workspace.toggle_panel_focus::<DebugPanel>(window, cx) {
120 workspace.close_panel::<DebugPanel>(window, cx);
121 }
122 })
123 .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
124 NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
125 })
126 .register_action(|workspace: &mut Workspace, _: &Rerun, window, cx| {
127 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
128 return;
129 };
130
131 debug_panel.update(cx, |debug_panel, cx| {
132 debug_panel.rerun_last_session(workspace, window, cx);
133 })
134 })
135 .register_action(
136 |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
137 workspace.project().update(cx, |project, cx| {
138 project.dap_store().update(cx, |store, cx| {
139 store.shutdown_sessions(cx).detach();
140 })
141 })
142 },
143 )
144 .register_action_renderer(|div, workspace, _, cx| {
145 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
146 return div;
147 };
148 let Some(active_item) = debug_panel
149 .read(cx)
150 .active_session()
151 .map(|session| session.read(cx).running_state().clone())
152 else {
153 return div;
154 };
155 let running_state = active_item.read(cx);
156 if running_state.session().read(cx).is_terminated() {
157 return div;
158 }
159
160 let caps = running_state.capabilities(cx);
161 let supports_step_back = caps.supports_step_back.unwrap_or_default();
162 let supports_detach = running_state.session().read(cx).is_attached();
163 let status = running_state.thread_status(cx);
164
165 let active_item = active_item.downgrade();
166 div.when(status == Some(ThreadStatus::Running), |div| {
167 let active_item = active_item.clone();
168 div.on_action(move |_: &Pause, _, cx| {
169 active_item
170 .update(cx, |item, cx| item.pause_thread(cx))
171 .ok();
172 })
173 })
174 .when(status == Some(ThreadStatus::Stopped), |div| {
175 div.on_action({
176 let active_item = active_item.clone();
177 move |_: &StepInto, _, cx| {
178 active_item.update(cx, |item, cx| item.step_in(cx)).ok();
179 }
180 })
181 .on_action({
182 let active_item = active_item.clone();
183 move |_: &StepOver, _, cx| {
184 active_item.update(cx, |item, cx| item.step_over(cx)).ok();
185 }
186 })
187 .on_action({
188 let active_item = active_item.clone();
189 move |_: &StepOut, _, cx| {
190 active_item.update(cx, |item, cx| item.step_out(cx)).ok();
191 }
192 })
193 .when(supports_step_back, |div| {
194 let active_item = active_item.clone();
195 div.on_action(move |_: &StepBack, _, cx| {
196 active_item.update(cx, |item, cx| item.step_back(cx)).ok();
197 })
198 })
199 .on_action({
200 let active_item = active_item.clone();
201 move |_: &Continue, _, cx| {
202 active_item
203 .update(cx, |item, cx| item.continue_thread(cx))
204 .ok();
205 }
206 })
207 })
208 .when(supports_detach, |div| {
209 let active_item = active_item.clone();
210 div.on_action(move |_: &Detach, _, cx| {
211 active_item
212 .update(cx, |item, cx| item.detach_client(cx))
213 .ok();
214 })
215 })
216 .on_action({
217 let active_item = active_item.clone();
218 move |_: &Restart, _, cx| {
219 active_item
220 .update(cx, |item, cx| item.restart_session(cx))
221 .ok();
222 }
223 })
224 .on_action({
225 let active_item = active_item.clone();
226 move |_: &RerunSession, window, cx| {
227 active_item
228 .update(cx, |item, cx| item.rerun_session(window, cx))
229 .ok();
230 }
231 })
232 .on_action({
233 let active_item = active_item.clone();
234 move |_: &Stop, _, cx| {
235 active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();
236 }
237 })
238 .on_action({
239 let active_item = active_item.clone();
240 move |_: &ToggleIgnoreBreakpoints, _, cx| {
241 active_item
242 .update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
243 .ok();
244 }
245 })
246 .on_action(move |_: &ToggleUserFrames, _, cx| {
247 if let Some((thread_status, stack_frame_list)) = active_item
248 .read_with(cx, |item, cx| {
249 (item.thread_status(cx), item.stack_frame_list().clone())
250 })
251 .ok()
252 {
253 stack_frame_list.update(cx, |stack_frame_list, cx| {
254 stack_frame_list.toggle_frame_filter(thread_status, cx);
255 })
256 }
257 })
258 });
259 })
260 .detach();
261
262 cx.observe_new({
263 move |editor: &mut Editor, _, _| {
264 editor
265 .register_action_renderer(move |editor, window, cx| {
266 let Some(workspace) = editor.workspace() else {
267 return;
268 };
269 let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {
270 return;
271 };
272 let Some(active_session) =
273 debug_panel.update(cx, |panel, _| panel.active_session())
274 else {
275 return;
276 };
277
278 let session = active_session
279 .read(cx)
280 .running_state
281 .read(cx)
282 .session()
283 .read(cx);
284
285 if session.is_terminated() {
286 return;
287 }
288
289 let editor = cx.entity().downgrade();
290
291 window.on_action_when(
292 session.any_stopped_thread(),
293 TypeId::of::<editor::actions::RunToCursor>(),
294 {
295 let editor = editor.clone();
296 let active_session = active_session.clone();
297 move |_, phase, _, cx| {
298 if phase != DispatchPhase::Bubble {
299 return;
300 }
301 maybe!({
302 let (buffer, position, _) = editor
303 .update(cx, |editor, cx| {
304 let cursor_point: language::Point = editor
305 .selections
306 .newest(&editor.display_snapshot(cx))
307 .head();
308
309 editor
310 .buffer()
311 .read(cx)
312 .point_to_buffer_point(cursor_point, cx)
313 })
314 .ok()??;
315
316 let path =
317 debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
318 &buffer, cx,
319 )?;
320
321 let source_breakpoint = SourceBreakpoint {
322 row: position.row,
323 path,
324 message: None,
325 condition: None,
326 hit_condition: None,
327 state: debugger::breakpoint_store::BreakpointState::Enabled,
328 };
329
330 active_session.update(cx, |session, cx| {
331 session.running_state().update(cx, |state, cx| {
332 if let Some(thread_id) = state.selected_thread_id() {
333 state.session().update(cx, |session, cx| {
334 session.run_to_position(
335 source_breakpoint,
336 thread_id,
337 cx,
338 );
339 })
340 }
341 });
342 });
343
344 Some(())
345 });
346 }
347 },
348 );
349
350 window.on_action(
351 TypeId::of::<editor::actions::EvaluateSelectedText>(),
352 move |_, _, window, cx| {
353 let status = maybe!({
354 let text = editor
355 .update(cx, |editor, cx| {
356 let range = editor
357 .selections
358 .newest::<MultiBufferOffsetUtf16>(
359 &editor.display_snapshot(cx),
360 )
361 .range();
362 editor.text_for_range(
363 range.start.0.0..range.end.0.0,
364 &mut None,
365 window,
366 cx,
367 )
368 })
369 .ok()??;
370
371 active_session.update(cx, |session, cx| {
372 session.running_state().update(cx, |state, cx| {
373 let stack_id = state.selected_stack_frame_id(cx);
374
375 state.session().update(cx, |session, cx| {
376 session
377 .evaluate(
378 text,
379 Some(dap::EvaluateArgumentsContext::Repl),
380 stack_id,
381 None,
382 cx,
383 )
384 .detach();
385 });
386 });
387 });
388
389 Some(())
390 });
391 if status.is_some() {
392 cx.stop_propagation();
393 }
394 },
395 );
396 })
397 .detach();
398 }
399 })
400 .detach();
401}
402
403fn spawn_task_or_modal(
404 workspace: &mut Workspace,
405 action: &Spawn,
406 window: &mut ui::Window,
407 cx: &mut ui::Context<Workspace>,
408) {
409 match action {
410 Spawn::ByName {
411 task_name,
412 reveal_target,
413 } => {
414 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
415 reveal_target: Some(reveal_target),
416 });
417 let name = task_name.clone();
418 tasks_ui::spawn_tasks_filtered(
419 move |(_, task)| task.label.eq(&name),
420 overrides,
421 window,
422 cx,
423 )
424 .detach_and_log_err(cx)
425 }
426 Spawn::ByTag {
427 task_tag,
428 reveal_target,
429 } => {
430 let overrides = reveal_target.map(|reveal_target| TaskOverrides {
431 reveal_target: Some(reveal_target),
432 });
433 let tag = task_tag.clone();
434 tasks_ui::spawn_tasks_filtered(
435 move |(_, task)| task.tags.contains(&tag),
436 overrides,
437 window,
438 cx,
439 )
440 .detach_and_log_err(cx)
441 }
442 Spawn::ViaModal { reveal_target } => {
443 NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx);
444 }
445 }
446}