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