Repository: helix-editor/helix
File: helix-term/src/commands.rs (Rust)

Code before:
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 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();

        // 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) = 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())),
            ),
            Ordering::Equal => match extend {
                Extend::Above => (
                    text.line_to_char((anchor_line + 1).min(text.len_lines())),
                    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())),
                ),
            },

            Ordering::Greater => (
                text.line_to_char((anchor_line + 1).min(text.len_lines())),
                text.line_to_char(head_line),
            ),

Review comment (refactor):
The `(x + y).min(text.len_lines())` gets a little repetitive here, I think it's a nice opportunity for a little helper function:

```rust
let saturating_add = |a: usize, b: usize| (a + b).min(text.len_lines());
```

Code after:
    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);
}
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();