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:
Generated
-1
@@ -14226,7 +14226,6 @@ dependencies = [
|
||||
"rand 0.9.2",
|
||||
"rayon",
|
||||
"regex",
|
||||
"smallvec",
|
||||
"sum_tree",
|
||||
"unicode-segmentation",
|
||||
"util",
|
||||
|
||||
@@ -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>),
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user