debugger: Touchups to log breakpoints (#27675)

This is a slight refactor that flattens Breakpoint struct in
anticipation of condition/hit breakpoints. It also adds a slight delay
before breakpoints are shown on gutter hover to make breakpoints less
attention grabbing.
Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2025-03-29 02:16:44 +01:00 committed by GitHub
parent 8ecf553279
commit f86977e2a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 136 additions and 276 deletions

View file

@ -147,7 +147,7 @@ use multi_buffer::{
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{ use project::{
debugger::breakpoint_store::{Breakpoint, BreakpointKind}, debugger::breakpoint_store::Breakpoint,
lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle}, lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
project_settings::{GitGutterSetting, ProjectSettings}, project_settings::{GitGutterSetting, ProjectSettings},
CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint, CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
@ -785,11 +785,11 @@ pub struct Editor {
expect_bounds_change: Option<Bounds<Pixels>>, expect_bounds_change: Option<Bounds<Pixels>>,
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>, tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
tasks_update_task: Option<Task<()>>, tasks_update_task: Option<Task<()>>,
pub breakpoint_store: Option<Entity<BreakpointStore>>, breakpoint_store: Option<Entity<BreakpointStore>>,
/// Allow's a user to create a breakpoint by selecting this indicator /// Allow's a user to create a breakpoint by selecting this indicator
/// It should be None while a user is not hovering over the gutter /// It should be None while a user is not hovering over the gutter
/// Otherwise it represents the point that the breakpoint will be shown /// Otherwise it represents the point that the breakpoint will be shown
pub gutter_breakpoint_indicator: Option<DisplayPoint>, gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
in_project_search: bool, in_project_search: bool,
previous_search_ranges: Option<Arc<[Range<Anchor>]>>, previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
breadcrumb_header: Option<String>, breadcrumb_header: Option<String>,
@ -1549,7 +1549,7 @@ impl Editor {
tasks: Default::default(), tasks: Default::default(),
breakpoint_store, breakpoint_store,
gutter_breakpoint_indicator: None, gutter_breakpoint_indicator: (None, None),
_subscriptions: vec![ _subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed), cx.observe(&buffer, Self::on_buffer_changed),
cx.subscribe_in(&buffer, window, Self::on_buffer_event), cx.subscribe_in(&buffer, window, Self::on_buffer_event),
@ -6226,10 +6226,7 @@ impl Editor {
.breakpoint_at_row(row, window, cx) .breakpoint_at_row(row, window, cx)
.map(|(_, bp)| Arc::from(bp)); .map(|(_, bp)| Arc::from(bp));
let log_breakpoint_msg = if breakpoint let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.message.is_some()) {
.as_ref()
.is_some_and(|bp| bp.kind.log_message().is_some())
{
"Edit Log Breakpoint" "Edit Log Breakpoint"
} else { } else {
"Set Log Breakpoint" "Set Log Breakpoint"
@ -6249,7 +6246,7 @@ impl Editor {
let breakpoint = breakpoint.unwrap_or_else(|| { let breakpoint = breakpoint.unwrap_or_else(|| {
Arc::new(Breakpoint { Arc::new(Breakpoint {
state: BreakpointState::Enabled, state: BreakpointState::Enabled,
kind: BreakpointKind::Standard, message: None,
}) })
}); });
@ -6308,16 +6305,17 @@ impl Editor {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> IconButton { ) -> IconButton {
let (color, icon) = { let (color, icon) = {
let icon = match (&breakpoint.kind, breakpoint.is_disabled()) { let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
(BreakpointKind::Standard, false) => ui::IconName::DebugBreakpoint, (false, false) => ui::IconName::DebugBreakpoint,
(BreakpointKind::Log(_), false) => ui::IconName::DebugLogBreakpoint, (true, false) => ui::IconName::DebugLogBreakpoint,
(BreakpointKind::Standard, true) => ui::IconName::DebugDisabledBreakpoint, (false, true) => ui::IconName::DebugDisabledBreakpoint,
(BreakpointKind::Log(_), true) => ui::IconName::DebugDisabledLogBreakpoint, (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
}; };
let color = if self let color = if self
.gutter_breakpoint_indicator .gutter_breakpoint_indicator
.is_some_and(|point| point.row() == row) .0
.is_some_and(|(point, is_visible)| is_visible && point.row() == row)
{ {
Color::Hint Color::Hint
} else { } else {
@ -8654,7 +8652,7 @@ impl Editor {
( (
breakpoint_position, breakpoint_position,
Breakpoint { Breakpoint {
kind: BreakpointKind::Standard, message: None,
state: BreakpointState::Enabled, state: BreakpointState::Enabled,
}, },
) )
@ -19758,8 +19756,8 @@ impl BreakpointPromptEditor {
let buffer = cx.new(|cx| { let buffer = cx.new(|cx| {
Buffer::local( Buffer::local(
breakpoint breakpoint
.kind .message
.log_message() .as_ref()
.map(|msg| msg.to_string()) .map(|msg| msg.to_string())
.unwrap_or_default(), .unwrap_or_default(),
cx, cx,

View file

@ -32,7 +32,7 @@ use multi_buffer::{IndentGuide, PathKey};
use parking_lot::Mutex; use parking_lot::Mutex;
use pretty_assertions::{assert_eq, assert_ne}; use pretty_assertions::{assert_eq, assert_ne};
use project::{ use project::{
debugger::breakpoint_store::{BreakpointKind, BreakpointState, SerializedBreakpoint}, debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
project_settings::{LspSettings, ProjectSettings}, project_settings::{LspSettings, ProjectSettings},
FakeFs, FakeFs,
}; };
@ -17390,12 +17390,12 @@ async fn assert_highlighted_edits(
#[track_caller] #[track_caller]
fn assert_breakpoint( fn assert_breakpoint(
breakpoints: &BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>, breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
path: &Arc<Path>, path: &Arc<Path>,
expected: Vec<(u32, Breakpoint)>, expected: Vec<(u32, Breakpoint)>,
) { ) {
if expected.len() == 0usize { if expected.len() == 0usize {
assert!(!breakpoints.contains_key(path)); assert!(!breakpoints.contains_key(path), "{}", path.display());
} else { } else {
let mut breakpoint = breakpoints let mut breakpoint = breakpoints
.get(path) .get(path)
@ -17403,9 +17403,9 @@ fn assert_breakpoint(
.into_iter() .into_iter()
.map(|breakpoint| { .map(|breakpoint| {
( (
breakpoint.position, breakpoint.row,
Breakpoint { Breakpoint {
kind: breakpoint.kind.clone(), message: breakpoint.message.clone(),
state: breakpoint.state, state: breakpoint.state,
}, },
) )
@ -17435,12 +17435,10 @@ fn add_log_breakpoint_at_cursor(
.buffer_snapshot .buffer_snapshot
.anchor_before(Point::new(cursor_position.row, 0)); .anchor_before(Point::new(cursor_position.row, 0));
let kind = BreakpointKind::Log(Arc::from(log_message));
( (
breakpoint_position, breakpoint_position,
Breakpoint { Breakpoint {
kind, message: Some(Arc::from(log_message)),
state: BreakpointState::Enabled, state: BreakpointState::Enabled,
}, },
) )
@ -17738,7 +17736,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
&abs_path, &abs_path,
vec![ vec![
(0, Breakpoint::new_standard()), (0, Breakpoint::new_standard()),
(3, Breakpoint::new_log("hello Earth !!")), (3, Breakpoint::new_log("hello Earth!!")),
], ],
); );
} }

View file

@ -71,6 +71,7 @@ use std::{
ops::{Deref, Range}, ops::{Deref, Range},
rc::Rc, rc::Rc,
sync::Arc, sync::Arc,
time::Duration,
}; };
use sum_tree::Bias; use sum_tree::Bias;
use text::BufferId; use text::BufferId;
@ -907,7 +908,6 @@ impl EditorElement {
let modifiers = event.modifiers; let modifiers = event.modifiers;
let gutter_hovered = gutter_hitbox.is_hovered(window); let gutter_hovered = gutter_hitbox.is_hovered(window);
editor.set_gutter_hovered(gutter_hovered, cx); editor.set_gutter_hovered(gutter_hovered, cx);
editor.gutter_breakpoint_indicator = None;
editor.mouse_cursor_hidden = false; editor.mouse_cursor_hidden = false;
if gutter_hovered { if gutter_hovered {
@ -924,8 +924,38 @@ impl EditorElement {
.buffer_for_excerpt(buffer_anchor.excerpt_id) .buffer_for_excerpt(buffer_anchor.excerpt_id)
.is_some_and(|buffer| buffer.file().is_some()) .is_some_and(|buffer| buffer.file().is_some())
{ {
editor.gutter_breakpoint_indicator = Some(new_point); let was_hovered = editor.gutter_breakpoint_indicator.0.is_some();
let is_visible = editor
.gutter_breakpoint_indicator
.0
.map_or(false, |(_, is_active)| is_active);
editor.gutter_breakpoint_indicator.0 = Some((new_point, is_visible));
editor.gutter_breakpoint_indicator.1.get_or_insert_with(|| {
cx.spawn(async move |this, cx| {
if !was_hovered {
cx.background_executor()
.timer(Duration::from_millis(200))
.await;
}
this.update(cx, |this, cx| {
if let Some((_, is_active)) =
this.gutter_breakpoint_indicator.0.as_mut()
{
*is_active = true;
}
cx.notify();
})
.ok();
})
});
} else {
editor.gutter_breakpoint_indicator = (None, None);
} }
} else {
editor.gutter_breakpoint_indicator = (None, None);
} }
cx.notify(); cx.notify();
@ -6851,8 +6881,10 @@ impl Element for EditorElement {
// has their mouse over that line when a breakpoint isn't there // has their mouse over that line when a breakpoint isn't there
if cx.has_flag::<Debugger>() { if cx.has_flag::<Debugger>() {
let gutter_breakpoint_indicator = let gutter_breakpoint_indicator =
self.editor.read(cx).gutter_breakpoint_indicator; self.editor.read(cx).gutter_breakpoint_indicator.0;
if let Some(gutter_breakpoint_point) = gutter_breakpoint_indicator { if let Some((gutter_breakpoint_point, _)) =
gutter_breakpoint_indicator.filter(|(_, is_active)| *is_active)
{
breakpoint_rows breakpoint_rows
.entry(gutter_breakpoint_point.row()) .entry(gutter_breakpoint_point.row())
.or_insert_with(|| { .or_insert_with(|| {

View file

@ -11,12 +11,7 @@ use rpc::{
proto::{self}, proto::{self},
AnyProtoClient, TypedEnvelope, AnyProtoClient, TypedEnvelope,
}; };
use std::{ use std::{hash::Hash, ops::Range, path::Path, sync::Arc};
hash::{Hash, Hasher},
ops::Range,
path::Path,
sync::Arc,
};
use text::{Point, PointUtf16}; use text::{Point, PointUtf16};
use crate::{buffer_store::BufferStore, worktree_store::WorktreeStore, Project, ProjectPath}; use crate::{buffer_store::BufferStore, worktree_store::WorktreeStore, Project, ProjectPath};
@ -274,8 +269,6 @@ impl BreakpointStore {
} }
BreakpointEditAction::EditLogMessage(log_message) => { BreakpointEditAction::EditLogMessage(log_message) => {
if !log_message.is_empty() { if !log_message.is_empty() {
breakpoint.1.kind = BreakpointKind::Log(log_message.clone());
let found_bp = let found_bp =
breakpoint_set breakpoint_set
.breakpoints .breakpoints
@ -289,18 +282,16 @@ impl BreakpointStore {
}); });
if let Some(found_bp) = found_bp { if let Some(found_bp) = found_bp {
found_bp.kind = BreakpointKind::Log(log_message.clone()); found_bp.message = Some(log_message.clone());
} else { } else {
breakpoint.1.message = Some(log_message.clone());
// We did not remove any breakpoint, hence let's toggle one. // We did not remove any breakpoint, hence let's toggle one.
breakpoint_set.breakpoints.push(breakpoint.clone()); breakpoint_set.breakpoints.push(breakpoint.clone());
} }
} else if matches!(&breakpoint.1.kind, BreakpointKind::Log(_)) { } else if breakpoint.1.message.is_some() {
breakpoint_set breakpoint_set.breakpoints.retain(|(other_pos, other)| {
.breakpoints &breakpoint.0 != other_pos && other.message.is_none()
.retain(|(other_pos, other_kind)| { })
&breakpoint.0 != other_pos
&& matches!(other_kind.kind, BreakpointKind::Standard)
});
} }
} }
} }
@ -419,7 +410,7 @@ impl BreakpointStore {
cx.notify(); cx.notify();
} }
pub fn breakpoints_from_path(&self, path: &Arc<Path>, cx: &App) -> Vec<SerializedBreakpoint> { pub fn breakpoints_from_path(&self, path: &Arc<Path>, cx: &App) -> Vec<SourceBreakpoint> {
self.breakpoints self.breakpoints
.get(path) .get(path)
.map(|bp| { .map(|bp| {
@ -428,11 +419,11 @@ impl BreakpointStore {
.iter() .iter()
.map(|(position, breakpoint)| { .map(|(position, breakpoint)| {
let position = snapshot.summary_for_anchor::<PointUtf16>(position).row; let position = snapshot.summary_for_anchor::<PointUtf16>(position).row;
SerializedBreakpoint { SourceBreakpoint {
position, row: position,
path: path.clone(), path: path.clone(),
kind: breakpoint.kind.clone(),
state: breakpoint.state, state: breakpoint.state,
message: breakpoint.message.clone(),
} }
}) })
.collect() .collect()
@ -440,7 +431,7 @@ impl BreakpointStore {
.unwrap_or_default() .unwrap_or_default()
} }
pub fn all_breakpoints(&self, cx: &App) -> BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>> { pub fn all_breakpoints(&self, cx: &App) -> BTreeMap<Arc<Path>, Vec<SourceBreakpoint>> {
self.breakpoints self.breakpoints
.iter() .iter()
.map(|(path, bp)| { .map(|(path, bp)| {
@ -451,10 +442,10 @@ impl BreakpointStore {
.iter() .iter()
.map(|(position, breakpoint)| { .map(|(position, breakpoint)| {
let position = snapshot.summary_for_anchor::<PointUtf16>(position).row; let position = snapshot.summary_for_anchor::<PointUtf16>(position).row;
SerializedBreakpoint { SourceBreakpoint {
position, row: position,
path: path.clone(), path: path.clone(),
kind: breakpoint.kind.clone(), message: breakpoint.message.clone(),
state: breakpoint.state, state: breakpoint.state,
} }
}) })
@ -466,7 +457,7 @@ impl BreakpointStore {
pub fn with_serialized_breakpoints( pub fn with_serialized_breakpoints(
&self, &self,
breakpoints: BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>, breakpoints: BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
cx: &mut Context<BreakpointStore>, cx: &mut Context<BreakpointStore>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if let BreakpointStoreMode::Local(mode) = &self.mode { if let BreakpointStoreMode::Local(mode) = &self.mode {
@ -503,11 +494,11 @@ impl BreakpointStore {
this.update(cx, |_, cx| BreakpointsInFile::new(buffer, cx))?; this.update(cx, |_, cx| BreakpointsInFile::new(buffer, cx))?;
for bp in bps { for bp in bps {
let position = snapshot.anchor_after(Point::new(bp.position, 0)); let position = snapshot.anchor_after(Point::new(bp.row, 0));
breakpoints_for_file.breakpoints.push(( breakpoints_for_file.breakpoints.push((
position, position,
Breakpoint { Breakpoint {
kind: bp.kind, message: bp.message,
state: bp.state, state: bp.state,
}, },
)) ))
@ -555,42 +546,6 @@ pub enum BreakpointEditAction {
EditLogMessage(LogMessage), EditLogMessage(LogMessage),
} }
#[derive(Clone, Debug)]
pub enum BreakpointKind {
Standard,
Log(LogMessage),
}
impl BreakpointKind {
pub fn to_int(&self) -> i32 {
match self {
BreakpointKind::Standard => 0,
BreakpointKind::Log(_) => 1,
}
}
pub fn log_message(&self) -> Option<LogMessage> {
match self {
BreakpointKind::Standard => None,
BreakpointKind::Log(message) => Some(message.clone()),
}
}
}
impl PartialEq for BreakpointKind {
fn eq(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
}
impl Eq for BreakpointKind {}
impl Hash for BreakpointKind {
fn hash<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum BreakpointState { pub enum BreakpointState {
Enabled, Enabled,
@ -619,22 +574,22 @@ impl BreakpointState {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Breakpoint { pub struct Breakpoint {
pub kind: BreakpointKind, pub message: Option<Arc<str>>,
pub state: BreakpointState, pub state: BreakpointState,
} }
impl Breakpoint { impl Breakpoint {
pub fn new_standard() -> Self { pub fn new_standard() -> Self {
Self { Self {
kind: BreakpointKind::Standard,
state: BreakpointState::Enabled, state: BreakpointState::Enabled,
message: None,
} }
} }
pub fn new_log(log_message: &str) -> Self { pub fn new_log(log_message: &str) -> Self {
Self { Self {
kind: BreakpointKind::Log(log_message.to_owned().into()),
state: BreakpointState::Enabled, state: BreakpointState::Enabled,
message: Some(log_message.into()),
} }
} }
@ -645,30 +600,17 @@ impl Breakpoint {
BreakpointState::Enabled => proto::BreakpointState::Enabled.into(), BreakpointState::Enabled => proto::BreakpointState::Enabled.into(),
BreakpointState::Disabled => proto::BreakpointState::Disabled.into(), BreakpointState::Disabled => proto::BreakpointState::Disabled.into(),
}, },
kind: match self.kind { message: self.message.as_ref().map(|s| String::from(s.as_ref())),
BreakpointKind::Standard => proto::BreakpointKind::Standard.into(),
BreakpointKind::Log(_) => proto::BreakpointKind::Log.into(),
},
message: if let BreakpointKind::Log(message) = &self.kind {
Some(message.to_string())
} else {
None
},
}) })
} }
fn from_proto(breakpoint: client::proto::Breakpoint) -> Option<Self> { fn from_proto(breakpoint: client::proto::Breakpoint) -> Option<Self> {
Some(Self { Some(Self {
kind: match proto::BreakpointKind::from_i32(breakpoint.kind) {
Some(proto::BreakpointKind::Log) => {
BreakpointKind::Log(breakpoint.message.clone().unwrap_or_default().into())
}
None | Some(proto::BreakpointKind::Standard) => BreakpointKind::Standard,
},
state: match proto::BreakpointState::from_i32(breakpoint.state) { state: match proto::BreakpointState::from_i32(breakpoint.state) {
Some(proto::BreakpointState::Disabled) => BreakpointState::Disabled, Some(proto::BreakpointState::Disabled) => BreakpointState::Disabled,
None | Some(proto::BreakpointState::Enabled) => BreakpointState::Enabled, None | Some(proto::BreakpointState::Enabled) => BreakpointState::Enabled,
}, },
message: breakpoint.message.map(|message| message.into()),
}) })
} }
@ -683,22 +625,23 @@ impl Breakpoint {
} }
} }
/// Breakpoint for location within source code.
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SerializedBreakpoint { pub struct SourceBreakpoint {
pub position: u32, pub row: u32,
pub path: Arc<Path>, pub path: Arc<Path>,
pub kind: BreakpointKind, pub message: Option<Arc<str>>,
pub state: BreakpointState, pub state: BreakpointState,
} }
impl From<SerializedBreakpoint> for dap::SourceBreakpoint { impl From<SourceBreakpoint> for dap::SourceBreakpoint {
fn from(bp: SerializedBreakpoint) -> Self { fn from(bp: SourceBreakpoint) -> Self {
Self { Self {
line: bp.position as u64 + 1, line: bp.row as u64 + 1,
column: None, column: None,
condition: None, condition: None,
hit_condition: None, hit_condition: None,
log_message: bp.kind.log_message().as_deref().map(Into::into), log_message: bp.message.map(|message| String::from(message.as_ref())),
mode: None, mode: None,
} }
} }

View file

@ -16,13 +16,10 @@ use dap::{
adapters::{DapStatus, DebugAdapterName}, adapters::{DapStatus, DebugAdapterName},
client::SessionId, client::SessionId,
messages::Message, messages::Message,
requests::{ requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
Completions, Evaluate, Request as _, RunInTerminal, SetExpression, SetVariable,
StartDebugging,
},
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse, Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse,
EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
SetExpressionArguments, SetVariableArguments, Source, StartDebuggingRequestArguments, Source, StartDebuggingRequestArguments,
}; };
use fs::Fs; use fs::Fs;
use futures::{ use futures::{
@ -710,59 +707,6 @@ impl DapStore {
}) })
} }
#[allow(clippy::too_many_arguments)]
pub fn set_variable_value(
&self,
session_id: &SessionId,
stack_frame_id: u64,
variables_reference: u64,
name: String,
value: String,
evaluate_name: Option<String>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let Some(client) = self
.session_by_id(session_id)
.and_then(|client| client.read(cx).adapter_client())
else {
return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
};
let supports_set_expression = self
.capabilities_by_id(session_id, cx)
.and_then(|caps| caps.supports_set_expression)
.unwrap_or_default();
cx.background_executor().spawn(async move {
if let Some(evaluate_name) = supports_set_expression.then(|| evaluate_name).flatten() {
client
.request::<SetExpression>(SetExpressionArguments {
expression: evaluate_name,
value,
frame_id: Some(stack_frame_id),
format: None,
})
.await?;
} else {
client
.request::<SetVariable>(SetVariableArguments {
variables_reference,
name,
value,
format: None,
})
.await?;
}
Ok(())
})
}
// .. get the client and what not
// let _ = client.modules(); // This can fire a request to a dap adapter or be a cheap getter.
// client.wait_for_request(request::Modules); // This ensures that the request that we've fired off runs to completions
// let returned_value = client.modules(); // this is a cheap getter.
pub fn shutdown_sessions(&mut self, cx: &mut Context<Self>) -> Task<()> { pub fn shutdown_sessions(&mut self, cx: &mut Context<Self>) -> Task<()> {
let mut tasks = vec![]; let mut tasks = vec![];
for session_id in self.sessions.keys().cloned().collect::<Vec<_>>() { for session_id in self.sessions.keys().cloned().collect::<Vec<_>>() {

View file

@ -2660,21 +2660,15 @@ message RefreshLlmToken {}
// Remote debugging // Remote debugging
enum BreakpointKind {
Standard = 0;
Log = 1;
}
enum BreakpointState { enum BreakpointState {
Enabled = 0; Enabled = 0;
Disabled = 1; Disabled = 1;
} }
message Breakpoint { message Breakpoint {
Anchor position = 1; Anchor position = 1;
BreakpointState state = 2; BreakpointState state = 2;
BreakpointKind kind = 3; reserved 3;
optional string message = 4; optional string message = 4;
} }

View file

@ -13,7 +13,7 @@ use client::DevServerProjectId;
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
use gpui::{point, size, Axis, Bounds, WindowBounds, WindowId}; use gpui::{point, size, Axis, Bounds, WindowBounds, WindowId};
use itertools::Itertools; use itertools::Itertools;
use project::debugger::breakpoint_store::{BreakpointKind, BreakpointState, SerializedBreakpoint}; use project::debugger::breakpoint_store::{BreakpointState, SourceBreakpoint};
use language::{LanguageName, Toolchain}; use language::{LanguageName, Toolchain};
use project::WorktreeId; use project::WorktreeId;
@ -147,7 +147,7 @@ impl Column for SerializedWindowBounds {
#[derive(Debug)] #[derive(Debug)]
pub struct Breakpoint { pub struct Breakpoint {
pub position: u32, pub position: u32,
pub kind: BreakpointKind, pub message: Option<Arc<str>>,
pub state: BreakpointState, pub state: BreakpointState,
} }
@ -183,50 +183,6 @@ impl Column for BreakpointStateWrapper<'_> {
} }
} }
/// Wrapper for DB type of a breakpoint
#[derive(Debug)]
struct BreakpointKindWrapper<'a>(Cow<'a, BreakpointKind>);
impl From<BreakpointKind> for BreakpointKindWrapper<'static> {
fn from(kind: BreakpointKind) -> Self {
BreakpointKindWrapper(Cow::Owned(kind))
}
}
impl StaticColumnCount for BreakpointKindWrapper<'_> {
fn column_count() -> usize {
1
}
}
impl Bind for BreakpointKindWrapper<'_> {
fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
let next_index = statement.bind(&self.0.to_int(), start_index)?;
match self.0.as_ref() {
BreakpointKind::Standard => {
statement.bind_null(next_index)?;
Ok(next_index + 1)
}
BreakpointKind::Log(message) => statement.bind(&message.as_ref(), next_index),
}
}
}
impl Column for BreakpointKindWrapper<'_> {
fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
let kind = statement.column_int(start_index)?;
match kind {
0 => Ok((BreakpointKind::Standard.into(), start_index + 2)),
1 => {
let message = statement.column_text(start_index)?.to_string();
Ok((BreakpointKind::Log(message.into()).into(), start_index + 1))
}
_ => Err(anyhow::anyhow!("Invalid BreakpointKind discriminant")),
}
}
}
/// This struct is used to implement traits on Vec<breakpoint> /// This struct is used to implement traits on Vec<breakpoint>
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
@ -234,7 +190,7 @@ struct Breakpoints(Vec<Breakpoint>);
impl sqlez::bindable::StaticColumnCount for Breakpoint { impl sqlez::bindable::StaticColumnCount for Breakpoint {
fn column_count() -> usize { fn column_count() -> usize {
1 + BreakpointKindWrapper::column_count() + BreakpointStateWrapper::column_count() 2 + BreakpointStateWrapper::column_count()
} }
} }
@ -245,10 +201,7 @@ impl sqlez::bindable::Bind for Breakpoint {
start_index: i32, start_index: i32,
) -> anyhow::Result<i32> { ) -> anyhow::Result<i32> {
let next_index = statement.bind(&self.position, start_index)?; let next_index = statement.bind(&self.position, start_index)?;
let next_index = statement.bind( let next_index = statement.bind(&self.message, next_index)?;
&BreakpointKindWrapper(Cow::Borrowed(&self.kind)),
next_index,
)?;
statement.bind( statement.bind(
&BreakpointStateWrapper(Cow::Borrowed(&self.state)), &BreakpointStateWrapper(Cow::Borrowed(&self.state)),
next_index, next_index,
@ -262,13 +215,13 @@ impl Column for Breakpoint {
.column_int(start_index) .column_int(start_index)
.with_context(|| format!("Failed to read BreakPoint at index {start_index}"))? .with_context(|| format!("Failed to read BreakPoint at index {start_index}"))?
as u32; as u32;
let (kind, next_index) = BreakpointKindWrapper::column(statement, start_index + 1)?; let (message, next_index) = Option::<String>::column(statement, start_index + 1)?;
let (state, next_index) = BreakpointStateWrapper::column(statement, next_index)?; let (state, next_index) = BreakpointStateWrapper::column(statement, next_index)?;
Ok(( Ok((
Breakpoint { Breakpoint {
position, position,
kind: kind.0.into_owned(), message: message.map(Arc::from),
state: state.0.into_owned(), state: state.0.into_owned(),
}, },
next_index, next_index,
@ -570,6 +523,9 @@ define_connection! {
), ),
sql!( sql!(
ALTER TABLE breakpoints ADD COLUMN state INTEGER DEFAULT(0) NOT NULL ALTER TABLE breakpoints ADD COLUMN state INTEGER DEFAULT(0) NOT NULL
),
sql!(
ALTER TABLE breakpoints DROP COLUMN kind
) )
]; ];
} }
@ -720,13 +676,10 @@ impl WorkspaceDb {
}) })
} }
fn breakpoints( fn breakpoints(&self, workspace_id: WorkspaceId) -> BTreeMap<Arc<Path>, Vec<SourceBreakpoint>> {
&self,
workspace_id: WorkspaceId,
) -> BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>> {
let breakpoints: Result<Vec<(PathBuf, Breakpoint)>> = self let breakpoints: Result<Vec<(PathBuf, Breakpoint)>> = self
.select_bound(sql! { .select_bound(sql! {
SELECT path, breakpoint_location, kind, log_message, state SELECT path, breakpoint_location, log_message, state
FROM breakpoints FROM breakpoints
WHERE workspace_id = ? WHERE workspace_id = ?
}) })
@ -738,18 +691,16 @@ impl WorkspaceDb {
log::debug!("Breakpoints are empty after querying database for them"); log::debug!("Breakpoints are empty after querying database for them");
} }
let mut map: BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>> = Default::default(); let mut map: BTreeMap<Arc<Path>, Vec<SourceBreakpoint>> = Default::default();
for (path, breakpoint) in bp { for (path, breakpoint) in bp {
let path: Arc<Path> = path.into(); let path: Arc<Path> = path.into();
map.entry(path.clone()) map.entry(path.clone()).or_default().push(SourceBreakpoint {
.or_default() row: breakpoint.position,
.push(SerializedBreakpoint { path,
position: breakpoint.position, message: breakpoint.message,
path, state: breakpoint.state,
kind: breakpoint.kind, });
state: breakpoint.state,
});
} }
map map
@ -775,17 +726,17 @@ impl WorkspaceDb {
conn.exec_bound(sql!(DELETE FROM breakpoints WHERE workspace_id = ?1 AND path = ?2))?((workspace.id, path.as_ref())) conn.exec_bound(sql!(DELETE FROM breakpoints WHERE workspace_id = ?1 AND path = ?2))?((workspace.id, path.as_ref()))
.context("Clearing old breakpoints")?; .context("Clearing old breakpoints")?;
for bp in breakpoints { for bp in breakpoints {
let kind = BreakpointKindWrapper::from(bp.kind); let message = bp.message;
let state = BreakpointStateWrapper::from(bp.state); let state = BreakpointStateWrapper::from(bp.state);
match conn.exec_bound(sql!( match conn.exec_bound(sql!(
INSERT INTO breakpoints (workspace_id, path, breakpoint_location, kind, log_message, state) INSERT INTO breakpoints (workspace_id, path, breakpoint_location, log_message, state)
VALUES (?1, ?2, ?3, ?4, ?5, ?6);))? VALUES (?1, ?2, ?3, ?4, ?5);))?
(( ((
workspace.id, workspace.id,
path.as_ref(), path.as_ref(),
bp.position, bp.row,
kind, message,
state, state,
)) { )) {
Ok(_) => {} Ok(_) => {}
@ -1453,19 +1404,19 @@ mod tests {
let breakpoint = Breakpoint { let breakpoint = Breakpoint {
position: 123, position: 123,
kind: BreakpointKind::Standard, message: None,
state: BreakpointState::Enabled, state: BreakpointState::Enabled,
}; };
let log_breakpoint = Breakpoint { let log_breakpoint = Breakpoint {
position: 456, position: 456,
kind: BreakpointKind::Log("Test log message".into()), message: Some("Test log message".into()),
state: BreakpointState::Enabled, state: BreakpointState::Enabled,
}; };
let disable_breakpoint = Breakpoint { let disable_breakpoint = Breakpoint {
position: 578, position: 578,
kind: BreakpointKind::Standard, message: None,
state: BreakpointState::Disabled, state: BreakpointState::Disabled,
}; };
@ -1482,22 +1433,22 @@ mod tests {
map.insert( map.insert(
Arc::from(path), Arc::from(path),
vec![ vec![
SerializedBreakpoint { SourceBreakpoint {
position: breakpoint.position, row: breakpoint.position,
path: Arc::from(path), path: Arc::from(path),
kind: breakpoint.kind.clone(), message: breakpoint.message.clone(),
state: breakpoint.state, state: breakpoint.state,
}, },
SerializedBreakpoint { SourceBreakpoint {
position: log_breakpoint.position, row: log_breakpoint.position,
path: Arc::from(path), path: Arc::from(path),
kind: log_breakpoint.kind.clone(), message: log_breakpoint.message.clone(),
state: log_breakpoint.state, state: log_breakpoint.state,
}, },
SerializedBreakpoint { SourceBreakpoint {
position: disable_breakpoint.position, row: disable_breakpoint.position,
path: Arc::from(path), path: Arc::from(path),
kind: disable_breakpoint.kind.clone(), message: disable_breakpoint.message.clone(),
state: disable_breakpoint.state, state: disable_breakpoint.state,
}, },
], ],
@ -1515,18 +1466,18 @@ mod tests {
assert_eq!(loaded_breakpoints.len(), 3); assert_eq!(loaded_breakpoints.len(), 3);
assert_eq!(loaded_breakpoints[0].position, breakpoint.position); assert_eq!(loaded_breakpoints[0].row, breakpoint.position);
assert_eq!(loaded_breakpoints[0].kind, breakpoint.kind); assert_eq!(loaded_breakpoints[0].message, breakpoint.message);
assert_eq!(loaded_breakpoints[0].state, breakpoint.state); assert_eq!(loaded_breakpoints[0].state, breakpoint.state);
assert_eq!(loaded_breakpoints[0].path, Arc::from(path)); assert_eq!(loaded_breakpoints[0].path, Arc::from(path));
assert_eq!(loaded_breakpoints[1].position, log_breakpoint.position); assert_eq!(loaded_breakpoints[1].row, log_breakpoint.position);
assert_eq!(loaded_breakpoints[1].kind, log_breakpoint.kind); assert_eq!(loaded_breakpoints[1].message, log_breakpoint.message);
assert_eq!(loaded_breakpoints[1].state, log_breakpoint.state); assert_eq!(loaded_breakpoints[1].state, log_breakpoint.state);
assert_eq!(loaded_breakpoints[1].path, Arc::from(path)); assert_eq!(loaded_breakpoints[1].path, Arc::from(path));
assert_eq!(loaded_breakpoints[2].position, disable_breakpoint.position); assert_eq!(loaded_breakpoints[2].row, disable_breakpoint.position);
assert_eq!(loaded_breakpoints[2].kind, disable_breakpoint.kind); assert_eq!(loaded_breakpoints[2].message, disable_breakpoint.message);
assert_eq!(loaded_breakpoints[2].state, disable_breakpoint.state); assert_eq!(loaded_breakpoints[2].state, disable_breakpoint.state);
assert_eq!(loaded_breakpoints[2].path, Arc::from(path)); assert_eq!(loaded_breakpoints[2].path, Arc::from(path));
} }

View file

@ -10,7 +10,7 @@ use db::sqlez::{
}; };
use gpui::{AsyncWindowContext, Entity, WeakEntity}; use gpui::{AsyncWindowContext, Entity, WeakEntity};
use itertools::Itertools as _; use itertools::Itertools as _;
use project::{debugger::breakpoint_store::SerializedBreakpoint, Project}; use project::{debugger::breakpoint_store::SourceBreakpoint, Project};
use remote::ssh_session::SshProjectId; use remote::ssh_session::SshProjectId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
@ -264,7 +264,7 @@ pub(crate) struct SerializedWorkspace {
pub(crate) display: Option<Uuid>, pub(crate) display: Option<Uuid>,
pub(crate) docks: DockStructure, pub(crate) docks: DockStructure,
pub(crate) session_id: Option<String>, pub(crate) session_id: Option<String>,
pub(crate) breakpoints: BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>, pub(crate) breakpoints: BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
pub(crate) window_id: Option<u64>, pub(crate) window_id: Option<u64>,
} }