This patch allows you to supply repeat prefixes to commands in
copy-mode. For instance, to move forward 10 characters, you'd type ‘10l’
("ten ell") in vim, and ‘M-1 0 C-f´ in emacs (of course, you could use
the right arrow-key instead of ell or C-f).

The prefix keys are actually bound commands (to "start-number-prefix"),
 so may be unbound if anyone finds them to be annoying, or you could
rebind the emacs ones so you don't have to hold Alt while typing the
first digit. They're mildly magical, as they trigger
numeric-prefix-input mode, and (of course) insert themselves (sans any
modifiers) as the first digit of the prefix.  The entry mode is
automatically ended when you type a non-digit, and then the prefix is
used for the command corresponding to the key you terminated with (if
any). The "start-number-prefix" command performs no action when
triggered by a non-digit key.

While these prefixes are currently only used for repeating a command,
they don't necessarily have to have this semantic. For instance, the
current "Goto Line" feature in copy mode would be much more natural to
vi-mode as <line-number> G. Perhaps I will write a patch for that as
well at some point, though I doubt I will unless there's demand for it,
as unlike in Emacs or Vim, I _never_ wish to go to an arbitrary line in
tmux's copy-mode.

Under-the-hood note: using this feature under the current patch results
in spurious grid-redrawing, at least internally. Especially for commands
that cause scrolling to take place. I don't really care, though, and I'm
betting Nicholas doesn't either, since a patch to address that would be
much more intrusive.

This patch applies cleanly after my other recent patches; it will
require minor adjustment if applied without those.

-- 
Micah J. Cowan
http://micah.cowan.name/
Index: mode-key.c
===================================================================
--- mode-key.c.orig
+++ mode-key.c
@@ -106,6 +106,7 @@
 	{ MODEKEYCOPY_SEARCHDOWN, "search-forward" },
 	{ MODEKEYCOPY_SEARCHREVERSE, "search-reverse" },
 	{ MODEKEYCOPY_SEARCHUP, "search-backward" },
+	{ MODEKEYCOPY_STARTNUMBERPREFIX, "start-number-prefix" },
 	{ MODEKEYCOPY_STARTOFLINE, "start-of-line" },
 	{ MODEKEYCOPY_STARTSELECTION, "begin-selection" },
 	{ MODEKEYCOPY_TOPLINE, "top-line" },
@@ -178,6 +179,15 @@
 	{ '$',			0, MODEKEYCOPY_ENDOFLINE },
 	{ '/',			0, MODEKEYCOPY_SEARCHDOWN },
 	{ '0',			0, MODEKEYCOPY_STARTOFLINE },
+	{ '1',			0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '2',			0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '3',			0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '4',			0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '5',			0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '6',			0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '7',			0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '8',			0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '9',			0, MODEKEYCOPY_STARTNUMBERPREFIX },
 	{ ':',			0, MODEKEYCOPY_GOTOLINE },
 	{ '?',			0, MODEKEYCOPY_SEARCHUP },
 	{ 'B',			0, MODEKEYCOPY_PREVIOUSSPACE },
@@ -280,6 +290,15 @@
 /* emacs copy mode keys. */
 const struct mode_key_entry mode_key_emacs_copy[] = {
 	{ ' ',			0, MODEKEYCOPY_NEXTPAGE },
+	{ '1' | KEYC_ESCAPE,	0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '2' | KEYC_ESCAPE,	0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '3' | KEYC_ESCAPE,	0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '4' | KEYC_ESCAPE,	0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '5' | KEYC_ESCAPE,	0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '6' | KEYC_ESCAPE,	0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '7' | KEYC_ESCAPE,	0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '8' | KEYC_ESCAPE,	0, MODEKEYCOPY_STARTNUMBERPREFIX },
+	{ '9' | KEYC_ESCAPE,	0, MODEKEYCOPY_STARTNUMBERPREFIX },
 	{ '<' | KEYC_ESCAPE,    0, MODEKEYCOPY_HISTORYTOP },
 	{ '>' | KEYC_ESCAPE,    0, MODEKEYCOPY_HISTORYBOTTOM },
 	{ 'R' | KEYC_ESCAPE,	0, MODEKEYCOPY_TOPLINE },
Index: tmux.h
===================================================================
--- tmux.h.orig
+++ tmux.h
@@ -476,6 +476,7 @@
 	MODEKEYCOPY_SEARCHDOWN,
 	MODEKEYCOPY_SEARCHREVERSE,
 	MODEKEYCOPY_SEARCHUP,
+	MODEKEYCOPY_STARTNUMBERPREFIX,
 	MODEKEYCOPY_STARTOFLINE,
 	MODEKEYCOPY_STARTSELECTION,
 	MODEKEYCOPY_TOPLINE,
Index: window-copy.c
===================================================================
--- window-copy.c.orig
+++ window-copy.c
@@ -28,6 +28,7 @@
 void	window_copy_resize(struct window_pane *, u_int, u_int);
 void	window_copy_key(struct window_pane *, struct client *, int);
 int	window_copy_key_input(struct window_pane *, int);
+int	window_copy_key_numeric_prefix(struct window_pane *, int);
 void	window_copy_mouse(
 	    struct window_pane *, struct client *, struct mouse_event *);
 
@@ -81,6 +82,7 @@
 
 enum window_copy_input_type {
 	WINDOW_COPY_OFF,
+	WINDOW_COPY_NUMERICPREFIX,
 	WINDOW_COPY_SEARCHUP,
 	WINDOW_COPY_SEARCHDOWN,
 	WINDOW_COPY_GOTOLINE,
@@ -112,10 +114,22 @@
 	const char     *inputprompt;
 	char   	       *inputstr;
 
+	unsigned long	numprefix;
+
 	enum window_copy_input_type searchtype;
 	char	       *searchstr;
 };
 
+/* NOTE: These macros evaluate their args multiple times. */
+#define C_ISDIGIT(x)		((x) >= '0' && (x) <= '9')
+
+/* Loop while there's a prefix (or 1, if there isn't one).
+ * Requires the variable "data", pointing at an instance of
+ * struct window_copy_mode_data. */
+#define REPEAT_NUMPREFIX_TIMES	\
+	if (data->numprefix == 0) data->numprefix++; \
+	while (data->numprefix--)
+
 struct screen *
 window_copy_init(struct window_pane *wp)
 {
@@ -138,6 +152,7 @@
 	data->inputtype = WINDOW_COPY_OFF;
 	data->inputprompt = NULL;
 	data->inputstr = xstrdup("");
+	data->numprefix = 0;
 
 	data->searchtype = WINDOW_COPY_OFF;
 	data->searchstr = NULL;
@@ -327,7 +342,12 @@
 	int				 keys;
 	enum mode_key_cmd		 cmd;
 
-	if (data->inputtype != WINDOW_COPY_OFF) {
+	if (data->inputtype == WINDOW_COPY_NUMERICPREFIX) {
+		if (window_copy_key_numeric_prefix(wp, key) == 0)
+			return;
+		data->inputtype = WINDOW_COPY_OFF;
+		window_copy_redraw_lines(wp, screen_size_y(s) - 1, 1);
+	} else if (data->inputtype != WINDOW_COPY_OFF) {
 		if (window_copy_key_input(wp, key) != 0)
 			goto input_off;
 		return;
@@ -336,55 +356,69 @@
 	cmd = mode_key_lookup(&data->mdata, key);
 	switch (cmd) {
 	case MODEKEYCOPY_CANCEL:
-		window_pane_reset_mode(wp);
+		REPEAT_NUMPREFIX_TIMES
+			window_pane_reset_mode(wp);
 		break;
 	case MODEKEYCOPY_LEFT:
-		window_copy_cursor_left(wp);
-		return;
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_left(wp);
+		break;
 	case MODEKEYCOPY_RIGHT:
-		window_copy_cursor_right(wp);
-		return;
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_right(wp);
+		break;
 	case MODEKEYCOPY_UP:
-		window_copy_cursor_up(wp, 0);
-		return;
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_up(wp, 0);
+		break;
 	case MODEKEYCOPY_DOWN:
-		window_copy_cursor_down(wp, 0);
-		return;
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_down(wp, 0);
+		break;
 	case MODEKEYCOPY_SCROLLUP:
-		window_copy_cursor_up(wp, 1);
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_up(wp, 1);
 		break;
 	case MODEKEYCOPY_SCROLLDOWN:
-		window_copy_cursor_down(wp, 1);
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_down(wp, 1);
 		break;
 	case MODEKEYCOPY_PREVIOUSPAGE:
-		window_copy_pageup(wp);
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_pageup(wp);
 		break;
 	case MODEKEYCOPY_NEXTPAGE:
 		n = 1;
 		if (screen_size_y(s) > 2)
 			n = screen_size_y(s) - 2;
-		if (data->oy < n)
-			data->oy = 0;
-		else
-			data->oy -= n;
+		REPEAT_NUMPREFIX_TIMES {
+			if (data->oy < n)
+				data->oy = 0;
+			else
+				data->oy -= n;
+		}
 		window_copy_update_selection(wp);
 		window_copy_redraw_screen(wp);
 		break;
 	case MODEKEYCOPY_HALFPAGEUP:
 		n = screen_size_y(s) / 2;
-		if (data->oy + n > screen_hsize(data->backing))
-			data->oy = screen_hsize(data->backing);
-		else
-			data->oy += n;
+		REPEAT_NUMPREFIX_TIMES {
+			if (data->oy + n > screen_hsize(data->backing))
+				data->oy = screen_hsize(data->backing);
+			else
+				data->oy += n;
+		}
 		window_copy_update_selection(wp);
 		window_copy_redraw_screen(wp);
 		break;
 	case MODEKEYCOPY_HALFPAGEDOWN:
 		n = screen_size_y(s) / 2;
-		if (data->oy < n)
-			data->oy = 0;
-		else
-			data->oy -= n;
+		REPEAT_NUMPREFIX_TIMES {
+			if (data->oy < n)
+				data->oy = 0;
+			else
+				data->oy -= n;
+		}
 		window_copy_update_selection(wp);
 		window_copy_redraw_screen(wp);
 		break;
@@ -444,28 +478,34 @@
 		window_copy_cursor_end_of_line(wp);
 		break;
 	case MODEKEYCOPY_NEXTSPACE:
-		window_copy_cursor_next_word(wp, " ");
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_next_word(wp, " ");
 		break;
 	case MODEKEYCOPY_NEXTSPACEEND:
-		window_copy_cursor_next_word_end(wp, " ");
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_next_word_end(wp, " ");
 		break;
 	case MODEKEYCOPY_NEXTWORD:
 		word_separators =
 		    options_get_string(&wp->window->options, "word-separators");
-		window_copy_cursor_next_word(wp, word_separators);
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_next_word(wp, word_separators);
 		break;
 	case MODEKEYCOPY_NEXTWORDEND:
 		word_separators =
 		    options_get_string(&wp->window->options, "word-separators");
-		window_copy_cursor_next_word_end(wp, word_separators);
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_next_word_end(wp, word_separators);
 		break;
 	case MODEKEYCOPY_PREVIOUSSPACE:
-		window_copy_cursor_previous_word(wp, " ");
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_previous_word(wp, " ");
 		break;
 	case MODEKEYCOPY_PREVIOUSWORD:
 		word_separators =
 		    options_get_string(&wp->window->options, "word-separators");
-		window_copy_cursor_previous_word(wp, word_separators);
+		REPEAT_NUMPREFIX_TIMES
+			window_copy_cursor_previous_word(wp, word_separators);
 		break;
 	case MODEKEYCOPY_SEARCHUP:
 		data->inputtype = WINDOW_COPY_SEARCHUP;
@@ -480,18 +520,29 @@
 		switch (data->searchtype) {
 		case WINDOW_COPY_OFF:
 		case WINDOW_COPY_GOTOLINE:
+		case WINDOW_COPY_NUMERICPREFIX:
 			break;
 		case WINDOW_COPY_SEARCHUP:
-			if (cmd == MODEKEYCOPY_SEARCHAGAIN)
-				window_copy_search_up(wp, data->searchstr);
-			else
-				window_copy_search_down(wp, data->searchstr);
+			if (cmd == MODEKEYCOPY_SEARCHAGAIN) {
+				REPEAT_NUMPREFIX_TIMES
+					window_copy_search_up(
+					    wp, data->searchstr);
+			} else {
+				REPEAT_NUMPREFIX_TIMES
+					window_copy_search_down(
+					    wp, data->searchstr);
+			}
 			break;
 		case WINDOW_COPY_SEARCHDOWN:
-			if (cmd == MODEKEYCOPY_SEARCHAGAIN)
-				window_copy_search_down(wp, data->searchstr);
-			else
-				window_copy_search_up(wp, data->searchstr);
+			if (cmd == MODEKEYCOPY_SEARCHAGAIN) {
+				REPEAT_NUMPREFIX_TIMES
+					window_copy_search_down(
+					    wp, data->searchstr);
+			} else {
+				REPEAT_NUMPREFIX_TIMES
+					window_copy_search_up(
+					    wp, data->searchstr);
+			}
 			break;
 		}
 		break;
@@ -500,13 +551,26 @@
 		data->inputprompt = "Goto Line";
 		*data->inputstr = '\0';
 		goto input_on;
+	case MODEKEYCOPY_STARTNUMBERPREFIX:
+		if (C_ISDIGIT(key & 0xff)) {
+			data->inputtype = WINDOW_COPY_NUMERICPREFIX;
+			data->numprefix = 0;
+			window_copy_key_numeric_prefix(wp, key & 0xff);
+			return;
+		} else {
+			/* Ignore this command for non-digit keys. */
+		}
+		break;
 	case MODEKEYCOPY_RECTANGLETOGGLE:
 		window_copy_rectangle_toggle(wp);
-		return;
+		break;
 	default:
 		break;
 	}
 
+	/* Remove numeric prefix after processing the command. */
+	data->numprefix = 0;
+
 	return;
 
 input_on:
@@ -553,6 +617,7 @@
 	case MODEKEYEDIT_ENTER:
 		switch (data->inputtype) {
 		case WINDOW_COPY_OFF:
+		case WINDOW_COPY_NUMERICPREFIX:
 			break;
 		case WINDOW_COPY_SEARCHUP:
 			window_copy_search_up(wp, data->inputstr);
@@ -587,6 +652,20 @@
 	return (0);
 }
 
+int
+window_copy_key_numeric_prefix(struct window_pane *wp, int key)
+{
+	struct window_copy_mode_data	*data = wp->modedata;
+	struct screen			*s = &data->screen;
+
+	if (!C_ISDIGIT(key))
+		return 1;
+	data->numprefix = data->numprefix * 10 + (key - '0');
+
+	window_copy_redraw_lines(wp, screen_size_y(s) - 1, 1);
+	return 0;
+}
+
 /* ARGSUSED */
 void
 window_copy_mouse(
@@ -855,8 +934,13 @@
 		screen_write_cursormove(ctx, screen_size_x(s) - size, 0);
 		screen_write_puts(ctx, &gc, "%s", hdr);
 	} else if (py == last && data->inputtype != WINDOW_COPY_OFF) {
-		xoff = size = xsnprintf(hdr, sizeof hdr,
-		    "%s: %s", data->inputprompt, data->inputstr);
+		if (data->inputtype == WINDOW_COPY_NUMERICPREFIX) {
+			xoff = size = xsnprintf(hdr, sizeof hdr,
+			    "(Prefix) %lu", data->numprefix);
+		} else {
+			xoff = size = xsnprintf(hdr, sizeof hdr,
+			    "%s: %s", data->inputprompt, data->inputstr);
+		}
 		screen_write_cursormove(ctx, 0, last);
 		screen_write_puts(ctx, &gc, "%s", hdr);
 	} else
Index: tmux.1
===================================================================
--- tmux.1.orig
+++ tmux.1
@@ -591,6 +591,16 @@
 The three next and previous space keys work similarly but use a space alone as
 the word separator.
 .Pp
+Most key commands in copy mode may be prefaced by an optional
+repeat count. In the vi bindings, you may just type the number before
+the command you desire; in the emacs bindings, you must hold the Alt
+(meta) key while you press the first digit. For example, to move the
+cursor forward by ten words, you would type
+.Ql M-1 0 M-f
+in emacs mode, and
+.Ql 10w
+in vi.
+.Pp
 These key bindings are defined in a set of named tables:
 .Em vi-edit
 and
------------------------------------------------------------------------------
SOLARIS 10 is the OS for Data Centers - provides features such as DTrace,
Predictive Self Healing and Award Winning ZFS. Get Solaris 10 NOW
http://p.sf.net/sfu/solaris-dev2dev
_______________________________________________
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users

Reply via email to