From bf5c8c7327343cfcbcfb65a5b28ba6c25c89df58 Mon Sep 17 00:00:00 2001 From: Emi Date: Wed, 13 Dec 2023 23:22:01 +0100 Subject: [PATCH 1/7] implement another selection modifying command --- helix-term/src/commands.rs | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e557977adf88..d26563e7df26 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -298,6 +298,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_up, "Select current line, if already selected, extend or shrink line above based on the anchor", + select_line_down, "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", @@ -2413,7 +2415,64 @@ fn extend_line_below(cx: &mut Context) { fn extend_line_above(cx: &mut Context) { extend_line_impl(cx, Extend::Above); } +fn select_line_down(cx: &mut Context) { + select_line_impl(cx, Extend::Below); +} +fn select_line_up(cx: &mut Context) { + select_line_impl(cx, Extend::Above); +} +fn select_line_impl(cx: &mut Context, extend: Extend) { + let count = cx.count(); + let (view, doc) = current!(cx.editor); + let text = doc.text(); + 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( + (end_line + 1) // newline of end_line + .min(text.len_lines()), + ); + let direction = range.direction(); + // extend or shrink to previous/next line if current line is selected + // extending or shrinking depends on the current direction of the selection + let (anchor, head) = if range.from() == start && range.to() == end { + match extend { + Extend::Above => match direction { + Direction::Forward => { + (start, text.line_to_char(end_line.saturating_sub(count - 1))) + } + Direction::Backward => { + (end, text.line_to_char(start_line.saturating_sub(count))) + } + }, + Extend::Below => match direction { + Direction::Forward => ( + start, + text.line_to_char((end_line + count + 1).min(text.len_lines())), + ), + Direction::Backward => ( + end, + text.line_to_char((start_line + count).min(text.len_lines())), + ), + }, + } + } else { + match extend { + Extend::Above => (end, text.line_to_char(start_line.saturating_sub(count - 1))), + Extend::Below => ( + start, + text.line_to_char((end_line + count).min(text.len_lines())), + ), + } + }; + + Range::new(anchor, head) + }); + + doc.set_selection(view.id, selection); +} fn extend_line_impl(cx: &mut Context, extend: Extend) { let count = cx.count(); let (view, doc) = current!(cx.editor); From de56be2aed2d25b76201cf5f07560f478827346e Mon Sep 17 00:00:00 2001 From: Emi Date: Thu, 4 Jan 2024 13:55:13 +0100 Subject: [PATCH 2/7] Selection feels more ergonomic in case of swapping the direction. This also fixes a problem when starting at an empty line. --- helix-term/src/commands.rs | 60 ++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d26563e7df26..910c43a2ef4f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2394,6 +2394,7 @@ fn global_search(cx: &mut Context) { ); } +#[derive(Clone, Copy)] enum Extend { Above, Below, @@ -2422,52 +2423,55 @@ fn select_line_up(cx: &mut Context) { select_line_impl(cx, Extend::Above); } fn select_line_impl(cx: &mut Context, extend: Extend) { - let count = cx.count(); + let mut count = cx.count(); let (view, doc) = current!(cx.editor); let text = doc.text(); 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( (end_line + 1) // newline of end_line .min(text.len_lines()), ); let direction = range.direction(); - // extend or shrink to previous/next line if current line is selected - // extending or shrinking depends on the current direction of the selection - let (anchor, head) = if range.from() == start && range.to() == end { - match extend { - Extend::Above => match direction { - Direction::Forward => { - (start, text.line_to_char(end_line.saturating_sub(count - 1))) - } - Direction::Backward => { - (end, text.line_to_char(start_line.saturating_sub(count))) - } - }, - Extend::Below => match direction { - Direction::Forward => ( - start, - text.line_to_char((end_line + count + 1).min(text.len_lines())), - ), - Direction::Backward => ( - end, - text.line_to_char((start_line + count).min(text.len_lines())), - ), - }, + + // 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, (end_line + count).min(text.len_lines())) + } + (Extend::Below, Direction::Backward) => { + (end_line, (start_line + count).min(text.len_lines())) } + }; + let (anchor, head) = if anchor_line < head_line { + ( + text.line_to_char(anchor_line), + text.line_to_char((head_line + 1).min(text.len_lines())), + ) + } else if anchor_line > head_line { + ( + text.line_to_char((anchor_line + 1).min(text.len_lines())), + text.line_to_char(head_line), + ) } else { match extend { - Extend::Above => (end, text.line_to_char(start_line.saturating_sub(count - 1))), + Extend::Above => ( + text.line_to_char((anchor_line + 1).min(text.len_lines())), + text.line_to_char(head_line), + ), Extend::Below => ( - start, - text.line_to_char((end_line + count).min(text.len_lines())), + text.line_to_char(head_line), + text.line_to_char((anchor_line + 1).min(text.len_lines())), ), } }; - Range::new(anchor, head) }); From b1ebfa213e3180d81fa8950a4eda6bd9dab8f5f9 Mon Sep 17 00:00:00 2001 From: Emi Date: Thu, 4 Jan 2024 14:07:32 +0100 Subject: [PATCH 3/7] rename select_line_up/down to select_line_above/below --- helix-term/src/commands.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 910c43a2ef4f..68cc913e20a0 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -298,8 +298,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_up, "Select current line, if already selected, extend or shrink line above based on the anchor", - select_line_down, "Select current line, if already selected, extend or shrink line below based on the anchor", + 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", @@ -2416,10 +2416,10 @@ fn extend_line_below(cx: &mut Context) { fn extend_line_above(cx: &mut Context) { extend_line_impl(cx, Extend::Above); } -fn select_line_down(cx: &mut Context) { +fn select_line_below(cx: &mut Context) { select_line_impl(cx, Extend::Below); } -fn select_line_up(cx: &mut Context) { +fn select_line_above(cx: &mut Context) { select_line_impl(cx, Extend::Above); } fn select_line_impl(cx: &mut Context, extend: Extend) { From 2fa5f48036826767cc1a020d6375328644156934 Mon Sep 17 00:00:00 2001 From: Emi Date: Wed, 17 Jan 2024 20:49:10 +0100 Subject: [PATCH 4/7] apply clippy suggestion of using cmp instead of if-chain --- helix-term/src/commands.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 68cc913e20a0..ecdfe7913765 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -61,6 +61,7 @@ use crate::{ use crate::job::{self, Jobs}; use futures_util::{stream::FuturesUnordered, TryStreamExt}; use std::{ + cmp::Ordering, collections::{HashMap, HashSet}, fmt, future::Future, @@ -2450,18 +2451,12 @@ fn select_line_impl(cx: &mut Context, extend: Extend) { (end_line, (start_line + count).min(text.len_lines())) } }; - let (anchor, head) = if anchor_line < head_line { - ( + let (anchor, head) = match anchor_line.cmp(&head_line) { + Ordering::Less => ( text.line_to_char(anchor_line), text.line_to_char((head_line + 1).min(text.len_lines())), - ) - } else if anchor_line > head_line { - ( - text.line_to_char((anchor_line + 1).min(text.len_lines())), - text.line_to_char(head_line), - ) - } else { - match extend { + ), + Ordering::Equal => match extend { Extend::Above => ( text.line_to_char((anchor_line + 1).min(text.len_lines())), text.line_to_char(head_line), @@ -2470,7 +2465,12 @@ fn select_line_impl(cx: &mut Context, extend: Extend) { text.line_to_char(head_line), text.line_to_char((anchor_line + 1).min(text.len_lines())), ), - } + }, + + Ordering::Greater => ( + text.line_to_char((anchor_line + 1).min(text.len_lines())), + text.line_to_char(head_line), + ), }; Range::new(anchor, head) }); From 1b25525c2b0cbeaa16940a3eb20ff65e0d60f806 Mon Sep 17 00:00:00 2001 From: Emi Date: Sun, 11 Feb 2024 13:54:38 +0100 Subject: [PATCH 5/7] revert `Extent` implementing `Clone/Copy` --- helix-term/src/commands.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ecdfe7913765..a51a673f6048 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2395,7 +2395,6 @@ fn global_search(cx: &mut Context) { ); } -#[derive(Clone, Copy)] enum Extend { Above, Below, @@ -2441,7 +2440,7 @@ fn select_line_impl(cx: &mut Context, extend: Extend) { if range.from() != start || range.to() != end { count = count.saturating_sub(1) } - let (anchor_line, head_line) = match (extend, direction) { + 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) => { From 5ac8e05563e5e9519a43dbdb9acc0555e2a46363 Mon Sep 17 00:00:00 2001 From: Emi Date: Sun, 11 Feb 2024 13:56:12 +0100 Subject: [PATCH 6/7] move select_line functions below extend_line implementations --- helix-term/src/commands.rs | 76 +++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a51a673f6048..cf85624418de 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2416,6 +2416,44 @@ 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); + + let text = doc.text(); + 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( + (end_line + 1) // newline of end_line + .min(text.len_lines()), + ); + + // extend to previous/next line if current line is selected + let (anchor, head) = if range.from() == start && range.to() == end { + match extend { + Extend::Above => (end, text.line_to_char(start_line.saturating_sub(count))), + Extend::Below => ( + start, + text.line_to_char((end_line + count + 1).min(text.len_lines())), + ), + } + } else { + match extend { + Extend::Above => (end, text.line_to_char(start_line.saturating_sub(count - 1))), + Extend::Below => ( + start, + text.line_to_char((end_line + count).min(text.len_lines())), + ), + } + }; + + Range::new(anchor, head) + }); + + doc.set_selection(view.id, selection); +} fn select_line_below(cx: &mut Context) { select_line_impl(cx, Extend::Below); } @@ -2476,44 +2514,6 @@ fn select_line_impl(cx: &mut Context, extend: Extend) { doc.set_selection(view.id, selection); } -fn extend_line_impl(cx: &mut Context, extend: Extend) { - let count = cx.count(); - let (view, doc) = current!(cx.editor); - - let text = doc.text(); - 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( - (end_line + 1) // newline of end_line - .min(text.len_lines()), - ); - - // extend to previous/next line if current line is selected - let (anchor, head) = if range.from() == start && range.to() == end { - match extend { - Extend::Above => (end, text.line_to_char(start_line.saturating_sub(count))), - Extend::Below => ( - start, - text.line_to_char((end_line + count + 1).min(text.len_lines())), - ), - } - } else { - match extend { - Extend::Above => (end, text.line_to_char(start_line.saturating_sub(count - 1))), - Extend::Below => ( - start, - text.line_to_char((end_line + count).min(text.len_lines())), - ), - } - }; - - Range::new(anchor, head) - }); - - doc.set_selection(view.id, selection); -} fn extend_to_line_bounds(cx: &mut Context) { let (view, doc) = current!(cx.editor); From 45b53edf9ba7d2e43210c75bb57a7421c36c03b5 Mon Sep 17 00:00:00 2001 From: Emi Date: Sun, 11 Feb 2024 14:02:36 +0100 Subject: [PATCH 7/7] implement help add function, which saturates at the number of text lines --- helix-term/src/commands.rs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index cf85624418de..2c4e13ce6a3c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2463,15 +2463,12 @@ fn select_line_above(cx: &mut Context) { 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( - (end_line + 1) // newline of end_line - .min(text.len_lines()), - ); + let end = text.line_to_char(saturating_add(end_line, 1)); let direction = range.direction(); // Extending to line bounds is counted as one step @@ -2481,31 +2478,27 @@ fn select_line_impl(cx: &mut Context, extend: Extend) { 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, (end_line + count).min(text.len_lines())) - } - (Extend::Below, Direction::Backward) => { - (end_line, (start_line + count).min(text.len_lines())) - } + (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((head_line + 1).min(text.len_lines())), + text.line_to_char(saturating_add(head_line, 1)), ), Ordering::Equal => match extend { Extend::Above => ( - text.line_to_char((anchor_line + 1).min(text.len_lines())), + 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((anchor_line + 1).min(text.len_lines())), + text.line_to_char(saturating_add(anchor_line, 1)), ), }, Ordering::Greater => ( - text.line_to_char((anchor_line + 1).min(text.len_lines())), + text.line_to_char(saturating_add(anchor_line, 1)), text.line_to_char(head_line), ), };