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