Add support for visual ranges ending with a newline

These primarily happen when first entering visual mode, but can also
be created with objects like `vi{`.

Along the way fix the way ranges like `vi{` are selected to be more
similar to nvim.
This commit is contained in:
Conrad Irwin 2023-07-27 21:39:37 -06:00
parent b53fb8633e
commit 5edcb74760
21 changed files with 301 additions and 135 deletions

View file

@ -35,12 +35,6 @@ pub enum FoldStatus {
Foldable,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Clip {
None,
EndOfLine,
}
pub trait ToDisplayPoint {
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
}
@ -56,7 +50,7 @@ pub struct DisplayMap {
wrap_map: ModelHandle<WrapMap>,
block_map: BlockMap,
text_highlights: TextHighlights,
pub default_clip: Clip,
pub clip_at_line_ends: bool,
}
impl Entity for DisplayMap {
@ -91,7 +85,7 @@ impl DisplayMap {
wrap_map,
block_map,
text_highlights: Default::default(),
default_clip: Clip::None,
clip_at_line_ends: false,
}
}
@ -115,7 +109,7 @@ impl DisplayMap {
wrap_snapshot,
block_snapshot,
text_highlights: self.text_highlights.clone(),
default_clip: self.default_clip,
clip_at_line_ends: self.clip_at_line_ends,
}
}
@ -302,7 +296,7 @@ pub struct DisplaySnapshot {
wrap_snapshot: wrap_map::WrapSnapshot,
block_snapshot: block_map::BlockSnapshot,
text_highlights: TextHighlights,
default_clip: Clip,
clip_at_line_ends: bool,
}
impl DisplaySnapshot {
@ -361,6 +355,9 @@ impl DisplaySnapshot {
pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
let mut new_start = self.prev_line_boundary(range.start).0;
if range.end.column == 0 {
return new_start..range.end;
}
let mut new_end = self.next_line_boundary(range.end).0;
if new_start.row == range.start.row && new_end.row == range.end.row {
@ -583,33 +580,21 @@ impl DisplaySnapshot {
column
}
pub fn move_left(&self, point: DisplayPoint, clip: Clip) -> DisplayPoint {
self.clip_point_with(
DisplayPoint::new(point.row(), point.column().saturating_sub(1)),
Bias::Left,
clip,
)
}
pub fn move_right(&self, point: DisplayPoint, clip: Clip) -> DisplayPoint {
self.clip_point_with(
DisplayPoint::new(point.row(), point.column() + 1),
Bias::Right,
clip,
)
}
pub fn clip_point_with(&self, point: DisplayPoint, bias: Bias, clip: Clip) -> DisplayPoint {
let new_point = DisplayPoint(self.block_snapshot.clip_point(point.0, bias));
if clip == Clip::EndOfLine && new_point.column() == self.line_len(new_point.row()) {
self.move_left(new_point, Clip::None)
} else {
new_point
}
}
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
self.clip_point_with(point, bias, self.default_clip)
let mut clipped = self.block_snapshot.clip_point(point.0, bias);
if self.clip_at_line_ends {
clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
}
DisplayPoint(clipped)
}
pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
let mut point = point.0;
if point.column == self.line_len(point.row) {
point.column = point.column.saturating_sub(1);
point = self.block_snapshot.clip_point(point, Bias::Left);
}
DisplayPoint(point)
}
pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
@ -1598,7 +1583,7 @@ pub mod tests {
fn assert(text: &str, cx: &mut gpui::AppContext) {
let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
unmarked_snapshot.default_clip = Clip::EndOfLine;
unmarked_snapshot.clip_at_line_ends = true;
assert_eq!(
unmarked_snapshot.clip_point(markers[1], Bias::Left),
markers[0]

View file

@ -1544,10 +1544,10 @@ impl Editor {
range.clone()
}
pub fn set_default_clip(&mut self, clip: Clip, cx: &mut ViewContext<Self>) {
if self.display_map.read(cx).default_clip != clip {
pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
if self.display_map.read(cx).clip_at_line_ends != clip {
self.display_map
.update(cx, |map, _| map.default_clip = clip);
.update(cx, |map, _| map.clip_at_line_ends = clip);
}
}

View file

@ -847,23 +847,26 @@ impl EditorElement {
if editor.show_local_cursors(cx) || replica_id != local_replica_id {
let cursor_position = selection.head;
let mut cursor_column = cursor_position.column() as usize;
let mut cursor_row = cursor_position.row();
if layout
.visible_display_row_range
.contains(&cursor_position.row())
if CursorShape::Block == selection.cursor_shape
&& !selection.range.is_empty()
&& !selection.reversed
{
let cursor_row_layout = &layout.position_map.line_layouts
[(cursor_position.row() - start_row) as usize]
.line;
let mut cursor_column = cursor_position.column() as usize;
if CursorShape::Block == selection.cursor_shape
&& !selection.range.is_empty()
&& !selection.reversed
&& cursor_column > 0
{
if cursor_column > 0 {
cursor_column -= 1;
} else if cursor_row > 0 {
cursor_row -= 1;
cursor_column =
layout.position_map.snapshot.line_len(cursor_row) as usize;
}
}
if layout.visible_display_row_range.contains(&cursor_row) {
let cursor_row_layout = &layout.position_map.line_layouts
[(cursor_row - start_row) as usize]
.line;
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
let mut block_width =
@ -876,7 +879,7 @@ impl EditorElement {
.position_map
.snapshot
.chars_at(DisplayPoint::new(
cursor_position.row(),
cursor_row as u32,
cursor_column as u32,
))
.next()
@ -903,8 +906,7 @@ impl EditorElement {
};
let x = cursor_character_x - scroll_left;
let y = cursor_position.row() as f32 * layout.position_map.line_height
- scroll_top;
let y = cursor_row as f32 * layout.position_map.line_height - scroll_top;
if selection.is_newest {
editor.pixel_position_of_newest_cursor = Some(vec2f(
bounds.origin_x() + x + block_width / 2.,

View file

@ -13,6 +13,13 @@ pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
map.clip_point(point, Bias::Left)
}
pub fn saturating_left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
if point.column() > 0 {
*point.column_mut() -= 1;
}
map.clip_point(point, Bias::Left)
}
pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
let max_column = map.line_len(point.row());
if point.column() < max_column {
@ -24,6 +31,11 @@ pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
map.clip_point(point, Bias::Right)
}
pub fn saturating_right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
*point.column_mut() += 1;
map.clip_point(point, Bias::Right)
}
pub fn up(
map: &DisplaySnapshot,
start: DisplayPoint,

View file

@ -498,6 +498,7 @@ impl<'a> MutableSelectionsCollection<'a> {
T: ToOffset + ToPoint + Ord + std::marker::Copy + std::fmt::Debug,
{
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
selections.sort_unstable_by_key(|s| s.start);
// Merge overlapping selections.
let mut i = 1;