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