Cargo.lock 🔗
@@ -14156,7 +14156,6 @@ dependencies = [
"log",
"rand 0.9.2",
"rayon",
- "smallvec",
"sum_tree",
"unicode-segmentation",
"util",
Lukas Wirth created
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 | 9 +
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
crates/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 | 65 ++++++++
crates/rope/src/rope.rs | 30 +--
crates/sum_tree/src/sum_tree.rs | 2
14 files changed, 277 insertions(+), 176 deletions(-)
@@ -14156,7 +14156,6 @@ dependencies = [
"log",
"rand 0.9.2",
"rayon",
- "smallvec",
"sum_tree",
"unicode-segmentation",
"util",
@@ -1427,6 +1427,15 @@ impl DisplaySnapshot {
}
}
+impl std::ops::Deref for DisplaySnapshot {
+ type Target = BlockSnapshot;
+
+ fn deref(&self) -> &Self::Target {
+ &self.block_snapshot
+ }
+}
+
+/// 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);
@@ -69,6 +69,8 @@ impl From<CustomBlockId> 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<dyn Send + Sync + Fn(&mut BlockContext) -> AnyElement>;
+/// Where to place a block.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BlockPlacement<T> {
+ /// 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<T>),
}
@@ -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, _, _| {
@@ -111,7 +111,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, InlayHighlight, find_file};
use hover_popover::{HoverState, hide_hover};
use indent_guides::ActiveIndentGuidesState;
@@ -164,7 +163,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};
@@ -2874,7 +2873,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),
@@ -2963,7 +2962,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);
}
@@ -3188,9 +3187,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 {
@@ -4373,16 +4372,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::<usize, _>(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::<Vec<_>>();
+ let new_selections =
+ resolve_selections_wrapping_blocks::<usize, _>(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::<Vec<_>>();
let mut i = 0;
for (position, delta, selection_id, pair) in new_autoclose_regions {
@@ -5894,7 +5894,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);
}
@@ -5909,7 +5913,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,
+ );
}
}
})
@@ -6974,6 +6982,7 @@ impl Editor {
fn prepare_highlight_query_from_selection(
&mut self,
+ window: &Window,
cx: &mut Context<Editor>,
) -> Option<(String, Range<Anchor>)> {
if matches!(self.mode, EditorMode::SingleLine) {
@@ -6985,24 +6994,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::<Point>(&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::<String>();
if query.trim().is_empty() {
return None;
}
- Some((query, selection.range()))
+ Some((query, selection_anchor_range))
}
fn update_selection_occurrence_highlights(
@@ -7078,32 +7086,36 @@ impl Editor {
})
}
- fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
+ fn refresh_single_line_folds(
+ &mut self,
+ display_snapshot: &DisplaySnapshot,
+ cx: &mut Context<Editor>,
+ ) {
struct NewlineFold;
let type_id = std::any::TypeId::of::<NewlineFold>();
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::<Vec<_>>();
- 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)
@@ -7152,17 +7164,19 @@ impl Editor {
fn refresh_selected_text_highlights(
&mut self,
on_buffer_edit: bool,
+ display_snapshot: &DisplaySnapshot,
window: &mut Window,
cx: &mut Context<Editor>,
) {
- 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::<SelectedTextHighlight>(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
@@ -7240,7 +7254,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()
@@ -7402,7 +7416,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(())
}
@@ -7418,7 +7432,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(
@@ -7557,8 +7571,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)
@@ -7577,7 +7596,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);
}
@@ -7910,7 +7929,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 {
@@ -7933,13 +7952,14 @@ impl Editor {
released_too_fast: since.elapsed() < Duration::from_millis(200),
};
self.clear_row_highlights::<EditPredictionPreview>();
- 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<Self>,
) -> Option<()> {
@@ -7954,7 +7974,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;
@@ -9787,7 +9807,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
{
@@ -17651,13 +17671,17 @@ impl Editor {
window.show_character_palette();
}
- fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
+ fn refresh_active_diagnostics(
+ &mut self,
+ display_snapshot: &DisplaySnapshot,
+ cx: &mut Context<Editor>,
+ ) {
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
@@ -20959,15 +20983,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(edited_buffer) = edited_buffer {
@@ -21105,7 +21130,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();
@@ -8533,7 +8533,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)
});
@@ -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>) {
- editor.clear_highlights::<MatchingBracketHighlight>(cx);
+impl Editor {
+ pub fn refresh_matching_bracket_highlights(
+ &mut self,
+ snapshot: &DisplaySnapshot,
+ cx: &mut Context<Editor>,
+ ) {
+ self.clear_highlights::<MatchingBracketHighlight>(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::<usize>(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::<MatchingBracketHighlight>(
- 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::<MatchingBracketHighlight>(
+ 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,
+ )
+ }
}
}
@@ -131,7 +131,7 @@ impl SelectionsCollection {
&self,
snapshot: &DisplaySnapshot,
) -> Option<Selection<D>> {
- resolve_selections(self.pending_anchor(), &snapshot).next()
+ resolve_selections_wrapping_blocks(self.pending_anchor(), &snapshot).next()
}
pub(crate) fn pending_mode(&self) -> Option<SelectMode> {
@@ -144,7 +144,8 @@ impl SelectionsCollection {
{
let disjoint_anchors = &self.disjoint;
let mut disjoint =
- resolve_selections::<D, _>(disjoint_anchors.iter(), &snapshot).peekable();
+ resolve_selections_wrapping_blocks::<D, _>(disjoint_anchors.iter(), &snapshot)
+ .peekable();
let mut pending_opt = self.pending::<D>(&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<Selection<Point>> {
- let mut selections = self
- .disjoint
- .iter()
- .chain(self.pending_anchor())
- .map(|anchor| anchor.map(|anchor| anchor.to_point(&snapshot)))
- .collect::<Vec<_>>();
- 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<Point> {
let mut selection = self.newest::<Point>(&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<Selection<DisplayPoint>> {
@@ -305,7 +285,7 @@ impl SelectionsCollection {
&self,
snapshot: &DisplaySnapshot,
) -> Selection<D> {
- 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<D> {
- 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<Selection<Anchor>>) {
let map = self.display_map();
let resolved_selections =
- resolve_selections::<usize, _>(&selections, &map).collect::<Vec<_>>();
+ resolve_selections_wrapping_blocks::<usize, _>(&selections, &map).collect::<Vec<_>>();
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::<usize>(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<Item = &'a Selection<Anchor>>,
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<Item = Selection<D>>
@@ -1065,6 +1052,8 @@ where
D: TextDimension + Ord + Sub<D, Output = D>,
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
{
+ // 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()
@@ -111,15 +111,14 @@ impl CursorPosition {
}
editor::EditorMode::Full { .. } => {
let mut last_selection = None::<Selection<Point>>;
- 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::<text::TextSummary, _>(
- 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));
}
}
@@ -6389,17 +6389,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<Point>) -> Range<Point> {
- 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"))]
@@ -15,7 +15,6 @@ path = "src/rope.rs"
arrayvec = "0.7.1"
log.workspace = true
rayon.workspace = true
-smallvec.workspace = true
sum_tree.workspace = true
unicode-segmentation.workspace = true
util.workspace = true
@@ -93,6 +93,71 @@ impl Chunk {
pub fn tabs(&self) -> Bitmap {
self.tabs
}
+
+ #[inline(always)]
+ pub fn is_char_boundary(&self, offset: usize) -> bool {
+ (1 as Bitmap).unbounded_shl(offset as u32) & self.chars != 0 || offset == self.text.len()
+ }
+
+ pub fn floor_char_boundary(&self, index: usize) -> usize {
+ #[inline]
+ pub(crate) const fn is_utf8_char_boundary(u8: u8) -> bool {
+ // This is bit magic equivalent to: b < 128 || b >= 192
+ (u8 as i8) >= -0x40
+ }
+
+ if index >= self.text.len() {
+ self.text.len()
+ } else {
+ let mut i = index;
+ while i > 0 {
+ if is_utf8_char_boundary(self.text.as_bytes()[i]) {
+ break;
+ }
+ i -= 1;
+ }
+
+ i
+ }
+ }
+
+ #[track_caller]
+ #[inline(always)]
+ pub fn assert_char_boundary(&self, offset: usize) {
+ if self.is_char_boundary(offset) {
+ return;
+ }
+ panic_char_boundary(self, offset);
+
+ #[cold]
+ #[inline(never)]
+ #[track_caller]
+ fn panic_char_boundary(chunk: &Chunk, offset: usize) {
+ if offset > chunk.text.len() {
+ panic!(
+ "byte index {} is out of bounds of `{:?}` (length: {})",
+ offset,
+ chunk.text,
+ chunk.text.len()
+ );
+ }
+ // find the character
+ let char_start = chunk.floor_char_boundary(offset);
+ // `char_start` must be less than len and a char boundary
+ let ch = chunk
+ .text
+ .get(char_start..)
+ .unwrap()
+ .chars()
+ .next()
+ .unwrap();
+ let char_range = char_start..char_start + ch.len_utf8();
+ panic!(
+ "byte index {} is not a char boundary; it is inside {:?} (bytes {:?})",
+ offset, ch, char_range,
+ );
+ }
+ }
}
#[derive(Clone, Copy, Debug)]
@@ -4,8 +4,8 @@ mod point;
mod point_utf16;
mod unclipped;
+use arrayvec::ArrayVec;
use rayon::iter::{IntoParallelIterator, ParallelIterator as _};
-use smallvec::SmallVec;
use std::{
cmp, fmt, io, mem,
ops::{self, AddAssign, Range},
@@ -192,10 +192,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());
@@ -206,19 +215,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();
}
@@ -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};