1517 lines · 55.2 KB · Rust
RawDownload
use std::{    ops::Range,    path::{Path, PathBuf},    sync::Arc,    time::Duration,}; use dap::{Capabilities, ExceptionBreakpointsFilter, adapters::DebugAdapterName};use db::kvp::KeyValueStore;use editor::Editor;use gpui::{    Action, AppContext, ClickEvent, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy,    Task, UniformListScrollHandle, WeakEntity, actions, uniform_list,};use itertools::Itertools;use language::Point;use project::{    Project,    debugger::{        breakpoint_store::{BreakpointEditAction, BreakpointStore, SourceBreakpoint},        dap_store::{DapStore, PersistedAdapterOptions},        session::Session,    },    worktree_store::WorktreeStore,};use ui::{    Divider, DividerColor, FluentBuilder as _, Indicator, IntoElement, ListItem, Render,    ScrollAxes, StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,};use util::rel_path::RelPath;use workspace::Workspace;use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint}; actions!(    debugger,    [        /// Navigates to the previous breakpoint property in the list.        PreviousBreakpointProperty,        /// Navigates to the next breakpoint property in the list.        NextBreakpointProperty    ]);#[derive(Clone, Copy, PartialEq)]pub(crate) enum SelectedBreakpointKind {    Source,    Exception,    Data,}pub(crate) struct BreakpointList {    workspace: WeakEntity<Workspace>,    breakpoint_store: Entity<BreakpointStore>,    dap_store: Entity<DapStore>,    worktree_store: Entity<WorktreeStore>,    breakpoints: Vec<BreakpointEntry>,    session: Option<Entity<Session>>,    focus_handle: FocusHandle,    scroll_handle: UniformListScrollHandle,    selected_ix: Option<usize>,    max_width_index: Option<usize>,    input: Entity<Editor>,    strip_mode: Option<ActiveBreakpointStripMode>,    serialize_exception_breakpoints_task: Option<Task<anyhow::Result<()>>>,} impl Focusable for BreakpointList {    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {        self.focus_handle.clone()    }} #[derive(Clone, Copy, PartialEq)]enum ActiveBreakpointStripMode {    Log,    Condition,    HitCondition,} impl BreakpointList {    pub(crate) fn new(        session: Option<Entity<Session>>,        workspace: WeakEntity<Workspace>,        project: &Entity<Project>,        window: &mut Window,        cx: &mut App,    ) -> Entity<Self> {        let project = project.read(cx);        let breakpoint_store = project.breakpoint_store();        let worktree_store = project.worktree_store();        let dap_store = project.dap_store();        let focus_handle = cx.focus_handle();        let scroll_handle = UniformListScrollHandle::new();         let adapter_name = session.as_ref().map(|session| session.read(cx).adapter());        cx.new(|cx| {            let this = Self {                breakpoint_store,                dap_store,                worktree_store,                breakpoints: Default::default(),                max_width_index: None,                workspace,                session,                focus_handle,                scroll_handle,                selected_ix: None,                input: cx.new(|cx| Editor::single_line(window, cx)),                strip_mode: None,                serialize_exception_breakpoints_task: None,            };            if let Some(name) = adapter_name {                _ = this.deserialize_exception_breakpoints(name, cx);            }            this        })    }     fn edit_line_breakpoint(        &self,        path: Arc<Path>,        row: u32,        action: BreakpointEditAction,        cx: &mut App,    ) {        Self::edit_line_breakpoint_inner(&self.breakpoint_store, path, row, action, cx);    }    fn edit_line_breakpoint_inner(        breakpoint_store: &Entity<BreakpointStore>,        path: Arc<Path>,        row: u32,        action: BreakpointEditAction,        cx: &mut App,    ) {        breakpoint_store.update(cx, |breakpoint_store, cx| {            if let Some((buffer, breakpoint)) = breakpoint_store.breakpoint_at_row(&path, row, cx) {                breakpoint_store.toggle_breakpoint(buffer, breakpoint, action, cx);            } else {                log::error!("Couldn't find breakpoint at row event though it exists: row {row}")            }        })    }     fn go_to_line_breakpoint(        &mut self,        path: Arc<Path>,        row: u32,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let task = self            .worktree_store            .update(cx, |this, cx| this.find_or_create_worktree(path, false, cx));        cx.spawn_in(window, async move |this, cx| {            let (worktree, relative_path) = task.await?;            let worktree_id = worktree.read_with(cx, |this, _| this.id());            let item = this                .update_in(cx, |this, window, cx| {                    this.workspace.update(cx, |this, cx| {                        this.open_path((worktree_id, relative_path), None, true, window, cx)                    })                })??                .await?;            if let Some(editor) = item.downcast::<Editor>() {                editor                    .update_in(cx, |this, window, cx| {                        this.go_to_singleton_buffer_point(Point { row, column: 0 }, window, cx);                    })                    .ok();            }            anyhow::Ok(())        })        .detach();    }     pub(crate) fn selection_kind(&self) -> Option<(SelectedBreakpointKind, bool)> {        self.selected_ix.and_then(|ix| {            self.breakpoints.get(ix).map(|bp| match &bp.kind {                BreakpointEntryKind::LineBreakpoint(bp) => (                    SelectedBreakpointKind::Source,                    bp.breakpoint.state                        == project::debugger::breakpoint_store::BreakpointState::Enabled,                ),                BreakpointEntryKind::ExceptionBreakpoint(bp) => {                    (SelectedBreakpointKind::Exception, bp.is_enabled)                }                BreakpointEntryKind::DataBreakpoint(bp) => {                    (SelectedBreakpointKind::Data, bp.0.is_enabled)                }            })        })    }     fn set_active_breakpoint_property(        &mut self,        prop: ActiveBreakpointStripMode,        window: &mut Window,        cx: &mut App,    ) {        self.strip_mode = Some(prop);        let placeholder = match prop {            ActiveBreakpointStripMode::Log => "Set Log Message",            ActiveBreakpointStripMode::Condition => "Set Condition",            ActiveBreakpointStripMode::HitCondition => "Set Hit Condition",        };        let mut is_exception_breakpoint = true;        let active_value = self.selected_ix.and_then(|ix| {            self.breakpoints.get(ix).and_then(|bp| {                if let BreakpointEntryKind::LineBreakpoint(bp) = &bp.kind {                    is_exception_breakpoint = false;                    match prop {                        ActiveBreakpointStripMode::Log => bp.breakpoint.message.clone(),                        ActiveBreakpointStripMode::Condition => bp.breakpoint.condition.clone(),                        ActiveBreakpointStripMode::HitCondition => {                            bp.breakpoint.hit_condition.clone()                        }                    }                } else {                    None                }            })        });         self.input.update(cx, |this, cx| {            this.set_placeholder_text(placeholder, window, cx);            this.set_read_only(is_exception_breakpoint);            this.set_text(active_value.as_deref().unwrap_or(""), window, cx);        });    }     fn select_ix(&mut self, ix: Option<usize>, window: &mut Window, cx: &mut Context<Self>) {        self.selected_ix = ix;        if let Some(ix) = ix {            self.scroll_handle                .scroll_to_item(ix, ScrollStrategy::Center);        }        if let Some(mode) = self.strip_mode {            self.set_active_breakpoint_property(mode, window, cx);        }         cx.notify();    }     fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {        if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) {            cx.propagate();            return;        }        let ix = match self.selected_ix {            _ if self.breakpoints.is_empty() => None,            None => Some(0),            Some(ix) => {                if ix == self.breakpoints.len() - 1 {                    Some(0)                } else {                    Some(ix + 1)                }            }        };        self.select_ix(ix, window, cx);    }     fn select_previous(        &mut self,        _: &menu::SelectPrevious,        window: &mut Window,        cx: &mut Context<Self>,    ) {        if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) {            cx.propagate();            return;        }        let ix = match self.selected_ix {            _ if self.breakpoints.is_empty() => None,            None => Some(self.breakpoints.len() - 1),            Some(ix) => {                if ix == 0 {                    Some(self.breakpoints.len() - 1)                } else {                    Some(ix - 1)                }            }        };        self.select_ix(ix, window, cx);    }     fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {        if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) {            cx.propagate();            return;        }        let ix = if !self.breakpoints.is_empty() {            Some(0)        } else {            None        };        self.select_ix(ix, window, cx);    }     fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {        if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) {            cx.propagate();            return;        }        let ix = if !self.breakpoints.is_empty() {            Some(self.breakpoints.len() - 1)        } else {            None        };        self.select_ix(ix, window, cx);    }     fn dismiss(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {        if self.input.focus_handle(cx).contains_focused(window, cx) {            self.focus_handle.focus(window, cx);        } else if self.strip_mode.is_some() {            self.strip_mode.take();            cx.notify();        } else {            cx.propagate();        }    }    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {        let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else {            return;        };         if let Some(mode) = self.strip_mode {            let handle = self.input.focus_handle(cx);            if handle.is_focused(window) {                // Go back to the main strip. Save the result as well.                let text = self.input.read(cx).text(cx);                 match mode {                    ActiveBreakpointStripMode::Log => {                        if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &entry.kind {                            Self::edit_line_breakpoint_inner(                                &self.breakpoint_store,                                line_breakpoint.breakpoint.path.clone(),                                line_breakpoint.breakpoint.row,                                BreakpointEditAction::EditLogMessage(Arc::from(text)),                                cx,                            );                        }                    }                    ActiveBreakpointStripMode::Condition => {                        if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &entry.kind {                            Self::edit_line_breakpoint_inner(                                &self.breakpoint_store,                                line_breakpoint.breakpoint.path.clone(),                                line_breakpoint.breakpoint.row,                                BreakpointEditAction::EditCondition(Arc::from(text)),                                cx,                            );                        }                    }                    ActiveBreakpointStripMode::HitCondition => {                        if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &entry.kind {                            Self::edit_line_breakpoint_inner(                                &self.breakpoint_store,                                line_breakpoint.breakpoint.path.clone(),                                line_breakpoint.breakpoint.row,                                BreakpointEditAction::EditHitCondition(Arc::from(text)),                                cx,                            );                        }                    }                }                self.focus_handle.focus(window, cx);            } else {                handle.focus(window, cx);            }             return;        }        match &mut entry.kind {            BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {                let path = line_breakpoint.breakpoint.path.clone();                let row = line_breakpoint.breakpoint.row;                self.go_to_line_breakpoint(path, row, window, cx);            }            BreakpointEntryKind::DataBreakpoint(_)            | BreakpointEntryKind::ExceptionBreakpoint(_) => {}        }    }     fn toggle_enable_breakpoint(        &mut self,        _: &ToggleEnableBreakpoint,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else {            return;        };        if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) {            cx.propagate();            return;        }         match &mut entry.kind {            BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {                let path = line_breakpoint.breakpoint.path.clone();                let row = line_breakpoint.breakpoint.row;                self.edit_line_breakpoint(path, row, BreakpointEditAction::InvertState, cx);            }            BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => {                let id = exception_breakpoint.id.clone();                self.toggle_exception_breakpoint(&id, cx);            }            BreakpointEntryKind::DataBreakpoint(data_breakpoint) => {                let id = data_breakpoint.0.dap.data_id.clone();                self.toggle_data_breakpoint(&id, cx);            }        }        cx.notify();    }     fn unset_breakpoint(        &mut self,        _: &UnsetBreakpoint,        _window: &mut Window,        cx: &mut Context<Self>,    ) {        let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else {            return;        };         if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &mut entry.kind {            let path = line_breakpoint.breakpoint.path.clone();            let row = line_breakpoint.breakpoint.row;            self.edit_line_breakpoint(path, row, BreakpointEditAction::Toggle, cx);        }        cx.notify();    }     fn previous_breakpoint_property(        &mut self,        _: &PreviousBreakpointProperty,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let next_mode = match self.strip_mode {            Some(ActiveBreakpointStripMode::Log) => None,            Some(ActiveBreakpointStripMode::Condition) => Some(ActiveBreakpointStripMode::Log),            Some(ActiveBreakpointStripMode::HitCondition) => {                Some(ActiveBreakpointStripMode::Condition)            }            None => Some(ActiveBreakpointStripMode::HitCondition),        };        if let Some(mode) = next_mode {            self.set_active_breakpoint_property(mode, window, cx);        } else {            self.strip_mode.take();        }         cx.notify();    }    fn next_breakpoint_property(        &mut self,        _: &NextBreakpointProperty,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let next_mode = match self.strip_mode {            Some(ActiveBreakpointStripMode::Log) => Some(ActiveBreakpointStripMode::Condition),            Some(ActiveBreakpointStripMode::Condition) => {                Some(ActiveBreakpointStripMode::HitCondition)            }            Some(ActiveBreakpointStripMode::HitCondition) => None,            None => Some(ActiveBreakpointStripMode::Log),        };        if let Some(mode) = next_mode {            self.set_active_breakpoint_property(mode, window, cx);        } else {            self.strip_mode.take();        }        cx.notify();    }     fn toggle_data_breakpoint(&mut self, id: &str, cx: &mut Context<Self>) {        if let Some(session) = &self.session {            session.update(cx, |this, cx| {                this.toggle_data_breakpoint(id, cx);            });        }    }     fn toggle_exception_breakpoint(&mut self, id: &str, cx: &mut Context<Self>) {        if let Some(session) = &self.session {            session.update(cx, |this, cx| {                this.toggle_exception_breakpoint(id, cx);            });            cx.notify();            const EXCEPTION_SERIALIZATION_INTERVAL: Duration = Duration::from_secs(1);            self.serialize_exception_breakpoints_task = Some(cx.spawn(async move |this, cx| {                cx.background_executor()                    .timer(EXCEPTION_SERIALIZATION_INTERVAL)                    .await;                this.update(cx, |this, cx| this.serialize_exception_breakpoints(cx))?                    .await?;                Ok(())

Lines 1–500 of 1517.