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