editor: Preserve scroll position when jumping from multibuffer (#9921)

This is a best-effort attempt, as the target offset from the top is just
an estimate; furthermore, this does not account for things like project
search header (which adds a bit of vertical offset by itself and is
removed once we jump into a buffer), but it still should improve the
situation quite a bit.

Fixes: #5296

Release Notes:

- Improved target selection when jumping from multibuffer; final
position in the buffer should more closely match the original position
of the cursor in the multibuffer.
This commit is contained in:
Piotr Osiewicz 2024-03-28 18:33:57 +01:00 committed by GitHub
parent 7f54935324
commit fcaf4383e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 44 additions and 7 deletions

View file

@ -7670,7 +7670,7 @@ impl Editor {
let range = target.range.to_offset(target.buffer.read(cx)); let range = target.range.to_offset(target.buffer.read(cx));
let range = editor.range_for_match(&range); let range = editor.range_for_match(&range);
if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() { if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| { editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
s.select_ranges([range]); s.select_ranges([range]);
}); });
} else { } else {
@ -7690,7 +7690,7 @@ impl Editor {
// to avoid creating a history entry at the previous cursor location. // to avoid creating a history entry at the previous cursor location.
pane.update(cx, |pane, _| pane.disable_history()); pane.update(cx, |pane, _| pane.disable_history());
target_editor.change_selections( target_editor.change_selections(
Some(Autoscroll::fit()), Some(Autoscroll::focused()),
cx, cx,
|s| { |s| {
s.select_ranges([range]); s.select_ranges([range]);
@ -9504,6 +9504,7 @@ impl Editor {
path: ProjectPath, path: ProjectPath,
position: Point, position: Point,
anchor: language::Anchor, anchor: language::Anchor,
offset_from_top: u32,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let workspace = self.workspace(); let workspace = self.workspace();
@ -9531,9 +9532,13 @@ impl Editor {
}; };
let nav_history = editor.nav_history.take(); let nav_history = editor.nav_history.take();
editor.change_selections(Some(Autoscroll::newest()), cx, |s| { editor.change_selections(
s.select_ranges([cursor..cursor]); Some(Autoscroll::top_relative(offset_from_top as usize)),
}); cx,
|s| {
s.select_ranges([cursor..cursor]);
},
);
editor.nav_history = nav_history; editor.nav_history = nav_history;
anyhow::Ok(()) anyhow::Ok(())

View file

@ -1406,6 +1406,7 @@ impl EditorElement {
let render_block = |block: &TransformBlock, let render_block = |block: &TransformBlock,
available_space: Size<AvailableSpace>, available_space: Size<AvailableSpace>,
block_id: usize, block_id: usize,
block_row_start: u32,
cx: &mut ElementContext| { cx: &mut ElementContext| {
let mut element = match block { let mut element = match block {
TransformBlock::Custom(block) => { TransformBlock::Custom(block) => {
@ -1440,6 +1441,7 @@ impl EditorElement {
buffer, buffer,
range, range,
starts_new_buffer, starts_new_buffer,
height,
.. ..
} => { } => {
let include_root = self let include_root = self
@ -1455,6 +1457,7 @@ impl EditorElement {
position: Point, position: Point,
anchor: text::Anchor, anchor: text::Anchor,
path: ProjectPath, path: ProjectPath,
line_offset_from_top: u32,
} }
let jump_data = project::File::from_dyn(buffer.file()).map(|file| { let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
@ -1466,12 +1469,29 @@ impl EditorElement {
.primary .primary
.as_ref() .as_ref()
.map_or(range.context.start, |primary| primary.start); .map_or(range.context.start, |primary| primary.start);
let excerpt_start = range.context.start;
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
let offset_from_excerpt_start = if jump_anchor == excerpt_start {
0
} else {
let excerpt_start_row =
language::ToPoint::to_point(&jump_anchor, buffer).row;
jump_position.row - excerpt_start_row
};
let line_offset_from_top =
block_row_start + *height as u32 + offset_from_excerpt_start
- snapshot
.scroll_anchor
.scroll_position(&snapshot.display_snapshot)
.y as u32;
JumpData { JumpData {
position: jump_position, position: jump_position,
anchor: jump_anchor, anchor: jump_anchor,
path: jump_path, path: jump_path,
line_offset_from_top,
} }
}); });
@ -1541,6 +1561,7 @@ impl EditorElement {
jump_data.path.clone(), jump_data.path.clone(),
jump_data.position, jump_data.position,
jump_data.anchor, jump_data.anchor,
jump_data.line_offset_from_top,
cx, cx,
); );
} }
@ -1599,6 +1620,7 @@ impl EditorElement {
path.clone(), path.clone(),
jump_data.position, jump_data.position,
jump_data.anchor, jump_data.anchor,
jump_data.line_offset_from_top,
cx, cx,
); );
} }
@ -1631,6 +1653,7 @@ impl EditorElement {
path.clone(), path.clone(),
jump_data.position, jump_data.position,
jump_data.anchor, jump_data.anchor,
jump_data.line_offset_from_top,
cx, cx,
); );
} }
@ -1663,7 +1686,7 @@ impl EditorElement {
AvailableSpace::MinContent, AvailableSpace::MinContent,
AvailableSpace::Definite(block.height() as f32 * line_height), AvailableSpace::Definite(block.height() as f32 * line_height),
); );
let (element, element_size) = render_block(block, available_space, block_id, cx); let (element, element_size) = render_block(block, available_space, block_id, row, cx);
block_id += 1; block_id += 1;
fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width); fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
blocks.push(BlockLayout { blocks.push(BlockLayout {
@ -1691,7 +1714,7 @@ impl EditorElement {
AvailableSpace::Definite(width), AvailableSpace::Definite(width),
AvailableSpace::Definite(block.height() as f32 * line_height), AvailableSpace::Definite(block.height() as f32 * line_height),
); );
let (element, _) = render_block(block, available_space, block_id, cx); let (element, _) = render_block(block, available_space, block_id, row, cx);
block_id += 1; block_id += 1;
blocks.push(BlockLayout { blocks.push(BlockLayout {
row, row,

View file

@ -32,6 +32,10 @@ impl Autoscroll {
pub fn focused() -> Self { pub fn focused() -> Self {
Self::Strategy(AutoscrollStrategy::Focused) Self::Strategy(AutoscrollStrategy::Focused)
} }
/// Scrolls so that the newest cursor is roughly an n-th line from the top.
pub fn top_relative(n: usize) -> Self {
Self::Strategy(AutoscrollStrategy::TopRelative(n))
}
} }
#[derive(PartialEq, Eq, Default, Clone, Copy)] #[derive(PartialEq, Eq, Default, Clone, Copy)]
@ -43,6 +47,7 @@ pub enum AutoscrollStrategy {
Focused, Focused,
Top, Top,
Bottom, Bottom,
TopRelative(usize),
} }
impl AutoscrollStrategy { impl AutoscrollStrategy {
@ -178,6 +183,10 @@ impl Editor {
scroll_position.y = (target_bottom - visible_lines).max(0.0); scroll_position.y = (target_bottom - visible_lines).max(0.0);
self.set_scroll_position_internal(scroll_position, local, true, cx); self.set_scroll_position_internal(scroll_position, local, true, cx);
} }
AutoscrollStrategy::TopRelative(lines) => {
scroll_position.y = target_top - lines as f32;
self.set_scroll_position_internal(scroll_position, local, true, cx);
}
} }
self.scroll_manager.last_autoscroll = Some(( self.scroll_manager.last_autoscroll = Some((