git: Fix desynced scrolling between LHS and RHS of side-by-side diff (#47913)
- Remove old attempt to sync scrolling - Share a `ScrollAnchor` between the two sides, and be sure to resolve it against the correct snapshot - Allow either side to initiate an autoscroll request, and make sure that request is processed in the same frame by the other side Release Notes: - N/A --------- Co-authored-by: cameron <cameron.studdstreet@gmail.com> Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com> Co-authored-by: Jakub <jakub@zed.dev>
This commit is contained in:
1 parent
84b8478185
commit
6cc71471f0
36 files changed
+602
-318
No files matched your search
@@ -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.));
|
||||
|
||||
+67
-54
@@ -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");
|
||||
|
||||
|
||||
+185
-51
@@ -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 {
|
||||
|
||||
+124
-32
@@ -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
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user