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