[PATCH] add keys for first/last item in a choose list

2013-06-11 Thread Tor Perkins
Hello and thanks for tmux!

As a recovering screen user, I'm used to hitting  or  in
the choose-window command so that I can warp the selected row to the
first or last item in the list.  For completeness, the patch adds some
other key mappings in both vi and emacs mode that I think make sense.

- Tor


#
#  Patch adds support for warping to the first/last item in a choose list...
#

Index: tmux-1.8/mode-key.c
===
--- tmux-1.8.orig/mode-key.c	2013-02-24 04:42:49.0 -0800
+++ tmux-1.8/mode-key.c	2013-06-11 09:43:40.0 -0700
@@ -80,6 +80,8 @@
 	{ MODEKEYCHOICE_DOWN, "down" },
 	{ MODEKEYCHOICE_PAGEDOWN, "page-down" },
 	{ MODEKEYCHOICE_PAGEUP, "page-up" },
+  { MODEKEYCHOICE_FIRST, "first" },
+  { MODEKEYCHOICE_LAST, "last" },
 	{ MODEKEYCHOICE_SCROLLDOWN, "scroll-down" },
 	{ MODEKEYCHOICE_SCROLLUP, "scroll-up" },
 	{ MODEKEYCHOICE_STARTNUMBERPREFIX, "start-number-prefix" },
@@ -224,6 +226,12 @@
 	{ KEYC_DOWN,		0, MODEKEYCHOICE_DOWN },
 	{ KEYC_NPAGE,		0, MODEKEYCHOICE_PAGEDOWN },
 	{ KEYC_PPAGE,		0, MODEKEYCHOICE_PAGEUP },
+  { 'g',  0, MODEKEYCHOICE_FIRST },
+  { 'G',  0, MODEKEYCHOICE_LAST },
+  { KEYC_HOME,0, MODEKEYCHOICE_FIRST },
+  { KEYC_END, 0, MODEKEYCHOICE_LAST },
+  { KEYC_HOME | KEYC_CTRL,0, MODEKEYCHOICE_FIRST },
+  { KEYC_END  | KEYC_CTRL,0, MODEKEYCHOICE_LAST },
 	{ KEYC_UP | KEYC_CTRL,	0, MODEKEYCHOICE_SCROLLUP },
 	{ KEYC_UP,		0, MODEKEYCHOICE_UP },
 	{ ' ',			0, MODEKEYCHOICE_TREE_TOGGLE },
@@ -366,6 +374,10 @@
 	{ KEYC_DOWN,		0, MODEKEYCHOICE_DOWN },
 	{ KEYC_NPAGE,		0, MODEKEYCHOICE_PAGEDOWN },
 	{ KEYC_PPAGE,		0, MODEKEYCHOICE_PAGEUP },
+  { KEYC_HOME,0, MODEKEYCHOICE_FIRST },
+  { KEYC_END, 0, MODEKEYCHOICE_LAST },
+  { '<' | KEYC_ESCAPE,0, MODEKEYCHOICE_FIRST },
+  { '>' | KEYC_ESCAPE,0, MODEKEYCHOICE_LAST },
 	{ KEYC_UP | KEYC_CTRL,	0, MODEKEYCHOICE_SCROLLUP },
 	{ KEYC_UP,		0, MODEKEYCHOICE_UP },
 	{ ' ',			0, MODEKEYCHOICE_TREE_TOGGLE },
Index: tmux-1.8/tmux.h
===
--- tmux-1.8.orig/tmux.h	2013-06-11 09:42:42.0 -0700
+++ tmux-1.8/tmux.h	2013-06-11 09:42:42.0 -0700
@@ -570,6 +570,8 @@
 	MODEKEYCHOICE_DOWN,
 	MODEKEYCHOICE_PAGEDOWN,
 	MODEKEYCHOICE_PAGEUP,
+	MODEKEYCHOICE_FIRST,
+	MODEKEYCHOICE_LAST,
 	MODEKEYCHOICE_SCROLLDOWN,
 	MODEKEYCHOICE_SCROLLUP,
 	MODEKEYCHOICE_STARTNUMBERPREFIX,
Index: tmux-1.8/window-choose.c
===
--- tmux-1.8.orig/window-choose.c	2013-03-17 07:03:37.0 -0700
+++ tmux-1.8/window-choose.c	2013-06-11 09:42:42.0 -0700
@@ -650,6 +650,19 @@
 			data->top = data->selected;
 		window_choose_redraw_screen(wp);
 		break;
+  case MODEKEYCHOICE_FIRST:
+data->selected = 0;
+data->top = 0;
+window_choose_redraw_screen(wp);
+break;
+  case MODEKEYCHOICE_LAST:
+data->selected = items - 1;
+if (screen_size_y(s) < items) {
+  data->top = items - screen_size_y(s);
+} else
+  data->top = 0;
+window_choose_redraw_screen(wp);
+break;
 	case MODEKEYCHOICE_BACKSPACE:
 		input_len = strlen(data->input_str);
 		if (input_len > 0)
--
This SF.net email is sponsored by Windows:

Build for Windows Store.

http://p.sf.net/sfu/windows-dev2dev___
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users


[PATCH 5/6] Move to the indentation of a line on scrolling

2013-06-11 Thread Ben Boeckel
When scrolling by page or half-page, vim moves to the first
non-whitespace character of a line. Emulate the behavior in copy mode.
---
 window-copy.c | 4 
 1 file changed, 4 insertions(+)

diff --git a/window-copy.c b/window-copy.c
index ee6a2e8..17808b8 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -458,6 +458,7 @@ window_copy_key(struct window_pane *wp, struct session 
*sess, int key)
case MODEKEYCOPY_PREVIOUSPAGE:
for (; np != 0; np--)
window_copy_pageup(wp);
+   window_copy_cursor_back_to_indentation(wp);
break;
case MODEKEYCOPY_NEXTPAGE:
n = 1;
@@ -471,6 +472,7 @@ window_copy_key(struct window_pane *wp, struct session 
*sess, int key)
}
window_copy_update_selection(wp);
window_copy_redraw_screen(wp);
+   window_copy_cursor_back_to_indentation(wp);
break;
case MODEKEYCOPY_HALFPAGEUP:
n = screen_size_y(s) / 2;
@@ -482,6 +484,7 @@ window_copy_key(struct window_pane *wp, struct session 
*sess, int key)
}
window_copy_update_selection(wp);
window_copy_redraw_screen(wp);
+   window_copy_cursor_back_to_indentation(wp);
break;
case MODEKEYCOPY_HALFPAGEDOWN:
n = screen_size_y(s) / 2;
@@ -493,6 +496,7 @@ window_copy_key(struct window_pane *wp, struct session 
*sess, int key)
}
window_copy_update_selection(wp);
window_copy_redraw_screen(wp);
+   window_copy_cursor_back_to_indentation(wp);
break;
case MODEKEYCOPY_TOPLINE:
data->cx = 0;
-- 
1.8.2.1


--
This SF.net email is sponsored by Windows:

Build for Windows Store.

http://p.sf.net/sfu/windows-dev2dev
___
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users


[PATCH 6/6] Whitespace fixes

2013-06-11 Thread Ben Boeckel
---
 mode-key.c| 8 
 status.c  | 2 +-
 window-copy.c | 2 +-
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/mode-key.c b/mode-key.c
index 2d6d468..1ed6d40 100644
--- a/mode-key.c
+++ b/mode-key.c
@@ -231,8 +231,8 @@ const struct mode_key_entry mode_key_vi_choice[] = {
{ KEYC_UP | KEYC_CTRL,  0, MODEKEYCHOICE_SCROLLUP },
{ KEYC_UP,  0, MODEKEYCHOICE_UP },
{ ' ',  0, MODEKEYCHOICE_TREE_TOGGLE },
-   { KEYC_LEFT,0, MODEKEYCHOICE_TREE_COLLAPSE },
-   { KEYC_RIGHT,   0, MODEKEYCHOICE_TREE_EXPAND },
+   { KEYC_LEFT,0, MODEKEYCHOICE_TREE_COLLAPSE },
+   { KEYC_RIGHT,   0, MODEKEYCHOICE_TREE_EXPAND },
{ KEYC_LEFT | KEYC_CTRL,0, MODEKEYCHOICE_TREE_COLLAPSE_ALL },
{ KEYC_RIGHT | KEYC_CTRL,   0, MODEKEYCHOICE_TREE_EXPAND_ALL },
 
@@ -373,8 +373,8 @@ const struct mode_key_entry mode_key_emacs_choice[] = {
{ KEYC_UP | KEYC_CTRL,  0, MODEKEYCHOICE_SCROLLUP },
{ KEYC_UP,  0, MODEKEYCHOICE_UP },
{ ' ',  0, MODEKEYCHOICE_TREE_TOGGLE },
-   { KEYC_LEFT,0, MODEKEYCHOICE_TREE_COLLAPSE },
-   { KEYC_RIGHT,   0, MODEKEYCHOICE_TREE_EXPAND },
+   { KEYC_LEFT,0, MODEKEYCHOICE_TREE_COLLAPSE },
+   { KEYC_RIGHT,   0, MODEKEYCHOICE_TREE_EXPAND },
{ KEYC_LEFT | KEYC_CTRL,0, MODEKEYCHOICE_TREE_COLLAPSE_ALL },
{ KEYC_RIGHT | KEYC_CTRL,   0, MODEKEYCHOICE_TREE_EXPAND_ALL },
 
diff --git a/status.c b/status.c
index 351852e..a843507 100644
--- a/status.c
+++ b/status.c
@@ -143,7 +143,7 @@ status_set_window_at(struct client *c, u_int x)
x += c->wlmouse;
RB_FOREACH(wl, winlinks, &s->windows) {
if (x < wl->status_width &&
-   session_select(s, wl->idx) == 0) {
+   session_select(s, wl->idx) == 0) {
server_redraw_session(s);
}
x -= wl->status_width + 1;
diff --git a/window-copy.c b/window-copy.c
index 17808b8..7e078bd 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -1138,7 +1138,7 @@ window_copy_write_line(
struct options  *oo = &wp->window->options;
struct grid_cell gc;
char hdr[32];
-   size_t   last, xoff = 0, size = 0;
+   size_t   last, xoff = 0, size = 0;
 
window_mode_attrs(&gc, oo);
 
-- 
1.8.2.1


--
This SF.net email is sponsored by Windows:

Build for Windows Store.

http://p.sf.net/sfu/windows-dev2dev
___
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users


[PATCH 3/6] Don't loop in copy mode on first and last lines

2013-06-11 Thread Ben Boeckel
Because we first move to the first column, then down, on the last line,
the first column is to "the right" of the last column. To fix this, the
current line is checked and if it changed, *then* the start of the line
motion is used. Similar logic is done for the first line.
---
 window-copy.c | 17 +++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/window-copy.c b/window-copy.c
index b8286e8..325c29f 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -1622,10 +1622,16 @@ void
 window_copy_cursor_left(struct window_pane *wp)
 {
struct window_copy_mode_data*data = wp->modedata;
+   u_intpy;
 
if (data->cx == 0) {
+   /* Remember the current line. */
+   py = data->cy;
+
window_copy_cursor_up(wp, 0);
-   window_copy_cursor_end_of_line(wp);
+
+   if (data->cy != py)
+   window_copy_cursor_end_of_line(wp);
} else {
window_copy_update_cursor(wp, data->cx - 1, data->cy);
if (window_copy_update_selection(wp))
@@ -1647,8 +1653,15 @@ window_copy_cursor_right(struct window_pane *wp)
}
 
if (data->cx >= px) {
-   window_copy_cursor_start_of_line(wp);
+   /* Remember the current line. */
+   py = data->cy;
+
window_copy_cursor_down(wp, 0);
+
+   /* If there is no line after the current line, there
+* is no need to move to the start of the line. */
+   if (data->cy != py)
+   window_copy_cursor_start_of_line(wp);
} else {
window_copy_update_cursor(wp, data->cx + 1, data->cy);
if (window_copy_update_selection(wp))
-- 
1.8.2.1


--
This SF.net email is sponsored by Windows:

Build for Windows Store.

http://p.sf.net/sfu/windows-dev2dev
___
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users


[PATCH 1/6] Implement 's', 'S', and 'C' vi keybindings

2013-06-11 Thread Ben Boeckel
---
 mode-key.c | 13 ++---
 status.c   |  3 +++
 tmux.h |  3 +++
 3 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/mode-key.c b/mode-key.c
index 94115eb..2d6d468 100644
--- a/mode-key.c
+++ b/mode-key.c
@@ -35,9 +35,7 @@
  *
  * vi command mode is handled by having a mode flag in the struct which allows
  * two sets of bindings to be swapped between. A couple of editing commands
- * (MODEKEYEDIT_SWITCHMODE, MODEKEYEDIT_SWITCHMODEAPPEND,
- * MODEKEYEDIT_SWITCHMODEAPPENDLINE, and MODEKEYEDIT_SWITCHMODEBEGINLINE)
- * are special-cased to do this.
+ * (any matching MODEKEYEDIT_SWITCHMODE*) are special-cased to do this.
  */
 
 /* Edit keys command strings. */
@@ -67,6 +65,9 @@ const struct mode_key_cmdstr mode_key_cmdstr_edit[] = {
{ MODEKEYEDIT_SWITCHMODEAPPEND, "switch-mode-append" },
{ MODEKEYEDIT_SWITCHMODEAPPENDLINE, "switch-mode-append-line" },
{ MODEKEYEDIT_SWITCHMODEBEGINLINE, "switch-mode-begin-line" },
+   { MODEKEYEDIT_SWITCHMODECHANGELINE, "switch-mode-change-line" },
+   { MODEKEYEDIT_SWITCHMODESUBSTITUTE, "switch-mode-substitute" },
+   { MODEKEYEDIT_SWITCHMODESUBSTITUTELINE, "switch-mode-substitute-line" },
{ MODEKEYEDIT_TRANSPOSECHARS, "transpose-chars" },
 
{ 0, NULL }
@@ -166,9 +167,11 @@ const struct mode_key_entry mode_key_vi_edit[] = {
{ '0',  1, MODEKEYEDIT_STARTOFLINE },
{ 'A',  1, MODEKEYEDIT_SWITCHMODEAPPENDLINE },
{ 'B',  1, MODEKEYEDIT_PREVIOUSSPACE },
+   { 'C',  1, MODEKEYEDIT_SWITCHMODECHANGELINE },
{ 'D',  1, MODEKEYEDIT_DELETETOENDOFLINE },
{ 'E',  1, MODEKEYEDIT_NEXTSPACEEND },
{ 'I',  1, MODEKEYEDIT_SWITCHMODEBEGINLINE },
+   { 'S',  1, MODEKEYEDIT_SWITCHMODESUBSTITUTELINE },
{ 'W',  1, MODEKEYEDIT_NEXTSPACE },
{ 'X',  1, MODEKEYEDIT_BACKSPACE },
{ '\003' /* C-c */, 1, MODEKEYEDIT_CANCEL },
@@ -185,6 +188,7 @@ const struct mode_key_entry mode_key_vi_edit[] = {
{ 'k',  1, MODEKEYEDIT_HISTORYUP },
{ 'l',  1, MODEKEYEDIT_CURSORRIGHT },
{ 'p',  1, MODEKEYEDIT_PASTE },
+   { 's',  1, MODEKEYEDIT_SWITCHMODESUBSTITUTE },
{ 'w',  1, MODEKEYEDIT_NEXTWORD },
{ 'x',  1, MODEKEYEDIT_DELETE },
{ KEYC_BSPACE,  1, MODEKEYEDIT_BACKSPACE },
@@ -545,6 +549,9 @@ mode_key_lookup(struct mode_key_data *mdata, int key, const 
char **arg)
case MODEKEYEDIT_SWITCHMODEAPPEND:
case MODEKEYEDIT_SWITCHMODEAPPENDLINE:
case MODEKEYEDIT_SWITCHMODEBEGINLINE:
+   case MODEKEYEDIT_SWITCHMODECHANGELINE:
+   case MODEKEYEDIT_SWITCHMODESUBSTITUTE:
+   case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE:
mdata->mode = 1 - mdata->mode;
/* FALLTHROUGH */
default:
diff --git a/status.c b/status.c
index 385ec8e..de56750 100644
--- a/status.c
+++ b/status.c
@@ -1099,6 +1099,7 @@ status_prompt_key(struct client *c, int key)
}
break;
case MODEKEYEDIT_DELETE:
+   case MODEKEYEDIT_SWITCHMODESUBSTITUTE:
if (c->prompt_index != size) {
memmove(c->prompt_buffer + c->prompt_index,
c->prompt_buffer + c->prompt_index + 1,
@@ -1107,11 +1108,13 @@ status_prompt_key(struct client *c, int key)
}
break;
case MODEKEYEDIT_DELETELINE:
+   case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE:
*c->prompt_buffer = '\0';
c->prompt_index = 0;
c->flags |= CLIENT_STATUS;
break;
case MODEKEYEDIT_DELETETOENDOFLINE:
+   case MODEKEYEDIT_SWITCHMODECHANGELINE:
if (c->prompt_index < size) {
c->prompt_buffer[c->prompt_index] = '\0';
c->flags |= CLIENT_STATUS;
diff --git a/tmux.h b/tmux.h
index 9ab9f58..3ae4195 100644
--- a/tmux.h
+++ b/tmux.h
@@ -544,6 +544,9 @@ enum mode_key_cmd {
MODEKEYEDIT_SWITCHMODEAPPEND,
MODEKEYEDIT_SWITCHMODEAPPENDLINE,
MODEKEYEDIT_SWITCHMODEBEGINLINE,
+   MODEKEYEDIT_SWITCHMODECHANGELINE,
+   MODEKEYEDIT_SWITCHMODESUBSTITUTE,
+   MODEKEYEDIT_SWITCHMODESUBSTITUTELINE,
MODEKEYEDIT_TRANSPOSECHARS,
 
/* Menu (choice) keys. */
-- 
1.8.2.1


--
This SF.net email is sponsored by Windows:

Build for Windows Store.

http://p.sf.net/sfu/windows-dev2dev
___
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users


[PATCH 0/6] Various vi-ish patches

2013-06-11 Thread Ben Boeckel
This patchset contains various fixes and changes to behave more like vi
(commands referenced by their vi keybindings since that's what I'm familiar
with). The patches are fairly distinct (though the EOL tracking should
go after the looping fix). I have done light spot testing, but will be
using them in my local tmux build to further work them.

Fixes:

  - Backs up a character with the 'e' and 'E' motions to match vi if the vi
keybindings are selected in the statusbar and copy mode.
  - Looping when moving left on the first line and right on the last line in
copy mode.

Features:

  - New 's', 'S', and 'C' commands when editing on the status line. These
commands enter insert mode while replacing the current character, the
entire line, and the text to the end of the line, respectively. I do not
think that emacs has equivalents of these since they are normal mode
commands (C-k for 'S' behavior is already bound).

Behavior changes:

  - EOL tracking in copy mode. When '$' is used when navigating in copy
mode to go the the end of a line, this fact is forgotten when moving
up and down between lines. Any other motion seems to reset this flag
in vi, so that behavior is copied here.
  - Moving to the "front" of the line on page-wise motions. When
moving page-wise, the cursor can end up hanging off the end of the
text of the new line. Vim seems to do '^' after page-wise movements
which seems to me to be a good compromise. If this should be guarded
by a rectflag check or only happen for vi keybindings, that can be
changed.

Nits:

  - Some whitespace errors I came across while implementing the changes (take
as you see fit).

Ben Boeckel (6):
  Implement 's', 'S', and 'C' vi keybindings
  Act like vi with word motions in the statusline
  Don't loop in copy mode on first and last lines
  Implement EOL tracking
  Move to the indentation of a line on scrolling
  Whitespace fixes

 mode-key.c| 21 ---
 status.c  | 10 -
 tmux.h|  3 +++
 window-copy.c | 66 ---
 4 files changed, 85 insertions(+), 15 deletions(-)

-- 
1.8.2.1


--
This SF.net email is sponsored by Windows:

Build for Windows Store.

http://p.sf.net/sfu/windows-dev2dev
___
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users


[PATCH 2/6] Act like vi with word motions in the statusline

2013-06-11 Thread Ben Boeckel
Vi uses the end-of-word character as the cursor location after an 'e' or
'E' motion. Once the motion is complete, we back up one character from
where emacs ends up (one-past-the-word).
---
 status.c  | 5 +
 window-copy.c | 6 ++
 2 files changed, 11 insertions(+)

diff --git a/status.c b/status.c
index de56750..351852e 100644
--- a/status.c
+++ b/status.c
@@ -1193,6 +1193,11 @@ status_prompt_key(struct client *c, int key)
break;
}
 
+   /* Back up to the end-of-word like vi. */
+   if ((MODEKEY_VI == options_get_number(oo, "status-keys")) &&
+   c->prompt_index)
+   c->prompt_index--;
+
c->flags |= CLIENT_STATUS;
break;
case MODEKEYEDIT_PREVIOUSSPACE:
diff --git a/window-copy.c b/window-copy.c
index 51a8f10..b8286e8 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -1894,6 +1894,7 @@ void
 window_copy_cursor_next_word_end(struct window_pane *wp, const char 
*separators)
 {
struct window_copy_mode_data*data = wp->modedata;
+   struct options  *oo = &wp->window->options;
struct screen   *back_s = data->backing;
u_intpx, py, xx, yy;
int  expected = 1;
@@ -1927,6 +1928,11 @@ window_copy_cursor_next_word_end(struct window_pane *wp, 
const char *separators)
expected = !expected;
} while (expected == 0);
 
+   /* Back up to the end-of-word like vi. */
+   if ((MODEKEY_VI == options_get_number(oo, "status-keys")) &&
+   px)
+   --px;
+
window_copy_update_cursor(wp, px, data->cy);
if (window_copy_update_selection(wp))
window_copy_redraw_lines(wp, data->cy, 1);
-- 
1.8.2.1


--
This SF.net email is sponsored by Windows:

Build for Windows Store.

http://p.sf.net/sfu/windows-dev2dev
___
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users


[PATCH 4/6] Implement EOL tracking

2013-06-11 Thread Ben Boeckel
When the EOL command is used, vim tracks that the EOL is the cursor
position, not the cursor that the EOL happened to occur on for the line
where the motion was given. To keep track of this, an EOL flag is added
to indicate this state. Most motions will clear this state, but a few
such as block mode toggle, up, down and right (on the last line)
preserve this state.

This change has the biggest impact on the block selection mode and
navigation (the cursor "sticks" to the end of the line rather than a
column when moving up and down). Block selection mode already has a "end
of screen" state, so no extra state is tracked for it.
---
 window-copy.c | 39 ++-
 1 file changed, 34 insertions(+), 5 deletions(-)

diff --git a/window-copy.c b/window-copy.c
index 325c29f..ee6a2e8 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -131,6 +131,7 @@ struct window_copy_mode_data {
u_int   sely;
 
u_int   rectflag; /* are we in rectangle copy mode? */
+   u_int   eolflag; /* are we at the end of the line? */
 
u_int   cx;
u_int   cy;
@@ -169,6 +170,7 @@ window_copy_init(struct window_pane *wp)
data->backing_written = 0;
 
data->rectflag = 0;
+   data->eolflag = 0;
 
data->inputtype = WINDOW_COPY_OFF;
data->inputprompt = NULL;
@@ -365,11 +367,18 @@ window_copy_key(struct window_pane *wp, struct session 
*sess, int key)
const char  *word_separators;
struct window_copy_mode_data*data = wp->modedata;
struct screen   *s = &data->screen;
-   u_intn;
+   u_intn, old_eolflag;
int  np, keys;
enum mode_key_cmdcmd;
const char  *arg;
 
+   /* By default, all motions break the cursor from the sticky "eol"
+* position. Only select commands preserve the state (up, down, the eol
+* motion itself and right (which may clear it on its own). These
+* commands should restore the eolflag as needed. */
+   old_eolflag = data->eolflag;
+   data->eolflag = 0;
+
np = data->numprefix;
if (np <= 0)
np = 1;
@@ -420,22 +429,29 @@ window_copy_key(struct window_pane *wp, struct session 
*sess, int key)
window_copy_cursor_left(wp);
break;
case MODEKEYCOPY_RIGHT:
+   /* If the EOL flag needs clearing, window_copy_cursor_right
+* clears it. */
+   data->eolflag = old_eolflag;
for (; np != 0; np--)
window_copy_cursor_right(wp);
break;
case MODEKEYCOPY_UP:
+   data->eolflag = old_eolflag;
for (; np != 0; np--)
window_copy_cursor_up(wp, 0);
break;
case MODEKEYCOPY_DOWN:
+   data->eolflag = old_eolflag;
for (; np != 0; np--)
window_copy_cursor_down(wp, 0);
break;
case MODEKEYCOPY_SCROLLUP:
+   data->eolflag = old_eolflag;
for (; np != 0; np--)
window_copy_cursor_up(wp, 1);
break;
case MODEKEYCOPY_SCROLLDOWN:
+   data->eolflag = old_eolflag;
for (; np != 0; np--)
window_copy_cursor_down(wp, 1);
break;
@@ -559,6 +575,7 @@ window_copy_key(struct window_pane *wp, struct session 
*sess, int key)
window_copy_cursor_back_to_indentation(wp);
break;
case MODEKEYCOPY_ENDOFLINE:
+   data->eolflag = 1;
window_copy_cursor_end_of_line(wp);
break;
case MODEKEYCOPY_NEXTSPACE:
@@ -707,6 +724,7 @@ window_copy_key(struct window_pane *wp, struct session 
*sess, int key)
}
break;
case MODEKEYCOPY_RECTANGLETOGGLE:
+   data->eolflag = old_eolflag;
window_copy_rectangle_toggle(wp);
break;
default:
@@ -1600,8 +1618,13 @@ window_copy_cursor_end_of_line(struct window_pane *wp)
px = window_copy_find_length(wp, py);
 
if (data->cx == px) {
-   if (data->screen.sel.flag && data->rectflag)
+   if (data->screen.sel.flag && data->rectflag) {
px = screen_size_x(back_s);
+   /* The flag is reset here since we're not on the
+* end-of-line as defined by the text, but rather by
+* the screen. */
+   data->eolflag = 0;
+   }
if (gd->linedata[py].flags & GRID_LINE_WRAPPED) {
while (py < gd->sy + gd->hsize &&
gd->linedata[py].flags