debugger_ui.rs

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