mirror of
https://github.com/tmux/tmux.git
synced 2026-08-03 05:58:59 +00:00
Do not update selection endpoints when the copy mode view scrolls unless a
mouse drag is active. This keeps an existing selection anchored to the same text while using scroll commands or the mouse wheel. Allow dragging from inside an existing selection to extend it from the nearest endpoint, including the copy mode cursor endpoint, while preserving the old behavior of starting a new selection when dragging from outside the selection. Added regression tests to test this behaviour.
This commit is contained in:
113
regress/copy-mode-selection-scroll.sh
Normal file
113
regress/copy-mode-selection-scroll.sh
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
expect_buffer()
|
||||
{
|
||||
expected=$1
|
||||
actual=$($TMUX show-buffer)
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "unexpected buffer: expected [$expected], got [$actual]"
|
||||
}
|
||||
wheel()
|
||||
{
|
||||
button=$1
|
||||
col=$2
|
||||
row=$3
|
||||
seq=$(printf '\033[<%s;%s;%sM' "$button" "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
trap cleanup 0
|
||||
trap 'exit 1' 1 2 3 15
|
||||
|
||||
$TMUX new -d -x40 -y10 \
|
||||
'i=0; while [ $i -lt 80 ]; do printf "line %02d xxxxxxxxxx\n" $i; i=$((i + 1)); done; cat' ||
|
||||
exit 1
|
||||
$TMUX set -g window-size manual || exit 1
|
||||
$TMUX set -g mouse on || exit 1
|
||||
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -N10 -X cursor-down || exit 1
|
||||
$TMUX send-keys -X start-of-line || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-down || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
|
||||
initial=$(printf 'line 10 xxxxxxxxxx\nline 11 xxxxxxxxxx')
|
||||
expect_buffer "$initial"
|
||||
|
||||
$TMUX send-keys -X stop-selection || exit 1
|
||||
$TMUX send-keys -N3 -X scroll-down || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
expect_buffer "$initial"
|
||||
|
||||
$TMUX send-keys -N2 -X scroll-up || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
expect_buffer "$initial"
|
||||
|
||||
$TMUX send-keys -X scroll-middle || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
expect_buffer "$initial"
|
||||
|
||||
$TMUX send-keys -X scroll-bottom || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
expect_buffer "$initial"
|
||||
|
||||
$TMUX send-keys -X scroll-top || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
expect_buffer "$initial"
|
||||
|
||||
$TMUX send-keys -X recentre-top-bottom || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
expect_buffer "$initial"
|
||||
|
||||
$TMUX send-keys -X other-end || exit 1
|
||||
$TMUX send-keys -X cursor-down || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
|
||||
extended_end=$(printf 'line 10 xxxxxxxxxx\nline 11 xxxxxxxxxx\nline 12 xxxxxxxxxx')
|
||||
expect_buffer "$extended_end"
|
||||
|
||||
$TMUX send-keys -X stop-selection || exit 1
|
||||
$TMUX send-keys -X other-end || exit 1
|
||||
$TMUX send-keys -X other-end || exit 1
|
||||
$TMUX send-keys -X cursor-up || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
|
||||
extended_start=$(printf 'line 09 xxxxxxxxxx\nline 10 xxxxxxxxxx\nline 11 xxxxxxxxxx\nline 12 xxxxxxxxxx')
|
||||
expect_buffer "$extended_start"
|
||||
|
||||
$TMUX2 new-session -d -x40 -y10 "$TMUX attach" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "no outer pane"
|
||||
|
||||
wheel 65 5 5
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
expect_buffer "$extended_start"
|
||||
|
||||
wheel 64 5 5
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
expect_buffer "$extended_start"
|
||||
|
||||
exit 0
|
||||
153
window-copy.c
153
window-copy.c
@@ -103,8 +103,12 @@ static void window_copy_goto_line(struct window_mode_entry *, const char *);
|
||||
static void window_copy_update_cursor(struct window_mode_entry *, u_int,
|
||||
u_int);
|
||||
static void window_copy_start_selection(struct window_mode_entry *);
|
||||
static int window_copy_mouse_in_selection(struct window_mode_entry *,
|
||||
u_int, u_int, int *, int *);
|
||||
static int window_copy_adjust_selection(struct window_mode_entry *,
|
||||
u_int *, u_int *);
|
||||
static int window_copy_update_selection_view(struct window_mode_entry *,
|
||||
int, int);
|
||||
static int window_copy_set_selection(struct window_mode_entry *, int, int);
|
||||
static int window_copy_update_selection(struct window_mode_entry *, int,
|
||||
int);
|
||||
@@ -861,7 +865,7 @@ window_copy_scroll1(struct window_mode_entry *wme, struct window_pane *wp,
|
||||
|
||||
if (data->searchmark != NULL && !data->timeout)
|
||||
window_copy_search_marks(wme, NULL, data->searchregex, 1);
|
||||
window_copy_update_selection(wme, 1, 0);
|
||||
window_copy_update_selection_view(wme, 1, 0);
|
||||
window_pane_scrollbar_show(wp, 1);
|
||||
window_copy_redraw_screen(wme);
|
||||
}
|
||||
@@ -1647,7 +1651,7 @@ window_copy_cmd_scroll_to(struct window_copy_cmd_state *cs, u_int to)
|
||||
data->cy += delta;
|
||||
}
|
||||
|
||||
window_copy_update_selection(wme, 0, 0);
|
||||
window_copy_update_selection_view(wme, 0, 0);
|
||||
return (WINDOW_COPY_CMD_REDRAW);
|
||||
}
|
||||
|
||||
@@ -2349,6 +2353,15 @@ window_copy_cmd_scroll_down(struct window_copy_cmd_state *cs)
|
||||
struct window_mode_entry *wme = cs->wme;
|
||||
struct window_copy_mode_data *data = wme->data;
|
||||
u_int np = wme->prefix;
|
||||
int dragging;
|
||||
|
||||
dragging = (cs->c != NULL && cs->c->tty.mouse_drag_flag != 0);
|
||||
if (data->screen.sel != NULL && !dragging) {
|
||||
data->cursordrag = CURSORDRAG_NONE;
|
||||
data->lineflag = LINE_SEL_NONE;
|
||||
window_copy_scroll_up(wme, np);
|
||||
return (WINDOW_COPY_CMD_NOTHING);
|
||||
}
|
||||
|
||||
for (; np != 0; np--)
|
||||
window_copy_cursor_down(wme, 1);
|
||||
@@ -2375,7 +2388,17 @@ static enum window_copy_cmd_action
|
||||
window_copy_cmd_scroll_up(struct window_copy_cmd_state *cs)
|
||||
{
|
||||
struct window_mode_entry *wme = cs->wme;
|
||||
struct window_copy_mode_data *data = wme->data;
|
||||
u_int np = wme->prefix;
|
||||
int dragging;
|
||||
|
||||
dragging = (cs->c != NULL && cs->c->tty.mouse_drag_flag != 0);
|
||||
if (data->screen.sel != NULL && !dragging) {
|
||||
data->cursordrag = CURSORDRAG_NONE;
|
||||
data->lineflag = LINE_SEL_NONE;
|
||||
window_copy_scroll_down(wme, np);
|
||||
return (WINDOW_COPY_CMD_NOTHING);
|
||||
}
|
||||
|
||||
for (; np != 0; np--)
|
||||
window_copy_cursor_up(wme, 1);
|
||||
@@ -3099,7 +3122,7 @@ window_copy_cmd_recentre_top_bottom(struct window_copy_cmd_state *cs)
|
||||
data->cy = cy + (data->oy - oy);
|
||||
break;
|
||||
}
|
||||
window_copy_update_selection(wme, 0, 0);
|
||||
window_copy_update_selection_view(wme, 0, 0);
|
||||
|
||||
return (WINDOW_COPY_CMD_REDRAW);
|
||||
}
|
||||
@@ -5486,6 +5509,77 @@ window_copy_start_selection(struct window_mode_entry *wme)
|
||||
window_copy_set_selection(wme, 1, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
window_copy_mouse_in_selection(struct window_mode_entry *wme, u_int x, u_int y,
|
||||
int *on_start, int *on_end)
|
||||
{
|
||||
struct window_copy_mode_data *data = wme->data;
|
||||
u_int hsize, screeny;
|
||||
u_int mx, my, selx, sely, endselx, endsely;
|
||||
u_int cursorx, cursory;
|
||||
long long mpos, spos, epos, dstart, dend;
|
||||
|
||||
if (on_start != NULL)
|
||||
*on_start = 0;
|
||||
if (on_end != NULL)
|
||||
*on_end = 0;
|
||||
if (data->screen.sel == NULL)
|
||||
return (0);
|
||||
|
||||
hsize = screen_hsize(data->backing);
|
||||
screeny = hsize - data->oy;
|
||||
|
||||
selx = window_copy_cursor_offset(wme, data->selx,
|
||||
screen_size_x(&data->screen));
|
||||
sely = data->sely - screeny;
|
||||
if (data->sely >= screeny && sely < screen_size_y(&data->screen) &&
|
||||
x == selx && y == sely) {
|
||||
if (on_start != NULL)
|
||||
*on_start = 1;
|
||||
return (1);
|
||||
}
|
||||
|
||||
endselx = window_copy_cursor_offset(wme, data->endselx,
|
||||
screen_size_x(&data->screen));
|
||||
endsely = data->endsely - screeny;
|
||||
if (data->endsely >= screeny &&
|
||||
endsely < screen_size_y(&data->screen) &&
|
||||
x == endselx && y == endsely) {
|
||||
if (on_end != NULL)
|
||||
*on_end = 1;
|
||||
return (1);
|
||||
}
|
||||
|
||||
cursorx = window_copy_cursor_offset(wme, data->cx,
|
||||
screen_size_x(&data->screen));
|
||||
cursory = data->cy;
|
||||
if (x != cursorx || y != cursory) {
|
||||
if (!screen_check_selection(&data->screen, x, y))
|
||||
return (0);
|
||||
}
|
||||
|
||||
if (on_start != NULL || on_end != NULL) {
|
||||
mx = window_copy_cursor_unoffset(wme, x,
|
||||
screen_size_x(&data->screen));
|
||||
my = screeny + y;
|
||||
mpos = (long long)my * (screen_size_x(&data->screen) + 1) + mx;
|
||||
spos = (long long)data->sely *
|
||||
(screen_size_x(&data->screen) + 1) + data->selx;
|
||||
epos = (long long)data->endsely *
|
||||
(screen_size_x(&data->screen) + 1) + data->endselx;
|
||||
dstart = llabs(mpos - spos);
|
||||
dend = llabs(mpos - epos);
|
||||
if (dstart <= dend) {
|
||||
if (on_start != NULL)
|
||||
*on_start = 1;
|
||||
} else {
|
||||
if (on_end != NULL)
|
||||
*on_end = 1;
|
||||
}
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
|
||||
static int
|
||||
window_copy_adjust_selection(struct window_mode_entry *wme, u_int *selx,
|
||||
u_int *sely)
|
||||
@@ -5531,6 +5625,35 @@ window_copy_update_selection(struct window_mode_entry *wme, int may_redraw,
|
||||
return (window_copy_set_selection(wme, may_redraw, no_reset));
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the visible selection after the view has changed. If the mouse is
|
||||
* not dragging, keep the selection endpoints anchored to the same text.
|
||||
*/
|
||||
static int
|
||||
window_copy_update_selection_view(struct window_mode_entry *wme, int may_redraw,
|
||||
int no_reset)
|
||||
{
|
||||
struct window_copy_mode_data *data = wme->data;
|
||||
u_int selx, sely, endselx, endsely;
|
||||
int changed;
|
||||
|
||||
if (data->cursordrag != CURSORDRAG_NONE)
|
||||
return (window_copy_update_selection(wme, may_redraw, no_reset));
|
||||
|
||||
selx = data->selx;
|
||||
sely = data->sely;
|
||||
endselx = data->endselx;
|
||||
endsely = data->endsely;
|
||||
|
||||
changed = window_copy_update_selection(wme, may_redraw, 1);
|
||||
|
||||
data->selx = selx;
|
||||
data->sely = sely;
|
||||
data->endselx = endselx;
|
||||
data->endsely = endsely;
|
||||
return (changed);
|
||||
}
|
||||
|
||||
static int
|
||||
window_copy_set_selection(struct window_mode_entry *wme, int may_redraw,
|
||||
int no_reset)
|
||||
@@ -6560,7 +6683,7 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny)
|
||||
|
||||
if (data->searchmark != NULL && !data->timeout)
|
||||
window_copy_search_marks(wme, NULL, data->searchregex, 1);
|
||||
window_copy_update_selection(wme, 0, 0);
|
||||
window_copy_update_selection_view(wme, 0, 0);
|
||||
if (window_copy_line_numbers_active(wme)) {
|
||||
if (window_copy_line_number_mode(wme) !=
|
||||
WINDOW_COPY_LINE_NUMBERS_ABSOLUTE) {
|
||||
@@ -6629,7 +6752,7 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny)
|
||||
|
||||
if (data->searchmark != NULL && !data->timeout)
|
||||
window_copy_search_marks(wme, NULL, data->searchregex, 1);
|
||||
window_copy_update_selection(wme, 0, 0);
|
||||
window_copy_update_selection_view(wme, 0, 0);
|
||||
if (window_copy_line_numbers_active(wme)) {
|
||||
if (window_copy_line_number_mode(wme) !=
|
||||
WINDOW_COPY_LINE_NUMBERS_ABSOLUTE) {
|
||||
@@ -6718,6 +6841,7 @@ window_copy_start_drag(struct client *c, struct mouse_event *m)
|
||||
struct window_mode_entry *wme;
|
||||
struct window_copy_mode_data *data;
|
||||
u_int x, y, yg;
|
||||
int inside_selection, on_start, on_end;
|
||||
|
||||
if (c == NULL)
|
||||
return;
|
||||
@@ -6738,10 +6862,16 @@ window_copy_start_drag(struct client *c, struct mouse_event *m)
|
||||
c->tty.mouse_drag_release = window_copy_drag_release;
|
||||
|
||||
data = wme->data;
|
||||
on_start = on_end = 0;
|
||||
inside_selection = window_copy_mouse_in_selection(wme, x, y,
|
||||
&on_start, &on_end);
|
||||
x = window_copy_cursor_unoffset(wme, x, screen_size_x(&data->screen));
|
||||
yg = screen_hsize(data->backing) + y - data->oy;
|
||||
if (x < data->selrx || x > data->endselrx || yg != data->selry)
|
||||
if (on_start || on_end || !inside_selection ||
|
||||
x < data->selrx || x > data->endselrx || yg != data->selry) {
|
||||
data->lineflag = LINE_SEL_NONE;
|
||||
data->selflag = SEL_CHAR;
|
||||
}
|
||||
switch (data->selflag) {
|
||||
case SEL_WORD:
|
||||
if (data->separators != NULL) {
|
||||
@@ -6757,7 +6887,15 @@ window_copy_start_drag(struct client *c, struct mouse_event *m)
|
||||
break;
|
||||
case SEL_CHAR:
|
||||
window_copy_update_cursor(wme, x, y);
|
||||
window_copy_start_selection(wme);
|
||||
if (!inside_selection)
|
||||
window_copy_start_selection(wme);
|
||||
else {
|
||||
if (on_start)
|
||||
data->cursordrag = CURSORDRAG_SEL;
|
||||
else if (on_end)
|
||||
data->cursordrag = CURSORDRAG_ENDSEL;
|
||||
window_copy_update_selection(wme, 1, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -6833,6 +6971,7 @@ window_copy_drag_release(struct client *c, struct mouse_event *m)
|
||||
data = wme->data;
|
||||
if (window_copy_line_numbers_active(wme))
|
||||
window_copy_drag_update(c, m);
|
||||
data->cursordrag = CURSORDRAG_NONE;
|
||||
evtimer_del(&data->dragtimer);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user