From 324a6ff7ae7b9879ec164c1b8267b882f8f17008 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 27 Apr 2021 13:23:41 +0200 Subject: [PATCH 01/28] Implement `select_all` for buffer --- zed/src/editor/buffer_view.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 77cf3bf7799520a13588b6f1aef1e7e4e5d8aaee..178e1d2ca8c7967854a8fb9ceb0c690879ddfddb 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -44,6 +44,7 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("shift-down", "buffer:select_down", Some("BufferView")), Binding::new("shift-left", "buffer:select_left", Some("BufferView")), Binding::new("shift-right", "buffer:select_right", Some("BufferView")), + Binding::new("cmd-a", "buffer:select_all", 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")), @@ -73,6 +74,7 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:select_down", BufferView::select_down); app.add_action("buffer:select_left", BufferView::select_left); app.add_action("buffer:select_right", BufferView::select_right); + app.add_action("buffer:select_all", BufferView::select_all); 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); @@ -816,6 +818,16 @@ impl BufferView { } } + pub fn select_all(&mut self, _: &(), ctx: &mut ViewContext) { + let selection = Selection { + start: Anchor::Start, + end: Anchor::End, + reversed: false, + goal_column: None, + }; + self.update_selections(vec![selection], false, ctx); + } + pub fn selections_in_range<'a>( &'a self, range: Range, @@ -1844,6 +1856,20 @@ mod tests { }); } + #[test] + fn test_select_all() { + App::test((), |app| { + let buffer = app.add_model(|ctx| Buffer::new(0, "abc\nde\nfgh", ctx)); + let settings = settings::channel(&app.font_cache()).unwrap().1; + let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + view.update(app, |b, ctx| b.select_all(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)] + ); + }); + } + impl BufferView { fn selection_ranges(&self, app: &AppContext) -> Vec> { self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app) From b7c30eba3089aa58c5d8fcfb0b38e7b773ff9b57 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 27 Apr 2021 13:29:21 +0200 Subject: [PATCH 02/28] Implemente `delete` for buffer --- zed/src/editor/buffer_view.rs | 69 +++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 178e1d2ca8c7967854a8fb9ceb0c690879ddfddb..1da58748d392be2e3d6f5ca677be014a8d6d6f0f 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -30,6 +30,7 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); pub fn init(app: &mut MutableAppContext) { app.add_bindings(vec![ Binding::new("backspace", "buffer:backspace", Some("BufferView")), + Binding::new("delete", "buffer:delete", Some("BufferView")), Binding::new("enter", "buffer:newline", Some("BufferView")), Binding::new("cmd-x", "buffer:cut", Some("BufferView")), Binding::new("cmd-c", "buffer:copy", Some("BufferView")), @@ -61,6 +62,7 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:insert", BufferView::insert); app.add_action("buffer:newline", BufferView::newline); app.add_action("buffer:backspace", BufferView::backspace); + app.add_action("buffer:delete", BufferView::delete); app.add_action("buffer:cut", BufferView::cut); app.add_action("buffer:copy", BufferView::copy); app.add_action("buffer:paste", BufferView::paste); @@ -489,6 +491,36 @@ impl BufferView { self.end_transaction(ctx); } + pub fn delete(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + let mut selections = self.selections(ctx.as_ref()).to_vec(); + { + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(ctx); + for selection in &mut selections { + if selection.range(buffer).is_empty() { + let head = selection + .head() + .to_display_point(map, ctx.as_ref()) + .unwrap(); + let cursor = map + .anchor_before( + movement::right(map, head, ctx.as_ref()).unwrap(), + Bias::Right, + ctx.as_ref(), + ) + .unwrap(); + selection.set_head(&buffer, cursor); + selection.goal_column = None; + } + } + } + + self.update_selections(selections, true, ctx); + self.insert(&String::new(), ctx); + self.end_transaction(ctx); + } + pub fn cut(&mut self, _: &(), ctx: &mut ViewContext) { self.start_transaction(ctx); let mut text = String::new(); @@ -1715,6 +1747,43 @@ mod tests { }) } + #[test] + fn test_delete() { + App::test((), |app| { + let buffer = app.add_model(|ctx| { + Buffer::new( + 0, + "one two three\nfour five six\nseven eight nine\nten\n", + ctx, + ) + }); + let settings = settings::channel(&app.font_cache()).unwrap().1; + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + + view.update(app, |view, ctx| { + view.select_display_ranges( + &[ + // an empty selection - the following character is deleted + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), + // one character selected - it is deleted + DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4), + // a line suffix selected - it is deleted + DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0), + ], + ctx, + ) + .unwrap(); + view.delete(&(), ctx); + }); + + assert_eq!( + buffer.read(app).text(), + "on two three\nfou five six\nseven ten\n" + ); + }) + } + #[test] fn test_clipboard() { App::test((), |app| { From 45452bbb3a0bd027b999c9456070a02ed9eab8ab Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 27 Apr 2021 13:35:41 +0200 Subject: [PATCH 03/28] Implement `move_to_beginning` and `move_to_end` for buffer --- zed/src/editor/buffer_view.rs | 77 ++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 1da58748d392be2e3d6f5ca677be014a8d6d6f0f..3f405f31a414a94d6d25438137de382e59a3dc94 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -41,6 +41,8 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("down", "buffer:move_down", Some("BufferView")), Binding::new("left", "buffer:move_left", Some("BufferView")), Binding::new("right", "buffer:move_right", Some("BufferView")), + Binding::new("cmd-up", "buffer:move_to_beginning", Some("BufferView")), + Binding::new("cmd-down", "buffer:move_to_end", Some("BufferView")), Binding::new("shift-up", "buffer:select_up", Some("BufferView")), Binding::new("shift-down", "buffer:select_down", Some("BufferView")), Binding::new("shift-left", "buffer:select_left", Some("BufferView")), @@ -72,6 +74,8 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:move_down", BufferView::move_down); app.add_action("buffer:move_left", BufferView::move_left); app.add_action("buffer:move_right", BufferView::move_right); + app.add_action("buffer:move_to_beginning", BufferView::move_to_beginning); + app.add_action("buffer:move_to_end", BufferView::move_to_end); app.add_action("buffer:select_up", BufferView::select_up); app.add_action("buffer:select_down", BufferView::select_down); app.add_action("buffer:select_left", BufferView::select_left); @@ -850,6 +854,26 @@ impl BufferView { } } + pub fn move_to_beginning(&mut self, _: &(), ctx: &mut ViewContext) { + let selection = Selection { + start: Anchor::Start, + end: Anchor::Start, + reversed: false, + goal_column: None, + }; + self.update_selections(vec![selection], true, ctx); + } + + pub fn move_to_end(&mut self, _: &(), ctx: &mut ViewContext) { + let selection = Selection { + start: Anchor::End, + end: Anchor::End, + reversed: false, + goal_column: None, + }; + self.update_selections(vec![selection], true, ctx); + } + pub fn select_all(&mut self, _: &(), ctx: &mut ViewContext) { let selection = Selection { start: Anchor::Start, @@ -1442,7 +1466,6 @@ impl workspace::ItemView for BufferView { mod tests { use super::*; use crate::{editor::Point, settings, test::sample_text}; - use anyhow::Error; use gpui::App; use unindent::Unindent; @@ -1674,7 +1697,7 @@ mod tests { } #[test] - fn test_move_cursor() -> Result<()> { + fn test_move_cursor() { App::test((), |app| { let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx)); let settings = settings::channel(&app.font_cache()).unwrap().1; @@ -1682,15 +1705,17 @@ mod tests { app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); buffer.update(app, |buffer, ctx| { - buffer.edit( - vec![ - Point::new(1, 0)..Point::new(1, 0), - Point::new(1, 1)..Point::new(1, 1), - ], - "\t", - Some(ctx), - ) - })?; + buffer + .edit( + vec![ + Point::new(1, 0)..Point::new(1, 0), + Point::new(1, 1)..Point::new(1, 1), + ], + "\t", + Some(ctx), + ) + .unwrap(); + }); view.update(app, |view, ctx| { view.move_down(&(), ctx); @@ -1698,16 +1723,38 @@ mod tests { view.selection_ranges(ctx.as_ref()), &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)] ); + view.move_right(&(), ctx); assert_eq!( view.selection_ranges(ctx.as_ref()), &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)] ); - Ok::<(), Error>(()) - })?; - Ok(()) - }) + view.move_left(&(), ctx); + assert_eq!( + view.selection_ranges(ctx.as_ref()), + &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)] + ); + + view.move_up(&(), ctx); + assert_eq!( + view.selection_ranges(ctx.as_ref()), + &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] + ); + + view.move_to_end(&(), ctx); + assert_eq!( + view.selection_ranges(ctx.as_ref()), + &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)] + ); + + view.move_to_beginning(&(), ctx); + assert_eq!( + view.selection_ranges(ctx.as_ref()), + &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] + ); + }); + }); } #[test] From c524cc4d61c46d0ed1cdd3cf89746b34ab338a2c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 27 Apr 2021 13:47:12 +0200 Subject: [PATCH 04/28] Implement `select_to_beginning` and `select_to_end` for buffer --- zed/src/editor/buffer_view.rs | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 3f405f31a414a94d6d25438137de382e59a3dc94..2b044a3ed0b000a78bb2217f6b469e90d0f42bbc 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -47,6 +47,12 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("shift-down", "buffer:select_down", Some("BufferView")), Binding::new("shift-left", "buffer:select_left", Some("BufferView")), Binding::new("shift-right", "buffer:select_right", Some("BufferView")), + Binding::new( + "cmd-shift-up", + "buffer:select_to_beginning", + Some("BufferView"), + ), + Binding::new("cmd-shift-down", "buffer:select_to_end", Some("BufferView")), Binding::new("cmd-a", "buffer:select_all", Some("BufferView")), Binding::new("pageup", "buffer:page_up", Some("BufferView")), Binding::new("pagedown", "buffer:page_down", Some("BufferView")), @@ -80,6 +86,11 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:select_down", BufferView::select_down); app.add_action("buffer:select_left", BufferView::select_left); app.add_action("buffer:select_right", BufferView::select_right); + app.add_action( + "buffer:select_to_beginning", + BufferView::select_to_beginning, + ); + app.add_action("buffer:select_to_end", BufferView::select_to_end); app.add_action("buffer:select_all", BufferView::select_all); app.add_action("buffer:page_up", BufferView::page_up); app.add_action("buffer:page_down", BufferView::page_down); @@ -864,6 +875,12 @@ impl BufferView { self.update_selections(vec![selection], true, ctx); } + pub fn select_to_beginning(&mut self, _: &(), ctx: &mut ViewContext) { + let mut selection = self.selections(ctx.as_ref()).last().unwrap().clone(); + selection.set_head(self.buffer.read(ctx), Anchor::Start); + self.update_selections(vec![selection], true, ctx); + } + pub fn move_to_end(&mut self, _: &(), ctx: &mut ViewContext) { let selection = Selection { start: Anchor::End, @@ -874,6 +891,12 @@ impl BufferView { self.update_selections(vec![selection], true, ctx); } + pub fn select_to_end(&mut self, _: &(), ctx: &mut ViewContext) { + let mut selection = self.selections(ctx.as_ref()).last().unwrap().clone(); + selection.set_head(self.buffer.read(ctx), Anchor::End); + self.update_selections(vec![selection], true, ctx); + } + pub fn select_all(&mut self, _: &(), ctx: &mut ViewContext) { let selection = Selection { start: Anchor::Start, @@ -1753,6 +1776,23 @@ mod tests { view.selection_ranges(ctx.as_ref()), &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] ); + + view.select_display_ranges( + &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)], + ctx, + ) + .unwrap(); + view.select_to_beginning(&(), ctx); + assert_eq!( + view.selection_ranges(ctx.as_ref()), + &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)] + ); + + view.select_to_end(&(), ctx); + assert_eq!( + view.selection_ranges(ctx.as_ref()), + &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)] + ); }); }); } From 21442bd2b7366997d7a138dbcf3a05073d69e0e9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 27 Apr 2021 13:49:02 +0200 Subject: [PATCH 05/28] Don't propagate action when selecting up/down in single-line editors --- zed/src/editor/buffer_view.rs | 56 +++++++++++++++-------------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 2b044a3ed0b000a78bb2217f6b469e90d0f42bbc..87d8fd9c81b60ce41da59d5df5481ff291c3f0bd 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -796,24 +796,20 @@ impl BufferView { } pub fn select_up(&mut self, _: &(), ctx: &mut ViewContext) { - if self.single_line { - ctx.propagate_action(); - } else { - let mut selections = self.selections(ctx.as_ref()).to_vec(); - { - let app = ctx.as_ref(); - let buffer = self.buffer.read(app); - let map = self.display_map.read(app); - for selection in &mut selections { - let head = selection.head().to_display_point(map, app).unwrap(); - let (head, goal_column) = - movement::up(map, head, selection.goal_column, app).unwrap(); - selection.set_head(&buffer, map.anchor_before(head, Bias::Left, app).unwrap()); - selection.goal_column = goal_column; - } + let mut selections = self.selections(ctx.as_ref()).to_vec(); + { + let app = ctx.as_ref(); + let buffer = self.buffer.read(app); + let map = self.display_map.read(app); + for selection in &mut selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let (head, goal_column) = + movement::up(map, head, selection.goal_column, app).unwrap(); + selection.set_head(&buffer, map.anchor_before(head, Bias::Left, app).unwrap()); + selection.goal_column = goal_column; } - self.update_selections(selections, true, ctx); } + self.update_selections(selections, true, ctx); } pub fn move_down(&mut self, _: &(), ctx: &mut ViewContext) { @@ -845,24 +841,20 @@ impl BufferView { } pub fn select_down(&mut self, _: &(), ctx: &mut ViewContext) { - if self.single_line { - ctx.propagate_action(); - } else { - let mut selections = self.selections(ctx.as_ref()).to_vec(); - { - let app = ctx.as_ref(); - let buffer = self.buffer.read(app); - let map = self.display_map.read(app); - for selection in &mut selections { - let head = selection.head().to_display_point(map, app).unwrap(); - let (head, goal_column) = - movement::down(map, head, selection.goal_column, app).unwrap(); - selection.set_head(&buffer, map.anchor_before(head, Bias::Right, app).unwrap()); - selection.goal_column = goal_column; - } + let mut selections = self.selections(ctx.as_ref()).to_vec(); + { + let app = ctx.as_ref(); + let buffer = self.buffer.read(app); + let map = self.display_map.read(app); + for selection in &mut selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let (head, goal_column) = + movement::down(map, head, selection.goal_column, app).unwrap(); + selection.set_head(&buffer, map.anchor_before(head, Bias::Right, app).unwrap()); + selection.goal_column = goal_column; } - self.update_selections(selections, true, ctx); } + self.update_selections(selections, true, ctx); } pub fn move_to_beginning(&mut self, _: &(), ctx: &mut ViewContext) { From 834602ef67670887299fe19d3024b794e254d718 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 27 Apr 2021 17:49:26 +0200 Subject: [PATCH 06/28] Implement `delete_line` for buffer This still needs unit tests. --- zed/src/editor/buffer_view.rs | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 87d8fd9c81b60ce41da59d5df5481ff291c3f0bd..0bb47c8966a303d60f86498dc0a064431ece9afa 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -32,6 +32,7 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("backspace", "buffer:backspace", Some("BufferView")), Binding::new("delete", "buffer:delete", Some("BufferView")), Binding::new("enter", "buffer:newline", Some("BufferView")), + Binding::new("ctrl-shift-K", "buffer:delete_line", Some("BufferView")), Binding::new("cmd-x", "buffer:cut", Some("BufferView")), Binding::new("cmd-c", "buffer:copy", Some("BufferView")), Binding::new("cmd-v", "buffer:paste", Some("BufferView")), @@ -71,6 +72,7 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:newline", BufferView::newline); app.add_action("buffer:backspace", BufferView::backspace); app.add_action("buffer:delete", BufferView::delete); + app.add_action("buffer:delete_line", BufferView::delete_line); app.add_action("buffer:cut", BufferView::cut); app.add_action("buffer:copy", BufferView::copy); app.add_action("buffer:paste", BufferView::paste); @@ -536,6 +538,74 @@ impl BufferView { self.end_transaction(ctx); } + pub fn delete_line(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + + let app = ctx.as_ref(); + let buffer = self.buffer.read(app); + + // Accumulate contiguous regions of rows that we want to delete. + let mut row_ranges: Vec<(u32, Range)> = Vec::new(); + for selection in self.selections(app) { + let start = selection.start.to_point(buffer).unwrap(); + let end = selection.end.to_point(buffer).unwrap(); + let goal_column = if selection.reversed { + start.column + } else { + end.column + }; + + if let Some((_, last_row_range)) = row_ranges.last_mut() { + if start.row <= last_row_range.end { + *last_row_range = last_row_range.start..start.row + 1; + } else { + row_ranges.push((goal_column, start.row..end.row + 1)); + } + } else { + row_ranges.push((goal_column, start.row..end.row + 1)); + } + } + + let mut edit_ranges = Vec::new(); + let mut new_selections = Vec::new(); + for (goal_column, range) in row_ranges { + let mut start = Point::new(range.start, 0).to_offset(buffer).unwrap(); + let end; + let mut cursor; + + if let Ok(end_offset) = Point::new(range.end, 0).to_offset(buffer) { + // If there's a line after the range, delete the \n from the end of the row range + // and position the cursor on the next line. + end = end_offset; + cursor = Point::new(range.end, goal_column); + } else { + // If there isn't a line after the range, delete the \n from the line before the + // start of the row range and position the cursor there. + start = start.saturating_sub(1); + end = buffer.len(); + cursor = Point::new(range.start.saturating_sub(1), goal_column); + } + // We tried to maintain the column of the original cursors but the new lines may be + // shorter, so clip the new cursor's column. + cursor.column = cmp::min(cursor.column, buffer.line_len(cursor.row).unwrap()); + let cursor = buffer.anchor_before(cursor).unwrap(); + + edit_ranges.push(start..end); + new_selections.push(Selection { + start: cursor.clone(), + end: cursor, + reversed: false, + goal_column: None, + }); + } + + self.update_selections(new_selections, true, ctx); + self.buffer + .update(ctx, |buffer, ctx| buffer.edit(edit_ranges, "", Some(ctx))) + .unwrap(); + self.end_transaction(ctx); + } + pub fn cut(&mut self, _: &(), ctx: &mut ViewContext) { self.start_transaction(ctx); let mut text = String::new(); From ccd244bac7e7b91822ed6ab79dcd6d6dd4053851 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 27 Apr 2021 19:28:00 +0200 Subject: [PATCH 07/28] WIP: start on a test for `delete_line` Discovered a bug that's highlighted by the test that causes selections to not be in the right order. --- zed/src/editor/buffer_view.rs | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 0bb47c8966a303d60f86498dc0a064431ece9afa..c7c02650f78df5c89ccb12619cc6edcdaf7fbb91 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1933,6 +1933,47 @@ mod tests { }) } + #[test] + fn test_delete_lines() { + App::test((), |app| { + let settings = settings::channel(&app.font_cache()).unwrap().1; + let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx)); + let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + view.update(app, |view, ctx| { + view.select_display_ranges( + &[ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), + DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), + ], + ctx, + ) + .unwrap(); + view.delete_line(&(), ctx); + }); + assert_eq!(view.read(app).text(app.as_ref()), "ghi"); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), + DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3) + ] + ); + + // view.undo(&(), ctx); + // view.select_display_ranges( + // &[ + // DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), + // DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), + // DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), + // ], + // ctx, + // ) + // .unwrap(); + // }); + }); + } + #[test] fn test_clipboard() { App::test((), |app| { From cee95091272c56a9a2feda55f41876baa4476941 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 28 Apr 2021 09:53:25 +0200 Subject: [PATCH 08/28] Fix `delete_line` test --- zed/src/editor/buffer_view.rs | 82 ++++++++++++++++------------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index c7c02650f78df5c89ccb12619cc6edcdaf7fbb91..80f4533274e69e62819f5e0b58d46846392524ec 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -544,61 +544,65 @@ impl BufferView { let app = ctx.as_ref(); let buffer = self.buffer.read(app); - // Accumulate contiguous regions of rows that we want to delete. - let mut row_ranges: Vec<(u32, Range)> = Vec::new(); - for selection in self.selections(app) { + let mut new_cursors = Vec::new(); + let mut edit_ranges = Vec::new(); + + let mut selections = self.selections(app).iter().peekable(); + while let Some(selection) = selections.next() { let start = selection.start.to_point(buffer).unwrap(); - let end = selection.end.to_point(buffer).unwrap(); + let mut end = selection.end.to_point(buffer).unwrap(); let goal_column = if selection.reversed { start.column } else { end.column }; - if let Some((_, last_row_range)) = row_ranges.last_mut() { - if start.row <= last_row_range.end { - *last_row_range = last_row_range.start..start.row + 1; + // Accumulate contiguous regions of rows that we want to delete. + while let Some(next_selection) = selections.peek() { + let next_start = next_selection.start.to_point(buffer).unwrap(); + if next_start.row <= end.row + 1 { + end = next_selection.end.to_point(buffer).unwrap(); + selections.next().unwrap(); } else { - row_ranges.push((goal_column, start.row..end.row + 1)); + break; } - } else { - row_ranges.push((goal_column, start.row..end.row + 1)); } - } - let mut edit_ranges = Vec::new(); - let mut new_selections = Vec::new(); - for (goal_column, range) in row_ranges { - let mut start = Point::new(range.start, 0).to_offset(buffer).unwrap(); - let end; + let mut edit_start = Point::new(start.row, 0).to_offset(buffer).unwrap(); + let edit_end; let mut cursor; - if let Ok(end_offset) = Point::new(range.end, 0).to_offset(buffer) { + if let Ok(end_offset) = Point::new(end.row + 1, 0).to_offset(buffer) { // If there's a line after the range, delete the \n from the end of the row range // and position the cursor on the next line. - end = end_offset; - cursor = Point::new(range.end, goal_column); + edit_end = end_offset; + cursor = Point::new(end.row + 1, goal_column); } else { // If there isn't a line after the range, delete the \n from the line before the // start of the row range and position the cursor there. - start = start.saturating_sub(1); - end = buffer.len(); - cursor = Point::new(range.start.saturating_sub(1), goal_column); + edit_start = edit_start.saturating_sub(1); + edit_end = buffer.len(); + cursor = Point::new(start.row.saturating_sub(1), goal_column); } - // We tried to maintain the column of the original cursors but the new lines may be + // We tried to maintain the column of the original cursor but the new line may be // shorter, so clip the new cursor's column. cursor.column = cmp::min(cursor.column, buffer.line_len(cursor.row).unwrap()); - let cursor = buffer.anchor_before(cursor).unwrap(); - edit_ranges.push(start..end); - new_selections.push(Selection { - start: cursor.clone(), - end: cursor, - reversed: false, - goal_column: None, - }); + new_cursors.push(cursor); + edit_ranges.push(edit_start..edit_end); } + new_cursors.sort_unstable(); + let new_selections = new_cursors + .into_iter() + .map(|cursor| buffer.anchor_before(cursor).unwrap()) + .map(|anchor| Selection { + start: anchor.clone(), + end: anchor, + reversed: false, + goal_column: None, + }) + .collect(); self.update_selections(new_selections, true, ctx); self.buffer .update(ctx, |buffer, ctx| buffer.edit(edit_ranges, "", Some(ctx))) @@ -1955,22 +1959,10 @@ mod tests { assert_eq!( view.read(app).selection_ranges(app.as_ref()), vec![ - DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), - DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3) + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1) ] ); - - // view.undo(&(), ctx); - // view.select_display_ranges( - // &[ - // DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), - // DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), - // DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), - // ], - // ctx, - // ) - // .unwrap(); - // }); }); } From 618cb8ac184c1186b79ddbb5923bf881562595c4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 28 Apr 2021 10:20:47 +0200 Subject: [PATCH 09/28] Bind also `ctrl-d` to `buffer:delete` --- zed/src/editor/buffer_view.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 80f4533274e69e62819f5e0b58d46846392524ec..622b7ba5653ad2917d33599f24cc53e31e5961ef 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -31,6 +31,7 @@ pub fn init(app: &mut MutableAppContext) { app.add_bindings(vec![ Binding::new("backspace", "buffer:backspace", Some("BufferView")), Binding::new("delete", "buffer:delete", Some("BufferView")), + Binding::new("ctrl-d", "buffer:delete", Some("BufferView")), Binding::new("enter", "buffer:newline", Some("BufferView")), Binding::new("ctrl-shift-K", "buffer:delete_line", Some("BufferView")), Binding::new("cmd-x", "buffer:cut", Some("BufferView")), From 2a0a2ee636a0464262d66525e06465103d03d34f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 28 Apr 2021 10:21:17 +0200 Subject: [PATCH 10/28] Fix `delete_line` for non-empty selections that end at the start of line --- zed/src/editor/buffer_view.rs | 42 +++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 622b7ba5653ad2917d33599f24cc53e31e5961ef..ba3ea337a44b689a957477362c6ae91f3f523e04 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -413,13 +413,24 @@ impl BufferView { where T: IntoIterator>, { + use std::mem; + let map = self.display_map.read(ctx); let mut selections = Vec::new(); for range in ranges { + let mut start = range.start; + let mut end = range.end; + let reversed = if start > end { + mem::swap(&mut start, &mut end); + true + } else { + false + }; + selections.push(Selection { - start: map.anchor_before(range.start, Bias::Left, ctx.as_ref())?, - end: map.anchor_before(range.end, Bias::Left, ctx.as_ref())?, - reversed: false, + start: map.anchor_before(start, Bias::Left, ctx.as_ref())?, + end: map.anchor_before(end, Bias::Left, ctx.as_ref())?, + reversed, goal_column: None, }); } @@ -569,6 +580,12 @@ impl BufferView { } } + // When the deletion straddles multiple rows but ends at the beginning of a line, avoid + // deleting that final line. + if start.row != end.row && end.column == 0 { + end.row -= 1; + } + let mut edit_start = Point::new(start.row, 0).to_offset(buffer).unwrap(); let edit_end; let mut cursor; @@ -1939,7 +1956,7 @@ mod tests { } #[test] - fn test_delete_lines() { + fn test_delete_line() { App::test((), |app| { let settings = settings::channel(&app.font_cache()).unwrap().1; let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx)); @@ -1964,6 +1981,23 @@ mod tests { DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1) ] ); + + let settings = settings::channel(&app.font_cache()).unwrap().1; + let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx)); + let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + view.update(app, |view, ctx| { + view.select_display_ranges( + &[DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)], + ctx, + ) + .unwrap(); + view.delete_line(&(), ctx); + }); + assert_eq!(view.read(app).text(app.as_ref()), "ghi\n"); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)] + ); }); } From 5d28fb871fa26af01468b5432d5be5f33b14ff4f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 28 Apr 2021 11:09:00 +0200 Subject: [PATCH 11/28] Extract a `Selection::buffer_row_range` method --- zed/src/editor/buffer/selection.rs | 25 ++++++++++++++- zed/src/editor/buffer_view.rs | 50 ++++++++++++++---------------- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/zed/src/editor/buffer/selection.rs b/zed/src/editor/buffer/selection.rs index 99c71e0a4426e61ac863729324434b586ab83eb5..514d87394bd7f0e76793da0bc9176dba71bb54c5 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -1,7 +1,7 @@ use crate::{ editor::{ buffer::{Anchor, Buffer, Point, ToPoint}, - display_map::DisplayMap, + display_map::{Bias, DisplayMap}, DisplayPoint, }, time, @@ -72,4 +72,27 @@ impl Selection { start..end } } + + pub fn buffer_row_range(&self, map: &DisplayMap, ctx: &AppContext) -> Range { + let display_start = self.start.to_display_point(map, ctx).unwrap(); + let buffer_start = DisplayPoint::new(display_start.row(), 0) + .to_buffer_point(map, Bias::Left, ctx) + .unwrap(); + + let mut display_end = self.end.to_display_point(map, ctx).unwrap(); + if display_end != map.max_point(ctx) + && display_start.row() != display_end.row() + && display_end.column() == 0 + { + *display_end.row_mut() -= 1; + } + let buffer_end = DisplayPoint::new( + display_end.row(), + map.line_len(display_end.row(), ctx).unwrap(), + ) + .to_buffer_point(map, Bias::Left, ctx) + .unwrap(); + + buffer_start.row..buffer_end.row + 1 + } } diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index ba3ea337a44b689a957477362c6ae91f3f523e04..3c0fd8be83f7cdc09412777261077f9b802c6047 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -554,6 +554,7 @@ impl BufferView { self.start_transaction(ctx); let app = ctx.as_ref(); + let map = self.display_map.read(app); let buffer = self.buffer.read(app); let mut new_cursors = Vec::new(); @@ -561,52 +562,49 @@ impl BufferView { let mut selections = self.selections(app).iter().peekable(); while let Some(selection) = selections.next() { - let start = selection.start.to_point(buffer).unwrap(); - let mut end = selection.end.to_point(buffer).unwrap(); - let goal_column = if selection.reversed { - start.column - } else { - end.column - }; + let mut range = selection.buffer_row_range(map, app); + let goal_display_column = selection + .head() + .to_display_point(map, app) + .unwrap() + .column(); // Accumulate contiguous regions of rows that we want to delete. while let Some(next_selection) = selections.peek() { - let next_start = next_selection.start.to_point(buffer).unwrap(); - if next_start.row <= end.row + 1 { - end = next_selection.end.to_point(buffer).unwrap(); + let next_range = next_selection.buffer_row_range(map, app); + if next_range.start <= range.end { + range.end = next_range.end; selections.next().unwrap(); } else { break; } } - // When the deletion straddles multiple rows but ends at the beginning of a line, avoid - // deleting that final line. - if start.row != end.row && end.column == 0 { - end.row -= 1; - } - - let mut edit_start = Point::new(start.row, 0).to_offset(buffer).unwrap(); + let mut edit_start = Point::new(range.start, 0).to_offset(buffer).unwrap(); let edit_end; - let mut cursor; - - if let Ok(end_offset) = Point::new(end.row + 1, 0).to_offset(buffer) { + let cursor_buffer_row; + if let Ok(end_offset) = Point::new(range.end, 0).to_offset(buffer) { // If there's a line after the range, delete the \n from the end of the row range // and position the cursor on the next line. edit_end = end_offset; - cursor = Point::new(end.row + 1, goal_column); + cursor_buffer_row = range.end; } else { // If there isn't a line after the range, delete the \n from the line before the // start of the row range and position the cursor there. edit_start = edit_start.saturating_sub(1); edit_end = buffer.len(); - cursor = Point::new(start.row.saturating_sub(1), goal_column); + cursor_buffer_row = range.start.saturating_sub(1); } - // We tried to maintain the column of the original cursor but the new line may be - // shorter, so clip the new cursor's column. - cursor.column = cmp::min(cursor.column, buffer.line_len(cursor.row).unwrap()); - new_cursors.push(cursor); + let mut cursor = Point::new(cursor_buffer_row, 0) + .to_display_point(map, app) + .unwrap(); + *cursor.column_mut() = cmp::min( + goal_display_column, + map.line_len(cursor.row(), app).unwrap(), + ); + + new_cursors.push(cursor.to_buffer_point(map, Bias::Left, app).unwrap()); edit_ranges.push(edit_start..edit_end); } From fdefd32de079fbe8fac29368faa1c9a87a2c7ab0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 28 Apr 2021 12:23:45 +0200 Subject: [PATCH 12/28] Implement `duplicate_line` for buffer --- zed/src/editor/buffer_view.rs | 125 ++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 3c0fd8be83f7cdc09412777261077f9b802c6047..b92fc6fea34bb32ae04d150fb9fa015318aa80e7 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -34,6 +34,7 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("ctrl-d", "buffer:delete", Some("BufferView")), Binding::new("enter", "buffer:newline", Some("BufferView")), Binding::new("ctrl-shift-K", "buffer:delete_line", Some("BufferView")), + Binding::new("cmd-shift-D", "buffer:duplicate_line", Some("BufferView")), Binding::new("cmd-x", "buffer:cut", Some("BufferView")), Binding::new("cmd-c", "buffer:copy", Some("BufferView")), Binding::new("cmd-v", "buffer:paste", Some("BufferView")), @@ -74,6 +75,7 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:backspace", BufferView::backspace); app.add_action("buffer:delete", BufferView::delete); app.add_action("buffer:delete_line", BufferView::delete_line); + app.add_action("buffer:duplicate_line", BufferView::duplicate_line); app.add_action("buffer:cut", BufferView::cut); app.add_action("buffer:copy", BufferView::copy); app.add_action("buffer:paste", BufferView::paste); @@ -626,6 +628,68 @@ impl BufferView { self.end_transaction(ctx); } + pub fn duplicate_line(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + + let mut selections = self.selections(ctx.as_ref()).to_vec(); + { + // Temporarily bias selections right to allow duplicate lines to push them down when + // they are at the start of a line. + let buffer = self.buffer.read(ctx); + for selection in &mut selections { + selection.start = selection.start.bias_right(buffer).unwrap(); + selection.end = selection.end.bias_right(buffer).unwrap(); + } + } + self.update_selections(selections.clone(), false, ctx); + + let app = ctx.as_ref(); + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(ctx); + + let mut edits = Vec::new(); + let mut selections_iter = selections.iter_mut().peekable(); + while let Some(selection) = selections_iter.next() { + // Avoid duplicating the same lines twice. + let mut range = selection.buffer_row_range(map, app); + while let Some(next_selection) = selections_iter.peek() { + let next_range = next_selection.buffer_row_range(map, app); + if next_range.start <= range.end - 1 { + range.end = next_range.end; + selections_iter.next().unwrap(); + } else { + break; + } + } + + // Copy the text from the selected row region and splice it at the start of the region. + let start = Point::new(range.start, 0); + let end = Point::new(range.end - 1, buffer.line_len(range.end - 1).unwrap()); + let text = buffer + .text_for_range(start..end) + .unwrap() + .chain(Some('\n')) + .collect::(); + edits.push((start, text)); + } + + self.buffer.update(ctx, |buffer, ctx| { + for (offset, text) in edits.into_iter().rev() { + buffer.edit(Some(offset..offset), text, Some(ctx)).unwrap(); + } + }); + + // Restore bias on selections. + let buffer = self.buffer.read(ctx); + for selection in &mut selections { + selection.start = selection.start.bias_right(buffer).unwrap(); + selection.end = selection.end.bias_right(buffer).unwrap(); + } + self.update_selections(selections, true, ctx); + + self.end_transaction(ctx); + } + pub fn cut(&mut self, _: &(), ctx: &mut ViewContext) { self.start_transaction(ctx); let mut text = String::new(); @@ -1999,6 +2063,67 @@ mod tests { }); } + #[test] + fn test_duplicate_line() { + App::test((), |app| { + let settings = settings::channel(&app.font_cache()).unwrap().1; + let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx)); + let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, 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(3, 0)..DisplayPoint::new(3, 0), + ], + ctx, + ) + .unwrap(); + view.duplicate_line(&(), ctx); + }); + assert_eq!( + view.read(app).text(app.as_ref()), + "abc\nabc\ndef\ndef\nghi\n\n" + ); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), + DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), + DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), + DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0), + ] + ); + + let settings = settings::channel(&app.font_cache()).unwrap().1; + let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx)); + let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + view.update(app, |view, ctx| { + view.select_display_ranges( + &[ + DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1), + DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1), + ], + ctx, + ) + .unwrap(); + view.duplicate_line(&(), ctx); + }); + assert_eq!( + view.read(app).text(app.as_ref()), + "abc\ndef\nghi\nabc\ndef\nghi\n" + ); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1), + DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1), + ] + ); + }); + } + #[test] fn test_clipboard() { App::test((), |app| { From 0aacf858ce9266e8bad052b019f198ef3b5fb862 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 29 Apr 2021 09:29:00 +0200 Subject: [PATCH 13/28] Don't rely on `Range::is_empty` to check for selection emptiness This method returns true when `start > end`, so our `backspace` and `delete` implementations were subtly wrong because they always deleted one extra character for reversed selections. --- zed/src/editor/buffer_view.rs | 28 ++++++++++++++++++---------- zed/src/editor/mod.rs | 9 +++------ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index b92fc6fea34bb32ae04d150fb9fa015318aa80e7..1833175fd98afeca613a47b57cb865639a770062 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -499,7 +499,8 @@ impl BufferView { let buffer = self.buffer.read(ctx); let map = self.display_map.read(ctx); for selection in &mut selections { - if selection.range(buffer).is_empty() { + let range = selection.range(buffer); + if range.start == range.end { let head = selection .head() .to_display_point(map, ctx.as_ref()) @@ -529,7 +530,8 @@ impl BufferView { let buffer = self.buffer.read(ctx); let map = self.display_map.read(ctx); for selection in &mut selections { - if selection.range(buffer).is_empty() { + let range = selection.range(buffer); + if range.start == range.end { let head = selection .head() .to_display_point(map, ctx.as_ref()) @@ -1174,15 +1176,19 @@ impl BufferView { let app = ctx.as_ref(); let map = self.display_map.read(app); for selection in self.selections(app) { - let (start, end) = selection.display_range(map, app).sorted(); - let buffer_start_row = start.to_buffer_point(map, Bias::Left, app).unwrap().row; + let range = selection.display_range(map, app).sorted(); + let buffer_start_row = range + .start + .to_buffer_point(map, Bias::Left, app) + .unwrap() + .row; - for row in (0..=end.row()).rev() { + for row in (0..=range.end.row()).rev() { if self.is_line_foldable(row, app) && !map.is_line_folded(row) { let fold_range = self.foldable_range_for_line(row, app).unwrap(); if fold_range.end.row >= buffer_start_row { fold_ranges.push(fold_range); - if row <= start.row() { + if row <= range.start.row() { break; } } @@ -1208,9 +1214,9 @@ impl BufferView { .selections(app) .iter() .map(|s| { - let (start, end) = s.display_range(map, app).sorted(); - let mut start = start.to_buffer_point(map, Bias::Left, app).unwrap(); - let mut end = end.to_buffer_point(map, Bias::Left, app).unwrap(); + let range = s.display_range(map, app).sorted(); + let mut start = range.start.to_buffer_point(map, Bias::Left, app).unwrap(); + let mut end = range.end.to_buffer_point(map, Bias::Left, app).unwrap(); start.column = 0; end.column = buffer.line_len(end.row).unwrap(); start..end @@ -1282,12 +1288,14 @@ impl BufferView { } pub fn fold_selected_ranges(&mut self, _: &(), ctx: &mut ViewContext) { + use super::RangeExt; + self.display_map.update(ctx, |map, ctx| { let buffer = self.buffer.read(ctx); let ranges = self .selections(ctx.as_ref()) .iter() - .map(|s| s.range(buffer)) + .map(|s| s.range(buffer).sorted()) .collect::>(); map.fold(ranges, ctx).unwrap(); }); diff --git a/zed/src/editor/mod.rs b/zed/src/editor/mod.rs index f998963376f443eaa0eb02e9569baa9303fee917..0229e6ae7e08169a4c5e4f7e8a265f712b55137b 100644 --- a/zed/src/editor/mod.rs +++ b/zed/src/editor/mod.rs @@ -12,14 +12,11 @@ use display_map::*; use std::{cmp, ops::Range}; trait RangeExt { - fn sorted(&self) -> (T, T); + fn sorted(&self) -> Range; } impl RangeExt for Range { - fn sorted(&self) -> (T, T) { - ( - cmp::min(&self.start, &self.end).clone(), - cmp::max(&self.start, &self.end).clone(), - ) + fn sorted(&self) -> Self { + cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone() } } From 283c734c91b96c5f1a7acac1af6aaa3df4f614cd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 29 Apr 2021 09:52:03 +0200 Subject: [PATCH 14/28] Implement `{move_to,select_to,delete_to}_{beginning,end}_of_line` --- zed/src/editor/buffer_view.rs | 319 +++++++++++++++++++++++++++--- zed/src/editor/display_map/mod.rs | 14 ++ zed/src/editor/movement.rs | 21 ++ 3 files changed, 330 insertions(+), 24 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 1833175fd98afeca613a47b57cb865639a770062..0b4e79eb652f6d762f2ef412e1ebc8b4091f7abe 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -34,6 +34,16 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("ctrl-d", "buffer:delete", Some("BufferView")), Binding::new("enter", "buffer:newline", Some("BufferView")), Binding::new("ctrl-shift-K", "buffer:delete_line", Some("BufferView")), + Binding::new( + "cmd-backspace", + "buffer:delete_to_beginning_of_line", + Some("BufferView"), + ), + Binding::new( + "cmd-delete", + "buffer:delete_to_end_of_line", + Some("BufferView"), + ), Binding::new("cmd-shift-D", "buffer:duplicate_line", Some("BufferView")), Binding::new("cmd-x", "buffer:cut", Some("BufferView")), Binding::new("cmd-c", "buffer:copy", Some("BufferView")), @@ -44,12 +54,50 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("down", "buffer:move_down", Some("BufferView")), Binding::new("left", "buffer:move_left", Some("BufferView")), Binding::new("right", "buffer:move_right", Some("BufferView")), + Binding::new( + "cmd-left", + "buffer:move_to_beginning_of_line", + Some("BufferView"), + ), + Binding::new( + "ctrl-a", + "buffer:move_to_beginning_of_line", + Some("BufferView"), + ), + Binding::new( + "cmd-right", + "buffer:move_to_end_of_line", + Some("BufferView"), + ), + Binding::new("ctrl-e", "buffer:move_to_end_of_line", Some("BufferView")), Binding::new("cmd-up", "buffer:move_to_beginning", Some("BufferView")), Binding::new("cmd-down", "buffer:move_to_end", Some("BufferView")), Binding::new("shift-up", "buffer:select_up", Some("BufferView")), Binding::new("shift-down", "buffer:select_down", Some("BufferView")), Binding::new("shift-left", "buffer:select_left", Some("BufferView")), Binding::new("shift-right", "buffer:select_right", Some("BufferView")), + Binding::new( + "cmd-shift-left", + "buffer:select_to_beginning_of_line", + Some("BufferView"), + ) + .with_arg(true), + Binding::new( + "ctrl-shift-A", + "buffer:select_to_beginning_of_line", + Some("BufferView"), + ) + .with_arg(true), + Binding::new( + "cmd-shift-right", + "buffer:select_to_end_of_line", + Some("BufferView"), + ), + Binding::new( + "ctrl-shift-E", + "buffer:select_to_end_of_line", + Some("BufferView"), + ), Binding::new( "cmd-shift-up", "buffer:select_to_beginning", @@ -75,6 +123,14 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:backspace", BufferView::backspace); app.add_action("buffer:delete", BufferView::delete); app.add_action("buffer:delete_line", BufferView::delete_line); + app.add_action( + "buffer:delete_to_beginning_of_line", + BufferView::delete_to_beginning_of_line, + ); + app.add_action( + "buffer:delete_to_end_of_line", + BufferView::delete_to_end_of_line, + ); app.add_action("buffer:duplicate_line", BufferView::duplicate_line); app.add_action("buffer:cut", BufferView::cut); app.add_action("buffer:copy", BufferView::copy); @@ -85,12 +141,28 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:move_down", BufferView::move_down); app.add_action("buffer:move_left", BufferView::move_left); app.add_action("buffer:move_right", BufferView::move_right); + app.add_action( + "buffer:move_to_beginning_of_line", + BufferView::move_to_beginning_of_line, + ); + app.add_action( + "buffer:move_to_end_of_line", + BufferView::move_to_end_of_line, + ); app.add_action("buffer:move_to_beginning", BufferView::move_to_beginning); app.add_action("buffer:move_to_end", BufferView::move_to_end); app.add_action("buffer:select_up", BufferView::select_up); app.add_action("buffer:select_down", BufferView::select_down); app.add_action("buffer:select_left", BufferView::select_left); app.add_action("buffer:select_right", BufferView::select_right); + app.add_action( + "buffer:select_to_beginning_of_line", + BufferView::select_to_beginning_of_line, + ); + app.add_action( + "buffer:select_to_end_of_line", + BufferView::select_to_end_of_line, + ); app.add_action( "buffer:select_to_beginning", BufferView::select_to_beginning, @@ -1013,6 +1085,94 @@ impl BufferView { self.update_selections(selections, true, ctx); } + pub fn move_to_beginning_of_line(&mut self, _: &(), ctx: &mut ViewContext) { + let app = ctx.as_ref(); + let mut selections = self.selections(app).to_vec(); + { + let map = self.display_map.read(app); + for selection in &mut selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let new_head = movement::line_beginning(map, head, true, app).unwrap(); + let anchor = map.anchor_before(new_head, Bias::Left, app).unwrap(); + selection.start = anchor.clone(); + selection.end = anchor; + selection.reversed = false; + selection.goal_column = None; + } + } + self.update_selections(selections, true, ctx); + } + + pub fn select_to_beginning_of_line( + &mut self, + toggle_indent: &bool, + ctx: &mut ViewContext, + ) { + let app = ctx.as_ref(); + let mut selections = self.selections(app).to_vec(); + { + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(app); + for selection in &mut selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let new_head = movement::line_beginning(map, head, *toggle_indent, app).unwrap(); + let anchor = map.anchor_before(new_head, Bias::Left, app).unwrap(); + selection.set_head(buffer, anchor); + selection.goal_column = None; + } + } + self.update_selections(selections, true, ctx); + } + + pub fn delete_to_beginning_of_line(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + self.select_to_beginning_of_line(&false, ctx); + self.backspace(&(), ctx); + self.end_transaction(ctx); + } + + pub fn move_to_end_of_line(&mut self, _: &(), ctx: &mut ViewContext) { + let app = ctx.as_ref(); + let mut selections = self.selections(app).to_vec(); + { + let map = self.display_map.read(app); + for selection in &mut selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let new_head = movement::line_end(map, head, app).unwrap(); + let anchor = map.anchor_before(new_head, Bias::Left, app).unwrap(); + selection.start = anchor.clone(); + selection.end = anchor; + selection.reversed = false; + selection.goal_column = None; + } + } + self.update_selections(selections, true, ctx); + } + + pub fn select_to_end_of_line(&mut self, _: &(), ctx: &mut ViewContext) { + let app = ctx.as_ref(); + let mut selections = self.selections(app).to_vec(); + { + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(app); + for selection in &mut selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let new_head = movement::line_end(map, head, app).unwrap(); + let anchor = map.anchor_before(new_head, Bias::Left, app).unwrap(); + selection.set_head(buffer, anchor); + selection.goal_column = None; + } + } + self.update_selections(selections, true, ctx); + } + + pub fn delete_to_end_of_line(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + self.select_to_end_of_line(&(), ctx); + self.delete(&(), ctx); + self.end_transaction(ctx); + } + pub fn move_to_beginning(&mut self, _: &(), ctx: &mut ViewContext) { let selection = Selection { start: Anchor::Start, @@ -1230,16 +1390,17 @@ impl BufferView { } fn is_line_foldable(&self, display_row: u32, app: &AppContext) -> bool { + let map = self.display_map.read(app); let max_point = self.max_point(app); if display_row >= max_point.row() { false } else { - let (start_indent, is_blank) = self.line_indent(display_row, app).unwrap(); + let (start_indent, is_blank) = map.line_indent(display_row, app).unwrap(); if is_blank { false } else { for display_row in display_row + 1..=max_point.row() { - let (indent, is_blank) = self.line_indent(display_row, app).unwrap(); + let (indent, is_blank) = map.line_indent(display_row, app).unwrap(); if !is_blank { return indent > start_indent; } @@ -1249,33 +1410,15 @@ impl BufferView { } } - fn line_indent(&self, display_row: u32, app: &AppContext) -> Result<(usize, bool)> { - let mut indent = 0; - let mut is_blank = true; - for c in self - .display_map - .read(app) - .chars_at(DisplayPoint::new(display_row, 0), app)? - { - if c == ' ' { - indent += 1; - } else { - is_blank = c == '\n'; - break; - } - } - Ok((indent, is_blank)) - } - fn foldable_range_for_line(&self, start_row: u32, app: &AppContext) -> Result> { let map = self.display_map.read(app); let max_point = self.max_point(app); - let (start_indent, _) = self.line_indent(start_row, app)?; + let (start_indent, _) = map.line_indent(start_row, app)?; let start = DisplayPoint::new(start_row, self.line_len(start_row, app)?); let mut end = None; for row in start_row + 1..=max_point.row() { - let (indent, is_blank) = self.line_indent(row, app)?; + let (indent, is_blank) = map.line_indent(row, app)?; if !is_blank && indent <= start_indent { end = Some(DisplayPoint::new(row - 1, self.line_len(row - 1, app)?)); break; @@ -1951,6 +2094,134 @@ mod tests { }); } + #[test] + fn test_beginning_end_of_line() { + App::test((), |app| { + let buffer = app.add_model(|ctx| Buffer::new(0, "abc\n def", ctx)); + let settings = settings::channel(&app.font_cache()).unwrap().1; + let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + view.update(app, |view, ctx| { + view.select_display_ranges( + &[ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4), + ], + ctx, + ) + .unwrap(); + }); + + view.update(app, |view, ctx| view.move_to_beginning_of_line(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), + DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), + ] + ); + + view.update(app, |view, ctx| view.move_to_beginning_of_line(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), + ] + ); + + view.update(app, |view, ctx| view.move_to_beginning_of_line(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), + DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), + ] + ); + + view.update(app, |view, ctx| view.move_to_end_of_line(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), + DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5), + ] + ); + + // Moving to the end of line again is a no-op. + view.update(app, |view, ctx| view.move_to_end_of_line(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), + DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5), + ] + ); + + view.update(app, |view, ctx| { + view.move_left(&(), ctx); + view.select_to_beginning_of_line(&true, ctx); + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2), + ] + ); + + view.update(app, |view, ctx| { + view.select_to_beginning_of_line(&true, ctx) + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0), + ] + ); + + view.update(app, |view, ctx| { + view.select_to_beginning_of_line(&true, ctx) + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2), + ] + ); + + view.update(app, |view, ctx| view.select_to_end_of_line(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5), + ] + ); + + view.update(app, |view, ctx| view.delete_to_end_of_line(&(), ctx)); + assert_eq!(view.read(app).text(app.as_ref()), "ab\n de"); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4), + ] + ); + + view.update(app, |view, ctx| view.delete_to_beginning_of_line(&(), ctx)); + assert_eq!(view.read(app).text(app.as_ref()), "\n"); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), + ] + ); + }); + } + #[test] fn test_backspace() { App::test((), |app| { @@ -1971,7 +2242,7 @@ mod tests { // an empty selection - the preceding character is deleted DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), // one character selected - it is deleted - DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3), // a line suffix selected - it is deleted DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0), ], @@ -2008,7 +2279,7 @@ mod tests { // an empty selection - the following character is deleted DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), // one character selected - it is deleted - DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3), // a line suffix selected - it is deleted DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0), ], diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index c44f3ca302396f95159b1540d8bf507b1cfcceb5..e8d2b6c6e5e199eb50111edb1d70cc43c29d7586 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -67,6 +67,20 @@ impl DisplayMap { Ok(chars.take_while(|c| *c != '\n').collect()) } + pub fn line_indent(&self, display_row: u32, app: &AppContext) -> Result<(u32, bool)> { + let mut indent = 0; + let mut is_blank = true; + for c in self.chars_at(DisplayPoint::new(display_row, 0), app)? { + if c == ' ' { + indent += 1; + } else { + is_blank = c == '\n'; + break; + } + } + Ok((indent, is_blank)) + } + pub fn chars_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Result> { let column = point.column() as usize; let (point, to_next_stop) = point.collapse_tabs(self, Bias::Left, app)?; diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index 08e3600255524796f2b5a8d7d965919e85256385..44e981f71b0bbddbe413cfe215057cbcb66363f4 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -58,3 +58,24 @@ pub fn down( Ok((point, goal_column)) } + +pub fn line_beginning( + map: &DisplayMap, + point: DisplayPoint, + toggle_indent: bool, + app: &AppContext, +) -> Result { + let (indent, is_blank) = map.line_indent(point.row(), app)?; + if toggle_indent && !is_blank && point.column() != indent { + Ok(DisplayPoint::new(point.row(), indent)) + } else { + Ok(DisplayPoint::new(point.row(), 0)) + } +} + +pub fn line_end(map: &DisplayMap, point: DisplayPoint, app: &AppContext) -> Result { + Ok(DisplayPoint::new( + point.row(), + map.line_len(point.row(), app)?, + )) +} From d19fd77f917fb29282acb0e1bd53cae30da7f0c4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 29 Apr 2021 10:29:06 +0200 Subject: [PATCH 15/28] :lipstick: --- zed/src/editor/buffer/selection.rs | 2 +- zed/src/editor/buffer_view.rs | 32 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/zed/src/editor/buffer/selection.rs b/zed/src/editor/buffer/selection.rs index 514d87394bd7f0e76793da0bc9176dba71bb54c5..1d7459482c22a16407bf379ddff3017357c51566 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -73,7 +73,7 @@ impl Selection { } } - pub fn buffer_row_range(&self, map: &DisplayMap, ctx: &AppContext) -> Range { + pub fn buffer_rows_for_display_rows(&self, map: &DisplayMap, ctx: &AppContext) -> Range { let display_start = self.start.to_display_point(map, ctx).unwrap(); let buffer_start = DisplayPoint::new(display_start.row(), 0) .to_buffer_point(map, Bias::Left, ctx) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 0b4e79eb652f6d762f2ef412e1ebc8b4091f7abe..e0d2897276490915c81ed484403bf55d8a8addb6 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -638,7 +638,7 @@ impl BufferView { let mut selections = self.selections(app).iter().peekable(); while let Some(selection) = selections.next() { - let mut range = selection.buffer_row_range(map, app); + let mut rows = selection.buffer_rows_for_display_rows(map, app); let goal_display_column = selection .head() .to_display_point(map, app) @@ -647,29 +647,29 @@ impl BufferView { // Accumulate contiguous regions of rows that we want to delete. while let Some(next_selection) = selections.peek() { - let next_range = next_selection.buffer_row_range(map, app); - if next_range.start <= range.end { - range.end = next_range.end; + let next_rows = next_selection.buffer_rows_for_display_rows(map, app); + if next_rows.start <= rows.end { + rows.end = next_rows.end; selections.next().unwrap(); } else { break; } } - let mut edit_start = Point::new(range.start, 0).to_offset(buffer).unwrap(); + let mut edit_start = Point::new(rows.start, 0).to_offset(buffer).unwrap(); let edit_end; let cursor_buffer_row; - if let Ok(end_offset) = Point::new(range.end, 0).to_offset(buffer) { + if let Ok(end_offset) = Point::new(rows.end, 0).to_offset(buffer) { // If there's a line after the range, delete the \n from the end of the row range // and position the cursor on the next line. edit_end = end_offset; - cursor_buffer_row = range.end; + cursor_buffer_row = rows.end; } else { // If there isn't a line after the range, delete the \n from the line before the // start of the row range and position the cursor there. edit_start = edit_start.saturating_sub(1); edit_end = buffer.len(); - cursor_buffer_row = range.start.saturating_sub(1); + cursor_buffer_row = rows.start.saturating_sub(1); } let mut cursor = Point::new(cursor_buffer_row, 0) @@ -707,8 +707,8 @@ impl BufferView { let mut selections = self.selections(ctx.as_ref()).to_vec(); { - // Temporarily bias selections right to allow duplicate lines to push them down when - // they are at the start of a line. + // Temporarily bias selections right to allow newly duplicate lines to push them down + // when the selections are at the beginning of a line. let buffer = self.buffer.read(ctx); for selection in &mut selections { selection.start = selection.start.bias_right(buffer).unwrap(); @@ -725,11 +725,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 range = selection.buffer_row_range(map, app); + let mut rows = selection.buffer_rows_for_display_rows(map, app); while let Some(next_selection) = selections_iter.peek() { - let next_range = next_selection.buffer_row_range(map, app); - if next_range.start <= range.end - 1 { - range.end = next_range.end; + let next_rows = next_selection.buffer_rows_for_display_rows(map, app); + if next_rows.start <= rows.end - 1 { + rows.end = next_rows.end; selections_iter.next().unwrap(); } else { break; @@ -737,8 +737,8 @@ impl BufferView { } // Copy the text from the selected row region and splice it at the start of the region. - let start = Point::new(range.start, 0); - let end = Point::new(range.end - 1, buffer.line_len(range.end - 1).unwrap()); + let start = Point::new(rows.start, 0); + let end = Point::new(rows.end - 1, buffer.line_len(rows.end - 1).unwrap()); let text = buffer .text_for_range(start..end) .unwrap() From 0a28c78a7a5ce13999fa04b1add211dc3f8bf404 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 29 Apr 2021 14:54:50 +0200 Subject: [PATCH 16/28] Implement `move_to_next_word_boundary` for buffer Co-Authored-By: Nathan Sobo --- zed/src/editor/buffer_view.rs | 27 ++++++++++++++++++++++++ zed/src/editor/movement.rs | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index c0b320cfac568a70af20476ba05d0b5375f9160e..f5aa563ccde07bdf93ea5cf5f4d277094f34ec71 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -54,6 +54,11 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("down", "buffer:move_down", Some("BufferView")), Binding::new("left", "buffer:move_left", Some("BufferView")), Binding::new("right", "buffer:move_right", Some("BufferView")), + Binding::new( + "alt-right", + "buffer:move_to_next_word_boundary", + Some("BufferView"), + ), Binding::new( "cmd-left", "buffer:move_to_beginning_of_line", @@ -141,6 +146,10 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:move_down", BufferView::move_down); app.add_action("buffer:move_left", BufferView::move_left); app.add_action("buffer:move_right", BufferView::move_right); + app.add_action( + "buffer:move_to_next_word_boundary", + BufferView::move_to_next_word_boundary, + ); app.add_action( "buffer:move_to_beginning_of_line", BufferView::move_to_beginning_of_line, @@ -1085,6 +1094,24 @@ impl BufferView { self.update_selections(selections, true, ctx); } + pub fn move_to_next_word_boundary(&mut self, _: &(), ctx: &mut ViewContext) { + let app = ctx.as_ref(); + let mut selections = self.selections(app).to_vec(); + { + let map = self.display_map.read(app); + for selection in &mut selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let new_head = movement::next_word_boundary(map, head, app).unwrap(); + let anchor = map.anchor_before(new_head, Bias::Left, app).unwrap(); + selection.start = anchor.clone(); + selection.end = anchor; + selection.reversed = false; + selection.goal_column = None; + } + } + self.update_selections(selections, true, ctx); + } + pub fn move_to_beginning_of_line(&mut self, _: &(), ctx: &mut ViewContext) { let app = ctx.as_ref(); let mut selections = self.selections(app).to_vec(); diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index 44e981f71b0bbddbe413cfe215057cbcb66363f4..040e59705dc40070269ca9ca26f8c9b753ddb593 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -79,3 +79,42 @@ pub fn line_end(map: &DisplayMap, point: DisplayPoint, app: &AppContext) -> Resu map.line_len(point.row(), app)?, )) } + +pub fn prev_word_boundary( + map: &DisplayMap, + point: DisplayPoint, + app: &AppContext, +) -> Result { + todo!() +} + +pub fn next_word_boundary( + map: &DisplayMap, + mut point: DisplayPoint, + app: &AppContext, +) -> Result { + let mut prev_c = None; + for c in map.chars_at(point, app)? { + if prev_c.is_some() && (c == '\n' || is_word_char(prev_c.unwrap()) != is_word_char(c)) { + break; + } + + if c == '\n' { + *point.row_mut() += 1; + *point.column_mut() = 0; + } else { + *point.column_mut() += 1; + } + prev_c = Some(c); + } + Ok(point) +} + +fn is_word_char(c: char) -> bool { + match c { + '/' | '\\' | '(' | ')' | '"' | '\'' | ':' | ',' | '.' | ';' | '<' | '>' | '~' | '!' + | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '|' | '+' | '=' | '[' | ']' | '{' | '}' + | '`' | '?' | '-' | '…' | ' ' | '\n' => false, + _ => true, + } +} From bc686b45615a67a9ea7395105641b684df250260 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 29 Apr 2021 18:52:11 +0200 Subject: [PATCH 17/28] Implement `move_to_previous_word_boundary` Co-Authored-By: Max Brunsfeld --- zed/src/editor/buffer_view.rs | 27 ++++++++++++++++++++ zed/src/editor/movement.rs | 48 +++++++++++++++++++++++++++++------ 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index f5aa563ccde07bdf93ea5cf5f4d277094f34ec71..985dce20d2213bc8f7952db44e666acd2bbedd8a 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -54,6 +54,11 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("down", "buffer:move_down", Some("BufferView")), Binding::new("left", "buffer:move_left", Some("BufferView")), Binding::new("right", "buffer:move_right", Some("BufferView")), + Binding::new( + "alt-left", + "buffer:move_to_previous_word_boundary", + Some("BufferView"), + ), Binding::new( "alt-right", "buffer:move_to_next_word_boundary", @@ -146,6 +151,10 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:move_down", BufferView::move_down); app.add_action("buffer:move_left", BufferView::move_left); app.add_action("buffer:move_right", BufferView::move_right); + app.add_action( + "buffer:move_to_previous_word_boundary", + BufferView::move_to_previous_word_boundary, + ); app.add_action( "buffer:move_to_next_word_boundary", BufferView::move_to_next_word_boundary, @@ -1094,6 +1103,24 @@ impl BufferView { self.update_selections(selections, true, ctx); } + pub fn move_to_previous_word_boundary(&mut self, _: &(), ctx: &mut ViewContext) { + let app = ctx.as_ref(); + let mut selections = self.selections(app).to_vec(); + { + let map = self.display_map.read(app); + for selection in &mut selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let new_head = movement::prev_word_boundary(map, head, app).unwrap(); + let anchor = map.anchor_before(new_head, Bias::Left, app).unwrap(); + selection.start = anchor.clone(); + selection.end = anchor; + selection.reversed = false; + selection.goal_column = None; + } + } + self.update_selections(selections, true, ctx); + } + pub fn move_to_next_word_boundary(&mut self, _: &(), ctx: &mut ViewContext) { let app = ctx.as_ref(); let mut selections = self.selections(app).to_vec(); diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index 040e59705dc40070269ca9ca26f8c9b753ddb593..5df463f43b2393862887722f62128bd450cfea1b 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -85,7 +85,31 @@ pub fn prev_word_boundary( point: DisplayPoint, app: &AppContext, ) -> Result { - todo!() + if point.column() == 0 { + if point.row() == 0 { + Ok(DisplayPoint::new(0, 0)) + } else { + let row = point.row() - 1; + Ok(DisplayPoint::new(row, map.line_len(row, app)?)) + } + } else { + let mut boundary = DisplayPoint::new(point.row(), 0); + let mut column = 0; + let mut prev_c = None; + for c in map.chars_at(boundary, app)? { + if column >= point.column() { + break; + } + + if prev_c.is_none() || char_kind(prev_c.unwrap()) != char_kind(c) { + *boundary.column_mut() = column; + } + + prev_c = Some(c); + column += 1; + } + Ok(boundary) + } } pub fn next_word_boundary( @@ -95,7 +119,7 @@ pub fn next_word_boundary( ) -> Result { let mut prev_c = None; for c in map.chars_at(point, app)? { - if prev_c.is_some() && (c == '\n' || is_word_char(prev_c.unwrap()) != is_word_char(c)) { + if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) { break; } @@ -110,11 +134,19 @@ pub fn next_word_boundary( Ok(point) } -fn is_word_char(c: char) -> bool { - match c { - '/' | '\\' | '(' | ')' | '"' | '\'' | ':' | ',' | '.' | ';' | '<' | '>' | '~' | '!' - | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '|' | '+' | '=' | '[' | ']' | '{' | '}' - | '`' | '?' | '-' | '…' | ' ' | '\n' => false, - _ => true, +#[derive(Copy, Clone, Eq, PartialEq)] +enum CharKind { + Whitespace, + Punctuation, + Word, +} + +fn char_kind(c: char) -> CharKind { + if c.is_whitespace() { + CharKind::Whitespace + } else if c.is_alphanumeric() || c == '_' { + CharKind::Word + } else { + CharKind::Punctuation } } From 1a0dbb2907af376d89b0ab8836ae4474e3125a4d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 30 Apr 2021 09:41:38 +0200 Subject: [PATCH 18/28] Implement `select_to_{previous,next}_word_boundary` --- zed/src/editor/buffer_view.rs | 52 +++++++++++++++++++++++++++++++++++ zed/src/editor/movement.rs | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 985dce20d2213bc8f7952db44e666acd2bbedd8a..3ba8ceb3f1cfac14b120695602253f8878a6385b 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -86,6 +86,16 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("shift-down", "buffer:select_down", Some("BufferView")), Binding::new("shift-left", "buffer:select_left", Some("BufferView")), Binding::new("shift-right", "buffer:select_right", Some("BufferView")), + Binding::new( + "alt-shift-left", + "buffer:select_to_previous_word_boundary", + Some("BufferView"), + ), + Binding::new( + "alt-shift-right", + "buffer:select_to_next_word_boundary", + Some("BufferView"), + ), Binding::new( "cmd-shift-left", "buffer:select_to_beginning_of_line", @@ -173,6 +183,14 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:select_down", BufferView::select_down); app.add_action("buffer:select_left", BufferView::select_left); app.add_action("buffer:select_right", BufferView::select_right); + app.add_action( + "buffer:select_to_previous_word_boundary", + BufferView::select_to_previous_word_boundary, + ); + app.add_action( + "buffer:select_to_next_word_boundary", + BufferView::select_to_next_word_boundary, + ); app.add_action( "buffer:select_to_beginning_of_line", BufferView::select_to_beginning_of_line, @@ -1121,6 +1139,23 @@ impl BufferView { self.update_selections(selections, true, ctx); } + pub fn select_to_previous_word_boundary(&mut self, _: &(), ctx: &mut ViewContext) { + let app = ctx.as_ref(); + let mut selections = self.selections(app).to_vec(); + { + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(app); + for selection in &mut selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let new_head = movement::prev_word_boundary(map, head, app).unwrap(); + let anchor = map.anchor_before(new_head, Bias::Left, app).unwrap(); + selection.set_head(buffer, anchor); + selection.goal_column = None; + } + } + self.update_selections(selections, true, ctx); + } + pub fn move_to_next_word_boundary(&mut self, _: &(), ctx: &mut ViewContext) { let app = ctx.as_ref(); let mut selections = self.selections(app).to_vec(); @@ -1139,6 +1174,23 @@ impl BufferView { self.update_selections(selections, true, ctx); } + pub fn select_to_next_word_boundary(&mut self, _: &(), ctx: &mut ViewContext) { + let app = ctx.as_ref(); + let mut selections = self.selections(app).to_vec(); + { + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(app); + for selection in &mut selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let new_head = movement::next_word_boundary(map, head, app).unwrap(); + let anchor = map.anchor_before(new_head, Bias::Left, app).unwrap(); + selection.set_head(buffer, anchor); + selection.goal_column = None; + } + } + self.update_selections(selections, true, ctx); + } + pub fn move_to_beginning_of_line(&mut self, _: &(), ctx: &mut ViewContext) { let app = ctx.as_ref(); let mut selections = self.selections(app).to_vec(); diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index 5df463f43b2393862887722f62128bd450cfea1b..f4f0b0da4409c2105c55503f55be9aa731dbca21 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -119,7 +119,7 @@ pub fn next_word_boundary( ) -> Result { let mut prev_c = None; for c in map.chars_at(point, app)? { - if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) { + if prev_c.is_some() && char_kind(prev_c.unwrap()) != char_kind(c) { break; } From f352cfb6865553adc12031a59c8727ae0c9637e7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 30 Apr 2021 09:45:22 +0200 Subject: [PATCH 19/28] Implement `delete_to_{previous,next}_word_boundary` --- zed/src/editor/buffer_view.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 3ba8ceb3f1cfac14b120695602253f8878a6385b..99c57e41f86674d6441bd4aeeb7ff68e634e24e5 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -34,6 +34,16 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("ctrl-d", "buffer:delete", Some("BufferView")), Binding::new("enter", "buffer:newline", Some("BufferView")), Binding::new("ctrl-shift-K", "buffer:delete_line", Some("BufferView")), + Binding::new( + "alt-backspace", + "buffer:delete_to_previous_word_boundary", + Some("BufferView"), + ), + Binding::new( + "alt-delete", + "buffer:delete_to_next_word_boundary", + Some("BufferView"), + ), Binding::new( "cmd-backspace", "buffer:delete_to_beginning_of_line", @@ -143,6 +153,14 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:backspace", BufferView::backspace); app.add_action("buffer:delete", BufferView::delete); app.add_action("buffer:delete_line", BufferView::delete_line); + app.add_action( + "buffer:delete_to_previous_word_boundary", + BufferView::delete_to_previous_word_boundary, + ); + app.add_action( + "buffer:delete_to_next_word_boundary", + BufferView::delete_to_next_word_boundary, + ); app.add_action( "buffer:delete_to_beginning_of_line", BufferView::delete_to_beginning_of_line, @@ -1156,6 +1174,13 @@ impl BufferView { self.update_selections(selections, true, ctx); } + pub fn delete_to_previous_word_boundary(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + self.select_to_previous_word_boundary(&(), ctx); + self.backspace(&(), ctx); + self.end_transaction(ctx); + } + pub fn move_to_next_word_boundary(&mut self, _: &(), ctx: &mut ViewContext) { let app = ctx.as_ref(); let mut selections = self.selections(app).to_vec(); @@ -1191,6 +1216,13 @@ impl BufferView { self.update_selections(selections, true, ctx); } + pub fn delete_to_next_word_boundary(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + self.select_to_next_word_boundary(&(), ctx); + self.delete(&(), ctx); + self.end_transaction(ctx); + } + pub fn move_to_beginning_of_line(&mut self, _: &(), ctx: &mut ViewContext) { let app = ctx.as_ref(); let mut selections = self.selections(app).to_vec(); From 9c3216507b254ed934b9854a8dce6bd5891ed63a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 30 Apr 2021 10:13:45 +0200 Subject: [PATCH 20/28] Add test for word boundary movement/selection/deletion --- zed/src/editor/buffer_view.rs | 182 ++++++++++++++++++++++++++++++++++ zed/src/editor/movement.rs | 7 +- 2 files changed, 187 insertions(+), 2 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 99c57e41f86674d6441bd4aeeb7ff68e634e24e5..f6217aa428d8237ccabd522e37744a50a60cc734 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2357,6 +2357,188 @@ mod tests { }); } + #[test] + fn test_prev_next_word_boundary() { + App::test((), |app| { + let buffer = app + .add_model(|ctx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", ctx)); + let settings = settings::channel(&app.font_cache()).unwrap().1; + let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + view.update(app, |view, ctx| { + view.select_display_ranges( + &[ + DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11), + DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4), + ], + ctx, + ) + .unwrap(); + }); + + view.update(app, |view, ctx| { + view.move_to_previous_word_boundary(&(), ctx) + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9), + DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3), + ] + ); + + view.update(app, |view, ctx| { + view.move_to_previous_word_boundary(&(), ctx) + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7), + DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2), + ] + ); + + view.update(app, |view, ctx| { + view.move_to_previous_word_boundary(&(), ctx) + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4), + DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0), + ] + ); + + view.update(app, |view, ctx| { + view.move_to_previous_word_boundary(&(), ctx) + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), + ] + ); + + view.update(app, |view, ctx| { + view.move_to_previous_word_boundary(&(), ctx) + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), + DisplayPoint::new(0, 24)..DisplayPoint::new(0, 24), + ] + ); + + view.update(app, |view, ctx| { + view.move_to_previous_word_boundary(&(), ctx) + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), + DisplayPoint::new(0, 23)..DisplayPoint::new(0, 23), + ] + ); + + view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), + DisplayPoint::new(0, 24)..DisplayPoint::new(0, 24), + ] + ); + + view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), + ] + ); + + view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7), + DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0), + ] + ); + + view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9), + DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2), + ] + ); + + view.update(app, |view, ctx| { + view.move_right(&(), ctx); + view.select_to_previous_word_boundary(&(), ctx); + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9), + DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2), + ] + ); + + view.update(app, |view, ctx| { + view.select_to_previous_word_boundary(&(), ctx) + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 10)..DisplayPoint::new(0, 7), + DisplayPoint::new(2, 3)..DisplayPoint::new(2, 0), + ] + ); + + view.update(app, |view, ctx| view.select_to_next_word_boundary(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9), + DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2), + ] + ); + + view.update(app, |view, ctx| view.delete_to_next_word_boundary(&(), ctx)); + assert_eq!( + view.read(app).text(app.as_ref()), + "use std::s::{foo, bar}\n\n {az.qux()}" + ); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 10)..DisplayPoint::new(0, 10), + DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3), + ] + ); + + view.update(app, |view, ctx| { + view.delete_to_previous_word_boundary(&(), ctx) + }); + assert_eq!( + view.read(app).text(app.as_ref()), + "use std::::{foo, bar}\n\n az.qux()}" + ); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + &[ + DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9), + DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2), + ] + ); + }); + } + #[test] fn test_backspace() { App::test((), |app| { diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index f4f0b0da4409c2105c55503f55be9aa731dbca21..f9ba5fe33d4189fef21a66f1284f2c5d9f133bb4 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -119,7 +119,7 @@ pub fn next_word_boundary( ) -> Result { let mut prev_c = None; for c in map.chars_at(point, app)? { - if prev_c.is_some() && char_kind(prev_c.unwrap()) != char_kind(c) { + if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) { break; } @@ -136,13 +136,16 @@ pub fn next_word_boundary( #[derive(Copy, Clone, Eq, PartialEq)] enum CharKind { + Newline, Whitespace, Punctuation, Word, } fn char_kind(c: char) -> CharKind { - if c.is_whitespace() { + if c == '\n' { + CharKind::Newline + } else if c.is_whitespace() { CharKind::Whitespace } else if c.is_alphanumeric() || c == '_' { CharKind::Word From 51ae37e57841c746a4cd91433316f12ca03bcc94 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 30 Apr 2021 10:45:58 +0200 Subject: [PATCH 21/28] Bind `ctrl-h` to `backspace` --- zed/src/editor/buffer_view.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index c0b320cfac568a70af20476ba05d0b5375f9160e..b48c16c23b941a6cab44dca439ede1406e1e855c 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -30,6 +30,7 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); pub fn init(app: &mut MutableAppContext) { app.add_bindings(vec![ Binding::new("backspace", "buffer:backspace", Some("BufferView")), + Binding::new("ctrl-h", "buffer:backspace", Some("BufferView")), Binding::new("delete", "buffer:delete", Some("BufferView")), Binding::new("ctrl-d", "buffer:delete", Some("BufferView")), Binding::new("enter", "buffer:newline", Some("BufferView")), From 8cd451f3ca49ff65cf20feb923dcb7080f92ebc1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 May 2021 11:32:29 +0200 Subject: [PATCH 22/28] Implement `move_line_up` and `move_line_down` This does not restore folds yet. --- zed/src/editor/buffer/selection.rs | 11 +- zed/src/editor/buffer_view.rs | 176 +++++++++++++++++++++++-- zed/src/editor/display_map/fold_map.rs | 7 + zed/src/editor/display_map/mod.rs | 5 + 4 files changed, 188 insertions(+), 11 deletions(-) diff --git a/zed/src/editor/buffer/selection.rs b/zed/src/editor/buffer/selection.rs index 1d7459482c22a16407bf379ddff3017357c51566..0999298357758cb00d29f155eb24a98a8907a8b6 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -73,7 +73,11 @@ impl Selection { } } - pub fn buffer_rows_for_display_rows(&self, map: &DisplayMap, ctx: &AppContext) -> Range { + pub fn buffer_rows_for_display_rows( + &self, + map: &DisplayMap, + ctx: &AppContext, + ) -> (Range, Range) { let display_start = self.start.to_display_point(map, ctx).unwrap(); let buffer_start = DisplayPoint::new(display_start.row(), 0) .to_buffer_point(map, Bias::Left, ctx) @@ -93,6 +97,9 @@ impl Selection { .to_buffer_point(map, Bias::Left, ctx) .unwrap(); - buffer_start.row..buffer_end.row + 1 + ( + buffer_start.row..buffer_end.row + 1, + display_start.row()..display_end.row() + 1, + ) } } diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index b48c16c23b941a6cab44dca439ede1406e1e855c..6bd727dc4fc93532ac7511d0976d37800681c77c 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -46,6 +46,8 @@ pub fn init(app: &mut MutableAppContext) { Some("BufferView"), ), Binding::new("cmd-shift-D", "buffer:duplicate_line", Some("BufferView")), + Binding::new("ctrl-cmd-up", "buffer:move_line_up", Some("BufferView")), + Binding::new("ctrl-cmd-down", "buffer:move_line_down", Some("BufferView")), Binding::new("cmd-x", "buffer:cut", Some("BufferView")), Binding::new("cmd-c", "buffer:copy", Some("BufferView")), Binding::new("cmd-v", "buffer:paste", Some("BufferView")), @@ -133,6 +135,8 @@ pub fn init(app: &mut MutableAppContext) { BufferView::delete_to_end_of_line, ); app.add_action("buffer:duplicate_line", BufferView::duplicate_line); + app.add_action("buffer:move_line_up", BufferView::move_line_up); + app.add_action("buffer:move_line_down", BufferView::move_line_down); app.add_action("buffer:cut", BufferView::cut); app.add_action("buffer:copy", BufferView::copy); app.add_action("buffer:paste", BufferView::paste); @@ -639,7 +643,7 @@ 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(map, app); + let (mut rows, _) = selection.buffer_rows_for_display_rows(map, app); let goal_display_column = selection .head() .to_display_point(map, app) @@ -648,7 +652,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(map, app); + let (next_rows, _) = next_selection.buffer_rows_for_display_rows(map, app); if next_rows.start <= rows.end { rows.end = next_rows.end; selections.next().unwrap(); @@ -696,10 +700,10 @@ impl BufferView { goal_column: None, }) .collect(); - self.update_selections(new_selections, true, ctx); self.buffer .update(ctx, |buffer, ctx| buffer.edit(edit_ranges, "", Some(ctx))) .unwrap(); + self.update_selections(new_selections, true, ctx); self.end_transaction(ctx); } @@ -726,9 +730,9 @@ 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(map, app); + let (mut rows, _) = selection.buffer_rows_for_display_rows(map, app); while let Some(next_selection) = selections_iter.peek() { - let next_rows = next_selection.buffer_rows_for_display_rows(map, app); + let (next_rows, _) = next_selection.buffer_rows_for_display_rows(map, app); if next_rows.start <= rows.end - 1 { rows.end = next_rows.end; selections_iter.next().unwrap(); @@ -765,6 +769,156 @@ impl BufferView { self.end_transaction(ctx); } + pub fn move_line_up(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + + let app = ctx.as_ref(); + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(ctx); + + let mut edits = Vec::new(); + let selections = self.selections(ctx.as_ref()).to_vec(); + let mut selections_iter = selections.iter().peekable(); + while let Some(selection) = selections_iter.next() { + // Accumulate contiguous regions of rows that we want to move. + let (mut buffer_rows, mut display_rows) = + selection.buffer_rows_for_display_rows(map, app); + while let Some(next_selection) = selections_iter.peek() { + let (next_buffer_rows, next_display_rows) = + next_selection.buffer_rows_for_display_rows(map, app); + if next_buffer_rows.start <= buffer_rows.end { + buffer_rows.end = next_buffer_rows.end; + display_rows.end = next_display_rows.end; + selections_iter.next().unwrap(); + } else { + break; + } + } + + // Cut the text from the previous line and paste it at the end of the selected region. + if display_rows.start != 0 { + let selection_line_end = Point::new( + buffer_rows.end - 1, + buffer.line_len(buffer_rows.end - 1).unwrap(), + ) + .to_offset(buffer) + .unwrap(); + let prev_line_display_start = DisplayPoint::new(display_rows.start - 1, 0); + let prev_line_display_end = DisplayPoint::new( + prev_line_display_start.row(), + map.line_len(prev_line_display_start.row(), app).unwrap(), + ); + let prev_line_start = prev_line_display_start + .to_buffer_offset(map, Bias::Left, app) + .unwrap(); + let prev_line_end = prev_line_display_end + .to_buffer_offset(map, Bias::Right, app) + .unwrap(); + + let mut text = String::new(); + text.push('\n'); + text.extend( + buffer + .text_for_range(prev_line_start..prev_line_end) + .unwrap(), + ); + edits.push((prev_line_start..prev_line_end + 1, String::new())); + edits.push((selection_line_end..selection_line_end, text)); + } + } + + self.buffer.update(ctx, |buffer, ctx| { + for (range, text) in edits.into_iter().rev() { + buffer.edit(Some(range), text, Some(ctx)).unwrap(); + } + }); + self.update_selections(selections, true, ctx); + self.end_transaction(ctx); + } + + pub fn move_line_down(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + + let mut selections = self.selections(ctx.as_ref()).to_vec(); + { + // Temporarily bias selections right to allow moved lines to push them down when the + // selections are at the beginning of a line. + let buffer = self.buffer.read(ctx); + for selection in &mut selections { + selection.start = selection.start.bias_right(buffer).unwrap(); + selection.end = selection.end.bias_right(buffer).unwrap(); + } + } + self.update_selections(selections.clone(), false, ctx); + + let app = ctx.as_ref(); + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(ctx); + + let mut edits = Vec::new(); + let selections = self.selections(ctx.as_ref()).to_vec(); + let mut selections_iter = selections.iter().peekable(); + while let Some(selection) = selections_iter.next() { + // Accumulate contiguous regions of rows that we want to move. + let (mut buffer_rows, mut display_rows) = + selection.buffer_rows_for_display_rows(map, app); + while let Some(next_selection) = selections_iter.peek() { + let (next_buffer_rows, next_display_rows) = + next_selection.buffer_rows_for_display_rows(map, app); + if next_buffer_rows.start <= buffer_rows.end { + buffer_rows.end = next_buffer_rows.end; + display_rows.end = next_display_rows.end; + selections_iter.next().unwrap(); + } else { + break; + } + } + + // Cut the text from the following line and paste it at the start of the selected region. + if buffer_rows.end <= buffer.max_point().row { + let selection_line_start = + Point::new(buffer_rows.start, 0).to_offset(buffer).unwrap(); + let next_line_display_end = DisplayPoint::new( + display_rows.end, + map.line_len(display_rows.end, app).unwrap(), + ); + let next_line_start = Point::new(buffer_rows.end, 0).to_offset(buffer).unwrap(); + let next_line_end = next_line_display_end + .to_buffer_offset(map, Bias::Right, app) + .unwrap(); + + let mut text = String::new(); + text.extend( + buffer + .text_for_range(next_line_start..next_line_end) + .unwrap(), + ); + text.push('\n'); + edits.push((selection_line_start..selection_line_start, text)); + edits.push((next_line_start - 1..next_line_end, String::new())); + } + } + + self.buffer.update(ctx, |buffer, ctx| { + for (range, text) in edits.into_iter().rev() { + buffer.edit(Some(range), text, Some(ctx)).unwrap(); + } + }); + + let mut selections = self.selections(ctx.as_ref()).to_vec(); + { + // Restore bias on selections. + let buffer = self.buffer.read(ctx); + for selection in &mut selections { + selection.start = selection.start.bias_left(buffer).unwrap(); + selection.end = selection.end.bias_left(buffer).unwrap(); + } + } + self.update_selections(selections, true, ctx); + + self.end_transaction(ctx); + } + pub fn cut(&mut self, _: &(), ctx: &mut ViewContext) { self.start_transaction(ctx); let mut text = String::new(); @@ -1175,9 +1329,11 @@ impl BufferView { } pub fn move_to_beginning(&mut self, _: &(), ctx: &mut ViewContext) { + let buffer = self.buffer.read(ctx); + let cursor = buffer.anchor_before(Point::new(0, 0)).unwrap(); let selection = Selection { - start: Anchor::Start, - end: Anchor::Start, + start: cursor.clone(), + end: cursor, reversed: false, goal_column: None, }; @@ -1191,9 +1347,11 @@ impl BufferView { } pub fn move_to_end(&mut self, _: &(), ctx: &mut ViewContext) { + let buffer = self.buffer.read(ctx); + let cursor = buffer.anchor_before(buffer.max_point()).unwrap(); let selection = Selection { - start: Anchor::End, - end: Anchor::End, + start: cursor.clone(), + end: cursor, reversed: false, goal_column: None, }; diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 536971a87d13cffc33fc9e63a0d64ae3e2f02099..fd63a3e2ec478acb0b32c4bca19d6d4b83a93b95 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -169,6 +169,13 @@ impl FoldMap { false } + pub fn to_buffer_offset(&self, point: DisplayPoint, app: &AppContext) -> Result { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&point, SeekBias::Right); + let overshoot = point.0 - cursor.start().display.lines; + (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(app)) + } + pub fn to_display_offset( &self, point: DisplayPoint, diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index e8d2b6c6e5e199eb50111edb1d70cc43c29d7586..77a3defb124faf98aed9304b5b1b561bb9af92e8 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -179,6 +179,11 @@ impl DisplayPoint { .to_buffer_point(self.collapse_tabs(map, bias, app)?.0)) } + pub fn to_buffer_offset(self, map: &DisplayMap, bias: Bias, app: &AppContext) -> Result { + map.fold_map + .to_buffer_offset(self.collapse_tabs(map, bias, app)?.0, app) + } + fn expand_tabs(mut self, map: &DisplayMap, app: &AppContext) -> Result { let chars = map .fold_map From 76b454d34b69e80218fbacf923213b16ee08b29e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 May 2021 14:49:35 +0200 Subject: [PATCH 23/28] Push selections down correctly when moving a line down --- zed/src/editor/buffer/selection.rs | 2 +- zed/src/editor/buffer_view.rs | 93 +++++++++++++++++++----------- 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/zed/src/editor/buffer/selection.rs b/zed/src/editor/buffer/selection.rs index 0999298357758cb00d29f155eb24a98a8907a8b6..431f8224c425d6e568da07718af36827520f7e64 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -84,7 +84,7 @@ impl Selection { .unwrap(); let mut display_end = self.end.to_display_point(map, ctx).unwrap(); - if display_end != map.max_point(ctx) + if 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 6bd727dc4fc93532ac7511d0976d37800681c77c..d1086f1790406c71cd0239107f013a346749226d 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -761,8 +761,8 @@ impl BufferView { // Restore bias on selections. let buffer = self.buffer.read(ctx); for selection in &mut selections { - selection.start = selection.start.bias_right(buffer).unwrap(); - selection.end = selection.end.bias_right(buffer).unwrap(); + selection.start = selection.start.bias_left(buffer).unwrap(); + selection.end = selection.end.bias_left(buffer).unwrap(); } self.update_selections(selections, true, ctx); @@ -777,19 +777,22 @@ impl BufferView { let map = self.display_map.read(ctx); let mut edits = Vec::new(); - let selections = self.selections(ctx.as_ref()).to_vec(); - let mut selections_iter = selections.iter().peekable(); - while let Some(selection) = selections_iter.next() { + let mut new_selection_ranges = Vec::new(); + let mut selections = self.selections(app).iter().peekable(); + let mut contiguous_selections = Vec::new(); + while let Some(selection) = selections.next() { // 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(map, app); - while let Some(next_selection) = selections_iter.peek() { + while let Some(next_selection) = selections.peek() { let (next_buffer_rows, next_display_rows) = next_selection.buffer_rows_for_display_rows(map, app); if next_buffer_rows.start <= buffer_rows.end { buffer_rows.end = next_buffer_rows.end; display_rows.end = next_display_rows.end; - selections_iter.next().unwrap(); + contiguous_selections.push(next_selection.range(buffer)); + selections.next().unwrap(); } else { break; } @@ -824,7 +827,14 @@ impl BufferView { ); edits.push((prev_line_start..prev_line_end + 1, String::new())); edits.push((selection_line_end..selection_line_end, text)); + + // Move selections to the previous line. + for range in &mut contiguous_selections { + range.start.row -= 1; + range.end.row -= 1; + } } + new_selection_ranges.extend(contiguous_selections.drain(..)); } self.buffer.update(ctx, |buffer, ctx| { @@ -832,43 +842,48 @@ impl BufferView { buffer.edit(Some(range), text, Some(ctx)).unwrap(); } }); - self.update_selections(selections, true, ctx); + + let buffer = self.buffer.read(ctx); + let mut new_selections = Vec::new(); + for range in new_selection_ranges { + let start = cmp::min(range.start, range.end); + let end = cmp::max(range.start, range.end); + new_selections.push(Selection { + start: buffer.anchor_before(start).unwrap(), + end: buffer.anchor_before(end).unwrap(), + reversed: range.start > range.end, + goal_column: None, + }); + } + self.update_selections(new_selections, true, ctx); + self.end_transaction(ctx); } pub fn move_line_down(&mut self, _: &(), ctx: &mut ViewContext) { self.start_transaction(ctx); - let mut selections = self.selections(ctx.as_ref()).to_vec(); - { - // Temporarily bias selections right to allow moved lines to push them down when the - // selections are at the beginning of a line. - let buffer = self.buffer.read(ctx); - for selection in &mut selections { - selection.start = selection.start.bias_right(buffer).unwrap(); - selection.end = selection.end.bias_right(buffer).unwrap(); - } - } - self.update_selections(selections.clone(), false, ctx); - let app = ctx.as_ref(); let buffer = self.buffer.read(ctx); let map = self.display_map.read(ctx); let mut edits = Vec::new(); - let selections = self.selections(ctx.as_ref()).to_vec(); - let mut selections_iter = selections.iter().peekable(); - while let Some(selection) = selections_iter.next() { + let mut new_selection_ranges = Vec::new(); + let mut selections = self.selections(app).iter().peekable(); + let mut contiguous_selections = Vec::new(); + while let Some(selection) = selections.next() { // 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(map, app); - while let Some(next_selection) = selections_iter.peek() { + while let Some(next_selection) = selections.peek() { let (next_buffer_rows, next_display_rows) = next_selection.buffer_rows_for_display_rows(map, app); if next_buffer_rows.start <= buffer_rows.end { buffer_rows.end = next_buffer_rows.end; display_rows.end = next_display_rows.end; - selections_iter.next().unwrap(); + contiguous_selections.push(next_selection.range(buffer)); + selections.next().unwrap(); } else { break; } @@ -896,7 +911,14 @@ impl BufferView { text.push('\n'); edits.push((selection_line_start..selection_line_start, text)); edits.push((next_line_start - 1..next_line_end, String::new())); + + // Move selections to the next line. + for range in &mut contiguous_selections { + range.start.row += 1; + range.end.row += 1; + } } + new_selection_ranges.extend(contiguous_selections.drain(..)); } self.buffer.update(ctx, |buffer, ctx| { @@ -905,16 +927,19 @@ impl BufferView { } }); - let mut selections = self.selections(ctx.as_ref()).to_vec(); - { - // Restore bias on selections. - let buffer = self.buffer.read(ctx); - for selection in &mut selections { - selection.start = selection.start.bias_left(buffer).unwrap(); - selection.end = selection.end.bias_left(buffer).unwrap(); - } + let buffer = self.buffer.read(ctx); + let mut new_selections = Vec::new(); + for range in new_selection_ranges { + let start = cmp::min(range.start, range.end); + let end = cmp::max(range.start, range.end); + new_selections.push(Selection { + start: buffer.anchor_before(start).unwrap(), + end: buffer.anchor_before(end).unwrap(), + reversed: range.start > range.end, + goal_column: None, + }); } - self.update_selections(selections, true, ctx); + self.update_selections(new_selections, true, ctx); self.end_transaction(ctx); } From 49dc0407863f4a6324d6af04bcff9654ed0f8450 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 May 2021 15:10:20 +0200 Subject: [PATCH 24/28] Use a more direct manipulation for moving lines up or down --- zed/src/editor/buffer_view.rs | 54 ++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index d1086f1790406c71cd0239107f013a346749226d..f971377af14312ecfa86c0c8506644d3f4861df9 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -798,35 +798,31 @@ impl BufferView { } } - // Cut the text from the previous line and paste it at the end of the selected region. + // Cut the text from the selected rows and paste it at the start of the previous line. if display_rows.start != 0 { - let selection_line_end = Point::new( + let selection_row_start = + Point::new(buffer_rows.start, 0).to_offset(buffer).unwrap(); + let selection_row_end = Point::new( buffer_rows.end - 1, buffer.line_len(buffer_rows.end - 1).unwrap(), ) .to_offset(buffer) .unwrap(); - let prev_line_display_start = DisplayPoint::new(display_rows.start - 1, 0); - let prev_line_display_end = DisplayPoint::new( - prev_line_display_start.row(), - map.line_len(prev_line_display_start.row(), app).unwrap(), - ); - let prev_line_start = prev_line_display_start + + let prev_row_display_start = DisplayPoint::new(display_rows.start - 1, 0); + let prev_row_start = prev_row_display_start .to_buffer_offset(map, Bias::Left, app) .unwrap(); - let prev_line_end = prev_line_display_end - .to_buffer_offset(map, Bias::Right, app) - .unwrap(); let mut text = String::new(); - text.push('\n'); text.extend( buffer - .text_for_range(prev_line_start..prev_line_end) + .text_for_range(selection_row_start..selection_row_end) .unwrap(), ); - edits.push((prev_line_start..prev_line_end + 1, String::new())); - edits.push((selection_line_end..selection_line_end, text)); + text.push('\n'); + edits.push((prev_row_start..prev_row_start, text)); + edits.push((selection_row_start - 1..selection_row_end, String::new())); // Move selections to the previous line. for range in &mut contiguous_selections { @@ -889,28 +885,34 @@ impl BufferView { } } - // Cut the text from the following line and paste it at the start of the selected region. - if buffer_rows.end <= buffer.max_point().row { - let selection_line_start = + // Cut the text from the selected rows and paste it at the end of the next line. + if display_rows.end <= map.max_point(app).row() { + let selection_row_start = Point::new(buffer_rows.start, 0).to_offset(buffer).unwrap(); - let next_line_display_end = DisplayPoint::new( + let selection_row_end = Point::new( + buffer_rows.end - 1, + buffer.line_len(buffer_rows.end - 1).unwrap(), + ) + .to_offset(buffer) + .unwrap(); + + let next_row_display_end = DisplayPoint::new( display_rows.end, map.line_len(display_rows.end, app).unwrap(), ); - let next_line_start = Point::new(buffer_rows.end, 0).to_offset(buffer).unwrap(); - let next_line_end = next_line_display_end - .to_buffer_offset(map, Bias::Right, app) + let next_row_end = next_row_display_end + .to_buffer_offset(map, Bias::Left, app) .unwrap(); let mut text = String::new(); + text.push('\n'); text.extend( buffer - .text_for_range(next_line_start..next_line_end) + .text_for_range(selection_row_start..selection_row_end) .unwrap(), ); - text.push('\n'); - edits.push((selection_line_start..selection_line_start, text)); - edits.push((next_line_start - 1..next_line_end, String::new())); + edits.push((selection_row_start..selection_row_end + 1, String::new())); + edits.push((next_row_end..next_row_end, text)); // Move selections to the next line. for range in &mut contiguous_selections { From 4c4657515e89fcbf20c814ca99fa55caea832ba4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 May 2021 16:13:09 +0200 Subject: [PATCH 25/28] Account for folds when moving selections up/down --- zed/src/editor/buffer_view.rs | 87 ++++++++++++++++------------------- 1 file changed, 39 insertions(+), 48 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index f971377af14312ecfa86c0c8506644d3f4861df9..c37ae4818f53def10ff184c35f92d813d62224ed 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -19,6 +19,7 @@ use std::{ cmp::{self, Ordering}, fmt::Write, iter::FromIterator, + mem, ops::Range, path::Path, sync::Arc, @@ -468,23 +469,30 @@ impl BufferView { self.pending_selection.is_some() } - #[cfg(test)] - fn select_ranges<'a, T>(&mut self, ranges: T, ctx: &mut ViewContext) -> Result<()> + fn select_ranges(&mut self, ranges: I, autoscroll: bool, ctx: &mut ViewContext) where - T: IntoIterator>, + I: IntoIterator>, + T: ToOffset, { let buffer = self.buffer.read(ctx); let mut selections = Vec::new(); for range in ranges { + let mut start = range.start.to_offset(buffer).unwrap(); + let mut end = range.end.to_offset(buffer).unwrap(); + let reversed = if start > end { + mem::swap(&mut start, &mut end); + true + } else { + false + }; selections.push(Selection { - start: buffer.anchor_before(range.start)?, - end: buffer.anchor_before(range.end)?, - reversed: false, + start: buffer.anchor_before(start).unwrap(), + end: buffer.anchor_before(end).unwrap(), + reversed, goal_column: None, }); } - self.update_selections(selections, false, ctx); - Ok(()) + self.update_selections(selections, autoscroll, ctx); } #[cfg(test)] @@ -492,8 +500,6 @@ impl BufferView { where T: IntoIterator>, { - use std::mem; - let map = self.display_map.read(ctx); let mut selections = Vec::new(); for range in ranges { @@ -824,10 +830,15 @@ impl BufferView { edits.push((prev_row_start..prev_row_start, text)); edits.push((selection_row_start - 1..selection_row_end, String::new())); - // Move selections to the previous line. + // Move selections up. + let row_delta = buffer_rows.start + - prev_row_display_start + .to_buffer_point(map, Bias::Left, app) + .unwrap() + .row; for range in &mut contiguous_selections { - range.start.row -= 1; - range.end.row -= 1; + range.start.row -= row_delta; + range.end.row -= row_delta; } } new_selection_ranges.extend(contiguous_selections.drain(..)); @@ -838,20 +849,7 @@ impl BufferView { buffer.edit(Some(range), text, Some(ctx)).unwrap(); } }); - - let buffer = self.buffer.read(ctx); - let mut new_selections = Vec::new(); - for range in new_selection_ranges { - let start = cmp::min(range.start, range.end); - let end = cmp::max(range.start, range.end); - new_selections.push(Selection { - start: buffer.anchor_before(start).unwrap(), - end: buffer.anchor_before(end).unwrap(), - reversed: range.start > range.end, - goal_column: None, - }); - } - self.update_selections(new_selections, true, ctx); + self.select_ranges(new_selection_ranges, true, ctx); self.end_transaction(ctx); } @@ -914,10 +912,16 @@ impl BufferView { edits.push((selection_row_start..selection_row_end + 1, String::new())); edits.push((next_row_end..next_row_end, text)); - // Move selections to the next line. + // Move selections down. + let row_delta = next_row_display_end + .to_buffer_point(map, Bias::Right, app) + .unwrap() + .row + - buffer_rows.end + + 1; for range in &mut contiguous_selections { - range.start.row += 1; - range.end.row += 1; + range.start.row += row_delta; + range.end.row += row_delta; } } new_selection_ranges.extend(contiguous_selections.drain(..)); @@ -928,20 +932,7 @@ impl BufferView { buffer.edit(Some(range), text, Some(ctx)).unwrap(); } }); - - let buffer = self.buffer.read(ctx); - let mut new_selections = Vec::new(); - for range in new_selection_ranges { - let start = cmp::min(range.start, range.end); - let end = cmp::max(range.start, range.end); - new_selections.push(Selection { - start: buffer.anchor_before(start).unwrap(), - end: buffer.anchor_before(end).unwrap(), - reversed: range.start > range.end, - goal_column: None, - }); - } - self.update_selections(new_selections, true, ctx); + self.select_ranges(new_selection_ranges, true, ctx); self.end_transaction(ctx); } @@ -2597,14 +2588,14 @@ mod tests { // Cut with three selections. Clipboard text is divided into three slices. view.update(app, |view, ctx| { - view.select_ranges(&[0..4, 8..14, 19..24], ctx).unwrap(); + view.select_ranges(vec![0..4, 8..14, 19..24], false, ctx); view.cut(&(), ctx); }); assert_eq!(view.read(app).text(app.as_ref()), "two four six "); // Paste with three cursors. Each cursor pastes one slice of the clipboard text. view.update(app, |view, ctx| { - view.select_ranges(&[4..4, 9..9, 13..13], ctx).unwrap(); + view.select_ranges(vec![4..4, 9..9, 13..13], false, ctx); view.paste(&(), ctx); }); assert_eq!( @@ -2624,7 +2615,7 @@ mod tests { // match the number of slices in the clipboard, the entire clipboard text // is pasted at each cursor. view.update(app, |view, ctx| { - view.select_ranges(&[0..0, 28..28], ctx).unwrap(); + view.select_ranges(vec![0..0, 28..28], false, ctx); view.insert(&"( ".to_string(), ctx); view.paste(&(), ctx); view.insert(&") ".to_string(), ctx); @@ -2635,7 +2626,7 @@ mod tests { ); view.update(app, |view, ctx| { - view.select_ranges(&[0..0], ctx).unwrap(); + view.select_ranges(vec![0..0], false, ctx); view.insert(&"123\n4567\n89\n".to_string(), ctx); }); assert_eq!( From 4b38f2850d75db9703067c64355c4efc64a159a7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 May 2021 18:58:39 +0200 Subject: [PATCH 26/28] WIP --- zed/src/editor/buffer_view.rs | 75 ++++++++++--- zed/src/editor/display_map/fold_map.rs | 142 ++++++++++++++++--------- zed/src/editor/display_map/mod.rs | 7 ++ 3 files changed, 162 insertions(+), 62 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index c37ae4818f53def10ff184c35f92d813d62224ed..b13af5a70ebccccfef5d338b41e4b9a8e55dab2d 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -784,6 +784,9 @@ impl BufferView { let mut edits = Vec::new(); let mut new_selection_ranges = Vec::new(); + let mut old_folds = Vec::new(); + let mut new_folds = Vec::new(); + let mut selections = self.selections(app).iter().peekable(); let mut contiguous_selections = Vec::new(); while let Some(selection) = selections.next() { @@ -830,25 +833,42 @@ impl BufferView { edits.push((prev_row_start..prev_row_start, text)); edits.push((selection_row_start - 1..selection_row_end, String::new())); - // Move selections up. let row_delta = buffer_rows.start - prev_row_display_start .to_buffer_point(map, Bias::Left, app) .unwrap() .row; + + // Move selections up. for range in &mut contiguous_selections { range.start.row -= row_delta; range.end.row -= row_delta; } + + // Move folds up. + old_folds.push(selection_row_start..selection_row_end); + for fold in map + .folds_in_range(selection_row_start..selection_row_end, app) + .unwrap() + { + let mut start = fold.start.to_point(buffer).unwrap(); + let mut end = fold.end.to_point(buffer).unwrap(); + start.row -= row_delta; + end.row -= row_delta; + new_folds.push(start..end); + } } + new_selection_ranges.extend(contiguous_selections.drain(..)); } + self.unfold_ranges(old_folds, ctx); self.buffer.update(ctx, |buffer, ctx| { for (range, text) in edits.into_iter().rev() { buffer.edit(Some(range), text, Some(ctx)).unwrap(); } }); + self.fold_ranges(new_folds, ctx); self.select_ranges(new_selection_ranges, true, ctx); self.end_transaction(ctx); @@ -863,6 +883,9 @@ impl BufferView { let mut edits = Vec::new(); let mut new_selection_ranges = Vec::new(); + let mut old_folds = Vec::new(); + let mut new_folds = Vec::new(); + let mut selections = self.selections(app).iter().peekable(); let mut contiguous_selections = Vec::new(); while let Some(selection) = selections.next() { @@ -912,26 +935,43 @@ impl BufferView { edits.push((selection_row_start..selection_row_end + 1, String::new())); edits.push((next_row_end..next_row_end, text)); - // Move selections down. let row_delta = next_row_display_end .to_buffer_point(map, Bias::Right, app) .unwrap() .row - buffer_rows.end + 1; + + // Move selections down. for range in &mut contiguous_selections { range.start.row += row_delta; range.end.row += row_delta; } + + // Move folds down. + old_folds.push(selection_row_start..selection_row_end); + for fold in map + .folds_in_range(selection_row_start..selection_row_end, app) + .unwrap() + { + let mut start = fold.start.to_point(buffer).unwrap(); + let mut end = fold.end.to_point(buffer).unwrap(); + start.row += row_delta; + end.row += row_delta; + new_folds.push(start..end); + } } + new_selection_ranges.extend(contiguous_selections.drain(..)); } + self.unfold_ranges(old_folds, ctx); self.buffer.update(ctx, |buffer, ctx| { for (range, text) in edits.into_iter().rev() { buffer.edit(Some(range), text, Some(ctx)).unwrap(); } }); + self.fold_ranges(new_folds, ctx); self.select_ranges(new_selection_ranges, true, ctx); self.end_transaction(ctx); @@ -1533,12 +1573,7 @@ impl BufferView { } } - if !fold_ranges.is_empty() { - self.display_map.update(ctx, |map, ctx| { - map.fold(fold_ranges, ctx).unwrap(); - }); - *self.autoscroll_requested.lock() = true; - } + self.fold_ranges(fold_ranges, ctx); } pub fn unfold(&mut self, _: &(), ctx: &mut ViewContext) { @@ -1559,11 +1594,7 @@ impl BufferView { start..end }) .collect::>(); - - self.display_map.update(ctx, |map, ctx| { - map.unfold(ranges, ctx).unwrap(); - }); - *self.autoscroll_requested.lock() = true; + self.unfold_ranges(ranges, ctx); } fn is_line_foldable(&self, display_row: u32, app: &AppContext) -> bool { @@ -1621,6 +1652,24 @@ impl BufferView { }); } + fn fold_ranges(&mut self, ranges: Vec>, ctx: &mut ViewContext) { + if !ranges.is_empty() { + self.display_map.update(ctx, |map, ctx| { + map.fold(ranges, ctx).unwrap(); + }); + *self.autoscroll_requested.lock() = true; + } + } + + fn unfold_ranges(&mut self, ranges: Vec>, ctx: &mut ViewContext) { + if !ranges.is_empty() { + self.display_map.update(ctx, |map, ctx| { + map.unfold(ranges, ctx).unwrap(); + }); + *self.autoscroll_requested.lock() = true; + } + } + pub fn line(&self, display_row: u32, app: &AppContext) -> Result { self.display_map.read(app).line(display_row, app) } diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index fd63a3e2ec478acb0b32c4bca19d6d4b83a93b95..0e3626578be8edbf473e6b391549c0796f377e22 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -89,6 +89,33 @@ impl FoldMap { DisplayPoint(self.transforms.summary().display.rightmost_point) } + pub fn folds_in_range(&self, range: Range, app: &AppContext) -> Result<&[Range]> + where + T: ToOffset, + { + let buffer = self.buffer.read(app); + let range = buffer.anchor_before(range.start)?..buffer.anchor_before(range.end)?; + let mut start_ix = find_insertion_index(&self.folds, |probe| probe.cmp(&range, buffer))?; + let mut end_ix = start_ix; + + for fold in self.folds[..start_ix].iter().rev() { + if fold.end.cmp(&range.start, buffer)? == Ordering::Greater { + start_ix -= 1; + } else { + break; + } + } + for fold in &self.folds[end_ix..] { + if range.end.cmp(&fold.start, buffer)? == Ordering::Greater { + end_ix += 1; + } else { + break; + } + } + + Ok(&self.folds[start_ix..end_ix]) + } + pub fn fold( &mut self, ranges: impl IntoIterator>, @@ -422,7 +449,7 @@ impl<'a> Iterator for Chars<'a> { return Some(c); } - if self.offset == self.cursor.end().display.chars { + while self.offset == self.cursor.end().display.chars && self.cursor.item().is_some() { self.cursor.next(); } @@ -584,6 +611,9 @@ mod tests { let iterations = env::var("ITERATIONS") .map(|i| i.parse().expect("invalid `ITERATIONS` variable")) .unwrap_or(100); + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); let seed_range = if let Ok(seed) = env::var("SEED") { let seed = seed.parse().expect("invalid `SEED` variable"); seed..seed + 1 @@ -591,6 +621,8 @@ mod tests { 0..iterations }; + let operations = 2; + let seed_range = 133..=133; for seed in seed_range { println!("{:?}", seed); let mut rng = StdRng::seed_from_u64(seed); @@ -603,66 +635,78 @@ mod tests { }); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); - { - let buffer = buffer.read(app); - - let fold_count = rng.gen_range(0..10); - let mut fold_ranges: Vec> = Vec::new(); - for _ in 0..fold_count { - let end = rng.gen_range(0..buffer.len() + 1); - let start = rng.gen_range(0..end + 1); - fold_ranges.push(start..end); + for op_ix in 0..operations { + dbg!(op_ix); + + log::info!("Text: {:?}", buffer.read(app).text()); + { + let buffer = buffer.read(app); + + let fold_count = rng.gen_range(0..=2); + let mut fold_ranges: Vec> = Vec::new(); + for _ in 0..fold_count { + let end = rng.gen_range(0..buffer.len() + 1); + let start = rng.gen_range(0..end + 1); + fold_ranges.push(start..end); + } + log::info!("Folding {:?}", fold_ranges); + if op_ix == 1 { + dbg!("stopping"); + } + map.fold(fold_ranges.clone(), app.as_ref()).unwrap(); + assert_eq!(map.transforms.summary().buffer.chars, buffer.len()); + + let mut expected_text = buffer.text(); + for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() { + expected_text.replace_range(fold_range.start..fold_range.end, "…"); + } + assert_eq!(map.text(app.as_ref()), expected_text); + + for fold_range in map.merged_fold_ranges(app.as_ref()) { + let display_point = + map.to_display_point(fold_range.start.to_point(buffer).unwrap()); + assert!(map.is_line_folded(display_point.row())); + } } - map.fold(fold_ranges, app.as_ref()).unwrap(); + let edits = buffer.update(app, |buffer, ctx| { + let start_version = buffer.version.clone(); + let edit_count = rng.gen_range(0..=2); + buffer.randomly_edit(&mut rng, edit_count, Some(ctx)); + buffer.edits_since(start_version).collect::>() + }); + log::info!("Editing {:?}", edits); + map.apply_edits(&edits, app.as_ref()).unwrap(); + assert_eq!( + map.transforms.summary().buffer.chars, + buffer.read(app).len() + ); + let buffer = map.buffer.read(app); let mut expected_text = buffer.text(); + let mut expected_buffer_rows = Vec::new(); + let mut next_row = buffer.max_point().row; for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() { + let fold_start = buffer.point_for_offset(fold_range.start).unwrap(); + let fold_end = buffer.point_for_offset(fold_range.end).unwrap(); + expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev()); + next_row = fold_start.row; + expected_text.replace_range(fold_range.start..fold_range.end, "…"); } + expected_buffer_rows.extend((0..=next_row).rev()); + expected_buffer_rows.reverse(); assert_eq!(map.text(app.as_ref()), expected_text); - for fold_range in map.merged_fold_ranges(app.as_ref()) { - let display_point = - map.to_display_point(fold_range.start.to_point(buffer).unwrap()); - assert!(map.is_line_folded(display_point.row())); + for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() { + let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row(); + assert_eq!( + map.buffer_rows(display_row).unwrap().collect::>(), + expected_buffer_rows[idx..], + ); } } - - let edits = buffer.update(app, |buffer, ctx| { - let start_version = buffer.version.clone(); - let edit_count = rng.gen_range(1..10); - buffer.randomly_edit(&mut rng, edit_count, Some(ctx)); - buffer.edits_since(start_version).collect::>() - }); - - map.apply_edits(&edits, app.as_ref()).unwrap(); - - let buffer = map.buffer.read(app); - let mut expected_text = buffer.text(); - let mut expected_buffer_rows = Vec::new(); - let mut next_row = buffer.max_point().row; - for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() { - let fold_start = buffer.point_for_offset(fold_range.start).unwrap(); - let fold_end = buffer.point_for_offset(fold_range.end).unwrap(); - expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev()); - next_row = fold_start.row; - - expected_text.replace_range(fold_range.start..fold_range.end, "…"); - } - expected_buffer_rows.extend((0..=next_row).rev()); - expected_buffer_rows.reverse(); - - assert_eq!(map.text(app.as_ref()), expected_text); - - for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() { - let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row(); - assert_eq!( - map.buffer_rows(display_row).unwrap().collect::>(), - expected_buffer_rows[idx..], - ); - } }); } } diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 77a3defb124faf98aed9304b5b1b561bb9af92e8..b69a70f3979870eb061f72c0cfdff25931165ccf 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -34,6 +34,13 @@ impl DisplayMap { } } + pub fn folds_in_range(&self, range: Range, app: &AppContext) -> Result<&[Range]> + where + T: ToOffset, + { + self.fold_map.folds_in_range(range, app) + } + pub fn fold( &mut self, ranges: impl IntoIterator>, From 189ee7c140688cc4168ee8cb9848acbfccc7d9e4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 3 May 2021 12:39:47 -0700 Subject: [PATCH 27/28] Add failing unit test for adjacent folds --- zed/src/editor/display_map/fold_map.rs | 64 ++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 0e3626578be8edbf473e6b391549c0796f377e22..e81d2ee84a4dc0bf7cc8512d7d27b5607f4ed3e5 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -553,6 +553,50 @@ mod tests { }); } + #[test] + fn test_adjacent_folds() { + App::test((), |app| { + let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx)); + + { + let mut map = FoldMap::new(buffer.clone(), app.as_ref()); + + map.fold(vec![5..8], app.as_ref()).unwrap(); + map.check_invariants(app.as_ref()); + assert_eq!(map.text(app.as_ref()), "abcde…ijkl"); + + // Create an fold adjacent to the start of the first fold. + map.fold(vec![1..1, 2..5], app.as_ref()).unwrap(); + map.check_invariants(app.as_ref()); + assert_eq!(map.text(app.as_ref()), "ab…ijkl"); + + // Create an fold adjacent to the end of the first fold. + map.fold(vec![11..11, 8..10], app.as_ref()).unwrap(); + map.check_invariants(app.as_ref()); + assert_eq!(map.text(app.as_ref()), "ab…kl"); + } + + { + let mut map = FoldMap::new(buffer.clone(), app.as_ref()); + + // Create two adjacent folds. + map.fold(vec![0..2, 2..5], app.as_ref()).unwrap(); + map.check_invariants(app.as_ref()); + assert_eq!(map.text(app.as_ref()), "…fghijkl"); + + // Edit within one of the folds. + let edits = buffer.update(app, |buffer, ctx| { + let version = buffer.version(); + buffer.edit(vec![0..1], "12345", Some(ctx)).unwrap(); + buffer.edits_since(version).collect::>() + }); + map.apply_edits(edits.as_slice(), app.as_ref()).unwrap(); + map.check_invariants(app.as_ref()); + assert_eq!(map.text(app.as_ref()), "12345…fghijkl"); + } + }); + } + #[test] fn test_overlapping_folds() { App::test((), |app| { @@ -621,8 +665,6 @@ mod tests { 0..iterations }; - let operations = 2; - let seed_range = 133..=133; for seed in seed_range { println!("{:?}", seed); let mut rng = StdRng::seed_from_u64(seed); @@ -650,11 +692,8 @@ mod tests { fold_ranges.push(start..end); } log::info!("Folding {:?}", fold_ranges); - if op_ix == 1 { - dbg!("stopping"); - } map.fold(fold_ranges.clone(), app.as_ref()).unwrap(); - assert_eq!(map.transforms.summary().buffer.chars, buffer.len()); + map.check_invariants(app.as_ref()); let mut expected_text = buffer.text(); for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() { @@ -677,10 +716,7 @@ mod tests { }); log::info!("Editing {:?}", edits); map.apply_edits(&edits, app.as_ref()).unwrap(); - assert_eq!( - map.transforms.summary().buffer.chars, - buffer.read(app).len() - ); + map.check_invariants(app.as_ref()); let buffer = map.buffer.read(app); let mut expected_text = buffer.text(); @@ -772,5 +808,13 @@ mod tests { } merged_ranges } + + fn check_invariants(&self, app: &AppContext) { + assert_eq!( + self.transforms.summary().buffer.chars, + self.buffer.read(app).len(), + "transform tree does not match buffer's length" + ); + } } } From 837f34a498ccd81a5a5cc4abad7dbfbd7cbabd97 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 3 May 2021 14:30:50 -0600 Subject: [PATCH 28/28] Fix cases where new folds that end where existing folds start Co-Authored-By: Max Brunsfeld --- zed/src/editor/display_map/fold_map.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index e81d2ee84a4dc0bf7cc8512d7d27b5607f4ed3e5..98c47ff08a84ea1a79ded4a0edf03da7ad0a681a 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -269,7 +269,7 @@ impl FoldMap { let next_edit = edits.next().unwrap(); delta += next_edit.delta(); - if next_edit.old_range.end > edit.old_range.end { + if next_edit.old_range.end >= edit.old_range.end { edit.old_range.end = next_edit.old_range.end; cursor.seek(&edit.old_range.end, SeekBias::Right); cursor.next(); @@ -566,14 +566,14 @@ mod tests { assert_eq!(map.text(app.as_ref()), "abcde…ijkl"); // Create an fold adjacent to the start of the first fold. - map.fold(vec![1..1, 2..5], app.as_ref()).unwrap(); + map.fold(vec![0..1, 2..5], app.as_ref()).unwrap(); map.check_invariants(app.as_ref()); - assert_eq!(map.text(app.as_ref()), "ab…ijkl"); + assert_eq!(map.text(app.as_ref()), "…b…ijkl"); // Create an fold adjacent to the end of the first fold. map.fold(vec![11..11, 8..10], app.as_ref()).unwrap(); map.check_invariants(app.as_ref()); - assert_eq!(map.text(app.as_ref()), "ab…kl"); + assert_eq!(map.text(app.as_ref()), "…b…kl"); } { @@ -677,10 +677,8 @@ mod tests { }); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); - for op_ix in 0..operations { - dbg!(op_ix); - - log::info!("Text: {:?}", buffer.read(app).text()); + for _ in 0..operations { + log::info!("text: {:?}", buffer.read(app).text()); { let buffer = buffer.read(app); @@ -691,7 +689,7 @@ mod tests { let start = rng.gen_range(0..end + 1); fold_ranges.push(start..end); } - log::info!("Folding {:?}", fold_ranges); + log::info!("folding {:?}", fold_ranges); map.fold(fold_ranges.clone(), app.as_ref()).unwrap(); map.check_invariants(app.as_ref()); @@ -714,7 +712,7 @@ mod tests { buffer.randomly_edit(&mut rng, edit_count, Some(ctx)); buffer.edits_since(start_version).collect::>() }); - log::info!("Editing {:?}", edits); + log::info!("editing {:?}", edits); map.apply_edits(&edits, app.as_ref()).unwrap(); map.check_invariants(app.as_ref());