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