xref: /netbsd/external/bsd/nvi/dist/vi/v_word.c (revision 943888a2)
1*943888a2Srin /*	$NetBSD: v_word.c,v 1.4 2017/11/21 07:43:47 rin Exp $ */
23a571abcSchristos /*-
33a571abcSchristos  * Copyright (c) 1992, 1993, 1994
43a571abcSchristos  *	The Regents of the University of California.  All rights reserved.
53a571abcSchristos  * Copyright (c) 1992, 1993, 1994, 1995, 1996
63a571abcSchristos  *	Keith Bostic.  All rights reserved.
73a571abcSchristos  *
83a571abcSchristos  * See the LICENSE file for redistribution information.
93a571abcSchristos  */
103a571abcSchristos 
113a571abcSchristos #include "config.h"
123a571abcSchristos 
13cc73507aSchristos #include <sys/cdefs.h>
14cc73507aSchristos #if 0
153a571abcSchristos #ifndef lint
163a571abcSchristos static const char sccsid[] = "Id: v_word.c,v 10.6 2001/06/25 15:19:36 skimo Exp  (Berkeley) Date: 2001/06/25 15:19:36 ";
173a571abcSchristos #endif /* not lint */
18cc73507aSchristos #else
19*943888a2Srin __RCSID("$NetBSD: v_word.c,v 1.4 2017/11/21 07:43:47 rin Exp $");
20cc73507aSchristos #endif
213a571abcSchristos 
223a571abcSchristos #include <sys/types.h>
233a571abcSchristos #include <sys/queue.h>
243a571abcSchristos #include <sys/time.h>
253a571abcSchristos 
263a571abcSchristos #include <bitstring.h>
273a571abcSchristos #include <ctype.h>
283a571abcSchristos #include <limits.h>
293a571abcSchristos #include <stdio.h>
303a571abcSchristos 
313a571abcSchristos #include "../common/common.h"
323a571abcSchristos #include "vi.h"
333a571abcSchristos 
343a571abcSchristos /*
353a571abcSchristos  * There are two types of "words".  Bigwords are easy -- groups of anything
363a571abcSchristos  * delimited by whitespace.  Normal words are trickier.  They are either a
373a571abcSchristos  * group of characters, numbers and underscores, or a group of anything but,
383a571abcSchristos  * delimited by whitespace.  When for a word, if you're in whitespace, it's
393a571abcSchristos  * easy, just remove the whitespace and go to the beginning or end of the
403a571abcSchristos  * word.  Otherwise, figure out if the next character is in a different group.
413a571abcSchristos  * If it is, go to the beginning or end of that group, otherwise, go to the
423a571abcSchristos  * beginning or end of the current group.  The historic version of vi didn't
433a571abcSchristos  * get this right, so, for example, there were cases where "4e" was not the
443a571abcSchristos  * same as "eeee" -- in particular, single character words, and commands that
453a571abcSchristos  * began in whitespace were almost always handled incorrectly.  To get it right
463a571abcSchristos  * you have to resolve the cursor after each search so that the look-ahead to
473a571abcSchristos  * figure out what type of "word" the cursor is in will be correct.
483a571abcSchristos  *
493a571abcSchristos  * Empty lines, and lines that consist of only white-space characters count
503a571abcSchristos  * as a single word, and the beginning and end of the file counts as an
513a571abcSchristos  * infinite number of words.
523a571abcSchristos  *
533a571abcSchristos  * Movements associated with commands are different than movement commands.
543a571abcSchristos  * For example, in "abc  def", with the cursor on the 'a', "cw" is from
553a571abcSchristos  * 'a' to 'c', while "w" is from 'a' to 'd'.  In general, trailing white
563a571abcSchristos  * space is discarded from the change movement.  Another example is that,
573a571abcSchristos  * in the same string, a "cw" on any white space character replaces that
583a571abcSchristos  * single character, and nothing else.  Ain't nothin' in here that's easy.
593a571abcSchristos  *
603a571abcSchristos  * One historic note -- in the original vi, the 'w', 'W' and 'B' commands
613a571abcSchristos  * would treat groups of empty lines as individual words, i.e. the command
623a571abcSchristos  * would move the cursor to each new empty line.  The 'e' and 'E' commands
633a571abcSchristos  * would treat groups of empty lines as a single word, i.e. the first use
643a571abcSchristos  * would move past the group of lines.  The 'b' command would just beep at
653a571abcSchristos  * you, or, if you did it from the start of the line as part of a motion
663a571abcSchristos  * command, go absolutely nuts.  If the lines contained only white-space
673a571abcSchristos  * characters, the 'w' and 'W' commands would just beep at you, and the 'B',
683a571abcSchristos  * 'b', 'E' and 'e' commands would treat the group as a single word, and
693a571abcSchristos  * the 'B' and 'b' commands will treat the lines as individual words.  This
703a571abcSchristos  * implementation treats all of these cases as a single white-space word.
71*943888a2Srin  *
72*943888a2Srin  * We regard a boundary between single- and multi-width characters as
73*943888a2Srin  * a word boundary.
743a571abcSchristos  */
753a571abcSchristos 
763a571abcSchristos enum which {BIGWORD, LITTLEWORD};
773a571abcSchristos 
783a571abcSchristos static int bword __P((SCR *, VICMD *, enum which));
793a571abcSchristos static int eword __P((SCR *, VICMD *, enum which));
803a571abcSchristos static int fword __P((SCR *, VICMD *, enum which));
813a571abcSchristos 
823a571abcSchristos /*
833a571abcSchristos  * v_wordW -- [count]W
843a571abcSchristos  *	Move forward a bigword at a time.
853a571abcSchristos  *
863a571abcSchristos  * PUBLIC: int v_wordW __P((SCR *, VICMD *));
873a571abcSchristos  */
883a571abcSchristos int
v_wordW(SCR * sp,VICMD * vp)893a571abcSchristos v_wordW(SCR *sp, VICMD *vp)
903a571abcSchristos {
913a571abcSchristos 	return (fword(sp, vp, BIGWORD));
923a571abcSchristos }
933a571abcSchristos 
943a571abcSchristos /*
953a571abcSchristos  * v_wordw -- [count]w
963a571abcSchristos  *	Move forward a word at a time.
973a571abcSchristos  *
983a571abcSchristos  * PUBLIC: int v_wordw __P((SCR *, VICMD *));
993a571abcSchristos  */
1003a571abcSchristos int
v_wordw(SCR * sp,VICMD * vp)1013a571abcSchristos v_wordw(SCR *sp, VICMD *vp)
1023a571abcSchristos {
1033a571abcSchristos 	return (fword(sp, vp, LITTLEWORD));
1043a571abcSchristos }
1053a571abcSchristos 
1063a571abcSchristos /*
1073a571abcSchristos  * fword --
1083a571abcSchristos  *	Move forward by words.
1093a571abcSchristos  */
1103a571abcSchristos static int
fword(SCR * sp,VICMD * vp,enum which type)1113a571abcSchristos fword(SCR *sp, VICMD *vp, enum which type)
1123a571abcSchristos {
1133a571abcSchristos 	enum { INWORD, NOTWORD } state;
1143a571abcSchristos 	VCS cs;
1153a571abcSchristos 	u_long cnt;
116*943888a2Srin 	int nmw, omw;
1173a571abcSchristos 
1183a571abcSchristos 	cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
1193a571abcSchristos 	cs.cs_lno = vp->m_start.lno;
1203a571abcSchristos 	cs.cs_cno = vp->m_start.cno;
1213a571abcSchristos 	if (cs_init(sp, &cs))
1223a571abcSchristos 		return (1);
1233a571abcSchristos 
1243a571abcSchristos 	/*
1253a571abcSchristos 	 * If in white-space:
1263a571abcSchristos 	 *	If the count is 1, and it's a change command, we're done.
1273a571abcSchristos 	 *	Else, move to the first non-white-space character, which
1283a571abcSchristos 	 *	counts as a single word move.  If it's a motion command,
1293a571abcSchristos 	 *	don't move off the end of the line.
1303a571abcSchristos 	 */
13108d478e3Schristos 	if (cs.cs_flags == CS_EMP || (cs.cs_flags == 0 && ISBLANK2(cs.cs_ch))) {
1323a571abcSchristos 		if (ISMOTION(vp) && cs.cs_flags != CS_EMP && cnt == 1) {
1333a571abcSchristos 			if (ISCMD(vp->rkp, 'c'))
1343a571abcSchristos 				return (0);
1353a571abcSchristos 			if (ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) {
1363a571abcSchristos 				if (cs_fspace(sp, &cs))
1373a571abcSchristos 					return (1);
1383a571abcSchristos 				goto ret;
1393a571abcSchristos 			}
1403a571abcSchristos 		}
1413a571abcSchristos 		if (cs_fblank(sp, &cs))
1423a571abcSchristos 			return (1);
1433a571abcSchristos 		--cnt;
1443a571abcSchristos 	}
1453a571abcSchristos 
1463a571abcSchristos 	/*
1473a571abcSchristos 	 * Cyclically move to the next word -- this involves skipping
1483a571abcSchristos 	 * over word characters and then any trailing non-word characters.
1493a571abcSchristos 	 * Note, for the 'w' command, the definition of a word keeps
1503a571abcSchristos 	 * switching.
1513a571abcSchristos 	 */
1523a571abcSchristos 	if (type == BIGWORD)
1533a571abcSchristos 		while (cnt--) {
154*943888a2Srin 			nmw = ISMULTIWIDTH(sp, cs.cs_ch);
1553a571abcSchristos 			for (;;) {
156*943888a2Srin 				omw = nmw;
1573a571abcSchristos 				if (cs_next(sp, &cs))
1583a571abcSchristos 					return (1);
1593a571abcSchristos 				if (cs.cs_flags == CS_EOF)
1603a571abcSchristos 					goto ret;
161*943888a2Srin 				if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) ||
162*943888a2Srin 				    (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw)
1633a571abcSchristos 					break;
1643a571abcSchristos 			}
1653a571abcSchristos 			/*
1663a571abcSchristos 			 * If a motion command and we're at the end of the
1673a571abcSchristos 			 * last word, we're done.  Delete and yank eat any
1683a571abcSchristos 			 * trailing blanks, but we don't move off the end
1693a571abcSchristos 			 * of the line regardless.
1703a571abcSchristos 			 */
1713a571abcSchristos 			if (cnt == 0 && ISMOTION(vp)) {
1723a571abcSchristos 				if ((ISCMD(vp->rkp, 'd') ||
1733a571abcSchristos 				    ISCMD(vp->rkp, 'y')) &&
1743a571abcSchristos 				    cs_fspace(sp, &cs))
1753a571abcSchristos 					return (1);
1763a571abcSchristos 				break;
1773a571abcSchristos 			}
1783a571abcSchristos 
1793a571abcSchristos 			/* Eat whitespace characters. */
180*943888a2Srin 			if (nmw == omw && cs_fblank(sp, &cs))
1813a571abcSchristos 				return (1);
1823a571abcSchristos 			if (cs.cs_flags == CS_EOF)
1833a571abcSchristos 				goto ret;
1843a571abcSchristos 		}
1853a571abcSchristos 	else
1863a571abcSchristos 		while (cnt--) {
1873a571abcSchristos 			state = cs.cs_flags == 0 &&
1883a571abcSchristos 			    inword(cs.cs_ch) ? INWORD : NOTWORD;
189*943888a2Srin 			nmw = ISMULTIWIDTH(sp, cs.cs_ch);
1903a571abcSchristos 			for (;;) {
191*943888a2Srin 				omw = nmw;
1923a571abcSchristos 				if (cs_next(sp, &cs))
1933a571abcSchristos 					return (1);
1943a571abcSchristos 				if (cs.cs_flags == CS_EOF)
1953a571abcSchristos 					goto ret;
196*943888a2Srin 				if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) ||
197*943888a2Srin 				    (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw)
1983a571abcSchristos 					break;
1993a571abcSchristos 				if (state == INWORD) {
2003a571abcSchristos 					if (!inword(cs.cs_ch))
2013a571abcSchristos 						break;
2023a571abcSchristos 				} else
2033a571abcSchristos 					if (inword(cs.cs_ch))
2043a571abcSchristos 						break;
2053a571abcSchristos 			}
2063a571abcSchristos 			/* See comment above. */
2073a571abcSchristos 			if (cnt == 0 && ISMOTION(vp)) {
2083a571abcSchristos 				if ((ISCMD(vp->rkp, 'd') ||
2093a571abcSchristos 				    ISCMD(vp->rkp, 'y')) &&
2103a571abcSchristos 				    cs_fspace(sp, &cs))
2113a571abcSchristos 					return (1);
2123a571abcSchristos 				break;
2133a571abcSchristos 			}
2143a571abcSchristos 
2153a571abcSchristos 			/* Eat whitespace characters. */
21608d478e3Schristos 			if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch))
2173a571abcSchristos 				if (cs_fblank(sp, &cs))
2183a571abcSchristos 					return (1);
2193a571abcSchristos 			if (cs.cs_flags == CS_EOF)
2203a571abcSchristos 				goto ret;
2213a571abcSchristos 		}
2223a571abcSchristos 
2233a571abcSchristos 	/*
2243a571abcSchristos 	 * If we didn't move, we must be at EOF.
2253a571abcSchristos 	 *
2263a571abcSchristos 	 * !!!
2273a571abcSchristos 	 * That's okay for motion commands, however.
2283a571abcSchristos 	 */
2293a571abcSchristos ret:	if (!ISMOTION(vp) &&
2303a571abcSchristos 	    cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) {
2313a571abcSchristos 		v_eof(sp, &vp->m_start);
2323a571abcSchristos 		return (1);
2333a571abcSchristos 	}
2343a571abcSchristos 
2353a571abcSchristos 	/* Adjust the end of the range for motion commands. */
2363a571abcSchristos 	vp->m_stop.lno = cs.cs_lno;
2373a571abcSchristos 	vp->m_stop.cno = cs.cs_cno;
2383a571abcSchristos 	if (ISMOTION(vp) && cs.cs_flags == 0)
2393a571abcSchristos 		--vp->m_stop.cno;
2403a571abcSchristos 
2413a571abcSchristos 	/*
2423a571abcSchristos 	 * Non-motion commands move to the end of the range.  Delete
2433a571abcSchristos 	 * and yank stay at the start, ignore others.
2443a571abcSchristos 	 */
2453a571abcSchristos 	vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
2463a571abcSchristos 	return (0);
2473a571abcSchristos }
2483a571abcSchristos 
2493a571abcSchristos /*
2503a571abcSchristos  * v_wordE -- [count]E
2513a571abcSchristos  *	Move forward to the end of the bigword.
2523a571abcSchristos  *
2533a571abcSchristos  * PUBLIC: int v_wordE __P((SCR *, VICMD *));
2543a571abcSchristos  */
2553a571abcSchristos int
v_wordE(SCR * sp,VICMD * vp)2563a571abcSchristos v_wordE(SCR *sp, VICMD *vp)
2573a571abcSchristos {
2583a571abcSchristos 	return (eword(sp, vp, BIGWORD));
2593a571abcSchristos }
2603a571abcSchristos 
2613a571abcSchristos /*
2623a571abcSchristos  * v_worde -- [count]e
2633a571abcSchristos  *	Move forward to the end of the word.
2643a571abcSchristos  *
2653a571abcSchristos  * PUBLIC: int v_worde __P((SCR *, VICMD *));
2663a571abcSchristos  */
2673a571abcSchristos int
v_worde(SCR * sp,VICMD * vp)2683a571abcSchristos v_worde(SCR *sp, VICMD *vp)
2693a571abcSchristos {
2703a571abcSchristos 	return (eword(sp, vp, LITTLEWORD));
2713a571abcSchristos }
2723a571abcSchristos 
2733a571abcSchristos /*
2743a571abcSchristos  * eword --
2753a571abcSchristos  *	Move forward to the end of the word.
2763a571abcSchristos  */
2773a571abcSchristos static int
eword(SCR * sp,VICMD * vp,enum which type)2783a571abcSchristos eword(SCR *sp, VICMD *vp, enum which type)
2793a571abcSchristos {
2803a571abcSchristos 	enum { INWORD, NOTWORD } state;
2813a571abcSchristos 	VCS cs;
2823a571abcSchristos 	u_long cnt;
283*943888a2Srin 	int nmw, omw;
2843a571abcSchristos 
2853a571abcSchristos 	cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
2863a571abcSchristos 	cs.cs_lno = vp->m_start.lno;
2873a571abcSchristos 	cs.cs_cno = vp->m_start.cno;
2883a571abcSchristos 	if (cs_init(sp, &cs))
2893a571abcSchristos 		return (1);
2903a571abcSchristos 
2913a571abcSchristos 	/*
2923a571abcSchristos 	 * !!!
2933a571abcSchristos 	 * If in whitespace, or the next character is whitespace, move past
2943a571abcSchristos 	 * it.  (This doesn't count as a word move.)  Stay at the character
2953a571abcSchristos 	 * past the current one, it sets word "state" for the 'e' command.
2963a571abcSchristos 	 */
29708d478e3Schristos 	if (cs.cs_flags == 0 && !ISBLANK2(cs.cs_ch)) {
2983a571abcSchristos 		if (cs_next(sp, &cs))
2993a571abcSchristos 			return (1);
30008d478e3Schristos 		if (cs.cs_flags == 0 && !ISBLANK2(cs.cs_ch))
3013a571abcSchristos 			goto start;
3023a571abcSchristos 	}
3033a571abcSchristos 	if (cs_fblank(sp, &cs))
3043a571abcSchristos 		return (1);
3053a571abcSchristos 
3063a571abcSchristos 	/*
3073a571abcSchristos 	 * Cyclically move to the next word -- this involves skipping
3083a571abcSchristos 	 * over word characters and then any trailing non-word characters.
3093a571abcSchristos 	 * Note, for the 'e' command, the definition of a word keeps
3103a571abcSchristos 	 * switching.
3113a571abcSchristos 	 */
3123a571abcSchristos start:	if (type == BIGWORD)
3133a571abcSchristos 		while (cnt--) {
314*943888a2Srin 			nmw = ISMULTIWIDTH(sp, cs.cs_ch);
3153a571abcSchristos 			for (;;) {
316*943888a2Srin 				omw = nmw;
3173a571abcSchristos 				if (cs_next(sp, &cs))
3183a571abcSchristos 					return (1);
3193a571abcSchristos 				if (cs.cs_flags == CS_EOF)
3203a571abcSchristos 					goto ret;
321*943888a2Srin 				if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) ||
322*943888a2Srin 				    (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw)
3233a571abcSchristos 					break;
3243a571abcSchristos 			}
3253a571abcSchristos 			/*
3263a571abcSchristos 			 * When we reach the start of the word after the last
3273a571abcSchristos 			 * word, we're done.  If we changed state, back up one
3283a571abcSchristos 			 * to the end of the previous word.
3293a571abcSchristos 			 */
3303a571abcSchristos 			if (cnt == 0) {
3313a571abcSchristos 				if (cs.cs_flags == 0 && cs_prev(sp, &cs))
3323a571abcSchristos 					return (1);
3333a571abcSchristos 				break;
3343a571abcSchristos 			}
3353a571abcSchristos 
3363a571abcSchristos 			/* Eat whitespace characters. */
337*943888a2Srin 			if (nmw == omw && cs_fblank(sp, &cs))
3383a571abcSchristos 				return (1);
3393a571abcSchristos 			if (cs.cs_flags == CS_EOF)
3403a571abcSchristos 				goto ret;
3413a571abcSchristos 		}
3423a571abcSchristos 	else
3433a571abcSchristos 		while (cnt--) {
3443a571abcSchristos 			state = cs.cs_flags == 0 &&
3453a571abcSchristos 			    inword(cs.cs_ch) ? INWORD : NOTWORD;
346*943888a2Srin 			nmw = ISMULTIWIDTH(sp, cs.cs_ch);
3473a571abcSchristos 			for (;;) {
348*943888a2Srin 				omw = nmw;
3493a571abcSchristos 				if (cs_next(sp, &cs))
3503a571abcSchristos 					return (1);
3513a571abcSchristos 				if (cs.cs_flags == CS_EOF)
3523a571abcSchristos 					goto ret;
353*943888a2Srin 				if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) ||
354*943888a2Srin 				    (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw)
3553a571abcSchristos 					break;
3563a571abcSchristos 				if (state == INWORD) {
3573a571abcSchristos 					if (!inword(cs.cs_ch))
3583a571abcSchristos 						break;
3593a571abcSchristos 				} else
3603a571abcSchristos 					if (inword(cs.cs_ch))
3613a571abcSchristos 						break;
3623a571abcSchristos 			}
3633a571abcSchristos 			/* See comment above. */
3643a571abcSchristos 			if (cnt == 0) {
3653a571abcSchristos 				if (cs.cs_flags == 0 && cs_prev(sp, &cs))
3663a571abcSchristos 					return (1);
3673a571abcSchristos 				break;
3683a571abcSchristos 			}
3693a571abcSchristos 
3703a571abcSchristos 			/* Eat whitespace characters. */
37108d478e3Schristos 			if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch))
3723a571abcSchristos 				if (cs_fblank(sp, &cs))
3733a571abcSchristos 					return (1);
3743a571abcSchristos 			if (cs.cs_flags == CS_EOF)
3753a571abcSchristos 				goto ret;
3763a571abcSchristos 		}
3773a571abcSchristos 
3783a571abcSchristos 	/*
3793a571abcSchristos 	 * If we didn't move, we must be at EOF.
3803a571abcSchristos 	 *
3813a571abcSchristos 	 * !!!
3823a571abcSchristos 	 * That's okay for motion commands, however.
3833a571abcSchristos 	 */
3843a571abcSchristos ret:	if (!ISMOTION(vp) &&
3853a571abcSchristos 	    cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) {
3863a571abcSchristos 		v_eof(sp, &vp->m_start);
3873a571abcSchristos 		return (1);
3883a571abcSchristos 	}
3893a571abcSchristos 
3903a571abcSchristos 	/* Set the end of the range for motion commands. */
3913a571abcSchristos 	vp->m_stop.lno = cs.cs_lno;
3923a571abcSchristos 	vp->m_stop.cno = cs.cs_cno;
3933a571abcSchristos 
3943a571abcSchristos 	/*
3953a571abcSchristos 	 * Non-motion commands move to the end of the range.
3963a571abcSchristos 	 * Delete and yank stay at the start, ignore others.
3973a571abcSchristos 	 */
3983a571abcSchristos 	vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
3993a571abcSchristos 	return (0);
4003a571abcSchristos }
4013a571abcSchristos 
4023a571abcSchristos /*
4033a571abcSchristos  * v_WordB -- [count]B
4043a571abcSchristos  *	Move backward a bigword at a time.
4053a571abcSchristos  *
4063a571abcSchristos  * PUBLIC: int v_wordB __P((SCR *, VICMD *));
4073a571abcSchristos  */
4083a571abcSchristos int
v_wordB(SCR * sp,VICMD * vp)4093a571abcSchristos v_wordB(SCR *sp, VICMD *vp)
4103a571abcSchristos {
4113a571abcSchristos 	return (bword(sp, vp, BIGWORD));
4123a571abcSchristos }
4133a571abcSchristos 
4143a571abcSchristos /*
4153a571abcSchristos  * v_wordb -- [count]b
4163a571abcSchristos  *	Move backward a word at a time.
4173a571abcSchristos  *
4183a571abcSchristos  * PUBLIC: int v_wordb __P((SCR *, VICMD *));
4193a571abcSchristos  */
4203a571abcSchristos int
v_wordb(SCR * sp,VICMD * vp)4213a571abcSchristos v_wordb(SCR *sp, VICMD *vp)
4223a571abcSchristos {
4233a571abcSchristos 	return (bword(sp, vp, LITTLEWORD));
4243a571abcSchristos }
4253a571abcSchristos 
4263a571abcSchristos /*
4273a571abcSchristos  * bword --
4283a571abcSchristos  *	Move backward by words.
4293a571abcSchristos  */
4303a571abcSchristos static int
bword(SCR * sp,VICMD * vp,enum which type)4313a571abcSchristos bword(SCR *sp, VICMD *vp, enum which type)
4323a571abcSchristos {
4333a571abcSchristos 	enum { INWORD, NOTWORD } state;
4343a571abcSchristos 	VCS cs;
4353a571abcSchristos 	u_long cnt;
436*943888a2Srin 	int nmw, omw;
4373a571abcSchristos 
4383a571abcSchristos 	cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
4393a571abcSchristos 	cs.cs_lno = vp->m_start.lno;
4403a571abcSchristos 	cs.cs_cno = vp->m_start.cno;
4413a571abcSchristos 	if (cs_init(sp, &cs))
4423a571abcSchristos 		return (1);
4433a571abcSchristos 
4443a571abcSchristos 	/*
4453a571abcSchristos 	 * !!!
4463a571abcSchristos 	 * If in whitespace, or the previous character is whitespace, move
4473a571abcSchristos 	 * past it.  (This doesn't count as a word move.)  Stay at the
4483a571abcSchristos 	 * character before the current one, it sets word "state" for the
4493a571abcSchristos 	 * 'b' command.
4503a571abcSchristos 	 */
45108d478e3Schristos 	if (cs.cs_flags == 0 && !ISBLANK2(cs.cs_ch)) {
4523a571abcSchristos 		if (cs_prev(sp, &cs))
4533a571abcSchristos 			return (1);
45408d478e3Schristos 		if (cs.cs_flags == 0 && !ISBLANK2(cs.cs_ch))
4553a571abcSchristos 			goto start;
4563a571abcSchristos 	}
4573a571abcSchristos 	if (cs_bblank(sp, &cs))
4583a571abcSchristos 		return (1);
4593a571abcSchristos 
4603a571abcSchristos 	/*
4613a571abcSchristos 	 * Cyclically move to the beginning of the previous word -- this
4623a571abcSchristos 	 * involves skipping over word characters and then any trailing
4633a571abcSchristos 	 * non-word characters.  Note, for the 'b' command, the definition
4643a571abcSchristos 	 * of a word keeps switching.
4653a571abcSchristos 	 */
4663a571abcSchristos start:	if (type == BIGWORD)
4673a571abcSchristos 		while (cnt--) {
468*943888a2Srin 			nmw = ISMULTIWIDTH(sp, cs.cs_ch);
4693a571abcSchristos 			for (;;) {
470*943888a2Srin 				omw = nmw;
4713a571abcSchristos 				if (cs_prev(sp, &cs))
4723a571abcSchristos 					return (1);
4733a571abcSchristos 				if (cs.cs_flags == CS_SOF)
4743a571abcSchristos 					goto ret;
475*943888a2Srin 				if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) ||
476*943888a2Srin 				    (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw)
4773a571abcSchristos 					break;
4783a571abcSchristos 			}
4793a571abcSchristos 			/*
4803a571abcSchristos 			 * When we reach the end of the word before the last
4813a571abcSchristos 			 * word, we're done.  If we changed state, move forward
4823a571abcSchristos 			 * one to the end of the next word.
4833a571abcSchristos 			 */
4843a571abcSchristos 			if (cnt == 0) {
4853a571abcSchristos 				if (cs.cs_flags == 0 && cs_next(sp, &cs))
4863a571abcSchristos 					return (1);
4873a571abcSchristos 				break;
4883a571abcSchristos 			}
4893a571abcSchristos 
4903a571abcSchristos 			/* Eat whitespace characters. */
491*943888a2Srin 			if (nmw == omw && cs_bblank(sp, &cs))
4923a571abcSchristos 				return (1);
4933a571abcSchristos 			if (cs.cs_flags == CS_SOF)
4943a571abcSchristos 				goto ret;
4953a571abcSchristos 		}
4963a571abcSchristos 	else
4973a571abcSchristos 		while (cnt--) {
4983a571abcSchristos 			state = cs.cs_flags == 0 &&
4993a571abcSchristos 			    inword(cs.cs_ch) ? INWORD : NOTWORD;
500*943888a2Srin 			nmw = ISMULTIWIDTH(sp, cs.cs_ch);
5013a571abcSchristos 			for (;;) {
502*943888a2Srin 				omw = nmw;
5033a571abcSchristos 				if (cs_prev(sp, &cs))
5043a571abcSchristos 					return (1);
5053a571abcSchristos 				if (cs.cs_flags == CS_SOF)
5063a571abcSchristos 					goto ret;
507*943888a2Srin 				if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) ||
508*943888a2Srin 				    (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw)
5093a571abcSchristos 					break;
5103a571abcSchristos 				if (state == INWORD) {
5113a571abcSchristos 					if (!inword(cs.cs_ch))
5123a571abcSchristos 						break;
5133a571abcSchristos 				} else
5143a571abcSchristos 					if (inword(cs.cs_ch))
5153a571abcSchristos 						break;
5163a571abcSchristos 			}
5173a571abcSchristos 			/* See comment above. */
5183a571abcSchristos 			if (cnt == 0) {
5193a571abcSchristos 				if (cs.cs_flags == 0 && cs_next(sp, &cs))
5203a571abcSchristos 					return (1);
5213a571abcSchristos 				break;
5223a571abcSchristos 			}
5233a571abcSchristos 
5243a571abcSchristos 			/* Eat whitespace characters. */
52508d478e3Schristos 			if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch))
5263a571abcSchristos 				if (cs_bblank(sp, &cs))
5273a571abcSchristos 					return (1);
5283a571abcSchristos 			if (cs.cs_flags == CS_SOF)
5293a571abcSchristos 				goto ret;
5303a571abcSchristos 		}
5313a571abcSchristos 
5323a571abcSchristos 	/* If we didn't move, we must be at SOF. */
5333a571abcSchristos ret:	if (cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) {
5343a571abcSchristos 		v_sof(sp, &vp->m_start);
5353a571abcSchristos 		return (1);
5363a571abcSchristos 	}
5373a571abcSchristos 
5383a571abcSchristos 	/* Set the end of the range for motion commands. */
5393a571abcSchristos 	vp->m_stop.lno = cs.cs_lno;
5403a571abcSchristos 	vp->m_stop.cno = cs.cs_cno;
5413a571abcSchristos 
5423a571abcSchristos 	/*
5433a571abcSchristos 	 * All commands move to the end of the range.  Motion commands
5443a571abcSchristos 	 * adjust the starting point to the character before the current
5453a571abcSchristos 	 * one.
5463a571abcSchristos 	 *
5473a571abcSchristos 	 * !!!
5483a571abcSchristos 	 * The historic vi didn't get this right -- the `yb' command yanked
5493a571abcSchristos 	 * the right stuff and even updated the cursor value, but the cursor
5503a571abcSchristos 	 * was not actually updated on the screen.
5513a571abcSchristos 	 */
5523a571abcSchristos 	vp->m_final = vp->m_stop;
5533a571abcSchristos 	if (ISMOTION(vp))
5543a571abcSchristos 		--vp->m_start.cno;
5553a571abcSchristos 	return (0);
5563a571abcSchristos }
557