Detailed changes
@@ -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<proto::view::Variant> {
- let text_thread = self.text_thread.read(cx);
+ fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option<proto::view::Variant> {
+ 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<proto::update_view::Variant>,
- 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(
@@ -563,18 +563,22 @@ impl FollowableItem for ChannelView {
self.remote_id
}
- fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant> {
- 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<proto::view::Variant> {
+ 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<proto::update_view::Variant>,
- 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(
@@ -141,7 +141,7 @@ impl FollowableItem for DebugSession {
self.remote_id
}
- fn to_state_proto(&self, _window: &Window, _cx: &App) -> Option<proto::view::Variant> {
+ fn to_state_proto(&self, _window: &mut Window, _cx: &mut App) -> Option<proto::view::Variant> {
None
}
@@ -159,8 +159,8 @@ impl FollowableItem for DebugSession {
&self,
_event: &Self::Event,
_update: &mut Option<proto::update_view::Variant>,
- _window: &Window,
- _cx: &App,
+ _window: &mut Window,
+ _cx: &mut App,
) -> bool {
// update.get_or_insert_with(|| proto::update_view::Variant::DebugPanel(Default::default()));
@@ -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<Self>) -> 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<Arc<DisplaySnapshot>>,
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.));
@@ -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<ScrollAnchor>,
+ previous_scroll_position: Option<SharedScrollAnchor>,
},
}
@@ -703,7 +703,7 @@ impl EditPredictionPreview {
}
}
- pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
+ pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
if let EditPredictionPreview::Active {
previous_scroll_position,
..
@@ -1332,7 +1332,6 @@ pub struct Editor {
folding_newlines: Task<()>,
select_next_is_case_sensitive: Option<bool>,
pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
- scroll_companion: Option<WeakEntity<Editor>>,
on_local_selections_changed:
Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
suppress_selection_callback: bool,
@@ -1388,7 +1387,7 @@ pub struct EditorSnapshot {
pub display_snapshot: DisplaySnapshot,
pub placeholder_display_snapshot: Option<DisplaySnapshot>,
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<Vec<OutlineItem<Anchor>>> {
@@ -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<Editor>,
) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
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>) {
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<MultiBufferOffset>, 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>,
) {
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>,
) {
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>) {
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>) {
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<Self>) -> 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<NavigationEntry> {
+ fn navigation_entry(
+ &self,
+ cursor_anchor: Anchor,
+ cx: &mut Context<Self>,
+ ) -> Option<NavigationEntry> {
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::<Point>(&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<WeakEntity<Editor>>) {
- self.scroll_companion = companion;
- }
-
- pub fn scroll_companion(&self) -> Option<&WeakEntity<Editor>> {
- self.scroll_companion.as_ref()
- }
-
pub fn set_on_local_selections_changed(
&mut self,
callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + '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<gpui::Point<Pixels>> {
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<gpui::Point<Pixels>> {
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::<T>())
}
- 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<Self>,
) -> Option<gpui::Bounds<Pixels>> {
- 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();
@@ -938,19 +938,34 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
// Set scroll position to check later
editor.set_scroll_position(gpui::Point::<f64>::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::<f64>::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();
}
@@ -192,23 +192,10 @@ struct RenderBlocksOutput {
resized_blocks: Option<HashMap<CustomBlockId, u32>>,
}
-/// Data passed to overlay painters during the paint phase.
-pub struct OverlayPainterData<'a> {
- pub editor: &'a Entity<Editor>,
- pub snapshot: &'a EditorSnapshot,
- pub scroll_position: gpui::Point<ScrollOffset>,
- pub line_height: Pixels,
- pub visible_row_range: Range<DisplayRow>,
- pub hitbox: &'a Hitbox,
-}
-
-pub type OverlayPainter = Box<dyn FnOnce(OverlayPainterData<'_>, &mut Window, &mut App)>;
-
pub struct EditorElement {
editor: Entity<Editor>,
style: EditorStyle,
split_side: Option<SplitSide>,
- overlay_painter: Option<OverlayPainter>,
}
#[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<EditorScrollbars> {
- 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::<DisplayRow>::new();
let mut rows = Vec::<StickyHeader>::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);
- }
});
})
})
@@ -204,17 +204,20 @@ impl FollowableItem for Editor {
cx.notify();
}
- fn to_state_proto(&self, _: &Window, cx: &App) -> Option<proto::view::Variant> {
- let buffer = self.buffer.read(cx);
- if buffer
+ fn to_state_proto(&self, _: &mut Window, cx: &mut App) -> Option<proto::view::Variant> {
+ 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<proto::update_view::Variant>,
- _: &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;
@@ -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),
@@ -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<WindowTextSystem>,
pub(crate) editor_style: EditorStyle,
pub(crate) rem_size: Pixels,
- pub scroll_anchor: ScrollAnchor,
+ pub scroll_anchor: SharedScrollAnchor,
pub visible_rows: Option<f64>,
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");
@@ -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<Axis>,
}
+/// 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<EntityId>,
+}
+
+impl SharedScrollAnchor {
+ pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
+ 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<SharedScrollAnchor>,
+ /// 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<f64>,
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<Editor>) -> 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<Editor>,
+ ) {
+ 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<Editor>,
+ ) {
+ 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<f64> {
+ 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<SharedScrollAnchor> {
+ self.anchor.clone()
+ }
+
+ pub fn set_shared_scroll_anchor(&mut self, entity: Entity<SharedScrollAnchor>) {
+ 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<ScrollOffset> {
- self.anchor.scroll_position(snapshot)
+ pub fn scroll_position(
+ &self,
+ snapshot: &DisplaySnapshot,
+ cx: &App,
+ ) -> gpui::Point<ScrollOffset> {
+ 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<Editor>,
) -> 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<Self>) -> gpui::Point<ScrollOffset> {
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;
@@ -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::<Point>(&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 {
@@ -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<SplittableEditor>, &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<String> = (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"
+ );
+ }
}
@@ -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::<DraggedSplitHandle>(move |event, window, cx| {
@@ -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<Editor>,
+ draw_size: Size<Pixels>,
+ 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| {
@@ -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)
@@ -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<AnyElement>; 2]>,
prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
image_cache: Option<Box<dyn ImageCacheProvider>>,
+ prepaint_order_fn: Option<Box<dyn Fn(&mut Window, &mut App) -> 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);
+ }
}
});
@@ -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)
@@ -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,
@@ -128,7 +128,7 @@ impl Vim {
cx: &mut Context<Self>,
) {
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>,
) {
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;
@@ -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| {
@@ -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
{
@@ -579,7 +579,7 @@ impl Vim {
cx: &mut Context<Self>,
) {
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::<Point>(&editor.display_snapshot(cx));
let snapshot = editor.buffer().read(cx).snapshot(cx);
@@ -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);
@@ -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| {
@@ -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();
@@ -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| {
@@ -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<Editor>,
) {
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(),
};
@@ -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 {
@@ -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| {
@@ -25,7 +25,7 @@ impl Vim {
cx: &mut Context<Self>,
) {
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();
@@ -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));
@@ -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| {
@@ -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);
@@ -218,7 +218,7 @@ impl Vim {
cx: &mut Context<Self>,
) {
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);
@@ -1190,7 +1190,7 @@ pub enum Dedup {
pub trait FollowableItem: Item {
fn remote_id(&self) -> Option<ViewId>;
- fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant>;
+ fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option<proto::view::Variant>;
fn from_state_proto(
project: Entity<Workspace>,
id: ViewId,
@@ -1203,8 +1203,8 @@ pub trait FollowableItem: Item {
&self,
event: &Self::Event,
update: &mut Option<proto::update_view::Variant>,
- window: &Window,
- cx: &App,
+ window: &mut Window,
+ cx: &mut App,
) -> bool;
fn apply_update_proto(
&mut self,
@@ -1284,7 +1284,7 @@ impl<T: FollowableItem> FollowableItemHandle for Entity<T> {
}
fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option<proto::view::Variant> {
- 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<T: FollowableItem> FollowableItemHandle for Entity<T> {
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
}