vi: improvements to range selection

Rewrite find_range(), pushing quite a bit of code from do_cmd()
down into it.

- The commands 'y', 'd', 'c', '<' and '>' can be given twice to
  specify a whole-line range.  BusyBox vi actually accepted any
  second character from that group, e.g. 'dc' or '<y', with the
  latter being accepted even if yank was disabled.  Require the
  two characters to match.

- '<' and '>' commands followed by ESC incorrectly issued an alert.

- Allow search commands and a marker (specified as "y'a", for example)
  to define a range for those operators that support it.

function                                             old     new   delta
find_range                                           518     707    +189
.rodata                                           105119  105133     +14
get_motion_char                                       68       -     -68
do_cmd                                              4860    4695    -165
------------------------------------------------------------------------------
(add/remove: 0/1 grow/shrink: 2/1 up/down: 203/-233)          Total: -30 bytes

Signed-off-by: Ron Yorston <rmy@pobox.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/editors/vi.c b/editors/vi.c
index 5dc14f1..9a17f65 100644
--- a/editors/vi.c
+++ b/editors/vi.c
@@ -3079,22 +3079,38 @@
 	return ((s == end - 2 && s[1] == '\n') || s == end - 1);
 }
 
-static int find_range(char **start, char **stop, char c)
+static int find_range(char **start, char **stop, int cmd)
 {
 	char *save_dot, *p, *q, *t;
 	int buftype = -1;
+	int c;
 
 	save_dot = dot;
 	p = q = dot;
 
-	if (strchr("cdy><", c)) {
+#if ENABLE_FEATURE_VI_YANKMARK
+	if (cmd == 'Y') {
+		c = 'y';
+	} else
+#endif
+	{
+		c = get_motion_char();
+	}
+
+#if ENABLE_FEATURE_VI_YANKMARK
+	if ((cmd == 'Y' || cmd == c) && strchr("cdy><", c)) {
+#else
+	if (cmd == c && strchr("cd><", c)) {
+#endif
 		// these cmds operate on whole lines
 		buftype = WHOLE;
 		if (--cmdcnt > 0)
 			do_cmd('j');
-	} else if (strchr("^%$0bBeEfFtTh|{}\b\177", c)) {
-		// These cmds operate on char positions
-		buftype = PARTIAL;
+	} else if (strchr("^%$0bBeEfFtThnN/?|{}\b\177", c)) {
+		// Most operate on char positions within a line.  Of those that
+		// don't '%' needs no special treatment, search commands are
+		// marked as MULTI and  "{}" are handled below.
+		buftype = strchr("nN/?", c) ? MULTI : PARTIAL;
 		do_cmd(c);		// execute movement cmd
 		if (p == dot)	// no movement is an error
 			buftype = -1;
@@ -3104,7 +3120,16 @@
 		// step back one char, but not if we're at end of file
 		if (dot > p && !at_eof(dot))
 			dot--;
-	} else if (strchr("GHL+-jk\r\n", c)) {
+		t = dot;
+		// don't include trailing WS as part of word
+		while (dot > p && isspace(*dot)) {
+			if (*dot-- == '\n')
+				t = dot;
+		}
+		// for non-change operations WS after NL is not part of word
+		if (cmd != 'c' && dot != p && *dot != '\n')
+			dot = t;
+	} else if (strchr("GHL+-jk'\r\n", c)) {
 		// these operate on whole lines
 		buftype = WHOLE;
 		do_cmd(c);		// execute movement cmd
@@ -3119,8 +3144,11 @@
 			dot--;
 	}
 
-	if (buftype == -1)
+	if (buftype == -1) {
+		if (c != 27)
+			indicate_error();
 		return buftype;
+	}
 
 	q = dot;
 	if (q < p) {
@@ -3131,7 +3159,7 @@
 
 	// movements which don't include end of range
 	if (q > p) {
-		if (strchr("^0bBFTh|\b\177", c)) {
+		if (strchr("^0bBFThnN/?|\b\177", c)) {
 			q--;
 		} else if (strchr("{}", c)) {
 			buftype = (p == begin_line(p) && (*q == '\n' || at_eof(q))) ?
@@ -3144,7 +3172,7 @@
 		}
 	}
 
-	if (buftype == WHOLE) {
+	if (buftype == WHOLE || cmd == '<' || cmd == '>') {
 		p = begin_line(p);
 		q = end_line(q);
 	}
@@ -3582,14 +3610,9 @@
 	case '<':			// <- Left  shift something
 	case '>':			// >- Right shift something
 		cnt = count_lines(text, dot);	// remember what line we are on
-		c1 = get_motion_char();	// get the type of thing to operate on
-		if (find_range(&p, &q, c1) == -1) {
-			indicate_error();
+		if (find_range(&p, &q, c) == -1)
 			goto dc6;
-		}
 		yank_delete(p, q, WHOLE, YANKONLY, NO_UNDO);	// save copy before change
-		p = begin_line(p);
-		q = end_line(q);
 		i = count_lines(p, q);	// # of lines we are shifting
 		for ( ; i > 0; i--, p = next_line(p)) {
 			if (c == '<') {
@@ -3820,39 +3843,17 @@
 	case 'Y':			// Y- Yank a line
 #endif
 	{
+		int yf = YANKDEL;	// assume either "c" or "d"
+		int buftype;
 #if ENABLE_FEATURE_VI_YANKMARK
 		char *savereg = reg[YDreg];
-#endif
-		int yf, buftype = 0;
-		yf = YANKDEL;	// assume either "c" or "d"
-#if ENABLE_FEATURE_VI_YANKMARK
 		if (c == 'y' || c == 'Y')
 			yf = YANKONLY;
 #endif
-		c1 = 'y';
-		if (c != 'Y') {
-			c1 = get_motion_char(); // get the type of thing to operate on
-			if (c1 == 27)	// ESC- user changed mind and wants out
-				goto dc6;
-		}
 		// determine range, and whether it spans lines
-		buftype = find_range(&p, &q, c1);
-		place_cursor(0, 0);
-		if (buftype == -1) { // invalid range
-			indicate_error();
+		buftype = find_range(&p, &q, c);
+		if (buftype == -1)	// invalid range
 			goto dc6;
-		}
-		if (c1 == 'w' || c1 == 'W') {
-			char *q0 = q;
-			// don't include trailing WS as part of word
-			while (q > p && isspace(*q)) {
-				if (*q-- == '\n')
-					q0 = q;
-			}
-			// for non-change operations WS after NL is not part of word
-			if (c != 'c' && q != p && *q != '\n')
-				q = q0;
-		}
 		dot = yank_delete(p, q, buftype, yf, ALLOW_UNDO);	// delete word
 		if (buftype == WHOLE) {
 			if (c == 'c') {