mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-29 19:14:13 +00:00
editor: Fix add selection when skipping over soft-wrapped lines (#46911)
Fixes an issue where `editor::AddSelectionAbove` and `editor::AddSelectionBelow`, when set with `skip_soft_wrap: true` , would lose track of the original cursor column when passing through short lines, as `editor::Editor.add_selection` was not using the oldest selection's column range when adding a new selection. These changes update the `editor::Editor.add_selection` method so as to keep track of the olde selection's column range for each group, as well as extracting `find_next_columnar_selection_by_display_row` and `find_next_columnar_selection_by_buffer_row` helper methods to clarify the two navigation strategies. Closes #46842 Release Notes: - Fixed `editor::AddSelectionAbove` and `editor::AddSelectionBelow` when skipping over soft-wrapped lines to preserve the original cursor column when adding selections through lines shorter than the starting position
This commit is contained in:
parent
a2728e39a8
commit
edae88c223
3 changed files with 199 additions and 47 deletions
|
|
@ -15141,6 +15141,32 @@ impl Editor {
|
|||
display_map.max_point().row()
|
||||
};
|
||||
|
||||
// When `skip_soft_wrap` is true, we use buffer columns instead of pixel
|
||||
// positions to place new selections, so we need to keep track of the
|
||||
// column range of the oldest selection in each group, because
|
||||
// intermediate selections may have been clamped to shorter lines.
|
||||
// selections may have been clamped to shorter lines.
|
||||
let mut goal_columns_by_selection_id = if skip_soft_wrap {
|
||||
let mut map = HashMap::default();
|
||||
for group in state.groups.iter() {
|
||||
if let Some(oldest_id) = group.stack.first() {
|
||||
if let Some(oldest_selection) =
|
||||
columnar_selections.iter().find(|s| s.id == *oldest_id)
|
||||
{
|
||||
let start_col = oldest_selection.start.column;
|
||||
let end_col = oldest_selection.end.column;
|
||||
let goal_columns = start_col.min(end_col)..start_col.max(end_col);
|
||||
for id in &group.stack {
|
||||
map.insert(*id, goal_columns.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
map
|
||||
} else {
|
||||
HashMap::default()
|
||||
};
|
||||
|
||||
let mut last_added_item_per_group = HashMap::default();
|
||||
for group in state.groups.iter_mut() {
|
||||
if let Some(last_id) = group.stack.last() {
|
||||
|
|
@ -15153,7 +15179,7 @@ impl Editor {
|
|||
if above == group.above {
|
||||
let range = selection.display_range(&display_map).sorted();
|
||||
debug_assert_eq!(range.start.row(), range.end.row());
|
||||
let mut row = range.start.row();
|
||||
let row = range.start.row();
|
||||
let positions =
|
||||
if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
|
||||
Pixels::from(start)..Pixels::from(end)
|
||||
|
|
@ -15165,52 +15191,34 @@ impl Editor {
|
|||
start_x.min(end_x)..start_x.max(end_x)
|
||||
};
|
||||
|
||||
let mut maybe_new_selection = None;
|
||||
let direction = if above { -1 } else { 1 };
|
||||
|
||||
while row != end_row {
|
||||
let new_buffer_row = if skip_soft_wrap {
|
||||
let new_row = display_map
|
||||
.start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction);
|
||||
row = new_row.row();
|
||||
Some(new_row.to_point(&display_map).row)
|
||||
} else {
|
||||
if above {
|
||||
row.0 -= 1;
|
||||
} else {
|
||||
row.0 += 1;
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
let new_selection = if let Some(buffer_row) = new_buffer_row {
|
||||
let start_col = selection.start.column;
|
||||
let end_col = selection.end.column;
|
||||
let buffer_columns = start_col.min(end_col)..start_col.max(end_col);
|
||||
|
||||
self.selections
|
||||
.build_columnar_selection_from_buffer_columns(
|
||||
&display_map,
|
||||
buffer_row,
|
||||
&buffer_columns,
|
||||
selection.reversed,
|
||||
&text_layout_details,
|
||||
)
|
||||
} else {
|
||||
self.selections.build_columnar_selection(
|
||||
&display_map,
|
||||
row,
|
||||
&positions,
|
||||
selection.reversed,
|
||||
&text_layout_details,
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(new_selection) = new_selection {
|
||||
maybe_new_selection = Some(new_selection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let maybe_new_selection = if skip_soft_wrap {
|
||||
let goal_columns = goal_columns_by_selection_id
|
||||
.remove(&selection.id)
|
||||
.unwrap_or_else(|| {
|
||||
let start_col = selection.start.column;
|
||||
let end_col = selection.end.column;
|
||||
start_col.min(end_col)..start_col.max(end_col)
|
||||
});
|
||||
self.selections.find_next_columnar_selection_by_buffer_row(
|
||||
&display_map,
|
||||
row,
|
||||
end_row,
|
||||
above,
|
||||
&goal_columns,
|
||||
selection.reversed,
|
||||
&text_layout_details,
|
||||
)
|
||||
} else {
|
||||
self.selections.find_next_columnar_selection_by_display_row(
|
||||
&display_map,
|
||||
row,
|
||||
end_row,
|
||||
above,
|
||||
&positions,
|
||||
selection.reversed,
|
||||
&text_layout_details,
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(new_selection) = maybe_new_selection {
|
||||
group.stack.push(new_selection.id);
|
||||
|
|
|
|||
|
|
@ -8364,6 +8364,84 @@ async fn test_add_selection_above_below(cx: &mut TestAppContext) {
|
|||
n«ˇlm»o
|
||||
"#
|
||||
));
|
||||
|
||||
// Assert that the oldest selection's goal column is used when adding more
|
||||
// selections, not the most recently added selection's actual column.
|
||||
cx.set_state(indoc! {"
|
||||
foo bar bazˇ
|
||||
foo
|
||||
foo bar
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.add_selection_below(
|
||||
&AddSelectionBelow {
|
||||
skip_soft_wrap: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
foo bar bazˇ
|
||||
fooˇ
|
||||
foo bar
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.add_selection_below(
|
||||
&AddSelectionBelow {
|
||||
skip_soft_wrap: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
foo bar bazˇ
|
||||
fooˇ
|
||||
foo barˇ
|
||||
"});
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
foo bar baz
|
||||
foo
|
||||
foo barˇ
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.add_selection_above(
|
||||
&AddSelectionAbove {
|
||||
skip_soft_wrap: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
foo bar baz
|
||||
fooˇ
|
||||
foo barˇ
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.add_selection_above(
|
||||
&AddSelectionAbove {
|
||||
skip_soft_wrap: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
foo barˇ baz
|
||||
fooˇ
|
||||
foo barˇ
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
|
|
|||
|
|
@ -461,6 +461,72 @@ impl SelectionsCollection {
|
|||
})
|
||||
}
|
||||
|
||||
/// Finds the next columnar selection by walking display rows one at a time
|
||||
/// so that soft-wrapped lines are considered and not skipped.
|
||||
pub fn find_next_columnar_selection_by_display_row(
|
||||
&mut self,
|
||||
display_map: &DisplaySnapshot,
|
||||
start_row: DisplayRow,
|
||||
end_row: DisplayRow,
|
||||
above: bool,
|
||||
positions: &Range<Pixels>,
|
||||
reversed: bool,
|
||||
text_layout_details: &TextLayoutDetails,
|
||||
) -> Option<Selection<Point>> {
|
||||
let mut row = start_row;
|
||||
while row != end_row {
|
||||
if above {
|
||||
row.0 -= 1;
|
||||
} else {
|
||||
row.0 += 1;
|
||||
}
|
||||
|
||||
if let Some(selection) = self.build_columnar_selection(
|
||||
display_map,
|
||||
row,
|
||||
positions,
|
||||
reversed,
|
||||
text_layout_details,
|
||||
) {
|
||||
return Some(selection);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Finds the next columnar selection by skipping to the next buffer row,
|
||||
/// ignoring soft-wrapped lines.
|
||||
pub fn find_next_columnar_selection_by_buffer_row(
|
||||
&mut self,
|
||||
display_map: &DisplaySnapshot,
|
||||
start_row: DisplayRow,
|
||||
end_row: DisplayRow,
|
||||
above: bool,
|
||||
goal_columns: &Range<u32>,
|
||||
reversed: bool,
|
||||
text_layout_details: &TextLayoutDetails,
|
||||
) -> Option<Selection<Point>> {
|
||||
let mut row = start_row;
|
||||
let direction = if above { -1 } else { 1 };
|
||||
while row != end_row {
|
||||
let new_row =
|
||||
display_map.start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction);
|
||||
row = new_row.row();
|
||||
let buffer_row = new_row.to_point(display_map).row;
|
||||
|
||||
if let Some(selection) = self.build_columnar_selection_from_buffer_columns(
|
||||
display_map,
|
||||
buffer_row,
|
||||
goal_columns,
|
||||
reversed,
|
||||
text_layout_details,
|
||||
) {
|
||||
return Some(selection);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn change_with<R>(
|
||||
&mut self,
|
||||
snapshot: &DisplaySnapshot,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue