1902 lines · 67.4 KB · Rust
RawDownload
pub(crate) mod breakpoint_list;pub(crate) mod console;pub(crate) mod loaded_source_list;pub(crate) mod memory_view;pub(crate) mod module_list;pub mod stack_frame_list;pub mod variable_list;use std::{    any::Any,    ops::ControlFlow,    path::PathBuf,    sync::{Arc, LazyLock},    time::Duration,}; use crate::{    ToggleExpandItem,    attach_modal::{AttachModal, ModalIntent},    new_process_modal::resolve_path,    persistence::{self, DebuggerPaneItem, SerializedLayout},    session::running::memory_view::MemoryView,}; use anyhow::{Context as _, Result, anyhow, bail};use breakpoint_list::BreakpointList;use collections::{HashMap, IndexMap};use console::Console;use dap::{    Capabilities, DapRegistry, RunInTerminalRequestArguments, Thread,    adapters::{DebugAdapterName, DebugTaskDefinition},    client::SessionId,    debugger_settings::DebuggerSettings,};use futures::{SinkExt, channel::mpsc};use gpui::{    Action as _, AnyView, AppContext, Axis, Entity, EntityId, EventEmitter, FocusHandle, Focusable,    NoAction, Pixels, Point, Subscription, Task, WeakEntity,};use language::Buffer;use loaded_source_list::LoadedSourceList;use module_list::ModuleList;use project::{    DebugScenarioContext, Project, WorktreeId,    debugger::session::{self, Session, SessionEvent, SessionStateEvent, ThreadId, ThreadStatus},};use rpc::proto::ViewId;use serde_json::Value;use settings::Settings;use stack_frame_list::StackFrameList;use task::{    BuildTaskDefinition, DebugScenario, Shell, ShellBuilder, SpawnInTerminal, TaskContext,    ZedDebugConfig, substitute_variables_in_str,};use terminal_view::TerminalView;use ui::{    FluentBuilder, IntoElement, Render, StatefulInteractiveElement, Tab, Tooltip, VisibleOnHover,    VisualContext, prelude::*,};use util::ResultExt;use variable_list::VariableList;use workspace::{    ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, SplitDirection,    Workspace, item::TabContentParams, move_item, pane::Event,}; static PROCESS_ID_PLACEHOLDER: LazyLock<String> =    LazyLock::new(|| task::VariableName::PickProcessId.template_value()); pub struct RunningState {    session: Entity<Session>,    thread_id: Option<ThreadId>,    focus_handle: FocusHandle,    _remote_id: Option<ViewId>,    workspace: WeakEntity<Workspace>,    session_id: SessionId,    variable_list: Entity<variable_list::VariableList>,    _subscriptions: Vec<Subscription>,    stack_frame_list: Entity<stack_frame_list::StackFrameList>,    loaded_sources_list: Entity<LoadedSourceList>,    pub debug_terminal: Entity<DebugTerminal>,    module_list: Entity<module_list::ModuleList>,    console: Entity<Console>,    breakpoint_list: Entity<BreakpointList>,    panes: PaneGroup,    active_pane: Entity<Pane>,    pane_close_subscriptions: HashMap<EntityId, Subscription>,    dock_axis: Axis,    _schedule_serialize: Option<Task<()>>,    pub(crate) scenario: Option<DebugScenario>,    pub(crate) scenario_context: Option<DebugScenarioContext>,    memory_view: Entity<MemoryView>,} impl RunningState {    pub(crate) fn thread_id(&self) -> Option<ThreadId> {        self.thread_id    }     pub(crate) fn active_pane(&self) -> &Entity<Pane> {        &self.active_pane    }} impl Render for RunningState {    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        let zoomed_pane = self            .panes            .panes()            .into_iter()            .find(|pane| pane.read(cx).is_zoomed());         let active = self.panes.panes().into_iter().next();        let pane = if let Some(zoomed_pane) = zoomed_pane {            zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element())        } else if let Some(active) = active {            self.panes                .render(                    None,                    &ActivePaneDecorator::new(active, &self.workspace),                    window,                    cx,                )                .into_any_element()        } else {            div().into_any_element()        };        let thread_status = self            .thread_id            .map(|thread_id| self.session.read(cx).thread_status(thread_id))            .unwrap_or(ThreadStatus::Exited);         self.variable_list.update(cx, |this, cx| {            this.disabled(thread_status != ThreadStatus::Stopped, cx);        });        v_flex()            .size_full()            .key_context("DebugSessionItem")            .track_focus(&self.focus_handle(cx))            .child(h_flex().flex_1().child(pane))    }} pub(crate) struct SubView {    inner: AnyView,    item_focus_handle: FocusHandle,    kind: DebuggerPaneItem,    show_indicator: Box<dyn Fn(&App) -> bool>,    actions: Option<Box<dyn FnMut(&mut Window, &mut App) -> AnyElement>>,    hovered: bool,} impl SubView {    pub(crate) fn new(        item_focus_handle: FocusHandle,        view: AnyView,        kind: DebuggerPaneItem,        cx: &mut App,    ) -> Entity<Self> {        cx.new(|_| Self {            kind,            inner: view,            item_focus_handle,            show_indicator: Box::new(|_| false),            actions: None,            hovered: false,        })    }     pub(crate) fn stack_frame_list(        stack_frame_list: Entity<StackFrameList>,        cx: &mut App,    ) -> Entity<Self> {        let weak_list = stack_frame_list.downgrade();        let this = Self::new(            stack_frame_list.focus_handle(cx),            stack_frame_list.into(),            DebuggerPaneItem::Frames,            cx,        );         this.update(cx, |this, _| {            this.with_actions(Box::new(move |_, cx| {                weak_list                    .update(cx, |this, _| this.render_control_strip())                    .unwrap_or_else(|_| div().into_any_element())            }));        });         this    }     pub(crate) fn console(console: Entity<Console>, cx: &mut App) -> Entity<Self> {        let weak_console = console.downgrade();        let this = Self::new(            console.focus_handle(cx),            console.into(),            DebuggerPaneItem::Console,            cx,        );        this.update(cx, |this, _| {            this.with_indicator(Box::new(move |cx| {                weak_console                    .read_with(cx, |console, cx| console.show_indicator(cx))                    .unwrap_or_default()            }))        });        this    }     pub(crate) fn breakpoint_list(list: Entity<BreakpointList>, cx: &mut App) -> Entity<Self> {        let weak_list = list.downgrade();        let focus_handle = list.focus_handle(cx);        let this = Self::new(            focus_handle,            list.into(),            DebuggerPaneItem::BreakpointList,            cx,        );         this.update(cx, |this, _| {            this.with_actions(Box::new(move |_, cx| {                weak_list                    .update(cx, |this, _| this.render_control_strip())                    .unwrap_or_else(|_| div().into_any_element())            }));        });        this    }     pub(crate) fn view_kind(&self) -> DebuggerPaneItem {        self.kind    }    pub(crate) fn with_indicator(&mut self, indicator: Box<dyn Fn(&App) -> bool>) {        self.show_indicator = indicator;    }    pub(crate) fn with_actions(        &mut self,        actions: Box<dyn FnMut(&mut Window, &mut App) -> AnyElement>,    ) {        self.actions = Some(actions);    }}impl Focusable for SubView {    fn focus_handle(&self, _: &App) -> FocusHandle {        self.item_focus_handle.clone()    }}impl EventEmitter<()> for SubView {}impl Item for SubView {    type Event = ();     /// This is used to serialize debugger pane layouts    /// A SharedString gets converted to a enum and back during serialization/deserialization.    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {        self.kind.to_shared_string()    }     fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> {        Some(self.kind.tab_tooltip())    }     fn tab_content(        &self,        params: workspace::item::TabContentParams,        _: &Window,        cx: &App,    ) -> AnyElement {        let label = Label::new(self.kind.to_shared_string())            .size(ui::LabelSize::Small)            .color(params.text_color())            .line_height_style(ui::LineHeightStyle::UiLabel);         if !params.selected && self.show_indicator.as_ref()(cx) {            return h_flex()                .justify_between()                .child(ui::Indicator::dot())                .gap_2()                .child(label)                .into_any_element();        }         label.into_any_element()    }} impl Render for SubView {    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        v_flex()            .id(format!(                "subview-container-{}",                self.kind.to_shared_string()            ))            .on_hover(cx.listener(|this, hovered, _, cx| {                this.hovered = *hovered;                cx.notify();            }))            .size_full()            // Add border unconditionally to prevent layout shifts on focus changes.            .border_1()            .when(self.item_focus_handle.contains_focused(window, cx), |el| {                el.border_color(cx.theme().colors().pane_focused_border)            })            .child(self.inner.clone())    }} pub(crate) fn new_debugger_pane(    workspace: WeakEntity<Workspace>,    project: Entity<Project>,    window: &mut Window,    cx: &mut Context<RunningState>,) -> Entity<Pane> {    let weak_running = cx.weak_entity();    let custom_drop_handle = {        let workspace = workspace.clone();        let project = project.downgrade();        let weak_running = weak_running.clone();        move |pane: &mut Pane, any: &dyn Any, window: &mut Window, cx: &mut Context<Pane>| {            let Some(tab) = any.downcast_ref::<DraggedTab>() else {                return ControlFlow::Break(());            };            let Some(project) = project.upgrade() else {                return ControlFlow::Break(());            };            let this_pane = cx.entity();            let item = if tab.pane == this_pane {                pane.item_for_index(tab.ix)            } else {                tab.pane.read(cx).item_for_index(tab.ix)            };            let Some(item) = item.filter(|item| item.downcast::<SubView>().is_some()) else {                return ControlFlow::Break(());            };             let source = tab.pane.clone();            let item_id_to_move = item.item_id();             let Ok(new_split_pane) = pane                .drag_split_direction()                .map(|split_direction| {                    weak_running.update(cx, |running, cx| {                        let new_pane =                            new_debugger_pane(workspace.clone(), project.clone(), window, cx);                        let _previous_subscription = running.pane_close_subscriptions.insert(                            new_pane.entity_id(),                            cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event),                        );                        debug_assert!(_previous_subscription.is_none());                        running                            .panes                            .split(&this_pane, &new_pane, split_direction, cx)?;                        anyhow::Ok(new_pane)                    })                })                .transpose()            else {                return ControlFlow::Break(());            };             match new_split_pane.transpose() {                // Source pane may be the one currently updated, so defer the move.                Ok(Some(new_pane)) => cx                    .spawn_in(window, async move |_, cx| {                        cx.update(|window, cx| {                            move_item(                                &source,                                &new_pane,                                item_id_to_move,                                new_pane.read(cx).active_item_index(),                                true,                                window,                                cx,                            );                        })                        .ok();                    })                    .detach(),                // If we drop into existing pane or current pane,                // regular pane drop handler will take care of it,                // using the right tab index for the operation.                Ok(None) => return ControlFlow::Continue(()),                err @ Err(_) => {                    err.log_err();                    return ControlFlow::Break(());                }            };             ControlFlow::Break(())        }    };     cx.new(move |cx| {        let mut pane = Pane::new(            workspace.clone(),            project.clone(),            Default::default(),            None,            NoAction.boxed_clone(),            true,            window,            cx,        );        let focus_handle = pane.focus_handle(cx);        pane.set_can_split(Some(Arc::new({            let weak_running = weak_running.clone();            move |pane, dragged_item, _window, cx| {                if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {                    let is_current_pane = tab.pane == cx.entity();                    let Some(can_drag_away) = weak_running                        .read_with(cx, |running_state, _| {                            let current_panes = running_state.panes.panes();                            !current_panes.contains(&&tab.pane)                                || current_panes.len() > 1                                || (!is_current_pane || pane.items_len() > 1)                        })                        .ok()                    else {                        return false;                    };                    if can_drag_away {                        let item = if is_current_pane {                            pane.item_for_index(tab.ix)                        } else {                            tab.pane.read(cx).item_for_index(tab.ix)                        };                        if let Some(item) = item {                            return item.downcast::<SubView>().is_some();                        }                    }                }                false            }        })));        pane.set_can_toggle_zoom(false, cx);        pane.display_nav_history_buttons(None);        pane.set_custom_drop_handle(cx, custom_drop_handle);        pane.set_should_display_tab_bar(|_, _| true);        pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None));        pane.set_render_tab_bar(cx, {            move |pane, window, cx| {                let active_pane_item = pane.active_item();                let pane_group_id: SharedString =                    format!("pane-zoom-button-hover-{}", cx.entity_id()).into();                let as_subview = active_pane_item                    .as_ref()                    .and_then(|item| item.downcast::<SubView>());                let is_hovered = as_subview                    .as_ref()                    .is_some_and(|item| item.read(cx).hovered);                 h_flex()                    .track_focus(&focus_handle)                    .group(pane_group_id.clone())                    .pl_1p5()                    .pr_1()                    .justify_between()                    .border_b_1()                    .border_color(cx.theme().colors().border)                    .bg(cx.theme().colors().tab_bar_background)                    .on_action(|_: &menu::Cancel, window, cx| {                        if cx.stop_active_drag(window) {                        } else {                            cx.propagate();                        }                    })                    .child(                        h_flex()                            .w_full()                            .gap_1()                            .h(Tab::container_height(cx))                            .drag_over::<DraggedTab>(|bar, _, _, cx| {                                bar.bg(cx.theme().colors().drop_target_background)                            })                            .on_drop(cx.listener(                                move |this, dragged_tab: &DraggedTab, window, cx| {                                    this.drag_split_direction = None;                                    this.handle_tab_drop(dragged_tab, this.items_len(), window, cx)                                },                            ))                            .children(pane.items().enumerate().map(|(ix, item)| {                                let selected = active_pane_item                                    .as_ref()                                    .is_some_and(|active| active.item_id() == item.item_id());                                let deemphasized = !pane.has_focus(window, cx);                                let item_ = item.boxed_clone();                                div()                                    .id(format!("debugger_tab_{}", item.item_id().as_u64()))                                    .p_1()                                    .rounded_md()                                    .cursor_pointer()                                    .when_some(item.tab_tooltip_text(cx), |this, tooltip| {                                        this.tooltip(Tooltip::text(tooltip))                                    })                                    .map(|this| {                                        let theme = cx.theme();                                        if selected {                                            let color = theme.colors().tab_active_background;                                            let color = if deemphasized {                                                color.opacity(0.5)                                            } else {

Lines 1–500 of 1902.