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