From 7e6c2cb23868fbfec11adacdc5da7e670a9b8bdb Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 24 Nov 2016 13:38:44 +0000 Subject: [PATCH 1/3] Make the selection able to exist independent of the cursor position, so that it is not affected by scrolling. If MouseDragEnd1Pane is bound to the new "stop-selection" command: bind -Tcopy-mode MouseDragEnd1Pane stop-selection A selection made with the mouse will stay as it is after button 1 is released. (It also works bound to a key.) From Artem Fokin. --- screen.c | 16 ++++- tmux.1 | 1 + tmux.h | 3 + window-copy.c | 192 +++++++++++++++++++++++++++++++++++++------------- 4 files changed, 162 insertions(+), 50 deletions(-) diff --git a/screen.c b/screen.c index bf342c45..d6fbfecd 100644 --- a/screen.c +++ b/screen.c @@ -258,6 +258,8 @@ screen_set_selection(struct screen *s, u_int sx, u_int sy, memcpy(&sel->cell, gc, sizeof sel->cell); sel->flag = 1; + sel->hidden = 0; + sel->rectflag = rectflag; sel->sx = sx; sel->sy = sy; @@ -271,9 +273,19 @@ screen_clear_selection(struct screen *s) struct screen_sel *sel = &s->sel; sel->flag = 0; + sel->hidden = 0; sel->lineflag = LINE_SEL_NONE; } +/* Hide selection. */ +void +screen_hide_selection(struct screen *s) +{ + struct screen_sel *sel = &s->sel; + + sel->hidden = 1; +} + /* Check if cell in selection. */ int screen_check_selection(struct screen *s, u_int px, u_int py) @@ -281,7 +293,7 @@ screen_check_selection(struct screen *s, u_int px, u_int py) struct screen_sel *sel = &s->sel; u_int xx; - if (!sel->flag) + if (!sel->flag || sel->hidden) return (0); if (sel->rectflag) { @@ -377,7 +389,7 @@ void screen_select_cell(struct screen *s, struct grid_cell *dst, const struct grid_cell *src) { - if (!s->sel.flag) + if (!s->sel.flag || s->sel.hidden) return; memcpy(dst, &s->sel.cell, sizeof *dst); diff --git a/tmux.1 b/tmux.1 index b474b578..69f0e167 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1072,6 +1072,7 @@ The following commands are supported in copy mode: .It Li "search-reverse" Ta "N" Ta "N" .It Li "select-line" Ta "V" Ta "" .It Li "start-of-line" Ta "0" Ta "C-a" +.It Li "stop-selection" Ta "" Ta "" .It Li "top-line" Ta "H" Ta "M-R" .El .Pp diff --git a/tmux.h b/tmux.h index 47be55b5..ff5fb184 100644 --- a/tmux.h +++ b/tmux.h @@ -681,6 +681,8 @@ LIST_HEAD(joblist, job); /* Screen selection. */ struct screen_sel { int flag; + int hidden; + int rectflag; enum { LINE_SEL_NONE, @@ -2055,6 +2057,7 @@ void screen_resize(struct screen *, u_int, u_int, int); void screen_set_selection(struct screen *, u_int, u_int, u_int, u_int, u_int, struct grid_cell *); void screen_clear_selection(struct screen *); +void screen_hide_selection(struct screen *); int screen_check_selection(struct screen *, u_int, u_int); void screen_select_cell(struct screen *, struct grid_cell *, const struct grid_cell *); diff --git a/window-copy.c b/window-copy.c index 0d83db01..90520c51 100644 --- a/window-copy.c +++ b/window-copy.c @@ -62,7 +62,10 @@ static void window_copy_search_down(struct window_pane *, const char *, static void window_copy_goto_line(struct window_pane *, const char *); static void window_copy_update_cursor(struct window_pane *, u_int, u_int); static void window_copy_start_selection(struct window_pane *); +static int window_copy_adjust_selection(struct window_pane *, u_int *, + u_int *); static int window_copy_update_selection(struct window_pane *, int); +static void window_copy_synchronize_cursor(struct window_pane *wp); static void *window_copy_get_selection(struct window_pane *, size_t *); static void window_copy_copy_buffer(struct window_pane *, const char *, void *, size_t); @@ -119,21 +122,25 @@ enum { WINDOW_COPY_JUMPTOBACKWARD, }; +enum { + WINDOW_COPY_REL_POS_ABOVE, + WINDOW_COPY_REL_POS_ON_SCREEN, + WINDOW_COPY_REL_POS_BELOW, +}; + /* - * Copy-mode's visible screen (the "screen" field) is filled from one of - * two sources: the original contents of the pane (used when we - * actually enter via the "copy-mode" command, to copy the contents of - * the current pane), or else a series of lines containing the output - * from an output-writing tmux command (such as any of the "show-*" or - * "list-*" commands). + * Copy mode's visible screen (the "screen" field) is filled from one of two + * sources: the original contents of the pane (used when we actually enter via + * the "copy-mode" command, to copy the contents of the current pane), or else + * a series of lines containing the output from an output-writing tmux command + * (such as any of the "show-*" or "list-*" commands). * - * In either case, the full content of the copy-mode grid is pointed at - * by the "backing" field, and is copied into "screen" as needed (that - * is, when scrolling occurs). When copy-mode is backed by a pane, - * backing points directly at that pane's screen structure (&wp->base); - * when backed by a list of output-lines from a command, it points at - * a newly-allocated screen structure (which is deallocated when the - * mode ends). + * In either case, the full content of the copy-mode grid is pointed at by the + * "backing" field, and is copied into "screen" as needed (that is, when + * scrolling occurs). When copy-mode is backed by a pane, backing points + * directly at that pane's screen structure (&wp->base); when backed by a list + * of output-lines from a command, it points at a newly-allocated screen + * structure (which is deallocated when the mode ends). */ struct window_copy_mode_data { struct screen screen; @@ -141,11 +148,20 @@ struct window_copy_mode_data { struct screen *backing; int backing_written; /* backing display started */ - u_int oy; + u_int oy; /* number of lines scrolled up */ - u_int selx; + u_int selx; /* beginning of selection */ u_int sely; + u_int endselx; /* end of selection */ + u_int endsely; + + enum { + CURSORDRAG_NONE, /* selection is independent of cursor */ + CURSORDRAG_ENDSEL, /* end is synchronized with cursor */ + CURSORDRAG_SEL, /* start is synchronized with cursor */ + } cursordrag; + int rectflag; /* in rectangle copy mode? */ int scroll_exit; /* exit on scroll to end? */ @@ -173,6 +189,8 @@ window_copy_init(struct window_pane *wp) data->cx = 0; data->cy = 0; + data->cursordrag = CURSORDRAG_NONE; + data->lastcx = 0; data->lastsx = 0; @@ -358,7 +376,7 @@ window_copy_pagedown(struct window_pane *wp, int half_page) { struct window_copy_mode_data *data = wp->modedata; struct screen *s = &data->screen; - u_int n, ox, oy; + u_int n, ox, oy, px, py; oy = screen_hsize(data->backing) + data->cy - data->oy; ox = window_copy_find_length(wp, oy); @@ -386,9 +404,10 @@ window_copy_pagedown(struct window_pane *wp, int half_page) data->oy -= n; if (!data->screen.sel.flag || !data->rectflag) { - u_int py = screen_hsize(data->backing) + data->cy - data->oy; - u_int px = window_copy_find_length(wp, py); - if ((data->cx >= data->lastsx && data->cx != px) || data->cx > px) + py = screen_hsize(data->backing) + data->cy - data->oy; + px = window_copy_find_length(wp, py); + if ((data->cx >= data->lastsx && data->cx != px) || + data->cx > px) window_copy_cursor_end_of_line(wp); } @@ -514,6 +533,8 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s, window_copy_redraw_screen(wp); } } + if (strcmp(command, "stop-selection") == 0) + data->cursordrag = CURSORDRAG_NONE; if (strcmp(command, "bottom-line") == 0) { data->cx = 0; data->cy = screen_size_y(sn) - 1; @@ -1150,6 +1171,29 @@ window_copy_redraw_screen(struct window_pane *wp) window_copy_redraw_lines(wp, 0, screen_size_y(&data->screen)); } +static void +window_copy_synchronize_cursor(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + u_int xx, yy; + + xx = data->cx; + yy = screen_hsize(data->backing) + data->cy - data->oy; + + switch (data->cursordrag) { + case CURSORDRAG_ENDSEL: + data->endselx = xx; + data->endsely = yy; + break; + case CURSORDRAG_SEL: + data->selx = xx; + data->sely = yy; + break; + case CURSORDRAG_NONE: + break; + } +} + static void window_copy_update_cursor(struct window_pane *wp, u_int cx, u_int cy) { @@ -1180,10 +1224,47 @@ window_copy_start_selection(struct window_pane *wp) data->selx = data->cx; data->sely = screen_hsize(data->backing) + data->cy - data->oy; + data->endselx = data->selx; + data->endsely = data->sely; + + data->cursordrag = CURSORDRAG_ENDSEL; + s->sel.flag = 1; window_copy_update_selection(wp, 1); } +static int +window_copy_adjust_selection(struct window_pane *wp, u_int *selx, u_int *sely) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + u_int sx, sy, ty; + int relpos; + + sx = *selx; + sy = *sely; + + ty = screen_hsize(data->backing) - data->oy; + if (sy < ty) { + relpos = WINDOW_COPY_REL_POS_ABOVE; + if (!data->rectflag) + sx = 0; + sy = 0; + } else if (sy > ty + screen_size_y(s) - 1) { + relpos = WINDOW_COPY_REL_POS_BELOW; + if (!data->rectflag) + sx = screen_size_x(s) - 1; + sy = screen_size_y(s) - 1; + } else { + relpos = WINDOW_COPY_REL_POS_ON_SCREEN; + sy -= ty; + } + + *selx = sx; + *sely = screen_hsize(s) + sy; + return (relpos); +} + static int window_copy_update_selection(struct window_pane *wp, int may_redraw) { @@ -1191,34 +1272,34 @@ window_copy_update_selection(struct window_pane *wp, int may_redraw) struct screen *s = &data->screen; struct options *oo = wp->window->options; struct grid_cell gc; - u_int sx, sy, ty, cy; + u_int sx, sy, cy, endsx, endsy; + int startrelpos, endrelpos; if (!s->sel.flag && s->sel.lineflag == LINE_SEL_NONE) return (0); - /* Set colours. */ - style_apply(&gc, oo, "mode-style"); - - /* Find top of screen. */ - ty = screen_hsize(data->backing) - data->oy; + window_copy_synchronize_cursor(wp); /* Adjust the selection. */ sx = data->selx; sy = data->sely; - if (sy < ty) { /* above screen */ - if (!data->rectflag) - sx = 0; - sy = 0; - } else if (sy > ty + screen_size_y(s) - 1) { /* below screen */ - if (!data->rectflag) - sx = screen_size_x(s) - 1; - sy = screen_size_y(s) - 1; - } else - sy -= ty; - sy = screen_hsize(s) + sy; + startrelpos = window_copy_adjust_selection(wp, &sx, &sy); - screen_set_selection(s, - sx, sy, data->cx, screen_hsize(s) + data->cy, data->rectflag, &gc); + /* Adjust the end of selection. */ + endsx = data->endselx; + endsy = data->endsely; + endrelpos = window_copy_adjust_selection(wp, &endsx, &endsy); + + /* Selection is outside of the current screen */ + if (startrelpos == endrelpos && + startrelpos != WINDOW_COPY_REL_POS_ON_SCREEN) { + screen_hide_selection(s); + return (0); + } + + /* Set colours and selection. */ + style_apply(&gc, oo, "mode-style"); + screen_set_selection(s, sx, sy, endsx, endsy, data->rectflag, &gc); if (data->rectflag && may_redraw) { /* @@ -1261,8 +1342,8 @@ window_copy_get_selection(struct window_pane *wp, size_t *len) */ /* Find start and end. */ - xx = data->cx; - yy = screen_hsize(data->backing) + data->cy - data->oy; + xx = data->endselx; + yy = data->endsely; if (yy < data->sely || (yy == data->sely && xx < data->selx)) { sx = xx; sy = yy; ex = data->selx; ey = data->sely; @@ -1500,6 +1581,8 @@ window_copy_clear_selection(struct window_pane *wp) screen_clear_selection(&data->screen); + data->cursordrag = CURSORDRAG_NONE; + py = screen_hsize(data->backing) + data->cy - data->oy; px = window_copy_find_length(wp, py); if (data->cx > px) @@ -1630,7 +1713,7 @@ window_copy_other_end(struct window_pane *wp) { struct window_copy_mode_data *data = wp->modedata; struct screen *s = &data->screen; - u_int selx, sely, cx, cy, yy, hsize; + u_int selx, sely, cy, yy, hsize; if (!s->sel.flag && s->sel.lineflag == LINE_SEL_NONE) return; @@ -1640,26 +1723,39 @@ window_copy_other_end(struct window_pane *wp) else if (s->sel.lineflag == LINE_SEL_RIGHT_LEFT) s->sel.lineflag = LINE_SEL_LEFT_RIGHT; - selx = data->selx; - sely = data->sely; - cx = data->cx; + switch (data->cursordrag) { + case CURSORDRAG_NONE: + case CURSORDRAG_SEL: + data->cursordrag = CURSORDRAG_ENDSEL; + break; + case CURSORDRAG_ENDSEL: + data->cursordrag = CURSORDRAG_SEL; + break; + } + + selx = data->endselx; + sely = data->endsely; + if (data->cursordrag == CURSORDRAG_SEL) { + selx = data->selx; + sely = data->sely; + } + cy = data->cy; yy = screen_hsize(data->backing) + data->cy - data->oy; - data->selx = cx; - data->sely = yy; data->cx = selx; hsize = screen_hsize(data->backing); - if (sely < hsize - data->oy) { + if (sely < hsize - data->oy) { /* above */ data->oy = hsize - sely; data->cy = 0; - } else if (sely > hsize - data->oy + screen_size_y(s)) { + } else if (sely > hsize - data->oy + screen_size_y(s)) { /* below */ data->oy = hsize - sely + screen_size_y(s) - 1; data->cy = screen_size_y(s) - 1; } else data->cy = cy + sely - yy; + window_copy_update_selection(wp, 1); window_copy_redraw_screen(wp); } From 0d1be2e32838cfb4f4b528fc3f94ef850b47eda7 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 24 Nov 2016 13:46:50 +0000 Subject: [PATCH 2/3] Fix so that we work out the right pane from mouse events - we were doing so too early, before the mouse event was necessarily valid, so could end up using the pane from the previous mouse event, or the active pane. It is important that we use the right pane now that different panes can have different key tables (for copy mode). Fixes problem reported by Greg Hurrell. --- key-bindings.c | 22 ++++++++++++---------- server-client.c | 21 ++++++++++++++------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/key-bindings.c b/key-bindings.c index 9bc61b62..e8eedca5 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -259,12 +259,13 @@ key_bindings_init(void) "bind -Tcopy-mode n send -X search-again", "bind -Tcopy-mode q send -X cancel", "bind -Tcopy-mode t command-prompt -1p'jump to forward' \"send -X jump-to-forward \\\"%%%\\\"\"", - "bind -Tcopy-mode MouseDrag1Pane send -X begin-selection", + "bind -Tcopy-mode MouseDown1Pane select-pane", + "bind -Tcopy-mode MouseDrag1Pane select-pane\\; send -X begin-selection", "bind -Tcopy-mode MouseDragEnd1Pane send -X copy-selection-and-cancel", - "bind -Tcopy-mode WheelUpPane send -N5 -X scroll-up", - "bind -Tcopy-mode WheelDownPane send -N5 -X scroll-down", - "bind -Tcopy-mode DoubleClick1Pane send -X select-word", - "bind -Tcopy-mode TripleClick1Pane send -X select-line", + "bind -Tcopy-mode WheelUpPane select-pane\\; send -N5 -X scroll-up", + "bind -Tcopy-mode WheelDownPane select-pane\\; send -N5 -X scroll-down", + "bind -Tcopy-mode DoubleClick1Pane select-pane\\; send -X select-word", + "bind -Tcopy-mode TripleClick1Pane select-pane\\; send -X select-line", "bind -Tcopy-mode NPage send -X page-down", "bind -Tcopy-mode PPage send -X page-up", "bind -Tcopy-mode Up send -X cursor-up", @@ -356,12 +357,13 @@ key_bindings_init(void) "bind -Tcopy-mode-vi w send -X next-word", "bind -Tcopy-mode-vi { send -X previous-paragraph", "bind -Tcopy-mode-vi } send -X next-paragraph", - "bind -Tcopy-mode-vi MouseDrag1Pane send -X begin-selection", + "bind -Tcopy-mode-vi MouseDown1Pane select-pane", + "bind -Tcopy-mode-vi MouseDrag1Pane select-pane\\; send -X begin-selection", "bind -Tcopy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel", - "bind -Tcopy-mode-vi WheelUpPane send -N5 -X scroll-up", - "bind -Tcopy-mode-vi WheelDownPane send -N5 -X scroll-down", - "bind -Tcopy-mode-vi DoubleClick1Pane send -X select-word", - "bind -Tcopy-mode-vi TripleClick1Pane send -X select-line", + "bind -Tcopy-mode-vi WheelUpPane select-pane\\; send -N5 -X scroll-up", + "bind -Tcopy-mode-vi WheelDownPane select-pane\\; send -N5 -X scroll-down", + "bind -Tcopy-mode-vi DoubleClick1Pane select-pane\\; send -X select-word", + "bind -Tcopy-mode-vi TripleClick1Pane select-pane\\; send -X select-line", "bind -Tcopy-mode-vi BSpace send -X cursor-left", "bind -Tcopy-mode-vi NPage send -X page-down", "bind -Tcopy-mode-vi PPage send -X page-up", diff --git a/server-client.c b/server-client.c index fbe14e82..dd2a274a 100644 --- a/server-client.c +++ b/server-client.c @@ -698,10 +698,6 @@ server_client_handle_key(struct client *c, key_code key) if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) return; w = s->curw->window; - if (KEYC_IS_MOUSE(key)) - wp = cmd_mouse_pane(m, NULL, NULL); - else - wp = w->active; /* Update the activity timer. */ if (gettimeofday(&c->activity_time, NULL) != 0) @@ -742,12 +738,19 @@ server_client_handle_key(struct client *c, key_code key) m->valid = 1; m->key = key; - - if (!options_get_number(s->options, "mouse")) - goto forward; } else m->valid = 0; + /* Find affected pane. */ + if (KEYC_IS_MOUSE(key) && m->valid) + wp = cmd_mouse_pane(m, NULL, NULL); + else + wp = w->active; + + /* Forward mouse keys if disabled. */ + if (key == KEYC_MOUSE && !options_get_number(s->options, "mouse")) + goto forward; + /* Treat everything as a regular key when pasting is detected. */ if (!KEYC_IS_MOUSE(key) && server_client_assume_paste(s)) goto forward; @@ -764,6 +767,10 @@ retry: table = c->keytable; else table = key_bindings_get_table(name, 1); + if (wp == NULL) + log_debug("key table %s (no pane)", table->name); + else + log_debug("key table %s (pane %%%u)", table->name, wp->id); /* Try to see if there is a key binding in the current table. */ bd_find.key = key; From 84319aa8f013238ccc1bbd6ea44b9e6be7c58db2 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 24 Nov 2016 14:38:55 +0000 Subject: [PATCH 3/3] If in the middle of a drag, don't use an invalid key, just use KEYC_MOUSE as a placeholder. Reported by Artem Fokin. --- server-client.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/server-client.c b/server-client.c index dd2a274a..ff43f73d 100644 --- a/server-client.c +++ b/server-client.c @@ -474,9 +474,10 @@ have_event: case NOTYPE: break; case DRAG: - if (c->tty.mouse_drag_update != NULL) + if (c->tty.mouse_drag_update != NULL) { c->tty.mouse_drag_update(c, m); - else { + key = KEYC_MOUSE; + } else { switch (MOUSE_BUTTONS(b)) { case 0: if (where == PANE) @@ -738,6 +739,13 @@ server_client_handle_key(struct client *c, key_code key) m->valid = 1; m->key = key; + + /* + * A mouse event that continues to be valid but that we do not + * want to pass through. + */ + if (key == KEYC_MOUSE) + return; } else m->valid = 0;