debugger_ui.rs

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