debugger: Add breakpoint list (#28496)

![image](https://github.com/user-attachments/assets/2cbe60cc-bf04-4233-a7bc-32affff8eef5)
Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
This commit is contained in:
Piotr Osiewicz 2025-04-10 20:18:58 +02:00 committed by GitHub
parent 3abf95216c
commit 26f4705198
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 711 additions and 18 deletions

View file

@ -7,9 +7,9 @@ use super::dap_command::{
self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand,
EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,
ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand,
ScopesCommand, SetVariableValueCommand, StackTraceCommand, StepBackCommand, StepCommand,
StepInCommand, StepOutCommand, TerminateCommand, TerminateThreadsCommand, ThreadsCommand,
VariablesCommand,
ScopesCommand, SetExceptionBreakpoints, SetVariableValueCommand, StackTraceCommand,
StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand,
TerminateThreadsCommand, ThreadsCommand, VariablesCommand,
};
use super::dap_store::DapAdapterDelegate;
use anyhow::{Context as _, Result, anyhow};
@ -23,7 +23,10 @@ use dap::{
client::{DebugAdapterClient, SessionId},
messages::{Events, Message},
};
use dap::{DapRegistry, DebugRequestType, OutputEventCategory};
use dap::{
DapRegistry, DebugRequestType, ExceptionBreakpointsFilter, ExceptionFilterOptions,
OutputEventCategory,
};
use futures::channel::oneshot;
use futures::{FutureExt, future::Shared};
use gpui::{
@ -34,6 +37,7 @@ use serde_json::{Value, json};
use settings::Settings;
use smol::stream::StreamExt;
use std::any::TypeId;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::u64;
use std::{
@ -324,6 +328,13 @@ impl LocalMode {
}
}
session
.client
.on_request::<dap::requests::SetExceptionBreakpoints, _>(move |_, _| {
Ok(dap::SetExceptionBreakpointsResponse { breakpoints: None })
})
.await;
session
.client
.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 breakpoints = self
.breakpoint_store
@ -588,15 +623,37 @@ impl LocalMode {
};
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 this = self.clone();
async move |cx| {
initialized_rx.await?;
// 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
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 {
this.request(ConfigurationDone {}, cx.background_executor().clone())
} else {
@ -727,6 +784,8 @@ impl ThreadStates {
}
const MAX_TRACKED_OUTPUT_EVENTS: usize = 5000;
type IsEnabled = bool;
#[derive(Copy, Clone, Default, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct OutputToken(pub usize);
/// 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>,
is_session_terminated: bool,
requests: HashMap<TypeId, HashMap<RequestSlot, Shared<Task<Option<()>>>>>,
exception_breakpoints: BTreeMap<String, (ExceptionBreakpointsFilter, IsEnabled)>,
_background_tasks: Vec<Task<()>>,
}
@ -956,6 +1016,7 @@ impl Session {
_background_tasks: Vec::default(),
locations: Default::default(),
is_session_terminated: false,
exception_breakpoints: Default::default(),
}
}
@ -1022,6 +1083,18 @@ impl Session {
let capabilities = capabilities.await?;
this.update(cx, |session, _| {
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(())
})
@ -1464,13 +1537,46 @@ impl Session {
self.ignore_breakpoints = ignore;
if let Some(local) = self.as_local() {
local.send_all_breakpoints(ignore, cx)
local.send_source_breakpoints(ignore, cx)
} else {
// todo(debugger): We need to propagate this change to downstream sessions and send a message to upstream sessions
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 {
self.ignore_breakpoints
}
@ -2084,6 +2190,7 @@ fn create_local_session(
threads: IndexMap::default(),
stack_frames: IndexMap::default(),
locations: Default::default(),
exception_breakpoints: Default::default(),
_background_tasks,
is_session_terminated: false,
}