From 06fb4e8200ab5cb91ad2c40da1d1470daf08de9e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 May 2021 11:13:21 +0200 Subject: [PATCH 1/6] Implement `select_line` for buffer --- zed/src/editor/buffer/selection.rs | 4 +- zed/src/editor/buffer_view.rs | 81 +++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/zed/src/editor/buffer/selection.rs b/zed/src/editor/buffer/selection.rs index 431f8224c425d6e568da07718af36827520f7e64..dd8ea1ea2f7031481afef4a2525e0d78adfaaffb 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -75,6 +75,7 @@ impl Selection { pub fn buffer_rows_for_display_rows( &self, + expand_if_ends_at_line_start: bool, map: &DisplayMap, ctx: &AppContext, ) -> (Range, Range) { @@ -84,7 +85,8 @@ impl Selection { .unwrap(); let mut display_end = self.end.to_display_point(map, ctx).unwrap(); - if display_end.row() != map.max_point(ctx).row() + if !expand_if_ends_at_line_start + && display_end.row() != map.max_point(ctx).row() && display_start.row() != display_end.row() && display_end.column() == 0 { diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 7d80c68217ac454d22684f582f4ab6b71ec3140d..9bf964a9f0d4facfe6d9e08008c1cd7da1ed1705 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -139,6 +139,7 @@ pub fn init(app: &mut MutableAppContext) { ), Binding::new("cmd-shift-down", "buffer:select_to_end", Some("BufferView")), Binding::new("cmd-a", "buffer:select_all", Some("BufferView")), + Binding::new("cmd-l", "buffer:select_line", Some("BufferView")), Binding::new("pageup", "buffer:page_up", Some("BufferView")), Binding::new("pagedown", "buffer:page_down", Some("BufferView")), Binding::new("alt-cmd-[", "buffer:fold", Some("BufferView")), @@ -229,6 +230,7 @@ pub fn init(app: &mut MutableAppContext) { ); app.add_action("buffer:select_to_end", BufferView::select_to_end); app.add_action("buffer:select_all", BufferView::select_all); + app.add_action("buffer:select_line", BufferView::select_line); app.add_action("buffer:page_up", BufferView::page_up); app.add_action("buffer:page_down", BufferView::page_down); app.add_action("buffer:fold", BufferView::fold); @@ -712,7 +714,8 @@ impl BufferView { let mut selections = self.selections(app).iter().peekable(); while let Some(selection) = selections.next() { - let (mut rows, _) = selection.buffer_rows_for_display_rows(&self.display_map, app); + let (mut rows, _) = + selection.buffer_rows_for_display_rows(false, &self.display_map, app); let goal_display_column = selection .head() .to_display_point(&self.display_map, app) @@ -722,7 +725,7 @@ impl BufferView { // Accumulate contiguous regions of rows that we want to delete. while let Some(next_selection) = selections.peek() { let (next_rows, _) = - next_selection.buffer_rows_for_display_rows(&self.display_map, app); + next_selection.buffer_rows_for_display_rows(false, &self.display_map, app); if next_rows.start <= rows.end { rows.end = next_rows.end; selections.next().unwrap(); @@ -803,10 +806,11 @@ impl BufferView { let mut selections_iter = selections.iter_mut().peekable(); while let Some(selection) = selections_iter.next() { // Avoid duplicating the same lines twice. - let (mut rows, _) = selection.buffer_rows_for_display_rows(&self.display_map, app); + let (mut rows, _) = + selection.buffer_rows_for_display_rows(false, &self.display_map, app); while let Some(next_selection) = selections_iter.peek() { let (next_rows, _) = - next_selection.buffer_rows_for_display_rows(&self.display_map, app); + next_selection.buffer_rows_for_display_rows(false, &self.display_map, app); if next_rows.start <= rows.end - 1 { rows.end = next_rows.end; selections_iter.next().unwrap(); @@ -860,10 +864,10 @@ impl BufferView { // Accumulate contiguous regions of rows that we want to move. contiguous_selections.push(selection.range(buffer)); let (mut buffer_rows, mut display_rows) = - selection.buffer_rows_for_display_rows(&self.display_map, app); + selection.buffer_rows_for_display_rows(false, &self.display_map, app); while let Some(next_selection) = selections.peek() { let (next_buffer_rows, next_display_rows) = - next_selection.buffer_rows_for_display_rows(&self.display_map, app); + next_selection.buffer_rows_for_display_rows(false, &self.display_map, app); if next_buffer_rows.start <= buffer_rows.end { buffer_rows.end = next_buffer_rows.end; display_rows.end = next_display_rows.end; @@ -950,10 +954,10 @@ impl BufferView { // Accumulate contiguous regions of rows that we want to move. contiguous_selections.push(selection.range(buffer)); let (mut buffer_rows, mut display_rows) = - selection.buffer_rows_for_display_rows(&self.display_map, app); + selection.buffer_rows_for_display_rows(false, &self.display_map, app); while let Some(next_selection) = selections.peek() { let (next_buffer_rows, next_display_rows) = - next_selection.buffer_rows_for_display_rows(&self.display_map, app); + next_selection.buffer_rows_for_display_rows(false, &self.display_map, app); if next_buffer_rows.start <= buffer_rows.end { buffer_rows.end = next_buffer_rows.end; display_rows.end = next_display_rows.end; @@ -1662,6 +1666,22 @@ impl BufferView { self.update_selections(vec![selection], false, ctx); } + pub fn select_line(&mut self, _: &(), ctx: &mut ViewContext) { + let app = ctx.as_ref(); + let buffer = self.buffer.read(app); + let mut selections = self.selections(app).to_vec(); + let max_point = buffer.max_point(); + for selection in &mut selections { + let (rows, _) = selection.buffer_rows_for_display_rows(true, &self.display_map, app); + selection.start = buffer.anchor_before(Point::new(rows.start, 0)).unwrap(); + selection.end = buffer + .anchor_before(cmp::min(max_point, Point::new(rows.end, 0))) + .unwrap(); + selection.reversed = false; + } + self.update_selections(selections, true, ctx); + } + pub fn selections_in_range<'a>( &'a self, range: Range, @@ -3284,6 +3304,51 @@ mod tests { }); } + #[test] + fn test_select_line() { + App::test((), |app| { + let settings = settings::channel(&app.font_cache()).unwrap().1; + let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 5))); + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); + view.update(app, |view, ctx| { + view.select_display_ranges( + &[ + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), + DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2), + ], + ctx, + ) + .unwrap(); + view.select_line(&(), ctx); + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0), + DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0), + ] + ); + + view.update(app, |view, ctx| view.select_line(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0), + DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5), + ] + ); + + view.update(app, |view, ctx| view.select_line(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)] + ); + }); + } + impl BufferView { fn selection_ranges(&self, app: &AppContext) -> Vec> { self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app) From 1c2f549c82af371a872059b2211083f61166c68c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 May 2021 14:30:40 +0200 Subject: [PATCH 2/6] Don't report folds that touch the endpoints of the given range Fold ranges are exclusive, exactly like the query ranges. So if the end of a fold coincides with the start of the query range, we shouldn't report that fold. Analogously, if the start of a fold coincides with the end of the query range, that fold shouldn't be reported. --- zed/src/editor/display_map/fold_map.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index c64d48d12d930d7d05f0a5bf0b424006801911aa..a14a0a5bbb26b092b680800c62a01f1833023cbc 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -192,8 +192,8 @@ impl FoldMap { let start = buffer.anchor_before(range.start.to_offset(buffer)?)?; let end = buffer.anchor_after(range.end.to_offset(buffer)?)?; Ok(self.folds.filter::<_, usize>(move |summary| { - start.cmp(&summary.max_end, buffer).unwrap() <= Ordering::Equal - && end.cmp(&summary.min_start, buffer).unwrap() >= Ordering::Equal + start.cmp(&summary.max_end, buffer).unwrap() == Ordering::Less + && end.cmp(&summary.min_start, buffer).unwrap() == Ordering::Greater })) } @@ -712,7 +712,7 @@ mod tests { }); assert_eq!(map.text(app.as_ref()), "123a…c123456eee"); - map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app.as_ref()) + map.unfold(Some(Point::new(0, 4)..Point::new(0, 5)), app.as_ref()) .unwrap(); assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee"); }); @@ -833,7 +833,6 @@ mod tests { fold_ranges, vec![ Point::new(0, 2)..Point::new(2, 2), - Point::new(0, 4)..Point::new(1, 0), Point::new(1, 2)..Point::new(3, 2) ] ); @@ -1007,9 +1006,10 @@ mod tests { .items() .into_iter() .filter(|fold| { - let fold_start = fold.0.start.to_offset(buffer).unwrap(); - let fold_end = fold.0.end.to_offset(buffer).unwrap(); - start <= fold_end && end >= fold_start + let start = buffer.anchor_before(start).unwrap(); + let end = buffer.anchor_after(end).unwrap(); + start.cmp(&fold.0.end, buffer).unwrap() == Ordering::Less + && end.cmp(&fold.0.start, buffer).unwrap() == Ordering::Greater }) .map(|fold| fold.0) .collect::>(); From 4797ccfdcd7bec9ae157e342861c9afcd99bb8fc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 May 2021 14:32:57 +0200 Subject: [PATCH 3/6] Implement `split_selection_into_lines` for buffer --- zed/src/editor/buffer_view.rs | 123 ++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 9bf964a9f0d4facfe6d9e08008c1cd7da1ed1705..877858a36f4d7c0508ec159ee1b0d7cd639cbab3 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -140,6 +140,11 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("cmd-shift-down", "buffer:select_to_end", Some("BufferView")), Binding::new("cmd-a", "buffer:select_all", Some("BufferView")), Binding::new("cmd-l", "buffer:select_line", Some("BufferView")), + Binding::new( + "cmd-shift-L", + "buffer:split_selection_into_lines", + Some("BufferView"), + ), Binding::new("pageup", "buffer:page_up", Some("BufferView")), Binding::new("pagedown", "buffer:page_down", Some("BufferView")), Binding::new("alt-cmd-[", "buffer:fold", Some("BufferView")), @@ -231,6 +236,10 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:select_to_end", BufferView::select_to_end); app.add_action("buffer:select_all", BufferView::select_all); app.add_action("buffer:select_line", BufferView::select_line); + app.add_action( + "buffer:split_selection_into_lines", + BufferView::split_selection_into_lines, + ); app.add_action("buffer:page_up", BufferView::page_up); app.add_action("buffer:page_down", BufferView::page_down); app.add_action("buffer:fold", BufferView::fold); @@ -1682,6 +1691,45 @@ impl BufferView { self.update_selections(selections, true, ctx); } + pub fn split_selection_into_lines(&mut self, _: &(), ctx: &mut ViewContext) { + let app = ctx.as_ref(); + let buffer = self.buffer.read(app); + + let mut to_unfold = Vec::new(); + let mut new_selections = Vec::new(); + for selection in self.selections(app) { + let range = selection.range(buffer); + if range.start.row != range.end.row { + new_selections.push(Selection { + start: selection.start.clone(), + end: selection.start.clone(), + reversed: false, + goal_column: None, + }); + } + for row in range.start.row + 1..range.end.row { + let cursor = buffer + .anchor_before(Point::new(row, buffer.line_len(row).unwrap())) + .unwrap(); + new_selections.push(Selection { + start: cursor.clone(), + end: cursor, + reversed: false, + goal_column: None, + }); + } + new_selections.push(Selection { + start: selection.end.clone(), + end: selection.end.clone(), + reversed: false, + goal_column: None, + }); + to_unfold.push(range); + } + self.unfold_ranges(to_unfold, ctx); + self.update_selections(new_selections, true, ctx); + } + pub fn selections_in_range<'a>( &'a self, range: Range, @@ -3349,6 +3397,81 @@ mod tests { }); } + #[test] + fn test_split_selection_into_lines() { + App::test((), |app| { + let settings = settings::channel(&app.font_cache()).unwrap().1; + let buffer = app.add_model(|_| Buffer::new(0, sample_text(9, 5))); + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); + view.update(app, |view, ctx| { + view.fold_ranges( + vec![ + Point::new(0, 2)..Point::new(1, 2), + Point::new(2, 3)..Point::new(4, 1), + Point::new(7, 0)..Point::new(8, 4), + ], + ctx, + ); + view.select_display_ranges( + &[ + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), + DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2), + ], + ctx, + ) + .unwrap(); + }); + assert_eq!( + view.read(app).text(app.as_ref()), + "aa…bbb\nccc…eeee\nfffff\nggggg\n…i" + ); + + view.update(app, |view, ctx| view.split_selection_into_lines(&(), ctx)); + assert_eq!( + view.read(app).text(app.as_ref()), + "aa…bbb\nccc…eeee\nfffff\nggggg\n…i" + ); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + [ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), + DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2) + ] + ); + + view.update(app, |view, ctx| { + view.select_display_ranges( + &[DisplayPoint::new(0, 1)..DisplayPoint::new(4, 0)], + ctx, + ) + .unwrap(); + view.split_selection_into_lines(&(), ctx); + }); + assert_eq!( + view.read(app).text(app.as_ref()), + "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\n…i" + ); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + [ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), + DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5), + DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5), + DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5), + DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5), + DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5), + DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5), + DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0) + ] + ); + }); + } + impl BufferView { fn selection_ranges(&self, app: &AppContext) -> Vec> { self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app) From 5f1d0b8850bc0fe9db222cded2529d11b4c3a0ae Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 May 2021 14:38:21 +0200 Subject: [PATCH 4/6] :lipstick: --- zed/src/editor/buffer/selection.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zed/src/editor/buffer/selection.rs b/zed/src/editor/buffer/selection.rs index dd8ea1ea2f7031481afef4a2525e0d78adfaaffb..8f436a04e7a4929c6a542c10e5df6d8086e0d10c 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -75,7 +75,7 @@ impl Selection { pub fn buffer_rows_for_display_rows( &self, - expand_if_ends_at_line_start: bool, + include_end_if_at_line_start: bool, map: &DisplayMap, ctx: &AppContext, ) -> (Range, Range) { @@ -85,7 +85,7 @@ impl Selection { .unwrap(); let mut display_end = self.end.to_display_point(map, ctx).unwrap(); - if !expand_if_ends_at_line_start + if !include_end_if_at_line_start && display_end.row() != map.max_point(ctx).row() && display_start.row() != display_end.row() && display_end.column() == 0 From 8bbaa0bd34caee0548e645cc2ecd7153ffac7206 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 May 2021 14:54:12 +0200 Subject: [PATCH 5/6] Handle reversed selections correctly in `split_selection_into_lines` --- zed/src/editor/buffer_view.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 877858a36f4d7c0508ec159ee1b0d7cd639cbab3..0cd948809183d29f0d6b2bfae4f785a7b4ac22bc 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1692,13 +1692,15 @@ impl BufferView { } pub fn split_selection_into_lines(&mut self, _: &(), ctx: &mut ViewContext) { + use super::RangeExt; + let app = ctx.as_ref(); let buffer = self.buffer.read(app); let mut to_unfold = Vec::new(); let mut new_selections = Vec::new(); for selection in self.selections(app) { - let range = selection.range(buffer); + let range = selection.range(buffer).sorted(); if range.start.row != range.end.row { new_selections.push(Selection { start: selection.start.clone(), @@ -3446,7 +3448,7 @@ mod tests { view.update(app, |view, ctx| { view.select_display_ranges( - &[DisplayPoint::new(0, 1)..DisplayPoint::new(4, 0)], + &[DisplayPoint::new(4, 0)..DisplayPoint::new(0, 1)], ctx, ) .unwrap(); From 806c09bf11f9c6f3c93b75cea04d07f852c72a13 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 May 2021 18:15:00 +0200 Subject: [PATCH 6/6] Autoscroll when folding an arbitrary range --- zed/src/editor/buffer_view.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 0cd948809183d29f0d6b2bfae4f785a7b4ac22bc..18e7a4d0c00ea78afe1850b149824d05190039d7 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1952,9 +1952,8 @@ impl BufferView { .selections(ctx.as_ref()) .iter() .map(|s| s.range(buffer).sorted()) - .collect::>(); - self.display_map.fold(ranges, ctx.as_ref()).unwrap(); - ctx.notify(); + .collect(); + self.fold_ranges(ranges, ctx); } fn fold_ranges(&mut self, ranges: Vec>, ctx: &mut ViewContext) {