debugger: Add keyboard navigation for breakpoint list (#31221)
Release Notes: - Debugger Beta: made it possible to navigate the breakpoint list using menu keybindings.
This commit is contained in:
parent
4acb4730a5
commit
ee415de45f
6 changed files with 385 additions and 171 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4248,6 +4248,7 @@ dependencies = [
|
||||||
"util",
|
"util",
|
||||||
"workspace",
|
"workspace",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
|
"zed_actions",
|
||||||
"zlog",
|
"zlog",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -872,6 +872,13 @@
|
||||||
"ctrl-i": "debugger::ToggleSessionPicker"
|
"ctrl-i": "debugger::ToggleSessionPicker"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "BreakpointList",
|
||||||
|
"bindings": {
|
||||||
|
"space": "debugger::ToggleEnableBreakpoint",
|
||||||
|
"backspace": "debugger::UnsetBreakpoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "CollabPanel && not_editing",
|
"context": "CollabPanel && not_editing",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|
|
@ -932,6 +932,13 @@
|
||||||
"cmd-i": "debugger::ToggleSessionPicker"
|
"cmd-i": "debugger::ToggleSessionPicker"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "BreakpointList",
|
||||||
|
"bindings": {
|
||||||
|
"space": "debugger::ToggleEnableBreakpoint",
|
||||||
|
"backspace": "debugger::UnsetBreakpoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "CollabPanel && not_editing",
|
"context": "CollabPanel && not_editing",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
|
|
|
@ -63,6 +63,7 @@ workspace.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
debugger_tools = { workspace = true, optional = true }
|
debugger_tools = { workspace = true, optional = true }
|
||||||
unindent = { workspace = true, optional = true }
|
unindent = { workspace = true, optional = true }
|
||||||
|
zed_actions.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dap = { workspace = true, features = ["test-support"] }
|
dap = { workspace = true, features = ["test-support"] }
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use dap::ExceptionBreakpointsFilter;
|
use dap::ExceptionBreakpointsFilter;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, Entity, FocusHandle, Focusable, ListState, MouseButton, Stateful, Task, WeakEntity,
|
AppContext, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, Stateful, Task,
|
||||||
list,
|
UniformListScrollHandle, WeakEntity, uniform_list,
|
||||||
};
|
};
|
||||||
use language::Point;
|
use language::Point;
|
||||||
use project::{
|
use project::{
|
||||||
|
@ -19,25 +20,27 @@ use project::{
|
||||||
worktree_store::WorktreeStore,
|
worktree_store::WorktreeStore,
|
||||||
};
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
App, Clickable, Color, Context, Div, Icon, IconButton, IconName, Indicator, InteractiveElement,
|
App, ButtonCommon, Clickable, Color, Context, Div, FluentBuilder as _, Icon, IconButton,
|
||||||
IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement, Render, RenderOnce,
|
IconName, Indicator, InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ListItem,
|
||||||
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Tooltip, Window,
|
ParentElement, Render, Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement,
|
||||||
div, h_flex, px, v_flex,
|
Styled, Toggleable, Tooltip, Window, div, h_flex, px, v_flex,
|
||||||
};
|
};
|
||||||
use util::{ResultExt, maybe};
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
|
||||||
|
|
||||||
pub(crate) struct BreakpointList {
|
pub(crate) struct BreakpointList {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
worktree_store: Entity<WorktreeStore>,
|
worktree_store: Entity<WorktreeStore>,
|
||||||
list_state: ListState,
|
|
||||||
scrollbar_state: ScrollbarState,
|
scrollbar_state: ScrollbarState,
|
||||||
breakpoints: Vec<BreakpointEntry>,
|
breakpoints: Vec<BreakpointEntry>,
|
||||||
session: Entity<Session>,
|
session: Entity<Session>,
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
hide_scrollbar_task: Option<Task<()>>,
|
||||||
show_scrollbar: bool,
|
show_scrollbar: bool,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
|
scroll_handle: UniformListScrollHandle,
|
||||||
|
selected_ix: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for BreakpointList {
|
impl Focusable for BreakpointList {
|
||||||
|
@ -56,38 +59,205 @@ impl BreakpointList {
|
||||||
let project = project.read(cx);
|
let project = project.read(cx);
|
||||||
let breakpoint_store = project.breakpoint_store();
|
let breakpoint_store = project.breakpoint_store();
|
||||||
let worktree_store = project.worktree_store();
|
let worktree_store = project.worktree_store();
|
||||||
|
let focus_handle = cx.focus_handle();
|
||||||
|
let scroll_handle = UniformListScrollHandle::new();
|
||||||
|
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
|
||||||
|
|
||||||
cx.new(|cx| {
|
cx.new(|_| {
|
||||||
let weak: gpui::WeakEntity<Self> = cx.weak_entity();
|
|
||||||
let list_state = ListState::new(
|
|
||||||
0,
|
|
||||||
gpui::ListAlignment::Top,
|
|
||||||
px(1000.),
|
|
||||||
move |ix, window, cx| {
|
|
||||||
let Ok(Some(breakpoint)) =
|
|
||||||
weak.update(cx, |this, _| this.breakpoints.get(ix).cloned())
|
|
||||||
else {
|
|
||||||
return div().into_any_element();
|
|
||||||
};
|
|
||||||
|
|
||||||
breakpoint.render(window, cx).into_any_element()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Self {
|
Self {
|
||||||
breakpoint_store,
|
breakpoint_store,
|
||||||
worktree_store,
|
worktree_store,
|
||||||
scrollbar_state: ScrollbarState::new(list_state.clone()),
|
scrollbar_state,
|
||||||
list_state,
|
// list_state,
|
||||||
breakpoints: Default::default(),
|
breakpoints: Default::default(),
|
||||||
hide_scrollbar_task: None,
|
hide_scrollbar_task: None,
|
||||||
show_scrollbar: false,
|
show_scrollbar: false,
|
||||||
workspace,
|
workspace,
|
||||||
session,
|
session,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle,
|
||||||
|
scroll_handle,
|
||||||
|
selected_ix: None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn edit_line_breakpoint(
|
||||||
|
&mut self,
|
||||||
|
path: Arc<Path>,
|
||||||
|
row: u32,
|
||||||
|
action: BreakpointEditAction,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.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.update(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();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_ix(&mut self, ix: Option<usize>, cx: &mut Context<Self>) {
|
||||||
|
self.selected_ix = ix;
|
||||||
|
if let Some(ix) = ix {
|
||||||
|
self.scroll_handle
|
||||||
|
.scroll_to_item(ix, ScrollStrategy::Center);
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let ix = match self.selected_ix {
|
||||||
|
_ if self.breakpoints.len() == 0 => None,
|
||||||
|
None => Some(0),
|
||||||
|
Some(ix) => {
|
||||||
|
if ix == self.breakpoints.len() - 1 {
|
||||||
|
Some(0)
|
||||||
|
} else {
|
||||||
|
Some(ix + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.select_ix(ix, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_previous(
|
||||||
|
&mut self,
|
||||||
|
_: &menu::SelectPrevious,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let ix = match self.selected_ix {
|
||||||
|
_ if self.breakpoints.len() == 0 => 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, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_first(
|
||||||
|
&mut self,
|
||||||
|
_: &menu::SelectFirst,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let ix = if self.breakpoints.len() > 0 {
|
||||||
|
Some(0)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.select_ix(ix, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let ix = if self.breakpoints.len() > 0 {
|
||||||
|
Some(self.breakpoints.len() - 1)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.select_ix(ix, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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::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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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.session.update(cx, |session, cx| {
|
||||||
|
session.toggle_exception_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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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::Toggle, cx);
|
||||||
|
}
|
||||||
|
BreakpointEntryKind::ExceptionBreakpoint(_) => {}
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
||||||
self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| {
|
self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| {
|
||||||
|
@ -103,6 +273,30 @@ impl BreakpointList {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let selected_ix = self.selected_ix;
|
||||||
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
uniform_list(
|
||||||
|
cx.entity(),
|
||||||
|
"breakpoint-list",
|
||||||
|
self.breakpoints.len(),
|
||||||
|
move |this, range, window, cx| {
|
||||||
|
range
|
||||||
|
.clone()
|
||||||
|
.zip(&mut this.breakpoints[range])
|
||||||
|
.map(|(ix, breakpoint)| {
|
||||||
|
breakpoint
|
||||||
|
.render(ix, focus_handle.clone(), window, cx)
|
||||||
|
.toggle_state(Some(ix) == selected_ix)
|
||||||
|
.into_any_element()
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.track_scroll(self.scroll_handle.clone())
|
||||||
|
.flex_grow()
|
||||||
|
}
|
||||||
|
|
||||||
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
|
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
|
||||||
if !(self.show_scrollbar || self.scrollbar_state.is_dragging()) {
|
if !(self.show_scrollbar || self.scrollbar_state.is_dragging()) {
|
||||||
return None;
|
return None;
|
||||||
|
@ -142,12 +336,8 @@ impl BreakpointList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Render for BreakpointList {
|
impl Render for BreakpointList {
|
||||||
fn render(
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
|
||||||
&mut self,
|
// let old_len = self.breakpoints.len();
|
||||||
_window: &mut ui::Window,
|
|
||||||
cx: &mut ui::Context<Self>,
|
|
||||||
) -> impl ui::IntoElement {
|
|
||||||
let old_len = self.breakpoints.len();
|
|
||||||
let breakpoints = self.breakpoint_store.read(cx).all_source_breakpoints(cx);
|
let breakpoints = self.breakpoint_store.read(cx).all_source_breakpoints(cx);
|
||||||
self.breakpoints.clear();
|
self.breakpoints.clear();
|
||||||
let weak = cx.weak_entity();
|
let weak = cx.weak_entity();
|
||||||
|
@ -183,7 +373,7 @@ impl Render for BreakpointList {
|
||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
.map(SharedString::from)?;
|
.map(SharedString::from)?;
|
||||||
let weak = weak.clone();
|
let weak = weak.clone();
|
||||||
let line = format!("Line {}", breakpoint.row + 1).into();
|
let line = breakpoint.row + 1;
|
||||||
Some(BreakpointEntry {
|
Some(BreakpointEntry {
|
||||||
kind: BreakpointEntryKind::LineBreakpoint(LineBreakpoint {
|
kind: BreakpointEntryKind::LineBreakpoint(LineBreakpoint {
|
||||||
name,
|
name,
|
||||||
|
@ -209,11 +399,9 @@ impl Render for BreakpointList {
|
||||||
});
|
});
|
||||||
self.breakpoints
|
self.breakpoints
|
||||||
.extend(breakpoints.chain(exception_breakpoints));
|
.extend(breakpoints.chain(exception_breakpoints));
|
||||||
if self.breakpoints.len() != old_len {
|
|
||||||
self.list_state.reset(self.breakpoints.len());
|
|
||||||
}
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("breakpoint-list")
|
.id("breakpoint-list")
|
||||||
|
.key_context("BreakpointList")
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.on_hover(cx.listener(|this, hovered, window, cx| {
|
.on_hover(cx.listener(|this, hovered, window, cx| {
|
||||||
if *hovered {
|
if *hovered {
|
||||||
|
@ -224,9 +412,16 @@ impl Render for BreakpointList {
|
||||||
this.hide_scrollbar(window, cx);
|
this.hide_scrollbar(window, cx);
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
.on_action(cx.listener(Self::select_next))
|
||||||
|
.on_action(cx.listener(Self::select_previous))
|
||||||
|
.on_action(cx.listener(Self::select_first))
|
||||||
|
.on_action(cx.listener(Self::select_last))
|
||||||
|
.on_action(cx.listener(Self::confirm))
|
||||||
|
.on_action(cx.listener(Self::toggle_enable_breakpoint))
|
||||||
|
.on_action(cx.listener(Self::unset_breakpoint))
|
||||||
.size_full()
|
.size_full()
|
||||||
.m_0p5()
|
.m_0p5()
|
||||||
.child(list(self.list_state.clone()).flex_grow())
|
.child(self.render_list(window, cx))
|
||||||
.children(self.render_vertical_scrollbar(cx))
|
.children(self.render_vertical_scrollbar(cx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,55 +429,58 @@ impl Render for BreakpointList {
|
||||||
struct LineBreakpoint {
|
struct LineBreakpoint {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
dir: Option<SharedString>,
|
dir: Option<SharedString>,
|
||||||
line: SharedString,
|
line: u32,
|
||||||
breakpoint: SourceBreakpoint,
|
breakpoint: SourceBreakpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LineBreakpoint {
|
impl LineBreakpoint {
|
||||||
fn render(self, weak: WeakEntity<BreakpointList>) -> ListItem {
|
fn render(
|
||||||
let LineBreakpoint {
|
&mut self,
|
||||||
name,
|
ix: usize,
|
||||||
dir,
|
focus_handle: FocusHandle,
|
||||||
line,
|
weak: WeakEntity<BreakpointList>,
|
||||||
breakpoint,
|
) -> ListItem {
|
||||||
} = self;
|
let icon_name = if self.breakpoint.state.is_enabled() {
|
||||||
let icon_name = if breakpoint.state.is_enabled() {
|
|
||||||
IconName::DebugBreakpoint
|
IconName::DebugBreakpoint
|
||||||
} else {
|
} else {
|
||||||
IconName::DebugDisabledBreakpoint
|
IconName::DebugDisabledBreakpoint
|
||||||
};
|
};
|
||||||
let path = breakpoint.path;
|
let path = self.breakpoint.path.clone();
|
||||||
let row = breakpoint.row;
|
let row = self.breakpoint.row;
|
||||||
|
let is_enabled = self.breakpoint.state.is_enabled();
|
||||||
let indicator = div()
|
let indicator = div()
|
||||||
.id(SharedString::from(format!(
|
.id(SharedString::from(format!(
|
||||||
"breakpoint-ui-toggle-{:?}/{}:{}",
|
"breakpoint-ui-toggle-{:?}/{}:{}",
|
||||||
dir, name, line
|
self.dir, self.name, self.line
|
||||||
)))
|
)))
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.tooltip(Tooltip::text(if breakpoint.state.is_enabled() {
|
.tooltip({
|
||||||
"Disable Breakpoint"
|
let focus_handle = focus_handle.clone();
|
||||||
} else {
|
move |window, cx| {
|
||||||
"Enable Breakpoint"
|
Tooltip::for_action_in(
|
||||||
}))
|
if is_enabled {
|
||||||
|
"Disable Breakpoint"
|
||||||
|
} else {
|
||||||
|
"Enable Breakpoint"
|
||||||
|
},
|
||||||
|
&ToggleEnableBreakpoint,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
.on_click({
|
.on_click({
|
||||||
let weak = weak.clone();
|
let weak = weak.clone();
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
move |_, _, cx| {
|
move |_, _, cx| {
|
||||||
weak.update(cx, |this, cx| {
|
weak.update(cx, |breakpoint_list, cx| {
|
||||||
this.breakpoint_store.update(cx, |this, cx| {
|
breakpoint_list.edit_line_breakpoint(
|
||||||
if let Some((buffer, breakpoint)) =
|
path.clone(),
|
||||||
this.breakpoint_at_row(&path, row, cx)
|
row,
|
||||||
{
|
BreakpointEditAction::InvertState,
|
||||||
this.toggle_breakpoint(
|
cx,
|
||||||
buffer,
|
);
|
||||||
breakpoint,
|
|
||||||
BreakpointEditAction::InvertState,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log::error!("Couldn't find breakpoint at row event though it exists: row {row}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
@ -291,8 +489,17 @@ impl LineBreakpoint {
|
||||||
.on_mouse_down(MouseButton::Left, move |_, _, _| {});
|
.on_mouse_down(MouseButton::Left, move |_, _, _| {});
|
||||||
ListItem::new(SharedString::from(format!(
|
ListItem::new(SharedString::from(format!(
|
||||||
"breakpoint-ui-item-{:?}/{}:{}",
|
"breakpoint-ui-item-{:?}/{}:{}",
|
||||||
dir, name, line
|
self.dir, self.name, self.line
|
||||||
)))
|
)))
|
||||||
|
.on_click({
|
||||||
|
let weak = weak.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
weak.update(cx, |breakpoint_list, cx| {
|
||||||
|
breakpoint_list.select_ix(Some(ix), cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
.start_slot(indicator)
|
.start_slot(indicator)
|
||||||
.rounded()
|
.rounded()
|
||||||
.on_secondary_mouse_down(|_, _, cx| {
|
.on_secondary_mouse_down(|_, _, cx| {
|
||||||
|
@ -302,7 +509,7 @@ impl LineBreakpoint {
|
||||||
IconButton::new(
|
IconButton::new(
|
||||||
SharedString::from(format!(
|
SharedString::from(format!(
|
||||||
"breakpoint-ui-on-click-go-to-line-remove-{:?}/{}:{}",
|
"breakpoint-ui-on-click-go-to-line-remove-{:?}/{}:{}",
|
||||||
dir, name, line
|
self.dir, self.name, self.line
|
||||||
)),
|
)),
|
||||||
IconName::Close,
|
IconName::Close,
|
||||||
)
|
)
|
||||||
|
@ -310,103 +517,60 @@ impl LineBreakpoint {
|
||||||
let weak = weak.clone();
|
let weak = weak.clone();
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
move |_, _, cx| {
|
move |_, _, cx| {
|
||||||
weak.update(cx, |this, cx| {
|
weak.update(cx, |breakpoint_list, cx| {
|
||||||
this.breakpoint_store.update(cx, |this, cx| {
|
breakpoint_list.edit_line_breakpoint(
|
||||||
if let Some((buffer, breakpoint)) =
|
path.clone(),
|
||||||
this.breakpoint_at_row(&path, row, cx)
|
row,
|
||||||
{
|
BreakpointEditAction::Toggle,
|
||||||
this.toggle_breakpoint(
|
cx,
|
||||||
buffer,
|
);
|
||||||
breakpoint,
|
|
||||||
BreakpointEditAction::Toggle,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log::error!("Couldn't find breakpoint at row event though it exists: row {row}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.icon_size(ui::IconSize::XSmall),
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::for_action_in(
|
||||||
|
"Unset Breakpoint",
|
||||||
|
&UnsetBreakpoint,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.icon_size(ui::IconSize::Indicator),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.py_1()
|
||||||
|
.gap_1()
|
||||||
|
.min_h(px(22.))
|
||||||
|
.justify_center()
|
||||||
.id(SharedString::from(format!(
|
.id(SharedString::from(format!(
|
||||||
"breakpoint-ui-on-click-go-to-line-{:?}/{}:{}",
|
"breakpoint-ui-on-click-go-to-line-{:?}/{}:{}",
|
||||||
dir, name, line
|
self.dir, self.name, self.line
|
||||||
)))
|
)))
|
||||||
.on_click(move |_, window, cx| {
|
.on_click(move |_, window, cx| {
|
||||||
let path = path.clone();
|
weak.update(cx, |breakpoint_list, cx| {
|
||||||
let weak = weak.clone();
|
breakpoint_list.select_ix(Some(ix), cx);
|
||||||
let row = breakpoint.row;
|
breakpoint_list.go_to_line_breakpoint(path.clone(), row, window, cx);
|
||||||
maybe!({
|
})
|
||||||
let task = weak
|
.ok();
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.worktree_store.update(cx, |this, cx| {
|
|
||||||
this.find_or_create_worktree(path, false, cx)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.ok()?;
|
|
||||||
window
|
|
||||||
.spawn(cx, async move |cx| {
|
|
||||||
let (worktree, relative_path) = task.await?;
|
|
||||||
let worktree_id = worktree.update(cx, |this, _| this.id())?;
|
|
||||||
let item = weak
|
|
||||||
.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();
|
|
||||||
|
|
||||||
Some(())
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.py_1()
|
|
||||||
.items_center()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
Label::new(name)
|
Label::new(format!("{}:{}", self.name, self.line))
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
.line_height_style(ui::LineHeightStyle::UiLabel),
|
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||||
)
|
)
|
||||||
.children(dir.map(|dir| {
|
.children(self.dir.clone().map(|dir| {
|
||||||
Label::new(dir)
|
Label::new(dir)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
.line_height_style(ui::LineHeightStyle::UiLabel)
|
.line_height_style(ui::LineHeightStyle::UiLabel)
|
||||||
})),
|
})),
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Label::new(line)
|
|
||||||
.size(LabelSize::XSmall)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.line_height_style(ui::LineHeightStyle::UiLabel),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -419,17 +583,31 @@ struct ExceptionBreakpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExceptionBreakpoint {
|
impl ExceptionBreakpoint {
|
||||||
fn render(self, list: WeakEntity<BreakpointList>) -> ListItem {
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
ix: usize,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
list: WeakEntity<BreakpointList>,
|
||||||
|
) -> ListItem {
|
||||||
let color = if self.is_enabled {
|
let color = if self.is_enabled {
|
||||||
Color::Debugger
|
Color::Debugger
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
};
|
};
|
||||||
let id = SharedString::from(&self.id);
|
let id = SharedString::from(&self.id);
|
||||||
|
let is_enabled = self.is_enabled;
|
||||||
|
|
||||||
ListItem::new(SharedString::from(format!(
|
ListItem::new(SharedString::from(format!(
|
||||||
"exception-breakpoint-ui-item-{}",
|
"exception-breakpoint-ui-item-{}",
|
||||||
self.id
|
self.id
|
||||||
)))
|
)))
|
||||||
|
.on_click({
|
||||||
|
let list = list.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
list.update(cx, |list, cx| list.select_ix(Some(ix), cx))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
.rounded()
|
.rounded()
|
||||||
.on_secondary_mouse_down(|_, _, cx| {
|
.on_secondary_mouse_down(|_, _, cx| {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
|
@ -440,38 +618,49 @@ impl ExceptionBreakpoint {
|
||||||
"exception-breakpoint-ui-item-{}-click-handler",
|
"exception-breakpoint-ui-item-{}-click-handler",
|
||||||
self.id
|
self.id
|
||||||
)))
|
)))
|
||||||
.tooltip(Tooltip::text(if self.is_enabled {
|
.tooltip(move |window, cx| {
|
||||||
"Disable Exception Breakpoint"
|
Tooltip::for_action_in(
|
||||||
} else {
|
if is_enabled {
|
||||||
"Enable Exception Breakpoint"
|
"Disable Exception Breakpoint"
|
||||||
}))
|
} else {
|
||||||
.on_click(move |_, _, cx| {
|
"Enable Exception Breakpoint"
|
||||||
list.update(cx, |this, cx| {
|
},
|
||||||
this.session.update(cx, |this, cx| {
|
&ToggleEnableBreakpoint,
|
||||||
this.toggle_exception_breakpoint(&id, cx);
|
&focus_handle,
|
||||||
});
|
window,
|
||||||
cx.notify();
|
cx,
|
||||||
})
|
)
|
||||||
.ok();
|
})
|
||||||
|
.on_click({
|
||||||
|
let list = list.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
list.update(cx, |this, cx| {
|
||||||
|
this.session.update(cx, |this, cx| {
|
||||||
|
this.toggle_exception_breakpoint(&id, cx);
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.child(Indicator::icon(Icon::new(IconName::Flame)).color(color)),
|
.child(Indicator::icon(Icon::new(IconName::Flame)).color(color)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
v_flex()
|
||||||
.py_1()
|
.py_1()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
.min_h(px(22.))
|
||||||
|
.justify_center()
|
||||||
|
.id(("exception-breakpoint-label", ix))
|
||||||
.child(
|
.child(
|
||||||
Label::new(self.data.label)
|
Label::new(self.data.label.clone())
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
.line_height_style(ui::LineHeightStyle::UiLabel),
|
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||||
)
|
)
|
||||||
.children(self.data.description.map(|description| {
|
.when_some(self.data.description.clone(), |el, description| {
|
||||||
Label::new(description)
|
el.tooltip(Tooltip::text(description))
|
||||||
.size(LabelSize::XSmall)
|
}),
|
||||||
.line_height_style(ui::LineHeightStyle::UiLabel)
|
|
||||||
.color(Color::Muted)
|
|
||||||
})),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -486,14 +675,21 @@ struct BreakpointEntry {
|
||||||
kind: BreakpointEntryKind,
|
kind: BreakpointEntryKind,
|
||||||
weak: WeakEntity<BreakpointList>,
|
weak: WeakEntity<BreakpointList>,
|
||||||
}
|
}
|
||||||
impl RenderOnce for BreakpointEntry {
|
|
||||||
fn render(self, _: &mut ui::Window, _: &mut App) -> impl ui::IntoElement {
|
impl BreakpointEntry {
|
||||||
match self.kind {
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
ix: usize,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
_: &mut Window,
|
||||||
|
_: &mut App,
|
||||||
|
) -> ListItem {
|
||||||
|
match &mut self.kind {
|
||||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||||
line_breakpoint.render(self.weak)
|
line_breakpoint.render(ix, focus_handle, self.weak.clone())
|
||||||
}
|
}
|
||||||
BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => {
|
BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => {
|
||||||
exception_breakpoint.render(self.weak)
|
exception_breakpoint.render(ix, focus_handle, self.weak.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,3 +339,5 @@ pub mod outline {
|
||||||
|
|
||||||
actions!(zed_predict_onboarding, [OpenZedPredictOnboarding]);
|
actions!(zed_predict_onboarding, [OpenZedPredictOnboarding]);
|
||||||
actions!(git_onboarding, [OpenGitIntegrationOnboarding]);
|
actions!(git_onboarding, [OpenGitIntegrationOnboarding]);
|
||||||
|
|
||||||
|
actions!(debugger, [ToggleEnableBreakpoint, UnsetBreakpoint]);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue