From ae3abf50d85f3480268dfa49b608e03d5dbf77bc Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 27 Oct 2025 09:26:39 +0100 Subject: [PATCH] editor: Fix panics in `CursorPosition::update_position` (#41237) Fixes a regression introduced in https://github.com/zed-industries/zed/pull/39857. As for the exact reason this causes this issue I am not yet sure will investigate (as per the todos in code) Fixes ZED-23R Release Notes: - N/A *or* Added/Fixed/Improved ... --- Cargo.lock | 1 - crates/editor/src/display_map.rs | 1 + crates/editor/src/display_map/block_map.rs | 7 + crates/editor/src/edit_prediction_tests.rs | 28 +++- crates/editor/src/editor.rs | 139 +++++++++++------- crates/editor/src/editor_tests.rs | 4 +- .../editor/src/highlight_matching_bracket.rs | 88 +++++------ crates/editor/src/selections_collection.rs | 47 +++--- crates/go_to_line/src/cursor_position.rs | 21 +-- crates/multi_buffer/src/multi_buffer.rs | 11 -- crates/rope/Cargo.toml | 1 - crates/rope/src/chunk.rs | 1 + crates/rope/src/rope.rs | 30 ++-- crates/sum_tree/src/sum_tree.rs | 2 +- 14 files changed, 205 insertions(+), 176 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d5fcbfa7ad4fa4db45af216c1d2d0a3ad52123c..a43717658477683e7a8a5c2409abbf15749938a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14226,7 +14226,6 @@ dependencies = [ "rand 0.9.2", "rayon", "regex", - "smallvec", "sum_tree", "unicode-segmentation", "util", diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f313535fca5ae40ab97d57c143dde85d278d6ac8..7a225d6019edf8f09b1758d62e8181917649cc2b 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1417,6 +1417,7 @@ impl std::ops::Deref for DisplaySnapshot { } } +/// A zero-indexed point in a text buffer consisting of a row and column adjusted for inserted blocks. #[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DisplayPoint(BlockPoint); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 4535e161392fd53e80ceb80fc736799ffafc84a7..99234899d3af7505c911355e34abe8f3fea3d0d2 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -69,6 +69,8 @@ impl From for ElementId { } } +/// A zero-indexed point in a text buffer consisting of a row and column +/// adjusted for inserted blocks, wrapped rows, tabs, folds and inlays. #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct BlockPoint(pub Point); @@ -80,11 +82,16 @@ struct WrapRow(u32); pub type RenderBlock = Arc AnyElement>; +/// Where to place a block. #[derive(Clone, Debug, Eq, PartialEq)] pub enum BlockPlacement { + /// Place the block above the given position. Above(T), + /// Place the block below the given position. Below(T), + /// Place the block next the given position. Near(T), + /// Replace the given range of positions with the block. Replace(RangeInclusive), } diff --git a/crates/editor/src/edit_prediction_tests.rs b/crates/editor/src/edit_prediction_tests.rs index 7d64dd9749c68cb0e436c1cfcb04e3458d052872..e43f18a10669562d2b6880bdd4c46298e355af10 100644 --- a/crates/editor/src/edit_prediction_tests.rs +++ b/crates/editor/src/edit_prediction_tests.rs @@ -19,7 +19,9 @@ async fn test_edit_prediction_insert(cx: &mut gpui::TestAppContext) { cx.set_state("let absolute_zero_celsius = ˇ;"); propose_edits(&provider, vec![(28..28, "-273.15")], &mut cx); - cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); + cx.update_editor(|editor, window, cx| { + editor.update_visible_edit_prediction(&editor.display_snapshot(cx), window, cx) + }); assert_editor_active_edit_completion(&mut cx, |_, edits| { assert_eq!(edits.len(), 1); @@ -41,7 +43,9 @@ async fn test_edit_prediction_modification(cx: &mut gpui::TestAppContext) { cx.set_state("let pi = ˇ\"foo\";"); propose_edits(&provider, vec![(9..14, "3.14159")], &mut cx); - cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); + cx.update_editor(|editor, window, cx| { + editor.update_visible_edit_prediction(&editor.display_snapshot(cx), window, cx) + }); assert_editor_active_edit_completion(&mut cx, |_, edits| { assert_eq!(edits.len(), 1); @@ -76,7 +80,9 @@ async fn test_edit_prediction_jump_button(cx: &mut gpui::TestAppContext) { &mut cx, ); - cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); + cx.update_editor(|editor, window, cx| { + editor.update_visible_edit_prediction(&editor.display_snapshot(cx), window, cx) + }); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), Point::new(4, 3)); }); @@ -106,7 +112,9 @@ async fn test_edit_prediction_jump_button(cx: &mut gpui::TestAppContext) { &mut cx, ); - cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); + cx.update_editor(|editor, window, cx| { + editor.update_visible_edit_prediction(&editor.display_snapshot(cx), window, cx) + }); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), Point::new(1, 3)); }); @@ -147,7 +155,9 @@ async fn test_edit_prediction_invalidation_range(cx: &mut gpui::TestAppContext) &mut cx, ); - cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); + cx.update_editor(|editor, window, cx| { + editor.update_visible_edit_prediction(&editor.display_snapshot(cx), window, cx) + }); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), edit_location); }); @@ -195,7 +205,9 @@ async fn test_edit_prediction_invalidation_range(cx: &mut gpui::TestAppContext) &mut cx, ); - cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); + cx.update_editor(|editor, window, cx| { + editor.update_visible_edit_prediction(&editor.display_snapshot(cx), window, cx) + }); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), edit_location); }); @@ -250,7 +262,9 @@ async fn test_edit_prediction_jump_disabled_for_non_zed_providers(cx: &mut gpui: &mut cx, ); - cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); + cx.update_editor(|editor, window, cx| { + editor.update_visible_edit_prediction(&editor.display_snapshot(cx), window, cx) + }); // For non-Zed providers, there should be no move completion (jump functionality disabled) cx.editor(|editor, _, _| { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ae833d48fe56160eae5b12c5eb2960e9a542ba9f..5ab4d3b6125954ed2646b3d04b89a532a06a99e5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -108,7 +108,6 @@ use gpui::{ UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative, size, }; -use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_links::{HoverLink, HoveredLinkState, find_file}; use hover_popover::{HoverState, hide_hover}; use indent_guides::ActiveIndentGuidesState; @@ -163,7 +162,7 @@ use rand::seq::SliceRandom; use rpc::{ErrorCode, ErrorExt, proto::PeerId}; use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager}; use selections_collection::{ - MutableSelectionsCollection, SelectionsCollection, resolve_selections, + MutableSelectionsCollection, SelectionsCollection, resolve_selections_wrapping_blocks, }; use serde::{Deserialize, Serialize}; use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file}; @@ -2821,7 +2820,7 @@ impl Editor { self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider { _subscription: cx.observe_in(&provider, window, |this, _, window, cx| { if this.focus_handle.is_focused(window) { - this.update_visible_edit_prediction(window, cx); + this.update_visible_edit_prediction(&this.display_snapshot(cx), window, cx); } }), provider: Arc::new(provider), @@ -2910,7 +2909,7 @@ impl Editor { if hidden != self.edit_predictions_hidden_for_vim_mode { self.edit_predictions_hidden_for_vim_mode = hidden; if hidden { - self.update_visible_edit_prediction(window, cx); + self.update_visible_edit_prediction(&self.display_snapshot(cx), window, cx); } else { self.refresh_edit_prediction(true, false, window, cx); } @@ -3135,9 +3134,9 @@ impl Editor { self.refresh_document_highlights(cx); refresh_linked_ranges(self, window, cx); - self.refresh_selected_text_highlights(false, window, cx); - refresh_matching_bracket_highlights(self, cx); - self.update_visible_edit_prediction(window, cx); + self.refresh_selected_text_highlights(false, &display_map, window, cx); + self.refresh_matching_bracket_highlights(&display_map, cx); + self.update_visible_edit_prediction(&display_map, window, cx); self.edit_prediction_requires_modifier_in_indent_conflict = true; self.inline_blame_popover.take(); if self.git_blame_inline_enabled { @@ -4320,16 +4319,17 @@ impl Editor { let new_anchor_selections = new_selections.iter().map(|e| &e.0); let new_selection_deltas = new_selections.iter().map(|e| e.1); let map = this.display_map.update(cx, |map, cx| map.snapshot(cx)); - let new_selections = resolve_selections::(new_anchor_selections, &map) - .zip(new_selection_deltas) - .map(|(selection, delta)| Selection { - id: selection.id, - start: selection.start + delta, - end: selection.end + delta, - reversed: selection.reversed, - goal: SelectionGoal::None, - }) - .collect::>(); + let new_selections = + resolve_selections_wrapping_blocks::(new_anchor_selections, &map) + .zip(new_selection_deltas) + .map(|(selection, delta)| Selection { + id: selection.id, + start: selection.start + delta, + end: selection.end + delta, + reversed: selection.reversed, + goal: SelectionGoal::None, + }) + .collect::>(); let mut i = 0; for (position, delta, selection_id, pair) in new_autoclose_regions { @@ -5651,7 +5651,11 @@ impl Editor { crate::hover_popover::hide_hover(editor, cx); if editor.show_edit_predictions_in_menu() { - editor.update_visible_edit_prediction(window, cx); + editor.update_visible_edit_prediction( + &editor.display_snapshot(cx), + window, + cx, + ); } else { editor.discard_edit_prediction(false, cx); } @@ -5666,7 +5670,11 @@ impl Editor { // If it was already hidden and we don't show edit predictions in the menu, // we should also show the edit prediction when available. if was_hidden && editor.show_edit_predictions_in_menu() { - editor.update_visible_edit_prediction(window, cx); + editor.update_visible_edit_prediction( + &editor.display_snapshot(cx), + window, + cx, + ); } } }) @@ -6730,6 +6738,7 @@ impl Editor { fn prepare_highlight_query_from_selection( &mut self, + window: &Window, cx: &mut Context, ) -> Option<(String, Range)> { if matches!(self.mode, EditorMode::SingleLine) { @@ -6741,24 +6750,23 @@ impl Editor { if self.selections.count() != 1 || self.selections.line_mode() { return None; } - let selection = self.selections.newest_anchor(); - let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); - let selection_point_range = selection.start.to_point(&multi_buffer_snapshot) - ..selection.end.to_point(&multi_buffer_snapshot); + let snapshot = self.snapshot(window, cx); + let selection = self.selections.newest::(&snapshot); // If the selection spans multiple rows OR it is empty - if selection_point_range.start.row != selection_point_range.end.row - || selection_point_range.start.column == selection_point_range.end.column + if selection.start.row != selection.end.row + || selection.start.column == selection.end.column { return None; } - - let query = multi_buffer_snapshot - .text_for_range(selection.range()) + let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot()); + let query = snapshot + .buffer_snapshot() + .text_for_range(selection_anchor_range.clone()) .collect::(); if query.trim().is_empty() { return None; } - Some((query, selection.range())) + Some((query, selection_anchor_range)) } fn update_selection_occurrence_highlights( @@ -6834,32 +6842,36 @@ impl Editor { }) } - fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context) { + fn refresh_single_line_folds( + &mut self, + display_snapshot: &DisplaySnapshot, + cx: &mut Context, + ) { struct NewlineFold; let type_id = std::any::TypeId::of::(); if !self.mode.is_single_line() { return; } - let snapshot = self.snapshot(window, cx); - if snapshot.buffer_snapshot().max_point().row == 0 { + let display_snapshot = display_snapshot.clone(); + if display_snapshot.buffer_snapshot().max_point().row == 0 { return; } let task = cx.background_spawn(async move { - let new_newlines = snapshot + let new_newlines = display_snapshot .buffer_chars_at(0) .filter_map(|(c, i)| { if c == '\n' { Some( - snapshot.buffer_snapshot().anchor_after(i) - ..snapshot.buffer_snapshot().anchor_before(i + 1), + display_snapshot.buffer_snapshot().anchor_after(i) + ..display_snapshot.buffer_snapshot().anchor_before(i + 1), ) } else { None } }) .collect::>(); - let existing_newlines = snapshot - .folds_in_range(0..snapshot.buffer_snapshot().len()) + let existing_newlines = display_snapshot + .folds_in_range(0..display_snapshot.buffer_snapshot().len()) .filter_map(|fold| { if fold.placeholder.type_tag == Some(type_id) { Some(fold.range.start..fold.range.end) @@ -6908,17 +6920,19 @@ impl Editor { fn refresh_selected_text_highlights( &mut self, on_buffer_edit: bool, + display_snapshot: &DisplaySnapshot, window: &mut Window, cx: &mut Context, ) { - let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx) + let Some((query_text, query_range)) = + self.prepare_highlight_query_from_selection(window, cx) else { self.clear_background_highlights::(cx); self.quick_selection_highlight_task.take(); self.debounced_selection_highlight_task.take(); return; }; - let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); + let multi_buffer_snapshot = display_snapshot.buffer_snapshot(); if on_buffer_edit || self .quick_selection_highlight_task @@ -6996,7 +7010,7 @@ impl Editor { return None; } - self.update_visible_edit_prediction(window, cx); + self.update_visible_edit_prediction(&self.display_snapshot(cx), window, cx); if !user_requested && (!self.should_show_edit_predictions() @@ -7158,7 +7172,7 @@ impl Editor { } provider.cycle(buffer, cursor_buffer_position, direction, cx); - self.update_visible_edit_prediction(window, cx); + self.update_visible_edit_prediction(&self.display_snapshot(cx), window, cx); Some(()) } @@ -7174,7 +7188,7 @@ impl Editor { return; } - self.update_visible_edit_prediction(window, cx); + self.update_visible_edit_prediction(&self.display_snapshot(cx), window, cx); } pub fn display_cursor_names( @@ -7313,8 +7327,13 @@ impl Editor { // Store the transaction ID and selections before applying the edit let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx); - let snapshot = self.buffer.read(cx).snapshot(cx); - let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot); + let snapshot = self.display_snapshot(cx); + let last_edit_end = edits + .last() + .unwrap() + .0 + .end + .bias_right(snapshot.buffer_snapshot()); self.buffer.update(cx, |buffer, cx| { buffer.edit(edits.iter().cloned(), None, cx) @@ -7333,7 +7352,7 @@ impl Editor { } } - self.update_visible_edit_prediction(window, cx); + self.update_visible_edit_prediction(&snapshot, window, cx); if self.active_edit_prediction.is_none() { self.refresh_edit_prediction(true, true, window, cx); } @@ -7666,7 +7685,7 @@ impl Editor { since: Instant::now(), }; - self.update_visible_edit_prediction(window, cx); + self.update_visible_edit_prediction(&self.display_snapshot(cx), window, cx); cx.notify(); } } else if let EditPredictionPreview::Active { @@ -7689,13 +7708,14 @@ impl Editor { released_too_fast: since.elapsed() < Duration::from_millis(200), }; self.clear_row_highlights::(); - self.update_visible_edit_prediction(window, cx); + self.update_visible_edit_prediction(&self.display_snapshot(cx), window, cx); cx.notify(); } } fn update_visible_edit_prediction( &mut self, + display_snapshot: &DisplaySnapshot, _window: &mut Window, cx: &mut Context, ) -> Option<()> { @@ -7710,7 +7730,7 @@ impl Editor { let selection = self.selections.newest_anchor(); let cursor = selection.head(); - let multibuffer = self.buffer.read(cx).snapshot(cx); + let multibuffer = display_snapshot.buffer_snapshot(); let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer)); let excerpt_id = cursor.excerpt_id; @@ -9542,7 +9562,7 @@ impl Editor { self.completion_tasks.clear(); let context_menu = self.context_menu.borrow_mut().take(); self.stale_edit_prediction_in_menu.take(); - self.update_visible_edit_prediction(window, cx); + self.update_visible_edit_prediction(&self.display_snapshot(cx), window, cx); if let Some(CodeContextMenu::Completions(_)) = &context_menu && let Some(completion_provider) = &self.completion_provider { @@ -17466,13 +17486,17 @@ impl Editor { window.show_character_palette(); } - fn refresh_active_diagnostics(&mut self, cx: &mut Context) { + fn refresh_active_diagnostics( + &mut self, + display_snapshot: &DisplaySnapshot, + cx: &mut Context, + ) { if !self.diagnostics_enabled() { return; } if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics { - let buffer = self.buffer.read(cx).snapshot(cx); + let buffer = display_snapshot.buffer_snapshot(); let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer); let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer); let is_valid = buffer @@ -20762,15 +20786,16 @@ impl Editor { ) { match event { multi_buffer::Event::Edited { edited_buffer } => { + let display_snapshot = self.display_snapshot(cx); self.scrollbar_marker_state.dirty = true; self.active_indent_guides_state.dirty = true; - self.refresh_active_diagnostics(cx); + self.refresh_active_diagnostics(&display_snapshot, cx); self.refresh_code_actions(window, cx); - self.refresh_selected_text_highlights(true, window, cx); - self.refresh_single_line_folds(window, cx); - refresh_matching_bracket_highlights(self, cx); + self.refresh_selected_text_highlights(true, &display_snapshot, window, cx); + self.refresh_single_line_folds(&display_snapshot, cx); + self.refresh_matching_bracket_highlights(&display_snapshot, cx); if self.has_active_edit_prediction() { - self.update_visible_edit_prediction(window, cx); + self.update_visible_edit_prediction(&display_snapshot, window, cx); } if let Some(buffer) = edited_buffer { @@ -20892,7 +20917,7 @@ impl Editor { if !self.diagnostics_enabled() { return; } - self.refresh_active_diagnostics(cx); + self.refresh_active_diagnostics(&self.display_snapshot(cx), cx); self.refresh_inline_diagnostics(true, window, cx); self.scrollbar_marker_state.dirty = true; cx.notify(); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index a319ad654d016204dbad748d0aa169dee545a44f..b8f7825b2a46e99e147826b9b314a5d470316642 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -8534,7 +8534,9 @@ async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) }) }); - cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); + cx.update_editor(|editor, window, cx| { + editor.update_visible_edit_prediction(&editor.display_snapshot(cx), window, cx) + }); cx.update_editor(|editor, window, cx| { editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx) }); diff --git a/crates/editor/src/highlight_matching_bracket.rs b/crates/editor/src/highlight_matching_bracket.rs index da0f847fe10b7365ac3f4686d183a5d83d17e73a..eee26b9c796dafd2bc565e883c86e719f0d1b344 100644 --- a/crates/editor/src/highlight_matching_bracket.rs +++ b/crates/editor/src/highlight_matching_bracket.rs @@ -1,57 +1,59 @@ -use crate::{Editor, RangeToAnchorExt}; +use crate::{Editor, RangeToAnchorExt, display_map::DisplaySnapshot}; use gpui::{Context, HighlightStyle}; use language::CursorShape; -use multi_buffer::ToOffset; use theme::ActiveTheme; enum MatchingBracketHighlight {} -pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut Context) { - editor.clear_highlights::(cx); +impl Editor { + pub fn refresh_matching_bracket_highlights( + &mut self, + snapshot: &DisplaySnapshot, + cx: &mut Context, + ) { + self.clear_highlights::(cx); - let buffer_snapshot = editor.buffer.read(cx).snapshot(cx); - let newest_selection = editor - .selections - .newest_anchor() - .map(|anchor| anchor.to_offset(&buffer_snapshot)); - // Don't highlight brackets if the selection isn't empty - if !newest_selection.is_empty() { - return; - } + let buffer_snapshot = snapshot.buffer_snapshot(); + let newest_selection = self.selections.newest::(snapshot); + // Don't highlight brackets if the selection isn't empty + if !newest_selection.is_empty() { + return; + } - let head = newest_selection.head(); - if head > buffer_snapshot.len() { - log::error!("bug: cursor offset is out of range while refreshing bracket highlights"); - return; - } + let head = newest_selection.head(); + if head > buffer_snapshot.len() { + log::error!("bug: cursor offset is out of range while refreshing bracket highlights"); + return; + } - let mut tail = head; - if (editor.cursor_shape == CursorShape::Block || editor.cursor_shape == CursorShape::Hollow) - && head < buffer_snapshot.len() - { - if let Some(tail_ch) = buffer_snapshot.chars_at(tail).next() { - tail += tail_ch.len_utf8(); + let mut tail = head; + if (self.cursor_shape == CursorShape::Block || self.cursor_shape == CursorShape::Hollow) + && head < buffer_snapshot.len() + { + if let Some(tail_ch) = buffer_snapshot.chars_at(tail).next() { + tail += tail_ch.len_utf8(); + } } - } - if let Some((opening_range, closing_range)) = - buffer_snapshot.innermost_enclosing_bracket_ranges(head..tail, None) - { - editor.highlight_text::( - vec![ - opening_range.to_anchors(&buffer_snapshot), - closing_range.to_anchors(&buffer_snapshot), - ], - HighlightStyle { - background_color: Some( - cx.theme() - .colors() - .editor_document_highlight_bracket_background, - ), - ..Default::default() - }, - cx, - ) + if let Some((opening_range, closing_range)) = + buffer_snapshot.innermost_enclosing_bracket_ranges(head..tail, None) + { + self.highlight_text::( + vec![ + opening_range.to_anchors(&buffer_snapshot), + closing_range.to_anchors(&buffer_snapshot), + ], + HighlightStyle { + background_color: Some( + cx.theme() + .colors() + .editor_document_highlight_bracket_background, + ), + ..Default::default() + }, + cx, + ) + } } } diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index ab0e78595310da43b803cd53b9177dec53a37d81..72a0fcf045955805efaa5f8dea7e6ef78525d26c 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -131,7 +131,7 @@ impl SelectionsCollection { &self, snapshot: &DisplaySnapshot, ) -> Option> { - resolve_selections(self.pending_anchor(), &snapshot).next() + resolve_selections_wrapping_blocks(self.pending_anchor(), &snapshot).next() } pub(crate) fn pending_mode(&self) -> Option { @@ -144,7 +144,8 @@ impl SelectionsCollection { { let disjoint_anchors = &self.disjoint; let mut disjoint = - resolve_selections::(disjoint_anchors.iter(), &snapshot).peekable(); + resolve_selections_wrapping_blocks::(disjoint_anchors.iter(), &snapshot) + .peekable(); let mut pending_opt = self.pending::(&snapshot); iter::from_fn(move || { if let Some(pending) = pending_opt.as_mut() { @@ -185,27 +186,6 @@ impl SelectionsCollection { selections } - /// Returns all of the selections, adjusted to take into account the selection line_mode. Uses a provided snapshot to resolve selections. - pub fn all_adjusted_with_snapshot( - &self, - snapshot: &MultiBufferSnapshot, - ) -> Vec> { - let mut selections = self - .disjoint - .iter() - .chain(self.pending_anchor()) - .map(|anchor| anchor.map(|anchor| anchor.to_point(&snapshot))) - .collect::>(); - if self.line_mode { - for selection in &mut selections { - let new_range = snapshot.expand_to_line(selection.range()); - selection.start = new_range.start; - selection.end = new_range.end; - } - } - selections - } - /// Returns the newest selection, adjusted to take into account the selection line_mode pub fn newest_adjusted(&self, snapshot: &DisplaySnapshot) -> Selection { let mut selection = self.newest::(&snapshot); @@ -259,7 +239,7 @@ impl SelectionsCollection { Ok(ix) => ix + 1, Err(ix) => ix, }; - resolve_selections(&self.disjoint[start_ix..end_ix], snapshot).collect() + resolve_selections_wrapping_blocks(&self.disjoint[start_ix..end_ix], snapshot).collect() } pub fn all_display(&self, snapshot: &DisplaySnapshot) -> Vec> { @@ -305,7 +285,7 @@ impl SelectionsCollection { &self, snapshot: &DisplaySnapshot, ) -> Selection { - resolve_selections([self.newest_anchor()], &snapshot) + resolve_selections_wrapping_blocks([self.newest_anchor()], &snapshot) .next() .unwrap() } @@ -328,7 +308,7 @@ impl SelectionsCollection { &self, snapshot: &DisplaySnapshot, ) -> Selection { - resolve_selections([self.oldest_anchor()], &snapshot) + resolve_selections_wrapping_blocks([self.oldest_anchor()], &snapshot) .next() .unwrap() } @@ -658,7 +638,7 @@ impl<'a> MutableSelectionsCollection<'a> { pub fn select_anchors(&mut self, selections: Vec>) { let map = self.display_map(); let resolved_selections = - resolve_selections::(&selections, &map).collect::>(); + resolve_selections_wrapping_blocks::(&selections, &map).collect::>(); self.select(resolved_selections); } @@ -940,7 +920,8 @@ impl<'a> MutableSelectionsCollection<'a> { if !adjusted_disjoint.is_empty() { let map = self.display_map(); - let resolved_selections = resolve_selections(adjusted_disjoint.iter(), &map).collect(); + let resolved_selections = + resolve_selections_wrapping_blocks(adjusted_disjoint.iter(), &map).collect(); self.select::(resolved_selections); } @@ -1025,6 +1006,7 @@ fn resolve_selections_point<'a>( } /// Panics if passed selections are not in order +/// Resolves the anchors to display positions fn resolve_selections_display<'a>( selections: impl 'a + IntoIterator>, map: &'a DisplaySnapshot, @@ -1056,8 +1038,13 @@ fn resolve_selections_display<'a>( coalesce_selections(selections) } +/// Resolves the passed in anchors to [`TextDimension`]s `D` +/// wrapping around blocks inbetween. +/// +/// # Panics +/// /// Panics if passed selections are not in order -pub(crate) fn resolve_selections<'a, D, I>( +pub(crate) fn resolve_selections_wrapping_blocks<'a, D, I>( selections: I, map: &'a DisplaySnapshot, ) -> impl 'a + Iterator> @@ -1065,6 +1052,8 @@ where D: TextDimension + Ord + Sub, I: 'a + IntoIterator>, { + // Transforms `Anchor -> DisplayPoint -> Point -> DisplayPoint -> D` + // todo(lw): We should be able to short circuit the `Anchor -> DisplayPoint -> Point` to `Anchor -> Point` let (to_convert, selections) = resolve_selections_display(selections, map).tee(); let mut converted_endpoints = map.buffer_snapshot() diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 5c10537e2869e0ca51e3178598f55c1589ceacd7..2638a49eba5d1c69a41a759efedfe4814ed6dc2c 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -111,15 +111,14 @@ impl CursorPosition { } editor::EditorMode::Full { .. } => { let mut last_selection = None::>; - let snapshot = editor.buffer().read(cx).snapshot(cx); - if snapshot.excerpts().count() > 0 { - for selection in - editor.selections.all_adjusted_with_snapshot(&snapshot) - { + let snapshot = editor.display_snapshot(cx); + if snapshot.buffer_snapshot().excerpts().count() > 0 { + for selection in editor.selections.all_adjusted(&snapshot) { let selection_summary = snapshot + .buffer_snapshot() .text_summary_for_range::( - selection.start..selection.end, - ); + selection.start..selection.end, + ); cursor_position.selected_count.characters += selection_summary.chars; if selection.end != selection.start { @@ -136,8 +135,12 @@ impl CursorPosition { } } } - cursor_position.position = last_selection - .map(|s| UserCaretPosition::at_selection_end(&s, &snapshot)); + cursor_position.position = last_selection.map(|s| { + UserCaretPosition::at_selection_end( + &s, + snapshot.buffer_snapshot(), + ) + }); cursor_position.context = Some(editor.focus_handle(cx)); } } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 0163a49c95eeea5372a61824d2754a233ec07740..4cd112d231fb340b67a712f235cccddd067234b3 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -5742,17 +5742,6 @@ impl MultiBufferSnapshot { debug_ranges.insert(key, text_ranges, format!("{value:?}").into()) }); } - - // used by line_mode selections and tries to match vim behavior - pub fn expand_to_line(&self, range: Range) -> Range { - let new_start = MultiBufferPoint::new(range.start.row, 0); - let new_end = if range.end.column > 0 { - MultiBufferPoint::new(range.end.row, self.line_len(MultiBufferRow(range.end.row))) - } else { - range.end - }; - new_start..new_end - } } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index f099248a5db49ac1e857900b7d00294a11cfbff2..f38d87fbdad116d8ec22db6668b20fd433c53716 100644 --- a/crates/rope/Cargo.toml +++ b/crates/rope/Cargo.toml @@ -16,7 +16,6 @@ arrayvec = "0.7.1" log.workspace = true rayon.workspace = true regex.workspace = true -smallvec.workspace = true sum_tree.workspace = true unicode-segmentation.workspace = true util.workspace = true diff --git a/crates/rope/src/chunk.rs b/crates/rope/src/chunk.rs index 2fa6112dd439a5835891db813dc9ce12cb22809d..6e17c35d7c770c429fa32725a38bca94a9e1dfc2 100644 --- a/crates/rope/src/chunk.rs +++ b/crates/rope/src/chunk.rs @@ -131,6 +131,7 @@ impl Chunk { #[cold] #[inline(never)] + #[track_caller] fn panic_char_boundary(chunk: &Chunk, offset: usize) { if offset > chunk.text.len() { panic!( diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index b5c5cd069e07a0957b01130eec2b4ecdf7f7120e..f8e06d23c245643f9e8c27e4433779e067a7ce5d 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -4,9 +4,9 @@ mod point; mod point_utf16; mod unclipped; +use arrayvec::ArrayVec; use rayon::iter::{IntoParallelIterator, ParallelIterator as _}; use regex::Regex; -use smallvec::SmallVec; use std::{ borrow::Cow, cmp, fmt, io, mem, @@ -283,10 +283,19 @@ impl Rope { (), ); - if text.len() > 2048 { + #[cfg(not(test))] + const NUM_CHUNKS: usize = 16; + #[cfg(test)] + const NUM_CHUNKS: usize = 4; + + // We accommodate for NUM_CHUNKS chunks of size MAX_BASE + // but given the chunk boundary can land within a character + // we need to accommodate for the worst case where every chunk gets cut short by up to 4 bytes + if text.len() > NUM_CHUNKS * chunk::MAX_BASE - NUM_CHUNKS * 4 { return self.push_large(text); } - let mut new_chunks = SmallVec::<[_; 16]>::new(); + // 16 is enough as otherwise we will hit the branch above + let mut new_chunks = ArrayVec::<_, NUM_CHUNKS>::new(); while !text.is_empty() { let mut split_ix = cmp::min(chunk::MAX_BASE, text.len()); @@ -297,19 +306,8 @@ impl Rope { new_chunks.push(chunk); text = remainder; } - - #[cfg(test)] - const PARALLEL_THRESHOLD: usize = 4; - #[cfg(not(test))] - const PARALLEL_THRESHOLD: usize = 4 * (2 * sum_tree::TREE_BASE); - - if new_chunks.len() >= PARALLEL_THRESHOLD { - self.chunks - .par_extend(new_chunks.into_vec().into_par_iter().map(Chunk::new), ()); - } else { - self.chunks - .extend(new_chunks.into_iter().map(Chunk::new), ()); - } + self.chunks + .extend(new_chunks.into_iter().map(Chunk::new), ()); self.check_invariants(); } diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index ab0e9d03c4594b89159893c7a671e8a9e3928b3f..95fbd5ed0d5f5700d0c894cda68ed15ce6590ced 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -3,7 +3,7 @@ mod tree_map; use arrayvec::ArrayVec; pub use cursor::{Cursor, FilterCursor, Iter}; -use rayon::prelude::*; +use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator as _}; use std::marker::PhantomData; use std::mem; use std::{cmp::Ordering, fmt, iter::FromIterator, sync::Arc};