diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index e4a06bfda95f90b5dcf3eb4f6d0a940f4e7886f3..64aa91dfdc84ba2031eee84836ff1534707e32b4 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -2378,8 +2378,11 @@ mod tests { use super::*; use cmp::Ordering; use gpui::App; - use std::collections::BTreeMap; use std::{cell::RefCell, rc::Rc}; + use std::{ + collections::BTreeMap, + sync::atomic::{self, AtomicUsize}, + }; #[test] fn test_edit() { @@ -3275,6 +3278,8 @@ mod tests { where I: IntoIterator>, { + static NEXT_SELECTION_ID: AtomicUsize = AtomicUsize::new(0); + let mut ranges = ranges.into_iter().collect::>(); ranges.sort_unstable_by_key(|range| range.start); @@ -3282,17 +3287,19 @@ mod tests { for range in ranges { if range.start > range.end { selections.push(Selection { + id: NEXT_SELECTION_ID.fetch_add(1, atomic::Ordering::SeqCst), start: self.anchor_before(range.end)?, end: self.anchor_before(range.start)?, reversed: true, - goal_column: None, + goal: SelectionGoal::None, }); } else { selections.push(Selection { + id: NEXT_SELECTION_ID.fetch_add(1, atomic::Ordering::SeqCst), start: self.anchor_after(range.start)?, end: self.anchor_before(range.end)?, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }); } } diff --git a/zed/src/editor/buffer/selection.rs b/zed/src/editor/buffer/selection.rs index 8f436a04e7a4929c6a542c10e5df6d8086e0d10c..bff91a83e922a712889a62dbf5a6c80184be821c 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -12,12 +12,20 @@ use std::{cmp::Ordering, mem, ops::Range}; pub type SelectionSetId = time::Lamport; pub type SelectionsVersion = usize; +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum SelectionGoal { + None, + Column(u32), + ColumnRange { start: u32, end: u32 }, +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct Selection { + pub id: usize, pub start: Anchor, pub end: Anchor, pub reversed: bool, - pub goal_column: Option, + pub goal: SelectionGoal, } impl Selection { diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index e54e0fa75ff2abd952e2992d6d3a91b375a4c1f2..f179d56d537d0af48b5eb3daa3f1b9fd924854d9 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1,8 +1,8 @@ use super::{ buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point, - Selection, SelectionSetId, ToOffset, ToPoint, + Selection, SelectionGoal, SelectionSetId, ToOffset, ToPoint, }; -use crate::{settings::Settings, workspace, worktree::FileHandle}; +use crate::{settings::Settings, util::post_inc, workspace, worktree::FileHandle}; use anyhow::Result; use futures_core::future::LocalBoxFuture; use gpui::{ @@ -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("escape", "buffer:cancel", Some("BufferView")), Binding::new("backspace", "buffer:backspace", Some("BufferView")), Binding::new("ctrl-h", "buffer:backspace", Some("BufferView")), Binding::new("delete", "buffer:delete", Some("BufferView")), @@ -149,6 +150,16 @@ pub fn init(app: &mut MutableAppContext) { "buffer:split_selection_into_lines", Some("BufferView"), ), + Binding::new( + "cmd-alt-up", + "buffer:add_selection_above", + Some("BufferView"), + ), + Binding::new( + "cmd-alt-down", + "buffer:add_selection_below", + 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")), @@ -162,6 +173,7 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("buffer:scroll", BufferView::scroll); app.add_action("buffer:select", BufferView::select); + app.add_action("buffer:cancel", BufferView::cancel); app.add_action("buffer:insert", BufferView::insert); app.add_action("buffer:newline", BufferView::newline); app.add_action("buffer:backspace", BufferView::backspace); @@ -244,6 +256,14 @@ pub fn init(app: &mut MutableAppContext) { "buffer:split_selection_into_lines", BufferView::split_selection_into_lines, ); + app.add_action( + "buffer:add_selection_above", + BufferView::add_selection_above, + ); + app.add_action( + "buffer:add_selection_below", + BufferView::add_selection_below, + ); 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); @@ -272,6 +292,8 @@ pub struct BufferView { display_map: DisplayMap, selection_set_id: SelectionSetId, pending_selection: Option, + next_selection_id: usize, + add_selections_state: Option, scroll_position: Mutex, autoscroll_requested: Mutex, settings: watch::Receiver, @@ -282,6 +304,11 @@ pub struct BufferView { single_line: bool, } +struct AddSelectionsState { + above: bool, + stack: Vec, +} + #[derive(Serialize, Deserialize)] struct ClipboardSelection { len: usize, @@ -305,13 +332,15 @@ impl BufferView { ctx.subscribe_to_model(&buffer, Self::on_buffer_event); let display_map = DisplayMap::new(buffer.clone(), settings.borrow().tab_size, ctx.as_ref()); + let mut next_selection_id = 0; let (selection_set_id, _) = buffer.update(ctx, |buffer, ctx| { buffer.add_selection_set( vec![Selection { + id: post_inc(&mut next_selection_id), start: buffer.anchor_before(0).unwrap(), end: buffer.anchor_before(0).unwrap(), reversed: false, - goal_column: None, + goal: SelectionGoal::None, }], Some(ctx), ) @@ -322,6 +351,8 @@ impl BufferView { display_map, selection_set_id, pending_selection: None, + next_selection_id, + add_selections_state: None, scroll_position: Mutex::new(Vector2F::zero()), autoscroll_requested: Mutex::new(false), settings, @@ -480,10 +511,11 @@ impl BufferView { .anchor_before(position, Bias::Left, ctx.as_ref()) .unwrap(); let selection = Selection { + id: post_inc(&mut self.next_selection_id), start: cursor.clone(), end: cursor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }; if !add { @@ -532,6 +564,22 @@ impl BufferView { self.pending_selection.is_some() } + pub fn cancel(&mut self, _: &(), ctx: &mut ViewContext) { + let selections = self.selections(ctx.as_ref()); + if let Some(pending_selection) = self.pending_selection.take() { + if selections.is_empty() { + self.update_selections(vec![pending_selection], true, ctx); + } + } else { + let mut oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); + if selections.len() == 1 { + oldest_selection.start = oldest_selection.head().clone(); + oldest_selection.end = oldest_selection.head().clone(); + } + self.update_selections(vec![oldest_selection], true, ctx); + } + } + fn select_ranges(&mut self, ranges: I, autoscroll: bool, ctx: &mut ViewContext) where I: IntoIterator>, @@ -549,10 +597,11 @@ impl BufferView { false }; selections.push(Selection { + id: post_inc(&mut self.next_selection_id), start: buffer.anchor_before(start).unwrap(), end: buffer.anchor_before(end).unwrap(), reversed, - goal_column: None, + goal: SelectionGoal::None, }); } self.update_selections(selections, autoscroll, ctx); @@ -575,6 +624,7 @@ impl BufferView { }; selections.push(Selection { + id: post_inc(&mut self.next_selection_id), start: self .display_map .anchor_before(start, Bias::Left, ctx.as_ref())?, @@ -582,7 +632,7 @@ impl BufferView { .display_map .anchor_before(end, Bias::Left, ctx.as_ref())?, reversed, - goal_column: None, + goal: SelectionGoal::None, }); } self.update_selections(selections, false, ctx); @@ -590,28 +640,28 @@ impl BufferView { } pub fn insert(&mut self, text: &String, ctx: &mut ViewContext) { - let mut offset_ranges = SmallVec::<[Range; 32]>::new(); + let mut old_selections = SmallVec::<[_; 32]>::new(); { let buffer = self.buffer.read(ctx); for selection in self.selections(ctx.as_ref()) { let start = selection.start.to_offset(buffer).unwrap(); let end = selection.end.to_offset(buffer).unwrap(); - offset_ranges.push(start..end); + old_selections.push((selection.id, start..end)); } } self.start_transaction(ctx); let mut new_selections = Vec::new(); self.buffer.update(ctx, |buffer, ctx| { - if let Err(error) = buffer.edit(offset_ranges.iter().cloned(), text.as_str(), Some(ctx)) - { + let edit_ranges = old_selections.iter().map(|(_, range)| range.clone()); + if let Err(error) = buffer.edit(edit_ranges, text.as_str(), Some(ctx)) { log::error!("error inserting text: {}", error); }; let char_count = text.chars().count() as isize; let mut delta = 0_isize; - new_selections = offset_ranges + new_selections = old_selections .into_iter() - .map(|range| { + .map(|(id, range)| { let start = range.start as isize; let end = range.end as isize; let anchor = buffer @@ -620,10 +670,11 @@ impl BufferView { let deleted_count = end - start; delta += char_count - deleted_count; Selection { + id, start: anchor.clone(), end: anchor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, } }) .collect(); @@ -662,7 +713,7 @@ impl BufferView { ) .unwrap(); selection.set_head(&buffer, cursor); - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } } @@ -693,7 +744,7 @@ impl BufferView { ) .unwrap(); selection.set_head(&buffer, cursor); - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } } @@ -758,23 +809,27 @@ impl BufferView { self.display_map.line_len(cursor.row(), app).unwrap(), ); - new_cursors.push( + new_cursors.push(( + selection.id, cursor .to_buffer_point(&self.display_map, Bias::Left, app) .unwrap(), - ); + )); edit_ranges.push(edit_start..edit_end); } - new_cursors.sort_unstable(); + new_cursors.sort_unstable_by_key(|(_, range)| range.clone()); 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, + .map(|(id, cursor)| { + let anchor = buffer.anchor_before(cursor).unwrap(); + Selection { + id, + start: anchor.clone(), + end: anchor, + reversed: false, + goal: SelectionGoal::None, + } }) .collect(); self.buffer @@ -1142,10 +1197,11 @@ impl BufferView { let new_selection_start = new_selection_start.bias_left(buffer).unwrap(); new_selections.push(Selection { + id: selection.id, start: new_selection_start.clone(), end: new_selection_start, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }); }); } @@ -1196,7 +1252,7 @@ impl BufferView { selection.end = cursor; } selection.reversed = false; - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1220,7 +1276,7 @@ impl BufferView { ) .unwrap(); selection.set_head(&buffer, cursor); - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1255,7 +1311,7 @@ impl BufferView { selection.end = cursor; } selection.reversed = false; - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1280,7 +1336,7 @@ impl BufferView { ) .unwrap(); selection.set_head(&buffer, cursor); - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1303,18 +1359,18 @@ impl BufferView { .to_display_point(&self.display_map, app) .unwrap(); if start != end { - selection.goal_column = None; + selection.goal = SelectionGoal::None; } - let (start, goal_column) = - movement::up(&self.display_map, start, selection.goal_column, app).unwrap(); + let (start, goal) = + movement::up(&self.display_map, start, selection.goal, app).unwrap(); let cursor = self .display_map .anchor_before(start, Bias::Left, app) .unwrap(); selection.start = cursor.clone(); selection.end = cursor; - selection.goal_column = goal_column; + selection.goal = goal; selection.reversed = false; } } @@ -1332,15 +1388,15 @@ impl BufferView { .head() .to_display_point(&self.display_map, app) .unwrap(); - let (head, goal_column) = - movement::up(&self.display_map, head, selection.goal_column, app).unwrap(); + let (head, goal) = + movement::up(&self.display_map, head, selection.goal, app).unwrap(); selection.set_head( &buffer, self.display_map .anchor_before(head, Bias::Left, app) .unwrap(), ); - selection.goal_column = goal_column; + selection.goal = goal; } } self.update_selections(selections, true, ctx); @@ -1363,18 +1419,18 @@ impl BufferView { .to_display_point(&self.display_map, app) .unwrap(); if start != end { - selection.goal_column = None; + selection.goal = SelectionGoal::None; } - let (start, goal_column) = - movement::down(&self.display_map, end, selection.goal_column, app).unwrap(); + let (start, goal) = + movement::down(&self.display_map, end, selection.goal, app).unwrap(); let cursor = self .display_map .anchor_before(start, Bias::Right, app) .unwrap(); selection.start = cursor.clone(); selection.end = cursor; - selection.goal_column = goal_column; + selection.goal = goal; selection.reversed = false; } } @@ -1392,15 +1448,15 @@ impl BufferView { .head() .to_display_point(&self.display_map, app) .unwrap(); - let (head, goal_column) = - movement::down(&self.display_map, head, selection.goal_column, app).unwrap(); + let (head, goal) = + movement::down(&self.display_map, head, selection.goal, app).unwrap(); selection.set_head( &buffer, self.display_map .anchor_before(head, Bias::Right, app) .unwrap(), ); - selection.goal_column = goal_column; + selection.goal = goal; } } self.update_selections(selections, true, ctx); @@ -1423,7 +1479,7 @@ impl BufferView { selection.start = anchor.clone(); selection.end = anchor; selection.reversed = false; - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1445,7 +1501,7 @@ impl BufferView { .anchor_before(new_head, Bias::Left, app) .unwrap(); selection.set_head(buffer, anchor); - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1475,7 +1531,7 @@ impl BufferView { selection.start = anchor.clone(); selection.end = anchor; selection.reversed = false; - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1497,7 +1553,7 @@ impl BufferView { .anchor_before(new_head, Bias::Left, app) .unwrap(); selection.set_head(buffer, anchor); - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1528,7 +1584,7 @@ impl BufferView { selection.start = anchor.clone(); selection.end = anchor; selection.reversed = false; - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1555,7 +1611,7 @@ impl BufferView { .anchor_before(new_head, Bias::Left, app) .unwrap(); selection.set_head(buffer, anchor); - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1585,7 +1641,7 @@ impl BufferView { selection.start = anchor.clone(); selection.end = anchor; selection.reversed = false; - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1607,7 +1663,7 @@ impl BufferView { .anchor_before(new_head, Bias::Left, app) .unwrap(); selection.set_head(buffer, anchor); - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } self.update_selections(selections, true, ctx); @@ -1624,10 +1680,11 @@ impl BufferView { let buffer = self.buffer.read(ctx); let cursor = buffer.anchor_before(Point::new(0, 0)).unwrap(); let selection = Selection { + id: post_inc(&mut self.next_selection_id), start: cursor.clone(), end: cursor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }; self.update_selections(vec![selection], true, ctx); } @@ -1642,10 +1699,11 @@ impl BufferView { let buffer = self.buffer.read(ctx); let cursor = buffer.anchor_before(buffer.max_point()).unwrap(); let selection = Selection { + id: post_inc(&mut self.next_selection_id), start: cursor.clone(), end: cursor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }; self.update_selections(vec![selection], true, ctx); } @@ -1658,10 +1716,11 @@ impl BufferView { pub fn select_all(&mut self, _: &(), ctx: &mut ViewContext) { let selection = Selection { + id: post_inc(&mut self.next_selection_id), start: Anchor::Start, end: Anchor::End, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }; self.update_selections(vec![selection], false, ctx); } @@ -1694,10 +1753,11 @@ impl BufferView { let range = selection.range(buffer).sorted(); if range.start.row != range.end.row { new_selections.push(Selection { + id: post_inc(&mut self.next_selection_id), start: selection.start.clone(), end: selection.start.clone(), reversed: false, - goal_column: None, + goal: SelectionGoal::None, }); } for row in range.start.row + 1..range.end.row { @@ -1705,17 +1765,19 @@ impl BufferView { .anchor_before(Point::new(row, buffer.line_len(row).unwrap())) .unwrap(); new_selections.push(Selection { + id: post_inc(&mut self.next_selection_id), start: cursor.clone(), end: cursor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }); } new_selections.push(Selection { + id: selection.id, start: selection.end.clone(), end: selection.end.clone(), reversed: false, - goal_column: None, + goal: SelectionGoal::None, }); to_unfold.push(range); } @@ -1723,6 +1785,139 @@ impl BufferView { self.update_selections(new_selections, true, ctx); } + pub fn add_selection_above(&mut self, _: &(), ctx: &mut ViewContext) { + self.add_selection(true, ctx); + } + + pub fn add_selection_below(&mut self, _: &(), ctx: &mut ViewContext) { + self.add_selection(false, ctx); + } + + pub fn add_selection(&mut self, above: bool, ctx: &mut ViewContext) { + use super::RangeExt; + + let app = ctx.as_ref(); + + let mut selections = self.selections(app).to_vec(); + let mut state = self.add_selections_state.take().unwrap_or_else(|| { + let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); + let range = oldest_selection + .display_range(&self.display_map, app) + .sorted(); + let columns = cmp::min(range.start.column(), range.end.column()) + ..cmp::max(range.start.column(), range.end.column()); + + selections.clear(); + let mut stack = Vec::new(); + for row in range.start.row()..=range.end.row() { + if let Some(selection) = + self.build_columnar_selection(row, &columns, oldest_selection.reversed, app) + { + stack.push(selection.id); + selections.push(selection); + } + } + + if above { + stack.reverse(); + } + + AddSelectionsState { above, stack } + }); + + let last_added_selection = *state.stack.last().unwrap(); + let mut new_selections = Vec::new(); + if above == state.above { + let end_row = if above { + 0 + } else { + self.display_map.max_point(app).row() + }; + + 'outer: for selection in selections { + if selection.id == last_added_selection { + let range = selection.display_range(&self.display_map, app).sorted(); + debug_assert_eq!(range.start.row(), range.end.row()); + let mut row = range.start.row(); + let columns = if let SelectionGoal::ColumnRange { start, end } = selection.goal + { + start..end + } else { + cmp::min(range.start.column(), range.end.column()) + ..cmp::max(range.start.column(), range.end.column()) + }; + + while row != end_row { + if above { + row -= 1; + } else { + row += 1; + } + + if let Some(new_selection) = + self.build_columnar_selection(row, &columns, selection.reversed, app) + { + state.stack.push(new_selection.id); + if above { + new_selections.push(new_selection); + new_selections.push(selection); + } else { + new_selections.push(selection); + new_selections.push(new_selection); + } + + continue 'outer; + } + } + } + + new_selections.push(selection); + } + } else { + new_selections = selections; + new_selections.retain(|s| s.id != last_added_selection); + state.stack.pop(); + } + + self.update_selections(new_selections, true, ctx); + if state.stack.len() > 1 { + self.add_selections_state = Some(state); + } + } + + fn build_columnar_selection( + &mut self, + row: u32, + columns: &Range, + reversed: bool, + ctx: &AppContext, + ) -> Option { + let is_empty = columns.start == columns.end; + let line_len = self.display_map.line_len(row, ctx).unwrap(); + if columns.start < line_len || (is_empty && columns.start == line_len) { + let start = DisplayPoint::new(row, columns.start); + let end = DisplayPoint::new(row, cmp::min(columns.end, line_len)); + Some(Selection { + id: post_inc(&mut self.next_selection_id), + start: self + .display_map + .anchor_before(start, Bias::Left, ctx) + .unwrap(), + end: self + .display_map + .anchor_before(end, Bias::Left, ctx) + .unwrap(), + reversed, + goal: SelectionGoal::ColumnRange { + start: columns.start, + end: columns.end, + }, + }) + } else { + None + } + } + pub fn selections_in_range<'a>( &'a self, range: Range, @@ -1811,6 +2006,8 @@ impl BufferView { *self.autoscroll_requested.lock() = true; ctx.notify(); } + + self.add_selections_state = None; } fn start_transaction(&self, ctx: &mut ViewContext) { @@ -2428,6 +2625,80 @@ mod tests { }); } + #[test] + fn test_canceling_pending_selection() { + App::test((), |app| { + let buffer = + app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", 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.begin_selection(DisplayPoint::new(2, 2), false, ctx); + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] + ); + + view.update(app, |view, ctx| { + view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx); + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] + ); + + view.update(app, |view, ctx| { + view.cancel(&(), ctx); + view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx); + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] + ); + }); + } + + #[test] + fn test_cancel() { + App::test((), |app| { + let buffer = + app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", 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.begin_selection(DisplayPoint::new(3, 4), false, ctx); + view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx); + view.end_selection(ctx); + + view.begin_selection(DisplayPoint::new(0, 1), true, ctx); + view.update_selection(DisplayPoint::new(0, 3), Vector2F::zero(), ctx); + view.end_selection(ctx); + }); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + [ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), + DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1), + ] + ); + + view.update(app, |view, ctx| view.cancel(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)] + ); + + view.update(app, |view, ctx| view.cancel(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)] + ); + }); + } + #[test] fn test_layout_line_numbers() { App::test((), |app| { @@ -3478,6 +3749,167 @@ mod tests { }); } + #[test] + fn test_add_selection_above_below() { + App::test((), |app| { + let settings = settings::channel(&app.font_cache()).unwrap().1; + let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndefghi\n\njk\nlmno\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(1, 3)..DisplayPoint::new(1, 3)], + ctx, + ) + .unwrap(); + }); + view.update(app, |view, ctx| view.add_selection_above(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), + DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3) + ] + ); + + view.update(app, |view, ctx| view.add_selection_above(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), + DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3) + ] + ); + + view.update(app, |view, ctx| view.add_selection_below(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)] + ); + + view.update(app, |view, ctx| view.add_selection_below(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3), + DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3) + ] + ); + + view.update(app, |view, ctx| view.add_selection_below(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3), + DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3) + ] + ); + + view.update(app, |view, ctx| { + view.select_display_ranges( + &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)], + ctx, + ) + .unwrap(); + }); + view.update(app, |view, ctx| view.add_selection_below(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3), + DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3) + ] + ); + + view.update(app, |view, ctx| view.add_selection_below(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3), + DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3) + ] + ); + + view.update(app, |view, ctx| view.add_selection_above(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)] + ); + + view.update(app, |view, ctx| view.add_selection_above(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)] + ); + + view.update(app, |view, ctx| { + view.select_display_ranges( + &[DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)], + ctx, + ) + .unwrap(); + }); + view.update(app, |view, ctx| view.add_selection_below(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), + DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4), + DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2), + ] + ); + + view.update(app, |view, ctx| view.add_selection_below(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), + DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4), + DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2), + DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4), + ] + ); + + view.update(app, |view, ctx| view.add_selection_above(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), + DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4), + DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2), + ] + ); + + view.update(app, |view, ctx| { + view.select_display_ranges( + &[DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)], + ctx, + ) + .unwrap(); + }); + view.update(app, |view, ctx| view.add_selection_above(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1), + DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1), + DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1), + DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1), + ] + ); + + view.update(app, |view, ctx| view.add_selection_below(&(), ctx)); + assert_eq!( + view.read(app).selection_ranges(app.as_ref()), + vec![ + DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1), + DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1), + DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1), + ] + ); + }); + } + impl BufferView { fn selection_ranges(&self, app: &AppContext) -> Vec> { self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app) diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index 742218553822ebd36211b2f9a406984effa49116..dbb1d4fafcb85b8e1d95eb84732c181fd920e998 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -1,4 +1,4 @@ -use super::{DisplayMap, DisplayPoint}; +use super::{DisplayMap, DisplayPoint, SelectionGoal}; use anyhow::Result; use gpui::AppContext; use std::cmp; @@ -27,36 +27,44 @@ pub fn right(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Res pub fn up( map: &DisplayMap, mut point: DisplayPoint, - goal_column: Option, + goal: SelectionGoal, app: &AppContext, -) -> Result<(DisplayPoint, Option)> { - let goal_column = goal_column.or(Some(point.column())); +) -> Result<(DisplayPoint, SelectionGoal)> { + let goal_column = if let SelectionGoal::Column(column) = goal { + column + } else { + point.column() + }; if point.row() > 0 { *point.row_mut() -= 1; - *point.column_mut() = cmp::min(goal_column.unwrap(), map.line_len(point.row(), app)?); + *point.column_mut() = cmp::min(goal_column, map.line_len(point.row(), app)?); } else { point = DisplayPoint::new(0, 0); } - Ok((point, goal_column)) + Ok((point, SelectionGoal::Column(goal_column))) } pub fn down( map: &DisplayMap, mut point: DisplayPoint, - goal_column: Option, + goal: SelectionGoal, app: &AppContext, -) -> Result<(DisplayPoint, Option)> { - let goal_column = goal_column.or(Some(point.column())); +) -> Result<(DisplayPoint, SelectionGoal)> { + let goal_column = if let SelectionGoal::Column(column) = goal { + column + } else { + point.column() + }; let max_point = map.max_point(app); if point.row() < max_point.row() { *point.row_mut() += 1; - *point.column_mut() = cmp::min(goal_column.unwrap(), map.line_len(point.row(), app)?) + *point.column_mut() = cmp::min(goal_column, map.line_len(point.row(), app)?) } else { point = max_point; } - Ok((point, goal_column)) + Ok((point, SelectionGoal::Column(goal_column))) } pub fn line_beginning(