debugger_ui.rs

  1use std::any::TypeId;
  2
  3use debugger_panel::DebugPanel;
  4use editor::{Editor, MultiBufferOffsetUtf16};
  5use gpui::{Action, App, DispatchPhase, EntityInputHandler, actions};
  6use new_process_modal::{NewProcessModal, NewProcessMode};
  7use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};
  8use schemars::JsonSchema;
  9use serde::Deserialize;
 10use session::DebugSession;
 11
 12use tasks_ui::{Spawn, TaskOverrides};
 13use ui::{FluentBuilder, InteractiveElement};
 14use util::maybe;
 15use workspace::{ShutdownDebugAdapters, Workspace};
 16use zed_actions::debug_panel::{Toggle, ToggleFocus};
 17
 18pub mod attach_modal;
 19pub mod debugger_panel;
 20mod dropdown_menus;
 21mod new_process_modal;
 22mod persistence;
 23pub(crate) mod session;
 24
 25#[cfg(any(test, feature = "test-support"))]
 26pub mod tests;
 27
 28// Let's see the diff-test in action.
 29actions!(
 30    debugger,
 31    [
 32        /// Starts a new debugging session.
 33        Start,
 34        /// Continues execution until the next breakpoint.
 35        Continue,
 36        /// Detaches the debugger from the running process.
 37        Detach,
 38        /// Pauses the currently running program.
 39        Pause,
 40        /// Restarts the current debugging session.
 41        Restart,
 42        /// Reruns the current debugging session with the same configuration.
 43        RerunSession,
 44        /// Steps into the next function call.
 45        StepInto,
 46        /// Steps over the current line.
 47        StepOver,
 48        /// Steps out of the current function.
 49        StepOut,
 50        /// Steps back to the previous statement.
 51        StepBack,
 52        /// Stops the debugging session.
 53        Stop,
 54        /// Toggles whether to ignore all breakpoints.
 55        ToggleIgnoreBreakpoints,
 56        /// Clears all breakpoints in the project.
 57        ClearAllBreakpoints,
 58        /// Focuses on the debugger console panel.
 59        FocusConsole,
 60        /// Focuses on the variables panel.
 61        FocusVariables,
 62        /// Focuses on the breakpoint list panel.
 63        FocusBreakpointList,
 64        /// Focuses on the call stack frames panel.
 65        FocusFrames,
 66        /// Focuses on the loaded modules panel.
 67        FocusModules,
 68        /// Focuses on the loaded sources panel.
 69        FocusLoadedSources,
 70        /// Focuses on the terminal panel.
 71        FocusTerminal,
 72        /// Toggles the thread picker dropdown.
 73        ToggleThreadPicker,
 74        /// Toggles the session picker dropdown.
 75        ToggleSessionPicker,
 76        /// Reruns the last debugging session.
 77        #[action(deprecated_aliases = ["debugger::RerunLastSession"])]
 78        Rerun,
 79        /// Toggles expansion of the selected item in the debugger UI.
 80        ToggleExpandItem,
 81        /// Toggle the user frame filter in the stack frame list
 82        /// When toggled on, only frames from the user's code are shown
 83        /// When toggled off, all frames are shown
 84        ToggleUserFrames,
 85    ]
 86);
 87
 88/// Set a data breakpoint on the selected variable or memory region.
 89#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
 90#[action(namespace = debugger)]
 91#[serde(deny_unknown_fields)]
 92pub struct ToggleDataBreakpoint {
 93    /// The type of data breakpoint
 94    /// Read & Write
 95    /// Read
 96    /// Write
 97    #[serde(default)]
 98    pub access_type: Option<dap::DataBreakpointAccessType>,
 99}
100
101actions!(
102    dev,
103    [
104        /// Copies debug adapter launch arguments to clipboard.
105        CopyDebugAdapterArguments
106    ]
107);
108
109pub fn init(cx: &mut App) {
110    workspace::FollowableViewRegistry::register::<DebugSession>(cx);
111
112    cx.observe_new(|workspace: &mut Workspace, _, _| {
113        workspace
114            .register_action(spawn_task_or_modal)
115            .register_action(|workspace, _: &ToggleFocus, window, cx| {
116                workspace.toggle_panel_focus::<DebugPanel>(window, cx);
117            })
118            .register_action(|workspace, _: &Toggle, window, cx| {
119                if !workspace.toggle_panel_focus::<DebugPanel>(window, cx) {
120                    workspace.close_panel::<DebugPanel>(window, cx);
121                }
122            })
123            .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
124                NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
125            })
126            .register_action(|workspace: &mut Workspace, _: &Rerun, window, cx| {
127                let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
128                    return;
129                };
130
131                debug_panel.update(cx, |debug_panel, cx| {
132                    debug_panel.rerun_last_session(workspace, window, cx);
133                })
134            })
135            .register_action(
136                |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
137                    workspace.project().update(cx, |project, cx| {
138                        project.dap_store().update(cx, |store, cx| {
139                            store.shutdown_sessions(cx).detach();
140                        })
141                    })
142                },
143            )
144            .register_action_renderer(|div, workspace, _, cx| {
145                let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
146                    return div;
147                };
148                let Some(active_item) = debug_panel
149                    .read(cx)
150                    .active_session()
151                    .map(|session| session.read(cx).running_state().clone())
152                else {
153                    return div;
154                };
155                let running_state = active_item.read(cx);
156                if running_state.session().read(cx).is_terminated() {
157                    return div;
158                }
159
160                let caps = running_state.capabilities(cx);
161                let supports_step_back = caps.supports_step_back.unwrap_or_default();
162                let supports_detach = running_state.session().read(cx).is_attached();
163                let status = running_state.thread_status(cx);
164
165                let active_item = active_item.downgrade();
166                div.when(status == Some(ThreadStatus::Running), |div| {
167                    let active_item = active_item.clone();
168                    div.on_action(move |_: &Pause, _, cx| {
169                        active_item
170                            .update(cx, |item, cx| item.pause_thread(cx))
171                            .ok();
172                    })
173                })
174                .when(status == Some(ThreadStatus::Stopped), |div| {
175                    div.on_action({
176                        let active_item = active_item.clone();
177                        move |_: &StepInto, _, cx| {
178                            active_item.update(cx, |item, cx| item.step_in(cx)).ok();
179                        }
180                    })
181                    .on_action({
182                        let active_item = active_item.clone();
183                        move |_: &StepOver, _, cx| {
184                            active_item.update(cx, |item, cx| item.step_over(cx)).ok();
185                        }
186                    })
187                    .on_action({
188                        let active_item = active_item.clone();
189                        move |_: &StepOut, _, cx| {
190                            active_item.update(cx, |item, cx| item.step_out(cx)).ok();
191                        }
192                    })
193                    .when(supports_step_back, |div| {
194                        let active_item = active_item.clone();
195                        div.on_action(move |_: &StepBack, _, cx| {
196                            active_item.update(cx, |item, cx| item.step_back(cx)).ok();
197                        })
198                    })
199                    .on_action({
200                        let active_item = active_item.clone();
201                        move |_: &Continue, _, cx| {
202                            active_item
203                                .update(cx, |item, cx| item.continue_thread(cx))
204                                .ok();
205                        }
206                    })
207                })
208                .when(supports_detach, |div| {
209                    let active_item = active_item.clone();
210                    div.on_action(move |_: &Detach, _, cx| {
211                        active_item
212                            .update(cx, |item, cx| item.detach_client(cx))
213                            .ok();
214                    })
215                })
216                .on_action({
217                    let active_item = active_item.clone();
218                    move |_: &Restart, _, cx| {
219                        active_item
220                            .update(cx, |item, cx| item.restart_session(cx))
221                            .ok();
222                    }
223                })
224                .on_action({
225                    let active_item = active_item.clone();
226                    move |_: &RerunSession, window, cx| {
227                        active_item
228                            .update(cx, |item, cx| item.rerun_session(window, cx))
229                            .ok();
230                    }
231                })
232                .on_action({
233                    let active_item = active_item.clone();
234                    move |_: &Stop, _, cx| {
235                        active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();
236                    }
237                })
238                .on_action({
239                    let active_item = active_item.clone();
240                    move |_: &ToggleIgnoreBreakpoints, _, cx| {
241                        active_item
242                            .update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
243                            .ok();
244                    }
245                })
246                .on_action(move |_: &ToggleUserFrames, _, cx| {
247                    if let Some((thread_status, stack_frame_list)) = active_item
248                        .read_with(cx, |item, cx| {
249                            (item.thread_status(cx), item.stack_frame_list().clone())
250                        })
251                        .ok()
252                    {
253                        stack_frame_list.update(cx, |stack_frame_list, cx| {
254                            stack_frame_list.toggle_frame_filter(thread_status, cx);
255                        })
256                    }
257                })
258            });
259    })
260    .detach();
261
262    cx.observe_new({
263        move |editor: &mut Editor, _, _| {
264            editor
265                .register_action_renderer(move |editor, window, cx| {
266                    let Some(workspace) = editor.workspace() else {
267                        return;
268                    };
269                    let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {
270                        return;
271                    };
272                    let Some(active_session) =
273                        debug_panel.update(cx, |panel, _| panel.active_session())
274                    else {
275                        return;
276                    };
277
278                    let session = active_session
279                        .read(cx)
280                        .running_state
281                        .read(cx)
282                        .session()
283                        .read(cx);
284
285                    if session.is_terminated() {
286                        return;
287                    }
288
289                    let editor = cx.entity().downgrade();
290
291                    window.on_action_when(
292                        session.any_stopped_thread(),
293                        TypeId::of::<editor::actions::RunToCursor>(),
294                        {
295                            let editor = editor.clone();
296                            let active_session = active_session.clone();
297                            move |_, phase, _, cx| {
298                                if phase != DispatchPhase::Bubble {
299                                    return;
300                                }
301                                maybe!({
302                                    let (buffer, position, _) = editor
303                                        .update(cx, |editor, cx| {
304                                            let cursor_point: language::Point = editor
305                                                .selections
306                                                .newest(&editor.display_snapshot(cx))
307                                                .head();
308
309                                            editor
310                                                .buffer()
311                                                .read(cx)
312                                                .point_to_buffer_point(cursor_point, cx)
313                                        })
314                                        .ok()??;
315
316                                    let path =
317                                debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
318                                    &buffer, cx,
319                                )?;
320
321                                    let source_breakpoint = SourceBreakpoint {
322                                        row: position.row,
323                                        path,
324                                        message: None,
325                                        condition: None,
326                                        hit_condition: None,
327                                        state: debugger::breakpoint_store::BreakpointState::Enabled,
328                                    };
329
330                                    active_session.update(cx, |session, cx| {
331                                        session.running_state().update(cx, |state, cx| {
332                                            if let Some(thread_id) = state.selected_thread_id() {
333                                                state.session().update(cx, |session, cx| {
334                                                    session.run_to_position(
335                                                        source_breakpoint,
336                                                        thread_id,
337                                                        cx,
338                                                    );
339                                                })
340                                            }
341                                        });
342                                    });
343
344                                    Some(())
345                                });
346                            }
347                        },
348                    );
349
350                    window.on_action(
351                        TypeId::of::<editor::actions::EvaluateSelectedText>(),
352                        move |_, _, window, cx| {
353                            let status = maybe!({
354                                let text = editor
355                                    .update(cx, |editor, cx| {
356                                        let range = editor
357                                            .selections
358                                            .newest::<MultiBufferOffsetUtf16>(
359                                                &editor.display_snapshot(cx),
360                                            )
361                                            .range();
362                                        editor.text_for_range(
363                                            range.start.0.0..range.end.0.0,
364                                            &mut None,
365                                            window,
366                                            cx,
367                                        )
368                                    })
369                                    .ok()??;
370
371                                active_session.update(cx, |session, cx| {
372                                    session.running_state().update(cx, |state, cx| {
373                                        let stack_id = state.selected_stack_frame_id(cx);
374
375                                        state.session().update(cx, |session, cx| {
376                                            session
377                                                .evaluate(
378                                                    text,
379                                                    Some(dap::EvaluateArgumentsContext::Repl),
380                                                    stack_id,
381                                                    None,
382                                                    cx,
383                                                )
384                                                .detach();
385                                        });
386                                    });
387                                });
388
389                                Some(())
390                            });
391                            if status.is_some() {
392                                cx.stop_propagation();
393                            }
394                        },
395                    );
396                })
397                .detach();
398        }
399    })
400    .detach();
401}
402
403fn spawn_task_or_modal(
404    workspace: &mut Workspace,
405    action: &Spawn,
406    window: &mut ui::Window,
407    cx: &mut ui::Context<Workspace>,
408) {
409    match action {
410        Spawn::ByName {
411            task_name,
412            reveal_target,
413        } => {
414            let overrides = reveal_target.map(|reveal_target| TaskOverrides {
415                reveal_target: Some(reveal_target),
416            });
417            let name = task_name.clone();
418            tasks_ui::spawn_tasks_filtered(
419                move |(_, task)| task.label.eq(&name),
420                overrides,
421                window,
422                cx,
423            )
424            .detach_and_log_err(cx)
425        }
426        Spawn::ByTag {
427            task_tag,
428            reveal_target,
429        } => {
430            let overrides = reveal_target.map(|reveal_target| TaskOverrides {
431                reveal_target: Some(reveal_target),
432            });
433            let tag = task_tag.clone();
434            tasks_ui::spawn_tasks_filtered(
435                move |(_, task)| task.tags.contains(&tag),
436                overrides,
437                window,
438                cx,
439            )
440            .detach_and_log_err(cx)
441        }
442        Spawn::ViaModal { reveal_target } => {
443            NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx);
444        }
445    }
446}