From 0baff0c61d47423528a2830a6b6a23bb675d794e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 May 2021 15:06:07 +0200 Subject: [PATCH 01/11] Implement `add_cursor_above` and `add_cursor_below` for buffer --- zed/src/editor/buffer/mod.rs | 4 +- zed/src/editor/buffer/selection.rs | 9 +- zed/src/editor/buffer_view.rs | 201 +++++++++++++++++++++++------ zed/src/editor/movement.rs | 30 +++-- 4 files changed, 188 insertions(+), 56 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index e4a06bfda95f90b5dcf3eb4f6d0a940f4e7886f3..89c3d6c558efd22326e88cddf33555d4835f1327 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -3285,14 +3285,14 @@ mod tests { start: self.anchor_before(range.end)?, end: self.anchor_before(range.start)?, reversed: true, - goal_column: None, + goal: SelectionGoal::None, }); } else { selections.push(Selection { 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..91cb715ddcb7eae674a5d765eb76c67d8f8b8093 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -12,12 +12,19 @@ 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 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..049b90f2409f2ffc48a1be393f98df55e5a56704 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1,6 +1,6 @@ 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 anyhow::Result; @@ -149,6 +149,16 @@ pub fn init(app: &mut MutableAppContext) { "buffer:split_selection_into_lines", Some("BufferView"), ), + Binding::new( + "ctrl-shift-up", + "buffer:add_cursor_above", + Some("BufferView"), + ), + Binding::new( + "ctrl-shift-down", + "buffer:add_cursor_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")), @@ -244,6 +254,8 @@ pub fn init(app: &mut MutableAppContext) { "buffer:split_selection_into_lines", BufferView::split_selection_into_lines, ); + app.add_action("buffer:add_cursor_above", BufferView::add_cursor_above); + app.add_action("buffer:add_cursor_below", BufferView::add_cursor_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); @@ -311,7 +323,7 @@ impl BufferView { start: buffer.anchor_before(0).unwrap(), end: buffer.anchor_before(0).unwrap(), reversed: false, - goal_column: None, + goal: SelectionGoal::None, }], Some(ctx), ) @@ -483,7 +495,7 @@ impl BufferView { start: cursor.clone(), end: cursor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }; if !add { @@ -552,7 +564,7 @@ impl BufferView { 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); @@ -582,7 +594,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); @@ -623,7 +635,7 @@ impl BufferView { start: anchor.clone(), end: anchor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, } }) .collect(); @@ -662,7 +674,7 @@ impl BufferView { ) .unwrap(); selection.set_head(&buffer, cursor); - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } } @@ -693,7 +705,7 @@ impl BufferView { ) .unwrap(); selection.set_head(&buffer, cursor); - selection.goal_column = None; + selection.goal = SelectionGoal::None; } } } @@ -774,7 +786,7 @@ impl BufferView { start: anchor.clone(), end: anchor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }) .collect(); self.buffer @@ -1145,7 +1157,7 @@ impl BufferView { start: new_selection_start.clone(), end: new_selection_start, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }); }); } @@ -1196,7 +1208,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 +1232,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 +1267,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 +1292,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 +1315,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 +1344,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 +1375,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 +1404,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 +1435,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 +1457,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 +1487,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 +1509,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 +1540,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 +1567,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 +1597,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 +1619,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); @@ -1627,7 +1639,7 @@ impl BufferView { start: cursor.clone(), end: cursor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }; self.update_selections(vec![selection], true, ctx); } @@ -1645,7 +1657,7 @@ impl BufferView { start: cursor.clone(), end: cursor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }; self.update_selections(vec![selection], true, ctx); } @@ -1661,7 +1673,7 @@ impl BufferView { start: Anchor::Start, end: Anchor::End, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }; self.update_selections(vec![selection], false, ctx); } @@ -1697,7 +1709,7 @@ impl BufferView { 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 { @@ -1708,14 +1720,14 @@ impl BufferView { start: cursor.clone(), end: cursor, reversed: false, - goal_column: None, + goal: SelectionGoal::None, }); } new_selections.push(Selection { start: selection.end.clone(), end: selection.end.clone(), reversed: false, - goal_column: None, + goal: SelectionGoal::None, }); to_unfold.push(range); } @@ -1723,6 +1735,111 @@ impl BufferView { self.update_selections(new_selections, true, ctx); } + pub fn add_cursor_above(&mut self, _: &(), ctx: &mut ViewContext) { + use super::RangeExt; + + let app = ctx.as_ref(); + let buffer = self.buffer.read(app); + + let mut new_selections = Vec::new(); + for selection in self.selections(app) { + let range = selection.display_range(&self.display_map, app).sorted(); + + let start_column; + let end_column; + if let SelectionGoal::ColumnRange { start, end } = selection.goal { + start_column = start; + end_column = end; + } else { + start_column = cmp::min(range.start.column(), range.end.column()); + end_column = cmp::max(range.start.column(), range.end.column()); + } + let is_empty = start_column == end_column; + + for row in (0..range.start.row()).rev() { + let line_len = self.display_map.line_len(row, app).unwrap(); + if start_column < line_len || (is_empty && start_column == line_len) { + let start = DisplayPoint::new(row, start_column); + let end = DisplayPoint::new(row, cmp::min(end_column, line_len)); + new_selections.push(Selection { + start: self + .display_map + .anchor_before(start, Bias::Left, app) + .unwrap(), + end: self + .display_map + .anchor_before(end, Bias::Left, app) + .unwrap(), + reversed: selection.reversed && range.start.row() == range.end.row(), + goal: SelectionGoal::ColumnRange { + start: start_column, + end: end_column, + }, + }); + break; + } + } + + new_selections.push(selection.clone()); + } + + new_selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap()); + self.update_selections(new_selections, true, ctx); + } + + pub fn add_cursor_below(&mut self, _: &(), ctx: &mut ViewContext) { + use super::RangeExt; + + let app = ctx.as_ref(); + let buffer = self.buffer.read(app); + let max_point = self.display_map.max_point(app); + + let mut new_selections = Vec::new(); + for selection in self.selections(app) { + let range = selection.display_range(&self.display_map, app).sorted(); + + let start_column; + let end_column; + if let SelectionGoal::ColumnRange { start, end } = selection.goal { + start_column = start; + end_column = end; + } else { + start_column = cmp::min(range.start.column(), range.end.column()); + end_column = cmp::max(range.start.column(), range.end.column()); + } + let is_empty = start_column == end_column; + + for row in range.end.row() + 1..=max_point.row() { + let line_len = self.display_map.line_len(row, app).unwrap(); + if start_column < line_len || (is_empty && start_column == line_len) { + let start = DisplayPoint::new(row, start_column); + let end = DisplayPoint::new(row, cmp::min(end_column, line_len)); + new_selections.push(Selection { + start: self + .display_map + .anchor_before(start, Bias::Left, app) + .unwrap(), + end: self + .display_map + .anchor_before(end, Bias::Left, app) + .unwrap(), + reversed: selection.reversed && range.start.row() == range.end.row(), + goal: SelectionGoal::ColumnRange { + start: start_column, + end: end_column, + }, + }); + break; + } + } + + new_selections.push(selection.clone()); + } + + new_selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap()); + self.update_selections(new_selections, true, ctx); + } + pub fn selections_in_range<'a>( &'a self, range: Range, 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( From 143c50f3212faa8bc718aa12b7caba1e56be7601 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 May 2021 16:10:41 +0200 Subject: [PATCH 02/11] Implement `cancel` for buffer --- zed/src/editor/buffer/mod.rs | 9 +++- zed/src/editor/buffer/selection.rs | 1 + zed/src/editor/buffer_view.rs | 67 +++++++++++++++++++++++------- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 89c3d6c558efd22326e88cddf33555d4835f1327..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,6 +3287,7 @@ 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, @@ -3289,6 +3295,7 @@ mod tests { }); } 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, diff --git a/zed/src/editor/buffer/selection.rs b/zed/src/editor/buffer/selection.rs index 91cb715ddcb7eae674a5d765eb76c67d8f8b8093..bff91a83e922a712889a62dbf5a6c80184be821c 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -21,6 +21,7 @@ pub enum SelectionGoal { #[derive(Clone, Debug, Eq, PartialEq)] pub struct Selection { + pub id: usize, pub start: Anchor, pub end: Anchor, pub reversed: bool, diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 049b90f2409f2ffc48a1be393f98df55e5a56704..999ca64fd4b8fa4e16ef28d7beea760d1d0fb6bb 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2,7 +2,7 @@ use super::{ buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point, 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")), @@ -172,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); @@ -284,6 +286,7 @@ pub struct BufferView { display_map: DisplayMap, selection_set_id: SelectionSetId, pending_selection: Option, + next_selection_id: usize, scroll_position: Mutex, autoscroll_requested: Mutex, settings: watch::Receiver, @@ -317,9 +320,11 @@ 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, @@ -334,6 +339,7 @@ impl BufferView { display_map, selection_set_id, pending_selection: None, + next_selection_id, scroll_position: Mutex::new(Vector2F::zero()), autoscroll_requested: Mutex::new(false), settings, @@ -492,6 +498,7 @@ 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, @@ -544,6 +551,18 @@ 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 oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); + self.update_selections(vec![oldest_selection], true, ctx); + } + } + fn select_ranges(&mut self, ranges: I, autoscroll: bool, ctx: &mut ViewContext) where I: IntoIterator>, @@ -561,6 +580,7 @@ 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, @@ -587,6 +607,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())?, @@ -602,28 +623,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 @@ -632,6 +653,7 @@ impl BufferView { let deleted_count = end - start; delta += char_count - deleted_count; Selection { + id, start: anchor.clone(), end: anchor, reversed: false, @@ -770,23 +792,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: SelectionGoal::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 @@ -1154,6 +1180,7 @@ 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, @@ -1636,6 +1663,7 @@ 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, @@ -1654,6 +1682,7 @@ 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, @@ -1670,6 +1699,7 @@ 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, @@ -1706,6 +1736,7 @@ 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, @@ -1717,6 +1748,7 @@ 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, @@ -1724,6 +1756,7 @@ impl BufferView { }); } new_selections.push(Selection { + id: selection.id, start: selection.end.clone(), end: selection.end.clone(), reversed: false, @@ -1762,6 +1795,7 @@ impl BufferView { let start = DisplayPoint::new(row, start_column); let end = DisplayPoint::new(row, cmp::min(end_column, line_len)); new_selections.push(Selection { + id: post_inc(&mut self.next_selection_id), start: self .display_map .anchor_before(start, Bias::Left, app) @@ -1815,6 +1849,7 @@ impl BufferView { let start = DisplayPoint::new(row, start_column); let end = DisplayPoint::new(row, cmp::min(end_column, line_len)); new_selections.push(Selection { + id: post_inc(&mut self.next_selection_id), start: self .display_map .anchor_before(start, Bias::Left, app) From acbda9184ecad67ab9b33b16492c238c50361866 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 May 2021 17:09:39 +0200 Subject: [PATCH 03/11] Empty last selection on cancel Co-Authored-By: Nathan Sobo --- zed/src/editor/buffer_view.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 999ca64fd4b8fa4e16ef28d7beea760d1d0fb6bb..7b77b3f993f4496de684ebb3d79abf3946ba7f09 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -558,7 +558,11 @@ impl BufferView { self.update_selections(vec![pending_selection], true, ctx); } } else { - let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); + 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); } } From b6449b3809db0c45d04b459dfde54d911669604a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 May 2021 18:06:47 +0200 Subject: [PATCH 04/11] Support undo of adding cursor above by adding cursor below and viceversa Co-Authored-By: Nathan Sobo --- zed/src/editor/buffer_view.rs | 179 +++++++++++++++++----------------- 1 file changed, 91 insertions(+), 88 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 7b77b3f993f4496de684ebb3d79abf3946ba7f09..7e97bfdf1bd195b3c18a6c05e1046f64453d5f3f 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -17,6 +17,7 @@ use smallvec::SmallVec; use smol::Timer; use std::{ cmp::{self, Ordering}, + collections::HashSet, fmt::Write, iter::FromIterator, mem, @@ -287,6 +288,7 @@ pub struct BufferView { selection_set_id: SelectionSetId, pending_selection: Option, next_selection_id: usize, + add_selections_state: Option, scroll_position: Mutex, autoscroll_requested: Mutex, settings: watch::Receiver, @@ -297,6 +299,11 @@ pub struct BufferView { single_line: bool, } +struct AddSelectionsState { + above: bool, + stack: Vec>, +} + #[derive(Serialize, Deserialize)] struct ClipboardSelection { len: usize, @@ -340,6 +347,7 @@ impl BufferView { 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, @@ -1773,110 +1781,103 @@ impl BufferView { } pub fn add_cursor_above(&mut self, _: &(), ctx: &mut ViewContext) { - use super::RangeExt; - - let app = ctx.as_ref(); - let buffer = self.buffer.read(app); - - let mut new_selections = Vec::new(); - for selection in self.selections(app) { - let range = selection.display_range(&self.display_map, app).sorted(); - - let start_column; - let end_column; - if let SelectionGoal::ColumnRange { start, end } = selection.goal { - start_column = start; - end_column = end; - } else { - start_column = cmp::min(range.start.column(), range.end.column()); - end_column = cmp::max(range.start.column(), range.end.column()); - } - let is_empty = start_column == end_column; - - for row in (0..range.start.row()).rev() { - let line_len = self.display_map.line_len(row, app).unwrap(); - if start_column < line_len || (is_empty && start_column == line_len) { - let start = DisplayPoint::new(row, start_column); - let end = DisplayPoint::new(row, cmp::min(end_column, line_len)); - new_selections.push(Selection { - id: post_inc(&mut self.next_selection_id), - start: self - .display_map - .anchor_before(start, Bias::Left, app) - .unwrap(), - end: self - .display_map - .anchor_before(end, Bias::Left, app) - .unwrap(), - reversed: selection.reversed && range.start.row() == range.end.row(), - goal: SelectionGoal::ColumnRange { - start: start_column, - end: end_column, - }, - }); - break; - } - } - - new_selections.push(selection.clone()); - } - - new_selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap()); - self.update_selections(new_selections, true, ctx); + self.add_cursor(true, ctx); } pub fn add_cursor_below(&mut self, _: &(), ctx: &mut ViewContext) { + self.add_cursor(false, ctx); + } + + pub fn add_cursor(&mut self, above: bool, ctx: &mut ViewContext) { use super::RangeExt; let app = ctx.as_ref(); let buffer = self.buffer.read(app); - let max_point = self.display_map.max_point(app); + let selections = self.selections(app); + let mut state = self + .add_selections_state + .take() + .unwrap_or_else(|| AddSelectionsState { + above, + stack: vec![selections.iter().map(|s| s.id).collect()], + }); + let last_added_selections = state.stack.last().unwrap(); let mut new_selections = Vec::new(); - for selection in self.selections(app) { - let range = selection.display_range(&self.display_map, app).sorted(); + if above == state.above { + let mut added_selections = HashSet::new(); + for selection in selections { + if last_added_selections.contains(&selection.id) { + let range = selection.display_range(&self.display_map, app).sorted(); + + let mut row = range.start.row(); + let start_column; + let end_column; + if let SelectionGoal::ColumnRange { start, end } = selection.goal { + start_column = start; + end_column = end; + } else { + start_column = cmp::min(range.start.column(), range.end.column()); + end_column = cmp::max(range.start.column(), range.end.column()); + } + let is_empty = start_column == end_column; - let start_column; - let end_column; - if let SelectionGoal::ColumnRange { start, end } = selection.goal { - start_column = start; - end_column = end; - } else { - start_column = cmp::min(range.start.column(), range.end.column()); - end_column = cmp::max(range.start.column(), range.end.column()); - } - let is_empty = start_column == end_column; - - for row in range.end.row() + 1..=max_point.row() { - let line_len = self.display_map.line_len(row, app).unwrap(); - if start_column < line_len || (is_empty && start_column == line_len) { - let start = DisplayPoint::new(row, start_column); - let end = DisplayPoint::new(row, cmp::min(end_column, line_len)); - new_selections.push(Selection { - id: post_inc(&mut self.next_selection_id), - start: self - .display_map - .anchor_before(start, Bias::Left, app) - .unwrap(), - end: self - .display_map - .anchor_before(end, Bias::Left, app) - .unwrap(), - reversed: selection.reversed && range.start.row() == range.end.row(), - goal: SelectionGoal::ColumnRange { - start: start_column, - end: end_column, - }, - }); - break; + while row > 0 && row < self.display_map.max_point(app).row() { + if above { + row -= 1; + } else { + row += 1; + } + + let line_len = self.display_map.line_len(row, app).unwrap(); + if start_column < line_len || (is_empty && start_column == line_len) { + let id = post_inc(&mut self.next_selection_id); + let start = DisplayPoint::new(row, start_column); + let end = DisplayPoint::new(row, cmp::min(end_column, line_len)); + new_selections.push(Selection { + id, + start: self + .display_map + .anchor_before(start, Bias::Left, app) + .unwrap(), + end: self + .display_map + .anchor_before(end, Bias::Left, app) + .unwrap(), + reversed: selection.reversed + && range.start.row() == range.end.row(), + goal: SelectionGoal::ColumnRange { + start: start_column, + end: end_column, + }, + }); + added_selections.insert(id); + break; + } + } } + + new_selections.push(selection.clone()); } - new_selections.push(selection.clone()); + if !added_selections.is_empty() { + state.stack.push(added_selections); + } + new_selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap()); + } else { + new_selections.extend( + selections + .into_iter() + .filter(|s| !last_added_selections.contains(&s.id)) + .cloned(), + ); + state.stack.pop(); } - new_selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap()); self.update_selections(new_selections, true, ctx); + if state.stack.len() > 1 { + self.add_selections_state = Some(state); + } } pub fn selections_in_range<'a>( @@ -1967,6 +1968,8 @@ impl BufferView { *self.autoscroll_requested.lock() = true; ctx.notify(); } + + self.add_selections_state = None; } fn start_transaction(&self, ctx: &mut ViewContext) { From 3fe64400c600247c2a0593684bed42545d62e323 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 May 2021 18:10:40 +0200 Subject: [PATCH 05/11] Rename `add_cursor_{above,below}` to `add_selection_{above, below}` Co-Authored-By: Nathan Sobo --- zed/src/editor/buffer_view.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 7e97bfdf1bd195b3c18a6c05e1046f64453d5f3f..c71c56cf7fc047528804a178828c91dd515126f6 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -153,12 +153,12 @@ pub fn init(app: &mut MutableAppContext) { ), Binding::new( "ctrl-shift-up", - "buffer:add_cursor_above", + "buffer:add_selection_above", Some("BufferView"), ), Binding::new( "ctrl-shift-down", - "buffer:add_cursor_below", + "buffer:add_selection_below", Some("BufferView"), ), Binding::new("pageup", "buffer:page_up", Some("BufferView")), @@ -257,8 +257,14 @@ pub fn init(app: &mut MutableAppContext) { "buffer:split_selection_into_lines", BufferView::split_selection_into_lines, ); - app.add_action("buffer:add_cursor_above", BufferView::add_cursor_above); - app.add_action("buffer:add_cursor_below", BufferView::add_cursor_below); + 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); @@ -1780,15 +1786,15 @@ impl BufferView { self.update_selections(new_selections, true, ctx); } - pub fn add_cursor_above(&mut self, _: &(), ctx: &mut ViewContext) { - self.add_cursor(true, ctx); + pub fn add_selection_above(&mut self, _: &(), ctx: &mut ViewContext) { + self.add_selection(true, ctx); } - pub fn add_cursor_below(&mut self, _: &(), ctx: &mut ViewContext) { - self.add_cursor(false, ctx); + pub fn add_selection_below(&mut self, _: &(), ctx: &mut ViewContext) { + self.add_selection(false, ctx); } - pub fn add_cursor(&mut self, above: bool, ctx: &mut ViewContext) { + pub fn add_selection(&mut self, above: bool, ctx: &mut ViewContext) { use super::RangeExt; let app = ctx.as_ref(); From 951aa0e4437227842398a077dc7509bca1b5f741 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 May 2021 18:22:25 +0200 Subject: [PATCH 06/11] Keep only one selection when starting to add selections above/below Co-Authored-By: Nathan Sobo --- zed/src/editor/buffer_view.rs | 42 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index c71c56cf7fc047528804a178828c91dd515126f6..70c2f905c81eabf3e20e69191842fb25d094b7b5 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -17,7 +17,6 @@ use smallvec::SmallVec; use smol::Timer; use std::{ cmp::{self, Ordering}, - collections::HashSet, fmt::Write, iter::FromIterator, mem, @@ -307,7 +306,7 @@ pub struct BufferView { struct AddSelectionsState { above: bool, - stack: Vec>, + stack: Vec, } #[derive(Serialize, Deserialize)] @@ -1799,24 +1798,34 @@ impl BufferView { let app = ctx.as_ref(); let buffer = self.buffer.read(app); - let selections = self.selections(app); - let mut state = self - .add_selections_state - .take() - .unwrap_or_else(|| AddSelectionsState { + let mut selections = self.selections(app); + let mut state = if let Some(state) = self.add_selections_state.take() { + state + } else { + let (ix, oldest_selection) = selections + .iter() + .enumerate() + .min_by_key(|(_, s)| s.id) + .unwrap(); + selections = &selections[ix..ix + 1]; + AddSelectionsState { above, - stack: vec![selections.iter().map(|s| s.id).collect()], - }); + stack: vec![oldest_selection.id], + } + }; - let last_added_selections = state.stack.last().unwrap(); + let last_added_selection = *state.stack.last().unwrap(); let mut new_selections = Vec::new(); if above == state.above { - let mut added_selections = HashSet::new(); for selection in selections { - if last_added_selections.contains(&selection.id) { + if selection.id == last_added_selection { let range = selection.display_range(&self.display_map, app).sorted(); - let mut row = range.start.row(); + let mut row = if above { + range.start.row() + } else { + range.end.row() + }; let start_column; let end_column; if let SelectionGoal::ColumnRange { start, end } = selection.goal { @@ -1857,7 +1866,7 @@ impl BufferView { end: end_column, }, }); - added_selections.insert(id); + state.stack.push(id); break; } } @@ -1866,15 +1875,12 @@ impl BufferView { new_selections.push(selection.clone()); } - if !added_selections.is_empty() { - state.stack.push(added_selections); - } new_selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap()); } else { new_selections.extend( selections .into_iter() - .filter(|s| !last_added_selections.contains(&s.id)) + .filter(|s| s.id != last_added_selection) .cloned(), ); state.stack.pop(); From 0622722ab7e1f8470d7852f32f1926a5f06648e3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 11 May 2021 11:54:53 +0200 Subject: [PATCH 07/11] Split multi-line selection when starting a new columnar selection --- zed/src/editor/buffer_view.rs | 153 +++++++++++++++++++++------------- 1 file changed, 93 insertions(+), 60 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 70c2f905c81eabf3e20e69191842fb25d094b7b5..9a496b6dee7cb2234e498d2f165de801e85aa6d8 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1797,92 +1797,92 @@ impl BufferView { use super::RangeExt; let app = ctx.as_ref(); - let buffer = self.buffer.read(app); - let mut selections = self.selections(app); + + let mut selections = self.selections(app).to_vec(); let mut state = if let Some(state) = self.add_selections_state.take() { state } else { - let (ix, oldest_selection) = selections - .iter() - .enumerate() - .min_by_key(|(_, s)| s.id) - .unwrap(); - selections = &selections[ix..ix + 1]; - AddSelectionsState { - above, - stack: vec![oldest_selection.id], + let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); + let range = oldest_selection + .display_range(&self.display_map, app) + .sorted(); + + selections.clear(); + let mut stack = Vec::new(); + if range.start.row() == range.end.row() { + stack.push(oldest_selection.id); + selections.push(oldest_selection); + } else { + let columns = cmp::min(range.start.column(), range.end.column()) + ..cmp::max(range.start.column(), range.end.column()); + 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 { - for selection in selections { + 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(); - - let mut row = if above { - range.start.row() + 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 { - range.end.row() + cmp::min(range.start.column(), range.end.column()) + ..cmp::max(range.start.column(), range.end.column()) }; - let start_column; - let end_column; - if let SelectionGoal::ColumnRange { start, end } = selection.goal { - start_column = start; - end_column = end; - } else { - start_column = cmp::min(range.start.column(), range.end.column()); - end_column = cmp::max(range.start.column(), range.end.column()); - } - let is_empty = start_column == end_column; - while row > 0 && row < self.display_map.max_point(app).row() { + while row != end_row { if above { row -= 1; } else { row += 1; } - let line_len = self.display_map.line_len(row, app).unwrap(); - if start_column < line_len || (is_empty && start_column == line_len) { - let id = post_inc(&mut self.next_selection_id); - let start = DisplayPoint::new(row, start_column); - let end = DisplayPoint::new(row, cmp::min(end_column, line_len)); - new_selections.push(Selection { - id, - start: self - .display_map - .anchor_before(start, Bias::Left, app) - .unwrap(), - end: self - .display_map - .anchor_before(end, Bias::Left, app) - .unwrap(), - reversed: selection.reversed - && range.start.row() == range.end.row(), - goal: SelectionGoal::ColumnRange { - start: start_column, - end: end_column, - }, - }); - state.stack.push(id); - break; + 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.clone()); + new_selections.push(selection); } - - new_selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap()); } else { - new_selections.extend( - selections - .into_iter() - .filter(|s| s.id != last_added_selection) - .cloned(), - ); + new_selections = selections; + new_selections.retain(|s| s.id != last_added_selection); state.stack.pop(); } @@ -1892,6 +1892,39 @@ impl BufferView { } } + 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, From 6f5e47d63111a0f21e830189a487479797d8e641 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 11 May 2021 12:00:35 +0200 Subject: [PATCH 08/11] Add test for `BufferView::cancel` --- zed/src/editor/buffer_view.rs | 74 +++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 9a496b6dee7cb2234e498d2f165de801e85aa6d8..ee36b73b36e5cfc972db5b3ed65023c1abd6ab3c 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2632,6 +2632,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| { From 17258d4f8cd398568a696f4dd986715aa838e9ff Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 11 May 2021 12:34:51 +0200 Subject: [PATCH 09/11] Add test for `add_selection_above` and `add_selection_below` --- zed/src/editor/buffer_view.rs | 161 ++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index ee36b73b36e5cfc972db5b3ed65023c1abd6ab3c..520dd1497253a606508c9a4911f8856371e3f05e 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -3756,6 +3756,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) From 07ef0d474e2595a470c1bd02983b4a5bf99d9268 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 11 May 2021 12:37:30 +0200 Subject: [PATCH 10/11] Bind `add_selection_{above,below}` to `cmd-alt-{up,down}` --- zed/src/editor/buffer_view.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 520dd1497253a606508c9a4911f8856371e3f05e..48b4e4ebc4ce34794bd472a5ec4f2b7e48fa8a6a 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -151,12 +151,12 @@ pub fn init(app: &mut MutableAppContext) { Some("BufferView"), ), Binding::new( - "ctrl-shift-up", + "cmd-alt-up", "buffer:add_selection_above", Some("BufferView"), ), Binding::new( - "ctrl-shift-down", + "cmd-alt-down", "buffer:add_selection_below", Some("BufferView"), ), From d9e7547d9569f1dbcf41599b684f5b0006f60266 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 11 May 2021 12:41:38 +0200 Subject: [PATCH 11/11] :lipstick: --- zed/src/editor/buffer_view.rs | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 48b4e4ebc4ce34794bd472a5ec4f2b7e48fa8a6a..f179d56d537d0af48b5eb3daa3f1b9fd924854d9 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1799,38 +1799,31 @@ impl BufferView { let app = ctx.as_ref(); let mut selections = self.selections(app).to_vec(); - let mut state = if let Some(state) = self.add_selections_state.take() { - state - } else { + 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(); - if range.start.row() == range.end.row() { - stack.push(oldest_selection.id); - selections.push(oldest_selection); - } else { - let columns = cmp::min(range.start.column(), range.end.column()) - ..cmp::max(range.start.column(), range.end.column()); - 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); - } + 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(); - } + if above { + stack.reverse(); } AddSelectionsState { above, stack } - }; + }); let last_added_selection = *state.stack.last().unwrap(); let mut new_selections = Vec::new();