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 ...
This commit is contained in:
Lukas Wirth
2025-10-27 08:26:39 +00:00
committed by GitHub
parent edf2ec7d4c
commit ae3abf50d8
14 changed files with 207 additions and 178 deletions
Generated
-1
View File
@@ -14226,7 +14226,6 @@ dependencies = [
"rand 0.9.2",
"rayon",
"regex",
"smallvec",
"sum_tree",
"unicode-segmentation",
"util",
+1
View File
@@ -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);
@@ -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>),
}
+21 -7
View File
@@ -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, _, _| {
+82 -57
View File
@@ -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::<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 {
@@ -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<Editor>,
) -> Option<(String, Range<Anchor>)> {
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::<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(
@@ -6834,32 +6842,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)
@@ -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<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
@@ -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::<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<()> {
@@ -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<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
@@ -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();
+3 -1
View File
@@ -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)
});
+47 -45
View File
@@ -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 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 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;
}
}
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,
)
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 (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)
{
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,
)
}
}
}
+18 -29
View File
@@ -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()
+12 -9
View File
@@ -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));
}
}
-11
View File
@@ -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<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"))]
-1
View File
@@ -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
+1
View File
@@ -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!(
+14 -16
View File
@@ -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();
}
+1 -1
View File
@@ -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};