diff --git a/Cargo.lock b/Cargo.lock index ac5056a01bd98..2ad8d7bc55293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -893,9 +893,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", @@ -2085,9 +2085,9 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.20.5" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88da5a13c620b4ca0078845707ea9c3faf11edbc3ffd8497d11d686211cd1ac0" +checksum = "ad639525b1c67b6a298f378417b060fbc04618bea559482a8484381cce27d965" dependencies = [ "serde", "toml 0.8.19", @@ -2275,9 +2275,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.19" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -2285,9 +2285,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -2307,9 +2307,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3740,6 +3740,7 @@ dependencies = [ "log", "lsp", "markdown", + "menu", "multi_buffer", "ordered-float 2.10.1", "parking_lot", @@ -6499,9 +6500,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libdbus-sys" @@ -7911,9 +7912,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" @@ -8421,9 +8422,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ "diff", "yansi", @@ -10238,9 +10239,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "indexmap 2.4.0", "itoa", @@ -10781,9 +10782,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.6" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" dependencies = [ "nom", "unicode_categories", @@ -11618,12 +11619,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ "rustix 0.38.35", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -11748,18 +11749,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -12074,9 +12075,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes 1.7.1", "futures-core", @@ -13218,9 +13219,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ "futures-util", "js-sys", @@ -14573,9 +14574,9 @@ dependencies = [ [[package]] name = "yansi" -version = "1.0.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "yazi" diff --git a/crates/debugger_ui/src/console.rs b/crates/debugger_ui/src/console.rs index c4d8dab4f272b..6b87205da65dd 100644 --- a/crates/debugger_ui/src/console.rs +++ b/crates/debugger_ui/src/console.rs @@ -84,19 +84,19 @@ impl Console { } fn evaluate(&mut self, _: &Confirm, cx: &mut ViewContext) { - let expession = self.query_bar.update(cx, |editor, cx| { - let expession = editor.text(cx); + let expression = self.query_bar.update(cx, |editor, cx| { + let expression = editor.text(cx); editor.clear(cx); - expession + expression }); let evaluate_task = self.dap_store.update(cx, |store, cx| { store.evaluate( &self.client_id, self.current_stack_frame_id, - expession, + expression, dap::EvaluateArgumentsContext::Variables, cx, ) diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index cfd9284f80765..ca9f264789b8e 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -52,6 +52,7 @@ linkify.workspace = true log.workspace = true lsp.workspace = true markdown.workspace = true +menu.workspace = true multi_buffer.workspace = true ordered-float.workspace = true parking_lot.workspace = true diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 69cacad403f95..f983dac0c2edb 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -592,6 +592,17 @@ impl DisplaySnapshot { .anchor_at(point.to_offset(self, bias), bias) } + pub fn display_point_to_breakpoint_anchor(&self, point: DisplayPoint) -> Anchor { + let bias = if point.is_zero() { + Bias::Right + } else { + Bias::Left + }; + + self.buffer_snapshot + .anchor_at(point.to_offset(self, bias), bias) + } + fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint { let block_point = point.0; let wrap_point = self.block_snapshot.to_wrap_point(block_point); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 84ae287e122cc..3276db49b65f0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -99,6 +99,7 @@ use language::{ }; use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange}; use linked_editing_ranges::refresh_linked_ranges; +use project::dap_store::BreakpointEditAction; pub use proposed_changes_editor::{ ProposedChangesBuffer, ProposedChangesEditor, ProposedChangesEditorToolbar, }; @@ -5438,7 +5439,7 @@ impl Editor { fn active_breakpoint_points( &mut self, cx: &mut ViewContext, - ) -> HashMap { + ) -> HashMap { let mut breakpoint_display_points = HashMap::default(); let Some(dap_store) = self.dap_store.clone() else { @@ -5458,7 +5459,7 @@ impl Editor { let point = breakpoint.point_for_buffer(&buffer); breakpoint_display_points - .insert(point.to_display_point(&snapshot), breakpoint.kind.clone()); + .insert(point.to_display_point(&snapshot).row(), breakpoint.clone()); } }; }; @@ -5514,7 +5515,7 @@ impl Editor { let position = excerpt_head + DisplayPoint::new(DisplayRow(delta), 0); - breakpoint_display_points.insert(position, breakpoint.kind.clone()); + breakpoint_display_points.insert(position.row(), breakpoint.clone()); } } }; @@ -5545,6 +5546,7 @@ impl Editor { BreakpointKind::Log(_) => ui::IconName::DebugLogBreakpoint, }; let arc_kind = Arc::new(kind.clone()); + let arc_kind2 = arc_kind.clone(); IconButton::new(("breakpoint_indicator", row.0 as usize), icon) .icon_size(IconSize::XSmall) @@ -5553,7 +5555,12 @@ impl Editor { .style(ButtonStyle::Transparent) .on_click(cx.listener(move |editor, _e, cx| { editor.focus(cx); - editor.toggle_breakpoint_at_anchor(position, (*arc_kind).clone(), cx); + editor.edit_breakpoint_at_anchor( + position, + (*arc_kind).clone(), + BreakpointEditAction::Toggle, + cx, + ); })) .on_right_click(cx.listener(move |editor, event: &ClickEvent, cx| { let source = editor @@ -5567,6 +5574,14 @@ impl Editor { let editor_weak = cx.view().downgrade(); let second_weak = editor_weak.clone(); + let log_message = arc_kind2.log_message(); + + let second_entry_msg = if log_message.is_some() { + "Edit Log Breakpoint" + } else { + "Toggle Log Breakpoint" + }; + let context_menu = ui::ContextMenu::build(cx, move |menu, _cx| { let anchor = position; menu.on_blur_subscription(Subscription::new(|| {})) @@ -5574,23 +5589,61 @@ impl Editor { .entry("Toggle Breakpoint", None, move |cx| { if let Some(editor) = editor_weak.upgrade() { editor.update(cx, |this, cx| { - this.toggle_breakpoint_at_anchor( + this.edit_breakpoint_at_anchor( anchor, BreakpointKind::Standard, + BreakpointEditAction::Toggle, cx, ); }) } }) - .entry("Toggle Log Breakpoint", None, move |cx| { + .entry(second_entry_msg, None, move |cx| { if let Some(editor) = second_weak.clone().upgrade() { + let log_message = log_message.clone(); editor.update(cx, |this, cx| { - this.toggle_breakpoint_at_anchor( - anchor, - BreakpointKind::Log("Log breakpoint".into()), - cx, + let position = this.snapshot(cx).display_point_to_anchor( + DisplayPoint::new(row, 0), + Bias::Right, ); - }) + + let weak_editor = cx.view().downgrade(); + let bp_prompt = cx.new_view(|cx| { + BreakpointPromptEditor::new( + weak_editor, + anchor, + log_message, + cx, + ) + }); + + let height = bp_prompt.update(cx, |this, cx| { + this.prompt.update(cx, |prompt, cx| { + prompt.max_point(cx).row().0 + 1 + 2 + }) + }); + let cloned_prompt = bp_prompt.clone(); + let blocks = vec![BlockProperties { + style: BlockStyle::Sticky, + position, + height, + render: Box::new(move |cx| { + *cloned_prompt.read(cx).gutter_dimensions.lock() = + *cx.gutter_dimensions; + cloned_prompt.clone().into_any_element() + }), + disposition: BlockDisposition::Above, + priority: 0, + }]; + + let focus_handle = bp_prompt.focus_handle(cx); + cx.focus(&focus_handle); + + let block_ids = this.insert_blocks(blocks, None, cx); + bp_prompt.update(cx, |prompt, _| { + prompt.add_block_ids(block_ids); + }); + }); } }) }); @@ -6446,15 +6499,6 @@ impl Editor { pub fn toggle_breakpoint(&mut self, _: &ToggleBreakpoint, cx: &mut ViewContext) { let cursor_position: Point = self.selections.newest(cx).head(); - // Bias is set to right when placing a breakpoint on the first row - // to avoid having the breakpoint's anchor be anchor::MIN & having - // it's buffer_id be None - let bias = if cursor_position.row == 0 { - Bias::Right - } else { - Bias::Left - }; - // We Set the column position to zero so this function interacts correctly // between calls by clicking on the gutter & using an action to toggle a // breakpoint. Otherwise, toggling a breakpoint through an action wouldn't @@ -6463,16 +6507,50 @@ impl Editor { .snapshot(cx) .display_snapshot .buffer_snapshot - .anchor_at(Point::new(cursor_position.row, 0), bias) + .breakpoint_anchor(Point::new(cursor_position.row, 0)) .text_anchor; - self.toggle_breakpoint_at_anchor(breakpoint_position, BreakpointKind::Standard, cx); + let project = self.project.clone(); + + let found_bp = maybe!({ + let buffer_id = breakpoint_position.buffer_id?; + let buffer = + project?.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?; + let (buffer_snapshot, project_path) = ( + buffer.read(cx).snapshot(), + buffer.read(cx).project_path(cx)?, + ); + + let row = buffer_snapshot + .summary_for_anchor::(&breakpoint_position) + .row; + + let bp = self.dap_store.clone()?.read_with(cx, |store, _cx| { + store.breakpoint_at_row(row, &project_path, buffer_snapshot) + })?; + + Some((bp.active_position?, bp.kind)) + }); + + let edit_action = BreakpointEditAction::Toggle; + + if let Some((anchor, kind)) = found_bp { + self.edit_breakpoint_at_anchor(anchor, kind, edit_action, cx); + } else { + self.edit_breakpoint_at_anchor( + breakpoint_position, + BreakpointKind::Standard, + edit_action, + cx, + ); + } } - pub fn toggle_breakpoint_at_anchor( + pub fn edit_breakpoint_at_anchor( &mut self, breakpoint_position: text::Anchor, kind: BreakpointKind, + edit_action: BreakpointEditAction, cx: &mut ViewContext, ) { let Some(project) = &self.project else { @@ -6506,6 +6584,7 @@ impl Editor { active_position: Some(breakpoint_position), kind, }, + edit_action, cx, ); }); @@ -14534,3 +14613,150 @@ fn check_multiline_range(buffer: &Buffer, range: Range) -> Range { range.start..range.start } } + +struct BreakpointPromptEditor { + pub(crate) prompt: View, + editor: WeakView, + breakpoint_anchor: text::Anchor, + block_ids: HashSet, + gutter_dimensions: Arc>, + _subscriptions: Vec, +} + +impl BreakpointPromptEditor { + const MAX_LINES: u8 = 4; + + fn new( + editor: WeakView, + breakpoint_anchor: text::Anchor, + log_message: Option>, + cx: &mut ViewContext, + ) -> Self { + let buffer = cx.new_model(|cx| { + Buffer::local( + log_message.map(|msg| msg.to_string()).unwrap_or_default(), + cx, + ) + }); + let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); + + let prompt = cx.new_view(|cx| { + let mut prompt = Editor::new( + EditorMode::AutoHeight { + max_lines: Self::MAX_LINES as usize, + }, + buffer, + None, + false, + cx, + ); + prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); + // Since the prompt editors for all inline assistants are linked, + // always show the cursor (even when it isn't focused) because + // typing in one will make what you typed appear in all of them. + prompt.set_show_cursor_when_unfocused(true, cx); + prompt.set_placeholder_text( + "Message to log when breakpoint is hit. Expressions within {} are interpolated.", + cx, + ); + + prompt + }); + + Self { + prompt, + editor, + breakpoint_anchor, + gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())), + block_ids: Default::default(), + _subscriptions: vec![], + } + } + + pub(crate) fn add_block_ids(&mut self, block_ids: Vec) { + self.block_ids.extend(block_ids) + } + + fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { + if let Some(editor) = self.editor.upgrade() { + let log_message = self + .prompt + .read(cx) + .buffer + .read(cx) + .as_singleton() + .expect("A multi buffer in breakpoint prompt isn't possible") + .read(cx) + .as_rope() + .to_string(); + + editor.update(cx, |editor, cx| { + editor.edit_breakpoint_at_anchor( + self.breakpoint_anchor, + BreakpointKind::Log(log_message.into()), + BreakpointEditAction::EditLogMessage, + cx, + ); + + editor.remove_blocks(self.block_ids.clone(), None, cx); + }); + } + } + + fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { + if let Some(editor) = self.editor.upgrade() { + editor.update(cx, |editor, cx| { + editor.remove_blocks(self.block_ids.clone(), None, cx); + }); + } + } + + fn render_prompt_editor(&self, cx: &mut ViewContext) -> impl IntoElement { + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: if self.prompt.read(cx).read_only(cx) { + cx.theme().colors().text_disabled + } else { + cx.theme().colors().text + }, + font_family: settings.buffer_font.family.clone(), + font_fallbacks: settings.buffer_font.fallbacks.clone(), + font_size: settings.buffer_font_size.into(), + font_weight: settings.buffer_font.weight, + line_height: relative(settings.buffer_line_height.value()), + ..Default::default() + }; + EditorElement::new( + &self.prompt, + EditorStyle { + background: cx.theme().colors().editor_background, + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + ) + } +} + +impl Render for BreakpointPromptEditor { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let gutter_dimensions = *self.gutter_dimensions.lock(); + h_flex() + .key_context("Editor") + .bg(cx.theme().colors().editor_background) + .border_y_1() + .border_color(cx.theme().status().info_border) + .size_full() + .py(cx.line_height() / 2.5) + .on_action(cx.listener(Self::confirm)) + .on_action(cx.listener(Self::cancel)) + .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))) + .child(div().flex_1().child(self.render_prompt_editor(cx))) + } +} + +impl FocusableView for BreakpointPromptEditor { + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.prompt.focus_handle(cx) + } +} diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 815d87ea6d9b5..7b52aeb4ec93c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -24,7 +24,7 @@ use crate::{ CURSORS_VISIBLE_FOR, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, }; use client::ParticipantIndex; -use collections::{BTreeMap, HashMap, HashSet}; +use collections::{BTreeMap, HashMap}; use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid}; use gpui::Subscription; use gpui::{ @@ -48,7 +48,7 @@ use language::{ use lsp::DiagnosticSeverity; use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow}; use project::{ - dap_store::BreakpointKind, + dap_store::{Breakpoint, BreakpointKind}, project_settings::{GitGutterSetting, ProjectSettings}, ProjectPath, }; @@ -1649,7 +1649,7 @@ impl EditorElement { gutter_hitbox: &Hitbox, rows_with_hunk_bounds: &HashMap>, snapshot: &EditorSnapshot, - breakpoints: HashMap, + breakpoints: HashMap, cx: &mut WindowContext, ) -> Vec { self.editor.update(cx, |editor, cx| { @@ -1659,10 +1659,10 @@ impl EditorElement { breakpoints .iter() - .filter_map(|(point, kind)| { - let row = MultiBufferRow { 0: point.row().0 }; + .filter_map(|(point, bp)| { + let row = MultiBufferRow { 0: point.0 }; - if range.start > point.row() || range.end < point.row() { + if range.start > *point || range.end < *point { return None; } @@ -1670,22 +1670,20 @@ impl EditorElement { return None; } - let bias = if point.is_zero() { - Bias::Right - } else { - Bias::Left - }; - - let position = snapshot - .display_snapshot - .display_point_to_anchor(*point, bias) + let backup_position = snapshot + .display_point_to_breakpoint_anchor(DisplayPoint::new(*point, 0)) .text_anchor; - let button = editor.render_breakpoint(position, point.row(), &kind, cx); + let button = editor.render_breakpoint( + bp.active_position.unwrap_or(backup_position), + *point, + &bp.kind, + cx, + ); let button = prepaint_gutter_button( button, - point.row(), + *point, line_height, gutter_dimensions, scroll_pixel_position, @@ -1709,7 +1707,7 @@ impl EditorElement { gutter_hitbox: &Hitbox, rows_with_hunk_bounds: &HashMap>, snapshot: &EditorSnapshot, - breakpoints: &mut HashMap, + breakpoints: &mut HashMap, cx: &mut WindowContext, ) -> Vec { self.editor.update(cx, |editor, cx| { @@ -1753,12 +1751,11 @@ impl EditorElement { } let display_row = multibuffer_point.to_display_point(snapshot).row(); - let display_point = DisplayPoint::new(display_row, 0); let button = editor.render_run_indicator( &self.style, Some(display_row) == active_task_indicator_row, display_row, - breakpoints.remove(&display_point).is_some(), + breakpoints.remove(&display_row).is_some(), cx, ); @@ -1787,7 +1784,7 @@ impl EditorElement { gutter_dimensions: &GutterDimensions, gutter_hitbox: &Hitbox, rows_with_hunk_bounds: &HashMap>, - breakpoint_points: &mut HashMap, + breakpoint_points: &mut HashMap, cx: &mut WindowContext, ) -> Option { let mut active = false; @@ -1805,10 +1802,7 @@ impl EditorElement { }); let button = button?; - let button = if breakpoint_points - .remove(&DisplayPoint::new(row, 0)) - .is_some() - { + let button = if breakpoint_points.remove(&row).is_some() { button.icon_color(Color::Debugger) } else { button @@ -1897,7 +1891,7 @@ impl EditorElement { active_rows: &BTreeMap, newest_selection_head: Option, snapshot: &EditorSnapshot, - breakpoint_rows: HashSet, + breakpoint_rows: &HashMap, cx: &mut WindowContext, ) -> Vec> { let include_line_numbers = snapshot.show_line_numbers.unwrap_or_else(|| { @@ -1937,7 +1931,7 @@ impl EditorElement { .map(|(ix, multibuffer_row)| { let multibuffer_row = multibuffer_row?; let display_row = DisplayRow(rows.start.0 + ix as u32); - let color = if breakpoint_rows.contains(&display_row) { + let color = if breakpoint_rows.contains_key(&display_row) { cx.theme().colors().debugger_accent } else if active_rows.contains_key(&display_row) { cx.theme().colors().editor_active_line_number @@ -5075,7 +5069,7 @@ impl Element for EditorElement { cx.set_view_id(self.editor.entity_id()); cx.set_focus_handle(&focus_handle); - let mut breakpoint_lines = self + let mut breakpoint_rows = self .editor .update(cx, |editor, cx| editor.active_breakpoint_points(cx)); @@ -5259,35 +5253,35 @@ impl Element for EditorElement { cx, ); - let gutter_breakpoint_indicator = - self.editor.read(cx).gutter_breakpoint_indicator; - - let breakpoint_rows = breakpoint_lines - .keys() - .map(|display_point| display_point.row()) - .collect(); - - // We want all lines with breakpoint's to have their number's painted - // debug accent color & we still want to render a grey breakpoint for the gutter - // indicator so we add that in after creating breakpoint_rows for layout line nums - // Otherwise, when a cursor is on a line number it will always be white even - // if that line has a breakpoint - if let Some(gutter_breakpoint_point) = gutter_breakpoint_indicator { - breakpoint_lines - .entry(gutter_breakpoint_point) - .or_insert(BreakpointKind::Standard); - } - let line_numbers = self.layout_line_numbers( start_row..end_row, buffer_rows.iter().copied(), &active_rows, newest_selection_head, &snapshot, - breakpoint_rows, + &breakpoint_rows, cx, ); + // We add the gutter breakpoint indicator to breakpoint_rows after painting + // line numbers so we don't paint a line number debug accent color if a user + // has their mouse over that line when a breakpoint isn't there + let gutter_breakpoint_indicator = + self.editor.read(cx).gutter_breakpoint_indicator; + if let Some(gutter_breakpoint_point) = gutter_breakpoint_indicator { + breakpoint_rows + .entry(gutter_breakpoint_point.row()) + .or_insert(Breakpoint { + active_position: Some( + snapshot + .display_point_to_breakpoint_anchor(gutter_breakpoint_point) + .text_anchor, + ), + cache_position: 0, + kind: BreakpointKind::Standard, + }); + } + let mut gutter_fold_toggles = cx.with_element_namespace("gutter_fold_toggles", |cx| { self.layout_gutter_fold_toggles( @@ -5612,7 +5606,7 @@ impl Element for EditorElement { &gutter_dimensions, &gutter_hitbox, &rows_with_hunk_bounds, - &mut breakpoint_lines, + &mut breakpoint_rows, cx, ); } @@ -5631,7 +5625,7 @@ impl Element for EditorElement { &gutter_hitbox, &rows_with_hunk_bounds, &snapshot, - &mut breakpoint_lines, + &mut breakpoint_rows, cx, ) } else { @@ -5646,7 +5640,7 @@ impl Element for EditorElement { &gutter_hitbox, &rows_with_hunk_bounds, &snapshot, - breakpoint_lines, + breakpoint_rows, cx, ); @@ -6513,7 +6507,7 @@ mod tests { let style = cx.update(|cx| editor.read(cx).style().unwrap().clone()); let element = EditorElement::new(&editor, style); let snapshot = window.update(cx, |editor, cx| editor.snapshot(cx)).unwrap(); - let breakpoint_rows = HashSet::default(); + let breakpoint_rows = HashMap::default(); let layouts = cx .update_window(*window, |_, cx| { @@ -6523,7 +6517,7 @@ mod tests { &Default::default(), Some(DisplayPoint::new(DisplayRow(0), 0)), &snapshot, - breakpoint_rows, + &breakpoint_rows, cx, ) }) diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 7aa733ba8fa37..f4fb53e221a5d 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -3166,6 +3166,16 @@ impl MultiBufferSnapshot { self.anchor_at(position, Bias::Right) } + pub fn breakpoint_anchor(&self, position: T) -> Anchor { + let bias = if position.to_offset(self) == 0usize { + Bias::Right + } else { + Bias::Left + }; + + self.anchor_at(position, bias) + } + pub fn anchor_at(&self, position: T, mut bias: Bias) -> Anchor { let offset = position.to_offset(self); if let Some((excerpt_id, buffer_id, buffer)) = self.as_singleton() { diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index 3a07bd267e300..7e506ae547081 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -140,6 +140,20 @@ impl DapStore { &self.breakpoints } + pub fn breakpoint_at_row( + &self, + row: u32, + project_path: &ProjectPath, + buffer_snapshot: BufferSnapshot, + ) -> Option { + let breakpoint_set = self.breakpoints.get(project_path)?; + + breakpoint_set + .iter() + .find(|bp| bp.point_for_buffer_snapshot(&buffer_snapshot).row == row) + .cloned() + } + pub fn on_open_buffer( &mut self, project_path: &ProjectPath, @@ -763,16 +777,21 @@ impl DapStore { breakpoint: Breakpoint, buffer_path: PathBuf, buffer_snapshot: BufferSnapshot, + edit_action: BreakpointEditAction, cx: &mut ModelContext, ) { let breakpoint_set = self.breakpoints.entry(project_path.clone()).or_default(); - if let Some(gotten_breakpoint) = breakpoint_set.take(&breakpoint) { - if gotten_breakpoint.kind != breakpoint.kind { + match edit_action { + BreakpointEditAction::Toggle => { + if !breakpoint_set.remove(&breakpoint) { + breakpoint_set.insert(breakpoint); + } + } + BreakpointEditAction::EditLogMessage => { + breakpoint_set.remove(&breakpoint); breakpoint_set.insert(breakpoint); } - } else { - breakpoint_set.insert(breakpoint); } cx.notify(); @@ -855,7 +874,13 @@ impl DapStore { type LogMessage = Arc; -#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug)] +pub enum BreakpointEditAction { + Toggle, + EditLogMessage, +} + +#[derive(Clone, Debug)] pub enum BreakpointKind { Standard, Log(LogMessage), @@ -868,6 +893,27 @@ impl BreakpointKind { BreakpointKind::Log(_) => 1, } } + + pub fn log_message(&self) -> Option { + 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(&self, state: &mut H) { + std::mem::discriminant(self).hash(state); + } } #[derive(Clone, Debug)] @@ -883,7 +929,12 @@ pub struct Breakpoint { // overlapping breakpoint's with them being aware. impl PartialEq for Breakpoint { fn eq(&self, other: &Self) -> bool { - self.active_position == other.active_position && self.cache_position == other.cache_position + match (&self.active_position, &other.active_position) { + (None, None) => self.cache_position == other.cache_position, + (None, Some(_)) => false, + (Some(_), None) => false, + (Some(self_position), Some(other_position)) => self_position == other_position, + } } } @@ -891,8 +942,11 @@ impl Eq for Breakpoint {} impl Hash for Breakpoint { fn hash(&self, state: &mut H) { - self.active_position.hash(state); - self.cache_position.hash(state); + if self.active_position.is_some() { + self.active_position.hash(state); + } else { + self.cache_position.hash(state); + } } } @@ -915,13 +969,8 @@ impl Breakpoint { pub fn set_active_position(&mut self, buffer: &Buffer) { if self.active_position.is_none() { - let bias = if self.cache_position == 0 { - text::Bias::Right - } else { - text::Bias::Left - }; - - self.active_position = Some(buffer.anchor_at(Point::new(self.cache_position, 0), bias)); + self.active_position = + Some(buffer.breakpoint_anchor(Point::new(self.cache_position, 0))); } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9637f9023fe52..67ea6f2663acd 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -35,7 +35,7 @@ use dap::{ }; use collections::{BTreeSet, HashMap, HashSet}; -use dap_store::{Breakpoint, DapStore, DapStoreEvent, SerializedBreakpoint}; +use dap_store::{Breakpoint, BreakpointEditAction, DapStore, DapStoreEvent, SerializedBreakpoint}; use debounced_delay::DebouncedDelay; pub use environment::ProjectEnvironment; use futures::{ @@ -1244,6 +1244,7 @@ impl Project { &self, buffer_id: BufferId, breakpoint: Breakpoint, + edit_action: BreakpointEditAction, cx: &mut ModelContext, ) { let Some(buffer) = self.buffer_for_id(buffer_id, cx) else { @@ -1267,6 +1268,7 @@ impl Project { breakpoint, buffer_path, buffer.read(cx).snapshot(), + edit_action, cx, ); }); diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index a0865f1cdf6fb..3bc5889caeb70 100644 --- a/crates/text/src/anchor.rs +++ b/crates/text/src/anchor.rs @@ -17,14 +17,6 @@ pub struct Anchor { } impl Anchor { - pub fn offset_for_breakpoint_anchor(offset: usize) -> Self { - Anchor { - timestamp: clock::Lamport::new(0), - offset, - bias: Bias::Left, - buffer_id: None, - } - } pub const MIN: Self = Self { timestamp: clock::Lamport::MIN, offset: usize::MIN, diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index a5b65e6f4bcf1..efd27bdf82721 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2270,6 +2270,18 @@ impl BufferSnapshot { self.anchor_at_offset(position.to_offset(self), bias) } + pub fn breakpoint_anchor(&self, position: T) -> Anchor { + let offset = position.to_offset(self); + + let bias = if offset == 0usize { + Bias::Right + } else { + Bias::Left + }; + + self.anchor_at_offset(offset, bias) + } + fn anchor_at_offset(&self, offset: usize, bias: Bias) -> Anchor { if bias == Bias::Left && offset == 0 { Anchor::MIN