mirror of
				https://github.com/tmux/tmux.git
				synced 2025-11-04 00:56:10 +00:00 
			
		
		
		
	More accurate vi(1) word navigation in copy mode and on the status line. This
changes the meaning of the word-separators option - setting it to the empty string is equivalent to the previous behavior. From Will Noble in GitHub issue 2693.
This commit is contained in:
		
							
								
								
									
										4
									
								
								CHANGES
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								CHANGES
									
									
									
									
									
								
							@@ -1,6 +1,8 @@
 | 
			
		||||
CHANGES FROM 3.2 TO 3.3
 | 
			
		||||
 | 
			
		||||
XXX
 | 
			
		||||
* More accurate vi(1) word navigation in copy mode and on the status line. This
 | 
			
		||||
  changes the meaning of the word-separators option - setting it to the empty
 | 
			
		||||
  string is equivalent to the previous behavior.
 | 
			
		||||
 | 
			
		||||
CHANGES FROM 3.1c TO 3.2
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										161
									
								
								grid-reader.c
									
									
									
									
									
								
							
							
						
						
									
										161
									
								
								grid-reader.c
									
									
									
									
									
								
							@@ -153,6 +153,29 @@ grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all)
 | 
			
		||||
		gr->cx = grid_reader_line_length(gr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Handle line wrapping while moving the cursor. */
 | 
			
		||||
static int
 | 
			
		||||
grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy)
 | 
			
		||||
{
 | 
			
		||||
	/*
 | 
			
		||||
	 * Make sure the cursor lies within the grid reader's bounding area,
 | 
			
		||||
	 * wrapping to the next line as necessary. Return zero if the cursor
 | 
			
		||||
	 * would wrap past the bottom of the grid.
 | 
			
		||||
	 */
 | 
			
		||||
	while (gr->cx > *xx) {
 | 
			
		||||
		if (gr->cy == *yy)
 | 
			
		||||
			return (0);
 | 
			
		||||
		grid_reader_cursor_start_of_line(gr, 0);
 | 
			
		||||
		grid_reader_cursor_down(gr);
 | 
			
		||||
 | 
			
		||||
		if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
 | 
			
		||||
			*xx = gr->gd->sx - 1;
 | 
			
		||||
		else
 | 
			
		||||
			*xx = grid_reader_line_length(gr);
 | 
			
		||||
	}
 | 
			
		||||
	return (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Check if character under cursor is in set. */
 | 
			
		||||
int
 | 
			
		||||
grid_reader_in_set(struct grid_reader *gr, const char *set)
 | 
			
		||||
@@ -170,7 +193,6 @@ void
 | 
			
		||||
grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators)
 | 
			
		||||
{
 | 
			
		||||
	u_int	xx, yy;
 | 
			
		||||
	int expected = 0;
 | 
			
		||||
 | 
			
		||||
	/* Do not break up wrapped words. */
 | 
			
		||||
	if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
 | 
			
		||||
@@ -180,33 +202,35 @@ grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators)
 | 
			
		||||
	yy = gr->gd->hsize + gr->gd->sy - 1;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * If we started inside a word, skip over word characters. Then skip
 | 
			
		||||
	 * over separators till the next word.
 | 
			
		||||
	 * When navigating via spaces (for example with next-space) separators
 | 
			
		||||
	 * should be empty.
 | 
			
		||||
	 *
 | 
			
		||||
	 * expected is initially set to 0 for the former and then 1 for the
 | 
			
		||||
	 * latter. It is finally set to 0 when the beginning of the next word is
 | 
			
		||||
	 * found.
 | 
			
		||||
	 * If we started on a separator that is not whitespace, skip over
 | 
			
		||||
	 * subsequent separators that are not whitespace. Otherwise, if we
 | 
			
		||||
	 * started on a non-whitespace character, skip over subsequent
 | 
			
		||||
	 * characters that are neither whitespace nor separators. Then, skip
 | 
			
		||||
	 * over whitespace (if any) until the next non-whitespace character.
 | 
			
		||||
	 */
 | 
			
		||||
	do {
 | 
			
		||||
		while (gr->cx > xx ||
 | 
			
		||||
		    grid_reader_in_set(gr, separators) == expected) {
 | 
			
		||||
			/* Move down if we are past the end of the line. */
 | 
			
		||||
			if (gr->cx > xx) {
 | 
			
		||||
				if (gr->cy == yy)
 | 
			
		||||
					return;
 | 
			
		||||
				grid_reader_cursor_start_of_line(gr, 0);
 | 
			
		||||
				grid_reader_cursor_down(gr);
 | 
			
		||||
 | 
			
		||||
				if (grid_get_line(gr->gd, gr->cy)->flags &
 | 
			
		||||
				    GRID_LINE_WRAPPED)
 | 
			
		||||
					xx = gr->gd->sx - 1;
 | 
			
		||||
				else
 | 
			
		||||
					xx = grid_reader_line_length(gr);
 | 
			
		||||
			} else
 | 
			
		||||
	if (!grid_reader_handle_wrap(gr, &xx, &yy))
 | 
			
		||||
		return;
 | 
			
		||||
	if (!grid_reader_in_set(gr, WHITESPACE)) {
 | 
			
		||||
		if (grid_reader_in_set(gr, separators)) {
 | 
			
		||||
			do
 | 
			
		||||
				gr->cx++;
 | 
			
		||||
			while (grid_reader_handle_wrap(gr, &xx, &yy) &&
 | 
			
		||||
			    grid_reader_in_set(gr, separators) &&
 | 
			
		||||
			    !grid_reader_in_set(gr, WHITESPACE));
 | 
			
		||||
		} else {
 | 
			
		||||
			do
 | 
			
		||||
				gr->cx++;
 | 
			
		||||
			while (grid_reader_handle_wrap(gr, &xx, &yy) &&
 | 
			
		||||
			    !(grid_reader_in_set(gr, separators) ||
 | 
			
		||||
			    grid_reader_in_set(gr, WHITESPACE)));
 | 
			
		||||
		}
 | 
			
		||||
		expected = !expected;
 | 
			
		||||
	} while (expected == 1);
 | 
			
		||||
	}
 | 
			
		||||
	while (grid_reader_handle_wrap(gr, &xx, &yy) &&
 | 
			
		||||
	    grid_reader_in_set(gr, WHITESPACE))
 | 
			
		||||
		gr->cx++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Move cursor to the end of the next word. */
 | 
			
		||||
@@ -214,7 +238,6 @@ void
 | 
			
		||||
grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators)
 | 
			
		||||
{
 | 
			
		||||
	u_int	xx, yy;
 | 
			
		||||
	int	expected = 1;
 | 
			
		||||
 | 
			
		||||
	/* Do not break up wrapped words. */
 | 
			
		||||
	if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
 | 
			
		||||
@@ -224,49 +247,54 @@ grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators)
 | 
			
		||||
	yy = gr->gd->hsize + gr->gd->sy - 1;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * If we started on a separator, skip over separators. Then skip over
 | 
			
		||||
	 * word characters till the next separator.
 | 
			
		||||
	 * When navigating via spaces (for example with next-space), separators
 | 
			
		||||
	 * should be empty in both modes.
 | 
			
		||||
	 *
 | 
			
		||||
	 * expected is initially set to 1 for the former and then 1 for the
 | 
			
		||||
	 * latter. It is finally set to 1 when the end of the next word is
 | 
			
		||||
	 * found.
 | 
			
		||||
	 * If we started on a whitespace, move until reaching the first
 | 
			
		||||
	 * non-whitespace character. If that character is a separator, treat
 | 
			
		||||
	 * subsequent separators as a word, and continue moving until the first
 | 
			
		||||
	 * non-separator. Otherwise, continue moving until the first separator
 | 
			
		||||
	 * or whitespace.
 | 
			
		||||
	 */
 | 
			
		||||
	do {
 | 
			
		||||
		while (gr->cx > xx ||
 | 
			
		||||
		    grid_reader_in_set(gr, separators) == expected) {
 | 
			
		||||
			/* Move down if we are past the end of the line. */
 | 
			
		||||
			if (gr->cx > xx) {
 | 
			
		||||
				if (gr->cy == yy)
 | 
			
		||||
					return;
 | 
			
		||||
				grid_reader_cursor_start_of_line(gr, 0);
 | 
			
		||||
				grid_reader_cursor_down(gr);
 | 
			
		||||
 | 
			
		||||
				if (grid_get_line(gr->gd, gr->cy)->flags &
 | 
			
		||||
				    GRID_LINE_WRAPPED)
 | 
			
		||||
					xx = gr->gd->sx - 1;
 | 
			
		||||
				else
 | 
			
		||||
					xx = grid_reader_line_length(gr);
 | 
			
		||||
			} else
 | 
			
		||||
	while (grid_reader_handle_wrap(gr, &xx, &yy)) {
 | 
			
		||||
		if (grid_reader_in_set(gr, WHITESPACE))
 | 
			
		||||
			gr->cx++;
 | 
			
		||||
		else if (grid_reader_in_set(gr, separators)) {
 | 
			
		||||
			do
 | 
			
		||||
				gr->cx++;
 | 
			
		||||
			while (grid_reader_handle_wrap(gr, &xx, &yy) &&
 | 
			
		||||
			    grid_reader_in_set(gr, separators) &&
 | 
			
		||||
			    !grid_reader_in_set(gr, WHITESPACE));
 | 
			
		||||
			return;
 | 
			
		||||
		} else {
 | 
			
		||||
			do
 | 
			
		||||
				gr->cx++;
 | 
			
		||||
			while (grid_reader_handle_wrap(gr, &xx, &yy) &&
 | 
			
		||||
			    !(grid_reader_in_set(gr, WHITESPACE) ||
 | 
			
		||||
			    grid_reader_in_set(gr, separators)));
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		expected = !expected;
 | 
			
		||||
	} while (expected == 0);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Move to the previous place where a word begins. */
 | 
			
		||||
void
 | 
			
		||||
grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators,
 | 
			
		||||
    int already)
 | 
			
		||||
    int already, int stop_at_eol)
 | 
			
		||||
{
 | 
			
		||||
	int	oldx, oldy, r;
 | 
			
		||||
	int	oldx, oldy, at_eol, word_is_letters;
 | 
			
		||||
 | 
			
		||||
	/* Move back to the previous word character. */
 | 
			
		||||
	if (already || grid_reader_in_set(gr, separators)) {
 | 
			
		||||
	if (already || grid_reader_in_set(gr, WHITESPACE)) {
 | 
			
		||||
		for (;;) {
 | 
			
		||||
			if (gr->cx > 0) {
 | 
			
		||||
				gr->cx--;
 | 
			
		||||
				if (!grid_reader_in_set(gr, separators))
 | 
			
		||||
				if (!grid_reader_in_set(gr, WHITESPACE)) {
 | 
			
		||||
					word_is_letters =
 | 
			
		||||
					    !grid_reader_in_set(gr, separators);
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if (gr->cy == 0)
 | 
			
		||||
					return;
 | 
			
		||||
@@ -274,17 +302,21 @@ grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators,
 | 
			
		||||
				grid_reader_cursor_end_of_line(gr, 0, 0);
 | 
			
		||||
 | 
			
		||||
				/* Stop if separator at EOL. */
 | 
			
		||||
				if (gr->cx > 0) {
 | 
			
		||||
				if (stop_at_eol && gr->cx > 0) {
 | 
			
		||||
					oldx = gr->cx;
 | 
			
		||||
					gr->cx--;
 | 
			
		||||
					r = grid_reader_in_set(gr, separators);
 | 
			
		||||
					at_eol = grid_reader_in_set(gr,
 | 
			
		||||
					    WHITESPACE);
 | 
			
		||||
					gr->cx = oldx;
 | 
			
		||||
					if (r)
 | 
			
		||||
					if (at_eol) {
 | 
			
		||||
						word_is_letters = 0;
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	} else
 | 
			
		||||
		word_is_letters = !grid_reader_in_set(gr, separators);
 | 
			
		||||
 | 
			
		||||
	/* Move back to the beginning of this word. */
 | 
			
		||||
	do {
 | 
			
		||||
@@ -292,15 +324,16 @@ grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators,
 | 
			
		||||
		oldy = gr->cy;
 | 
			
		||||
		if (gr->cx == 0) {
 | 
			
		||||
			if (gr->cy == 0 ||
 | 
			
		||||
			  ~grid_get_line(gr->gd, gr->cy - 1)->flags &
 | 
			
		||||
			  GRID_LINE_WRAPPED)
 | 
			
		||||
			    (~grid_get_line(gr->gd, gr->cy - 1)->flags &
 | 
			
		||||
			    GRID_LINE_WRAPPED))
 | 
			
		||||
				break;
 | 
			
		||||
			grid_reader_cursor_up(gr);
 | 
			
		||||
			grid_reader_cursor_end_of_line(gr, 0, 1);
 | 
			
		||||
		}
 | 
			
		||||
		if (gr->cx > 0)
 | 
			
		||||
			gr->cx--;
 | 
			
		||||
	} while (!grid_reader_in_set(gr, separators));
 | 
			
		||||
	} while (!grid_reader_in_set(gr, WHITESPACE) &&
 | 
			
		||||
	    word_is_letters != grid_reader_in_set(gr, separators));
 | 
			
		||||
	gr->cx = oldx;
 | 
			
		||||
	gr->cy = oldy;
 | 
			
		||||
}
 | 
			
		||||
@@ -324,17 +357,17 @@ grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc)
 | 
			
		||||
			    memcmp(gc.data.data, jc->data, gc.data.size) == 0) {
 | 
			
		||||
				gr->cx = px;
 | 
			
		||||
				gr->cy = py;
 | 
			
		||||
				return 1;
 | 
			
		||||
				return (1);
 | 
			
		||||
			}
 | 
			
		||||
			px++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (py == yy ||
 | 
			
		||||
		    !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED))
 | 
			
		||||
			return 0;
 | 
			
		||||
			return (0);
 | 
			
		||||
		px = 0;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
	return (0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Jump back to character. */
 | 
			
		||||
@@ -354,16 +387,16 @@ grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc)
 | 
			
		||||
			    memcmp(gc.data.data, jc->data, gc.data.size) == 0) {
 | 
			
		||||
				gr->cx = px - 1;
 | 
			
		||||
				gr->cy = py - 1;
 | 
			
		||||
				return 1;
 | 
			
		||||
				return (1);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (py == 1 ||
 | 
			
		||||
		    !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED))
 | 
			
		||||
			return 0;
 | 
			
		||||
			return (0);
 | 
			
		||||
		xx = grid_line_length(gr->gd, py - 2);
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
	return (0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Jump back to the first non-blank character of the line. */
 | 
			
		||||
 
 | 
			
		||||
@@ -754,7 +754,11 @@ const struct options_table_entry options_table[] = {
 | 
			
		||||
	{ .name = "word-separators",
 | 
			
		||||
	  .type = OPTIONS_TABLE_STRING,
 | 
			
		||||
	  .scope = OPTIONS_TABLE_SESSION,
 | 
			
		||||
	  .default_str = " ",
 | 
			
		||||
	  /*
 | 
			
		||||
	   * The set of non-alphanumeric printable ASCII characters minus the
 | 
			
		||||
	   * underscore.
 | 
			
		||||
	   */
 | 
			
		||||
	  .default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~",
 | 
			
		||||
	  .text = "Characters considered to separate words."
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										116
									
								
								regress/copy-mode-test-emacs.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								regress/copy-mode-test-emacs.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
PATH=/bin:/usr/bin
 | 
			
		||||
TERM=screen
 | 
			
		||||
 | 
			
		||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
 | 
			
		||||
TMUX="$TEST_TMUX -f/dev/null -Ltest"
 | 
			
		||||
$TMUX kill-server 2>/dev/null
 | 
			
		||||
 | 
			
		||||
$TMUX new -d -x40 -y10 \
 | 
			
		||||
      "cat copy-mode-test.txt; printf '\e[9;15H'; cat" || exit 1
 | 
			
		||||
$TMUX set -g window-size manual || exit 1
 | 
			
		||||
 | 
			
		||||
# Enter copy mode and go to the first column of the first row.
 | 
			
		||||
$TMUX set-window-option -g mode-keys emacs
 | 
			
		||||
$TMUX set-window-option -g word-separators ""
 | 
			
		||||
$TMUX copy-mode
 | 
			
		||||
$TMUX send-keys -X history-top
 | 
			
		||||
$TMUX send-keys -X start-of-line
 | 
			
		||||
 | 
			
		||||
# Test that `previous-word` and `previous-space`
 | 
			
		||||
# do not go past the start of text.
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X previous-space
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer 2>/dev/null)" = "" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word-end` does not skip single-letter words.
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "A" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word-end` wraps around indented line breaks.
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "$(echo -e "words\n        Indented")" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word` wraps around un-indented line breaks.
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "$(echo -e "line\n")" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word-end` treats periods as letters.
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "line..." ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `previous-word` and `next-word` treat periods as letters.
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "$(echo -e "line...\n")" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `previous-space` and `next-space` treat periods as letters.
 | 
			
		||||
$TMUX send-keys -X previous-space
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-space
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "$(echo -e "line...\n")" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word` and `next-word-end` treat other symbols as letters.
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "... @nd then \$ym_bols[]{}" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `previous-word` treats other symbols as letters
 | 
			
		||||
# and `next-word` wraps around for indented symbols
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "$(echo -e "\$ym_bols[]{}\n ")" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word-end` treats digits as letters
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = " 500xyz" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `previous-word` treats digits as letters
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "500xyz" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word` and `next-word-end` stop at the end of text.
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-space
 | 
			
		||||
$TMUX send-keys -X next-space-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "500xyz" ] || exit 1
 | 
			
		||||
 | 
			
		||||
$TMUX kill-server 2>/dev/null
 | 
			
		||||
exit 0
 | 
			
		||||
							
								
								
									
										115
									
								
								regress/copy-mode-test-vi.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								regress/copy-mode-test-vi.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
PATH=/bin:/usr/bin
 | 
			
		||||
TERM=screen
 | 
			
		||||
 | 
			
		||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
 | 
			
		||||
TMUX="$TEST_TMUX -f/dev/null -Ltest"
 | 
			
		||||
$TMUX kill-server 2>/dev/null
 | 
			
		||||
 | 
			
		||||
$TMUX new -d -x40 -y10 \
 | 
			
		||||
      "cat copy-mode-test.txt; printf '\e[9;15H'; cat" || exit 1
 | 
			
		||||
$TMUX set -g window-size manual || exit 1
 | 
			
		||||
 | 
			
		||||
# Enter copy mode and go to the first column of the first row.
 | 
			
		||||
$TMUX set-window-option -g mode-keys vi
 | 
			
		||||
$TMUX copy-mode
 | 
			
		||||
$TMUX send-keys -X history-top
 | 
			
		||||
$TMUX send-keys -X start-of-line
 | 
			
		||||
 | 
			
		||||
# Test that `previous-word` and `previous-space`
 | 
			
		||||
# do not go past the start of text.
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X previous-space
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "A" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word-end` skips single-letter words
 | 
			
		||||
# and `previous-word` does not skip multi-letter words.
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "line" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word-end` wraps around indented line breaks.
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "$(echo -e "words\n        Indented")" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word` wraps around un-indented line breaks.
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "$(echo -e "line\nA")" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word-end` does not treat periods as letters.
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "line" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-space-end` treats periods as letters.
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-space-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "line..." ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `previous-space` and `next-space` treat periods as letters.
 | 
			
		||||
$TMUX send-keys -X previous-space
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-space
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "$(echo -e "line...\n.")" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word` and `next-word-end` do not treat other symbols as letters.
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "... @nd then" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-space` wraps around for indented symbols
 | 
			
		||||
$TMUX send-keys -X next-space
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-space
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "$(echo -e "\$ym_bols[]{}\n ?")" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word-end` treats digits as letters
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "? 500xyz" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `previous-word` treats digits as letters
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X previous-word
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "500xyz" ] || exit 1
 | 
			
		||||
 | 
			
		||||
# Test that `next-word`, `next-word-end`,
 | 
			
		||||
# `next-space`, and `next-space-end` stop at the end of text.
 | 
			
		||||
$TMUX send-keys -X begin-selection
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-word-end
 | 
			
		||||
$TMUX send-keys -X next-word
 | 
			
		||||
$TMUX send-keys -X next-space
 | 
			
		||||
$TMUX send-keys -X next-space-end
 | 
			
		||||
$TMUX send-keys -X copy-selection
 | 
			
		||||
[ "$($TMUX show-buffer)" = "500xyz" ] || exit 1
 | 
			
		||||
 | 
			
		||||
$TMUX kill-server 2>/dev/null
 | 
			
		||||
exit 0
 | 
			
		||||
							
								
								
									
										5
									
								
								regress/copy-mode-test.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								regress/copy-mode-test.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
A line of words
 | 
			
		||||
	Indented line
 | 
			
		||||
Another line...
 | 
			
		||||
... @nd then $ym_bols[]{}
 | 
			
		||||
 ?? 500xyz
 | 
			
		||||
							
								
								
									
										207
									
								
								status.c
									
									
									
									
									
								
							
							
						
						
									
										207
									
								
								status.c
									
									
									
									
									
								
							@@ -876,17 +876,25 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key)
 | 
			
		||||
		*new_key = KEYC_BSPACE;
 | 
			
		||||
		return (1);
 | 
			
		||||
	case 'b':
 | 
			
		||||
	case 'B':
 | 
			
		||||
		*new_key = 'b'|KEYC_META;
 | 
			
		||||
		return (1);
 | 
			
		||||
	case 'B':
 | 
			
		||||
		*new_key = 'B'|KEYC_VI;
 | 
			
		||||
		return (1);
 | 
			
		||||
	case 'd':
 | 
			
		||||
		*new_key = '\025';
 | 
			
		||||
		return (1);
 | 
			
		||||
	case 'e':
 | 
			
		||||
		*new_key = 'e'|KEYC_VI;
 | 
			
		||||
		return (1);
 | 
			
		||||
	case 'E':
 | 
			
		||||
		*new_key = 'E'|KEYC_VI;
 | 
			
		||||
		return (1);
 | 
			
		||||
	case 'w':
 | 
			
		||||
		*new_key = 'w'|KEYC_VI;
 | 
			
		||||
		return (1);
 | 
			
		||||
	case 'W':
 | 
			
		||||
		*new_key = 'f'|KEYC_META;
 | 
			
		||||
		*new_key = 'W'|KEYC_VI;
 | 
			
		||||
		return (1);
 | 
			
		||||
	case 'p':
 | 
			
		||||
		*new_key = '\031'; /* C-y */
 | 
			
		||||
@@ -1061,16 +1069,125 @@ status_prompt_replace_complete(struct client *c, const char *s)
 | 
			
		||||
	return (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Prompt forward to the next beginning of a word. */
 | 
			
		||||
static void
 | 
			
		||||
status_prompt_forward_word(struct client *c, size_t size, int vi,
 | 
			
		||||
    const char *separators)
 | 
			
		||||
{
 | 
			
		||||
	size_t		 idx = c->prompt_index;
 | 
			
		||||
	int		 word_is_separators;
 | 
			
		||||
 | 
			
		||||
	/* In emacs mode, skip until the first non-whitespace character. */
 | 
			
		||||
	if (!vi)
 | 
			
		||||
		while (idx != size &&
 | 
			
		||||
		    status_prompt_space(&c->prompt_buffer[idx]))
 | 
			
		||||
			idx++;
 | 
			
		||||
 | 
			
		||||
	/* Can't move forward if we're already at the end. */
 | 
			
		||||
	if (idx == size) {
 | 
			
		||||
		c->prompt_index = idx;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Determine the current character class (separators or not). */
 | 
			
		||||
	word_is_separators = status_prompt_in_list(separators,
 | 
			
		||||
	    &c->prompt_buffer[idx]) &&
 | 
			
		||||
	    !status_prompt_space(&c->prompt_buffer[idx]);
 | 
			
		||||
 | 
			
		||||
	/* Skip ahead until the first space or opposite character class. */
 | 
			
		||||
	do {
 | 
			
		||||
		idx++;
 | 
			
		||||
		if (status_prompt_space(&c->prompt_buffer[idx])) {
 | 
			
		||||
			/* In vi mode, go to the start of the next word. */
 | 
			
		||||
			if (vi)
 | 
			
		||||
				while (idx != size &&
 | 
			
		||||
				    status_prompt_space(&c->prompt_buffer[idx]))
 | 
			
		||||
					idx++;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	} while (idx != size && word_is_separators == status_prompt_in_list(
 | 
			
		||||
	    separators, &c->prompt_buffer[idx]));
 | 
			
		||||
 | 
			
		||||
	c->prompt_index = idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Prompt forward to the next end of a word. */
 | 
			
		||||
static void
 | 
			
		||||
status_prompt_end_word(struct client *c, size_t size, const char *separators)
 | 
			
		||||
{
 | 
			
		||||
	size_t		 idx = c->prompt_index;
 | 
			
		||||
	int		 word_is_separators;
 | 
			
		||||
 | 
			
		||||
	/* Can't move forward if we're already at the end. */
 | 
			
		||||
	if (idx == size)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	/* Find the next word. */
 | 
			
		||||
	do {
 | 
			
		||||
		idx++;
 | 
			
		||||
		if (idx == size) {
 | 
			
		||||
			c->prompt_index = idx;
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
	} while (status_prompt_space(&c->prompt_buffer[idx]));
 | 
			
		||||
 | 
			
		||||
	/* Determine the character class (separators or not). */
 | 
			
		||||
	word_is_separators = status_prompt_in_list(separators,
 | 
			
		||||
	    &c->prompt_buffer[idx]);
 | 
			
		||||
 | 
			
		||||
	/* Skip ahead until the next space or opposite character class. */
 | 
			
		||||
	do {
 | 
			
		||||
		idx++;
 | 
			
		||||
		if (idx == size)
 | 
			
		||||
			break;
 | 
			
		||||
	} while (!status_prompt_space(&c->prompt_buffer[idx]) &&
 | 
			
		||||
	    word_is_separators == status_prompt_in_list(separators,
 | 
			
		||||
	    &c->prompt_buffer[idx]));
 | 
			
		||||
 | 
			
		||||
	/* Back up to the previous character to stop at the end of the word. */
 | 
			
		||||
	c->prompt_index = idx - 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Prompt backward to the previous beginning of a word. */
 | 
			
		||||
static void
 | 
			
		||||
status_prompt_backward_word(struct client *c, const char *separators)
 | 
			
		||||
{
 | 
			
		||||
	size_t	idx = c->prompt_index;
 | 
			
		||||
	int	word_is_separators;
 | 
			
		||||
 | 
			
		||||
	/* Find non-whitespace. */
 | 
			
		||||
	while (idx != 0) {
 | 
			
		||||
		--idx;
 | 
			
		||||
		if (!status_prompt_space(&c->prompt_buffer[idx]))
 | 
			
		||||
			break;
 | 
			
		||||
	}
 | 
			
		||||
	word_is_separators = status_prompt_in_list(separators,
 | 
			
		||||
	    &c->prompt_buffer[idx]);
 | 
			
		||||
 | 
			
		||||
	/* Find the character before the beginning of the word. */
 | 
			
		||||
	while (idx != 0) {
 | 
			
		||||
		--idx;
 | 
			
		||||
		if (status_prompt_space(&c->prompt_buffer[idx]) ||
 | 
			
		||||
		    word_is_separators != status_prompt_in_list(separators,
 | 
			
		||||
		    &c->prompt_buffer[idx])) {
 | 
			
		||||
			/* Go back to the word. */
 | 
			
		||||
			idx++;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	c->prompt_index = idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Handle keys in prompt. */
 | 
			
		||||
int
 | 
			
		||||
status_prompt_key(struct client *c, key_code key)
 | 
			
		||||
{
 | 
			
		||||
	struct options		*oo = c->session->options;
 | 
			
		||||
	char			*s, *cp, prefix = '=';
 | 
			
		||||
	const char		*histstr, *ws = NULL, *keystring;
 | 
			
		||||
	const char		*histstr, *separators = NULL, *keystring;
 | 
			
		||||
	size_t			 size, idx;
 | 
			
		||||
	struct utf8_data	 tmp;
 | 
			
		||||
	int			 keys;
 | 
			
		||||
	int			 keys, word_is_separators;
 | 
			
		||||
 | 
			
		||||
	if (c->prompt_flags & PROMPT_KEY) {
 | 
			
		||||
		keystring = key_string_lookup_key(key, 0);
 | 
			
		||||
@@ -1173,20 +1290,24 @@ process_key:
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	case '\027': /* C-w */
 | 
			
		||||
		ws = options_get_string(oo, "word-separators");
 | 
			
		||||
		separators = options_get_string(oo, "word-separators");
 | 
			
		||||
		idx = c->prompt_index;
 | 
			
		||||
 | 
			
		||||
		/* Find a non-separator. */
 | 
			
		||||
		/* Find non-whitespace. */
 | 
			
		||||
		while (idx != 0) {
 | 
			
		||||
			idx--;
 | 
			
		||||
			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
 | 
			
		||||
			if (!status_prompt_space(&c->prompt_buffer[idx]))
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
		word_is_separators = status_prompt_in_list(separators,
 | 
			
		||||
		    &c->prompt_buffer[idx]);
 | 
			
		||||
 | 
			
		||||
		/* Find the separator at the beginning of the word. */
 | 
			
		||||
		/* Find the character before the beginning of the word. */
 | 
			
		||||
		while (idx != 0) {
 | 
			
		||||
			idx--;
 | 
			
		||||
			if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) {
 | 
			
		||||
			if (status_prompt_space(&c->prompt_buffer[idx]) ||
 | 
			
		||||
			    word_is_separators != status_prompt_in_list(
 | 
			
		||||
			    separators, &c->prompt_buffer[idx])) {
 | 
			
		||||
				/* Go back to the word. */
 | 
			
		||||
				idx++;
 | 
			
		||||
				break;
 | 
			
		||||
@@ -1208,50 +1329,32 @@ process_key:
 | 
			
		||||
		c->prompt_index = idx;
 | 
			
		||||
 | 
			
		||||
		goto changed;
 | 
			
		||||
	case 'f'|KEYC_META:
 | 
			
		||||
	case KEYC_RIGHT|KEYC_CTRL:
 | 
			
		||||
		ws = options_get_string(oo, "word-separators");
 | 
			
		||||
 | 
			
		||||
		/* Find a word. */
 | 
			
		||||
		while (c->prompt_index != size) {
 | 
			
		||||
			idx = ++c->prompt_index;
 | 
			
		||||
			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Find the separator at the end of the word. */
 | 
			
		||||
		while (c->prompt_index != size) {
 | 
			
		||||
			idx = ++c->prompt_index;
 | 
			
		||||
			if (status_prompt_in_list(ws, &c->prompt_buffer[idx]))
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Back up to the end-of-word like vi. */
 | 
			
		||||
		if (options_get_number(oo, "status-keys") == MODEKEY_VI &&
 | 
			
		||||
		    c->prompt_index != 0)
 | 
			
		||||
			c->prompt_index--;
 | 
			
		||||
 | 
			
		||||
	case 'f'|KEYC_META:
 | 
			
		||||
		separators = options_get_string(oo, "word-separators");
 | 
			
		||||
		status_prompt_forward_word(c, size, 0, separators);
 | 
			
		||||
		goto changed;
 | 
			
		||||
	case 'E'|KEYC_VI:
 | 
			
		||||
		status_prompt_end_word(c, size, "");
 | 
			
		||||
		goto changed;
 | 
			
		||||
	case 'e'|KEYC_VI:
 | 
			
		||||
		separators = options_get_string(oo, "word-separators");
 | 
			
		||||
		status_prompt_end_word(c, size, separators);
 | 
			
		||||
		goto changed;
 | 
			
		||||
	case 'W'|KEYC_VI:
 | 
			
		||||
		status_prompt_forward_word(c, size, 1, "");
 | 
			
		||||
		goto changed;
 | 
			
		||||
	case 'w'|KEYC_VI:
 | 
			
		||||
		separators = options_get_string(oo, "word-separators");
 | 
			
		||||
		status_prompt_forward_word(c, size, 1, separators);
 | 
			
		||||
		goto changed;
 | 
			
		||||
	case 'B'|KEYC_VI:
 | 
			
		||||
		status_prompt_backward_word(c, "");
 | 
			
		||||
		goto changed;
 | 
			
		||||
	case 'b'|KEYC_META:
 | 
			
		||||
	case KEYC_LEFT|KEYC_CTRL:
 | 
			
		||||
		ws = options_get_string(oo, "word-separators");
 | 
			
		||||
 | 
			
		||||
		/* Find a non-separator. */
 | 
			
		||||
		while (c->prompt_index != 0) {
 | 
			
		||||
			idx = --c->prompt_index;
 | 
			
		||||
			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Find the separator at the beginning of the word. */
 | 
			
		||||
		while (c->prompt_index != 0) {
 | 
			
		||||
			idx = --c->prompt_index;
 | 
			
		||||
			if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) {
 | 
			
		||||
				/* Go back to the word. */
 | 
			
		||||
				c->prompt_index++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case 'b'|KEYC_META:
 | 
			
		||||
		separators = options_get_string(oo, "word-separators");
 | 
			
		||||
		status_prompt_backward_word(c, separators);
 | 
			
		||||
		goto changed;
 | 
			
		||||
	case KEYC_UP:
 | 
			
		||||
	case '\020': /* C-p */
 | 
			
		||||
@@ -1339,8 +1442,10 @@ append_key:
 | 
			
		||||
		return (0);
 | 
			
		||||
	if (key <= 0x7f)
 | 
			
		||||
		utf8_set(&tmp, key);
 | 
			
		||||
	else
 | 
			
		||||
	else if (KEYC_IS_UNICODE(key))
 | 
			
		||||
		utf8_to_data(key, &tmp);
 | 
			
		||||
	else
 | 
			
		||||
		return (0);
 | 
			
		||||
 | 
			
		||||
	c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2,
 | 
			
		||||
	    sizeof *c->prompt_buffer);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								tmux.1
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								tmux.1
									
									
									
									
									
								
							@@ -1782,19 +1782,18 @@ commands) or when the cursor reaches the bottom (for scrolling commands).
 | 
			
		||||
.Ql -no-clear
 | 
			
		||||
variants do not clear the selection.
 | 
			
		||||
.Pp
 | 
			
		||||
The next and previous word keys use space and the
 | 
			
		||||
.Ql - ,
 | 
			
		||||
.Ql _
 | 
			
		||||
and
 | 
			
		||||
.Ql @
 | 
			
		||||
characters as word delimiters by default, but this can be adjusted by
 | 
			
		||||
setting the
 | 
			
		||||
The next and previous word keys skip over whitespace and treat consecutive
 | 
			
		||||
runs of either word separators or other letters as words.
 | 
			
		||||
Word separators can be customized with the
 | 
			
		||||
.Em word-separators
 | 
			
		||||
session option.
 | 
			
		||||
Next word moves to the start of the next word, next word end to the end of the
 | 
			
		||||
next word and previous word to the start of the previous word.
 | 
			
		||||
The three next and previous space keys work similarly but use a space alone as
 | 
			
		||||
the word separator.
 | 
			
		||||
Setting
 | 
			
		||||
.Em word-separators
 | 
			
		||||
to the empty string makes next/previous word equivalent to next/previous space.
 | 
			
		||||
.Pp
 | 
			
		||||
The jump commands enable quick movement within a line.
 | 
			
		||||
For instance, typing
 | 
			
		||||
@@ -3974,9 +3973,6 @@ If set to both, a bell and a message are produced.
 | 
			
		||||
Sets the session's conception of what characters are considered word
 | 
			
		||||
separators, for the purposes of the next and previous word commands in
 | 
			
		||||
copy mode.
 | 
			
		||||
The default is
 | 
			
		||||
.Ql \ -_@ .
 | 
			
		||||
.El
 | 
			
		||||
.Pp
 | 
			
		||||
Available window options are:
 | 
			
		||||
.Pp
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								tmux.h
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								tmux.h
									
									
									
									
									
								
							@@ -131,6 +131,7 @@ struct winlink;
 | 
			
		||||
#define KEYC_CURSOR          0x04000000000000ULL
 | 
			
		||||
#define KEYC_IMPLIED_META    0x08000000000000ULL
 | 
			
		||||
#define KEYC_BUILD_MODIFIERS 0x10000000000000ULL
 | 
			
		||||
#define KEYC_VI              0x20000000000000ULL
 | 
			
		||||
 | 
			
		||||
/* Masks for key bits. */
 | 
			
		||||
#define KEYC_MASK_MODIFIERS  0x00f00000000000ULL
 | 
			
		||||
@@ -590,6 +591,9 @@ struct msg_write_close {
 | 
			
		||||
	int	stream;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Character classes. */
 | 
			
		||||
#define WHITESPACE " "
 | 
			
		||||
 | 
			
		||||
/* Mode keys. */
 | 
			
		||||
#define MODEKEY_EMACS 0
 | 
			
		||||
#define MODEKEY_VI 1
 | 
			
		||||
@@ -2640,7 +2644,7 @@ void	 grid_reader_cursor_end_of_line(struct grid_reader *, int, int);
 | 
			
		||||
void	 grid_reader_cursor_next_word(struct grid_reader *, const char *);
 | 
			
		||||
void	 grid_reader_cursor_next_word_end(struct grid_reader *, const char *);
 | 
			
		||||
void	 grid_reader_cursor_previous_word(struct grid_reader *, const char *,
 | 
			
		||||
	     int);
 | 
			
		||||
	     int, int);
 | 
			
		||||
int	 grid_reader_cursor_jump(struct grid_reader *,
 | 
			
		||||
	     const struct utf8_data *);
 | 
			
		||||
int	 grid_reader_cursor_jump_back(struct grid_reader *,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								utf8.c
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								utf8.c
									
									
									
									
									
								
							@@ -64,7 +64,7 @@ static struct utf8_index_tree utf8_index_tree = RB_INITIALIZER(utf8_index_tree);
 | 
			
		||||
static u_int utf8_next_index;
 | 
			
		||||
 | 
			
		||||
#define UTF8_GET_SIZE(uc) (((uc) >> 24) & 0x1f)
 | 
			
		||||
#define UTF8_GET_WIDTH(flags) (((uc) >> 29) - 1)
 | 
			
		||||
#define UTF8_GET_WIDTH(uc) (((uc) >> 29) - 1)
 | 
			
		||||
 | 
			
		||||
#define UTF8_SET_SIZE(size) (((utf8_char)(size)) << 24)
 | 
			
		||||
#define UTF8_SET_WIDTH(width) ((((utf8_char)(width)) + 1) << 29)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										102
									
								
								window-copy.c
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								window-copy.c
									
									
									
									
									
								
							@@ -128,7 +128,7 @@ static void	window_copy_cursor_next_word_end_pos(struct window_mode_entry *,
 | 
			
		||||
static void	window_copy_cursor_next_word_end(struct window_mode_entry *,
 | 
			
		||||
		    const char *, int);
 | 
			
		||||
static void	window_copy_cursor_previous_word_pos(struct window_mode_entry *,
 | 
			
		||||
		    const char *, int, u_int *, u_int *);
 | 
			
		||||
		    const char *, u_int *, u_int *);
 | 
			
		||||
static void	window_copy_cursor_previous_word(struct window_mode_entry *,
 | 
			
		||||
		    const char *, int);
 | 
			
		||||
static void	window_copy_scroll_up(struct window_mode_entry *, u_int);
 | 
			
		||||
@@ -255,7 +255,7 @@ struct window_copy_mode_data {
 | 
			
		||||
		SEL_LINE,		/* select one line at a time */
 | 
			
		||||
	} selflag;
 | 
			
		||||
 | 
			
		||||
	const char	*ws;		/* word separators */
 | 
			
		||||
	const char	*separators;	/* word separators */
 | 
			
		||||
 | 
			
		||||
	u_int		 dx;		/* drag start position */
 | 
			
		||||
	u_int		 dy;
 | 
			
		||||
@@ -1321,7 +1321,7 @@ window_copy_cmd_previous_matching_bracket(struct window_copy_cmd_state *cs)
 | 
			
		||||
					tried = 1;
 | 
			
		||||
					goto retry;
 | 
			
		||||
				}
 | 
			
		||||
				window_copy_cursor_previous_word(wme, "}]) ", 1);
 | 
			
		||||
				window_copy_cursor_previous_word(wme, close, 1);
 | 
			
		||||
			}
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
@@ -1433,8 +1433,7 @@ window_copy_cmd_next_matching_bracket(struct window_copy_cmd_state *cs)
 | 
			
		||||
					tried = 1;
 | 
			
		||||
					goto retry;
 | 
			
		||||
				}
 | 
			
		||||
				window_copy_cursor_next_word_end(wme, "{[( ",
 | 
			
		||||
				    0);
 | 
			
		||||
				window_copy_cursor_next_word_end(wme, open, 0);
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			/* For vi, continue searching for bracket until EOL. */
 | 
			
		||||
@@ -1506,7 +1505,7 @@ window_copy_cmd_next_space(struct window_copy_cmd_state *cs)
 | 
			
		||||
	u_int				 np = wme->prefix;
 | 
			
		||||
 | 
			
		||||
	for (; np != 0; np--)
 | 
			
		||||
		window_copy_cursor_next_word(wme, " ");
 | 
			
		||||
		window_copy_cursor_next_word(wme, "");
 | 
			
		||||
	return (WINDOW_COPY_CMD_NOTHING);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1517,7 +1516,7 @@ window_copy_cmd_next_space_end(struct window_copy_cmd_state *cs)
 | 
			
		||||
	u_int				 np = wme->prefix;
 | 
			
		||||
 | 
			
		||||
	for (; np != 0; np--)
 | 
			
		||||
		window_copy_cursor_next_word_end(wme, " ", 0);
 | 
			
		||||
		window_copy_cursor_next_word_end(wme, "", 0);
 | 
			
		||||
	return (WINDOW_COPY_CMD_NOTHING);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1525,13 +1524,13 @@ static enum window_copy_cmd_action
 | 
			
		||||
window_copy_cmd_next_word(struct window_copy_cmd_state *cs)
 | 
			
		||||
{
 | 
			
		||||
	struct window_mode_entry	*wme = cs->wme;
 | 
			
		||||
	struct session			*s = cs->s;
 | 
			
		||||
	u_int				 np = wme->prefix;
 | 
			
		||||
	const char			*ws;
 | 
			
		||||
	const char			*separators;
 | 
			
		||||
 | 
			
		||||
	separators = options_get_string(cs->s->options, "word-separators");
 | 
			
		||||
 | 
			
		||||
	ws = options_get_string(s->options, "word-separators");
 | 
			
		||||
	for (; np != 0; np--)
 | 
			
		||||
		window_copy_cursor_next_word(wme, ws);
 | 
			
		||||
		window_copy_cursor_next_word(wme, separators);
 | 
			
		||||
	return (WINDOW_COPY_CMD_NOTHING);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1539,13 +1538,13 @@ static enum window_copy_cmd_action
 | 
			
		||||
window_copy_cmd_next_word_end(struct window_copy_cmd_state *cs)
 | 
			
		||||
{
 | 
			
		||||
	struct window_mode_entry	*wme = cs->wme;
 | 
			
		||||
	struct session			*s = cs->s;
 | 
			
		||||
	u_int				 np = wme->prefix;
 | 
			
		||||
	const char			*ws;
 | 
			
		||||
	const char			*separators;
 | 
			
		||||
 | 
			
		||||
	separators = options_get_string(cs->s->options, "word-separators");
 | 
			
		||||
 | 
			
		||||
	ws = options_get_string(s->options, "word-separators");
 | 
			
		||||
	for (; np != 0; np--)
 | 
			
		||||
		window_copy_cursor_next_word_end(wme, ws, 0);
 | 
			
		||||
		window_copy_cursor_next_word_end(wme, separators, 0);
 | 
			
		||||
	return (WINDOW_COPY_CMD_NOTHING);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1618,7 +1617,7 @@ window_copy_cmd_previous_space(struct window_copy_cmd_state *cs)
 | 
			
		||||
	u_int				 np = wme->prefix;
 | 
			
		||||
 | 
			
		||||
	for (; np != 0; np--)
 | 
			
		||||
		window_copy_cursor_previous_word(wme, " ", 1);
 | 
			
		||||
		window_copy_cursor_previous_word(wme, "", 1);
 | 
			
		||||
	return (WINDOW_COPY_CMD_NOTHING);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1626,13 +1625,13 @@ static enum window_copy_cmd_action
 | 
			
		||||
window_copy_cmd_previous_word(struct window_copy_cmd_state *cs)
 | 
			
		||||
{
 | 
			
		||||
	struct window_mode_entry	*wme = cs->wme;
 | 
			
		||||
	struct session			*s = cs->s;
 | 
			
		||||
	u_int				 np = wme->prefix;
 | 
			
		||||
	const char			*ws;
 | 
			
		||||
	const char			*separators;
 | 
			
		||||
 | 
			
		||||
	separators = options_get_string(cs->s->options, "word-separators");
 | 
			
		||||
 | 
			
		||||
	ws = options_get_string(s->options, "word-separators");
 | 
			
		||||
	for (; np != 0; np--)
 | 
			
		||||
		window_copy_cursor_previous_word(wme, ws, 1);
 | 
			
		||||
		window_copy_cursor_previous_word(wme, separators, 1);
 | 
			
		||||
	return (WINDOW_COPY_CMD_NOTHING);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1778,18 +1777,20 @@ static enum window_copy_cmd_action
 | 
			
		||||
window_copy_cmd_select_word(struct window_copy_cmd_state *cs)
 | 
			
		||||
{
 | 
			
		||||
	struct window_mode_entry	*wme = cs->wme;
 | 
			
		||||
	struct session			*s = cs->s;
 | 
			
		||||
	struct options			*session_options = cs->s->options;
 | 
			
		||||
	struct window_copy_mode_data	*data = wme->data;
 | 
			
		||||
	u_int				 px, py, nextx, nexty;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	data->lineflag = LINE_SEL_LEFT_RIGHT;
 | 
			
		||||
	data->rectflag = 0;
 | 
			
		||||
	data->selflag = SEL_WORD;
 | 
			
		||||
	data->dx = data->cx;
 | 
			
		||||
	data->dy = screen_hsize(data->backing) + data->cy - data->oy;
 | 
			
		||||
 | 
			
		||||
	data->ws = options_get_string(s->options, "word-separators");
 | 
			
		||||
	window_copy_cursor_previous_word(wme, data->ws, 0);
 | 
			
		||||
	data->separators = options_get_string(session_options,
 | 
			
		||||
	    "word-separators");
 | 
			
		||||
	window_copy_cursor_previous_word(wme, data->separators, 0);
 | 
			
		||||
	px = data->cx;
 | 
			
		||||
	py = screen_hsize(data->backing) + data->cy - data->oy;
 | 
			
		||||
	data->selrx = px;
 | 
			
		||||
@@ -1805,8 +1806,8 @@ window_copy_cmd_select_word(struct window_copy_cmd_state *cs)
 | 
			
		||||
		nexty++;
 | 
			
		||||
	}
 | 
			
		||||
	if (px >= window_copy_find_length(wme, py) ||
 | 
			
		||||
	    !window_copy_in_set(wme, nextx, nexty, data->ws))
 | 
			
		||||
		window_copy_cursor_next_word_end(wme, data->ws, 1);
 | 
			
		||||
	    !window_copy_in_set(wme, nextx, nexty, WHITESPACE))
 | 
			
		||||
		window_copy_cursor_next_word_end(wme, data->separators, 1);
 | 
			
		||||
	else {
 | 
			
		||||
		window_copy_update_cursor(wme, px, data->cy);
 | 
			
		||||
		if (window_copy_update_selection(wme, 1, 1))
 | 
			
		||||
@@ -3730,8 +3731,8 @@ window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin,
 | 
			
		||||
		begin = 0;
 | 
			
		||||
		if (data->dy > yy || (data->dy == yy && data->dx > xx)) {
 | 
			
		||||
			/* Right to left selection. */
 | 
			
		||||
			window_copy_cursor_previous_word_pos(wme, data->ws, 0,
 | 
			
		||||
			    &xx, &yy);
 | 
			
		||||
			window_copy_cursor_previous_word_pos(wme,
 | 
			
		||||
			    data->separators, &xx, &yy);
 | 
			
		||||
			begin = 1;
 | 
			
		||||
 | 
			
		||||
			/* Reset the end. */
 | 
			
		||||
@@ -3740,9 +3741,10 @@ window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin,
 | 
			
		||||
		} else {
 | 
			
		||||
			/* Left to right selection. */
 | 
			
		||||
			if (xx >= window_copy_find_length(wme, yy) ||
 | 
			
		||||
			    !window_copy_in_set(wme, xx + 1, yy, data->ws))
 | 
			
		||||
			    !window_copy_in_set(wme, xx + 1, yy, WHITESPACE)) {
 | 
			
		||||
				window_copy_cursor_next_word_end_pos(wme,
 | 
			
		||||
				    data->ws, &xx, &yy);
 | 
			
		||||
				    data->separators, &xx, &yy);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* Reset the start. */
 | 
			
		||||
			data->selx = data->selrx;
 | 
			
		||||
@@ -4666,19 +4668,19 @@ window_copy_cursor_next_word_end_pos(struct window_mode_entry *wme,
 | 
			
		||||
	struct screen			*back_s = data->backing;
 | 
			
		||||
	struct grid_reader		 gr;
 | 
			
		||||
	u_int				 px, py, hsize;
 | 
			
		||||
	int				 keys;
 | 
			
		||||
 | 
			
		||||
	px = data->cx;
 | 
			
		||||
	hsize = screen_hsize(back_s);
 | 
			
		||||
	py =  hsize + data->cy - data->oy;
 | 
			
		||||
 | 
			
		||||
	grid_reader_start(&gr, back_s->grid, px, py);
 | 
			
		||||
	keys = options_get_number(oo, "mode-keys");
 | 
			
		||||
	if (keys == MODEKEY_VI && !grid_reader_in_set(&gr, separators))
 | 
			
		||||
		grid_reader_cursor_right(&gr, 0, 0);
 | 
			
		||||
	grid_reader_cursor_next_word_end(&gr, separators);
 | 
			
		||||
	if (keys == MODEKEY_VI)
 | 
			
		||||
	if (options_get_number(oo, "mode-keys") == MODEKEY_VI) {
 | 
			
		||||
		if (!grid_reader_in_set(&gr, WHITESPACE))
 | 
			
		||||
			grid_reader_cursor_right(&gr, 0, 0);
 | 
			
		||||
		grid_reader_cursor_next_word_end(&gr, separators);
 | 
			
		||||
		grid_reader_cursor_left(&gr, 1);
 | 
			
		||||
	} else
 | 
			
		||||
		grid_reader_cursor_next_word_end(&gr, separators);
 | 
			
		||||
	grid_reader_get_cursor(&gr, &px, &py);
 | 
			
		||||
	*ppx = px;
 | 
			
		||||
	*ppy = py;
 | 
			
		||||
@@ -4695,7 +4697,6 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme,
 | 
			
		||||
	struct screen			*back_s = data->backing;
 | 
			
		||||
	struct grid_reader		 gr;
 | 
			
		||||
	u_int				 px, py, oldy, hsize;
 | 
			
		||||
	int				 keys;
 | 
			
		||||
 | 
			
		||||
	px = data->cx;
 | 
			
		||||
	hsize = screen_hsize(back_s);
 | 
			
		||||
@@ -4703,12 +4704,13 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme,
 | 
			
		||||
	oldy = data->cy;
 | 
			
		||||
 | 
			
		||||
	grid_reader_start(&gr, back_s->grid, px, py);
 | 
			
		||||
	keys = options_get_number(oo, "mode-keys");
 | 
			
		||||
	if (keys == MODEKEY_VI && !grid_reader_in_set(&gr, separators))
 | 
			
		||||
		grid_reader_cursor_right(&gr, 0, 0);
 | 
			
		||||
	grid_reader_cursor_next_word_end(&gr, separators);
 | 
			
		||||
	if (keys == MODEKEY_VI)
 | 
			
		||||
	if (options_get_number(oo, "mode-keys") == MODEKEY_VI) {
 | 
			
		||||
		if (!grid_reader_in_set(&gr, WHITESPACE))
 | 
			
		||||
			grid_reader_cursor_right(&gr, 0, 0);
 | 
			
		||||
		grid_reader_cursor_next_word_end(&gr, separators);
 | 
			
		||||
		grid_reader_cursor_left(&gr, 1);
 | 
			
		||||
	} else
 | 
			
		||||
		grid_reader_cursor_next_word_end(&gr, separators);
 | 
			
		||||
	grid_reader_get_cursor(&gr, &px, &py);
 | 
			
		||||
	window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s),
 | 
			
		||||
	    data->oy, oldy, px, py, no_reset);
 | 
			
		||||
@@ -4717,7 +4719,7 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme,
 | 
			
		||||
/* Compute the previous place where a word begins. */
 | 
			
		||||
static void
 | 
			
		||||
window_copy_cursor_previous_word_pos(struct window_mode_entry *wme,
 | 
			
		||||
    const char *separators, int already, u_int *ppx, u_int *ppy)
 | 
			
		||||
    const char *separators, u_int *ppx, u_int *ppy)
 | 
			
		||||
{
 | 
			
		||||
	struct window_copy_mode_data	*data = wme->data;
 | 
			
		||||
	struct screen			*back_s = data->backing;
 | 
			
		||||
@@ -4729,7 +4731,8 @@ window_copy_cursor_previous_word_pos(struct window_mode_entry *wme,
 | 
			
		||||
	py = hsize + data->cy - data->oy;
 | 
			
		||||
 | 
			
		||||
	grid_reader_start(&gr, back_s->grid, px, py);
 | 
			
		||||
	grid_reader_cursor_previous_word(&gr, separators, already);
 | 
			
		||||
	grid_reader_cursor_previous_word(&gr, separators, /* already= */ 0,
 | 
			
		||||
        /* stop_at_eol= */ 1);
 | 
			
		||||
	grid_reader_get_cursor(&gr, &px, &py);
 | 
			
		||||
	*ppx = px;
 | 
			
		||||
	*ppy = py;
 | 
			
		||||
@@ -4744,6 +4747,11 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme,
 | 
			
		||||
	struct screen			*back_s = data->backing;
 | 
			
		||||
	struct grid_reader		 gr;
 | 
			
		||||
	u_int				 px, py, oldy, hsize;
 | 
			
		||||
	int				 stop_at_eol;
 | 
			
		||||
 | 
			
		||||
	stop_at_eol =
 | 
			
		||||
	    options_get_number(wme->wp->window->options, "mode-keys")
 | 
			
		||||
	        == MODEKEY_EMACS;
 | 
			
		||||
 | 
			
		||||
	px = data->cx;
 | 
			
		||||
	hsize = screen_hsize(back_s);
 | 
			
		||||
@@ -4751,7 +4759,7 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme,
 | 
			
		||||
	oldy = data->cy;
 | 
			
		||||
 | 
			
		||||
	grid_reader_start(&gr, back_s->grid, px, py);
 | 
			
		||||
	grid_reader_cursor_previous_word(&gr, separators, already);
 | 
			
		||||
	grid_reader_cursor_previous_word(&gr, separators, already, stop_at_eol);
 | 
			
		||||
	grid_reader_get_cursor(&gr, &px, &py);
 | 
			
		||||
	window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
 | 
			
		||||
}
 | 
			
		||||
@@ -4893,10 +4901,10 @@ window_copy_start_drag(struct client *c, struct mouse_event *m)
 | 
			
		||||
		data->selflag = SEL_CHAR;
 | 
			
		||||
	switch (data->selflag) {
 | 
			
		||||
	case SEL_WORD:
 | 
			
		||||
		if (data->ws != NULL) {
 | 
			
		||||
		if (data->separators != NULL) {
 | 
			
		||||
			window_copy_update_cursor(wme, x, y);
 | 
			
		||||
			window_copy_cursor_previous_word_pos(wme, data->ws, 0,
 | 
			
		||||
			    &x, &y);
 | 
			
		||||
			window_copy_cursor_previous_word_pos(wme,
 | 
			
		||||
			    data->separators, &x, &y);
 | 
			
		||||
			y -= screen_hsize(data->backing) - data->oy;
 | 
			
		||||
		}
 | 
			
		||||
		window_copy_update_cursor(wme, x, y);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user