diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 6c381dee00ddbe0b8c70d4fe30966973ec0bb2f9..4970ee25860a2408a6a0f9a5f4648d70fe1cc573 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -1009,8 +1009,7 @@ impl TextThreadEditor { .as_f64(); let scroll_position = editor .scroll_manager - .anchor() - .scroll_position(&snapshot.display_snapshot); + .scroll_position(&snapshot.display_snapshot, cx); let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.); if (scroll_position.y..scroll_bottom).contains(&cursor_row) { @@ -3026,14 +3025,15 @@ impl FollowableItem for TextThreadEditor { self.remote_id } - fn to_state_proto(&self, window: &Window, cx: &App) -> Option { - let text_thread = self.text_thread.read(cx); + fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option { + let context_id = self.text_thread.read(cx).id().to_proto(); + let editor_proto = self + .editor + .update(cx, |editor, cx| editor.to_state_proto(window, cx)); Some(proto::view::Variant::ContextEditor( proto::view::ContextEditor { - context_id: text_thread.id().to_proto(), - editor: if let Some(proto::view::Variant::Editor(proto)) = - self.editor.read(cx).to_state_proto(window, cx) - { + context_id, + editor: if let Some(proto::view::Variant::Editor(proto)) = editor_proto { Some(proto) } else { None @@ -3100,12 +3100,12 @@ impl FollowableItem for TextThreadEditor { &self, event: &Self::Event, update: &mut Option, - window: &Window, - cx: &App, + window: &mut Window, + cx: &mut App, ) -> bool { - self.editor - .read(cx) - .add_event_to_update_proto(event, update, window, cx) + self.editor.update(cx, |editor, cx| { + editor.add_event_to_update_proto(event, update, window, cx) + }) } fn apply_update_proto( diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 859def4415a401f9f21a1779d5fde6c9101b07b1..cbfa06142e2e8a520653d031dfe8c4b69c08667a 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -563,18 +563,22 @@ impl FollowableItem for ChannelView { self.remote_id } - fn to_state_proto(&self, window: &Window, cx: &App) -> Option { - let channel_buffer = self.channel_buffer.read(cx); - if !channel_buffer.is_connected() { + fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option { + let (is_connected, channel_id) = { + let channel_buffer = self.channel_buffer.read(cx); + (channel_buffer.is_connected(), channel_buffer.channel_id.0) + }; + if !is_connected { return None; } + let editor_proto = self + .editor + .update(cx, |editor, cx| editor.to_state_proto(window, cx)); Some(proto::view::Variant::ChannelView( proto::view::ChannelView { - channel_id: channel_buffer.channel_id.0, - editor: if let Some(proto::view::Variant::Editor(proto)) = - self.editor.read(cx).to_state_proto(window, cx) - { + channel_id, + editor: if let Some(proto::view::Variant::Editor(proto)) = editor_proto { Some(proto) } else { None @@ -638,12 +642,12 @@ impl FollowableItem for ChannelView { &self, event: &EditorEvent, update: &mut Option, - window: &Window, - cx: &App, + window: &mut Window, + cx: &mut App, ) -> bool { - self.editor - .read(cx) - .add_event_to_update_proto(event, update, window, cx) + self.editor.update(cx, |editor, cx| { + editor.add_event_to_update_proto(event, update, window, cx) + }) } fn apply_update_proto( diff --git a/crates/debugger_ui/src/session.rs b/crates/debugger_ui/src/session.rs index 40c9bd810f9c5c9691f51f3d38957a98c9f037a2..fc11f40e851a48ff9d0d4634eb69dd899b48a113 100644 --- a/crates/debugger_ui/src/session.rs +++ b/crates/debugger_ui/src/session.rs @@ -141,7 +141,7 @@ impl FollowableItem for DebugSession { self.remote_id } - fn to_state_proto(&self, _window: &Window, _cx: &App) -> Option { + fn to_state_proto(&self, _window: &mut Window, _cx: &mut App) -> Option { None } @@ -159,8 +159,8 @@ impl FollowableItem for DebugSession { &self, _event: &Self::Event, _update: &mut Option, - _window: &Window, - _cx: &App, + _window: &mut Window, + _cx: &mut App, ) -> bool { // update.get_or_insert_with(|| proto::update_view::Variant::DebugPanel(Default::default())); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 0e39642c3e66a98384a5e0522d56a6bf873b4a09..88e5e84a5c3ef5a62c05709def6b233f552fdbfb 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -480,7 +480,37 @@ impl DisplayMap { }); } + let companion_display_snapshot = self.companion.as_ref().and_then(|(companion_dm, _)| { + companion_dm + .update(cx, |dm, cx| Arc::new(dm.snapshot_simple(cx))) + .ok() + }); + DisplaySnapshot { + display_map_id: self.entity_id, + companion_display_snapshot, + block_snapshot, + diagnostics_max_severity: self.diagnostics_max_severity, + crease_snapshot: self.crease_map.snapshot(), + text_highlights: self.text_highlights.clone(), + inlay_highlights: self.inlay_highlights.clone(), + clip_at_line_ends: self.clip_at_line_ends, + masked: self.masked, + fold_placeholder: self.fold_placeholder.clone(), + } + } + + fn snapshot_simple(&mut self, cx: &mut Context) -> DisplaySnapshot { + let (wrap_snapshot, wrap_edits) = self.sync_through_wrap(cx); + + let block_snapshot = self + .block_map + .read(wrap_snapshot, wrap_edits, None, None) + .snapshot; + + DisplaySnapshot { + display_map_id: self.entity_id, + companion_display_snapshot: None, block_snapshot, diagnostics_max_severity: self.diagnostics_max_severity, crease_snapshot: self.crease_map.snapshot(), @@ -1547,6 +1577,8 @@ impl<'a> HighlightedChunk<'a> { #[derive(Clone)] pub struct DisplaySnapshot { + pub display_map_id: EntityId, + pub companion_display_snapshot: Option>, pub crease_snapshot: CreaseSnapshot, block_snapshot: BlockSnapshot, text_highlights: TextHighlights, @@ -1558,6 +1590,10 @@ pub struct DisplaySnapshot { } impl DisplaySnapshot { + pub fn companion_snapshot(&self) -> Option<&DisplaySnapshot> { + self.companion_display_snapshot.as_deref() + } + pub fn wrap_snapshot(&self) -> &WrapSnapshot { &self.block_snapshot.wrap_snapshot } @@ -2733,7 +2769,7 @@ pub mod tests { _ = cx.update_window(window, |_, window, cx| { let text_layout_details = - editor.update(cx, |editor, _cx| editor.text_layout_details(window)); + editor.update(cx, |editor, cx| editor.text_layout_details(window, cx)); let font_size = px(12.0); let wrap_width = Some(px(96.)); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index aae80501b4fd57ab7036167fdd99358fe36c1edc..f70e39fc1cf62c653674f1e8595eb452fe6782de 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -58,8 +58,8 @@ pub use editor_settings::{ HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, }; pub use element::{ - CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, OverlayPainter, - OverlayPainterData, PointForPosition, render_breadcrumb_text, + CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition, + render_breadcrumb_text, }; pub use git::blame::BlameRenderer; pub use hover_popover::hover_markdown_style; @@ -168,7 +168,7 @@ use project::{ use rand::seq::SliceRandom; use regex::Regex; use rpc::{ErrorCode, ErrorExt, proto::PeerId}; -use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager}; +use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor}; use selections_collection::{MutableSelectionsCollection, SelectionsCollection}; use serde::{Deserialize, Serialize}; use settings::{ @@ -691,7 +691,7 @@ pub enum EditPredictionPreview { /// Modifier pressed Active { since: Instant, - previous_scroll_position: Option, + previous_scroll_position: Option, }, } @@ -703,7 +703,7 @@ impl EditPredictionPreview { } } - pub fn set_previous_scroll_position(&mut self, scroll_position: Option) { + pub fn set_previous_scroll_position(&mut self, scroll_position: Option) { if let EditPredictionPreview::Active { previous_scroll_position, .. @@ -1332,7 +1332,6 @@ pub struct Editor { folding_newlines: Task<()>, select_next_is_case_sensitive: Option, pub lookup_key: Option>, - scroll_companion: Option>, on_local_selections_changed: Option) + 'static>>, suppress_selection_callback: bool, @@ -1388,7 +1387,7 @@ pub struct EditorSnapshot { pub display_snapshot: DisplaySnapshot, pub placeholder_display_snapshot: Option, is_focused: bool, - scroll_anchor: ScrollAnchor, + scroll_anchor: SharedScrollAnchor, ongoing_scroll: OngoingScroll, current_line_highlight: CurrentLineHighlight, gutter_hovered: bool, @@ -1939,15 +1938,19 @@ impl Editor { window, cx, ); - self.display_map.update(cx, |display_map, cx| { + let my_snapshot = self.display_map.update(cx, |display_map, cx| { let snapshot = display_map.snapshot(cx); clone.display_map.update(cx, |display_map, cx| { display_map.set_state(&snapshot, cx); }); + snapshot }); + let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx)); clone.folds_did_change(cx); clone.selections.clone_state(&self.selections); - clone.scroll_manager.clone_state(&self.scroll_manager); + clone + .scroll_manager + .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx); clone.searchable = self.searchable; clone.read_only = self.read_only; clone @@ -1965,6 +1968,7 @@ impl Editor { pub fn sticky_headers( &self, + display_snapshot: &DisplaySnapshot, style: &EditorStyle, cx: &App, ) -> Option>> { @@ -1972,7 +1976,7 @@ impl Editor { let multi_buffer_snapshot = multi_buffer.snapshot(cx); let multi_buffer_visible_start = self .scroll_manager - .anchor() + .native_anchor(display_snapshot, cx) .anchor .to_point(&multi_buffer_snapshot); let max_row = multi_buffer_snapshot.max_point().row; @@ -2542,7 +2546,6 @@ impl Editor { folding_newlines: Task::ready(()), lookup_key: None, select_next_is_case_sensitive: None, - scroll_companion: None, on_local_selections_changed: None, suppress_selection_callback: false, applicable_language_settings: HashMap::default(), @@ -2576,8 +2579,10 @@ impl Editor { if *local { editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape); editor.inline_blame_popover.take(); - let new_anchor = editor.scroll_manager.anchor(); let snapshot = editor.snapshot(window, cx); + let new_anchor = editor + .scroll_manager + .native_anchor(&snapshot.display_snapshot, cx); editor.update_restoration_data(cx, move |data| { data.scroll_position = ( new_anchor.top_row(snapshot.buffer_snapshot()), @@ -3078,6 +3083,8 @@ impl Editor { }) .flatten(); + let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + EditorSnapshot { mode: self.mode.clone(), show_gutter: self.show_gutter, @@ -3089,12 +3096,12 @@ impl Editor { show_runnables: self.show_runnables, show_breakpoints: self.show_breakpoints, git_blame_gutter_max_author_length, - display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)), + scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx), + display_snapshot, placeholder_display_snapshot: self .placeholder_display_map .as_ref() .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))), - scroll_anchor: self.scroll_manager.anchor(), ongoing_scroll: self.scroll_manager.ongoing_scroll(), is_focused: self.focus_handle.is_focused(window), current_line_highlight: self @@ -5573,11 +5580,12 @@ impl Editor { cx: &mut Context, ) -> HashMap, clock::Global, Range)> { let project = self.project().cloned(); + let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let multi_buffer = self.buffer().read(cx); let multi_buffer_snapshot = multi_buffer.snapshot(cx); let multi_buffer_visible_start = self .scroll_manager - .anchor() + .native_anchor(&display_snapshot, cx) .anchor .to_point(&multi_buffer_snapshot); let multi_buffer_visible_end = multi_buffer_snapshot.clip_point( @@ -5623,12 +5631,12 @@ impl Editor { .collect() } - pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails { + pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails { TextLayoutDetails { text_system: window.text_system().clone(), editor_style: self.style.clone().unwrap(), rem_size: window.rem_size(), - scroll_anchor: self.scroll_manager.anchor(), + scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx), visible_rows: self.visible_line_count(), vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin, } @@ -7537,6 +7545,7 @@ impl Editor { self.debounced_selection_highlight_complete = false; return; }; + let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); let query_changed = self .quick_selection_highlight_task @@ -7548,7 +7557,7 @@ impl Editor { if on_buffer_edit || query_changed { let multi_buffer_visible_start = self .scroll_manager - .anchor() + .native_anchor(&display_snapshot, cx) .anchor .to_point(&multi_buffer_snapshot); let multi_buffer_visible_end = multi_buffer_snapshot.clip_point( @@ -11078,7 +11087,7 @@ impl Editor { (buffer.len(), rows.start.previous_row()) }; - let text_layout_details = self.text_layout_details(window); + let text_layout_details = self.text_layout_details(window, cx); let x = display_map.x_for_display_point( selection.head().to_display_point(&display_map), &text_layout_details, @@ -12847,7 +12856,7 @@ impl Editor { pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.transact(window, cx, |this, window, cx| { let edits = this.change_selections(Default::default(), window, cx, |s| { let mut edits: Vec<(Range, String)> = Default::default(); @@ -13846,7 +13855,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); let selection_count = self.selections.count(); let first_selection = self.selections.first_anchor(); @@ -13889,7 +13898,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { @@ -13926,7 +13935,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { @@ -13953,7 +13962,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details) @@ -13968,7 +13977,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details) @@ -13988,7 +13997,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { @@ -14034,7 +14043,7 @@ impl Editor { SelectionEffects::default() }; - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.change_selections(effects, window, cx, |s| { s.move_with(|map, selection| { @@ -14056,7 +14065,7 @@ impl Editor { pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up(map, head, goal, false, text_layout_details) @@ -14074,7 +14083,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); let selection_count = self.selections.count(); let first_selection = self.selections.first_anchor(); @@ -14112,7 +14121,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { @@ -14158,7 +14167,7 @@ impl Editor { SelectionEffects::default() }; - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.change_selections(effects, window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { @@ -14179,7 +14188,7 @@ impl Editor { pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down(map, head, goal, false, text_layout_details) @@ -14990,10 +14999,11 @@ impl Editor { ); } - fn navigation_data(&self, cursor_anchor: Anchor, cx: &App) -> NavigationData { + fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context) -> NavigationData { + let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx).read(cx); let cursor_position = cursor_anchor.to_point(&buffer); - let scroll_anchor = self.scroll_manager.anchor(); + let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx); let scroll_top_row = scroll_anchor.top_row(&buffer); drop(buffer); @@ -15005,7 +15015,11 @@ impl Editor { } } - fn navigation_entry(&self, cursor_anchor: Anchor, cx: &App) -> Option { + fn navigation_entry( + &self, + cursor_anchor: Anchor, + cx: &mut Context, + ) -> Option { let Some(history) = self.nav_history.clone() else { return None; }; @@ -15161,7 +15175,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let all_selections = self.selections.all::(&display_map); - let text_layout_details = self.text_layout_details(window); + let text_layout_details = self.text_layout_details(window, cx); let (mut columnar_selections, new_selections_to_columnarize) = { if let Some(state) = self.add_selections_state.as_ref() { @@ -15860,7 +15874,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); - let text_layout_details = &self.text_layout_details(window); + let text_layout_details = &self.text_layout_details(window, cx); self.transact(window, cx, |this, window, cx| { let mut selections = this .selections @@ -20777,7 +20791,14 @@ impl Editor { window, cx, ); - minimap.scroll_manager.clone_state(&self.scroll_manager); + let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx)); + minimap.scroll_manager.clone_state( + &self.scroll_manager, + &my_snapshot, + &minimap_snapshot, + cx, + ); minimap.set_text_style_refinement(TextStyleRefinement { font_size: Some(MINIMAP_FONT_SIZE), font_weight: Some(MINIMAP_FONT_WEIGHT), @@ -21086,14 +21107,6 @@ impl Editor { self.delegate_expand_excerpts = delegate; } - pub fn set_scroll_companion(&mut self, companion: Option>) { - self.scroll_companion = companion; - } - - pub fn scroll_companion(&self) -> Option<&WeakEntity> { - self.scroll_companion.as_ref() - } - pub fn set_on_local_selections_changed( &mut self, callback: Option) + 'static>>, @@ -24756,10 +24769,10 @@ impl Editor { pub fn to_pixel_point( &mut self, - source: multi_buffer::Anchor, + source: Anchor, editor_snapshot: &EditorSnapshot, window: &mut Window, - cx: &App, + cx: &mut App, ) -> Option> { let source_point = source.to_display_point(editor_snapshot); self.display_to_pixel_point(source_point, editor_snapshot, window, cx) @@ -24770,10 +24783,10 @@ impl Editor { source: DisplayPoint, editor_snapshot: &EditorSnapshot, window: &mut Window, - cx: &App, + cx: &mut App, ) -> Option> { let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size()); - let text_layout_details = self.text_layout_details(window); + let text_layout_details = self.text_layout_details(window, cx); let scroll_top = text_layout_details .scroll_anchor .scroll_position(editor_snapshot) @@ -24820,8 +24833,8 @@ impl Editor { .and_then(|item| item.to_any_mut()?.downcast_mut::()) } - fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions { - let text_layout_details = self.text_layout_details(window); + fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions { + let text_layout_details = self.text_layout_details(window, cx); let style = &text_layout_details.editor_style; let font_id = window.text_system().resolve_font(&style.text.font()); let font_size = style.text.font_size.to_pixels(window.rem_size()); @@ -27657,12 +27670,12 @@ impl EntityInputHandler for Editor { window: &mut Window, cx: &mut Context, ) -> Option> { - let text_layout_details = self.text_layout_details(window); + let text_layout_details = self.text_layout_details(window, cx); let CharacterDimensions { em_width, em_advance, line_height, - } = self.character_dimensions(window); + } = self.character_dimensions(window, cx); let snapshot = self.snapshot(window, cx); let scroll_position = snapshot.scroll_position(); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index d4335524ab4f9f7a41a48426533e9fc4bbacf1f5..3f938cf3bc89074b0666a784246ea59cb3bcc7c4 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -938,19 +938,34 @@ async fn test_navigation_history(cx: &mut TestAppContext) { // Set scroll position to check later editor.set_scroll_position(gpui::Point::::new(5.5, 5.5), window, cx); - let original_scroll_position = editor.scroll_manager.anchor(); + let original_scroll_position = editor + .scroll_manager + .native_anchor(&editor.display_snapshot(cx), cx); // Jump to the end of the document and adjust scroll editor.move_to_end(&MoveToEnd, window, cx); editor.set_scroll_position(gpui::Point::::new(-2.5, -0.5), window, cx); - assert_ne!(editor.scroll_manager.anchor(), original_scroll_position); + assert_ne!( + editor + .scroll_manager + .native_anchor(&editor.display_snapshot(cx), cx), + original_scroll_position + ); let nav_entry = pop_history(&mut editor, cx).unwrap(); editor.navigate(nav_entry.data.unwrap(), window, cx); - assert_eq!(editor.scroll_manager.anchor(), original_scroll_position); + assert_eq!( + editor + .scroll_manager + .native_anchor(&editor.display_snapshot(cx), cx), + original_scroll_position + ); // Ensure we don't panic when navigation data contains invalid anchors *and* points. - let mut invalid_anchor = editor.scroll_manager.anchor().anchor; + let mut invalid_anchor = editor + .scroll_manager + .native_anchor(&editor.display_snapshot(cx), cx) + .anchor; invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok(); let invalid_point = Point::new(9999, 0); editor.navigate( @@ -17709,12 +17724,14 @@ async fn test_following(cx: &mut TestAppContext) { &leader_entity, window, move |_, leader, event, window, cx| { - leader.read(cx).add_event_to_update_proto( - event, - &mut update.borrow_mut(), - window, - cx, - ); + leader.update(cx, |leader, cx| { + leader.add_event_to_update_proto( + event, + &mut update.borrow_mut(), + window, + cx, + ); + }); }, ) .detach(); @@ -17935,12 +17952,9 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) { let update = update_message.clone(); |_, window, cx| { cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| { - leader.read(cx).add_event_to_update_proto( - event, - &mut update.borrow_mut(), - window, - cx, - ); + leader.update(cx, |leader, cx| { + leader.add_event_to_update_proto(event, &mut update.borrow_mut(), window, cx); + }); }) .detach(); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index cd626cdb33389b748ad31928c1080de736a3dd53..e3e805cf91bd2668ba7dabc90b9623bdd0b63dc6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -192,23 +192,10 @@ struct RenderBlocksOutput { resized_blocks: Option>, } -/// Data passed to overlay painters during the paint phase. -pub struct OverlayPainterData<'a> { - pub editor: &'a Entity, - pub snapshot: &'a EditorSnapshot, - pub scroll_position: gpui::Point, - pub line_height: Pixels, - pub visible_row_range: Range, - pub hitbox: &'a Hitbox, -} - -pub type OverlayPainter = Box, &mut Window, &mut App)>; - pub struct EditorElement { editor: Entity, style: EditorStyle, split_side: Option, - overlay_painter: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -225,7 +212,6 @@ impl EditorElement { editor: editor.clone(), style, split_side: None, - overlay_painter: None, } } @@ -233,10 +219,6 @@ impl EditorElement { self.split_side = Some(side); } - pub fn set_overlay_painter(&mut self, painter: OverlayPainter) { - self.overlay_painter = Some(painter); - } - fn should_show_buffer_headers(&self) -> bool { self.split_side.is_none() } @@ -1977,10 +1959,6 @@ impl EditorElement { window: &mut Window, cx: &mut App, ) -> Option { - if self.split_side == Some(SplitSide::Left) { - return None; - } - let show_scrollbars = self.editor.read(cx).show_scrollbars; if (!show_scrollbars.horizontal && !show_scrollbars.vertical) || self.style.scrollbar_width.is_zero() @@ -4537,7 +4515,9 @@ impl EditorElement { let mut end_rows = Vec::::new(); let mut rows = Vec::::new(); - let items = editor.sticky_headers(style, cx).unwrap_or_default(); + let items = editor + .sticky_headers(&snapshot.display_snapshot, style, cx) + .unwrap_or_default(); for item in items { let start_point = item.range.start.to_point(snapshot.buffer_snapshot()); @@ -5239,7 +5219,7 @@ impl EditorElement { snapshot, visible_display_row_range.clone(), max_size, - &editor.text_layout_details(window), + &editor.text_layout_details(window, cx), window, cx, ) @@ -9632,38 +9612,6 @@ impl Element for EditorElement { } }; - // When jumping from one side of a side-by-side diff to the - // other, we autoscroll autoscroll to keep the target range in view. - // - // If our scroll companion has a pending autoscroll request, process it - // first so that both editors render with synchronized scroll positions. - // This is important for split diff views where one editor may prepaint - // before the other. - if let Some(companion) = self - .editor - .read(cx) - .scroll_companion() - .and_then(|c| c.upgrade()) - { - if companion.read(cx).scroll_manager.has_autoscroll_request() { - companion.update(cx, |companion_editor, cx| { - let companion_autoscroll_request = - companion_editor.scroll_manager.take_autoscroll_request(); - companion_editor.autoscroll_vertically( - bounds, - line_height, - max_scroll_top, - companion_autoscroll_request, - window, - cx, - ); - }); - snapshot = self - .editor - .update(cx, |editor, cx| editor.snapshot(window, cx)); - } - } - let ( autoscroll_request, autoscroll_containing_element, @@ -10261,8 +10209,8 @@ impl Element for EditorElement { ); self.editor.update(cx, |editor, cx| { - if editor.scroll_manager.clamp_scroll_left(scroll_max.x) { - scroll_position.x = scroll_position.x.min(scroll_max.x); + if editor.scroll_manager.clamp_scroll_left(scroll_max.x, cx) { + scroll_position.x = scroll_max.x.min(scroll_position.x); } if needs_horizontal_autoscroll.0 @@ -10919,18 +10867,6 @@ impl Element for EditorElement { self.paint_scrollbars(layout, window, cx); self.paint_edit_prediction_popover(layout, window, cx); self.paint_mouse_context_menu(layout, window, cx); - - if let Some(overlay_painter) = self.overlay_painter.take() { - let data = OverlayPainterData { - editor: &self.editor, - snapshot: &layout.position_map.snapshot, - scroll_position: layout.position_map.snapshot.scroll_position(), - line_height: layout.position_map.line_height, - visible_row_range: layout.visible_display_row_range.clone(), - hitbox: &layout.hitbox, - }; - overlay_painter(data, window, cx); - } }); }) }) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 047be5b9f29d4e20a1047e0366b6f554264547f5..3ff4d7da4eb654cfe48a223afe2906294fd77c31 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -204,17 +204,20 @@ impl FollowableItem for Editor { cx.notify(); } - fn to_state_proto(&self, _: &Window, cx: &App) -> Option { - let buffer = self.buffer.read(cx); - if buffer + fn to_state_proto(&self, _: &mut Window, cx: &mut App) -> Option { + let is_private = self + .buffer + .read(cx) .as_singleton() .and_then(|buffer| buffer.read(cx).file()) - .is_some_and(|file| file.is_private()) - { + .is_some_and(|file| file.is_private()); + if is_private { return None; } - let scroll_anchor = self.scroll_manager.anchor(); + let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx); + let buffer = self.buffer.read(cx); let excerpts = buffer .read(cx) .excerpts() @@ -269,8 +272,8 @@ impl FollowableItem for Editor { &self, event: &EditorEvent, update: &mut Option, - _: &Window, - cx: &App, + _: &mut Window, + cx: &mut App, ) -> bool { let update = update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default())); @@ -305,8 +308,9 @@ impl FollowableItem for Editor { true } EditorEvent::ScrollPositionChanged { autoscroll, .. } if !autoscroll => { + let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let snapshot = self.buffer.read(cx).snapshot(cx); - let scroll_anchor = self.scroll_manager.anchor(); + let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx); update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.anchor, &snapshot)); update.scroll_x = scroll_anchor.offset.x; diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 2cc050dae8be73fda89a6b9982b052bddd639063..d64183e037fb948b11c35fad65a4d6d618a5bec6 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -331,7 +331,7 @@ pub fn deploy_context_menu( cx, ), None => { - let character_size = editor.character_dimensions(window); + let character_size = editor.character_dimensions(window, cx); let menu_position = MenuPosition::PinnedToEditor { source: source_anchor, offset: gpui::point(character_size.em_width, character_size.line_height), diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 498ea4e278cc6b07652af4da73b47e0c4b116dfe..de84ffafa363bbdfe475a7d2ad646f118268be2b 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -4,7 +4,7 @@ use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint}; use crate::{ DisplayRow, EditorStyle, ToOffset, ToPoint, - scroll::{ScrollAnchor, ScrollOffset}, + scroll::{ScrollOffset, SharedScrollAnchor}, }; use gpui::{Pixels, WindowTextSystem}; use language::{CharClassifier, Point}; @@ -29,7 +29,7 @@ pub struct TextLayoutDetails { pub(crate) text_system: Arc, pub(crate) editor_style: EditorStyle, pub(crate) rem_size: Pixels, - pub scroll_anchor: ScrollAnchor, + pub scroll_anchor: SharedScrollAnchor, pub visible_rows: Option, pub vertical_scroll_margin: ScrollOffset, } @@ -1224,7 +1224,8 @@ mod tests { let editor = cx.editor.clone(); let window = cx.window; _ = cx.update_window(window, |_, window, cx| { - let text_layout_details = editor.read(cx).text_layout_details(window); + let text_layout_details = + editor.update(cx, |editor, cx| editor.text_layout_details(window, cx)); let font = font("Helvetica"); diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index b719df4bf48e9386135659587019ad6951714bc4..c862a43cf95911600ba732c7086f0a9338ee94c8 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -12,7 +12,9 @@ use crate::{ }; pub use autoscroll::{Autoscroll, AutoscrollStrategy}; use core::fmt::Debug; -use gpui::{Along, App, Axis, Context, Pixels, Task, Window, point, px}; +use gpui::{ + Along, App, AppContext as _, Axis, Context, Entity, EntityId, Pixels, Task, Window, point, px, +}; use language::language_settings::{AllLanguageSettings, SoftWrap}; use language::{Bias, Point}; pub use scroll_amount::ScrollAmount; @@ -68,6 +70,47 @@ pub struct OngoingScroll { axis: Option, } +/// In the side-by-side diff view, the two sides share a ScrollAnchor using this struct. +/// Either side can set a ScrollAnchor that points to its own multibuffer, and we store the ID of the display map +/// that the last-written anchor came from so that we know how to resolve it to a DisplayPoint. +/// +/// For normal editors, this just acts as a wrapper around a ScrollAnchor. +#[derive(Clone, Copy, Debug)] +pub struct SharedScrollAnchor { + pub scroll_anchor: ScrollAnchor, + pub display_map_id: Option, +} + +impl SharedScrollAnchor { + pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point { + let snapshot = if let Some(display_map_id) = self.display_map_id + && display_map_id != snapshot.display_map_id + { + let companion_snapshot = snapshot.companion_snapshot().unwrap(); + assert_eq!(companion_snapshot.display_map_id, display_map_id); + companion_snapshot + } else { + snapshot + }; + + self.scroll_anchor.scroll_position(snapshot) + } + + pub fn scroll_top_display_point(&self, snapshot: &DisplaySnapshot) -> DisplayPoint { + let snapshot = if let Some(display_map_id) = self.display_map_id + && display_map_id != snapshot.display_map_id + { + let companion_snapshot = snapshot.companion_snapshot().unwrap(); + assert_eq!(companion_snapshot.display_map_id, display_map_id); + companion_snapshot + } else { + snapshot + }; + + self.scroll_anchor.anchor.to_display_point(snapshot) + } +} + impl OngoingScroll { fn new() -> Self { Self { @@ -150,7 +193,13 @@ impl ActiveScrollbarState { pub struct ScrollManager { pub(crate) vertical_scroll_margin: ScrollOffset, - anchor: ScrollAnchor, + anchor: Entity, + /// Value to be used for clamping the x component of the SharedScrollAnchor's offset. + /// + /// We store this outside the SharedScrollAnchor so that the two sides of a side-by-side diff can share + /// a horizontal scroll offset that may be out of range for one of the editors (when one side is wider than the other). + /// Each side separately clamps the x component using its own scroll_max_x when reading from the SharedScrollAnchor. + scroll_max_x: Option, ongoing: OngoingScroll, /// Number of sticky header lines currently being rendered for the current scroll position. sticky_header_line_count: usize, @@ -175,10 +224,15 @@ pub struct ScrollManager { } impl ScrollManager { - pub fn new(cx: &mut App) -> Self { + pub fn new(cx: &mut Context) -> Self { + let anchor = cx.new(|_| SharedScrollAnchor { + scroll_anchor: ScrollAnchor::new(), + display_map_id: None, + }); ScrollManager { vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin, - anchor: ScrollAnchor::new(), + anchor, + scroll_max_x: None, ongoing: OngoingScroll::new(), sticky_header_line_count: 0, autoscroll_request: None, @@ -194,14 +248,95 @@ impl ScrollManager { } } - pub fn clone_state(&mut self, other: &Self) { - self.anchor = other.anchor; + pub fn set_native_display_map_id( + &mut self, + display_map_id: EntityId, + cx: &mut Context, + ) { + self.anchor.update(cx, |shared, _| { + if shared.display_map_id.is_none() { + shared.display_map_id = Some(display_map_id); + } + }); + } + + pub fn clone_state( + &mut self, + other: &Self, + other_snapshot: &DisplaySnapshot, + my_snapshot: &DisplaySnapshot, + cx: &mut Context, + ) { + let native_anchor = other.native_anchor(other_snapshot, cx); + self.anchor.update(cx, |this, _| { + this.scroll_anchor = native_anchor; + this.display_map_id = Some(my_snapshot.display_map_id); + }); self.ongoing = other.ongoing; self.sticky_header_line_count = other.sticky_header_line_count; } - pub fn anchor(&self) -> ScrollAnchor { - self.anchor + pub fn offset(&self, cx: &App) -> gpui::Point { + let mut offset = self.anchor.read(cx).scroll_anchor.offset; + if let Some(max_x) = self.scroll_max_x { + offset.x = offset.x.min(max_x); + } + offset + } + + /// Get a ScrollAnchor whose `anchor` field is guaranteed to point into the multibuffer for the provided snapshot. + /// + /// For normal editors, this just retrieves the internal ScrollAnchor and is lossless. When the editor is part of a side-by-side diff, + /// we may need to translate the anchor to point to the "native" multibuffer first. That translation is lossy, + /// so this method should be used sparingly---if you just need a scroll position or display point, call the appropriate helper method instead, + /// since they can losslessly handle the case where the ScrollAnchor was last set from the other side. + pub fn native_anchor(&self, snapshot: &DisplaySnapshot, cx: &App) -> ScrollAnchor { + let shared = self.anchor.read(cx); + + let mut result = if let Some(display_map_id) = shared.display_map_id + && display_map_id != snapshot.display_map_id + { + let companion_snapshot = snapshot.companion_snapshot().unwrap(); + assert_eq!(companion_snapshot.display_map_id, display_map_id); + let mut display_point = shared + .scroll_anchor + .anchor + .to_display_point(companion_snapshot); + *display_point.column_mut() = 0; + let buffer_point = snapshot.display_point_to_point(display_point, Bias::Left); + let anchor = snapshot.buffer_snapshot().anchor_before(buffer_point); + ScrollAnchor { + anchor, + offset: shared.scroll_anchor.offset, + } + } else { + shared.scroll_anchor + }; + + if let Some(max_x) = self.scroll_max_x { + result.offset.x = result.offset.x.min(max_x); + } + result + } + + pub fn shared_scroll_anchor(&self, cx: &App) -> SharedScrollAnchor { + let mut shared = *self.anchor.read(cx); + if let Some(max_x) = self.scroll_max_x { + shared.scroll_anchor.offset.x = shared.scroll_anchor.offset.x.min(max_x); + } + shared + } + + pub fn scroll_top_display_point(&self, snapshot: &DisplaySnapshot, cx: &App) -> DisplayPoint { + self.anchor.read(cx).scroll_top_display_point(snapshot) + } + + pub fn scroll_anchor_entity(&self) -> Entity { + self.anchor.clone() + } + + pub fn set_shared_scroll_anchor(&mut self, entity: Entity) { + self.anchor = entity; } pub fn ongoing_scroll(&self) -> OngoingScroll { @@ -213,8 +348,16 @@ impl ScrollManager { self.ongoing.axis = axis; } - pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point { - self.anchor.scroll_position(snapshot) + pub fn scroll_position( + &self, + snapshot: &DisplaySnapshot, + cx: &App, + ) -> gpui::Point { + let mut pos = self.anchor.read(cx).scroll_position(snapshot); + if let Some(max_x) = self.scroll_max_x { + pos.x = pos.x.min(max_x); + } + pos } pub fn sticky_header_line_count(&self) -> usize { @@ -265,10 +408,6 @@ impl ScrollManager { Bias::Left, ) .to_point(map); - // Anchor the scroll position to the *left* of the first visible buffer point. - // - // This prevents the viewport from shifting down when blocks (e.g. expanded diff hunk - // deletions) are inserted *above* the first buffer character in the file. let top_anchor = map.buffer_snapshot().anchor_before(scroll_top_buffer_point); self.set_anchor( @@ -279,6 +418,7 @@ impl ScrollManager { scroll_top - top_anchor.to_display_point(map).row().as_f64(), ), }, + map, scroll_top_buffer_point.row, local, autoscroll, @@ -291,6 +431,7 @@ impl ScrollManager { fn set_anchor( &mut self, anchor: ScrollAnchor, + display_map: &DisplaySnapshot, top_row: u32, local: bool, autoscroll: bool, @@ -299,20 +440,27 @@ impl ScrollManager { cx: &mut Context, ) -> WasScrolled { let adjusted_anchor = if self.forbid_vertical_scroll { + let current = self.anchor.read(cx); ScrollAnchor { - offset: gpui::Point::new(anchor.offset.x, self.anchor.offset.y), - anchor: self.anchor.anchor, + offset: gpui::Point::new(anchor.offset.x, current.scroll_anchor.offset.y), + anchor: current.scroll_anchor.anchor, } } else { anchor }; + self.scroll_max_x.take(); self.autoscroll_request.take(); - if self.anchor == adjusted_anchor { + + let current = self.anchor.read(cx); + if current.scroll_anchor == adjusted_anchor { return WasScrolled(false); } - self.anchor = adjusted_anchor; + self.anchor.update(cx, |shared, _| { + shared.scroll_anchor = adjusted_anchor; + shared.display_map_id = Some(display_map.display_map_id); + }); cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll }); self.show_scrollbars(window, cx); if let Some(workspace_id) = workspace_id { @@ -466,13 +614,10 @@ impl ScrollManager { self.minimap_thumb_state } - pub fn clamp_scroll_left(&mut self, max: f64) -> bool { - if max < self.anchor.offset.x { - self.anchor.offset.x = max; - true - } else { - false - } + pub fn clamp_scroll_left(&mut self, max: f64, cx: &App) -> bool { + let current_x = self.anchor.read(cx).scroll_anchor.offset.x; + self.scroll_max_x = Some(max); + current_x > max } pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) { @@ -485,6 +630,10 @@ impl ScrollManager { } impl Editor { + pub fn has_autoscroll_request(&self) -> bool { + self.scroll_manager.has_autoscroll_request() + } + pub fn vertical_scroll_margin(&self) -> usize { self.scroll_manager.vertical_scroll_margin as usize } @@ -544,8 +693,7 @@ impl Editor { delta.y = 0.0; } let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let position = - self.scroll_manager.anchor.scroll_position(&display_map) + delta.map(f64::from); + let position = self.scroll_manager.scroll_position(&display_map, cx) + delta.map(f64::from); self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx); } @@ -603,20 +751,6 @@ impl Editor { cx, ); - if local && was_scrolled.0 { - if let Some(companion) = self.scroll_companion.as_ref().and_then(|c| c.upgrade()) { - companion.update(cx, |companion_editor, cx| { - companion_editor.set_scroll_position_internal( - scroll_position, - false, - false, - window, - cx, - ); - }); - } - } - was_scrolled } @@ -636,7 +770,7 @@ impl Editor { .set_previous_scroll_position(None); let adjusted_position = if self.scroll_manager.forbid_vertical_scroll { - let current_position = self.scroll_manager.anchor.scroll_position(&display_map); + let current_position = self.scroll_manager.scroll_position(&display_map, cx); gpui::Point::new(scroll_position.x, current_position.y) } else { scroll_position @@ -655,7 +789,7 @@ impl Editor { pub fn scroll_position(&self, cx: &mut Context) -> gpui::Point { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - self.scroll_manager.anchor.scroll_position(&display_map) + self.scroll_manager.scroll_position(&display_map, cx) } pub fn set_scroll_anchor( @@ -666,12 +800,14 @@ impl Editor { ) { hide_hover(self, cx); let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1); + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let top_row = scroll_anchor .anchor .to_point(&self.buffer().read(cx).snapshot(cx)) .row; self.scroll_manager.set_anchor( scroll_anchor, + &display_map, top_row, true, false, @@ -689,14 +825,16 @@ impl Editor { ) { hide_hover(self, cx); let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1); - let snapshot = &self.buffer().read(cx).snapshot(cx); - if !scroll_anchor.anchor.is_valid(snapshot) { + let buffer_snapshot = self.buffer().read(cx).snapshot(cx); + if !scroll_anchor.anchor.is_valid(&buffer_snapshot) { log::warn!("Invalid scroll anchor: {:?}", scroll_anchor); return; } - let top_row = scroll_anchor.anchor.to_point(snapshot).row; + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let top_row = scroll_anchor.anchor.to_point(&buffer_snapshot).row; self.scroll_manager.set_anchor( scroll_anchor, + &display_map, top_row, false, false, @@ -775,11 +913,7 @@ impl Editor { .newest_anchor() .head() .to_display_point(&snapshot); - let screen_top = self - .scroll_manager - .anchor - .anchor - .to_display_point(&snapshot); + let screen_top = self.scroll_manager.scroll_top_display_point(&snapshot, cx); if screen_top > newest_head { return Ordering::Less; diff --git a/crates/editor/src/scroll/autoscroll.rs b/crates/editor/src/scroll/autoscroll.rs index 2078cf45e8179806541883c5dbf19982b2b5318f..832059f8c049be7fea164771eb13b8fb1b9334f2 100644 --- a/crates/editor/src/scroll/autoscroll.rs +++ b/crates/editor/src/scroll/autoscroll.rs @@ -115,7 +115,7 @@ impl Editor { let viewport_height = bounds.size.height; let visible_lines = ScrollOffset::from(viewport_height / line_height); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut scroll_position = self.scroll_manager.scroll_position(&display_map); + let mut scroll_position = self.scroll_manager.scroll_position(&display_map, cx); let original_y = scroll_position.y; if let Some(last_bounds) = self.expect_bounds_change.take() && scroll_position.y != 0. @@ -201,7 +201,7 @@ impl Editor { .last_autoscroll .as_ref() .filter(|(offset, last_target_top, last_target_bottom, _)| { - self.scroll_manager.anchor.offset == *offset + self.scroll_manager.offset(cx) == *offset && target_top == *last_target_top && target_bottom == *last_target_bottom }) @@ -264,7 +264,7 @@ impl Editor { }; self.scroll_manager.last_autoscroll = Some(( - self.scroll_manager.anchor.offset, + self.scroll_manager.offset(cx), target_top, target_bottom, strategy, @@ -292,7 +292,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let selections = self.selections.all::(&display_map); - let mut scroll_position = self.scroll_manager.scroll_position(&display_map); + let mut scroll_position = self.scroll_manager.scroll_position(&display_map, cx); let mut target_left; let mut target_right: f64; @@ -334,7 +334,7 @@ impl Editor { return None; } - let scroll_left = self.scroll_manager.anchor.offset.x * em_advance; + let scroll_left = self.scroll_manager.offset(cx).x * em_advance; let scroll_right = scroll_left + viewport_width; let was_scrolled = if target_left < scroll_left { diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index 92859d1d25c2a2f2f51f4a7abcad33378d8e858a..b6c94d8bc37f8ba7707f1ede5b08f63d89c5a4b5 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -527,12 +527,19 @@ impl SplittableEditor { dm.set_companion(Some((rhs_display_map.downgrade(), companion)), cx); }); - let rhs_weak = self.rhs_editor.downgrade(); - let lhs_weak = lhs.editor.downgrade(); + let shared_scroll_anchor = self + .rhs_editor + .read(cx) + .scroll_manager + .scroll_anchor_entity(); + lhs.editor.update(cx, |editor, _cx| { + editor + .scroll_manager + .set_shared_scroll_anchor(shared_scroll_anchor); + }); let this = cx.entity().downgrade(); self.rhs_editor.update(cx, |editor, _cx| { - editor.set_scroll_companion(Some(lhs_weak)); let this = this.clone(); editor.set_on_local_selections_changed(Some(Box::new( move |cursor_position, window, cx| { @@ -549,7 +556,6 @@ impl SplittableEditor { ))); }); lhs.editor.update(cx, |editor, _cx| { - editor.set_scroll_companion(Some(rhs_weak)); let this = this.clone(); editor.set_on_local_selections_changed(Some(Box::new( move |cursor_position, window, cx| { @@ -566,13 +572,6 @@ impl SplittableEditor { ))); }); - let rhs_scroll_position = self - .rhs_editor - .update(cx, |editor, cx| editor.scroll_position(cx)); - lhs.editor.update(cx, |editor, cx| { - editor.set_scroll_position_internal(rhs_scroll_position, false, false, window, cx); - }); - // Copy soft wrap state from rhs (source of truth) to lhs let rhs_soft_wrap_override = self.rhs_editor.read(cx).soft_wrap_mode_override; lhs.editor.update(cx, |editor, cx| { @@ -848,8 +847,17 @@ impl SplittableEditor { }; self.panes.remove(&lhs.pane, cx).unwrap(); self.rhs_editor.update(cx, |rhs, cx| { + let rhs_snapshot = rhs.display_map.update(cx, |dm, cx| dm.snapshot(cx)); + let native_anchor = rhs.scroll_manager.native_anchor(&rhs_snapshot, cx); + let rhs_display_map_id = rhs_snapshot.display_map_id; + rhs.scroll_manager + .scroll_anchor_entity() + .update(cx, |shared, _| { + shared.scroll_anchor = native_anchor; + shared.display_map_id = Some(rhs_display_map_id); + }); + rhs.set_on_local_selections_changed(None); - rhs.set_scroll_companion(None); rhs.set_delegate_expand_excerpts(false); rhs.buffer().update(cx, |buffer, cx| { buffer.set_show_deleted_hunks(true, cx); @@ -861,7 +869,6 @@ impl SplittableEditor { }); lhs.editor.update(cx, |editor, _cx| { editor.set_on_local_selections_changed(None); - editor.set_scroll_companion(None); }); cx.notify(); } @@ -1671,6 +1678,7 @@ mod tests { async fn init_test( cx: &mut gpui::TestAppContext, + soft_wrap: SoftWrap, ) -> (Entity, &mut VisualTestContext) { cx.update(|cx| { let store = SettingsStore::test(cx); @@ -1696,7 +1704,7 @@ mod tests { ); editor.split(&Default::default(), window, cx); editor.rhs_editor.update(cx, |editor, cx| { - editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); + editor.set_soft_wrap_mode(soft_wrap, cx); }); editor .lhs @@ -1704,7 +1712,7 @@ mod tests { .unwrap() .editor .update(cx, |editor, cx| { - editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); + editor.set_soft_wrap_mode(soft_wrap, cx); }); editor }); @@ -1775,7 +1783,7 @@ mod tests { async fn test_random_split_editor(mut rng: StdRng, cx: &mut gpui::TestAppContext) { use rand::prelude::*; - let (editor, cx) = init_test(cx).await; + let (editor, cx) = init_test(cx, SoftWrap::EditorWidth).await; let operations = std::env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -1859,7 +1867,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = " aaa @@ -1988,7 +1996,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text1 = " aaa @@ -2146,7 +2154,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = " aaa @@ -2265,7 +2273,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = " aaa @@ -2394,7 +2402,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = " aaa @@ -2519,7 +2527,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = " aaa @@ -2632,7 +2640,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let text = "aaaa bbbb cccc dddd eeee ffff"; @@ -2700,7 +2708,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = " aaaa bbbb cccc dddd eeee ffff @@ -2762,7 +2770,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = " aaaa bbbb cccc dddd eeee ffff @@ -2831,7 +2839,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let text = " aaaa bbbb cccc dddd eeee ffff @@ -2943,7 +2951,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let (buffer1, diff1) = buffer_with_diff("xxx\nyyy", "xxx\nyyy", &mut cx); @@ -3047,7 +3055,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = " aaa @@ -3129,7 +3137,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = "aaaa bbbb cccc dddd eeee ffff\n"; @@ -3208,7 +3216,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = " aaa @@ -3330,7 +3338,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = ""; let current_text = " @@ -3406,7 +3414,7 @@ mod tests { use rope::Point; use unindent::Unindent as _; - let (editor, mut cx) = init_test(cx).await; + let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await; let base_text = " aaa @@ -3494,4 +3502,88 @@ mod tests { &mut cx, ); } + + #[gpui::test] + async fn test_scrolling(cx: &mut gpui::TestAppContext) { + use crate::test::editor_content_with_blocks_and_size; + use gpui::size; + use rope::Point; + + let (editor, mut cx) = init_test(cx, SoftWrap::None).await; + + let long_line = "x".repeat(200); + let mut lines: Vec = (0..50).map(|i| format!("line {i}")).collect(); + lines[25] = long_line; + let content = lines.join("\n"); + + let (buffer, diff) = buffer_with_diff(&content, &content, &mut cx); + + editor.update(cx, |editor, cx| { + let path = PathKey::for_buffer(&buffer, cx); + editor.set_excerpts_for_path( + path, + buffer.clone(), + vec![Point::new(0, 0)..buffer.read(cx).max_point()], + 0, + diff.clone(), + cx, + ); + }); + + cx.run_until_parked(); + + let (rhs_editor, lhs_editor) = editor.update(cx, |editor, _cx| { + let lhs = editor.lhs.as_ref().expect("should have lhs editor"); + (editor.rhs_editor.clone(), lhs.editor.clone()) + }); + + rhs_editor.update_in(cx, |e, window, cx| { + e.set_scroll_position(gpui::Point::new(0., 10.), window, cx); + }); + + let rhs_pos = + rhs_editor.update_in(cx, |e, window, cx| e.snapshot(window, cx).scroll_position()); + let lhs_pos = + lhs_editor.update_in(cx, |e, window, cx| e.snapshot(window, cx).scroll_position()); + assert_eq!(rhs_pos.y, 10., "RHS should be scrolled to row 10"); + assert_eq!( + lhs_pos.y, rhs_pos.y, + "LHS should have same scroll position as RHS after set_scroll_position" + ); + + let draw_size = size(px(300.), px(300.)); + + rhs_editor.update_in(cx, |e, window, cx| { + e.change_selections(Some(crate::Autoscroll::fit()).into(), window, cx, |s| { + s.select_ranges([Point::new(25, 150)..Point::new(25, 150)]); + }); + }); + + let _ = editor_content_with_blocks_and_size(&rhs_editor, draw_size, &mut cx); + cx.run_until_parked(); + let _ = editor_content_with_blocks_and_size(&lhs_editor, draw_size, &mut cx); + cx.run_until_parked(); + + let rhs_pos = + rhs_editor.update_in(cx, |e, window, cx| e.snapshot(window, cx).scroll_position()); + let lhs_pos = + lhs_editor.update_in(cx, |e, window, cx| e.snapshot(window, cx).scroll_position()); + + assert!( + rhs_pos.y > 0., + "RHS should have scrolled vertically to show cursor at row 25" + ); + assert!( + rhs_pos.x > 0., + "RHS should have scrolled horizontally to show cursor at column 150" + ); + assert_eq!( + lhs_pos.y, rhs_pos.y, + "LHS should have same vertical scroll position as RHS after autoscroll" + ); + assert_eq!( + lhs_pos.x, rhs_pos.x, + "LHS should have same horizontal scroll position as RHS after autoscroll" + ); + } } diff --git a/crates/editor/src/split_editor_view.rs b/crates/editor/src/split_editor_view.rs index 7e60deca54693fecea4eb13bb71153974d10aa74..454013c530ab8389314892011e5eb115ee6e0957 100644 --- a/crates/editor/src/split_editor_view.rs +++ b/crates/editor/src/split_editor_view.rs @@ -9,6 +9,7 @@ use gpui::{ }; use multi_buffer::{Anchor, ExcerptId}; use settings::Settings; +use smallvec::smallvec; use text::BufferId; use theme::ActiveTheme; use ui::scrollbars::ShowScrollbar; @@ -171,7 +172,10 @@ impl RenderOnce for SplitEditorView { let state_for_drag = self.split_state.downgrade(); let state_for_drop = self.split_state.downgrade(); - let buffer_headers = SplitBufferHeadersElement::new(rhs_editor, self.style.clone()); + let buffer_headers = SplitBufferHeadersElement::new(rhs_editor.clone(), self.style.clone()); + + let lhs_editor_for_order = lhs_editor; + let rhs_editor_for_order = rhs_editor; div() .id("split-editor-view-container") @@ -179,6 +183,14 @@ impl RenderOnce for SplitEditorView { .relative() .child( h_flex() + .with_dynamic_prepaint_order(move |_window, cx| { + let lhs_needs = lhs_editor_for_order.read(cx).has_autoscroll_request(); + let rhs_needs = rhs_editor_for_order.read(cx).has_autoscroll_request(); + match (lhs_needs, rhs_needs) { + (false, true) => smallvec![2, 1, 0], + _ => smallvec![0, 1, 2], + } + }) .id("split-editor-view") .size_full() .on_drag_move::(move |event, window, cx| { diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 474a3c16d6829d5faea1d2d0ad658b15f0f63eea..92fccf1e0579a79cd891047686d8312bc7541b1e 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -5,7 +5,7 @@ use std::{rc::Rc, sync::LazyLock}; pub use crate::rust_analyzer_ext::expand_macro_recursively; use crate::{ - DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, SelectionEffects, + DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, SelectionEffects, Size, display_map::{ Block, BlockPlacement, CustomBlockId, DisplayMap, DisplayRow, DisplaySnapshot, ToDisplayPoint, @@ -184,7 +184,14 @@ pub fn editor_content_with_blocks_and_width( width: Pixels, cx: &mut VisualTestContext, ) -> String { - let draw_size = size(width, px(3000.0)); + editor_content_with_blocks_and_size(editor, size(width, px(3000.0)), cx) +} + +pub fn editor_content_with_blocks_and_size( + editor: &Entity, + draw_size: Size, + cx: &mut VisualTestContext, +) -> String { cx.simulate_resize(draw_size); cx.draw(gpui::Point::default(), draw_size, |_, _| editor.clone()); let (snapshot, mut lines, blocks) = editor.update_in(cx, |editor, window, cx| { diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 267058691d0070678830ba9d7c40f54a9363737b..7e335f93e30d1568e4ff699520e0b59b02a30144 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -287,7 +287,7 @@ impl EditorTestContext { .text .line_height_in_pixels(window.rem_size()); let snapshot = editor.snapshot(window, cx); - let details = editor.text_layout_details(window); + let details = editor.text_layout_details(window, cx); let y = pixel_position.y + f32::from(line_height) diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index ad561d6f778aca1c9fe87729674d5d474431f0cb..0d079ee9485e327751711473ed7ab2d61ae8721d 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1295,6 +1295,7 @@ pub fn div() -> Div { children: SmallVec::default(), prepaint_listener: None, image_cache: None, + prepaint_order_fn: None, } } @@ -1304,6 +1305,7 @@ pub struct Div { children: SmallVec<[StackSafe; 2]>, prepaint_listener: Option>, &mut Window, &mut App) + 'static>>, image_cache: Option>, + prepaint_order_fn: Option SmallVec<[usize; 8]>>>, } impl Div { @@ -1322,6 +1324,22 @@ impl Div { self.image_cache = Some(Box::new(cache)); self } + + /// Specify a function that determines the order in which children are prepainted. + /// + /// The function is called at prepaint time and should return a vector of child indices + /// in the desired prepaint order. Each index should appear exactly once. + /// + /// This is useful when the prepaint of one child affects state that another child reads. + /// For example, in split editor views, the editor with an autoscroll request should + /// be prepainted first so its scroll position update is visible to the other editor. + pub fn with_dynamic_prepaint_order( + mut self, + order_fn: impl Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]> + 'static, + ) -> Self { + self.prepaint_order_fn = Some(Box::new(order_fn)); + self + } } /// A frame state for a `Div` element, which contains layout IDs for its children. @@ -1486,8 +1504,17 @@ impl Element for Div { window.with_image_cache(image_cache, |window| { window.with_element_offset(scroll_offset, |window| { - for child in &mut self.children { - child.prepaint(window, cx); + if let Some(order_fn) = &self.prepaint_order_fn { + let order = order_fn(window, cx); + for idx in order { + if let Some(child) = self.children.get_mut(idx) { + child.prepaint(window, cx); + } + } + } else { + for child in &mut self.children { + child.prepaint(window, cx); + } } }); diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 888ba58c83a0f545e7ec43a79511e2b2347e7e62..f9a0f4c6e7b4bc4af2ae53e9f1f0ae9c06a0960c 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1313,7 +1313,7 @@ impl BufferSearchBar { let search = self.update_matches(false, true, window, cx); let width = editor.update(cx, |editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); let snapshot = editor.snapshot(window, cx).display_snapshot; snapshot.x_for_display_point(snapshot.max_point(), &text_layout_details) diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 2498ed812938c5b53120e55663ce9ba9a8787d88..423c3b387b197edd2d8e86398b09157fdcb7711a 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -2355,7 +2355,7 @@ impl Vim { let start = editor .selections .newest_display(&editor.display_snapshot(cx)); - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); let (mut range, _) = motion .range( &snapshot, diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index cfcf874e0325023107bf2dc779029738fe7b6bca..2c25ed267df415a63acc5383f3dc6e9befdc2eea 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -128,7 +128,7 @@ impl Vim { cx: &mut Context, ) { self.update_editor(cx, |_, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.change_selections(Default::default(), window, cx, |s| { if let Motion::ZedSearchResult { new_selections, .. } = &motion { s.select_anchor_ranges(new_selections.clone()); @@ -319,7 +319,7 @@ impl Vim { cx: &mut Context, ) { self.update_editor(cx, |_, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; @@ -399,7 +399,7 @@ impl Vim { // In Helix mode, EndOfLine should position cursor ON the last character, // not after it. We therefore need special handling for it. self.update_editor(cx, |_, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; diff --git a/crates/vim/src/indent.rs b/crates/vim/src/indent.rs index 927edf4d9aa01502fd2112c1cb5b3fb5af12145f..287eca3ff8cbead1bcae89f802fbb5189ae88f56 100644 --- a/crates/vim/src/indent.rs +++ b/crates/vim/src/indent.rs @@ -102,7 +102,7 @@ impl Vim { ) { self.stop_recording(cx); self.update_editor(cx, |_, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 837bca2ab11b36c2a48eda76350156c2f8a141fe..7447ba14ea46b06887a7caf8a4fbb13d5360c6cf 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -2871,8 +2871,7 @@ fn window_top( ) -> (DisplayPoint, SelectionGoal) { let first_visible_line = text_layout_details .scroll_anchor - .anchor - .to_display_point(map); + .scroll_top_display_point(map); if first_visible_line.row() != DisplayRow(0) && text_layout_details.vertical_scroll_margin as usize > times @@ -2907,8 +2906,7 @@ fn window_middle( if let Some(visible_rows) = text_layout_details.visible_rows { let first_visible_line = text_layout_details .scroll_anchor - .anchor - .to_display_point(map); + .scroll_top_display_point(map); let max_visible_rows = (visible_rows as u32).min(map.max_point().row().0 - first_visible_line.row().0); @@ -2933,10 +2931,10 @@ fn window_bottom( if let Some(visible_rows) = text_layout_details.visible_rows { let first_visible_line = text_layout_details .scroll_anchor - .anchor - .to_display_point(map); + .scroll_top_display_point(map); let bottom_row = first_visible_line.row().0 - + (visible_rows + text_layout_details.scroll_anchor.offset.y - 1.).floor() as u32; + + (visible_rows + text_layout_details.scroll_anchor.scroll_anchor.offset.y - 1.).floor() + as u32; if bottom_row < map.max_point().row().0 && text_layout_details.vertical_scroll_margin as usize > times { diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 4437f4dc91f1cb6f0d2009b46d4dfbbc7d6c99e9..19e3ab214509e197c53c21aa9f41c7ab96c41979 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -579,7 +579,7 @@ impl Vim { cx: &mut Context, ) { self.update_editor(cx, |vim, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); // If vim is in temporary mode and the motion being used is // `EndOfLine` ($), we'll want to disable clipping at line ends so @@ -748,7 +748,7 @@ impl Vim { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); self.update_editor(cx, |_, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { let selections = editor.selections.all::(&editor.display_snapshot(cx)); let snapshot = editor.buffer().read(cx).snapshot(cx); diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index b0b0bddae19b27fa382d4c84c3fdd4df8ba83a43..64b6d1e99e7500f349529575b4c743e9055ef4cc 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -35,7 +35,7 @@ impl Vim { None }; self.update_editor(cx, |vim, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { // We are swapping to insert mode anyway. Just set the line end clipping behavior now editor.set_clip_at_line_ends(false, cx); diff --git a/crates/vim/src/normal/convert.rs b/crates/vim/src/normal/convert.rs index 0ee132a44d20723970fecbbef4cef13ff31e310c..5cbeadbd6aeb134ef4cd16f755e89e04ff079a48 100644 --- a/crates/vim/src/normal/convert.rs +++ b/crates/vim/src/normal/convert.rs @@ -33,7 +33,7 @@ impl Vim { self.stop_recording(cx); self.update_editor(cx, |_, editor, cx| { editor.set_clip_at_line_ends(false, cx); - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index b1c41315a82df7646531c16a5b701d46a8ba82fd..bf6c6c97f0d4275f1070072a035e63ca9af76c9d 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -24,7 +24,7 @@ impl Vim { ) { self.stop_recording(cx); self.update_editor(cx, |vim, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); let mut original_columns: HashMap<_, _> = Default::default(); diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 9ae168c6f62b59a4c149c6b32ae9a830fc1b6c21..1acbe2b10797e81b95b701319cba27a22f9c2177 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -36,7 +36,7 @@ impl Vim { Vim::take_forced_motion(cx); self.update_editor(cx, |vim, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); @@ -283,7 +283,7 @@ impl Vim { self.stop_recording(cx); let selected_register = self.selected_register.take(); self.update_editor(cx, |_, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { diff --git a/crates/vim/src/normal/scroll.rs b/crates/vim/src/normal/scroll.rs index 6360dc1ecd1ed1f39059e0be54253240c486b698..cc4e2b2b3473895d9fc8bccddd9d2c4df67a4275 100644 --- a/crates/vim/src/normal/scroll.rs +++ b/crates/vim/src/normal/scroll.rs @@ -1,7 +1,6 @@ use crate::{Vim, state::Mode}; use editor::{ - DisplayPoint, Editor, EditorSettings, SelectionEffects, - display_map::{DisplayRow, ToDisplayPoint}, + DisplayPoint, Editor, EditorSettings, SelectionEffects, display_map::DisplayRow, scroll::ScrollAmount, }; use gpui::{Context, Window, actions}; @@ -113,7 +112,10 @@ fn scroll_editor( cx: &mut Context, ) { let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq(); - let old_top_anchor = editor.scroll_manager.anchor().anchor; + let display_snapshot = editor.display_map.update(cx, |map, cx| map.snapshot(cx)); + let old_top = editor + .scroll_manager + .scroll_top_display_point(&display_snapshot, cx); if editor.scroll_hover(amount, window, cx) { return; @@ -144,7 +146,10 @@ fn scroll_editor( return; }; - let top_anchor = editor.scroll_manager.anchor().anchor; + let display_snapshot = editor.display_map.update(cx, |map, cx| map.snapshot(cx)); + let top = editor + .scroll_manager + .scroll_top_display_point(&display_snapshot, cx); let vertical_scroll_margin = EditorSettings::get_global(cx).vertical_scroll_margin; editor.change_selections( @@ -159,7 +164,6 @@ fn scroll_editor( // so we don't need to calculate both and deal with logic for // both. let mut head = selection.head(); - let top = top_anchor.to_display_point(map); let max_point = map.max_point(); let starting_column = head.column(); @@ -167,7 +171,6 @@ fn scroll_editor( (vertical_scroll_margin as u32).min(visible_line_count as u32 / 2); if preserve_cursor_position { - let old_top = old_top_anchor.to_display_point(map); let new_row = if old_top.row() == top.row() { DisplayRow( head.row() @@ -175,7 +178,9 @@ fn scroll_editor( .saturating_add_signed(amount.lines(visible_line_count) as i32), ) } else { - DisplayRow(top.row().0 + selection.head().row().0 - old_top.row().0) + DisplayRow(top.row().0.saturating_add_signed( + selection.head().row().0 as i32 - old_top.row().0 as i32, + )) }; head = map.clip_point(DisplayPoint::new(new_row, head.column()), Bias::Left) } @@ -222,7 +227,7 @@ fn scroll_editor( // maximum column for the current line, so the minimum column // would end up being the same as the maximum column. let min_column = match preserve_cursor_position { - true => old_top_anchor.to_display_point(map).column(), + true => old_top.column(), false => top.column(), }; diff --git a/crates/vim/src/normal/substitute.rs b/crates/vim/src/normal/substitute.rs index df8d7b4879e21491ed808de1dad78cfebc5b12ec..f1b70b6acc4ef35b14afe59ab8a8ff741ad16844 100644 --- a/crates/vim/src/normal/substitute.rs +++ b/crates/vim/src/normal/substitute.rs @@ -48,7 +48,7 @@ impl Vim { self.update_editor(cx, |vim, editor, cx| { editor.set_clip_at_line_ends(false, cx); editor.transact(window, cx, |editor, window, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { if selection.start == selection.end { diff --git a/crates/vim/src/normal/toggle_comments.rs b/crates/vim/src/normal/toggle_comments.rs index 17c3b2d363e308f2683a48483286a845e0844ccf..1e4726999dd3efbb0c440d2da0680d1929ebf736 100644 --- a/crates/vim/src/normal/toggle_comments.rs +++ b/crates/vim/src/normal/toggle_comments.rs @@ -15,7 +15,7 @@ impl Vim { ) { self.stop_recording(cx); self.update_editor(cx, |_, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { diff --git a/crates/vim/src/normal/yank.rs b/crates/vim/src/normal/yank.rs index 9920b8fc88d86625a1eb6642f59c894730905c77..c1d90130de2af30f9c5c68fa72883c094ef30ce6 100644 --- a/crates/vim/src/normal/yank.rs +++ b/crates/vim/src/normal/yank.rs @@ -25,7 +25,7 @@ impl Vim { cx: &mut Context, ) { self.update_editor(cx, |vim, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); let mut original_positions: HashMap<_, _> = Default::default(); diff --git a/crates/vim/src/replace.rs b/crates/vim/src/replace.rs index 63d452f84bfd5ee1cea8970698962169dc8fe94a..866890e321d2ee762f9406bf643d287ea7ef2df5 100644 --- a/crates/vim/src/replace.rs +++ b/crates/vim/src/replace.rs @@ -197,7 +197,7 @@ impl Vim { self.stop_recording(cx); self.update_editor(cx, |vim, editor, cx| { editor.set_clip_at_line_ends(false, cx); - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); let mut selection = editor .selections .newest_display(&editor.display_snapshot(cx)); diff --git a/crates/vim/src/rewrap.rs b/crates/vim/src/rewrap.rs index 85e1967af040856f4ba01a2e604c3e637e9b1f2c..137b6e88abe59e77dba361f2643e017b0575e852 100644 --- a/crates/vim/src/rewrap.rs +++ b/crates/vim/src/rewrap.rs @@ -56,7 +56,7 @@ impl Vim { ) { self.stop_recording(cx); self.update_editor(cx, |_, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 07dec88a2262c26a2776b4580afb3e1b44b0e911..3732475b6b9d97118f8c2b13b4259446a8314387 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -88,7 +88,7 @@ impl Vim { let forced_motion = Vim::take_forced_motion(cx); let mode = self.mode; self.update_editor(cx, |_, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 5667190bb7239ee3e534a5556d96452a7c68b1ef..588a87367143a3276a1c9519f4bef49676e2ed80 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -218,7 +218,7 @@ impl Vim { cx: &mut Context, ) { self.update_editor(cx, |vim, editor, cx| { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); if vim.mode == Mode::VisualBlock && !matches!( motion, @@ -302,7 +302,7 @@ impl Vim { SelectionGoal, ) -> Option<(DisplayPoint, SelectionGoal)>, ) { - let text_layout_details = editor.text_layout_details(window); + let text_layout_details = editor.text_layout_details(window, cx); editor.change_selections(Default::default(), window, cx, |s| { let map = &s.display_snapshot(); let mut head = s.newest_anchor().head().to_display_point(map); diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 727e0d474b8529f2eaf2c9613abd213f620be73f..3e0839482c61978d45ff7b61ade464410135af8c 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1190,7 +1190,7 @@ pub enum Dedup { pub trait FollowableItem: Item { fn remote_id(&self) -> Option; - fn to_state_proto(&self, window: &Window, cx: &App) -> Option; + fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option; fn from_state_proto( project: Entity, id: ViewId, @@ -1203,8 +1203,8 @@ pub trait FollowableItem: Item { &self, event: &Self::Event, update: &mut Option, - window: &Window, - cx: &App, + window: &mut Window, + cx: &mut App, ) -> bool; fn apply_update_proto( &mut self, @@ -1284,7 +1284,7 @@ impl FollowableItemHandle for Entity { } fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option { - self.read(cx).to_state_proto(window, cx) + self.update(cx, |this, cx| this.to_state_proto(window, cx)) } fn add_event_to_update_proto( @@ -1295,8 +1295,9 @@ impl FollowableItemHandle for Entity { cx: &mut App, ) -> bool { if let Some(event) = event.downcast_ref() { - self.read(cx) - .add_event_to_update_proto(event, update, window, cx) + self.update(cx, |this, cx| { + this.add_event_to_update_proto(event, update, window, cx) + }) } else { false }