diff --git a/CHANGES b/CHANGES index 0a68f6323..9d64f62a2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,8 @@ CHANGES FROM 3.7c TO 3.8 +* Add copy-mode -c to start with the current cursor column as the preferred + column, rather than initially following line ends. + * Many improvements to floating panes: - new-pane and split-window can now set the title with -T and border lines diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c index 28a1a742e..b12c4400e 100644 --- a/cmd-copy-mode.c +++ b/cmd-copy-mode.c @@ -30,8 +30,8 @@ const struct cmd_entry cmd_copy_mode_entry = { .name = "copy-mode", .alias = NULL, - .args = { "dekHMqSs:t:u", 0, 0, NULL }, - .usage = "[-dekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE, + .args = { "cdekHMqSs:t:u", 0, 0, NULL }, + .usage = "[-cdekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE, .source = { 's', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 }, diff --git a/regress/copy-mode-entry-column.sh b/regress/copy-mode-entry-column.sh new file mode 100644 index 000000000..42c614728 --- /dev/null +++ b/regress/copy-mode-entry-column.sh @@ -0,0 +1,190 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +export PATH TERM LC_ALL + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Lentry-column-$$ -f/dev/null" + +fail() +{ + echo "$mode: $*" >&2 + exit 1 +} + +cleanup() +{ + $TMUX kill-server 2>/dev/null +} +trap cleanup 0 1 15 + +x() +{ + $TMUX send-keys -X "$@" || fail "copy command failed: $*" +} + +check_cursor() +{ + actual=$($TMUX display-message -p '#{copy_cursor_x},#{copy_cursor_y}') + [ "$actual" = "$1" ] || fail "expected cursor $1, got $actual" +} + +wait_cursor() +{ + i=0 + while [ "$i" -lt 50 ]; do + actual=$($TMUX display-message -p '#{cursor_x},#{cursor_y}') + [ "$actual" = "$1" ] && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "pane cursor did not reach $1" +} + +mode=emacs +$TMUX new-session -d -x40 -y10 \ + "printf '%s\r\n' ABCDEFGHIJKLMNOPQRST abc ABCDEFGHIJKLMNOPQRST \ + abcdefgh ABCDEFGHIJKLMNOP ab 01234567890123456789; \ + printf '\033[4;9H'; exec cat" || + fail "new-session failed" +$TMUX set-option -g status off || fail "set status failed" +$TMUX set-option -g window-size manual || fail "set window-size failed" +wait_cursor 8,3 + +for mode in emacs vi; do + $TMUX set-window-option -g mode-keys "$mode" || + fail "set mode-keys failed" + + # Ordinary entry still follows line ends; -c starts at the same physical + # position but treats its column as a numeric preference. + for direction in up down; do + if [ "$direction" = up ]; then + row=2 + end=20 + else + row=4 + end=16 + fi + [ "$mode" = emacs ] || end=$((end - 1)) + $TMUX copy-mode || fail "copy-mode failed" + check_cursor 8,3 + x "cursor-$direction" + check_cursor "$end,$row" + x cancel + + $TMUX copy-mode -c || fail "copy-mode -c failed" + check_cursor 8,3 + x "cursor-$direction" + check_cursor "8,$row" + x cancel + done + + # Short lines retain the existing emacs and vi movement rules. Emacs + # restores the preferred column; vi resumes from its last-character clamp. + $TMUX copy-mode -c || fail "copy-mode -c failed" + x cursor-up + check_cursor 8,2 + x cursor-up + if [ "$mode" = emacs ]; then + check_cursor 3,1 + restored=8 + else + check_cursor 2,1 + restored=2 + fi + x cursor-up + check_cursor "$restored,0" + x cancel + + $TMUX copy-mode -c || fail "copy-mode -c failed" + x cursor-down + check_cursor 8,4 + x cursor-down + if [ "$mode" = emacs ]; then + check_cursor 2,5 + restored=8 + else + check_cursor 1,5 + restored=1 + fi + x cursor-down + check_cursor "$restored,6" + x cancel + + # Horizontal movement before the first vertical move replaces the initial + # preference, rather than leaving the entry column permanently fixed. + $TMUX copy-mode -c || fail "copy-mode -c failed" + x cursor-left + check_cursor 7,3 + x cursor-up + check_cursor 7,2 + x cancel +done + +# Passing -c while already in copy mode must not reinitialize its preference. +mode=emacs +$TMUX set-window-option -g mode-keys "$mode" || fail "set mode-keys failed" +$TMUX copy-mode -c || fail "copy-mode -c failed" +x cursor-left +x cursor-up +x cursor-up +check_cursor 3,1 +$TMUX copy-mode -c || fail "second copy-mode -c failed" +check_cursor 3,1 +x cursor-up +check_cursor 7,0 +x cancel + +# An empty entry line has a numeric column zero with -c. +$TMUX respawn-pane -k \ + "printf '\033[H\033[2JABCDEFGHIJKLMNOPQRST\033[2;1H'; exec cat" || + fail "respawn-pane failed" +wait_cursor 0,1 +for mode in emacs vi; do + $TMUX set-window-option -g mode-keys "$mode" || + fail "set mode-keys failed" + $TMUX copy-mode -c || fail "copy-mode -c failed" + check_cursor 0,1 + x cursor-up + check_cursor 0,0 + x cancel +done + +# The initial preference counts terminal columns, not characters. +$TMUX respawn-pane -k \ + "printf '\033[H\033[2J'; \ + printf '%s\r\n' ABCDEFGHIJKLMNOPQRST ABCDEFGHIJKLMNOPQRST; \ + printf 'abc中def'; exec cat" || + fail "respawn-pane failed" +wait_cursor 8,2 +for mode in emacs vi; do + $TMUX set-window-option -g mode-keys "$mode" || + fail "set mode-keys failed" + $TMUX copy-mode -c || fail "copy-mode -c failed" + check_cursor 8,2 + x cursor-up + check_cursor 8,1 + x cancel +done + +# Initialize before the optional entry-time page movement. +$TMUX respawn-pane -k \ + "printf '\033[H\033[2JABCDEFGHIJKLMNOPQRST'; \ + printf '\033[9;1Habcdefgh\033[10;1HABCDEFGHIJKLMNOP\033[9;9H'; \ + exec cat" || + fail "respawn-pane failed" +wait_cursor 8,8 +for mode in emacs vi; do + $TMUX set-window-option -g mode-keys "$mode" || + fail "set mode-keys failed" + $TMUX copy-mode -cu || fail "copy-mode -cu failed" + check_cursor 8,0 + x cancel + $TMUX copy-mode -cd || fail "copy-mode -cd failed" + check_cursor 8,9 + x cancel +done + +exit 0 diff --git a/tmux.1 b/tmux.1 index 88eb9e7f3..d52002341 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2609,12 +2609,17 @@ The synopsis for the command is: .Bl -tag -width Ds .It Xo Ic copy\-mode -.Op Fl dekHMqSu +.Op Fl cdekHMqSu .Op Fl s Ar src\-pane .Op Fl t Ar target\-pane .Xc Enter copy mode. .Pp +.Fl c +initializes the preferred cursor column to the column at which copy mode is +entered, rather than initially following the end of each line. +Subsequent cursor movements use the normal copy mode behaviour. +.Pp .Fl u enters copy mode and scrolls one page up and .Fl d diff --git a/window-copy.c b/window-copy.c index c6126de38..9bd4b90f6 100644 --- a/window-copy.c +++ b/window-copy.c @@ -614,6 +614,11 @@ window_copy_init(struct window_mode_entry *wme, window_copy_sync_snapshot(data, base->grid); data->cx = cx; + if (args_has(args, 'c')) { + /* Start with a preferred column rather than end of line. */ + data->lastcx = cx; + data->lastsx = cx + 1; + } if (cy < screen_hsize(data->backing)) { data->cy = 0; data->oy = screen_hsize(data->backing) - cy;