Implement add_cursor_above and add_cursor_below for buffer
This commit is contained in:
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<u32>,
|
||||
pub goal: SelectionGoal,
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
|
||||
+159
-42
@@ -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<Self>) {
|
||||
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<Self>) {
|
||||
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<DisplayPoint>,
|
||||
|
||||
+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