diff --git a/CHANGELOG.md b/CHANGELOG.md index 67b5a1df..0299d3b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ the Wayland window. * **title** option to `footrc`, that sets the initial window title. * `--title` command line option, that sets the initial window title. +* Right mouse button extends the current selection. ### Changed diff --git a/README.md b/README.md index 8f5cc079..18dfc559 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,9 @@ These are the default shortcuts. See `man 5 foot` and the example middle : Paste from _primary_ selection +right +: Extend current selection + wheel : Scroll up/down in history diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 7c2dbccd..551b6a33 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -194,6 +194,9 @@ Note that these are just the defaults; they can be changed in the *middle* Paste from the _primary_ selection +*right* + Extend current selection + *wheel* Scroll up/down in history diff --git a/input.c b/input.c index 4fff710f..22cefa1e 100644 --- a/input.c +++ b/input.c @@ -1174,7 +1174,9 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, switch (state) { case WL_POINTER_BUTTON_STATE_PRESSED: { - if (button == BTN_LEFT) { + if (button == BTN_LEFT && wayl->mouse.count <= 3) { + selection_cancel(term); + switch (wayl->mouse.count) { case 1: selection_start( @@ -1193,6 +1195,10 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, } } + else if (button == BTN_RIGHT && wayl->mouse.count == 1) { + selection_extend(term, wayl->mouse.col, wayl->mouse.row, serial); + } + else { for (size_t i = 0; i < ALEN(wayl->conf->bindings.mouse); i++) { const struct mouse_binding *binding = @@ -1211,7 +1217,6 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, execute_binding(term, binding->action, serial); break; } - selection_cancel(term); } term_mouse_down(term, button, wayl->mouse.row, wayl->mouse.col); @@ -1219,9 +1224,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, } case WL_POINTER_BUTTON_STATE_RELEASED: - if (button != BTN_LEFT || term->selection.end.col == -1) - selection_cancel(term); - else + if (button == BTN_LEFT && term->selection.end.col != -1) selection_finalize(term, serial); term_mouse_up(term, button, wayl->mouse.row, wayl->mouse.col); diff --git a/selection.c b/selection.c index 189119d9..534a0888 100644 --- a/selection.c +++ b/selection.c @@ -335,6 +335,32 @@ mark_selected(struct terminal *term, struct row *row, struct cell *cell, cell->attrs.clean = 0; } +static void +selection_modify(struct terminal *term, struct coord start, struct coord end) +{ + assert(selection_enabled(term)); + assert(term->selection.start.row != -1); + assert(start.row != -1 && start.col != -1); + assert(end.row != -1 && end.col != -1); + + /* Premark all cells that *will* be selected */ + foreach_selected(term, start, end, &premark_selected, NULL); + + if (term->selection.end.row != -1) { + /* Unmark previous selection, ignoring cells that are part of + * the new selection */ + foreach_selected(term, term->selection.start, term->selection.end, + &unmark_selected, NULL); + } + + term->selection.start = start; + term->selection.end = end; + + /* Mark new selection */ + foreach_selected(term, start, end, &mark_selected, NULL); + render_refresh(term); +} + void selection_update(struct terminal *term, int col, int row) { @@ -350,26 +376,159 @@ selection_update(struct terminal *term, int col, int row) assert(term->grid->view + row != -1); struct coord new_end = {col, term->grid->view + row}; + selection_modify(term, term->selection.start, new_end); +} - /* Premark all cells that *will* be selected */ - foreach_selected( - term, term->selection.start, new_end, &premark_selected, NULL); +static void +selection_extend_normal(struct terminal *term, int col, int row, uint32_t serial) +{ + const struct coord *start = &term->selection.start; + const struct coord *end = &term->selection.end; - if (term->selection.end.row != -1) { - /* Unmark previous selection, ignoring cells that are part of - * the new selection */ - foreach_selected(term, term->selection.start, term->selection.end, - &unmark_selected, NULL); + if (start->row > end->row || + (start->row == end->row && start->col > end->col)) + { + const struct coord *tmp = start; + start = end; + end = tmp; } - term->selection.end = new_end; - assert(term->selection.start.row != -1 && term->selection.end.row != -1); + assert(start->row < end->row || start->col < end->col); - /* Mark new selection */ - foreach_selected( - term, term->selection.start, term->selection.end, &mark_selected, NULL); + struct coord new_start, new_end; - render_refresh(term); + if (row < start->row || (row == start->row && col < start->col)) { + /* Extend selection to start *before* current start */ + new_start = (struct coord){col, row}; + new_end = *end; + } + + else if (row > end->row || (row == end->row && col > end->col)) { + /* Extend selection to end *after* current end */ + new_start = *start; + new_end = (struct coord){col, row}; + } + + else { + /* Shrink selection from start or end, depending on which one is closest */ + + const int linear = row * term->cols + col; + + if (abs(linear - (start->row * term->cols + start->col)) < + abs(linear - (end->row * term->cols + end->col))) + { + /* Move start point */ + new_start = (struct coord){col, row}; + new_end = *end; + } + + else { + /* Move end point */ + new_start = *start; + new_end = (struct coord){col, row}; + } + } + + selection_modify(term, new_start, new_end); +} + +static void +selection_extend_block(struct terminal *term, int col, int row, uint32_t serial) +{ + const struct coord *start = &term->selection.start; + const struct coord *end = &term->selection.end; + + struct coord top_left = { + .row = min(start->row, end->row), + .col = min(start->col, end->col), + }; + + struct coord top_right = { + .row = min(start->row, end->row), + .col = max(start->col, end->col), + }; + + struct coord bottom_left = { + .row = max(start->row, end->row), + .col = min(start->col, end->col), + }; + + struct coord bottom_right = { + .row = max(start->row, end->row), + .col = max(start->col, end->col), + }; + + struct coord new_start; + struct coord new_end; + + if (row <= top_left.row || + abs(row - top_left.row) < abs(row - bottom_left.row)) + { + /* Move one of the top corners */ + + if (abs(col - top_left.col) < abs(col - top_right.col)) { + new_start = (struct coord){col, row}; + new_end = bottom_right; + } + + else { + new_start = (struct coord){col, row}; + new_end = bottom_left; + } + } + + else { + /* Move one of the bottom corners */ + + if (abs(col - bottom_left.col) < abs(col - bottom_right.col)) { + new_start = top_right; + new_end = (struct coord){col, row}; + } + + else { + new_start = top_left; + new_end = (struct coord){col, row}; + } + } + + selection_modify(term, new_start, new_end); +} + +void +selection_extend(struct terminal *term, int col, int row, uint32_t serial) +{ + if (!selection_enabled(term)) + return; + + if (term->selection.start.row == -1 || term->selection.end.row == -1) { + /* No existing selection */ + return; + } + + row += term->grid->view; + + if ((row == term->selection.start.row && col == term->selection.start.col) || + (row == term->selection.end.row && col == term->selection.end.col)) + { + /* Extension point *is* one of the current end points */ + return; + } + + switch (term->selection.kind) { + case SELECTION_NONE: + assert(false); + return; + + case SELECTION_NORMAL: + selection_extend_normal(term, col, row, serial); + break; + + case SELECTION_BLOCK: + selection_extend_block(term, col, row, serial); + break; + } + + selection_to_primary(term, serial); } static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener; diff --git a/selection.h b/selection.h index bc690eb1..5bc216e1 100644 --- a/selection.h +++ b/selection.h @@ -14,6 +14,7 @@ void selection_start( void selection_update(struct terminal *term, int col, int row); void selection_finalize(struct terminal *term, uint32_t serial); void selection_cancel(struct terminal *term); +void selection_extend(struct terminal *term, int col, int row, uint32_t serial); bool selection_on_row_in_view(const struct terminal *term, int row_no);