Merge pull request #51 from zed-industries/add-cursors-and-cancel-selections
Implement `add_selection_above`, `add_selection_below` and `cancel`
This commit is contained in:
@@ -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<Item = Range<usize>>,
|
||||
{
|
||||
static NEXT_SELECTION_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
let mut ranges = ranges.into_iter().collect::<Vec<_>>();
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<u32>,
|
||||
pub goal: SelectionGoal,
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
|
||||
+489
-57
@@ -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<Selection>,
|
||||
next_selection_id: usize,
|
||||
add_selections_state: Option<AddSelectionsState>,
|
||||
scroll_position: Mutex<Vector2F>,
|
||||
autoscroll_requested: Mutex<bool>,
|
||||
settings: watch::Receiver<Settings>,
|
||||
@@ -282,6 +304,11 @@ pub struct BufferView {
|
||||
single_line: bool,
|
||||
}
|
||||
|
||||
struct AddSelectionsState {
|
||||
above: bool,
|
||||
stack: Vec<usize>,
|
||||
}
|
||||
|
||||
#[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<Self>) {
|
||||
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<I, T>(&mut self, ranges: I, autoscroll: bool, ctx: &mut ViewContext<Self>)
|
||||
where
|
||||
I: IntoIterator<Item = Range<T>>,
|
||||
@@ -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<Self>) {
|
||||
let mut offset_ranges = SmallVec::<[Range<usize>; 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<Self>) {
|
||||
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>) {
|
||||
self.add_selection(true, ctx);
|
||||
}
|
||||
|
||||
pub fn add_selection_below(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
self.add_selection(false, ctx);
|
||||
}
|
||||
|
||||
pub fn add_selection(&mut self, above: bool, ctx: &mut ViewContext<Self>) {
|
||||
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<u32>,
|
||||
reversed: bool,
|
||||
ctx: &AppContext,
|
||||
) -> Option<Selection> {
|
||||
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<DisplayPoint>,
|
||||
@@ -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<Self>) {
|
||||
@@ -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<Range<DisplayPoint>> {
|
||||
self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app)
|
||||
|
||||
+19
-11
@@ -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<u32>,
|
||||
goal: SelectionGoal,
|
||||
app: &AppContext,
|
||||
) -> Result<(DisplayPoint, Option<u32>)> {
|
||||
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<u32>,
|
||||
goal: SelectionGoal,
|
||||
app: &AppContext,
|
||||
) -> Result<(DisplayPoint, Option<u32>)> {
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user