debugger: Add breakpoint list (#28496)
 Release Notes: - N/A --------- Co-authored-by: Anthony Eid <hello@anthonyeid.me>
This commit is contained in:
parent
3abf95216c
commit
26f4705198
13 changed files with 711 additions and 18 deletions
1
assets/icons/binary.svg
Normal file
1
assets/icons/binary.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-binary-icon lucide-binary"><rect x="14" y="14" width="4" height="6" rx="2"/><rect x="6" y="4" width="4" height="6" rx="2"/><path d="M6 20h4"/><path d="M14 10h4"/><path d="M6 14h2v6"/><path d="M14 4h2v6"/></svg>
|
After Width: | Height: | Size: 413 B |
1
assets/icons/flame.svg
Normal file
1
assets/icons/flame.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-flame-icon lucide-flame"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg>
|
After Width: | Height: | Size: 415 B |
1
assets/icons/function.svg
Normal file
1
assets/icons/function.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-function-icon lucide-square-function"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3"/><path d="M9 11.2h5.7"/></svg>
|
After Width: | Height: | Size: 387 B |
|
@ -105,6 +105,12 @@ impl DebugAdapter for CodeLldbDebugAdapter {
|
||||||
Ok(DebugAdapterBinary {
|
Ok(DebugAdapterBinary {
|
||||||
command,
|
command,
|
||||||
cwd: Some(adapter_dir),
|
cwd: Some(adapter_dir),
|
||||||
|
arguments: Some(vec![
|
||||||
|
"--settings".into(),
|
||||||
|
json!({"sourceLanguages": ["cpp", "rust"]})
|
||||||
|
.to_string()
|
||||||
|
.into(),
|
||||||
|
]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -117,6 +123,8 @@ impl DebugAdapter for CodeLldbDebugAdapter {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let map = args.as_object_mut().unwrap();
|
let map = args.as_object_mut().unwrap();
|
||||||
|
// CodeLLDB uses `name` for a terminal label.
|
||||||
|
map.insert("name".into(), Value::String(config.label.clone()));
|
||||||
match &config.request {
|
match &config.request {
|
||||||
DebugRequestType::Attach(attach) => {
|
DebugRequestType::Attach(attach) => {
|
||||||
map.insert("pid".into(), attach.process_id.into());
|
map.insert("pid".into(), attach.process_id.into());
|
||||||
|
|
|
@ -417,7 +417,8 @@ impl DebugPanel {
|
||||||
DropdownMenu::new_with_element(
|
DropdownMenu::new_with_element(
|
||||||
"debugger-session-list",
|
"debugger-session-list",
|
||||||
label,
|
label,
|
||||||
ContextMenu::build(window, cx, move |mut this, _, _| {
|
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
||||||
|
let context_menu = cx.weak_entity();
|
||||||
for session in sessions.into_iter() {
|
for session in sessions.into_iter() {
|
||||||
let weak_session = session.downgrade();
|
let weak_session = session.downgrade();
|
||||||
let weak_session_id = weak_session.entity_id();
|
let weak_session_id = weak_session.entity_id();
|
||||||
|
@ -425,11 +426,17 @@ impl DebugPanel {
|
||||||
this = this.custom_entry(
|
this = this.custom_entry(
|
||||||
{
|
{
|
||||||
let weak = weak.clone();
|
let weak = weak.clone();
|
||||||
|
let context_menu = context_menu.clone();
|
||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
weak_session
|
weak_session
|
||||||
.read_with(cx, |session, cx| {
|
.read_with(cx, |session, cx| {
|
||||||
|
let context_menu = context_menu.clone();
|
||||||
|
let id: SharedString =
|
||||||
|
format!("debug-session-{}", session.session_id(cx).0)
|
||||||
|
.into();
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
.group(id.clone())
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(session.label_element(cx))
|
.child(session.label_element(cx))
|
||||||
.child(
|
.child(
|
||||||
|
@ -437,15 +444,25 @@ impl DebugPanel {
|
||||||
"close-debug-session",
|
"close-debug-session",
|
||||||
IconName::Close,
|
IconName::Close,
|
||||||
)
|
)
|
||||||
|
.visible_on_hover(id.clone())
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.on_click({
|
.on_click({
|
||||||
let weak = weak.clone();
|
let weak = weak.clone();
|
||||||
move |_, _, cx| {
|
move |_, window, cx| {
|
||||||
weak.update(cx, |panel, cx| {
|
weak.update(cx, |panel, cx| {
|
||||||
panel
|
panel
|
||||||
.close_session(weak_session_id, cx);
|
.close_session(weak_session_id, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
context_menu
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.cancel(
|
||||||
|
&Default::default(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod breakpoint_list;
|
||||||
mod console;
|
mod console;
|
||||||
mod loaded_source_list;
|
mod loaded_source_list;
|
||||||
mod module_list;
|
mod module_list;
|
||||||
|
@ -7,6 +8,7 @@ pub mod variable_list;
|
||||||
use std::{any::Any, ops::ControlFlow, sync::Arc};
|
use std::{any::Any, ops::ControlFlow, sync::Arc};
|
||||||
|
|
||||||
use super::DebugPanelItemEvent;
|
use super::DebugPanelItemEvent;
|
||||||
|
use breakpoint_list::BreakpointList;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use console::Console;
|
use console::Console;
|
||||||
use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
|
use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
|
||||||
|
@ -321,6 +323,21 @@ impl RunningState {
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
let breakpoints = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
|
||||||
|
this.add_item(
|
||||||
|
Box::new(SubView::new(
|
||||||
|
breakpoints.focus_handle(cx),
|
||||||
|
breakpoints.into(),
|
||||||
|
SharedString::new_static("Breakpoints"),
|
||||||
|
cx,
|
||||||
|
)),
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
this.activate_item(0, false, false, window, cx);
|
||||||
});
|
});
|
||||||
let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||||
center_pane.update(cx, |this, cx| {
|
center_pane.update(cx, |this, cx| {
|
||||||
|
|
482
crates/debugger_ui/src/session/running/breakpoint_list.rs
Normal file
482
crates/debugger_ui/src/session/running/breakpoint_list.rs
Normal file
|
@ -0,0 +1,482 @@
|
||||||
|
use std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use dap::ExceptionBreakpointsFilter;
|
||||||
|
use editor::Editor;
|
||||||
|
use gpui::{
|
||||||
|
AppContext, Entity, FocusHandle, Focusable, ListState, MouseButton, Stateful, Task, WeakEntity,
|
||||||
|
list,
|
||||||
|
};
|
||||||
|
use language::Point;
|
||||||
|
use project::{
|
||||||
|
Project,
|
||||||
|
debugger::{
|
||||||
|
breakpoint_store::{BreakpointEditAction, BreakpointStore, SourceBreakpoint},
|
||||||
|
session::Session,
|
||||||
|
},
|
||||||
|
worktree_store::WorktreeStore,
|
||||||
|
};
|
||||||
|
use ui::{
|
||||||
|
App, Clickable, Color, Context, Div, Icon, IconButton, IconName, Indicator, InteractiveElement,
|
||||||
|
IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement, Render, RenderOnce,
|
||||||
|
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Window, div,
|
||||||
|
h_flex, px, v_flex,
|
||||||
|
};
|
||||||
|
use util::{ResultExt, maybe};
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
pub(super) struct BreakpointList {
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
|
worktree_store: Entity<WorktreeStore>,
|
||||||
|
list_state: ListState,
|
||||||
|
scrollbar_state: ScrollbarState,
|
||||||
|
breakpoints: Vec<BreakpointEntry>,
|
||||||
|
session: Entity<Session>,
|
||||||
|
hide_scrollbar_task: Option<Task<()>>,
|
||||||
|
show_scrollbar: bool,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Focusable for BreakpointList {
|
||||||
|
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl BreakpointList {
|
||||||
|
pub(super) fn new(
|
||||||
|
session: Entity<Session>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
project: &Entity<Project>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Entity<Self> {
|
||||||
|
let project = project.read(cx);
|
||||||
|
let breakpoint_store = project.breakpoint_store();
|
||||||
|
let worktree_store = project.worktree_store();
|
||||||
|
|
||||||
|
cx.new(|cx| {
|
||||||
|
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 {
|
||||||
|
breakpoint_store,
|
||||||
|
worktree_store,
|
||||||
|
scrollbar_state: ScrollbarState::new(list_state.clone()),
|
||||||
|
list_state,
|
||||||
|
breakpoints: Default::default(),
|
||||||
|
hide_scrollbar_task: None,
|
||||||
|
show_scrollbar: false,
|
||||||
|
workspace,
|
||||||
|
session,
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
||||||
|
self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| {
|
||||||
|
cx.background_executor()
|
||||||
|
.timer(SCROLLBAR_SHOW_INTERVAL)
|
||||||
|
.await;
|
||||||
|
panel
|
||||||
|
.update(cx, |panel, cx| {
|
||||||
|
panel.show_scrollbar = false;
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
|
||||||
|
if !(self.show_scrollbar || self.scrollbar_state.is_dragging()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(
|
||||||
|
div()
|
||||||
|
.occlude()
|
||||||
|
.id("breakpoint-list-vertical-scrollbar")
|
||||||
|
.on_mouse_move(cx.listener(|_, _, _, cx| {
|
||||||
|
cx.notify();
|
||||||
|
cx.stop_propagation()
|
||||||
|
}))
|
||||||
|
.on_hover(|_, _, cx| {
|
||||||
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
|
.on_any_mouse_down(|_, _, cx| {
|
||||||
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
|
.on_mouse_up(
|
||||||
|
MouseButton::Left,
|
||||||
|
cx.listener(|_, _, _, cx| {
|
||||||
|
cx.stop_propagation();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
|
||||||
|
cx.notify();
|
||||||
|
}))
|
||||||
|
.h_full()
|
||||||
|
.absolute()
|
||||||
|
.right_1()
|
||||||
|
.top_1()
|
||||||
|
.bottom_0()
|
||||||
|
.w(px(12.))
|
||||||
|
.cursor_default()
|
||||||
|
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Render for BreakpointList {
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
_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_breakpoints(cx);
|
||||||
|
self.breakpoints.clear();
|
||||||
|
let weak = cx.weak_entity();
|
||||||
|
let breakpoints = breakpoints.into_iter().flat_map(|(path, mut breakpoints)| {
|
||||||
|
let relative_worktree_path = self
|
||||||
|
.worktree_store
|
||||||
|
.read(cx)
|
||||||
|
.find_worktree(&path, cx)
|
||||||
|
.and_then(|(worktree, relative_path)| {
|
||||||
|
worktree
|
||||||
|
.read(cx)
|
||||||
|
.is_visible()
|
||||||
|
.then(|| Path::new(worktree.read(cx).root_name()).join(relative_path))
|
||||||
|
});
|
||||||
|
breakpoints.sort_by_key(|breakpoint| breakpoint.row);
|
||||||
|
let weak = weak.clone();
|
||||||
|
breakpoints.into_iter().filter_map(move |breakpoint| {
|
||||||
|
debug_assert_eq!(&path, &breakpoint.path);
|
||||||
|
let file_name = breakpoint.path.file_name()?;
|
||||||
|
|
||||||
|
let dir = relative_worktree_path
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| PathBuf::from(&*breakpoint.path))
|
||||||
|
.parent()
|
||||||
|
.and_then(|parent| {
|
||||||
|
parent
|
||||||
|
.to_str()
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.map(SharedString::from)
|
||||||
|
});
|
||||||
|
let name = file_name
|
||||||
|
.to_str()
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.map(SharedString::from)?;
|
||||||
|
let weak = weak.clone();
|
||||||
|
let line = format!("Line {}", breakpoint.row + 1).into();
|
||||||
|
Some(BreakpointEntry {
|
||||||
|
kind: BreakpointEntryKind::LineBreakpoint(LineBreakpoint {
|
||||||
|
name,
|
||||||
|
dir,
|
||||||
|
line,
|
||||||
|
breakpoint,
|
||||||
|
}),
|
||||||
|
weak,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let exception_breakpoints =
|
||||||
|
self.session
|
||||||
|
.read(cx)
|
||||||
|
.exception_breakpoints()
|
||||||
|
.map(|(data, is_enabled)| BreakpointEntry {
|
||||||
|
kind: BreakpointEntryKind::ExceptionBreakpoint(ExceptionBreakpoint {
|
||||||
|
id: data.filter.clone(),
|
||||||
|
data: data.clone(),
|
||||||
|
is_enabled: *is_enabled,
|
||||||
|
}),
|
||||||
|
weak: weak.clone(),
|
||||||
|
});
|
||||||
|
self.breakpoints
|
||||||
|
.extend(breakpoints.chain(exception_breakpoints));
|
||||||
|
if self.breakpoints.len() != old_len {
|
||||||
|
self.list_state.reset(self.breakpoints.len());
|
||||||
|
}
|
||||||
|
v_flex()
|
||||||
|
.id("breakpoint-list")
|
||||||
|
.on_hover(cx.listener(|this, hovered, window, cx| {
|
||||||
|
if *hovered {
|
||||||
|
this.show_scrollbar = true;
|
||||||
|
this.hide_scrollbar_task.take();
|
||||||
|
cx.notify();
|
||||||
|
} else if !this.focus_handle.contains_focused(window, cx) {
|
||||||
|
this.hide_scrollbar(window, cx);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.size_full()
|
||||||
|
.m_0p5()
|
||||||
|
.child(list(self.list_state.clone()).flex_grow())
|
||||||
|
.children(self.render_vertical_scrollbar(cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct LineBreakpoint {
|
||||||
|
name: SharedString,
|
||||||
|
dir: Option<SharedString>,
|
||||||
|
line: SharedString,
|
||||||
|
breakpoint: SourceBreakpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LineBreakpoint {
|
||||||
|
fn render(self, weak: WeakEntity<BreakpointList>) -> ListItem {
|
||||||
|
let LineBreakpoint {
|
||||||
|
name,
|
||||||
|
dir,
|
||||||
|
line,
|
||||||
|
breakpoint,
|
||||||
|
} = self;
|
||||||
|
let icon_name = if breakpoint.state.is_enabled() {
|
||||||
|
IconName::DebugBreakpoint
|
||||||
|
} else {
|
||||||
|
IconName::DebugDisabledBreakpoint
|
||||||
|
};
|
||||||
|
let path = breakpoint.path;
|
||||||
|
let row = breakpoint.row;
|
||||||
|
let indicator = div()
|
||||||
|
.id(SharedString::from(format!(
|
||||||
|
"breakpoint-ui-toggle-{:?}/{}:{}",
|
||||||
|
dir, name, line
|
||||||
|
)))
|
||||||
|
.cursor_pointer()
|
||||||
|
.on_click({
|
||||||
|
let weak = weak.clone();
|
||||||
|
let path = path.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
weak.update(cx, |this, cx| {
|
||||||
|
this.breakpoint_store.update(cx, |this, cx| {
|
||||||
|
if let Some((buffer, breakpoint)) =
|
||||||
|
this.breakpoint_at_row(&path, row, cx)
|
||||||
|
{
|
||||||
|
this.toggle_breakpoint(
|
||||||
|
buffer,
|
||||||
|
breakpoint,
|
||||||
|
BreakpointEditAction::InvertState,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log::error!("Couldn't find breakpoint at row event though it exists: row {row}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.child(Indicator::icon(Icon::new(icon_name)).color(Color::Debugger))
|
||||||
|
.on_mouse_down(MouseButton::Left, move |_, _, _| {});
|
||||||
|
ListItem::new(SharedString::from(format!(
|
||||||
|
"breakpoint-ui-item-{:?}/{}:{}",
|
||||||
|
dir, name, line
|
||||||
|
)))
|
||||||
|
.start_slot(indicator)
|
||||||
|
.rounded()
|
||||||
|
.end_hover_slot(
|
||||||
|
IconButton::new(
|
||||||
|
SharedString::from(format!(
|
||||||
|
"breakpoint-ui-on-click-go-to-line-{:?}/{}:{}",
|
||||||
|
dir, name, line
|
||||||
|
)),
|
||||||
|
IconName::Close,
|
||||||
|
)
|
||||||
|
.on_click({
|
||||||
|
let weak = weak.clone();
|
||||||
|
let path = path.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
weak.update(cx, |this, cx| {
|
||||||
|
this.breakpoint_store.update(cx, |this, cx| {
|
||||||
|
if let Some((buffer, breakpoint)) =
|
||||||
|
this.breakpoint_at_row(&path, row, cx)
|
||||||
|
{
|
||||||
|
this.toggle_breakpoint(
|
||||||
|
buffer,
|
||||||
|
breakpoint,
|
||||||
|
BreakpointEditAction::Toggle,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log::error!("Couldn't find breakpoint at row event though it exists: row {row}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.icon_size(ui::IconSize::XSmall),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.id(SharedString::from(format!(
|
||||||
|
"breakpoint-ui-on-click-go-to-line-{:?}/{}:{}",
|
||||||
|
dir, name, line
|
||||||
|
)))
|
||||||
|
.on_click(move |_, window, cx| {
|
||||||
|
let path = path.clone();
|
||||||
|
let weak = weak.clone();
|
||||||
|
let row = breakpoint.row;
|
||||||
|
maybe!({
|
||||||
|
let task = weak
|
||||||
|
.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();
|
||||||
|
}
|
||||||
|
Result::<_, anyhow::Error>::Ok(())
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.cursor_pointer()
|
||||||
|
.py_1()
|
||||||
|
.items_center()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Label::new(name)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||||
|
)
|
||||||
|
.children(dir.map(|dir| {
|
||||||
|
Label::new(dir)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.line_height_style(ui::LineHeightStyle::UiLabel)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(line)
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct ExceptionBreakpoint {
|
||||||
|
id: String,
|
||||||
|
data: ExceptionBreakpointsFilter,
|
||||||
|
is_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExceptionBreakpoint {
|
||||||
|
fn render(self, list: WeakEntity<BreakpointList>) -> ListItem {
|
||||||
|
let color = if self.is_enabled {
|
||||||
|
Color::Debugger
|
||||||
|
} else {
|
||||||
|
Color::Muted
|
||||||
|
};
|
||||||
|
let id = SharedString::from(&self.id);
|
||||||
|
ListItem::new(SharedString::from(format!(
|
||||||
|
"exception-breakpoint-ui-item-{}",
|
||||||
|
self.id
|
||||||
|
)))
|
||||||
|
.rounded()
|
||||||
|
.start_slot(
|
||||||
|
div()
|
||||||
|
.id(SharedString::from(format!(
|
||||||
|
"exception-breakpoint-ui-item-{}-click-handler",
|
||||||
|
self.id
|
||||||
|
)))
|
||||||
|
.on_click(move |_, _, cx| {
|
||||||
|
list.update(cx, |this, cx| {
|
||||||
|
this.session.update(cx, |this, cx| {
|
||||||
|
this.toggle_exception_breakpoint(&id, cx);
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.cursor_pointer()
|
||||||
|
.child(Indicator::icon(Icon::new(IconName::Flame)).color(color)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.py_1()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Label::new(self.data.label)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||||
|
)
|
||||||
|
.children(self.data.description.map(|description| {
|
||||||
|
Label::new(description)
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.line_height_style(ui::LineHeightStyle::UiLabel)
|
||||||
|
.color(Color::Muted)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum BreakpointEntryKind {
|
||||||
|
LineBreakpoint(LineBreakpoint),
|
||||||
|
ExceptionBreakpoint(ExceptionBreakpoint),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct BreakpointEntry {
|
||||||
|
kind: BreakpointEntryKind,
|
||||||
|
weak: WeakEntity<BreakpointList>,
|
||||||
|
}
|
||||||
|
impl RenderOnce for BreakpointEntry {
|
||||||
|
fn render(self, _: &mut ui::Window, _: &mut App) -> impl ui::IntoElement {
|
||||||
|
match self.kind {
|
||||||
|
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||||
|
line_breakpoint.render(self.weak)
|
||||||
|
}
|
||||||
|
BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => {
|
||||||
|
exception_breakpoint.render(self.weak)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ use project::{
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{cell::RefCell, rc::Rc, usize};
|
use std::{cell::RefCell, rc::Rc, usize};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::prelude::*;
|
use ui::{Divider, prelude::*};
|
||||||
|
|
||||||
pub struct Console {
|
pub struct Console {
|
||||||
console: Entity<Editor>,
|
console: Entity<Editor>,
|
||||||
|
@ -229,7 +229,8 @@ impl Render for Console {
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(self.render_console(cx))
|
.child(self.render_console(cx))
|
||||||
.when(self.is_local(cx), |this| {
|
.when(self.is_local(cx), |this| {
|
||||||
this.child(self.render_query_bar(cx))
|
this.child(Divider::horizontal())
|
||||||
|
.child(self.render_query_bar(cx))
|
||||||
.pt(DynamicSpacing::Base04.rems(cx))
|
.pt(DynamicSpacing::Base04.rems(cx))
|
||||||
})
|
})
|
||||||
.border_2()
|
.border_2()
|
||||||
|
|
|
@ -39,6 +39,7 @@ pub enum IconName {
|
||||||
BellDot,
|
BellDot,
|
||||||
BellOff,
|
BellOff,
|
||||||
BellRing,
|
BellRing,
|
||||||
|
Binary,
|
||||||
Blocks,
|
Blocks,
|
||||||
Bolt,
|
Bolt,
|
||||||
Book,
|
Book,
|
||||||
|
@ -119,6 +120,7 @@ pub enum IconName {
|
||||||
FileToml,
|
FileToml,
|
||||||
FileTree,
|
FileTree,
|
||||||
Filter,
|
Filter,
|
||||||
|
Flame,
|
||||||
Folder,
|
Folder,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
FolderX,
|
FolderX,
|
||||||
|
@ -126,6 +128,7 @@ pub enum IconName {
|
||||||
FontSize,
|
FontSize,
|
||||||
FontWeight,
|
FontWeight,
|
||||||
ForwardArrow,
|
ForwardArrow,
|
||||||
|
Function,
|
||||||
GenericClose,
|
GenericClose,
|
||||||
GenericMaximize,
|
GenericMaximize,
|
||||||
GenericMinimize,
|
GenericMinimize,
|
||||||
|
|
|
@ -12,7 +12,7 @@ use rpc::{
|
||||||
proto::{self},
|
proto::{self},
|
||||||
};
|
};
|
||||||
use std::{hash::Hash, ops::Range, path::Path, sync::Arc};
|
use std::{hash::Hash, ops::Range, path::Path, sync::Arc};
|
||||||
use text::PointUtf16;
|
use text::{Point, PointUtf16};
|
||||||
|
|
||||||
use crate::{Project, ProjectPath, buffer_store::BufferStore, worktree_store::WorktreeStore};
|
use crate::{Project, ProjectPath, buffer_store::BufferStore, worktree_store::WorktreeStore};
|
||||||
|
|
||||||
|
@ -464,6 +464,23 @@ impl BreakpointStore {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn breakpoint_at_row(
|
||||||
|
&self,
|
||||||
|
path: &Path,
|
||||||
|
row: u32,
|
||||||
|
cx: &App,
|
||||||
|
) -> Option<(Entity<Buffer>, (text::Anchor, Breakpoint))> {
|
||||||
|
self.breakpoints.get(path).and_then(|breakpoints| {
|
||||||
|
let snapshot = breakpoints.buffer.read(cx).text_snapshot();
|
||||||
|
|
||||||
|
breakpoints
|
||||||
|
.breakpoints
|
||||||
|
.iter()
|
||||||
|
.find(|(anchor, _)| anchor.summary::<Point>(&snapshot).row == row)
|
||||||
|
.map(|breakpoint| (breakpoints.buffer.clone(), breakpoint.clone()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn breakpoints_from_path(&self, path: &Arc<Path>, cx: &App) -> Vec<SourceBreakpoint> {
|
pub fn breakpoints_from_path(&self, path: &Arc<Path>, cx: &App) -> Vec<SourceBreakpoint> {
|
||||||
self.breakpoints
|
self.breakpoints
|
||||||
.get(path)
|
.get(path)
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Ok, Result, anyhow};
|
use anyhow::{Ok, Result, anyhow};
|
||||||
use dap::{
|
use dap::{
|
||||||
Capabilities, ContinueArguments, InitializeRequestArguments,
|
Capabilities, ContinueArguments, ExceptionFilterOptions, InitializeRequestArguments,
|
||||||
InitializeRequestArgumentsPathFormat, NextArguments, SetVariableResponse, SourceBreakpoint,
|
InitializeRequestArgumentsPathFormat, NextArguments, SetVariableResponse, SourceBreakpoint,
|
||||||
StepInArguments, StepOutArguments, SteppingGranularity, ValueFormat, Variable,
|
StepInArguments, StepOutArguments, SteppingGranularity, ValueFormat, Variable,
|
||||||
VariablesArgumentsFilter,
|
VariablesArgumentsFilter,
|
||||||
|
@ -1665,6 +1665,44 @@ impl LocalDapCommand for SetBreakpoints {
|
||||||
Ok(message.breakpoints)
|
Ok(message.breakpoints)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||||
|
pub(super) enum SetExceptionBreakpoints {
|
||||||
|
Plain {
|
||||||
|
filters: Vec<String>,
|
||||||
|
},
|
||||||
|
WithOptions {
|
||||||
|
filters: Vec<ExceptionFilterOptions>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalDapCommand for SetExceptionBreakpoints {
|
||||||
|
type Response = Vec<dap::Breakpoint>;
|
||||||
|
type DapRequest = dap::requests::SetExceptionBreakpoints;
|
||||||
|
|
||||||
|
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
|
||||||
|
match self {
|
||||||
|
SetExceptionBreakpoints::Plain { filters } => dap::SetExceptionBreakpointsArguments {
|
||||||
|
filters: filters.clone(),
|
||||||
|
exception_options: None,
|
||||||
|
filter_options: None,
|
||||||
|
},
|
||||||
|
SetExceptionBreakpoints::WithOptions { filters } => {
|
||||||
|
dap::SetExceptionBreakpointsArguments {
|
||||||
|
filters: vec![],
|
||||||
|
filter_options: Some(filters.clone()),
|
||||||
|
exception_options: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_from_dap(
|
||||||
|
&self,
|
||||||
|
message: <Self::DapRequest as dap::requests::Request>::Response,
|
||||||
|
) -> Result<Self::Response> {
|
||||||
|
Ok(message.breakpoints.unwrap_or_default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub(super) struct LocationsCommand {
|
pub(super) struct LocationsCommand {
|
||||||
|
|
|
@ -852,8 +852,7 @@ fn create_new_session(
|
||||||
cx.emit(DapStoreEvent::DebugClientStarted(session_id));
|
cx.emit(DapStoreEvent::DebugClientStarted(session_id));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
|
let seq_result = {
|
||||||
match {
|
|
||||||
session
|
session
|
||||||
.update(cx, |session, cx| session.request_initialize(cx))?
|
.update(cx, |session, cx| session.request_initialize(cx))?
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -863,7 +862,8 @@ fn create_new_session(
|
||||||
session.initialize_sequence(initialized_rx, cx)
|
session.initialize_sequence(initialized_rx, cx)
|
||||||
})?
|
})?
|
||||||
.await
|
.await
|
||||||
} {
|
};
|
||||||
|
match seq_result {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
|
|
@ -7,9 +7,9 @@ use super::dap_command::{
|
||||||
self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand,
|
self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand,
|
||||||
EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,
|
EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,
|
||||||
ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand,
|
ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand,
|
||||||
ScopesCommand, SetVariableValueCommand, StackTraceCommand, StepBackCommand, StepCommand,
|
ScopesCommand, SetExceptionBreakpoints, SetVariableValueCommand, StackTraceCommand,
|
||||||
StepInCommand, StepOutCommand, TerminateCommand, TerminateThreadsCommand, ThreadsCommand,
|
StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand,
|
||||||
VariablesCommand,
|
TerminateThreadsCommand, ThreadsCommand, VariablesCommand,
|
||||||
};
|
};
|
||||||
use super::dap_store::DapAdapterDelegate;
|
use super::dap_store::DapAdapterDelegate;
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
|
@ -23,7 +23,10 @@ use dap::{
|
||||||
client::{DebugAdapterClient, SessionId},
|
client::{DebugAdapterClient, SessionId},
|
||||||
messages::{Events, Message},
|
messages::{Events, Message},
|
||||||
};
|
};
|
||||||
use dap::{DapRegistry, DebugRequestType, OutputEventCategory};
|
use dap::{
|
||||||
|
DapRegistry, DebugRequestType, ExceptionBreakpointsFilter, ExceptionFilterOptions,
|
||||||
|
OutputEventCategory,
|
||||||
|
};
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use futures::{FutureExt, future::Shared};
|
use futures::{FutureExt, future::Shared};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -34,6 +37,7 @@ use serde_json::{Value, json};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::u64;
|
use std::u64;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -324,6 +328,13 @@ impl LocalMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session
|
||||||
|
.client
|
||||||
|
.on_request::<dap::requests::SetExceptionBreakpoints, _>(move |_, _| {
|
||||||
|
Ok(dap::SetExceptionBreakpointsResponse { breakpoints: None })
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
session
|
session
|
||||||
.client
|
.client
|
||||||
.on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(()))
|
.on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(()))
|
||||||
|
@ -456,7 +467,31 @@ impl LocalMode {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_all_breakpoints(&self, ignore_breakpoints: bool, cx: &App) -> Task<()> {
|
fn send_exception_breakpoints(
|
||||||
|
&self,
|
||||||
|
filters: Vec<ExceptionBreakpointsFilter>,
|
||||||
|
supports_filter_options: bool,
|
||||||
|
cx: &App,
|
||||||
|
) -> Task<Result<Vec<dap::Breakpoint>>> {
|
||||||
|
let arg = if supports_filter_options {
|
||||||
|
SetExceptionBreakpoints::WithOptions {
|
||||||
|
filters: filters
|
||||||
|
.into_iter()
|
||||||
|
.map(|filter| ExceptionFilterOptions {
|
||||||
|
filter_id: filter.filter,
|
||||||
|
condition: None,
|
||||||
|
mode: None,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SetExceptionBreakpoints::Plain {
|
||||||
|
filters: filters.into_iter().map(|filter| filter.filter).collect(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.request(arg, cx.background_executor().clone())
|
||||||
|
}
|
||||||
|
fn send_source_breakpoints(&self, ignore_breakpoints: bool, cx: &App) -> Task<()> {
|
||||||
let mut breakpoint_tasks = Vec::new();
|
let mut breakpoint_tasks = Vec::new();
|
||||||
let breakpoints = self
|
let breakpoints = self
|
||||||
.breakpoint_store
|
.breakpoint_store
|
||||||
|
@ -588,15 +623,37 @@ impl LocalMode {
|
||||||
};
|
};
|
||||||
|
|
||||||
let configuration_done_supported = ConfigurationDone::is_supported(capabilities);
|
let configuration_done_supported = ConfigurationDone::is_supported(capabilities);
|
||||||
|
let exception_filters = capabilities
|
||||||
|
.exception_breakpoint_filters
|
||||||
|
.as_ref()
|
||||||
|
.map(|exception_filters| {
|
||||||
|
exception_filters
|
||||||
|
.iter()
|
||||||
|
.filter(|filter| filter.default == Some(true))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let supports_exception_filters = capabilities
|
||||||
|
.supports_exception_filter_options
|
||||||
|
.unwrap_or_default();
|
||||||
let configuration_sequence = cx.spawn({
|
let configuration_sequence = cx.spawn({
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
async move |cx| {
|
async move |cx| {
|
||||||
initialized_rx.await?;
|
initialized_rx.await?;
|
||||||
// todo(debugger) figure out if we want to handle a breakpoint response error
|
// todo(debugger) figure out if we want to handle a breakpoint response error
|
||||||
// This will probably consist of letting a user know that breakpoints failed to be set
|
// This will probably consist of letting a user know that breakpoints failed to be set
|
||||||
cx.update(|cx| this.send_all_breakpoints(false, cx))?.await;
|
cx.update(|cx| this.send_source_breakpoints(false, cx))?
|
||||||
|
.await;
|
||||||
|
cx.update(|cx| {
|
||||||
|
this.send_exception_breakpoints(
|
||||||
|
exception_filters,
|
||||||
|
supports_exception_filters,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
if configuration_done_supported {
|
if configuration_done_supported {
|
||||||
this.request(ConfigurationDone {}, cx.background_executor().clone())
|
this.request(ConfigurationDone {}, cx.background_executor().clone())
|
||||||
} else {
|
} else {
|
||||||
|
@ -727,6 +784,8 @@ impl ThreadStates {
|
||||||
}
|
}
|
||||||
const MAX_TRACKED_OUTPUT_EVENTS: usize = 5000;
|
const MAX_TRACKED_OUTPUT_EVENTS: usize = 5000;
|
||||||
|
|
||||||
|
type IsEnabled = bool;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, PartialEq, PartialOrd, Eq, Ord)]
|
#[derive(Copy, Clone, Default, Debug, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
pub struct OutputToken(pub usize);
|
pub struct OutputToken(pub usize);
|
||||||
/// Represents a current state of a single debug adapter and provides ways to mutate it.
|
/// Represents a current state of a single debug adapter and provides ways to mutate it.
|
||||||
|
@ -748,6 +807,7 @@ pub struct Session {
|
||||||
locations: HashMap<u64, dap::LocationsResponse>,
|
locations: HashMap<u64, dap::LocationsResponse>,
|
||||||
is_session_terminated: bool,
|
is_session_terminated: bool,
|
||||||
requests: HashMap<TypeId, HashMap<RequestSlot, Shared<Task<Option<()>>>>>,
|
requests: HashMap<TypeId, HashMap<RequestSlot, Shared<Task<Option<()>>>>>,
|
||||||
|
exception_breakpoints: BTreeMap<String, (ExceptionBreakpointsFilter, IsEnabled)>,
|
||||||
_background_tasks: Vec<Task<()>>,
|
_background_tasks: Vec<Task<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -956,6 +1016,7 @@ impl Session {
|
||||||
_background_tasks: Vec::default(),
|
_background_tasks: Vec::default(),
|
||||||
locations: Default::default(),
|
locations: Default::default(),
|
||||||
is_session_terminated: false,
|
is_session_terminated: false,
|
||||||
|
exception_breakpoints: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1022,6 +1083,18 @@ impl Session {
|
||||||
let capabilities = capabilities.await?;
|
let capabilities = capabilities.await?;
|
||||||
this.update(cx, |session, _| {
|
this.update(cx, |session, _| {
|
||||||
session.capabilities = capabilities;
|
session.capabilities = capabilities;
|
||||||
|
let filters = session
|
||||||
|
.capabilities
|
||||||
|
.exception_breakpoint_filters
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default();
|
||||||
|
for filter in filters {
|
||||||
|
let default = filter.default.unwrap_or_default();
|
||||||
|
session
|
||||||
|
.exception_breakpoints
|
||||||
|
.entry(filter.filter.clone())
|
||||||
|
.or_insert_with(|| (filter, default));
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
@ -1464,13 +1537,46 @@ impl Session {
|
||||||
self.ignore_breakpoints = ignore;
|
self.ignore_breakpoints = ignore;
|
||||||
|
|
||||||
if let Some(local) = self.as_local() {
|
if let Some(local) = self.as_local() {
|
||||||
local.send_all_breakpoints(ignore, cx)
|
local.send_source_breakpoints(ignore, cx)
|
||||||
} else {
|
} else {
|
||||||
// todo(debugger): We need to propagate this change to downstream sessions and send a message to upstream sessions
|
// todo(debugger): We need to propagate this change to downstream sessions and send a message to upstream sessions
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn exception_breakpoints(
|
||||||
|
&self,
|
||||||
|
) -> impl Iterator<Item = &(ExceptionBreakpointsFilter, IsEnabled)> {
|
||||||
|
self.exception_breakpoints.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_exception_breakpoint(&mut self, id: &str, cx: &App) {
|
||||||
|
if let Some((_, is_enabled)) = self.exception_breakpoints.get_mut(id) {
|
||||||
|
*is_enabled = !*is_enabled;
|
||||||
|
self.send_exception_breakpoints(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_exception_breakpoints(&mut self, cx: &App) {
|
||||||
|
if let Some(local) = self.as_local() {
|
||||||
|
let exception_filters = self
|
||||||
|
.exception_breakpoints
|
||||||
|
.values()
|
||||||
|
.filter_map(|(filter, is_enabled)| is_enabled.then(|| filter.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let supports_exception_filters = self
|
||||||
|
.capabilities
|
||||||
|
.supports_exception_filter_options
|
||||||
|
.unwrap_or_default();
|
||||||
|
local
|
||||||
|
.send_exception_breakpoints(exception_filters, supports_exception_filters, cx)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
} else {
|
||||||
|
debug_assert!(false, "Not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn breakpoints_enabled(&self) -> bool {
|
pub fn breakpoints_enabled(&self) -> bool {
|
||||||
self.ignore_breakpoints
|
self.ignore_breakpoints
|
||||||
}
|
}
|
||||||
|
@ -2084,6 +2190,7 @@ fn create_local_session(
|
||||||
threads: IndexMap::default(),
|
threads: IndexMap::default(),
|
||||||
stack_frames: IndexMap::default(),
|
stack_frames: IndexMap::default(),
|
||||||
locations: Default::default(),
|
locations: Default::default(),
|
||||||
|
exception_breakpoints: Default::default(),
|
||||||
_background_tasks,
|
_background_tasks,
|
||||||
is_session_terminated: false,
|
is_session_terminated: false,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue