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);