debugger_ui.rs

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