446 lines · 17.8 KB · Rust
RawDownload
use std::any::TypeId; use debugger_panel::DebugPanel;use editor::{Editor, MultiBufferOffsetUtf16};use gpui::{Action, App, DispatchPhase, EntityInputHandler, actions};use new_process_modal::{NewProcessModal, NewProcessMode};use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};use schemars::JsonSchema;use serde::Deserialize;use session::DebugSession; use tasks_ui::{Spawn, TaskOverrides};use ui::{FluentBuilder, InteractiveElement};use util::maybe;use workspace::{ShutdownDebugAdapters, Workspace};use zed_actions::debug_panel::{Toggle, ToggleFocus}; pub mod attach_modal;pub mod debugger_panel;mod dropdown_menus;mod new_process_modal;mod persistence;pub(crate) mod session; #[cfg(any(test, feature = "test-support"))]pub mod tests; // Let's see the diff-test in action.actions!(    debugger,    [        /// Starts a new debugging session.        Start,        /// Continues execution until the next breakpoint.        Continue,        /// Detaches the debugger from the running process.        Detach,        /// Pauses the currently running program.        Pause,        /// Restarts the current debugging session.        Restart,        /// Reruns the current debugging session with the same configuration.        RerunSession,        /// Steps into the next function call.        StepInto,        /// Steps over the current line.        StepOver,        /// Steps out of the current function.        StepOut,        /// Steps back to the previous statement.        StepBack,        /// Stops the debugging session.        Stop,        /// Toggles whether to ignore all breakpoints.        ToggleIgnoreBreakpoints,        /// Clears all breakpoints in the project.        ClearAllBreakpoints,        /// Focuses on the debugger console panel.        FocusConsole,        /// Focuses on the variables panel.        FocusVariables,        /// Focuses on the breakpoint list panel.        FocusBreakpointList,        /// Focuses on the call stack frames panel.        FocusFrames,        /// Focuses on the loaded modules panel.        FocusModules,        /// Focuses on the loaded sources panel.        FocusLoadedSources,        /// Focuses on the terminal panel.        FocusTerminal,        /// Toggles the thread picker dropdown.        ToggleThreadPicker,        /// Toggles the session picker dropdown.        ToggleSessionPicker,        /// Reruns the last debugging session.        #[action(deprecated_aliases = ["debugger::RerunLastSession"])]        Rerun,        /// Toggles expansion of the selected item in the debugger UI.        ToggleExpandItem,        /// Toggle the user frame filter in the stack frame list        /// When toggled on, only frames from the user's code are shown        /// When toggled off, all frames are shown        ToggleUserFrames,    ]); /// Set a data breakpoint on the selected variable or memory region.#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]#[action(namespace = debugger)]#[serde(deny_unknown_fields)]pub struct ToggleDataBreakpoint {    /// The type of data breakpoint    /// Read & Write    /// Read    /// Write    #[serde(default)]    pub access_type: Option<dap::DataBreakpointAccessType>,} actions!(    dev,    [        /// Copies debug adapter launch arguments to clipboard.        CopyDebugAdapterArguments    ]); pub fn init(cx: &mut App) {    workspace::FollowableViewRegistry::register::<DebugSession>(cx);     cx.observe_new(|workspace: &mut Workspace, _, _| {        workspace            .register_action(spawn_task_or_modal)            .register_action(|workspace, _: &ToggleFocus, window, cx| {                workspace.toggle_panel_focus::<DebugPanel>(window, cx);            })            .register_action(|workspace, _: &Toggle, window, cx| {                if !workspace.toggle_panel_focus::<DebugPanel>(window, cx) {                    workspace.close_panel::<DebugPanel>(window, cx);                }            })            .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {                NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);            })            .register_action(|workspace: &mut Workspace, _: &Rerun, window, cx| {                let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {                    return;                };                 debug_panel.update(cx, |debug_panel, cx| {                    debug_panel.rerun_last_session(workspace, window, cx);                })            })            .register_action(                |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {                    workspace.project().update(cx, |project, cx| {                        project.dap_store().update(cx, |store, cx| {                            store.shutdown_sessions(cx).detach();                        })                    })                },            )            .register_action_renderer(|div, workspace, _, cx| {                let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {                    return div;                };                let Some(active_item) = debug_panel                    .read(cx)                    .active_session()                    .map(|session| session.read(cx).running_state().clone())                else {                    return div;                };                let running_state = active_item.read(cx);                if running_state.session().read(cx).is_terminated() {                    return div;                }                 let caps = running_state.capabilities(cx);                let supports_step_back = caps.supports_step_back.unwrap_or_default();                let supports_detach = running_state.session().read(cx).is_attached();                let status = running_state.thread_status(cx);                 let active_item = active_item.downgrade();                div.when(status == Some(ThreadStatus::Running), |div| {                    let active_item = active_item.clone();                    div.on_action(move |_: &Pause, _, cx| {                        active_item                            .update(cx, |item, cx| item.pause_thread(cx))                            .ok();                    })                })                .when(status == Some(ThreadStatus::Stopped), |div| {                    div.on_action({                        let active_item = active_item.clone();                        move |_: &StepInto, _, cx| {                            active_item.update(cx, |item, cx| item.step_in(cx)).ok();                        }                    })                    .on_action({                        let active_item = active_item.clone();                        move |_: &StepOver, _, cx| {                            active_item.update(cx, |item, cx| item.step_over(cx)).ok();                        }                    })                    .on_action({                        let active_item = active_item.clone();                        move |_: &StepOut, _, cx| {                            active_item.update(cx, |item, cx| item.step_out(cx)).ok();                        }                    })                    .when(supports_step_back, |div| {                        let active_item = active_item.clone();                        div.on_action(move |_: &StepBack, _, cx| {                            active_item.update(cx, |item, cx| item.step_back(cx)).ok();                        })                    })                    .on_action({                        let active_item = active_item.clone();                        move |_: &Continue, _, cx| {                            active_item                                .update(cx, |item, cx| item.continue_thread(cx))                                .ok();                        }                    })                })                .when(supports_detach, |div| {                    let active_item = active_item.clone();                    div.on_action(move |_: &Detach, _, cx| {                        active_item                            .update(cx, |item, cx| item.detach_client(cx))                            .ok();                    })                })                .on_action({                    let active_item = active_item.clone();                    move |_: &Restart, _, cx| {                        active_item                            .update(cx, |item, cx| item.restart_session(cx))                            .ok();                    }                })                .on_action({                    let active_item = active_item.clone();                    move |_: &RerunSession, window, cx| {                        active_item                            .update(cx, |item, cx| item.rerun_session(window, cx))                            .ok();                    }                })                .on_action({                    let active_item = active_item.clone();                    move |_: &Stop, _, cx| {                        active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();                    }                })                .on_action({                    let active_item = active_item.clone();                    move |_: &ToggleIgnoreBreakpoints, _, cx| {                        active_item                            .update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))                            .ok();                    }                })                .on_action(move |_: &ToggleUserFrames, _, cx| {                    if let Some((thread_status, stack_frame_list)) = active_item                        .read_with(cx, |item, cx| {                            (item.thread_status(cx), item.stack_frame_list().clone())                        })                        .ok()                    {                        stack_frame_list.update(cx, |stack_frame_list, cx| {                            stack_frame_list.toggle_frame_filter(thread_status, cx);                        })                    }                })            });    })    .detach();     cx.observe_new({        move |editor: &mut Editor, _, _| {            editor                .register_action_renderer(move |editor, window, cx| {                    let Some(workspace) = editor.workspace() else {                        return;                    };                    let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {                        return;                    };                    let Some(active_session) =                        debug_panel.update(cx, |panel, _| panel.active_session())                    else {                        return;                    };                     let session = active_session                        .read(cx)                        .running_state                        .read(cx)                        .session()                        .read(cx);                     if session.is_terminated() {                        return;                    }                     let editor = cx.entity().downgrade();                     window.on_action_when(                        session.any_stopped_thread(),                        TypeId::of::<editor::actions::RunToCursor>(),                        {                            let editor = editor.clone();                            let active_session = active_session.clone();                            move |_, phase, _, cx| {                                if phase != DispatchPhase::Bubble {                                    return;                                }                                maybe!({                                    let (buffer, position, _) = editor                                        .update(cx, |editor, cx| {                                            let cursor_point: language::Point = editor                                                .selections                                                .newest(&editor.display_snapshot(cx))                                                .head();                                             editor                                                .buffer()                                                .read(cx)                                                .point_to_buffer_point(cursor_point, cx)                                        })                                        .ok()??;                                     let path =                                debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(                                    &buffer, cx,                                )?;                                     let source_breakpoint = SourceBreakpoint {                                        row: position.row,                                        path,                                        message: None,                                        condition: None,                                        hit_condition: None,                                        state: debugger::breakpoint_store::BreakpointState::Enabled,                                    };                                     active_session.update(cx, |session, cx| {                                        session.running_state().update(cx, |state, cx| {                                            if let Some(thread_id) = state.selected_thread_id() {                                                state.session().update(cx, |session, cx| {                                                    session.run_to_position(                                                        source_breakpoint,                                                        thread_id,                                                        cx,                                                    );                                                })                                            }                                        });                                    });                                     Some(())                                });                            }                        },                    );                     window.on_action(                        TypeId::of::<editor::actions::EvaluateSelectedText>(),                        move |_, _, window, cx| {                            let status = maybe!({                                let text = editor                                    .update(cx, |editor, cx| {                                        let range = editor                                            .selections                                            .newest::<MultiBufferOffsetUtf16>(                                                &editor.display_snapshot(cx),                                            )                                            .range();                                        editor.text_for_range(                                            range.start.0.0..range.end.0.0,                                            &mut None,                                            window,                                            cx,                                        )                                    })                                    .ok()??;                                 active_session.update(cx, |session, cx| {                                    session.running_state().update(cx, |state, cx| {                                        let stack_id = state.selected_stack_frame_id(cx);                                         state.session().update(cx, |session, cx| {                                            session                                                .evaluate(                                                    text,                                                    Some(dap::EvaluateArgumentsContext::Repl),                                                    stack_id,                                                    None,                                                    cx,                                                )                                                .detach();                                        });                                    });                                });                                 Some(())                            });                            if status.is_some() {                                cx.stop_propagation();                            }                        },                    );                })                .detach();        }    })    .detach();} fn spawn_task_or_modal(    workspace: &mut Workspace,    action: &Spawn,    window: &mut ui::Window,    cx: &mut ui::Context<Workspace>,) {    match action {        Spawn::ByName {            task_name,            reveal_target,        } => {            let overrides = reveal_target.map(|reveal_target| TaskOverrides {                reveal_target: Some(reveal_target),            });            let name = task_name.clone();            tasks_ui::spawn_tasks_filtered(                move |(_, task)| task.label.eq(&name),                overrides,                window,                cx,            )            .detach_and_log_err(cx)        }        Spawn::ByTag {            task_tag,            reveal_target,        } => {            let overrides = reveal_target.map(|reveal_target| TaskOverrides {                reveal_target: Some(reveal_target),            });            let tag = task_tag.clone();            tasks_ui::spawn_tasks_filtered(                move |(_, task)| task.tags.contains(&tag),                overrides,                window,                cx,            )            .detach_and_log_err(cx)        }        Spawn::ViaModal { reveal_target } => {            NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx);        }    }}