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