debugger_ui.rs

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