Keybind for Extend/shrink selection up and down (#9080)

* implement another selection modifying command

* Selection feels more ergonomic in case of swapping the direction. This also fixes a problem when starting at an empty line.

* rename select_line_up/down to select_line_above/below

* apply clippy suggestion of using cmp instead of if-chain

* revert `Extent` implementing `Clone/Copy`

* move select_line functions below extend_line implementations

* implement help add function, which saturates at the number of text lines

---------

Co-authored-by: Emi <emanuel.boehm@gmail.com>
This commit is contained in:
Emi 2024-03-17 23:06:24 +01:00 committed by GitHub
parent 6fea7876a4
commit 761df60077
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -57,6 +57,7 @@ use crate::{
use crate::job::{self, Jobs};
use std::{
cmp::Ordering,
collections::{HashMap, HashSet},
fmt,
future::Future,
@ -300,6 +301,8 @@ impl MappableCommand {
extend_line, "Select current line, if already selected, extend to another line based on the anchor",
extend_line_below, "Select current line, if already selected, extend to next line",
extend_line_above, "Select current line, if already selected, extend to previous line",
select_line_above, "Select current line, if already selected, extend or shrink line above based on the anchor",
select_line_below, "Select current line, if already selected, extend or shrink line below based on the anchor",
extend_to_line_bounds, "Extend selection to line bounds",
shrink_to_line_bounds, "Shrink selection to line bounds",
delete_selection, "Delete selection",
@ -2435,7 +2438,6 @@ fn extend_line_below(cx: &mut Context) {
fn extend_line_above(cx: &mut Context) {
extend_line_impl(cx, Extend::Above);
}
fn extend_line_impl(cx: &mut Context, extend: Extend) {
let count = cx.count();
let (view, doc) = current!(cx.editor);
@ -2474,6 +2476,59 @@ fn extend_line_impl(cx: &mut Context, extend: Extend) {
doc.set_selection(view.id, selection);
}
fn select_line_below(cx: &mut Context) {
select_line_impl(cx, Extend::Below);
}
fn select_line_above(cx: &mut Context) {
select_line_impl(cx, Extend::Above);
}
fn select_line_impl(cx: &mut Context, extend: Extend) {
let mut count = cx.count();
let (view, doc) = current!(cx.editor);
let text = doc.text();
let saturating_add = |a: usize, b: usize| (a + b).min(text.len_lines());
let selection = doc.selection(view.id).clone().transform(|range| {
let (start_line, end_line) = range.line_range(text.slice(..));
let start = text.line_to_char(start_line);
let end = text.line_to_char(saturating_add(end_line, 1));
let direction = range.direction();
// Extending to line bounds is counted as one step
if range.from() != start || range.to() != end {
count = count.saturating_sub(1)
}
let (anchor_line, head_line) = match (&extend, direction) {
(Extend::Above, Direction::Forward) => (start_line, end_line.saturating_sub(count)),
(Extend::Above, Direction::Backward) => (end_line, start_line.saturating_sub(count)),
(Extend::Below, Direction::Forward) => (start_line, saturating_add(end_line, count)),
(Extend::Below, Direction::Backward) => (end_line, saturating_add(start_line, count)),
};
let (anchor, head) = match anchor_line.cmp(&head_line) {
Ordering::Less => (
text.line_to_char(anchor_line),
text.line_to_char(saturating_add(head_line, 1)),
),
Ordering::Equal => match extend {
Extend::Above => (
text.line_to_char(saturating_add(anchor_line, 1)),
text.line_to_char(head_line),
),
Extend::Below => (
text.line_to_char(head_line),
text.line_to_char(saturating_add(anchor_line, 1)),
),
},
Ordering::Greater => (
text.line_to_char(saturating_add(anchor_line, 1)),
text.line_to_char(head_line),
),
};
Range::new(anchor, head)
});
doc.set_selection(view.id, selection);
}
fn extend_to_line_bounds(cx: &mut Context) {
let (view, doc) = current!(cx.editor);